Skip to content

Commit

Permalink
Merge pull request #4 from dmitriyKvant/feat/product-card
Browse files Browse the repository at this point in the history
Feat/product card
  • Loading branch information
kired01 authored Jun 17, 2024
2 parents 708222e + 13df5b3 commit d8b4cfc
Show file tree
Hide file tree
Showing 53 changed files with 683 additions and 677 deletions.
File renamed without changes.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
.turbo

.env
.yarn/unplugged
.yarn/unplugged
node_modules
264 changes: 0 additions & 264 deletions .pnp.cjs

Large diffs are not rendered by default.

Binary file modified .yarn/install-state.gz
Binary file not shown.
7 changes: 6 additions & 1 deletion apps/client/.env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# General
NODE_ENV=development

# Cms
NEXT_PUBLIC_CMS_API_URL=http://localhost:1337/api
CMS_UPLOADS_URL=http://localhost:1337/uploads
UPLOADS_PATH=/uploads
UPLOADS_ROUTE=/uploads

# Next Auth
NEXTAUTH_URL=http://localhost:3000
Expand All @@ -11,4 +14,6 @@ GOOGLE_CLIENT_SECRET=tobemodified

# Sentry
NEXT_PUBLIC_SENTRY_DSN=tobemodified
SENTRY_ORG=tobemodified
SENTRY_PROJECT=tobemodified
SENTRY_AUTH_TOKEN=tobemodified
2 changes: 1 addition & 1 deletion apps/client/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const config = {
async rewrites() {
return [
{
source: `${process.env.UPLOADS_PATH}/:path*`,
source: `${process.env.UPLOADS_ROUTE}/:path*`,
destination: `${process.env.CMS_UPLOADS_URL}/:path*`,
},
]
Expand Down
3 changes: 0 additions & 3 deletions apps/client/public/icons/arrow-left.svg

This file was deleted.

File renamed without changes
File renamed without changes
File renamed without changes
2 changes: 1 addition & 1 deletion apps/client/src/app/(public)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function PublicLayout({ children }: Readonly<{ children: React.Re
return (
<div className="relative flex min-h-screen flex-col">
<Header />
<main className="flex-grow">{children}</main>
<main className="grow">{children}</main>
<Footer />
</div>
)
Expand Down
5 changes: 3 additions & 2 deletions apps/client/src/app/(public)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { HydrationBoundary, QueryClient, dehydrate } from "@tanstack/react-query"
import { HydrationBoundary, dehydrate } from "@tanstack/react-query"

import { Catalog } from "@/widgets/catalog"

import { MainCarousel, getCarouselItems } from "@/entities/carousel-item"
import { getProducts } from "@/entities/product"

import { queryClient } from "@/shared/api"

export default async function HomePage() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ["carousel-items"],
queryFn: () => getCarouselItems(),
Expand Down
21 changes: 16 additions & 5 deletions apps/client/src/app/(public)/product/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { HydrationBoundary, dehydrate } from "@tanstack/react-query"

import { getProduct } from "@/entities/product"

import { queryClient } from "@/shared/api"

import { ProductDetailsCard } from "./ui/product-details-card"

export default async function ProductPage({ params }: { params: { slug: string } }) {
const res = await fetch(
`${process.env.NEXT_PUBLIC_CMS_API_URL}/products?populate=*&filters[slug]=${params.slug}`,
await queryClient.prefetchQuery({
queryKey: ["products", params.slug],
queryFn: () => getProduct(params.slug),
})
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<ProductDetailsCard slug={params.slug} />
</HydrationBoundary>
)
const data = await res.json()
console.log(data.data)
return <div>{params.slug}</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"use client"

import { Spacer } from "@nextui-org/spacer"

import { CartAddButton } from "@/features/cart"
import { SelectSize } from "@/features/select-size"
import { WhishlistAddButton } from "@/features/whishlist"

import { useProduct } from "@/entities/product"

import { ProductDetailsCarousel } from "./product-details-carousel"
import { ProductDetailsDescription } from "./product-details-description"
import { ProductDetailsInfo } from "./product-details-info"

type TypeProductDetailsCardProps = {
slug: string
}

export const ProductDetailsCard: React.FC<TypeProductDetailsCardProps> = ({ slug }) => {
const { isPending, data } = useProduct(slug)
if (isPending) {
return (
<div className="container flex justify-between pt-[160px] content-offset">
<ProductDetailsCarousel isLoaded={!isPending} />
<div>
<ProductDetailsInfo isLoaded={!isPending} />
<Spacer y={20} />
<SelectSize isLoaded={!isPending} />
<Spacer y={10} />
<CartAddButton isLoaded={!isPending} />
<Spacer y={3} />
<WhishlistAddButton isLoaded={!isPending} />
<Spacer y={10} />
<ProductDetailsDescription isLoaded={!isPending} />
</div>
</div>
)
}
return (
<div className="container flex justify-between pt-[160px] content-offset">
<div>
{data?.map((product) => (
<ProductDetailsCarousel
key={product.id}
{...product.attributes}
/>
))}
</div>
{data?.map((product) => (
<div key={product.id}>
<ProductDetailsInfo {...product.attributes} />
<Spacer y={20} />
<SelectSize {...product.attributes} />
<Spacer y={10} />
<CartAddButton />
<Spacer y={3} />
<WhishlistAddButton />
<Spacer y={10} />
{product.attributes.description && (
<ProductDetailsDescription description={product.attributes.description} />
)}
</div>
))}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Skeleton } from "@nextui-org/skeleton"
import Autoplay from "embla-carousel-autoplay"
import Image from "next/image"

import type { IProductAttributes } from "@/entities/product"

import { Carousel, CarouselContent, CarouselItem } from "@/shared/ui"

interface IProductDetailsCarouselProps extends Required<Pick<IProductAttributes, "images">> {
isLoaded?: boolean
}

interface IProductDetailsCarouselSkeletonProps extends Partial<Pick<IProductAttributes, "images">> {
isLoaded: boolean
}

export const ProductDetailsCarousel: React.FC<
IProductDetailsCarouselProps | IProductDetailsCarouselSkeletonProps
> = ({ images, isLoaded = true }) => {
return (
<Carousel
className="w-[537px]"
plugins={[
Autoplay({
delay: 3000,
stopOnMouseEnter: true,
stopOnInteraction: false,
}),
]}
opts={{
loop: true,
}}>
<Skeleton
isLoaded={isLoaded}
className="h-[671.25px]">
<CarouselContent className="ml-0">
{images?.data.map((image) => (
<CarouselItem key={image.id}>
<Image
src={image.attributes.url ?? ""}
alt={image.attributes.alternativeText ?? "Sneakers"}
width={537}
height={671.25}
/>
</CarouselItem>
))}
</CarouselContent>
</Skeleton>
</Carousel>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Skeleton } from "@nextui-org/skeleton"
import Markdown from "react-markdown"

import type { IProductAttributes } from "@/entities/product"

interface IProductDetailsDescriptionProps
extends Required<Pick<IProductAttributes, "description">> {
isLoaded?: boolean
}

interface IProductDetailsDescriptionSkeletonProps
extends Partial<Pick<IProductAttributes, "description">> {
isLoaded: boolean
}

export const ProductDetailsDescription: React.FC<
IProductDetailsDescriptionProps | IProductDetailsDescriptionSkeletonProps
> = ({ description, isLoaded = true }) => {
return (
<div>
<Skeleton
className="h-[28px] w-[150px]"
isLoaded={isLoaded}>
<h3 className="text-lg font-bold">Product Details</h3>
</Skeleton>
<Skeleton
className="mt-[20px] h-[280px] w-[408px]"
isLoaded={isLoaded}>
<Markdown className="markdown">{description}</Markdown>
</Skeleton>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Skeleton } from "@nextui-org/skeleton"

import type { IProductAttributes } from "@/entities/product"

import { findDiscountPercentage } from "@/shared/lib"

interface ProductDetailsInfoProps
extends Pick<IProductAttributes, "discountPrice" | "price" | "title" | "sex"> {
isLoaded?: boolean
}

interface ProductDetailsInfoSkeletonProps
extends Pick<Partial<IProductAttributes>, "discountPrice" | "price" | "title" | "sex"> {
isLoaded: boolean
}

export const ProductDetailsInfo: React.FC<
ProductDetailsInfoProps | ProductDetailsInfoSkeletonProps
> = ({ discountPrice, sex, price, title, isLoaded = true }) => {
return (
<div className="flex w-[408px] flex-col gap-y-[20px]">
<div className="font-semibold">
<Skeleton
isLoaded={isLoaded}
className="h-[45px] w-[330px]">
<h1 className="text-4xl">{title}</h1>
</Skeleton>
<Skeleton
isLoaded={isLoaded}
className="mt-[7.5px] h-[28px] w-[150px]">
<p className="text-lg">{sex === "man" ? "Men's Shoes" : "Women's Shoes"}</p>
</Skeleton>
</div>
<div>
<div className="flex justify-between">
<Skeleton
isLoaded={isLoaded}
className="h-[28px] w-[240px]">
<div className="flex items-center gap-x-[8px]">
<p className="text-lg font-semibold">MRP : ₹{discountPrice}</p>
<p className="line-through">{price}</p>
</div>
</Skeleton>
<Skeleton
className="h-[28px] w-[100px]"
isLoaded={isLoaded}>
{discountPrice && price && (
<p className="text-right font-medium text-green">
{findDiscountPercentage(price, discountPrice)}% off
</p>
)}
</Skeleton>
</div>
<Skeleton
isLoaded={isLoaded}
className="mt-[5px] w-full">
<div className="font-medium text-gray">
<p>incl. of taxes</p>
<p>(Also includes all applicable duties)</p>
</div>
</Skeleton>
</div>
</div>
)
}
16 changes: 3 additions & 13 deletions apps/client/src/app/providers/tanstack-provider/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
"use client"

import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { useState } from "react"
import { QueryClientProvider } from "@tanstack/react-query"

export const TanstackProvider = ({ children }: { children: React.ReactNode }) => {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
},
},
}),
)
import { queryClient } from "@/shared/api"

export const TanstackProvider = ({ children }: { children: React.ReactNode }) => {
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
}
3 changes: 3 additions & 0 deletions apps/client/src/app/styles/components.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
@tailwind components;

@layer components {
.markdown ul {
@apply mt-[20px] flex flex-col gap-y-[10px];
}
}
2 changes: 1 addition & 1 deletion apps/client/src/entities/carousel-item/api/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { http } from "@/shared/api"
import type { ICarouselResponse } from "../model"

const carouselItemsQueries: Record<string, string> = {
populate: "preview",
populate: "preview,product",
sort: "id:asc",
"filters[product][$notNull]": "true",
}
Expand Down
10 changes: 7 additions & 3 deletions apps/client/src/entities/carousel-item/model/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import type { IProductData } from "@/entities/product"

import type { ICmsAttributes, ICmsImage, ICmsMeta } from "@/shared/api"
import type { ICmsAttributes, ICmsImageData, ICmsMeta } from "@/shared/api"

export interface ICarouselAttributes extends ICmsAttributes {
preview: ICmsImage
product: IProductData
preview: {
data: ICmsImageData
}
product: {
data: IProductData
}
}

export interface ICarouselData {
Expand Down
3 changes: 2 additions & 1 deletion apps/client/src/entities/carousel-item/ui/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Autoplay from "embla-carousel-autoplay"
import Image from "next/image"
import NextLink from "next/link"

import { ROUTE } from "@/shared/config"
import {
Carousel,
CarouselContent,
Expand Down Expand Up @@ -49,7 +50,7 @@ export const MainCarousel: React.FC = () => {
/>
<Link
as={NextLink}
href={"/"}
href={`${ROUTE.PRODUCT}/${carouselItem.attributes.product.data.attributes.slug}`}
className="absolute bottom-[75px] z-20 bg-white px-[40px] py-[25px] font-oswald text-3xl font-medium uppercase">
Shop Now
</Link>
Expand Down
2 changes: 1 addition & 1 deletion apps/client/src/entities/product/api/get-product.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { http } from "@/shared/api"
import type { IProductResponse } from "../model"

const productQueries: Record<string, string> = {
populate: "picture",
populate: "preview,images",
sort: "id:asc",
"pagination[pageSize]": "12",
}
Expand Down
Loading

0 comments on commit d8b4cfc

Please sign in to comment.