Commit b9563406 by Sakilesh J

store

parent d9464e19
...@@ -27,6 +27,7 @@ yarn-error.log* ...@@ -27,6 +27,7 @@ yarn-error.log*
# local env files # local env files
.env*.local .env*.local
.env
# vercel # vercel
.vercel .vercel
......
import { Billboard } from "@/types";
const URL = `${process.env.NEXT_PUBLIC_API_URL}/billboards`;
const getBillboards = async (id: string): Promise<Billboard> => {
const res = await fetch(`${URL}/${id}`)
return res.json()
}
export default getBillboards;
\ No newline at end of file
import { Category } from "@/types";
const URL = `${process.env.NEXT_PUBLIC_API_URL}/categories`;
const getCategory = async (id: string): Promise<Category> => {
const res = await fetch(`${URL}/${id}`)
return res.json()
}
export default getCategory;
\ No newline at end of file
import { Category } from "@/types"
const URL = `${process.env.NEXT_PUBLIC_API_URL}/categories`
const getCategories = async (): Promise<Category[]> => {
const res = await fetch(URL)
return res.json()
}
export default getCategories
\ No newline at end of file
import { Color } from "@/types";
const URL = `${process.env.NEXT_PUBLIC_API_URL}/colors`;
const getSizes = async (): Promise<Color[]> => {
const res = await fetch(`${URL}`)
return res.json()
}
export default getSizes;
\ No newline at end of file
import { Billboard } from "@/types";
const URL = `${process.env.NEXT_PUBLIC_API_URL}/products`;
const getProduct = async (id: string): Promise<Billboard> => {
const res = await fetch(`${URL}/${id}`)
return res.json()
}
export default getProduct;
\ No newline at end of file
import { product } from "@/types";
import qs from "query-string"
const URL = `${process.env.NEXT_PUBLIC_API_URL}/products`
interface Query {
categoryId?: string;
colorId?: string;
sizeId?: string;
isFeatured?: boolean;
}
const getProducts = async (query: Query): Promise<product[]> => {
console.log(query)
const url = qs.stringifyUrl({
url: URL,
query: {
categoryId: query.categoryId,
colorId: query.colorId,
sizeId: query.sizeId,
isFeatured: query.isFeatured,
}
})
const res = await fetch(url)
return res.json()
}
export default getProducts
\ No newline at end of file
import { Size } from "@/types";
const URL = `${process.env.NEXT_PUBLIC_API_URL}/sizes`;
const getSizes = async (): Promise<Size[]> => {
const res = await fetch(`${URL}`)
return res.json()
}
export default getSizes;
\ No newline at end of file
import IconButton from "@/components/ui/icon-button";
import useCartModel from "@/hooks/ue-carts";
import { product } from "@/types";
import { X } from "lucide-react";
import Image from "next/image";
interface CartItemProps {
data: product
}
const CartItem: React.FC<CartItemProps> = ({ data }) => {
const cart = useCartModel()
const handleDelete = () => {
cart.removeItem(data)
}
return (<li className="flex py-6 border-b w-full">
<div className="relative h-24 w-24 rounded-md oveflow-hidden sm:h-48 sm:w-48">
<Image
fill
src={data.Image[0].url}
className=" object-cover object-center"
alt=""
/>
<div className="relative ml-4 flex flex-1 flex-col justify-between md:ml-6">
<div className="absolute z-10 right-[-10px] top-[-10px]">
<IconButton icon={<X size={20} />} onClick={handleDelete} />
</div>
</div>
</div>
<div className="mx-6 flex-1 flex gap-x-6 justify-evenly">
<div className="flex justify-between">
<p className="text-lg font-semibold text-black">
{data.name}
</p>
</div>
<div className="mt-1 flex text-sm">
<p className="text-gray-500">
{data.colors.name}
</p>
<p className="text-gray-500 ml-2 border-l border-gray-200 pl-4 h-[min-content]">
{data.sizes.name}
</p>
</div>
</div>
</li>);
}
export default CartItem;
\ No newline at end of file
"use client"
import { Container } from "@/components/ui/Container";
import useCartModel from "@/hooks/ue-carts";
import CartItem from "./components/cart-item";
import IconButton from "@/components/ui/icon-button";
import { Trash } from "lucide-react";
import SummeryComponent from "@/components/summary";
const CartPage = () => {
const cart = useCartModel();
return (
<div className="bg-white">
<Container>
<div className="px-4 py-16 sm:px-6 w-full lg:px-8">
<div className="flex justify-between">
<h1 className="text-3xl font-bold text-black">Shopping Cart</h1>
<div className="float-right">
<IconButton icon={<Trash />} onClick={cart.removeAll} />
</div>
</div>
<div className="mt-12 lg:grid lg:grid-cols-12 lg:items-start lg:gap-x-3 lg:w-full">
<div className="lg:col-span-7">
{
cart.item.length === 0
&& <p className="text-neutral-500">No items is added</p>
}
<ul className="w-full">
{
cart.item.map((item) => {
return (<CartItem data={item} key={item.id} />)
})
}
</ul>
</div>
<SummeryComponent />
</div>
</div>
</Container>
</div>
);
}
export default CartPage;
\ No newline at end of file
"use client"
import Button from "@/components/ui/Button";
import { cn } from "@/lib/utils";
import { Color, Size } from "@/types";
import { useSearchParams } from "next/navigation";
import { useRouter } from "next/navigation";
import qs from "query-string"
interface FilterProps {
data: (Size | Color)[]
name: string
valueKey: string
}
export const Filter = ({ data, name, valueKey }: FilterProps) => {
const searchParams = useSearchParams()
const router = useRouter();
const selectedValue = searchParams.get(valueKey);
const onClick = (id: string) => {
const current = qs.parse(searchParams.toString());
const query = {
...current,
[valueKey]: id
}
if (current[valueKey] === id) {
query[valueKey] = null;
}
const url = qs.stringifyUrl({
url: window.location.href,
query
}, { skipNull: true })
router.push(url)
}
return (
<div className="mb-8">
<h3 className="text-lg font-semibold">
{
name
}
<hr className="my-4" />
<div className="flex flex-wrap gap-2">
{
data.map((item) => (
<div key={item.id} className="flex items-center">
<Button className={
cn('rounded-md text-sm text-gray-800 bg-white border border-gray-300', selectedValue === item.id ? 'bg-black text-white' : '')
} onClick={() => onClick(item.id)}>
{item.name}
</Button>
</div>
))
}
</div>
</h3>
</div>
);
}
\ No newline at end of file
"use client"
import Button from "@/components/ui/Button";
import IconButton from "@/components/ui/icon-button";
import { Color, Size } from "@/types";
import { Dialog } from "@headlessui/react";
import { Plus, X } from "lucide-react";
import React, { useState } from "react";
import { Filter } from "./Filter";
interface MobileFilterProps {
sizes: Size[]
colors: Color[]
}
const MobileFilter: React.FC<MobileFilterProps> = ({ sizes, colors }) => {
const [open, setopen] = useState(false)
const onOpen = () => setopen(true)
const onCLose = () => setopen(false)
return (
<div className="lg:hidden">
<Button onClick={onOpen} className="flex gap-x-2 items-center">
Filter
<Plus size={20} />
</Button>
<Dialog open={open} as="div" className={'relative z-40'} onClose={onCLose}>
<div className="fixed inset-0 bg-black bg-opacity-25">
<Dialog.Panel className={'relative ml-auto flex h-full w-full max-w-xs flex-col overflow-y-auto bg-white py-4 pb-6 shadow-xl'}>
<div className="flex items-center justify-end px-4">
<IconButton icon={<X size={20} onClick={onCLose} />} />
</div>
<div className="p-4">
<Filter name="size" valueKey="sizeId" data={sizes} />
<Filter name="Color" valueKey="colorId" data={colors} />
</div>
</Dialog.Panel>
</div>
</Dialog>
</div>
);
}
export default MobileFilter;
\ No newline at end of file
import getColors from "@/actions/get-colors";
import getProducts from "@/actions/get-products";
import getSizes from "@/actions/get-sizes";
import Billboards from "@/components/Billboard";
import { Container } from "@/components/ui/Container";
import { Filter } from "./Components/Filter";
import NoResult from "@/components/ui/no-results";
import getCategory from "@/actions/get-categories";
import ProductCard from "@/components/ui/product-card";
import MobileFilter from "./Components/MobileFilter";
interface CategoryProps {
params: {
categoryId: string
},
searchParams: {
colorId: string;
sizeId: string;
}
}
export const revalidate = 0;
const Category = async ({
params, searchParams
}: CategoryProps) => {
const products = await getProducts({
categoryId: params.categoryId,
colorId: searchParams.colorId,
sizeId: searchParams.sizeId,
});
const sizes = await getSizes();
const colors = await getColors();
const category = await getCategory(params.categoryId);
return (
<div className="bg-white">
<Container>
<Billboards data={category.Billboard} />
<div className="px-4 sm:px-6 lg:px-8 pb-24">
<div className="lg:grid lg:grid-cols-5 lg:gap-x-8">
<MobileFilter sizes={sizes} colors={colors} />
<div className="hidden lg:block">
<Filter name="size" valueKey="sizeId" data={sizes} />
<Filter name="Color" valueKey="colorId" data={colors} />
</div>
<div className="mt-6 lg:col-span-4 lg:mt-0">
{
products.length === 0 && <NoResult />
}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{
products.map((product) => {
return <ProductCard key={product.id} data={product} />
})
}
</div>
</div>
</div>
</div>
</Container>
</div>);
}
export default Category;
\ No newline at end of file
import getBillboards from "@/actions/get-billboard";
import getProducts from "@/actions/get-products";
import Billboards from "@/components/Billboard";
import { ProductList } from "@/components/product-list";
import { Container } from "@/components/ui/Container";
import { Billboard } from "@/types";
export const revalidate = 0;
const Home = async () => {
const products = await getProducts({ isFeatured: true });
const billboard: Billboard = await getBillboards('97ecc8b3-6e40-45f0-9ee5-e87e5d9c6f9e');
return (
<Container>
<div className="space-y-10 pb-10">
<Billboards data={billboard} />
</div>
<div className="flex flex-col gap-y-8 px-4 sm:px-6 lg:px-8">
<ProductList title="featured Products" items={products} />
</div>
</Container>
);
}
export default Home;
\ No newline at end of file
const PaymentLayout = ({
children
}: {
children: React.ReactNode;
}) => {
return (<div className="w-full h-full">
{children}
</div>);
}
export default PaymentLayout;
\ No newline at end of file
const PaymentPage = () => {
return (<>PAyment</>);
}
export default PaymentPage;
\ No newline at end of file
import getProduct from "@/actions/get-product";
import getProducts from "@/actions/get-products";
import Gallery from "@/components/gallery/index";
import InfoComponent from "@/components/Info";
import { ProductList } from "@/components/product-list";
import { Container } from "@/components/ui/Container";
import { product } from "@/types";
interface ProductPageProps {
params: {
productId: string
}
}
const ProductPage = async ({
params
}: ProductPageProps) => {
const product: product = await getProduct(params.productId);
const suggestProducts = await getProducts({
categoryId: product?.category?.id,
})
return (<div className="bg-white">
<Container>
<div className="px-4 py-6 sm:px-6 lg:px-8">
<div className="lg:grid lg:grid-cols-2 lg:items-start lg:gap-x-8">
<Gallery images={product.Image} />
<div className="mt-10 px-4 sm:mt-16 sm:px-0 lg:mt-0">
<InfoComponent data={product} />
</div>
</div>
<hr className="mt-10" />
<ProductList title="Related Items" items={suggestProducts} />
</div>
</Container>
</div>);
}
export default ProductPage;
\ No newline at end of file
...@@ -2,32 +2,9 @@ ...@@ -2,32 +2,9 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
body { html,
color: rgb(var(--foreground-rgb)); body,
background: linear-gradient( :root {
to bottom, height: 100%;
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
} }
\ No newline at end of file
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Inter } from "next/font/google"; import { Inter, Urbanist } from "next/font/google";
import "./globals.css"; import "./globals.css";
import Footer from "@/components/Footer";
import { Navbar } from "@/components/Navbar";
import ModelProvider from "@/provider/model-provider";
import ToastProvider from "@/provider/toast-provide";
const inter = Inter({ subsets: ["latin"] }); const font = Urbanist({ subsets: ["latin"] });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App", title: "Store",
description: "Generated by create next app", description: "Store",
}; };
export default function RootLayout({ export default function RootLayout({
...@@ -16,7 +20,13 @@ export default function RootLayout({ ...@@ -16,7 +20,13 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html lang="en"> <html lang="en">
<body className={inter.className}>{children}</body> <body className={font.className}>
<Navbar />
<ModelProvider />
<ToastProvider />
{children}
<Footer />
</body>
</html> </html>
); );
} }
import Image from "next/image";
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="z-10 w-full max-w-5xl items-center justify-between font-mono text-sm lg:flex">
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
Get started by editing&nbsp;
<code className="font-mono font-bold">app/page.tsx</code>
</p>
<div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:size-auto lg:bg-none">
<a
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
By{" "}
<Image
src="/vercel.svg"
alt="Vercel Logo"
className="dark:invert"
width={100}
height={24}
priority
/>
</a>
</div>
</div>
<div className="relative z-[-1] flex place-items-center before:absolute before:h-[300px] before:w-full before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-full after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 sm:before:w-[480px] sm:after:w-[240px] before:lg:h-[360px]">
<Image
className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
src="/next.svg"
alt="Next.js Logo"
width={180}
height={37}
priority
/>
</div>
<div className="mb-32 grid text-center lg:mb-0 lg:w-full lg:max-w-5xl lg:grid-cols-4 lg:text-left">
<a
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className="mb-3 text-2xl font-semibold">
Docs{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className="m-0 max-w-[30ch] text-sm opacity-50">
Find in-depth information about Next.js features and API.
</p>
</a>
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className="mb-3 text-2xl font-semibold">
Learn{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className="m-0 max-w-[30ch] text-sm opacity-50">
Learn about Next.js in an interactive course with&nbsp;quizzes!
</p>
</a>
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className="mb-3 text-2xl font-semibold">
Templates{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className="m-0 max-w-[30ch] text-sm opacity-50">
Explore starter templates for Next.js.
</p>
</a>
<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className="mb-3 text-2xl font-semibold">
Deploy{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className="m-0 max-w-[30ch] text-balance text-sm opacity-50">
Instantly deploy your Next.js site to a shareable URL with Vercel.
</p>
</a>
</div>
</main>
);
}
import React from 'react'
import { Billboard } from '@/types'
interface billboardsProps {
data: Billboard
}
const Billboards: React.FC<billboardsProps> = ({ data }) => {
return (
<div className='p-4 sm:p-6 lg:p-8 rounded-xl overflow-hidden'>
<div style={{
backgroundImage: `url("${data?.imageURL}")`
}} className='rounded-xl relative aspect-square md:aspect-[2.4/1] overflow-hidden bg-cover'>
<div className='w-full h-full flex flex-col justify-center items-center text-center gap-y-8'>
<div className='font-bold text-2xl sm:text-5xl lg:text-6xl sm:max-w-xl max-w-xs'>
{data.label}
</div>
</div>
</div>
</div>
)
}
export default Billboards
\ No newline at end of file
const Footer = () => {
return (
<footer className="bg-white border-t">
<div className="mx-auto py-10">
<p className="text-center text-xs text-black">
&copy; 2023 FakeStoreName, Inc.All rights reserved
</p>
</div>
</footer>
);
}
export default Footer;
\ No newline at end of file
const Gallery = () => {
return (<>Gallery</>);
}
export default Gallery;
\ No newline at end of file
"use client"
import { product } from "@/types";
import React from "react";
import Currency from "@/components/ui/Currency";
import Button from "@/components/ui/Button";
import { ShoppingCart } from "lucide-react";
import useCartModel from "@/hooks/ue-carts";
interface InfoProps {
data: product;
}
const InfoComponent: React.FC<InfoProps> = ({ data }) => {
const cart = useCartModel();
const handleCart = () => {
cart.addItem(data);
}
return (
<div>
<h1 className="text-3xl font-bold text-gray-900">{data.name}</h1>
<div className="mt-3 items-end justify-between">
<p className="text-2xl text-gray-900">
<Currency value={data?.price} />
</p>
</div>
<hr className="my-4" />
<div className="flex items-center gap-x-4 ">
<h1 className="font-semibold text-black">size:</h1>
<div>
{data?.sizes?.name}({data?.sizes?.value})
</div>
</div>
<div className="flex items-center gap-x-4 mt-4">
<h1 className="font-semibold text-black">color:</h1>
<div className="h-6 w-6 rounded-full border-gray-600" style={{
backgroundColor: data?.colors?.value,
}} />
</div>
<div className="mt-10 flex items-center gap-x-3">
<Button className="flex items-center gap-x-4" onClick={handleCart}>
Add To cart
<ShoppingCart className="h-4 w-4" />
</Button>
</div>
</div>
);
}
export default InfoComponent;
\ No newline at end of file
import { Container } from "@/components/ui/Container";
import Link from "next/link";
import { MainNav } from "./main-nav";
import getCategories from "@/actions/get-category";
import NavbarActions from "./navbar-actions";
export const revalidate = 0;
export const Navbar = async () => {
const categories = await getCategories()
return (
<div>
<Container>
<div className="relatice px-4 sm:px-6 lg:px-8 flex h-16 items-center">
<Link href="/" className="ml-4 flex lg:ml-0 gap-x-2">
<p className="font-bold text-xl uppercase">Store</p>
</Link>
<MainNav data={categories} />
<NavbarActions />
</div>
</Container>
</div>
);
}
\ No newline at end of file
import Image from "next/image";
import { Tab } from "@headlessui/react";
import { cn } from "@/lib/utils";
import { Image as Img } from "@/types";
interface GalleryTabProps {
image: Img
}
const GalleryTab: React.FC<GalleryTabProps> = ({ image }) => {
return (
<Tab className="relative flex aspect-square rounded-md cursor-pointer items-center justify-center">
{({ selected }) => {
return (
<div>
<span className="absolute h-full w-full aspect-square inset-0 overflow-hidden rounded-md">
<Image src={image.url} fill alt="" className="object-cover
object-center"/>
</span>
<span className={cn(`absolute inset-0 rounded-md ring-offset-2`, selected ? "ring-black" : "ring-transparent")} />
</div>
)
}}
</Tab>);
}
export default GalleryTab;
\ No newline at end of file
"use client";
import { Tab } from "@headlessui/react";
import { Image as ImgType } from "@/types"
import React from "react";
import GalleryTab from "./Tabs";
import Image from "next/image";
interface ImgProps {
images: ImgType[]
}
const Gallery: React.FC<ImgProps> = ({ images }) => {
return (
<Tab.Group as={"div"} className="flex flex-col-reverse gap-y-4">
<div className="mx-auto w-full max-w-2xl sm:block lg:max-w-none">
<Tab.List className="grid grid-cols-4 gap-6">
{
images.map((image) => {
return <GalleryTab key={image.id} image={image} />
})
}
</Tab.List>
</div>
<Tab.Panels className={"aspect-square w-full"}>
{
images.map((image) => {
return <Tab.Panel key={image.id}>
<div className="aspect-square relative h-full w-full sm:rounded-lg overflow-hidden">
<Image fill src={image.url} alt={" "} className={'object-cover object-center'} />
</div>
</Tab.Panel>
})
}
</Tab.Panels>
</Tab.Group>
);
}
export default Gallery;
\ No newline at end of file
"use client";
import { cn } from "@/lib/utils";
import { Category } from "@/types";
import Link from "next/link";
import { usePathname } from "next/navigation";
type Props = {
data: Category[];
}
export const MainNav = ({ data }: Props) => {
const pathname = usePathname();
const route = data.map((route) => ({
href: `/category/${route.id}`,
label: route.name,
active: pathname === `/category/${route.id}`
}))
return (
<nav className="mx-6 flex items-center space-x-4 lg:space-x-6">
{
route.map((route) => (
<Link href={route.href}
key={route.href}
className={cn('text-sm font-medium transition-color hover:text-black', route.active ? 'text-black' : 'text-neutral-500')}
>{route.label}</Link>
))
}
</nav>
);
}
\ No newline at end of file
"use client"
import Button from "@/components/ui/Button";
import useCartModel from "@/hooks/ue-carts";
import { ShoppingBag } from "lucide-react";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
const NavbarActions = () => {
const [isMounted, setisMounted] = useState(false)
const route = useRouter()
const cart = useCartModel()
useEffect(() => {
setisMounted(true)
}, [])
if (!isMounted) {
return null
}
const redirect = () => {
route.push('/cart')
}
return (<div className="ml-auto flex flex-row items-center gap-x-4">
<Button className="flex items-center rounded-full bg-black px-4 py-2" onClick={redirect}>
<ShoppingBag size={20} color="white" />
<span className="ml-2 text-sm font-medium text-white">
{cart.item.length}
</span>
</Button>
</div>);
}
export default NavbarActions;
\ No newline at end of file
"use client"
import usePreviewModel from "@/hooks/use-preview";
import { Model } from "./ui/model";
import Gallery from "./gallery/index";
import InfoComponent from "./Info";
const PreviewModel = () => {
const previewModel = usePreviewModel()
const product = usePreviewModel((state) => state.data)
if (!product) {
return null
}
return (
<Model open={previewModel.isOpen} onClose={previewModel.onClose}>
<div className="grid w-full grid-cols-1 items-start gap-x-6 gap-y-8 sm:grid-cols-12 lg:gap-x-8">
<div className="sm:col-span-4 lg:col-span-5">
<Gallery images={product.Image} />
</div>
<div className="sm:col-span-8 lg:col-span-7">
<InfoComponent data={product} />
</div>
</div>
</Model>
);
}
export default PreviewModel;
\ No newline at end of file
import { product } from "@/types";
import NoResult from "./ui/no-results";
import ProductCard from "./ui/product-card";
type ProductListProps = {
title: string;
items: product[];
}
export const ProductList = ({ title, items }: ProductListProps) => {
return (
<div className="space-y-4 mb-3">
<h3 className="font-bold text-3xl uppercase">{title}</h3>
{
items.length === 0 && <NoResult />
}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grrid-cols-4 gap-4">
{
items.map((item) => {
return (<ProductCard key={item.id} data={item} />)
})
}
</div>
</div>
);
}
\ No newline at end of file
"use client";
import useCartModel from "@/hooks/ue-carts";
import Currency from "./ui/Currency";
import Button from "./ui/Button";
import toast from "react-hot-toast";
import { useEffect } from "react";
import { useSearchParams, useRouter } from "next/navigation";
const SummeryComponent = () => {
const router = useRouter();
const searchparams = useSearchParams()
const cart = useCartModel()
useEffect(() => {
if (searchparams.get('success')) {
toast.success("Payment Completed!")
cart.removeAll()
}
if (searchparams.get('cancelled')) {
toast.error("Payment Failed!")
}
}, [searchparams, cart.removeAll])
const onCheckout = () => {
router.push("/payment")
}
const totalamt = cart.item.reduce((acc, item) => (acc + Number(item.price)), 0)
return (
<div className="lg:col-span-5 rounded-md
bg-gray-50 p-4 md:p-6 lg:p-8 w-full">
<h3 className="font-semibold text-2xl border-b pb-2">
Order summary
</h3>
<div className="py-3 my-3">
<Currency value={totalamt} />
</div>
<Button className="w-full my-2" onClick={onCheckout}>
Continue to checkout
</Button>
</div>);
}
export default SummeryComponent;
\ No newline at end of file
import { cn } from "@/lib/utils";
import React, { ForwardedRef, forwardRef } from "react";
export interface ButtonPops extends React.ButtonHTMLAttributes<HTMLButtonElement> {
}
const Button = forwardRef<HTMLButtonElement, ButtonPops>(({
className,
children,
disabled,
type = "button",
...props
}, ref) => {
return (
<button {...props} ref={ref} className={cn(` w-auto rounded-full
px-5 py-3 disabled:cursor-not-allowed text-white font-semibold
hover:opacity-75 transition bg-black border-transparent`, className)}>
{children}
</button>
)
})
Button.displayName = "Button"
export default Button;
\ No newline at end of file
type ContainerProps = {
children: React.ReactNode
}
export const Container = ({ children }: ContainerProps) => {
return (
<div className="mx-auto max-w-7xl">
{children}
</div>
);
}
\ No newline at end of file
"use client";
import React, { useEffect, useState } from "react";
const formatter = new Intl.NumberFormat("en-us", {
style: "currency",
currency: 'INR',
})
interface CurrencyProps {
value?: string | number;
}
const Currency: React.FC<CurrencyProps> = ({
value
}) => {
const [ismounted, setismounted] = useState(false)
useEffect(() => {
setismounted(true)
}, [])
if (!ismounted) {
return null;
}
return (<div className="font-semibold">
{
formatter.format(Number(value))
}
</div>);
}
export default Currency;
\ No newline at end of file
import { cn } from "@/lib/utils";
import React, { MouseEventHandler } from "react";
interface IconButtonProps {
onClick?: MouseEventHandler<HTMLButtonElement> | undefined;
icon: React.ReactElement;
className?: string
}
const IconButton: React.FC<IconButtonProps> = ({ className, onClick, icon }) => {
return (
<button
onClick={onClick}
className={cn("rounded-full flex items-center bg-white border shadow-md p-2 hover:scale-110 transition", className)}
>
{icon}
</button>
);
}
export default IconButton;
\ No newline at end of file
"use client"
import { Dialog, Transition } from "@headlessui/react";
import React, { Fragment } from "react";
import IconButton from "./icon-button";
import { X } from "lucide-react";
interface ModelProps {
open: boolean;
onClose: () => void;
children: React.ReactNode;
}
export const Model: React.FC<ModelProps> = ({ open, onClose, children }) => {
return (
<Transition show={open} as={Fragment}>
<Dialog as="div" className={`relative z-10 `} onClose={onClose}>
<div className="fixed inset-0 bg-black bg-opacity-50" />
<div className="inset-0 fixed overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className={'w-full max-w-3xl overflow-hidden rounded-lg text-left align-middle'}>
<div className="relative flex w-full items-center overflow-hidden bg-white px-4 pb-8 pt-14 shadow-2xl sm:px-6 sm:pt-8 md:p-6 lg:p-8">
<div className="absolute right-4 top-4">
<IconButton icon={<X size={20} />} onClick={onClose} />
</div>
{children}
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
);
}
\ No newline at end of file
const NoResult = () => {
return (<div className="flex items-center
justify-center h-full w-full text-neutral-500">No results Found</div>);
}
export default NoResult;
\ No newline at end of file
"use client";
import { product } from "@/types";
import Image from "next/image";
import React, { MouseEventHandler } from "react";
import IconButton from "./icon-button";
import { Expand, ShoppingCart } from "lucide-react";
import Currency from "./Currency";
import { useRouter } from "next/navigation";
import usePreviewModel from "@/hooks/use-preview";
import useCartModel from "@/hooks/ue-carts";
interface ProductCardProps {
data: product
}
const ProductCard: React.FC<ProductCardProps> = ({ data }) => {
const previewmodel = usePreviewModel();
const cart = useCartModel();
const router = useRouter();
const handleClick = () => {
router.push(`/product/${data?.id}`)
}
const handleModel: MouseEventHandler<HTMLButtonElement> = (e) => {
e.stopPropagation();
previewmodel.onOpen(data)
}
const addItem: MouseEventHandler<HTMLButtonElement> = (e) => {
e.stopPropagation();
cart.addItem(data)
}
return (
<div onClick={handleClick} className="bg-whte group cursor-pointer rounded-xl border p-3 space-y-4">
<div className="aspect-square rounded-xl bg-gray-100 relative">
<Image src={data?.Image?.[0].url} fill alt="*produt Image" className="aspect-square object-cover rounded-md " />
<div className="opacity-0 group-hover:opacity-100
transition absolute w-full px-6 bottom-5">
<div className="flex gap-x-6 justify-center">
<IconButton onClick={handleModel} icon={<Expand size={20} className="text-gray-600" />} />
<IconButton onClick={addItem} icon={<ShoppingCart size={20} className="text-gray-600" />} />
</div>
</div>
</div>
<div>
<p className="font-semibold text-lg capitalize">
{
data.name
}
</p>
<p className="text-gray-500 text-sm capitalize">
{
data.category.name
}
</p>
</div>
<div className="flex items-center justify-between">
<Currency value={data.price} />
</div>
</div>);
}
export default ProductCard;
\ No newline at end of file
import { create } from "zustand";
import { product } from "@/types";
import { createJSONStorage, persist } from "zustand/middleware";
import toast from "react-hot-toast";
interface CartModel {
item: product[]
addItem: (data: product) => void
removeItem: (data: product) => void
removeAll: () => void
}
const useCartModel = create(persist<CartModel>((set, get) => ({
item: [],
addItem: (data) => {
const isexist = get().item.find(item => item.id === data.id)
if (isexist) {
return toast.success("Item is already Exists")
}
set((state) => ({
item: [...state.item, data]
}))
toast.success("Item is Added")
},
removeItem: (data) => {
set((state) => ({
item: state.item.filter((item) => item.id !== data.id)
}))
toast.success("Item is Removed")
},
removeAll: () => {
set((state) => ({
item: []
}))
toast.success("All Items are Removed")
}
}), {
name: "cart-Storage",
storage: createJSONStorage(() => localStorage)
}))
export default useCartModel;
\ No newline at end of file
import { create } from "zustand";
import { product } from "@/types";
interface PreviewModelProps {
isOpen: boolean
data?: product
onOpen: (data: product) => void
onClose: () => void
}
const usePreviewModel = create<PreviewModelProps>((set) => (
{
isOpen: false,
onOpen: (data: product) => {
set({
data, isOpen: true
})
},
onClose: () => { set({ isOpen: false }) }
})
)
export default usePreviewModel;
\ No newline at end of file
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
\ No newline at end of file
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = {}; const nextConfig = {
images: {
domains: [
"res.cloudinary.com"
]
}
};
export default nextConfig; export default nextConfig;
...@@ -9,18 +9,25 @@ ...@@ -9,18 +9,25 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@headlessui/react": "^1.7.19",
"clsx": "^2.1.0",
"lucide-react": "^0.368.0",
"next": "14.2.1",
"query-string": "^9.0.0",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"next": "14.2.1" "react-hot-toast": "^2.4.1",
"tailwind-merge": "^2.2.2",
"zustand": "^4.5.2"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.1",
"postcss": "^8", "postcss": "^8",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"eslint": "^8", "typescript": "^5"
"eslint-config-next": "14.2.1"
} }
} }
"use client"
import PreviewModel from "@/components/preview-model";
import { useEffect, useState } from "react";
const ModelProvider = () => {
const [mounted, setmounted] = useState(false)
useEffect(() => {
setmounted(true)
}, [])
if (!mounted) {
return null
}
return (<>
<PreviewModel />
</>);
}
export default ModelProvider;
\ No newline at end of file
"use client"
import { Toaster } from "react-hot-toast";
const ToastProvider = () => {
return (<>
<Toaster />
</>);
}
export default ToastProvider;
\ No newline at end of file
export interface Billboard {
id: string;
label: string;
imageURL: string;
}
export interface Category {
id: string;
name: string;
Billboard: string;
}
export interface Size {
id: string;
name: string;
value: string;
}
export interface Color {
id: string;
name: string;
value: string;
}
export interface Image {
id: string;
url: string;
}
export interface product {
id: string;
category: Category;
name: string;
price: string;
isFeatured: boolean;
sizes: Size;
colors: Color;
Image: Image[];
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment