Compare commits
No commits in common. "7c870930cc968a7dd2ad6e86764552123db3ffe3" and "63f16bd0591c9e08e04d2a46f78371d962dd69c7" have entirely different histories.
7c870930cc
...
63f16bd059
24
.gitignore
vendored
24
.gitignore
vendored
|
@ -1,24 +0,0 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
|
@ -1,38 +0,0 @@
|
|||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import react from 'eslint-plugin-react'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
|
||||
export default [
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
files: ['**/*.{js,jsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
ecmaFeatures: { jsx: true },
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
settings: { react: { version: '18.3' } },
|
||||
plugins: {
|
||||
react,
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...js.configs.recommended.rules,
|
||||
...react.configs.recommended.rules,
|
||||
...react.configs['jsx-runtime'].rules,
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react/jsx-no-target-blank': 'off',
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
13
index.html
13
index.html
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- <title>e-shop</title> -->
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
5600
package-lock.json
generated
5600
package-lock.json
generated
File diff suppressed because it is too large
Load diff
36
package.json
36
package.json
|
@ -1,36 +0,0 @@
|
|||
{
|
||||
"name": "ecommerce",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^2.4.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-helmet-async": "^2.0.5",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-router": "^7.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.15.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.15.0",
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.14",
|
||||
"globals": "^15.12.0",
|
||||
"postcss": "^8.4.49",
|
||||
"tailwindcss": "^3.4.16",
|
||||
"vite": "^6.0.1"
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
Before Width: | Height: | Size: 1.5 KiB |
86
src/App.jsx
86
src/App.jsx
|
@ -1,86 +0,0 @@
|
|||
import { createBrowserRouter, RouterProvider } from "react-router";
|
||||
import "./App.css";
|
||||
import Layout from "./components/layout/Layout";
|
||||
import Error from "./components/Error";
|
||||
import Home from "./pages/Home";
|
||||
import About from "./pages/About";
|
||||
import Contact from "./pages/Contact";
|
||||
import Shop from "./pages/Shop";
|
||||
import Cart from "./pages/Cart";
|
||||
import Checkout from "./pages/Checkout";
|
||||
import { useState } from "react";
|
||||
import Order from "./pages/Order";
|
||||
import FilterData from "./components/FilterData";
|
||||
import ProductDetail from "./pages/ProductDetail";
|
||||
import { HelmetProvider } from "react-helmet-async";
|
||||
function App() {
|
||||
const [order, setOrder] = useState({
|
||||
products: [],
|
||||
orderNumber: "",
|
||||
shippingInformation: { address: "", city: "", zip: "" },
|
||||
totalPrice: 0,
|
||||
});
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <Layout />,
|
||||
errorElement: <Error />,
|
||||
children: [
|
||||
{
|
||||
path: "/",
|
||||
element: <Home />,
|
||||
},
|
||||
{
|
||||
path: "/about",
|
||||
element: <About />,
|
||||
},
|
||||
|
||||
{
|
||||
path: "/contact",
|
||||
element: <Contact />,
|
||||
},
|
||||
|
||||
{
|
||||
path: "/shop",
|
||||
element: <Shop />,
|
||||
},
|
||||
|
||||
{
|
||||
path: "/cart",
|
||||
element: <Cart />,
|
||||
},
|
||||
|
||||
{
|
||||
path: "/product/:id",
|
||||
element: <ProductDetail />,
|
||||
},
|
||||
|
||||
{
|
||||
path: "/checkout",
|
||||
element: <Checkout setOrder={setOrder} />,
|
||||
},
|
||||
|
||||
{
|
||||
path: "/order-confirmation",
|
||||
element: <Order order={order} />,
|
||||
},
|
||||
|
||||
{
|
||||
path: "/filter-data",
|
||||
element: <FilterData />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<HelmetProvider>
|
||||
<RouterProvider router={router} />
|
||||
</HelmetProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
Binary file not shown.
Before Width: | Height: | Size: 55 KiB |
|
@ -1,72 +0,0 @@
|
|||
export const categories = [
|
||||
"Electronics",
|
||||
"Fashion",
|
||||
"Home & Kitchen",
|
||||
"Beauty",
|
||||
"Sports",
|
||||
"Automotive",
|
||||
];
|
||||
|
||||
export const mockData = [
|
||||
{
|
||||
id: 1,
|
||||
image: "https://m.media-amazon.com/images/I/71IYWw-1KBL._SX569_.jpg",
|
||||
name: "Men Upper",
|
||||
price: 29.99,
|
||||
},
|
||||
|
||||
{
|
||||
id: 2,
|
||||
image:
|
||||
"https://m.media-amazon.com/images/I/51A2iKgaiML._SX300_SY300_QL70_FMwebp_.jpg",
|
||||
name: "Motorcycle Helmet",
|
||||
price: 39.99,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
image:
|
||||
"https://m.media-amazon.com/images/I/31Cdcr1VxEL._SX300_SY300_QL70_FMwebp_.jpg",
|
||||
name: "Fry Pan",
|
||||
price: 49.99,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
image:
|
||||
"https://m.media-amazon.com/images/I/514CfZOJ5rL._SX300_SY300_QL70_FMwebp_.jpg",
|
||||
name: "Smartwatch",
|
||||
price: 59.99,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
image:
|
||||
"https://m.media-amazon.com/images/I/31xXPjbn4jL._SX300_SY300_QL70_FMwebp_.jpg",
|
||||
name: "Bluetooh Earphone",
|
||||
price: 69.99,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
image:
|
||||
"https://m.media-amazon.com/images/I/41A7WrRCOpL._SY445_SX342_QL70_FMwebp_.jpg",
|
||||
name: "Microwave Ovens",
|
||||
price: 79.99,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
image:
|
||||
"https://m.media-amazon.com/images/I/41BMuuS76SL._SX300_SY300_QL70_FMwebp_.jpg",
|
||||
name: "Wall Clock",
|
||||
price: 89.99,
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
image: "https://m.media-amazon.com/images/I/61SFGUb9y8L._AC_UL320_.jpg",
|
||||
name: "Chess",
|
||||
price: 99.99,
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
image: "https://m.media-amazon.com/images/I/710SxepIfiL._SL1500_.jpg",
|
||||
name: "dumbbell set",
|
||||
price: 109.99,
|
||||
},
|
||||
];
|
|
@ -1,68 +0,0 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
|
||||
const category = [
|
||||
{
|
||||
title: "Men",
|
||||
imageUrl:
|
||||
"https://d2ki7eiqd260sq.cloudfront.net/MSLS00005GREENFIGS_2c9ab0616-5513-4e8d-8a78-6e294c31173a.webp",
|
||||
},
|
||||
{
|
||||
title: "Women",
|
||||
imageUrl:
|
||||
"https://objectstorage.ap-mumbai-1.oraclecloud.com/n/softlogicbicloud/b/cdn/o/category-images/60ab26835e322.png",
|
||||
},
|
||||
{
|
||||
title: "Kids",
|
||||
imageUrl:
|
||||
"https://objectstorage.ap-mumbai-1.oraclecloud.com/n/softlogicbicloud/b/cdn/o/category-images/60b88eb7c6211.png",
|
||||
},
|
||||
];
|
||||
const CategorySection = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 1500);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="container mx-auto flex justify-center items-center ">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-6 md:space-x-14 animate-pulse ">
|
||||
<div className="h-60 w-64 rounded-xl shadow bg-gray-300"></div>
|
||||
<div className="h-60 w-64 rounded-xl shadow bg-gray-300"></div>
|
||||
<div className="h-60 w-64 rounded-xl shadow bg-gray-300"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className=" container mx-auto flex justify-center items-center">
|
||||
<div className=" grid grid-cols-1 sm:grid-cols-3 gap-6 md:space-x-14 ">
|
||||
{category.map((elem, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="relative h-64 transform transition-transform duration-300 hover:scale-105 cursor-pointer"
|
||||
>
|
||||
<img
|
||||
src={elem.imageUrl}
|
||||
alt=""
|
||||
className="h-full object-cover rounded-lg shadow-md"
|
||||
/>
|
||||
|
||||
<div className="absolute top-20 left-2">
|
||||
<p className="text-md font-bold">{elem.title}</p>
|
||||
<p className="text-gray-600">View All</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CategorySection;
|
|
@ -1,37 +0,0 @@
|
|||
import React, { useState } from "react";
|
||||
|
||||
const ChangeAddress = ({ setAddress, setIsModelOpen }) => {
|
||||
const onClose = () => {
|
||||
setAddress(newAddress);
|
||||
setIsModelOpen(false);
|
||||
};
|
||||
|
||||
const [newAddress, setNewAddress] = useState("");
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="enter new address"
|
||||
className="w-full p-2 border mb-4 outline-none"
|
||||
onChange={(e) => setNewAddress(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
className="bg-gray-500 hover:bg-gray-700 py-2 px-4 text-white rounded mr-2"
|
||||
onClick={() => setIsModelOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="bg-blue-500 hover:bg-blue-700 py-2 px-4 rounded text-white"
|
||||
onClick={onClose}
|
||||
>
|
||||
Save Address
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChangeAddress;
|
|
@ -1,14 +0,0 @@
|
|||
import React from "react";
|
||||
import { Link, useRouteError } from "react-router";
|
||||
const Error = () => {
|
||||
const error = useRouteError();
|
||||
return (
|
||||
<div>
|
||||
<h1>Oops! An error occured.</h1>
|
||||
<p> {error.data}</p>
|
||||
<Link to="/">Go Home</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Error;
|
|
@ -1,24 +0,0 @@
|
|||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import ProductCard from "./ProductCard";
|
||||
const FilterData = () => {
|
||||
const filterProducts = useSelector((state) => state.products.filterData);
|
||||
return (
|
||||
<div className="mx-auto py-12 px-4 md:px-16 lg:px-24">
|
||||
{filterProducts.length > 0 ? (
|
||||
<>
|
||||
<h2 className="text-2xl font-bold mb-6 text-center">Shop</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-6 cursor-pointer">
|
||||
{filterProducts.map((product) => (
|
||||
<ProductCard product={product} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<h1 className="text-center">not found</h1>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterData;
|
|
@ -1,95 +0,0 @@
|
|||
import React from "react";
|
||||
import { BsTwitter } from "react-icons/bs";
|
||||
import { FaFacebook, FaGithub, FaLinkedin } from "react-icons/fa";
|
||||
import { Link } from "react-router";
|
||||
const Footer = () => {
|
||||
return (
|
||||
<footer className="bg-gray-800 text-white py-10 px-4 md:px-16 lg:px-24 ">
|
||||
<div className="container mx-auto grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold">e-shop</h3>
|
||||
<p className="mt-4">
|
||||
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Labore ex
|
||||
animi accusamus consectetur harum. Rem mollitia provident
|
||||
consequuntur ducimus exercitationem!
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col md:items-center">
|
||||
<h4 className="text-lg font-semibold">Quick Links</h4>
|
||||
<ul className="mt-4 space-y-2">
|
||||
<li>
|
||||
<Link to="/" className="hover:underline">
|
||||
Home
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Link to="/shop" className="hover:underline">
|
||||
Shop
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Link to="/about" className="hover:underline">
|
||||
About
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Link to="/contact" className="hover:underline">
|
||||
Contact
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-lg font-semibold">Follow us</h4>
|
||||
<div className="flex space-x-4 mt-4">
|
||||
<a href="/" className="hover:text-gray-400">
|
||||
<FaFacebook />
|
||||
</a>
|
||||
|
||||
<a href="/" className="hover:text-gray-400">
|
||||
<BsTwitter />
|
||||
</a>
|
||||
|
||||
<a href="/" className="hover:text-gray-400">
|
||||
<FaGithub />
|
||||
</a>
|
||||
|
||||
<a href="/" className="hover:text-gray-400">
|
||||
<FaLinkedin />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<form action="" className="flex items-center justify-center mt-8 ">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="enter email"
|
||||
className="w-full p-2 rounded-lg bg-gray-800 border border-gray-600 outline-none"
|
||||
/>
|
||||
<button className="bg-red-500 px-4 py-2 text-white rounded-md ml-2 hover:bg-red-700">
|
||||
Subscibe
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 border-t border-gray-700 pt-4">
|
||||
<div className="container mx-auto flex flex-col md:flex-row justify-between items-center">
|
||||
<p>© {new Date().getFullYear()} e-shop All rights reserved.</p>
|
||||
<div className="flex space-x-4 mt-4 md:mt-0">
|
||||
<a href="/" className="hover:underline">
|
||||
Privacy Policy
|
||||
</a>
|
||||
<a href="/" className="hover:underline">
|
||||
Terms & Conditions
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
|
@ -1,87 +0,0 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { FaHeadset, FaLock, FaShoppingCart, FaTag } from "react-icons/fa";
|
||||
import { FaMoneyBill1Wave } from "react-icons/fa6";
|
||||
const InfoSection = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 1500);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
});
|
||||
const infoItems = [
|
||||
{
|
||||
icon: <FaShoppingCart className="text-3xl text-red-600" />,
|
||||
title: "Free Shipping",
|
||||
description: "Get your orders delivered with no extra cost",
|
||||
},
|
||||
{
|
||||
icon: <FaHeadset className="text-3xl text-red-600" />,
|
||||
title: "Support 24/7",
|
||||
description: "We are here to assist you anytime",
|
||||
},
|
||||
{
|
||||
icon: <FaMoneyBill1Wave className="text-3xl text-red-600" />,
|
||||
title: "100% Money Back",
|
||||
description: "Full refund if you are not satisfied ",
|
||||
},
|
||||
{
|
||||
icon: <FaLock className="text-3xl text-red-600" />,
|
||||
title: "Payment Secure",
|
||||
description: "your payment information is safe with us",
|
||||
},
|
||||
|
||||
{
|
||||
icon: <FaTag className="text-3xl text-red-600" />,
|
||||
title: "Discount",
|
||||
description: "Enjoy the best prices on your products",
|
||||
},
|
||||
];
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="bg-white shadow rounded mb-8 mt-12 ">
|
||||
<div className="container mx-auto grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6">
|
||||
{infoItems.map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col items-center text-center p-4 border rounded-lg shadow-md animate-pulse h-48
|
||||
"
|
||||
>
|
||||
<div className="bg-gray-300 w-12 h-12 mb-4 rounded-full"></div>
|
||||
<h3 className="w-3/4 bg-gray-300 h-5 rounded mb-4"></h3>
|
||||
<p className="bg-gray-300 rounded h-3 w-full mb-2"></p>
|
||||
<p className="bg-gray-300 rounded h-3 w-full "></p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white mb-8 mt-12">
|
||||
<div className="container mx-auto grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6">
|
||||
{infoItems.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col items-center text-center p-4 border rounded-lg shadow-md
|
||||
transform transition-transform duration-300 hover:scale-105 cursor-pointer "
|
||||
>
|
||||
{item.icon}
|
||||
<h3 className="mt-4 text-lg font-serif font-semibold">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="mt-2 text-gray-600">{item.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoSection;
|
|
@ -1,21 +0,0 @@
|
|||
import React from "react";
|
||||
|
||||
const Modal = ({ isModelOpen, setIsModelOpen, children }) => {
|
||||
if (!isModelOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-gray-800 bg-opacity-75 flex justify-center items-center z-50">
|
||||
<div className="bg-white shadow-lg rounded-lg p-6 w-full max-w-md">
|
||||
<button
|
||||
className="absolute top-4 right-4 text-gray-300 text-3xl"
|
||||
onClick={() => setIsModelOpen(false)}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
|
@ -1,107 +0,0 @@
|
|||
import { useState } from "react";
|
||||
import { FaSearch, FaShoppingCart, FaUser } from "react-icons/fa";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Link, useNavigate } from "react-router";
|
||||
import Login from "../pages/Login";
|
||||
import Register from "../pages/Register";
|
||||
import Modal from "./Model";
|
||||
import { setSearchTerm } from "../redux/productSlice";
|
||||
const Navbar = () => {
|
||||
const products = useSelector((state) => state.cart.products);
|
||||
const [isModelOpen, setIsModelOpen] = useState(false);
|
||||
const [isLogin, setIsLogin] = useState(true);
|
||||
const [search, setSearch] = useState();
|
||||
const disptach = useDispatch();
|
||||
const navigate = useNavigate("/");
|
||||
|
||||
const openSignUp = () => {
|
||||
setIsLogin(false);
|
||||
setIsModelOpen(true);
|
||||
};
|
||||
|
||||
const openLogin = () => {
|
||||
setIsLogin(true);
|
||||
setIsModelOpen(true);
|
||||
};
|
||||
|
||||
const handleSearch = (e) => {
|
||||
e.preventDefault();
|
||||
disptach(setSearchTerm(search));
|
||||
navigate("/filter-data");
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="w-full bg-white shadow-md">
|
||||
<div className="container mx-auto px-4 md:px-16 lg:px-24 py-4 flex justify-between items-center">
|
||||
<div className="text-lg font-bold">
|
||||
<Link to="/">
|
||||
<span className="font-serif font-bold">E-Shop</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="relative flex-1 mx-4 ">
|
||||
<form onSubmit={handleSearch}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="search product"
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="w-full border py-2 px-4 outline-none rounded-full"
|
||||
/>
|
||||
<FaSearch className="top-3 absolute right-3 text-red-500" />
|
||||
</form>
|
||||
</div>
|
||||
<div className="flex item-center space-x-4">
|
||||
<Link to="/cart" className="relative">
|
||||
<FaShoppingCart className="text-xl" />
|
||||
{products.length > 0 && (
|
||||
<span className="absolute top-0 text-xs w-3 left-3 bg-red-600 rounded-full flex justify-center items-center text-white">
|
||||
{products.length}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
className="hidden md:block hover:underline"
|
||||
onClick={openLogin}
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
<span> | </span>
|
||||
|
||||
<button
|
||||
className="hidden md:block hover:underline"
|
||||
onClick={openSignUp}
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
<button className="block md:hidden ">
|
||||
<FaUser />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center space-x-10 py-4 text-sm font-bold">
|
||||
<Link to="/" className="hover:underline">
|
||||
Home
|
||||
</Link>
|
||||
<Link to="/shop" className="hover:underline">
|
||||
Shop
|
||||
</Link>
|
||||
<Link to="/contact" className="hover:underline">
|
||||
Contact
|
||||
</Link>
|
||||
<Link to="/about" className="hover:underline">
|
||||
About
|
||||
</Link>
|
||||
</div>
|
||||
<Modal setIsModelOpen={setIsModelOpen} isModelOpen={isModelOpen}>
|
||||
{isLogin ? (
|
||||
<Login openSignUp={openSignUp} />
|
||||
) : (
|
||||
<Register openLogin={openLogin} />
|
||||
)}
|
||||
</Modal>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
|
@ -1,77 +0,0 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { FaStar } from "react-icons/fa";
|
||||
import { addToCart } from "../redux/cartSlice";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { Link } from "react-router";
|
||||
|
||||
const ProductCard = ({ product }) => {
|
||||
const dispatch = useDispatch();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const handleAddToCart = (e, product) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
dispatch(addToCart(product));
|
||||
alert("Product Added Successfully!");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 1500);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="bg-white p-4 shadow rounded relative border transform transition-transform cursor-pointer duration-300 hover:scale-105 animate-pulse">
|
||||
<div className="w-full h-48 bg-gray-300 mb-4 rounded-lg"></div>
|
||||
<div className="h-4 bg-gray-300 mb-2 rounded w-3/4"></div>
|
||||
<div className="h-4 bg-gray-300 mb-2 rounded w-1/2"></div>
|
||||
|
||||
<div className="flex items-center mt-2">
|
||||
{[...Array(5)].map((_, index) => (
|
||||
<FaStar key={index} className="text-gray-300" />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-4 right-2 flex items-center justify-center w-8 h-8 bg-gray-300 text-white text-sm rounded-full"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link to={`/product/${product.id}`}>
|
||||
<div className="bg-white p-4 shadow rounded relative border transform transition-transform cursor-pointer duration-300 hover:scale-105">
|
||||
<img
|
||||
src={product.image}
|
||||
alt={product.name}
|
||||
className="w-full h-48 object-contain mb-4"
|
||||
/>
|
||||
<h3 className="text-lg font-semibold">{product.name}</h3>
|
||||
<p className="text-gray-500">${product.price}</p>
|
||||
|
||||
<div className="flex items-center mt-2">
|
||||
<FaStar className="text-yellow-500" />
|
||||
<FaStar className="text-yellow-500" />
|
||||
<FaStar className="text-yellow-500" />
|
||||
<FaStar className="text-yellow-500" />
|
||||
<FaStar className="text-yellow-500" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="absolute bottom-4 right-2 flex items-center justify-center w-8 h-8 bg-red-600
|
||||
group text-white text-sm rounded-full hover:w-32 hover:bg-red-700 duration-300 transition-all"
|
||||
onClick={(e) => handleAddToCart(e, product)}
|
||||
>
|
||||
<span className="group-hover:hidden"> + </span>
|
||||
<span className="hidden group-hover:block"> Add to cart </span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductCard;
|
|
@ -1,15 +0,0 @@
|
|||
import React from "react";
|
||||
import Navbar from "../Navbar";
|
||||
import Footer from "../Footer";
|
||||
import { Outlet } from "react-router";
|
||||
const Layout = () => {
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<Outlet />
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
|
@ -1,3 +0,0 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
13
src/main.jsx
13
src/main.jsx
|
@ -1,13 +0,0 @@
|
|||
import { createRoot } from "react-dom/client";
|
||||
import "./index.css";
|
||||
import App from "./App.jsx";
|
||||
import { Provider } from "react-redux";
|
||||
import { store } from "./redux/store.jsx";
|
||||
import { HelmetProvider } from "react-helmet-async";
|
||||
createRoot(document.getElementById("root")).render(
|
||||
<HelmetProvider>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</HelmetProvider>
|
||||
);
|
|
@ -1,122 +0,0 @@
|
|||
import React from "react";
|
||||
import { FaCarSide } from "react-icons/fa";
|
||||
import { IoIosPricetags } from "react-icons/io";
|
||||
import { FaCartShopping } from "react-icons/fa6";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
const About = () => {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-12 px-4 md:px-16 lg:px-24">
|
||||
<Helmet>
|
||||
<title>About Us - Esop</title>
|
||||
|
||||
<meta name="description" content="" />
|
||||
</Helmet>
|
||||
|
||||
{/* Hero Section */}
|
||||
<div className="relative bg-white rounded-lg shadow-lg overflow-hidden">
|
||||
<div className=" bg-black bg-opacity-50 z-10"></div>
|
||||
<div className="flex justify-center items-center">
|
||||
<img
|
||||
src="https://pujabhandar-1.web.app/assets/1-S31Ps3IF.jpg"
|
||||
alt="About Us"
|
||||
className="w-90 h-72 md:h-66 text-center object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mission Section */}
|
||||
<div className="mt-16 max-w-5xl mx-auto text-center">
|
||||
<h2 className="text-2xl md:text-3xl font-bold mb-6 text-gray-800">
|
||||
Our Mission
|
||||
</h2>
|
||||
<p className="text-gray-600 leading-relaxed text-lg">
|
||||
At <span className="text-red-600 font-semibold">E-Shop</span>, our
|
||||
mission is to provide an exceptional online shopping experience by
|
||||
offering a wide range of high-quality products, seamless customer
|
||||
service, and a commitment to your satisfaction.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Company Highlights */}
|
||||
<div className="mt-16 max-w-6xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<div className="text-center">
|
||||
<div className="bg-red-100 text-red-600 w-16 h-16 mx-auto flex items-center justify-center rounded-full mb-4">
|
||||
<FaCartShopping className="w-8 h-8" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-800">
|
||||
Quality Products
|
||||
</h3>
|
||||
<p className="text-gray-600 mt-2">
|
||||
We offer a curated selection of top-quality products for all your
|
||||
needs.
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="bg-red-100 text-red-600 w-16 h-16 mx-auto flex items-center justify-center rounded-full mb-4">
|
||||
<FaCarSide className="w-8 h-8" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-800">Fast Delivery</h3>
|
||||
<p className="text-gray-600 mt-2">
|
||||
We ensure quick and reliable shipping to your doorstep.
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="bg-red-100 text-red-600 w-16 h-16 mx-auto flex items-center justify-center rounded-full mb-4">
|
||||
<IoIosPricetags className="w-8 h-8" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-800">
|
||||
Affordable Prices
|
||||
</h3>
|
||||
<p className="text-gray-600 mt-2">
|
||||
Get the best value for your money with our competitive pricing.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Meet the Team */}
|
||||
<div className="mt-16 max-w-6xl mx-auto">
|
||||
<h2 className="text-2xl md:text-3xl font-bold mb-8 text-gray-800 text-center">
|
||||
Meet the Team
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-8">
|
||||
{[
|
||||
{
|
||||
name: "John Doe",
|
||||
role: "Founder & CEO",
|
||||
img: "https://static.vecteezy.com/system/resources/previews/024/183/538/non_2x/male-avatar-portrait-of-a-business-man-in-a-suit-illustration-of-male-character-in-modern-color-style-vector.jpg",
|
||||
},
|
||||
{
|
||||
name: "Jane Smith",
|
||||
role: "Chief Marketing Officer",
|
||||
img: "https://static.vecteezy.com/system/resources/previews/024/183/538/non_2x/male-avatar-portrait-of-a-business-man-in-a-suit-illustration-of-male-character-in-modern-color-style-vector.jpg",
|
||||
},
|
||||
{
|
||||
name: "Emily Johnson",
|
||||
role: "Product Manager",
|
||||
img: "https://static.vecteezy.com/system/resources/previews/024/183/538/non_2x/male-avatar-portrait-of-a-business-man-in-a-suit-illustration-of-male-character-in-modern-color-style-vector.jpg",
|
||||
},
|
||||
{
|
||||
name: "Michael Brown",
|
||||
role: "Lead Developer",
|
||||
img: "https://static.vecteezy.com/system/resources/previews/024/183/538/non_2x/male-avatar-portrait-of-a-business-man-in-a-suit-illustration-of-male-character-in-modern-color-style-vector.jpg",
|
||||
},
|
||||
].map((member, index) => (
|
||||
<div key={index} className="text-center">
|
||||
<img
|
||||
src={member.img}
|
||||
alt={member.name}
|
||||
className="w-32 h-32 mx-auto rounded-full mb-4"
|
||||
/>
|
||||
<h3 className="text-lg font-semibold text-gray-800">
|
||||
{member.name}
|
||||
</h3>
|
||||
<p className="text-gray-600">{member.role}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default About;
|
|
@ -1,160 +0,0 @@
|
|||
import React, { useState } from "react";
|
||||
import { FaTrashAlt } from "react-icons/fa";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import Modal from "../components/Model";
|
||||
import ChangeAddress from "../components/ChangeAddress";
|
||||
import {
|
||||
decreaseQuantity,
|
||||
increaseQuantity,
|
||||
removeFromCart,
|
||||
} from "../redux/cartSlice";
|
||||
import { useNavigate } from "react-router";
|
||||
const Cart = () => {
|
||||
const cart = useSelector((state) => state.cart);
|
||||
const [address, setAddress] = useState("main street, 123");
|
||||
const [isModelOpen, setIsModelOpen] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div className="container mx-auto py-8 min-h-96 px-4 md:px-16 lg:px-24">
|
||||
{cart.products.length > 0 ? (
|
||||
<div>
|
||||
<h3 className="text-2xl font-semibold mb-4">SHOPPING CART</h3>
|
||||
<div className="flex flex-col md:flex-row justify-between space-x-10 mt-8">
|
||||
<div className="md:w-2/3">
|
||||
<div className="hidden md:flex justify-between border-b items-center mb-4 text-xs font-bold">
|
||||
<p>PRODUCTS</p>
|
||||
<div className="flex space-x-8">
|
||||
<p>PRICE</p>
|
||||
<p>QUANTITY</p>
|
||||
<p>SUBTOTAL</p>
|
||||
<p>REMOVE</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{cart.products.map((product) => (
|
||||
<div
|
||||
key={product.id}
|
||||
className="flex flex-col md:flex-row justify-between items-start md:items-center p-3 border-b space-y-4 md:space-y-0"
|
||||
>
|
||||
{/* Product Details */}
|
||||
<div className="flex items-center space-x-4">
|
||||
<img
|
||||
src={product.image}
|
||||
alt={product.name}
|
||||
className="w-16 h-16 object-contain rounded"
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-md font-semibold">
|
||||
{product.name}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Responsive Details */}
|
||||
<div className="flex flex-col md:flex-row md:space-x-12 items-start md:items-center w-full md:w-auto space-y-4 md:space-y-0">
|
||||
{/* Price */}
|
||||
<div className="md:hidden font-bold">Price:</div>
|
||||
<p>${product.price}</p>
|
||||
|
||||
{/* Quantity */}
|
||||
<div className="flex items-center">
|
||||
<div className="md:hidden font-bold mr-2">
|
||||
Quantity:
|
||||
</div>
|
||||
<div className="flex items-center border">
|
||||
<button
|
||||
className="text-xl font-bold px-1.5 border-r"
|
||||
onClick={() =>
|
||||
dispatch(decreaseQuantity(product.id))
|
||||
}
|
||||
>
|
||||
-
|
||||
</button>
|
||||
<p className="text-sm px-2">{product.quantity}</p>
|
||||
<button
|
||||
className="text-xl px-1 border-1"
|
||||
onClick={() =>
|
||||
dispatch(increaseQuantity(product.id))
|
||||
}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Subtotal */}
|
||||
<div>
|
||||
<div className="md:hidden font-bold">Subtotal:</div>
|
||||
<p>${(product.quantity * product.price).toFixed(2)}</p>
|
||||
</div>
|
||||
|
||||
{/* Remove */}
|
||||
<div>
|
||||
<div className="md:hidden font-bold">Remove:</div>
|
||||
<button
|
||||
className="text-red-500 hover:text-red-700"
|
||||
onClick={() => dispatch(removeFromCart(product.id))}
|
||||
>
|
||||
<FaTrashAlt />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="md:w-1/3 bg-white p-6 rounded-lg shadow-md border ">
|
||||
<h3 className="text-sm font-semibold mb-5">CART TOTAL</h3>
|
||||
<div className="flex justify-between mb-5 border-b pb-1">
|
||||
<span className="text-sm">Total Items: </span>
|
||||
<span>{cart.totalQuantity} </span>
|
||||
</div>
|
||||
<div className="mb-4 border-b pb-2">
|
||||
{/* <p>Shipping:</p> */}
|
||||
<p>Shipping to:</p>
|
||||
<span className="text-xs font-bold">{address}</span>
|
||||
<button
|
||||
className="text-blue-500 hover:underline mt-1 ml-2"
|
||||
onClick={() => setIsModelOpen(true)}
|
||||
>
|
||||
Change Address
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between mb-4">
|
||||
<span>Total Price: </span>
|
||||
<span>${cart.totalPrice.toFixed(2)}</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="w-full bg-red-600 text-white py-2 hover:bg-red-800"
|
||||
onClick={() => navigate("/checkout")}
|
||||
>
|
||||
Proceed to Checkout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal isModelOpen={isModelOpen} setIsModelOpen={setIsModelOpen}>
|
||||
<ChangeAddress
|
||||
setAddress={setAddress}
|
||||
setIsModelOpen={setIsModelOpen}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center">
|
||||
<img
|
||||
src="https://static.vecteezy.com/system/resources/previews/016/462/240/non_2x/empty-shopping-cart-illustration-concept-on-white-background-vector.jpg"
|
||||
alt=""
|
||||
className="h-96 my-5"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Cart;
|
|
@ -1,304 +0,0 @@
|
|||
import React, { useState } from "react";
|
||||
import { FaAngleUp, FaAngleDown } from "react-icons/fa";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router";
|
||||
const Checkout = ({ setOrder }) => {
|
||||
const [billingToggle, setBillingToggle] = useState(true);
|
||||
const [shippingToggle, setShippingToggle] = useState(false);
|
||||
const [paymentToggle, setPaymentToggle] = useState(false);
|
||||
const [paymentMethod, setPaymentMethod] = useState("cod");
|
||||
|
||||
const [shippingInfo, setShippingInfo] = useState({
|
||||
address: " ",
|
||||
city: "",
|
||||
zip: "",
|
||||
});
|
||||
|
||||
const cart = useSelector((state) => state.cart);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleOrder = () => {
|
||||
const newOrder = {
|
||||
products: cart.products,
|
||||
orderNumber: "12344",
|
||||
shippingInformation: shippingInfo,
|
||||
totalPrice: cart.totalPrice,
|
||||
};
|
||||
setOrder(newOrder);
|
||||
navigate("/order-confirmation");
|
||||
console.log("button clicked");
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="container mx-auto py-8 min-h-96 px-4 md:px-16 lg:px-24">
|
||||
<h3 className="text-2xl font-semibold mb-4">CHECKOUT</h3>
|
||||
<div className="flex flex-col md:flex-row justify-between space-x-10 mt-8">
|
||||
<div className="border md:w-2/3 p-5 ">
|
||||
{/* Billing Information */}
|
||||
<div>
|
||||
<div
|
||||
className="flex items-center justify-between"
|
||||
onClick={() => setBillingToggle(!billingToggle)}
|
||||
>
|
||||
<h3 className="text-lg font-semibold mb-2">
|
||||
Billing Information
|
||||
</h3>
|
||||
{billingToggle ? <FaAngleDown /> : <FaAngleUp />}
|
||||
</div>
|
||||
|
||||
<div className={`${billingToggle ? "" : "hidden"}`}>
|
||||
<div>
|
||||
<div>
|
||||
<label className="block text-gray-700">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="enter name"
|
||||
className="w-full px-3 py-2 border mb-4 rounded outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<label className="block text-gray-700" htmlFor="">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="enter email"
|
||||
className="w-full px-3 py-2 border mb-4 rounded outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<label htmlFor="" className="block text-gray-700">
|
||||
Phone
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="enter phone"
|
||||
className="w-full px-3 py-2 border mb-4 rounded outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* shipping Information */}
|
||||
<div className="mt-5">
|
||||
<div
|
||||
className="flex items-center justify-between"
|
||||
onClick={() => setShippingToggle(!shippingToggle)}
|
||||
>
|
||||
<h3 className="text-lg font-semibold mb-2">
|
||||
Shipping Information
|
||||
</h3>
|
||||
{shippingToggle ? <FaAngleDown /> : <FaAngleUp />}
|
||||
</div>
|
||||
|
||||
<div className={`${shippingToggle ? "" : "hidden"}`}>
|
||||
<div>
|
||||
<div>
|
||||
<label className="block text-gray-700">Address</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="enter address"
|
||||
className="w-full px-3 py-2 border mb-4 rounded outline-none"
|
||||
onChange={(e) =>
|
||||
setShippingInfo({
|
||||
...shippingInfo,
|
||||
address: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<label className="block text-gray-700" htmlFor="">
|
||||
City
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="enter city name"
|
||||
className="w-full px-3 py-2 border mb-4 rounded outline-none"
|
||||
onChange={(e) =>
|
||||
setShippingInfo({
|
||||
...shippingInfo,
|
||||
city: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<label htmlFor="" className="block text-gray-700">
|
||||
Zipcode
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="enter zip code"
|
||||
className="w-full px-3 py-2 border mb-4 rounded outline-none"
|
||||
onChange={(e) =>
|
||||
setShippingInfo({
|
||||
...shippingInfo,
|
||||
zip: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* payment Information */}
|
||||
<div className="mt-5">
|
||||
<div
|
||||
className="flex items-center justify-between"
|
||||
onClick={() => setPaymentToggle(!paymentToggle)}
|
||||
>
|
||||
<h3 className="text-lg font-semibold mb-2">Payment Method</h3>
|
||||
{paymentToggle ? <FaAngleDown /> : <FaAngleUp />}
|
||||
</div>
|
||||
|
||||
<div className={`${paymentToggle ? "" : "hidden"}`}>
|
||||
<div className="flex items-center mb-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="payment"
|
||||
checked={paymentMethod === "cod"}
|
||||
onChange={() => setPaymentMethod("cod")}
|
||||
/>
|
||||
<label className="block text-gray-700 ml-2">
|
||||
Cash on Delivery
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center mb-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="payment"
|
||||
checked={paymentMethod === "dc"}
|
||||
onChange={() => setPaymentMethod("dc")}
|
||||
/>
|
||||
<label className="block text-gray-700 ml-2">Debit Card</label>
|
||||
</div>
|
||||
{paymentMethod === "dc" && (
|
||||
<div className="bg-gray-100 p-4 rounded-lg mb-4">
|
||||
<h3 className="text-xl font-semibold mb-4">
|
||||
Debit Card Information
|
||||
</h3>
|
||||
<div className="mb-4">
|
||||
<label
|
||||
htmlFor=""
|
||||
className="block text-gray-700 font-semibold mb-2"
|
||||
>
|
||||
Card Number
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="enter card number"
|
||||
className="border p-2 w-full rounded outline-none"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 font-semibold mb-2">
|
||||
Card Holder Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="enter card holder name"
|
||||
className="border p-2 w-full rounded outline-none"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between mb-4">
|
||||
<div className="w-1/2 mr-2">
|
||||
<label
|
||||
htmlFor=""
|
||||
className="block text-gray-700 font-semibold mb-2"
|
||||
>
|
||||
Expire Date
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="MM/YY"
|
||||
className="p-2 border w-full rounded outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-1/2 ml-2">
|
||||
<label
|
||||
htmlFor=""
|
||||
className="block text-gray-700 font-semibold mb-2"
|
||||
>
|
||||
CVV
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="CVV"
|
||||
className="border p-2 w-full rounded outline-none"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="md:w-1/3 bg-white p-6 rounded-lg shadow-md border mt-8 md:mt-0">
|
||||
<h3 className="text-lg font-semibold mb-4">Order Summary</h3>
|
||||
<div className="space-y-4">
|
||||
{cart.products.map((product) => (
|
||||
<div key={product.id} className="flex justify-between">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={product.image}
|
||||
alt={product.name}
|
||||
className="w-16 h-16 object-contain rounded"
|
||||
/>
|
||||
<div className="ml-4">
|
||||
<h4 className="text-md font-semibold">{product.name}</h4>
|
||||
<p className="text-gray-600">
|
||||
${product.price} X {product.quantity}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-gray-800">
|
||||
${product.price}
|
||||
{/* * {product.quantity} */}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-4 border-t pt-4">
|
||||
<div className="flex justify-between">
|
||||
<span>Total Price: </span>
|
||||
<span className="font-semibold">
|
||||
${cart.totalPrice.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="w-full bg-red-600 text-white py-2 mt-6 hover:bg-red-800"
|
||||
onClick={handleOrder}
|
||||
>
|
||||
Place Order
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Checkout;
|
|
@ -1,119 +0,0 @@
|
|||
import React from "react";
|
||||
import { IoLocationSharp } from "react-icons/io5";
|
||||
import { IoMdMail } from "react-icons/io";
|
||||
import { FaPhone } from "react-icons/fa6";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
|
||||
const Contact = () => {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 py-12 px-4 md:px-16 lg:px-24">
|
||||
<Helmet>
|
||||
<title>Contact Us - E-Shop</title>
|
||||
<meta name="description" content="" />
|
||||
</Helmet>
|
||||
|
||||
<div className="max-w-5xl mx-auto bg-white shadow-lg rounded-lg overflow-hidden">
|
||||
<div className="flex flex-col md:flex-row">
|
||||
<div className="bg-red-600 text-white p-8 md:w-1/2 flex flex-col justify-center">
|
||||
<h2 className="text-3xl font-bold mb-4">Contact Us</h2>
|
||||
|
||||
<p className="mb-6">
|
||||
Have any questions? We'd love to hear from you. Feel free to reach
|
||||
out to us!
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center">
|
||||
<IoLocationSharp className="text-xl mr-2" />
|
||||
<p>123 Street Name, City, State, ZIP</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<IoMdMail className="text-xl mr-2" />
|
||||
<p>contact@example.com</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<FaPhone className="text-xl mr-2" />
|
||||
|
||||
<p>+1 (555) 123-4567</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-8 md:w-1/2">
|
||||
<h2 className="text-3xl uppercase font-bold mb-6 text-gray-800">
|
||||
Get in Touch
|
||||
</h2>
|
||||
|
||||
<form action="#" method="POST" className="space-y-6">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
id="name"
|
||||
className="mt-1 block w-full px-4 py-2 outline-none border border-gray-300 rounded-md shadow-sm"
|
||||
placeholder="your name"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
id="email"
|
||||
className="mt-1 block w-full px-4 py-2 outline-none border border-gray-300 rounded-md shadow-sm"
|
||||
placeholder="enter your email"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="message"
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
Message
|
||||
</label>
|
||||
|
||||
<textarea
|
||||
name="message"
|
||||
id="message"
|
||||
rows="4"
|
||||
className="mt-1 block w-full px-4 py-2 outline-none border border-gray-300 rounded-md shadow-sm"
|
||||
placeholder="enter your message"
|
||||
required
|
||||
></textarea>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full py-2 px-4 bg-red-600 text-white font-bold
|
||||
rounded-md hover:bg-red-700 mt-3"
|
||||
>
|
||||
Send Message
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contact;
|
|
@ -1,105 +0,0 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import Hero from "../assets/images/hero-banner.jpg";
|
||||
import InfoSection from "../components/InfoSection";
|
||||
import CategorySection from "../components/CategorySection";
|
||||
import { setProducts } from "../redux/productSlice";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { categories, mockData } from "../assets/mockData";
|
||||
import ProductCard from "../components/ProductCard";
|
||||
import { Link } from "react-router";
|
||||
import Shop from "./Shop";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
const Home = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const products = useSelector((state) => state.products);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setProducts(mockData));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 1500);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Home - E-Shop</title>
|
||||
<meta name="description" content="" />
|
||||
</Helmet>
|
||||
|
||||
<div className="bg-white mt-2 px-4 md:px-16 lg:px-24">
|
||||
<div className="container mx-auto py-4 flex flex-col md:flex-row md:space-x-4">
|
||||
{/* Categories Section */}
|
||||
<div className="w-full md:w-3/12">
|
||||
<div className="bg-red-600 text-white text-sm font-bold px-2 py-2.5">
|
||||
Shop By Categories
|
||||
</div>
|
||||
<ul className="space-y-4 bg-gray-100 p-3 border">
|
||||
{categories.map((category, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="flex items-center text-sm font-medium"
|
||||
>
|
||||
<div className="w-2 h-2 border border-red-500 rounded-full mr-2"></div>
|
||||
<div>{category}</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Hero Section */}
|
||||
<div className="w-full md:w-9/12 relative">
|
||||
<div className="w-full">
|
||||
<img
|
||||
src={Hero}
|
||||
alt="Hero Banner"
|
||||
className="w-full h-96 object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute inset-0 flex flex-col justify-center items-center text-center bg-black bg-opacity-50 text-white">
|
||||
<h2 className="text-2xl md:text-4xl font-bold">
|
||||
WELCOME TO E-SHOP
|
||||
</h2>
|
||||
<p className="text-base md:text-lg">MILLIONS + PRODUCTS</p>
|
||||
<Link to="/shop">
|
||||
<button
|
||||
className="bg-red-600 px-8 py-1.5 text-white mt-4 hover:bg-red-700 transform
|
||||
transition-transform duration-300 hover:scale-105"
|
||||
>
|
||||
SHOP NOW
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<InfoSection />
|
||||
<CategorySection />
|
||||
<div className="container mx-auto py-12">
|
||||
{loading ? (
|
||||
<div className="h-8 w-32 mx-auto bg-gray-300 rounded-xl mb-6 animate-pulse"></div>
|
||||
) : (
|
||||
<h2 className="text-2xl font-bold mb-6 text-center">
|
||||
Top Products
|
||||
</h2>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-6 cursor-pointer">
|
||||
{products?.products?.slice(0, 5).map((product) => (
|
||||
<ProductCard product={product} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Shop />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
|
@ -1,57 +0,0 @@
|
|||
import React from "react";
|
||||
const Login = ({ openSignUp }) => {
|
||||
return (
|
||||
<div className="max-w-md mx-auto p-6 bg-white rounded-lg">
|
||||
<h2 className="text-2xl font-bold mb-4">Login</h2>
|
||||
<form action="">
|
||||
<div className="mb-4">
|
||||
<label htmlFor="block text-gray-700 mb-1">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
className="w-full px-4 py-2 border border-gray-300 outline-none rounded-lg"
|
||||
placeholder="enter an email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 mb-1">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg outline-none"
|
||||
placeholder="enter password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<label htmlFor="inline-flex items-center">
|
||||
<input type="checkbox" className="form-checkbox" />
|
||||
<span className="ml-2 text-gray-700 ">Remember Me</span>
|
||||
</label>
|
||||
<a href="/" className="text-gray-700">
|
||||
Forgot Password?
|
||||
</a>
|
||||
</div>
|
||||
<div className="mb-4 ">
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-red-600 text-white py-2 rounded-full hover:bg-red-700"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="text-center">
|
||||
<span className="text-gray-700">Don't have an account ? </span>
|
||||
<button
|
||||
className="text-red-600 hover:text-red-700 hover:underline"
|
||||
onClick={openSignUp}
|
||||
>
|
||||
Sign Up
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
|
@ -1,57 +0,0 @@
|
|||
import React from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
const Order = ({ order }) => {
|
||||
const navigate = useNavigate("/");
|
||||
return (
|
||||
<div className="container mx-auto py-8 px-4 md:px-16 lg:px-24">
|
||||
<h2 className="text-2xl font-semibold mb-4">Thank you for your order</h2>
|
||||
<p>
|
||||
Your order has been placed successfully you will recieve email shortly.
|
||||
</p>
|
||||
<div className="mt-6 p-4 border rounded-lg bg-gray-100">
|
||||
<h3 className="text-lg font-semibold mb-2">Order Summary</h3>
|
||||
<p>Order Number : {order.orderNumber}</p>
|
||||
<div className="mt-4">
|
||||
<h4 className="text-md font-semibold mb-2">Shipping Information</h4>
|
||||
<p>{order.shippingInformation.address}</p>
|
||||
<p>{order.shippingInformation.city}</p>
|
||||
<p>{order.shippingInformation.zip}</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<h3 className="text-md font-semibold mb-2">Items Ordered</h3>
|
||||
{order.products.map((product) => (
|
||||
<div key={product.id} className="flex justify-between mt-2">
|
||||
<p>
|
||||
{product.name} (X{product.quantity})
|
||||
</p>
|
||||
|
||||
<p>{product.price * product.quantity}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-4 flex justify-between">
|
||||
<span>Total Price:</span>
|
||||
<span className="font-semibold">{order.totalPrice}</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<button className="bg-green-500 text-white py-2 px-4 hover:bg-green-600">
|
||||
Order Tracking
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="ml-4 bg-red-600 text-white py-2 px-4 hover:bg-red-800
|
||||
"
|
||||
onClick={() => navigate("/")}
|
||||
>
|
||||
Continue Shopping
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Order;
|
|
@ -1,94 +0,0 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { FaCarSide } from "react-icons/fa";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useParams } from "react-router";
|
||||
import { FaQuestion } from "react-icons/fa";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
const ProductDetail = () => {
|
||||
const { id } = useParams();
|
||||
const products = useSelector((state) => state.products.products);
|
||||
const [product, setProduct] = useState();
|
||||
const [quantity, setQuantity] = useState(1);
|
||||
useEffect(() => {
|
||||
const newProduct = products.find((product) => product.id === parseInt(id));
|
||||
setProduct(newProduct);
|
||||
}, [id, products]);
|
||||
|
||||
if (!product) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-8 px-4 md:px-16 lg:px-24">
|
||||
<Helmet>
|
||||
<title>{product.name} - Product Description</title>
|
||||
|
||||
<meta
|
||||
name="description"
|
||||
content={`Details about ${product.name}`}
|
||||
></meta>
|
||||
</Helmet>
|
||||
|
||||
<div className="flex flex-col md:flex-row gap-x-16">
|
||||
{/* product image */}
|
||||
<div className="md:w-1/2 py-4 shadow-md md:px-8 h-96 flex justify-center">
|
||||
<img src={product.image} alt={product.name} className="h-full" />
|
||||
</div>
|
||||
|
||||
{/* product information */}
|
||||
<div className="md:w-1/2 p-4 shadow-md md:p-16 flex flex-col items-center gap-y-2">
|
||||
<h2 className="text-3xl font-semibold mb-2"> {product.name} </h2>
|
||||
<p className="text-xl font-semibold text-gray-800 mb-4">
|
||||
${product.price}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap items-center justify-center mb-4 gap-4">
|
||||
<button
|
||||
className="bg-red-600 hover:bg-red-700 text-xl font-bold text-white w-10 h-10
|
||||
rounded-full"
|
||||
onClick={() => setQuantity(quantity > 1 ? quantity - 1 : 1)}
|
||||
>
|
||||
-
|
||||
</button>
|
||||
<input
|
||||
type="text"
|
||||
id="quantity"
|
||||
value={quantity}
|
||||
className="border border-gray-300 text-center text-lg font-semibold rounded-md p-2 w-14"
|
||||
/>
|
||||
<button
|
||||
className="bg-red-600 hover:bg-red-700 text-xl font-bold text-white w-10 h-10
|
||||
rounded-full"
|
||||
onClick={() => setQuantity(quantity + 1)}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
|
||||
<button className="bg-red-600 text-white py-1.5 px-4 hover:bg-red-800">
|
||||
Add to Cart
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-y-4 mt-4">
|
||||
<p className="flex items-center">
|
||||
<FaCarSide className="mr-1" />
|
||||
Delivery & Return
|
||||
</p>
|
||||
|
||||
<p className="flex items-center">
|
||||
<FaQuestion className="mr-1" />
|
||||
Ask a Question
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<h3 className="text-xl font-bold mb-2">Product Description</h3>
|
||||
<p>Product Description will goes here.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductDetail;
|
|
@ -1,62 +0,0 @@
|
|||
import React from "react";
|
||||
const Register = ({ openLogin }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="max-w-md mx-auto p-6 bg-white rounded-lg">
|
||||
<h2 className="text-2xl font-bold mb-4 text-gray-800">Register</h2>
|
||||
<form action="">
|
||||
<div className="mb-4">
|
||||
<label htmlFor="block text-gray-700 mb-1">Name</label>
|
||||
<input
|
||||
type="name"
|
||||
className="w-full px-4 py-2 border border=gray-300 rounded-lg outline-none"
|
||||
placeholder="enter your name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 mb-1 ">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg
|
||||
outline-none"
|
||||
placeholder="enter an email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 mb-1">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg
|
||||
outline-none"
|
||||
placeholder="enter a password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4 ">
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-red-600 text-white py-2 rounded-full hover:bg-red-700
|
||||
transition duration-300"
|
||||
>
|
||||
Sign Up
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="text-center">
|
||||
<span className="text-gray-700">Already have an account ? </span>
|
||||
<button
|
||||
className="text-red-600 hover:underline focus:outline-none"
|
||||
onClick={openLogin}
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Register;
|
|
@ -1,37 +0,0 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import ProductCard from "../components/ProductCard";
|
||||
|
||||
const Shop = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const products = useSelector((state) => state.products);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 1500);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mx-auto py-12 px-4 md:px-16 lg:px-24">
|
||||
{loading ? (
|
||||
<div className="h-8 w-32 mx-auto mb-6 bg-gray-300 rounded-xl animate-pulse"></div>
|
||||
) : (
|
||||
<h2 className="text-2xl font-bold mb-6 text-center">Shop</h2>
|
||||
)}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-6 cursor-pointer">
|
||||
{products?.products?.map((product) => (
|
||||
<ProductCard product={product} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Shop;
|
|
@ -1,75 +0,0 @@
|
|||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
const initialState = {
|
||||
products: [],
|
||||
totalQuantity: 0,
|
||||
totalPrice: 0,
|
||||
};
|
||||
|
||||
export const cartSlice = createSlice({
|
||||
name: "cart",
|
||||
initialState,
|
||||
reducers: {
|
||||
addToCart(state, action) {
|
||||
const newItem = action.payload;
|
||||
const itemIndex = state.products.find((item) => item.id === newItem.id);
|
||||
|
||||
if (itemIndex) {
|
||||
itemIndex.quantity++;
|
||||
itemIndex.totalPrice += newItem.price;
|
||||
} else {
|
||||
state.products.push({
|
||||
id: newItem.id,
|
||||
name: newItem.name,
|
||||
price: newItem.price,
|
||||
quantity: 1,
|
||||
totalPrice: newItem.price,
|
||||
image: newItem.image,
|
||||
});
|
||||
}
|
||||
|
||||
state.totalPrice += newItem.price;
|
||||
state.totalQuantity++;
|
||||
},
|
||||
|
||||
removeFromCart(state, action) {
|
||||
const id = action.payload;
|
||||
const findItem = state.products.find((item) => item.id === id);
|
||||
|
||||
if (findItem) {
|
||||
state.totalPrice -= findItem.totalPrice;
|
||||
state.totalQuantity -= findItem.quantity;
|
||||
state.products = state.products.filter((item) => item.id !== id);
|
||||
}
|
||||
},
|
||||
|
||||
increaseQuantity(state, action) {
|
||||
const id = action.payload;
|
||||
const findItem = state.products.find((item) => item.id === id);
|
||||
if (findItem) {
|
||||
findItem.quantity++;
|
||||
findItem.totalPrice += findItem.price;
|
||||
state.totalQuantity++;
|
||||
state.totalPrice += findItem.price;
|
||||
}
|
||||
},
|
||||
|
||||
decreaseQuantity(state, action) {
|
||||
const id = action.payload;
|
||||
const findItem = state.products.find((item) => item.id === id);
|
||||
|
||||
if (findItem.quantity > 1) {
|
||||
if (findItem) {
|
||||
findItem.quantity--;
|
||||
findItem.totalPrice -= findItem.price;
|
||||
state.totalQuantity--;
|
||||
state.totalPrice -= findItem.price;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { addToCart, removeFromCart, increaseQuantity, decreaseQuantity } =
|
||||
cartSlice.actions;
|
||||
export default cartSlice.reducer;
|
|
@ -1,28 +0,0 @@
|
|||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
const initialState = {
|
||||
products: [],
|
||||
searchTerm: "",
|
||||
filterData: [],
|
||||
};
|
||||
|
||||
export const productSlice = createSlice({
|
||||
name: "products",
|
||||
initialState,
|
||||
reducers: {
|
||||
setProducts(state, action) {
|
||||
state.products = action.payload;
|
||||
},
|
||||
|
||||
setSearchTerm(state, action) {
|
||||
state.searchTerm = action.payload;
|
||||
state.filterData = state.products.filter((product) =>
|
||||
product.name.toLowerCase().includes(state.searchTerm.toLowerCase())
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setSearchTerm, setProducts } = productSlice.actions;
|
||||
|
||||
export default productSlice.reducer;
|
|
@ -1,9 +0,0 @@
|
|||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import cartReducer from "./cartSlice";
|
||||
import productReducer from "./productSlice";
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
cart: cartReducer,
|
||||
products: productReducer,
|
||||
},
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
|
@ -1,7 +0,0 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
Loading…
Reference in a new issue