Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6,163 changes: 6,163 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

14 changes: 9 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@
"lint": "next lint"
},
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"lucide-react": "^0.544.0",
"next": "15.4.5",
"react": "19.1.0",
"react-dom": "19.1.0",
"next": "15.4.5"
"react-hook-form": "^7.65.0",
"zod": "^4.1.12"
},
"devDependencies": {
"typescript": "^5",
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@tailwindcss/postcss": "^4",
"tailwindcss": "^4",
"eslint": "^9",
"eslint-config-next": "15.4.5",
"@eslint/eslintrc": "^3"
"tailwindcss": "^4",
"typescript": "^5"
}
}
203 changes: 203 additions & 0 deletions src/app/cart/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
"use client"

import { PaymentForm } from "@/components/PaymentForm";
import ShippingForm from "@/components/ShippingForm";
import { CartsType } from "@/types/types";
import { ArrowRight, Trash2 } from "lucide-react";
import Image from "next/image";
import { useRouter, useSearchParams } from "next/navigation";
import React, { useState } from "react";

const CartPage = () => {
const steps = [
{ id: 1, name: "Shopping Cart" },
{ id: 2, name: "Shopping Address" },
{ id: 3, name: "Payment Method" },
];
const cartItem: CartsType = [
{
id: 1,
name: "Adidas CoreFit T-Shirt",
shortDescription:
"Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.",
description:
"Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.",
price: 39.9,
sizes: ["s", "m", "l", "xl", "xxl"],
colors: ["gray", "purple", "green"],
images: {
gray: "/products/1g.png",
purple: "/products/1p.png",
green: "/products/1gr.png",
},
quantity: 1,
selectedSize: "L",
selectedColor: "gray",
},
{
id: 2,
name: "Puma Ultra Warm Zip",
shortDescription:
"Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.",
description:
"Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.",
price: 59.9,
sizes: ["s", "m", "l", "xl"],
colors: ["gray", "green"],
images: { gray: "/products/2g.png", green: "/products/2gr.png" },
quantity: 4,
selectedSize: "xs",
selectedColor: "gray",
},
{
id: 3,
name: "Nike Air Essentials Pullover",
shortDescription:
"Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.",
description:
"Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.",
price: 69.9,
sizes: ["s", "m", "l"],
colors: ["green", "blue", "black"],
images: {
green: "/products/3gr.png",
blue: "/products/3b.png",
black: "/products/3bl.png",
},
quantity: 1,
selectedSize: "L",
selectedColor: "gray",
},
];

const param = useSearchParams();
const router = useRouter();
const [shippingForm, setShippingForm] = useState(null)

const activeStep = parseInt(param.get("step") || "1")
return (
<div className="flex flex-col gap-8 items-center justify-center my-12">
<h1 className="text-2xl font-medium">Shopping Cart</h1>
<div className="flex flex-col lg:flex-row items-center gap-8 lg:gap-16">
{steps.map((step) => (
<div
key={step.id}
className={`flex items-center gap-2 border-b-2 pb-4 ${
activeStep === step.id ? "border-gray-800" : "border-gray-200"
}`}
>
<div
className={`w-6 h-6 rounded-full text-white p-4 flex items-center justify-center ${
activeStep === step.id ? "bg-gray-800" : "bg-gray-200"
}`}
>
{step.id}
</div>
<p
className={`text-sm font-meduim ${
step.id === activeStep ? "text-gray-800" : "text-gray-400"
}`}
>
{step.name}
</p>
</div>
))}
</div>
{/* Steps and details */}
<div className="w-full flex flex-col lg:flex-row gap-16">
{/* Steps */}
<div className="w-full lg:w-7/12 shadow-lg border-1 border-gray-100 p-8 rounded-lg flex flex-col gap-8">
{activeStep === 1
? cartItem.map((item) => (
// SINGLE CART ITEM
<div
className="flex items-center justify-between"
key={item.id + item.selectedSize + item.selectedColor}
>
{/* IMAGE AND DETAILS */}
<div className="flex gap-8">
{/* IMAGE */}
<div className="relative w-32 h-32 bg-gray-50 rounded-lg overflow-hidden">
<Image
src={item.images[item.selectedColor]}
alt={item.name}
fill
className="object-contain"
/>
</div>
{/* ITEM DETAILS */}
<div className="flex flex-col justify-between">
<div className="flex flex-col gap-1">
<p className="text-sm font-medium">{item.name}</p>
<p className="text-xs text-gray-500">
Quantity: {item.quantity}
</p>
<p className="text-xs text-gray-500">
Size: {item.selectedSize}
</p>
<p className="text-xs text-gray-500">
Color: {item.selectedColor}
</p>
</div>
<p className="font-medium">${item.price.toFixed(2)}</p>
</div>
</div>
{/* DELETE BUTTON */}
<button
// onClick={() => removeFromCart(item)}
className="w-8 h-8 rounded-full bg-red-100 hover:bg-red-200 transition-all duration-300 text-red-400 flex items-center justify-center cursor-pointer"
>
<Trash2 className="w-3 h-3" />
</button>
</div>
))
: activeStep === 2 ? (
<ShippingForm setShippingForm={setShippingForm} />
):(
activeStep === 3 && shippingForm ? (<PaymentForm />) : (<p className="text-sm text-gray-500">Please fill in the shipping form to continue.</p>)

)
}
</div>
{/* Details */}
<div className="w-full lg:w-5/12 shadow-lg border-1 border-gray-100 p-8 rounded-lg flex flex-col gap-8 h-max">
<h2 className="font-semibold">Cart Details</h2>
<div className="flex flex-col gap-4">
<div className="flex justify-between text-sm">
<p className="text-gray-500">Subtotal</p>
</div>
<div className="flex justify-between text-sm">
<p className="text-gray-500">Discount(10%)</p>
<p className="font-medium">$ 10</p>
</div>
<div className="flex justify-between text-sm">
<p className="text-gray-500">Shipping Fee</p>
<p className="font-medium">$10</p>
</div>
<hr className="border-gray-200" />
<div className="flex justify-between">
<p className="text-gray-800 font-semibold">Total</p>
<p className="font-medium">
$
{cartItem
.reduce((acc, item) => acc + item.price * item.quantity, 0)
.toFixed(2)}
</p>
</div>
{activeStep === 1 && (
<button
onClick={() => router.push("/cart?step=2", { scroll: false })}
className="w-full bg-gray-800 hover:bg-gray-900 transition-all duration-300 text-white p-2 rounded-lg cursor-pointer flex items-center justify-center gap-2"
>
Continue
<ArrowRight className="w-3 h-3" />
</button>
)}
</div>
</div>
</div>
</div>
);
};

export default CartPage;
8 changes: 7 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer"
import "./globals.css";

const geistSans = Geist({
Expand Down Expand Up @@ -27,7 +29,11 @@ export default function RootLayout({
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
<div className="mx-auto p-4 sm:max-w-xl md:max-w-2xl lg:max-w-4xl xl:max-w-6xl">
<Navbar />
{children}
<Footer />
</div>
</body>
</html>
);
Expand Down
19 changes: 17 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
const Homepage = () => {

import ProductList from "@/components/ProductList"
import Image from "next/image"

const Homepage = async ({
searchParams,
}: {
searchParams: Promise<{ category: string }>;
}) => {
const category = (await searchParams).category;
return (
<div className=''>Homepage</div>
<div>
<div className=' relative aspect-[3/1] mb-12'>
<Image src={'/featured.png'} alt="featured image" fill />

</div>
<ProductList category={category} params="homepage" />
</div>
)
}

Expand Down
17 changes: 17 additions & 0 deletions src/app/products/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import ProductList from "@/components/ProductList";


const ProductsPage = async ({
searchParams,
}: {
searchParams: Promise<{ category: string }>;
}) => {
const category = (await searchParams).category;
return (
<div className="">
<ProductList category={category} params="products"/>
</div>
);
};

export default ProductsPage;
88 changes: 88 additions & 0 deletions src/components/Categories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"use client"

import { Briefcase, Footprints, Glasses, Hand, Shirt, ShoppingBasket, Venus } from 'lucide-react';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import React from 'react'

const Categories = () => {
const categories = [
{
name: "All",
icon: <ShoppingBasket className="w-4 h-4" />,
slug: "all",
},
{
name: "T-shirts",
icon: <Shirt className="w-4 h-4" />,
slug: "t-shirts",
},
{
name: "Shoes",
icon: <Footprints className="w-4 h-4" />,
slug: "shoes",
},
{
name: "Accessories",
icon: <Glasses className="w-4 h-4" />,
slug: "accessories",
},
{
name: "Bags",
icon: <Briefcase className="w-4 h-4" />,
slug: "bags",
},
{
name: "Dresses",
icon: <Venus className="w-4 h-4" />,
slug: "dresses",
},
{
name: "Jackets",
icon: <Shirt className="w-4 h-4" />,
slug: "jackets",
},
{
name: "Gloves",
icon: <Hand className="w-4 h-4" />,
slug: "gloves",
},
];

const searchParams = useSearchParams();
const valueParam = searchParams.get("category");
const router = useRouter();
const pathname = usePathname();

const handleClick = (value: string | null) => {
const params = new URLSearchParams(searchParams.toString());

if (value && value !== "all") {
params.set("category", value);
} else {
params.delete("category");
}

router.push(`${pathname}?${params.toString()}`, {scroll: false});
}

return (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-2 bg-gray-100 p-2 rounded-lg mb-4 text-sm">
{categories.map((category) => (
<div
key={category.name}
className={`flex items-center justify-center gap-2 cursor-pointer p-2 rounded-md transition-colors ${
(category.slug === valueParam || (category.slug === "all" && !valueParam))
? "bg-white text-black shadow-sm"
: "text-gray-500 hover:text-gray-700 hover:bg-gray-200"
}`}
onClick={() => handleClick(category.slug)}
>
{category.icon}
{category.name}
</div>
))}
</div>
);
}

export default Categories;
Loading