├── src ├── Data │ ├── testimoni.json │ ├── orders.json │ └── customers.json ├── main.jsx ├── components │ ├── logo.jsx │ ├── StatCard.jsx │ ├── ErrorPage.jsx │ ├── Loading.jsx │ ├── ScrollToTopButton.jsx │ ├── ErrorBoundary.jsx │ ├── PageHeader.jsx │ ├── navbar.jsx │ ├── SpotLightCard.jsx │ ├── ListMenu1.jsx │ ├── Header.jsx │ ├── LiveBidding.jsx │ ├── NewItem.jsx │ ├── Sidebar.jsx │ ├── Testimonials.jsx │ ├── About.jsx │ ├── colection.jsx │ ├── ListMenu.jsx │ ├── News.jsx │ ├── TopSellers.jsx │ ├── FlowingMenu.jsx │ ├── Footer.jsx │ ├── Services.jsx │ ├── AnimatedList.jsx │ ├── VariableProximity.jsx │ ├── Balatro.jsx │ └── VertexShader.jsx ├── layouts │ ├── AuthLayout.jsx │ ├── MainLayout.jsx │ └── GuestLayout.jsx ├── pages │ ├── FoodPages.jsx │ ├── UserPages.jsx │ ├── CustomerPages.jsx │ ├── Error400.jsx │ ├── Error401.jsx │ ├── Error403.jsx │ ├── GuestPage.jsx │ ├── Auth │ │ ├── Forgot.jsx │ │ ├── Register.jsx │ │ └── Login.jsx │ ├── hero.jsx │ ├── Orders.jsx │ ├── Dashboard.jsx │ ├── Food.jsx │ ├── Customers.jsx │ ├── User1.jsx │ ├── User.jsx │ └── Customers1.jsx ├── context │ └── BreadcrumbContext.jsx ├── App.css ├── assets │ ├── tailwind.css │ └── react.svg ├── index.css └── App.jsx ├── public ├── wallet │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ └── 8.png ├── img │ ├── bg │ │ ├── noise.gif │ │ ├── bg-image-22.jpg │ │ └── bg-image-23.jpg │ ├── makanan.jpg │ ├── logo-nanta.png │ ├── produk │ │ ├── abon.jpg │ │ ├── kopi.jpg │ │ ├── dodol.jpg │ │ ├── sambal.jpg │ │ ├── granola.jpg │ │ ├── keripik.jpg │ │ ├── upload.svg │ │ ├── camera.svg │ │ ├── menu.svg │ │ └── delivery.svg │ ├── shape │ │ ├── shape-1.png │ │ ├── shape-5.png │ │ ├── shape-6.png │ │ └── shape-7.png │ ├── 20250430_235827.png │ ├── client │ │ ├── client-1.png │ │ ├── client-10.png │ │ ├── client-11.png │ │ ├── client-12.png │ │ ├── client-13.png │ │ ├── client-14.png │ │ ├── client-15.png │ │ ├── client-2.png │ │ ├── client-3.png │ │ ├── client-4.png │ │ ├── client-5.png │ │ ├── client-6.png │ │ ├── client-7.png │ │ ├── client-8.png │ │ └── client-9.png │ ├── portfolio │ │ ├── portfolio-01.jpg │ │ ├── portfolio-02.jpg │ │ ├── portfolio-03.jpg │ │ ├── portfolio-04.jpg │ │ ├── portfolio-05.jpg │ │ ├── portfolio-06.jpg │ │ ├── portfolio-07.jpg │ │ ├── portfolio-09.jpg │ │ └── portfolio-10.jpg │ └── collection │ │ ├── collection-lg-01.jpg │ │ ├── collection-lg-02.jpg │ │ ├── collection-lg-03.jpg │ │ ├── collection-lg-04.jpg │ │ ├── collection-sm-01.jpg │ │ ├── collection-sm-02.jpg │ │ ├── collection-sm-03.jpg │ │ ├── collection-sm-04.jpg │ │ ├── collection-sm-05.jpg │ │ ├── collection-sm-06.jpg │ │ ├── collection-sm-07.jpg │ │ ├── collection-sm-08.jpg │ │ ├── collection-sm-09.jpg │ │ ├── collection-sm-10.jpg │ │ ├── collection-sm-11.jpg │ │ └── collection-sm-12.jpg ├── font │ ├── Barlow-Regular.ttf │ ├── Poppins-Regular.ttf │ └── Poppins-ExtraBold.ttf └── vite.svg ├── vercel.json ├── vite.config.js ├── .gitignore ├── products.html ├── index.html ├── eslint.config.js ├── package.json └── README.md /src/Data/testimoni.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/wallet/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/wallet/1.png -------------------------------------------------------------------------------- /public/wallet/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/wallet/2.png -------------------------------------------------------------------------------- /public/wallet/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/wallet/3.png -------------------------------------------------------------------------------- /public/wallet/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/wallet/4.png -------------------------------------------------------------------------------- /public/wallet/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/wallet/5.png -------------------------------------------------------------------------------- /public/wallet/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/wallet/6.png -------------------------------------------------------------------------------- /public/wallet/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/wallet/7.png -------------------------------------------------------------------------------- /public/wallet/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/wallet/8.png -------------------------------------------------------------------------------- /public/img/bg/noise.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/bg/noise.gif -------------------------------------------------------------------------------- /public/img/makanan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/makanan.jpg -------------------------------------------------------------------------------- /public/img/logo-nanta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/logo-nanta.png -------------------------------------------------------------------------------- /public/img/produk/abon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/produk/abon.jpg -------------------------------------------------------------------------------- /public/img/produk/kopi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/produk/kopi.jpg -------------------------------------------------------------------------------- /public/img/produk/dodol.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/produk/dodol.jpg -------------------------------------------------------------------------------- /public/img/produk/sambal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/produk/sambal.jpg -------------------------------------------------------------------------------- /public/img/shape/shape-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/shape/shape-1.png -------------------------------------------------------------------------------- /public/img/shape/shape-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/shape/shape-5.png -------------------------------------------------------------------------------- /public/img/shape/shape-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/shape/shape-6.png -------------------------------------------------------------------------------- /public/img/shape/shape-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/shape/shape-7.png -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | { "source": "/(.*)", "destination": "/" } 4 | ] 5 | } 6 | 7 | -------------------------------------------------------------------------------- /public/font/Barlow-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/font/Barlow-Regular.ttf -------------------------------------------------------------------------------- /public/font/Poppins-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/font/Poppins-Regular.ttf -------------------------------------------------------------------------------- /public/img/20250430_235827.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/20250430_235827.png -------------------------------------------------------------------------------- /public/img/bg/bg-image-22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/bg/bg-image-22.jpg -------------------------------------------------------------------------------- /public/img/bg/bg-image-23.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/bg/bg-image-23.jpg -------------------------------------------------------------------------------- /public/img/client/client-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/client/client-1.png -------------------------------------------------------------------------------- /public/img/client/client-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/client/client-10.png -------------------------------------------------------------------------------- /public/img/client/client-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/client/client-11.png -------------------------------------------------------------------------------- /public/img/client/client-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/client/client-12.png -------------------------------------------------------------------------------- /public/img/client/client-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/client/client-13.png -------------------------------------------------------------------------------- /public/img/client/client-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/client/client-14.png -------------------------------------------------------------------------------- /public/img/client/client-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/client/client-15.png -------------------------------------------------------------------------------- /public/img/client/client-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/client/client-2.png -------------------------------------------------------------------------------- /public/img/client/client-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/client/client-3.png -------------------------------------------------------------------------------- /public/img/client/client-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/client/client-4.png -------------------------------------------------------------------------------- /public/img/client/client-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/client/client-5.png -------------------------------------------------------------------------------- /public/img/client/client-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/client/client-6.png -------------------------------------------------------------------------------- /public/img/client/client-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/client/client-7.png -------------------------------------------------------------------------------- /public/img/client/client-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/client/client-8.png -------------------------------------------------------------------------------- /public/img/client/client-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/client/client-9.png -------------------------------------------------------------------------------- /public/img/produk/granola.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/produk/granola.jpg -------------------------------------------------------------------------------- /public/img/produk/keripik.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/produk/keripik.jpg -------------------------------------------------------------------------------- /public/font/Poppins-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/font/Poppins-ExtraBold.ttf -------------------------------------------------------------------------------- /public/img/portfolio/portfolio-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/portfolio/portfolio-01.jpg -------------------------------------------------------------------------------- /public/img/portfolio/portfolio-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/portfolio/portfolio-02.jpg -------------------------------------------------------------------------------- /public/img/portfolio/portfolio-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/portfolio/portfolio-03.jpg -------------------------------------------------------------------------------- /public/img/portfolio/portfolio-04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/portfolio/portfolio-04.jpg -------------------------------------------------------------------------------- /public/img/portfolio/portfolio-05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/portfolio/portfolio-05.jpg -------------------------------------------------------------------------------- /public/img/portfolio/portfolio-06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/portfolio/portfolio-06.jpg -------------------------------------------------------------------------------- /public/img/portfolio/portfolio-07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/portfolio/portfolio-07.jpg -------------------------------------------------------------------------------- /public/img/portfolio/portfolio-09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/portfolio/portfolio-09.jpg -------------------------------------------------------------------------------- /public/img/portfolio/portfolio-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/portfolio/portfolio-10.jpg -------------------------------------------------------------------------------- /public/img/collection/collection-lg-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/collection/collection-lg-01.jpg -------------------------------------------------------------------------------- /public/img/collection/collection-lg-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/collection/collection-lg-02.jpg -------------------------------------------------------------------------------- /public/img/collection/collection-lg-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/collection/collection-lg-03.jpg -------------------------------------------------------------------------------- /public/img/collection/collection-lg-04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/collection/collection-lg-04.jpg -------------------------------------------------------------------------------- /public/img/collection/collection-sm-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/collection/collection-sm-01.jpg -------------------------------------------------------------------------------- /public/img/collection/collection-sm-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/collection/collection-sm-02.jpg -------------------------------------------------------------------------------- /public/img/collection/collection-sm-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/collection/collection-sm-03.jpg -------------------------------------------------------------------------------- /public/img/collection/collection-sm-04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/collection/collection-sm-04.jpg -------------------------------------------------------------------------------- /public/img/collection/collection-sm-05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/collection/collection-sm-05.jpg -------------------------------------------------------------------------------- /public/img/collection/collection-sm-06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/collection/collection-sm-06.jpg -------------------------------------------------------------------------------- /public/img/collection/collection-sm-07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/collection/collection-sm-07.jpg -------------------------------------------------------------------------------- /public/img/collection/collection-sm-08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/collection/collection-sm-08.jpg -------------------------------------------------------------------------------- /public/img/collection/collection-sm-09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/collection/collection-sm-09.jpg -------------------------------------------------------------------------------- /public/img/collection/collection-sm-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/collection/collection-sm-10.jpg -------------------------------------------------------------------------------- /public/img/collection/collection-sm-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/collection/collection-sm-11.jpg -------------------------------------------------------------------------------- /public/img/collection/collection-sm-12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ananta-TI/React-Web-Sedap/HEAD/public/img/collection/collection-sm-12.jpg -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import tailwindcss from '@tailwindcss/vite' 4 | 5 | // https://vite.dev/config/ 6 | export default defineConfig({ 7 | base: '/React-Web-Sedap/', 8 | plugins: [ 9 | react(), 10 | tailwindcss(), 11 | ], 12 | }) 13 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import App from './App.jsx'; 4 | import { BrowserRouter } from 'react-router-dom'; 5 | 6 | createRoot(document.getElementById("root")).render( 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /src/components/logo.jsx: -------------------------------------------------------------------------------- 1 | export default function Navbar() { 2 | return ( 3 |
4 |
5 | S 6 |
7 | edap 8 |
9 | ) 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/layouts/AuthLayout.jsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | import Footer from "../components/Footer"; 3 | import Navbar from "../components/Navbar"; 4 | 5 | export default function AuthLayout() { 6 | return ( 7 |
8 | 9 | 10 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/pages/FoodPages.jsx: -------------------------------------------------------------------------------- 1 | 2 | import Footer from '../components/Footer'; 3 | import Food from './Food'; 4 | // import Testimonials from '../components/Testimonials'; 5 | // import TopProducts from '../components/TopProduct'; 6 | 7 | export default function FoodPage() { 8 | return ( 9 |
10 | 11 | 12 |
15 | ); 16 | } -------------------------------------------------------------------------------- /src/pages/UserPages.jsx: -------------------------------------------------------------------------------- 1 | 2 | import Footer from '../components/Footer'; 3 | import Users from './User'; 4 | // import Testimonials from '../components/Testimonials'; 5 | // import TopProducts from '../components/TopProduct'; 6 | 7 | export default function UserPage() { 8 | return ( 9 |
10 | 11 | 12 |
15 | ); 16 | } -------------------------------------------------------------------------------- /public/img/produk/upload.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/CustomerPages.jsx: -------------------------------------------------------------------------------- 1 | 2 | import Footer from '../components/Footer'; 3 | import Customers from './Customers'; 4 | // import Testimonials from '../components/Testimonials'; 5 | // import TopProducts from '../components/TopProduct'; 6 | export default function CustPage({ customers }) { 7 | return ( 8 |
9 | 10 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /products.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Pertemuan 2 | React 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | NTA 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/StatCard.jsx: -------------------------------------------------------------------------------- 1 | // components/StatCard.jsx 2 | export default function StatCard({ icon, color, value, label }) { 3 | return ( 4 |
5 |
6 | {icon} 7 |
8 |
9 |
{value}
10 |
{label}
11 |
12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/layouts/MainLayout.jsx: -------------------------------------------------------------------------------- 1 | import Sidebar from "../components/Sidebar.jsx" 2 | import Header from "../components/Header.jsx" 3 | import { Outlet } from "react-router-dom" 4 | 5 | export default function MainLayout() { 6 | return ( 7 |
8 |
9 | 10 |
11 |
12 | 13 |
14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/layouts/GuestLayout.jsx: -------------------------------------------------------------------------------- 1 | import Navbar from "../components/Navbar"; 2 | import { Outlet } from "react-router-dom"; 3 | import SplashCursor from "../components/SplashCursor"; 4 | import ScrollToTopButton from "../components/ScrollToTopButton"; 5 | 6 | export default function GuestLayout() { 7 | return ( 8 |
9 | 10 |
11 | {/* */} 12 | 13 | 14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/context/BreadcrumbContext.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useState } from "react"; 2 | // Membuat context untuk breadcrumb 3 | const BreadcrumbContext = createContext(); 4 | export const useBreadcrumb = () => { 5 | return useContext(BreadcrumbContext); 6 | }; 7 | export const BreadcrumbProvider = ({ children }) => { 8 | const [breadcrumb, setBreadcrumb] = useState(["Home", "Dashboard"]); 9 | const updateBreadcrumb = (newBreadcrumb) => { 10 | setBreadcrumb(newBreadcrumb); 11 | }; 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/pages/Error400.jsx: -------------------------------------------------------------------------------- 1 | export default function Error() { 2 | return ( 3 |
4 | Error Illustration 9 |

400 - Bad Request

10 |

11 | Maaf, halaman yang kamu cari tidak tersedia atau sudah dipindahkan. 12 |

13 | 17 | Kembali ke Beranda 18 | 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /src/pages/Error401.jsx: -------------------------------------------------------------------------------- 1 | export default function Error() { 2 | return ( 3 |
4 | Error Illustration 9 |

401 - Unauthorized

10 |

11 | Maaf, halaman yang kamu cari tidak tersedia atau sudah dipindahkan. 12 |

13 | 17 | Kembali ke Beranda 18 | 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/pages/Error403.jsx: -------------------------------------------------------------------------------- 1 | export default function Error() { 2 | return ( 3 |
4 | Error Illustration 9 |

403 - Halaman Tidak Ditemukan

10 |

11 | Maaf, halaman yang kamu cari tidak tersedia atau sudah dipindahkan. 12 |

13 | 17 | Kembali ke Beranda 18 | 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/assets/tailwind.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | /* @font-face { 3 | font-family: 'Barlow'; 4 | src: url('/font/Barlow-Regular.ttf'); 5 | } 6 | @font-face { 7 | font-family: 'Poppins'; 8 | src: url('/font/Poppins-Regular.ttf'); 9 | } 10 | @font-face { 11 | font-family: 'PoppinsBold'; 12 | src: url('/font/Poppins-ExtraBold.ttf'); 13 | } 14 | 15 | @theme { 16 | --font-barlow: "Barlow", sans-serif; 17 | --font-Poppins: "Poppins", sans-serif; 18 | --font-PoppinsBold: "PoppinsBold", sans-serif; 19 | --color-latar: #f3f4f6; 20 | --color-teks: #374151; 21 | --color-teks-samping: #6b7280; 22 | --color-garis: #e5e7eb; 23 | --color-hijau: #00B074; 24 | --color-merah: #ef4444; 25 | --color-biru: #3b82f6; 26 | --color-kuning: #f59e0b; 27 | } 28 | 29 | body { 30 | @apply font-barlow; 31 | } */ -------------------------------------------------------------------------------- /src/components/ErrorPage.jsx: -------------------------------------------------------------------------------- 1 | import { useRouteError } from "react-router-dom"; 2 | 3 | export default function ErrorPage() { 4 | const error = useRouteError(); 5 | console.error(error); 6 | 7 | return ( 8 |
9 |
10 |

Oops!

11 |

12 | Something went wrong. 13 |

14 |

15 | {error?.statusText || error?.message || "Unknown Error"} 16 |

17 | 21 | Back to Home 22 | 23 |
24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | 6 | export default [ 7 | { ignores: ['dist'] }, 8 | { 9 | files: ['**/*.{js,jsx}'], 10 | languageOptions: { 11 | ecmaVersion: 2020, 12 | globals: globals.browser, 13 | parserOptions: { 14 | ecmaVersion: 'latest', 15 | ecmaFeatures: { jsx: true }, 16 | sourceType: 'module', 17 | }, 18 | }, 19 | plugins: { 20 | 'react-hooks': reactHooks, 21 | 'react-refresh': reactRefresh, 22 | }, 23 | rules: { 24 | ...js.configs.recommended.rules, 25 | ...reactHooks.configs.recommended.rules, 26 | 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], 27 | 'react-refresh/only-export-components': [ 28 | 'warn', 29 | { allowConstantExport: true }, 30 | ], 31 | }, 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /src/components/Loading.jsx: -------------------------------------------------------------------------------- 1 | export default function Loading() { 2 | return ( 3 |
4 |
7 | 10 | 11 |
12 | 13 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/pages/GuestPage.jsx: -------------------------------------------------------------------------------- 1 | import AboutUs from '../components/About'; 2 | // import Footer from '../components/Footer'; 3 | import Hero from '../pages/hero'; 4 | import Colection from '../components/colection' 5 | import LiveBidding from '../components/LiveBidding' 6 | import Services from '../components/Services' 7 | import News from '../components/News' 8 | import NewsItem from '../components/NewItem'; 9 | import TopSellers from '../components/TopSellers'; 10 | import Footer from '../components/Footer'; 11 | import Testimonials from '../components/Testimonials'; 12 | // import Testimonials from '../components/Testimonials'; 13 | // import TopProducts from '../components/TopProduct'; 14 | 15 | export default function GuestPage() { 16 | return ( 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {/* */} 27 |
37 | ); 38 | } -------------------------------------------------------------------------------- /src/components/ScrollToTopButton.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { FaArrowUp } from "react-icons/fa"; 3 | 4 | export default function ScrollToTopButton() { 5 | const [isVisible, setIsVisible] = useState(false); 6 | 7 | // Cek scroll position 8 | useEffect(() => { 9 | const toggleVisibility = () => { 10 | setIsVisible(window.pageYOffset > 300); // muncul setelah scroll 300px 11 | }; 12 | 13 | window.addEventListener("scroll", toggleVisibility); 14 | return () => window.removeEventListener("scroll", toggleVisibility); 15 | }, []); 16 | 17 | // Scroll ke atas 18 | const scrollToTop = () => { 19 | window.scrollTo({ 20 | top: 0, 21 | behavior: "smooth", 22 | }); 23 | }; 24 | 25 | return ( 26 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aes-app", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "homepage": "https://Ananta-TI.github.io/React-Web-Sedap", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "vite build", 10 | "lint": "eslint .", 11 | "preview": "vite preview", 12 | "deploy": "vite build && gh-pages -d dist" 13 | }, 14 | "dependencies": { 15 | "@tailwindcss/vite": "^4.0.15", 16 | "axios": "^1.9.0", 17 | "framer-motion": "^12.12.1", 18 | "gsap": "^3.13.0", 19 | "ogl": "^1.0.11", 20 | "react": "^19.0.0", 21 | "react-dom": "^19.0.0", 22 | "react-feather": "^2.0.10", 23 | "react-icons": "^5.5.0", 24 | "react-router-dom": "^7.6.0", 25 | "react-scroll": "^1.9.3", 26 | "styled-components": "^6.1.18", 27 | "swiper": "^11.2.6", 28 | "tailwindcss": "^4.0.15", 29 | "three": "^0.176.0" 30 | }, 31 | "devDependencies": { 32 | "@eslint/js": "^9.21.0", 33 | "@types/react": "^19.0.10", 34 | "@types/react-dom": "^19.0.4", 35 | "@vitejs/plugin-react": "^4.3.4", 36 | "eslint": "^9.21.0", 37 | "eslint-plugin-react-hooks": "^5.1.0", 38 | "eslint-plugin-react-refresh": "^0.4.19", 39 | "gh-pages": "^6.3.0", 40 | "globals": "^15.15.0", 41 | "vite": "^6.2.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/ErrorBoundary.jsx: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | 3 | export default class ErrorBoundary extends Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { hasError: false, error: null }; 7 | } 8 | 9 | static getDerivedStateFromError(error) { 10 | return { hasError: true, error: error }; 11 | } 12 | 13 | componentDidCatch(error, errorInfo) { 14 | console.error("Error caught by boundary:", error, errorInfo); 15 | } 16 | 17 | render() { 18 | if (this.state.hasError) { 19 | return ( 20 |
21 |
22 |

HA!! Nyari Apo?

23 |

24 | Halaman atau komponen yang kamu cari tidak bisa ditampilkan. 25 |

26 | 30 | Kembali ke Beranda 31 | 32 |
33 |
34 | ); 35 | } 36 | 37 | return this.props.children; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/PageHeader.jsx: -------------------------------------------------------------------------------- 1 | export default function PageHeader({ title = "Dashboard", breadcrumb, children }) { 2 | const renderBreadcrumb = () => { 3 | if (typeof breadcrumb === "string") { 4 | return
{breadcrumb}
; 5 | } 6 | if (Array.isArray(breadcrumb)) { 7 | return ( 8 |
9 | {breadcrumb.map((item, index) => ( 10 | 11 | {item} 12 | {index < breadcrumb.length - 1 && /} 13 | 14 | ))} 15 |
16 | ); 17 | } 18 | return null; 19 | }; 20 | return ( 21 |
25 |
26 | 27 | {title} 28 | 29 | 32 |
33 | 34 |
35 | {children} 36 |
37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #747bff; 64 | } 65 | button { 66 | background-color: #f9f9f9; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/navbar.jsx: -------------------------------------------------------------------------------- 1 | import { IoWallet } from "react-icons/io5"; 2 | import { useEffect, useState } from "react"; 3 | import { FaSun, FaMoon } from "react-icons/fa"; 4 | import ListMenu from "./ListMenu"; 5 | import Logo from "./logo"; 6 | 7 | export default function Navbar() { 8 | const [isDarkMode, setIsDarkMode] = useState(true); 9 | 10 | useEffect(() => { 11 | if (isDarkMode) { 12 | document.documentElement.classList.add("dark"); 13 | } else { 14 | document.documentElement.classList.remove("dark"); 15 | } 16 | }, [isDarkMode]); 17 | const toggleDarkMode = () => { 18 | setIsDarkMode(!isDarkMode); 19 | }; 20 | return ( 21 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/components/SpotLightCard.jsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from "react"; 2 | 3 | const SpotlightCard = ({ children, className = "", spotlightColor = "#4C1F7A" }) => { 4 | const divRef = useRef(null); 5 | const [isFocused, setIsFocused] = useState(false); 6 | const [position, setPosition] = useState({ x: 0, y: 0 }); 7 | const [opacity, setOpacity] = useState(0); 8 | 9 | const handleMouseMove = (e) => { 10 | if (!divRef.current || isFocused) return; 11 | 12 | const rect = divRef.current.getBoundingClientRect(); 13 | setPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top }); 14 | }; 15 | 16 | const handleFocus = () => { 17 | setIsFocused(true); 18 | setOpacity(0.6); 19 | }; 20 | 21 | const handleBlur = () => { 22 | setIsFocused(false); 23 | setOpacity(0); 24 | }; 25 | 26 | const handleMouseEnter = () => { 27 | setOpacity(0.6); 28 | }; 29 | 30 | const handleMouseLeave = () => { 31 | setOpacity(0); 32 | }; 33 | 34 | return ( 35 |
44 |
51 | {children} 52 |
53 | ); 54 | }; 55 | 56 | export default SpotlightCard; -------------------------------------------------------------------------------- /src/components/ListMenu1.jsx: -------------------------------------------------------------------------------- 1 | import { MdDashboard, MdPeople, MdAssignment, MdShoppingCart } from "react-icons/md"; 2 | import { useBreadcrumb } from "../context/BreadcrumbContext"; 3 | import { useNavigate } from "react-router-dom"; 4 | import { useState } from "react"; 5 | 6 | export default function SidebarMenu() { 7 | const { updateBreadcrumb } = useBreadcrumb(); 8 | const navigate = useNavigate(); 9 | 10 | const menuItems = [ 11 | { label: "Dashboard", icon: , path: "/", key: "dashboard" }, 12 | { label: "Users1", icon: , path: "/users1", key: "users1" }, 13 | { label: "Customers1", icon: , path: "/customers1", key: "customers1" }, 14 | { label: "Orders", icon: , path: "/orders", key: "orders" }, 15 | ]; 16 | 17 | const handleClick = (key) => { 18 | const breadcrumbs = { 19 | dashboard: ["Dashboard"], 20 | users1: ["Users1"], 21 | customers: ["Customers1"], 22 | orders: ["Orders"], 23 | }; 24 | updateBreadcrumb(breadcrumbs[key] || ["Dashboard"]); 25 | }; 26 | 27 | const handleNavigation = (path, key) => { 28 | handleClick(key); 29 | navigate(path); 30 | }; 31 | 32 | return ( 33 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /public/img/produk/camera.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import { FaBell, FaSearch } from "react-icons/fa"; 2 | import { FcAreaChart } from "react-icons/fc"; 3 | import { SlSettings } from "react-icons/sl"; 4 | 5 | export default function Header({ searchTerm, setSearchTerm }) { 6 | return ( 7 |
8 | {/* Search Bar */} 9 | 20 | 21 | {/* Icon & Profile Section */} 22 |
23 |
24 | 25 | 50 26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 | 34 | {/* Profile Section */} 35 |
36 | 37 | Hello, N T A 38 | 39 | 43 |
44 |
45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Sedap 🍽️ - Vite + Tailwind + Router 2 | 3 | Ini adalah proyek React modern dengan Vite yang telah dikonfigurasi menggunakan: 4 | 5 | - ⚡️ Vite — build tool super cepat 6 | - 💅 Tailwind CSS — styling utility-first 7 | - 🔁 React Router DOM — routing halaman 8 | - 📦 gh-pages — untuk deployment ke GitHub Pages 9 | - 🧠 Context API — untuk state global seperti breadcrumb 10 | - 🌀 Lazy Loading & Suspense — untuk pemuatan komponen dinamis 11 | - 🛠️ ESLint — untuk menjaga kualitas kode 12 | 13 | ## 📂 Struktur Proyek 14 | 15 | ``` 16 | src/ 17 | ├── components/ # Komponen UI global 18 | ├── context/ # Context API (contoh: BreadcrumbContext) 19 | ├── layouts/ # Layout untuk Auth/Main/Guest 20 | ├── pages/ # Semua halaman 21 | ├── App.jsx # Routing utama 22 | └── main.jsx # Entry point aplikasi 23 | public/ 24 | ├── Data/ # Berisi data JSON yang digunakan via fetch 25 | ``` 26 | 27 | ## 🚀 Jalankan secara lokal 28 | 29 | ```bash 30 | npm install 31 | npm run dev 32 | ``` 33 | 34 | Akses di browser: [http://localhost:5173](http://localhost:5173) 35 | 36 | ## 🛠️ Build untuk Production 37 | 38 | ```bash 39 | npm run build 40 | ``` 41 | 42 | ## 🌍 Deploy ke GitHub Pages 43 | 44 | ```bash 45 | npm run deploy 46 | ``` 47 | 48 | > Pastikan repository GitHub sudah dikonfigurasi dengan benar di `package.json` dan remote git. 49 | 50 | ## 📋 Scripts 51 | 52 | | Perintah | Deskripsi | 53 | |------------------|------------------------------------| 54 | | `npm run dev` | Jalankan server development | 55 | | `npm run build` | Build aplikasi untuk production | 56 | | `npm run preview`| Preview hasil build secara lokal | 57 | | `npm run deploy` | Deploy ke GitHub Pages | 58 | | `npm run lint` | Jalankan pengecekan linting | 59 | 60 | --- 61 | 62 | ## 📦 Teknologi yang Digunakan 63 | 64 | - React 19 65 | - Vite 6 66 | - Tailwind CSS 4 67 | - React Router DOM 7 68 | - Axios, React Icons, Framer Motion, Swiper, dan lainnya 69 | 70 | --- 71 | 72 | ## ✨ Kontribusi 73 | 74 | Pull request dan saran perbaikan sangat diterima! 75 | 76 | --- 77 | 78 | ## 📄 Lisensi 79 | 80 | MIT License © 2025 Ananta 81 | -------------------------------------------------------------------------------- /public/img/produk/menu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/Auth/Forgot.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | export default function Forgot() { 5 | const [email, setEmail] = useState(''); 6 | const [submitted, setSubmitted] = useState(false); 7 | 8 | const handleSubmit = (e) => { 9 | e.preventDefault(); 10 | // Simulasi kirim link reset password 11 | setSubmitted(true); 12 | }; 13 | 14 | return ( 15 |
16 |
17 |
18 |

19 | Forgot Your Password? 20 |

21 | 22 |

23 | Enter your email and we’ll send you a link to reset your password. 24 |

25 | 26 | {submitted ? ( 27 |

28 | Reset link has been sent to your email. 29 |

30 | ) : ( 31 |
32 |
33 | 39 | setEmail(e.target.value)} 44 | required 45 | className="w-full px-4 py-2 bg-gray-50 border border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 transition transform hover:scale-105 duration-300" 46 | placeholder="you@example.com" 47 | /> 48 |
49 | 55 |
56 | )} 57 | 58 |
59 | 63 | Back to Login 64 | 65 |
66 |
67 |
68 |
69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /src/components/LiveBidding.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | 3 | export default function LiveBidding() { 4 | const [recipes, setRecipes] = useState([]); 5 | const [loading, setLoading] = useState(true); 6 | 7 | useEffect(() => { 8 | const fetchRecipes = async () => { 9 | try { 10 | const res = await fetch("https://dummyjson.com/recipes"); 11 | const data = await res.json(); 12 | setRecipes(data.recipes.slice(0, 4)); // Ambil 8 item pertama 13 | setLoading(false); 14 | } catch (err) { 15 | console.error("Error fetching recipes:", err); 16 | setLoading(false); 17 | } 18 | }; 19 | 20 | fetchRecipes(); 21 | }, []); 22 | 23 | if (loading) { 24 | return
Loading Live Bidding...
; 25 | } 26 | 27 | return ( 28 |
32 |
41 |
42 |

Live Bidding

43 |
44 | {recipes.map((item) => ( 45 |
49 | {item.name} 54 |
55 |

{item.name}

56 | {item.cuisine} 57 |
58 |
59 | 🔥 {item.rating} ⭐ 60 | 🍽 {item.servings} servings 61 |
62 |
63 | Prep: {item.prepTimeMinutes}m 64 | Cook: {item.cookTimeMinutes}m 65 |
66 |
67 | Calories: {item.caloriesPerServing} 68 |
69 |
70 | ))} 71 |
72 |
73 |
74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /src/components/NewItem.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import ShapeBlur from './VertexShader'; 3 | import SpotlightCard from './SpotLightCard'; 4 | 5 | 6 | const NewItems = () => { 7 | const [recipes, setRecipes] = useState([]); 8 | 9 | useEffect(() => { 10 | fetch("https://dummyjson.com/recipes") 11 | .then((res) => res.json()) 12 | .then((data) => setRecipes(data.recipes.slice(0, 4))) 13 | .catch((error) => console.error("Error fetching recipes:", error)); 14 | }, []); 15 | 16 | return ( 17 |
20 | 21 |
30 |
31 |
32 |

New Recipes

33 | 37 | View All → 38 | 39 |
40 |
41 | {recipes.map((recipe) => ( 42 | 46 | {recipe.name} 51 |
52 |
53 | {recipe.name} 54 | {recipe.cuisine} 55 |
56 |
57 | {recipe.instructions} 58 |
59 |
60 | 61 | Calories: {recipe.caloriesPerServing} 62 | 63 | {recipe.difficulty} 64 |
65 |
66 |
67 | ))} 68 |
69 |
70 |
71 | ); 72 | }; 73 | 74 | export default NewItems; 75 | -------------------------------------------------------------------------------- /src/pages/hero.jsx: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import VariableProximity from "../components/VariableProximity"; 3 | import Balatro from "../components/Balatro"; 4 | 5 | export default function Hero() { 6 | const containerRef = useRef(); 7 | return ( 8 |
12 | {/* Balatro Background */} 13 |
14 | 15 |
16 | {/* Overlay Gelap */} 17 |
18 |
19 |
20 | {/* Konten */} 21 |
22 |

23 | Makanan Nusantara 24 |

25 |

26 | 34 |

35 |

36 | Kami mempersembahkan produk makanan lokal terbaik: dari camilan tradisional, 37 | makanan sehat, hingga kuliner modern kekinian. Semua tersedia dalam satu tempat. 38 |

39 | {/* Ikon Produk */} 40 |
41 | {[ 42 | { src: "img/produk/keripik.jpg", alt: "Keripik Pisang" }, 43 | { src: "img/produk/sambal.jpg", alt: "Sambal Rumahan" }, 44 | { src: "img/produk/granola.jpg", alt: "Granola Sehat" }, 45 | { src: "img/produk/kopi.jpg", alt: "Kopi Lokal" }, 46 | { src: "img/produk/dodol.jpg", alt: "Dodol Khas" }, 47 | { src: "img/produk/abon.jpg", alt: "Abon Lezat" }, 48 | ].map(({ src, alt }) => ( 49 | 54 | {`${alt} 58 | 59 | ))} 60 |
61 | 64 |
65 |
66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /src/components/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import { MdDashboard, MdPeople, MdAssignment, MdShoppingCart } from "react-icons/md"; 2 | import { useBreadcrumb } from "../context/BreadcrumbContext"; 3 | import { useNavigate } from "react-router-dom"; 4 | 5 | export default function Sidebar() { 6 | const { updateBreadcrumb } = useBreadcrumb(); 7 | const navigate = useNavigate(); 8 | 9 | const menuItems = [ 10 | { label: "Dashboard", icon: , path: "/", key: "dashboard" }, 11 | { label: "Users1", icon: , path: "/users1", key: "users1" }, 12 | { label: "Customers1", icon: , path: "/customers1", key: "customers1" }, 13 | { label: "Orders", icon: , path: "/orders", key: "orders" }, 14 | ]; 15 | 16 | // Fungsi update breadcrumb sesuai key menu 17 | const handleClick = (key) => { 18 | const breadcrumbs = { 19 | dashboard: ["Dashboard"], 20 | users1: ["Users1"], 21 | customers1: ["Customers1"], 22 | orders: ["Orders"], 23 | }; 24 | updateBreadcrumb(breadcrumbs[key] || ["Dashboard"]); 25 | }; 26 | 27 | // Fungsi navigasi & update breadcrumb 28 | const handleNavigation = (path, key) => { 29 | handleClick(key); 30 | navigate(path); 31 | }; 32 | 33 | return ( 34 | 79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /public/img/produk/delivery.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Testimonials.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Swiper, SwiperSlide } from "swiper/react"; 3 | import "swiper/css"; 4 | import "swiper/css/free-mode"; 5 | import { Autoplay, FreeMode } from "swiper/modules"; 6 | const testimonials = [ 7 | { 8 | name: "Adit Pratama", 9 | review: "Sangat puas dengan kualitas dan pelayanannya. Sangat direkomendasikan!", 10 | avatar: "https://avatar.iran.liara.run/public/7", 11 | }, 12 | { 13 | name: "Nina Kartika", 14 | review: "Cepat, ramah, dan profesional. Akan kembali lagi!", 15 | avatar: "https://avatar.iran.liara.run/public/92", 16 | }, 17 | { 18 | name: "Rafi Alamsyah", 19 | review: "Layanan pelanggan sangat membantu. Terima kasih!", 20 | avatar: "https://avatar.iran.liara.run/public/11", 21 | }, 22 | { 23 | name: "Indah Nuraini", 24 | review: "Produknya keren banget, sesuai dengan ekspektasi saya!", 25 | avatar: "https://avatar.iran.liara.run/public/95", 26 | }, 27 | { 28 | name: "Dimas Yudha", 29 | review: "Harga bersaing dan pengiriman cepat. Top!", 30 | avatar: "https://avatar.iran.liara.run/public/3", 31 | }, 32 | ]; 33 | const TestimonialsSection = () => { 34 | return ( 35 |
36 | {/* Background noise */} 37 |
45 | 46 | {/* Header */} 47 |
48 |

Apa Kata Mereka?

49 |

50 | Testimoni nyata dari pengguna kami yang puas dengan layanan & produk. 51 |

52 |
53 | {/* Smooth continuous scroll swiper */} 54 |
55 | 67 | {testimonials.map((user, index) => ( 68 | 71 |
72 | {user.name} 76 |
77 |
{user.name}
78 |

"{user.review}"

79 |
80 |
81 |
82 | ))} 83 |
84 |
85 |
86 | ); 87 | }; 88 | export default TestimonialsSection; 89 | -------------------------------------------------------------------------------- /src/pages/Auth/Register.jsx: -------------------------------------------------------------------------------- 1 | export default function Register() { 2 | return ( 3 |
4 | 5 | 6 |
7 |
8 |
9 |

10 | Create Your Account ✨ 11 |

12 | 13 |
14 |
15 | 21 | 28 |
29 | 30 |
31 | 37 | 44 |
45 | 46 |
47 | 53 | 60 |
61 | 62 | 68 |
69 | 70 |
71 |

72 | Already have an account?{' '} 73 | 77 | Log In 78 | 79 |

80 |
81 |
82 |
83 |
84 |
85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /src/pages/Orders.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useBreadcrumb } from "../context/BreadcrumbContext"; 3 | import { FaUserPlus, FaDownload, FaArrowLeft } from "react-icons/fa"; 4 | import PageHeader from "../components/PageHeader"; 5 | export default function Orders({ orders }) { 6 | const { breadcrumb } = useBreadcrumb(); 7 | return ( 8 |
9 |
10 |
11 | {/* Header Section */} 12 | 15 |
16 | 20 | 24 | 28 |
29 |
30 | {/* Order List */} 31 |
32 |
    33 | {orders.map((order, index) => ( 34 |
  • 35 |
    36 |

    Order ID: 37 | {order["Order ID"]} 38 |

    39 |

    Customer Name: 40 | {order["Customer Name"]} 41 |

    42 |

    Status: 43 | 45 | {order.Status} 46 | 47 |

    48 |

    Total Price: 49 | ${order["Total Price"].toFixed(2)} 50 |

    51 |

    Order Date: 52 | {order["Order Date"]} 53 |

    54 |
    55 |
  • 56 | ))} 57 |
58 |
59 |
60 |
61 |
62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /src/pages/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import PageHeader from "../components/PageHeader.jsx"; 2 | import { FaShoppingCart, FaTruck, FaBan, FaDollarSign, FaPlus } from "react-icons/fa"; 3 | import { useBreadcrumb } from "../context/BreadcrumbContext"; // Mengimpor context 4 | export default function Dashboard() { 5 | const { breadcrumb } = useBreadcrumb(); // Mengambil breadcrumb dari context 6 | return ( 7 |
8 |
9 |
10 |
11 |
12 | 15 |
16 | 20 | 24 | 28 |
29 |
30 |
31 | {/* Orders */} 32 |
33 |
34 | 35 |
36 |
37 |
75
38 |
Total Orders
39 |
40 |
41 | {/* Delivered */} 42 |
43 |
44 | 45 |
46 |
47 |
175
48 |
Total Delivered
49 |
50 |
51 | {/* Canceled */} 52 |
53 |
54 | 55 |
56 |
57 |
40
58 |
Total Canceled
59 |
60 |
61 | {/* Revenue */} 62 |
63 |
64 | 65 |
66 |
67 |
Rp.128
68 |
Total Revenue
69 |
70 |
71 |
72 |
73 |
74 |
75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /src/components/About.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import SpotlightCard from './SpotLightCard'; 3 | export default function AboutUs() { 4 | return ( 5 |
8 | {/* Background noise */} 9 |
17 |
18 | {/* Content */} 19 |
20 |

Tentang Kami

21 |

22 | Kami adalah platform yang menghadirkan berbagai produk makanan lokal 23 | terbaik dari berbagai daerah di Indonesia. Dari camilan tradisional, 24 | makanan sehat, hingga makanan kekinian—semua tersedia untuk memanjakan lidah 25 | dan mendukung pelaku UMKM kuliner. 26 |

27 |
28 | 29 |

🍱 Misi Kami

30 |

31 | Menyediakan makanan berkualitas tinggi, sehat, dan aman, sambil 32 | memajukan industri kuliner lokal dengan memberdayakan produsen makanan 33 | rumahan dan UMKM di seluruh Indonesia. 34 |

35 |
36 | 37 |

🌍 Visi Kami

38 |

39 | Menjadi platform kuliner terpercaya yang mengenalkan kekayaan cita rasa 40 | Indonesia ke seluruh penjuru nusantara dan dunia. 41 |

42 |
43 |
44 |
45 |

Produk Unggulan

46 |
47 | {[ 48 | { name: "Keripik Pisang Coklat", desc: "Camilan renyah dari pisang asli", img: "/img/produk/keripik.jpg" }, 49 | { name: "Sambal Rumahan", desc: "Sambal khas pedesnya nendang!", img: "/img/produk/sambal.jpg" }, 50 | { name: "Granola Sehat", desc: "Snack sehat tanpa pengawet", img: "/img/produk/granola.jpg" }, 51 | ].map(({ name, desc, img }) => ( 52 | 55 | {name} 59 |

{name}

60 |

{desc}

61 |
62 | ))} 63 |
64 |
65 |
66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /src/components/colection.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | export default function TopCollection() { 3 | const [collections, setCollections] = useState([]); 4 | useEffect(() => { 5 | fetch("https://dummyjson.com/recipes?limit=8") // ambil 8 resep untuk ditampilkan 6 | .then((res) => res.json()) 7 | .then((data) => { 8 | const mapped = data.recipes.map((recipe, index, arr) => { 9 | const otherImages = arr 10 | .filter((r) => r.image !== recipe.image) 11 | .map((r) => r.image); 12 | const shuffled = otherImages.sort(() => 0.5 - Math.random()); 13 | const smallImgs = shuffled.slice(0, 3); 14 | 15 | return { 16 | title: recipe.name, 17 | items: `${recipe.ingredients.length} Ingredients`, 18 | bigImg: recipe.image, 19 | smallImgs, 20 | profileImg: `https://i.pravatar.cc/150?img=${index + 10}`, 21 | }; 22 | }); 23 | setCollections(mapped); 24 | }); 25 | }, []); 26 | 27 | return ( 28 |
29 | {/* Noise layer */} 30 |
38 |
39 | {/* Header */} 40 |
41 |

🔥 Top Recipes

42 | 45 | View All → 46 | 47 |
48 | {/* Grid */} 49 |
50 | {collections.map((col, idx) => ( 51 |
55 |
56 | {col.title} 60 |
61 |
62 | {col.smallImgs.map((img, i) => ( 63 | {`thumb-${i}`} 68 | ))} 69 |
70 |
71 | profile 75 |
76 |

{col.title}

77 |

{col.items}

78 |
79 |
80 |
81 | ))} 82 |
83 |
84 |
85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /src/components/ListMenu.jsx: -------------------------------------------------------------------------------- 1 | import { MdDashboard, MdShop, MdExpandMore } from "react-icons/md"; 2 | import { useBreadcrumb } from "../context/BreadcrumbContext"; 3 | import FlowingMenu from "./FlowingMenu"; 4 | import { useState } from "react"; 5 | 6 | export default function ListMenu() { 7 | const { updateBreadcrumb } = useBreadcrumb(); 8 | 9 | const menuItems = [ 10 | { label: "Home", icon: , id: "hero-section" , key: "home" }, 11 | { label: "About Us", icon: , id: "about-section", key: "about" }, 12 | { label: "Top Recipes", icon: , id: "Top Recipes", key: "Recipes" }, 13 | { label: "LiveBidding", icon: , id: "LiveBidding", key: "live" }, 14 | { label: "Services", icon: , id: "Services", key: "services" }, 15 | { label: "Testimonial", icon: , id: "Testi", key: "Testi" }, 16 | ]; 17 | 18 | const flowingMenuItems = [ 19 | { 20 | text: "User", 21 | link: "/user", 22 | image: "/img/user.jpg", 23 | }, 24 | { 25 | text: "Customers", 26 | link: "/customers", 27 | image: "../img/portfolio/portfolio-02.jpg", 28 | }, 29 | { 30 | text: "Food", 31 | link: "/food", 32 | image: "/img/food.jpg", 33 | }, 34 | { 35 | text: "Orders", 36 | link: "/orders", 37 | image: "/img/orders.jpg", 38 | }, 39 | ]; 40 | 41 | const [showFlowingMenu, setShowFlowingMenu] = useState(false); 42 | 43 | const handleClick = (key) => { 44 | const breadcrumbs = { 45 | about: ["Home", "Collection"], 46 | live: ["Home", "LiveBidding"], 47 | services: ["Home", "Services"], 48 | }; 49 | 50 | updateBreadcrumb(breadcrumbs[key] || ["Home"]); 51 | }; 52 | 53 | const scrollToSection = (id) => { 54 | const section = document.getElementById(id); 55 | if (section) { 56 | section.scrollIntoView({ behavior: "smooth" }); 57 | } 58 | }; 59 | 60 | const handleHomeClick = () => { 61 | window.location.href = "/guest"; 62 | }; 63 | 64 | return ( 65 |
    66 | {menuItems.map((item) => ( 67 |
  • 68 | 80 |
  • 81 | ))} 82 | {/* FlowingMenu dropdown */} 83 |
  • 84 | 91 | {showFlowingMenu && ( 92 |
    setShowFlowingMenu(false)}> 95 | 96 |
    97 | )} 98 |
  • 99 |
100 | );} 101 | -------------------------------------------------------------------------------- /src/components/News.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import customers from "../Data/customers.json"; // sesuaikan path jika berbeda 3 | const getBadgeColor = (loyalty) => { 4 | switch (loyalty) { 5 | case "Master": 6 | return "bg-red-500 text-gray-900"; 7 | case "Gold": 8 | return "bg-yellow-500 text-yellow-900"; 9 | case "Silver": 10 | return "bg-gray-400 text-gray-800"; 11 | case "Bronze": 12 | return "bg-amber-600 text-white"; 13 | default: 14 | return "bg-gray-200 text-black"; 15 | } 16 | }; 17 | const MembershipCheckSection = () => { 18 | const [email, setEmail] = useState(""); 19 | const [result, setResult] = useState(null); 20 | const [error, setError] = useState(""); 21 | const validateEmail = (email) => { 22 | const regex = /^\S+@\S+\.\S+$/; 23 | return regex.test(email); 24 | }; 25 | const handleCheck = () => { 26 | setError(""); 27 | setResult(null); 28 | if (!email.trim()) { 29 | setError("Email tidak boleh kosong."); 30 | return; 31 | } 32 | if (!validateEmail(email)) { 33 | setError("Format email tidak valid."); 34 | return; 35 | } 36 | const found = customers.find( 37 | (customer) => customer.Email.toLowerCase() === email.toLowerCase() 38 | ); 39 | if (found) { 40 | setResult({ 41 | found: true, 42 | name: found["Customer Name"], 43 | loyalty: found.Loyalty, 44 | }); 45 | } else { 46 | setResult({ found: false }); 47 | } 48 | }; 49 | 50 | return ( 51 |
52 |
60 |
61 |

62 | Cek Keanggotaan 63 |

64 |

65 | Masukkan email Anda untuk mengetahui status member Anda 66 |

67 |
68 | setEmail(e.target.value)}/> 74 | 79 |
80 | {error &&

{error}

} 81 | {result && result.found && ( 82 |
84 | 🧾 Selamat datang, {result.name}! Anda adalah member{" "} 85 | {result.loyalty}. 86 |
87 | )} 88 | {result && !result.found && ( 89 |
90 | ❌ Email tidak terdaftar sebagai member. 91 |
92 | )} 93 |
94 |
95 | ); 96 | }; 97 | export default MembershipCheckSection; 98 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/TopSellers.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | const sellers = [ 4 | { 5 | name: "Brodband", 6 | image: "/img/client/client-12.png", 7 | amount: "$2500,000", 8 | verified: true, 9 | }, 10 | { 11 | name: "Ms. Parkline", 12 | image: "/img/client/client-2.png", 13 | amount: "$2300,000", 14 | verified: false, 15 | }, 16 | { 17 | name: "Methods", 18 | image: "/img/client/client-3.png", 19 | amount: "$2100,000", 20 | verified: false, 21 | }, 22 | { 23 | name: "Jone sone", 24 | image: "/img/client/client-4.png", 25 | amount: "$2000,000", 26 | verified: true, 27 | }, 28 | { 29 | name: "Siddhart", 30 | image: "/img/client/client-5.png", 31 | amount: "$200,000", 32 | verified: false, 33 | }, 34 | { 35 | name: "Sobuj Mk", 36 | image: "/img/client/client-6.png", 37 | amount: "$2000,000", 38 | verified: true, 39 | }, 40 | { 41 | name: "Trodband", 42 | image: "/img/client/client-7.png", 43 | amount: "$2500,000", 44 | verified: true, 45 | }, 46 | { 47 | name: "Yash", 48 | image: "/img/client/client-8.png", 49 | amount: "$2500,000", 50 | verified: false, 51 | }, 52 | { 53 | name: "YASHKIB", 54 | image: "/img/client/client-9.png", 55 | amount: "$2500,000", 56 | verified: false, 57 | }, 58 | { 59 | name: "Brodband", 60 | image: "/img/client/client-10.png", 61 | amount: "$2500,000", 62 | verified: true, 63 | }, 64 | ]; 65 | 66 | const filterOptions = ["1 Day", "7 Day's", "15 Day's", "30 Day's"]; 67 | 68 | export default function TopSellers() { 69 | const [selectedFilter, setSelectedFilter] = useState("7 Day's"); 70 | 71 | return ( 72 |
73 | {/* Background noise */} 74 |
83 | 84 | {/* Header */} 85 |
86 |

87 | Top Seller in{" "} 88 | {selectedFilter} 89 |

90 | 101 |
102 | 103 | {/* Sellers List */} 104 |
105 | {sellers.map((seller, index) => ( 106 |
107 | {/* Rank number background */} 108 |
109 | {index + 1} 110 |
111 | 112 | {/* Avatar + verified */} 113 | 114 |
115 | {seller.name} 120 | {seller.verified && ( 121 | 122 | ✓ 123 | 124 | )} 125 |
126 | 127 | {/* Text content */} 128 |
129 |
{seller.name}
130 | {seller.amount} 131 |
132 |
133 | 134 | ))} 135 |
136 |
137 | ); 138 | } 139 | -------------------------------------------------------------------------------- /src/components/FlowingMenu.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { gsap } from 'gsap'; 3 | 4 | function FlowingMenu({ items = [] }) { 5 | return ( 6 |
7 | 12 |
13 | ); 14 | } 15 | 16 | function MenuItem({ link, text, image }) { 17 | const itemRef = React.useRef(null); 18 | const marqueeRef = React.useRef(null); 19 | const marqueeInnerRef = React.useRef(null); 20 | 21 | const animationDefaults = { duration: 0.6, ease: 'expo' }; 22 | 23 | const findClosestEdge = (mouseX, mouseY, width, height) => { 24 | const topEdgeDist = (mouseX - width / 2) ** 2 + mouseY ** 2; 25 | const bottomEdgeDist = (mouseX - width / 2) ** 2 + (mouseY - height) ** 2; 26 | return topEdgeDist < bottomEdgeDist ? 'top' : 'bottom'; 27 | }; 28 | 29 | const handleMouseEnter = (ev) => { 30 | if (!itemRef.current || !marqueeRef.current || !marqueeInnerRef.current) return; 31 | const rect = itemRef.current.getBoundingClientRect(); 32 | const edge = findClosestEdge( 33 | ev.clientX - rect.left, 34 | ev.clientY - rect.top, 35 | rect.width, 36 | rect.height 37 | ); 38 | 39 | gsap.timeline({ defaults: animationDefaults }) 40 | .set(marqueeRef.current, { y: edge === 'top' ? '-101%' : '101%' }) 41 | .set(marqueeInnerRef.current, { y: edge === 'top' ? '101%' : '-101%' }) 42 | .to([marqueeRef.current, marqueeInnerRef.current], { y: '0%' }); 43 | }; 44 | 45 | const handleMouseLeave = (ev) => { 46 | if (!itemRef.current || !marqueeRef.current || !marqueeInnerRef.current) return; 47 | const rect = itemRef.current.getBoundingClientRect(); 48 | const edge = findClosestEdge( 49 | ev.clientX - rect.left, 50 | ev.clientY - rect.top, 51 | rect.width, 52 | rect.height 53 | ); 54 | 55 | gsap.timeline({ defaults: animationDefaults }) 56 | .to(marqueeRef.current, { y: edge === 'top' ? '-101%' : '101%' }) 57 | .to(marqueeInnerRef.current, { y: edge === 'top' ? '101%' : '-101%' }); 58 | }; 59 | 60 | const repeatedMarqueeContent = Array.from({ length: 4 }).map((_, idx) => ( 61 | 62 | 63 | {text} 64 | 65 |
69 | 70 | )); 71 | 72 | return ( 73 |
74 | 80 | {text} 81 | 82 |
86 |
87 |
88 | {repeatedMarqueeContent} 89 |
90 |
91 |
92 |
93 | ); 94 | } 95 | 96 | export default FlowingMenu; 97 | 98 | // Note: this is also needed 99 | // /** @type {import('tailwindcss').Config} */ 100 | // export default { 101 | // content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 102 | // theme: { 103 | // extend: { 104 | // translate: { 105 | // '101': '101%', 106 | // }, 107 | // keyframes: { 108 | // marquee: { 109 | // 'from': { transform: 'translateX(0%)' }, 110 | // 'to': { transform: 'translateX(-50%)' } 111 | // } 112 | // }, 113 | // animation: { 114 | // marquee: 'marquee 15s linear infinite' 115 | // } 116 | // } 117 | // }, 118 | // plugins: [], 119 | // }; -------------------------------------------------------------------------------- /src/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Logo from "../components/logo.jsx" 3 | const Footer = () => { 4 | return ( 5 |
6 | {/* Brand Images */} 7 |
8 |
9 | {[1, 2, 3, 4, 5, 6, 7, 8].map((item, index) => ( 10 | {`brand-${item}`} 15 | ))} 16 |
17 |
18 | {/* Footer Content */} 19 |
20 |
21 |
22 | 23 |
24 |

25 | Created with the collaboration of over 60 of the world's best Nuron Artists. 26 |

27 |
Get The Latest Nuron Updates
28 |
29 | 33 | 36 |
37 |

Email is safe. We don't spam.

38 |
39 |
40 |
Nuron
41 |
    42 | {[ 43 | "Protocol Explore", 44 | "System Token", 45 | "Otimize Time", 46 | "Visual Checking", 47 | "Fadeup System", 48 | "Activity Log", 49 | "System Auto Since", 50 | ].map((item, index) => ( 51 |
  • {item}
  • 52 | ))} 53 |
54 |
55 |
56 |
Information
57 |
    58 | {[ 59 | "Market Explore", 60 | "Ready Token", 61 | "Main Option", 62 | "Product Checking", 63 | ["Blog Grid", "blog.html"], 64 | ["About Us", "about.html"], 65 | "Fix Bug", 66 | ].map((item, index) => ( 67 |
  • 68 | 71 | {Array.isArray(item) ? item[0] : item} 72 | 73 |
  • 74 | ))} 75 |
76 |
77 |
78 |
Recent Sold Out
79 |
    80 | {[ 81 | { 82 | img: "/img/portfolio/portfolio-01.jpg", 83 | title: "#21 The Wonder", 84 | bid: "Highest bid 1/20", 85 | price: "0.244wETH", 86 | }, 87 | { 88 | img: "/img/portfolio/portfolio-02.jpg", 89 | title: "Diamond Dog", 90 | bid: "Highest bid 1/20", 91 | price: "0.022wETH", 92 | }, 93 | { 94 | img: "/img/portfolio/portfolio-03.jpg", 95 | title: "Morgan11", 96 | bid: "Highest bid 1/20", 97 | price: "0.892wETH", 98 | }, 99 | ].map((post, index) => ( 100 |
  • 101 | {post.title} 105 |
    106 |
    107 | 108 | {post.title} 109 | 110 |
    111 |

    {post.bid}

    112 | {post.price} 113 |
    114 |
  • 115 | ))} 116 |
117 |
118 |
119 |
120 | ); 121 | }; 122 | 123 | export default Footer; 124 | -------------------------------------------------------------------------------- /src/components/Services.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | const services = [ 3 | { 4 | step: 'Langkah 1', 5 | title: 'Buat Menu Makanan', 6 | description: 'Tentukan daftar makanan dan minuman yang ingin kamu jual, lengkap dengan harga dan deskripsi.', 7 | icon: '../img/produk/menu.svg', 8 | }, 9 | { 10 | step: 'Langkah 2', 11 | title: 'Ambil Foto Menarik', 12 | description: 'Ambil gambar makananmu dengan pencahayaan yang bagus agar menggugah selera.', 13 | icon: '../img/produk/camera.svg', 14 | }, 15 | { 16 | step: 'Langkah 3', 17 | title: 'Upload ke Platform', 18 | description: 'Pasang menu dan foto makanan ke aplikasi atau website jualan online.', 19 | icon: '../img/produk/upload.svg', 20 | }, 21 | { 22 | step: 'Langkah 4', 23 | title: 'Terima Pesanan', 24 | description: 'Pantau pesanan masuk dan siapkan makanan untuk dikirim dengan cepat dan rapi.', 25 | icon: '../img/produk/delivery.svg', 26 | }, 27 | ]; 28 | 29 | 30 | const NFTStepsFancy = () => { 31 | return ( 32 |
36 | 37 | {/* Background noise layer */} 38 |
47 |
48 |

Create and Sell Your Food

49 |
50 | {services.map((item, index) => ( 51 |
52 |
53 | {item.title} 58 |
59 | 60 |
61 |
62 |

63 | {item.title} 64 |

65 |

{item.description}

66 |
67 | 68 |
69 | 78 | 82 | 83 |
84 |
85 |
86 | ))} 87 |
88 |
89 |
90 | 93 |
94 |
95 | ); 96 | }; 97 | 98 | export default NFTStepsFancy; 99 | -------------------------------------------------------------------------------- /src/pages/Auth/Login.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { useNavigate, Link } from 'react-router-dom'; 3 | import axios from 'axios'; 4 | import { ImSpinner2 } from 'react-icons/im'; 5 | import { BsFillExclamationDiamondFill } from 'react-icons/bs'; 6 | 7 | export default function Login() { 8 | const navigate = useNavigate(); 9 | const [formData, setFormData] = useState({ email: '', password: '' }); 10 | const [loading, setLoading] = useState(false); 11 | const [error, setError] = useState(''); 12 | 13 | const handleChange = (e) => { 14 | setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value })); 15 | }; 16 | 17 | const handleSubmit = async (e) => { 18 | e.preventDefault(); 19 | setError(''); 20 | setLoading(true); 21 | 22 | try { 23 | const res = await axios.post('https://dummyjson.com/user/login', { 24 | username: formData.email, 25 | password: formData.password, 26 | }); 27 | 28 | if (res.status === 200) { 29 | navigate('/'); 30 | } else { 31 | setError('Login gagal. Cek kembali email & password.'); 32 | } 33 | } catch (err) { 34 | setError(err.response?.data?.message || 'Terjadi kesalahan saat login.'); 35 | } finally { 36 | setLoading(false); 37 | } 38 | }; 39 | 40 | return ( 41 |
42 |
43 |
44 |
45 |

46 | Welcome Back 👋 47 |

48 | 49 | {error && ( 50 |
51 | 52 | {error} 53 |
54 | )} 55 | 56 | {loading && ( 57 |
58 | 59 | Memproses login... 60 |
61 | )} 62 | 63 |
64 |
65 | 71 | 81 |
82 | 83 |
84 | 90 | 100 |
101 | 102 |
103 | 107 | Forgot Password? 108 | 109 |
110 | 111 | 118 |
119 | 120 |
121 |

122 | Don't have an account?{' '} 123 | 127 | Register 128 | 129 |

130 |
131 |
132 |
133 |
134 |
135 | ); 136 | } 137 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, Suspense, lazy } from 'react'; 2 | import './assets/tailwind.css'; 3 | // import Login from './pages/Auth/Login'; 4 | // import Register from './pages/Auth/Register'; 5 | // import Forgot from './pages/Auth/Forgot'; 6 | import { BreadcrumbProvider } from './context/BreadcrumbContext'; 7 | import { Routes, Route } from 'react-router-dom'; 8 | import MainLayout from './layouts/mainLayout'; 9 | import AuthLayout from './layouts/AuthLayout'; 10 | import GuestLayout from './layouts/GuestLayout'; 11 | import Loading from './components/Loading'; // ← ini dia 12 | import ErrorPage from './components/ErrorPage.jsx'; // ⬅ import error page 13 | import ErrorBoundary from './components/ErrorBoundary.jsx'; 14 | import GuestPage from './pages/GuestPage.jsx'; 15 | import UserPage from './pages/UserPages.jsx'; 16 | import CustPage from './pages/CustomerPages.jsx'; 17 | import FoodPage from './pages/FoodPages.jsx'; 18 | import Login from './pages/Auth/Login.jsx'; 19 | import SplashCursor from './components/SplashCursor.jsx'; 20 | import Customers1 from './pages/Customers1.jsx'; 21 | import User1 from './pages/User1.jsx'; 22 | import Home from './pages/hero.jsx'; 23 | 24 | // ananta 25 | 26 | // Lazy loaded pages 27 | const Dashboard = lazy(() => import('./pages/Dashboard')); 28 | const Orders = lazy(() => import('./pages/Orders')); 29 | // const Customers = lazy(() => import('./pages/Customers')); 30 | const Error400 = lazy(() => import('./pages/Error400')); 31 | const Error401 = lazy(() => import('./pages/Error401')); 32 | const Error403 = lazy(() => import('./pages/Error403')); 33 | // const Login = lazy(() => import('./pages/Auth/LoginPages')); 34 | const Hero = lazy(() => import('./pages/hero.jsx')); 35 | const Register = lazy(() => import('./pages/Auth/Register')); 36 | const Forgot = lazy(() => import('./pages/Auth/Forgot')); 37 | // const Users1 = lazy(() => import('./pages/User1')); 38 | // const Customers1 = lazy(() => import('./pages/Customers1')); 39 | 40 | // const GuestPage = lazy(() => import('./pages/GuestPage')); 41 | 42 | 43 | function App() { 44 | const [searchTerm, setSearchTerm] = useState(''); 45 | const [orders, setOrders] = useState([]); 46 | const [customers, setCustomers] = useState([]); 47 | 48 | useEffect(() => { 49 | const fetchOrders = async () => { 50 | const response = await fetch('src/Data/orders.json'); 51 | const data = await response.json(); 52 | setOrders(data); 53 | }; 54 | fetchOrders(); 55 | }, []); 56 | 57 | useEffect(() => { 58 | const fetchCustomers = async () => { 59 | const response = await fetch('src/Data/customers.json'); 60 | const data = await response.json(); 61 | setCustomers(data); 62 | }; 63 | fetchCustomers(); 64 | }, []); 65 | 66 | const filteredOrders = orders.filter(order => 67 | order['Customer Name'].toLowerCase().includes(searchTerm.toLowerCase()) || 68 | order['Order ID'].toLowerCase().includes(searchTerm.toLowerCase()) || 69 | order['Status'].toLowerCase().includes(searchTerm.toLowerCase()) || 70 | order['Total Price'].toString().includes(searchTerm) || 71 | order['Order Date'].toLowerCase().includes(searchTerm.toLowerCase()) 72 | ); 73 | 74 | const filteredCustomers = customers.filter(customer => 75 | customer['Customer Name'].toLowerCase().includes(searchTerm.toLowerCase()) || 76 | customer['Customer ID'].toLowerCase().includes(searchTerm.toLowerCase()) || 77 | customer['Email'].toLowerCase().includes(searchTerm.toLowerCase()) || 78 | customer['Phone'].toLowerCase().includes(searchTerm.toLowerCase()) || 79 | customer['Loyalty'].toLowerCase().includes(searchTerm.toLowerCase()) 80 | ); 81 | 82 | 83 | return ( 84 | 85 | 86 | 87 | 88 | 89 | }> 90 | 91 | }> 92 | 93 | } /> 94 | {/* } /> */} 95 | } /> 96 | } /> 97 | } /> 98 | 99 | 100 | 101 | }> 102 | } /> 103 | } /> 104 | } /> 105 | } /> 106 | } /> 107 | } /> 108 | } /> 109 | 110 | 111 | }> 112 | } /> 113 | } /> 114 | } /> 115 | 116 | } /> 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | ); 125 | } 126 | 127 | export default App; 128 | -------------------------------------------------------------------------------- /src/Data/orders.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Order ID": "Order 1", 4 | "Customer Name": "Sabrina Kaufman", 5 | "Status": "Completed", 6 | "Total Price": 67.15, 7 | "Order Date": "2025-04-18" 8 | }, 9 | { 10 | "Order ID": "Order 2", 11 | "Customer Name": "Christopher Brown", 12 | "Status": "Cancelled", 13 | "Total Price": 256.08, 14 | "Order Date": "2025-05-01" 15 | }, 16 | { 17 | "Order ID": "Order 3", 18 | "Customer Name": "Keith Singh", 19 | "Status": "Cancelled", 20 | "Total Price": 17.73, 21 | "Order Date": "2025-04-28" 22 | }, 23 | { 24 | "Order ID": "Order 4", 25 | "Customer Name": "Angelica Cruz", 26 | "Status": "Completed", 27 | "Total Price": 43.74, 28 | "Order Date": "2025-04-18" 29 | }, 30 | { 31 | "Order ID": "Order 5", 32 | "Customer Name": "Angela Jones", 33 | "Status": "Pending", 34 | "Total Price": 308.37, 35 | "Order Date": "2025-04-13" 36 | }, 37 | { 38 | "Order ID": "Order 6", 39 | "Customer Name": "Micheal Foster", 40 | "Status": "Cancelled", 41 | "Total Price": 332.76, 42 | "Order Date": "2025-04-12" 43 | }, 44 | { 45 | "Order ID": "Order 7", 46 | "Customer Name": "Angela Kemp", 47 | "Status": "Pending", 48 | "Total Price": 428.05, 49 | "Order Date": "2025-05-03" 50 | }, 51 | { 52 | "Order ID": "Order 8", 53 | "Customer Name": "Scott Allen", 54 | "Status": "Pending", 55 | "Total Price": 413.64, 56 | "Order Date": "2025-05-02" 57 | }, 58 | { 59 | "Order ID": "Order 9", 60 | "Customer Name": "Christian Thompson", 61 | "Status": "Cancelled", 62 | "Total Price": 270.68, 63 | "Order Date": "2025-04-10" 64 | }, 65 | { 66 | "Order ID": "Order 10", 67 | "Customer Name": "Ashley Ferguson", 68 | "Status": "Completed", 69 | "Total Price": 129.59, 70 | "Order Date": "2025-04-09" 71 | }, 72 | { 73 | "Order ID": "Order 11", 74 | "Customer Name": "Ashley Klein", 75 | "Status": "Pending", 76 | "Total Price": 289.91, 77 | "Order Date": "2025-04-15" 78 | }, 79 | { 80 | "Order ID": "Order 12", 81 | "Customer Name": "Jacqueline Figueroa", 82 | "Status": "Cancelled", 83 | "Total Price": 359.19, 84 | "Order Date": "2025-04-12" 85 | }, 86 | { 87 | "Order ID": "Order 13", 88 | "Customer Name": "Anthony Stone", 89 | "Status": "Pending", 90 | "Total Price": 424.8, 91 | "Order Date": "2025-04-25" 92 | }, 93 | { 94 | "Order ID": "Order 14", 95 | "Customer Name": "Ananta Firdaus", 96 | "Status": "Completed", 97 | "Total Price": 1000.0, 98 | "Order Date": "2025-04-26" 99 | }, 100 | { 101 | "Order ID": "Order 15", 102 | "Customer Name": "Jeffrey Gibbs", 103 | "Status": "Pending", 104 | "Total Price": 400.04, 105 | "Order Date": "2025-04-09" 106 | }, 107 | { 108 | "Order ID": "Order 16", 109 | "Customer Name": "Tiffany Davidson", 110 | "Status": "Completed", 111 | "Total Price": 388.4, 112 | "Order Date": "2025-04-08" 113 | }, 114 | { 115 | "Order ID": "Order 17", 116 | "Customer Name": "Robert Banks", 117 | "Status": "Completed", 118 | "Total Price": 444.09, 119 | "Order Date": "2025-05-03" 120 | }, 121 | { 122 | "Order ID": "Order 18", 123 | "Customer Name": "Anthony Pierce", 124 | "Status": "Cancelled", 125 | "Total Price": 445.69, 126 | "Order Date": "2025-04-20" 127 | }, 128 | { 129 | "Order ID": "Order 19", 130 | "Customer Name": "Alexander Sutton", 131 | "Status": "Completed", 132 | "Total Price": 78.51, 133 | "Order Date": "2025-05-05" 134 | }, 135 | { 136 | "Order ID": "Order 20", 137 | "Customer Name": "Brett Wright", 138 | "Status": "Completed", 139 | "Total Price": 79.8, 140 | "Order Date": "2025-04-18" 141 | }, 142 | { 143 | "Order ID": "Order 21", 144 | "Customer Name": "Megan Taylor", 145 | "Status": "Pending", 146 | "Total Price": 281.94, 147 | "Order Date": "2025-04-29" 148 | }, 149 | { 150 | "Order ID": "Order 22", 151 | "Customer Name": "Daniel Wong", 152 | "Status": "Pending", 153 | "Total Price": 228.19, 154 | "Order Date": "2025-04-23" 155 | }, 156 | { 157 | "Order ID": "Order 23", 158 | "Customer Name": "Sarah Scott", 159 | "Status": "Cancelled", 160 | "Total Price": 62.76, 161 | "Order Date": "2025-04-19" 162 | }, 163 | { 164 | "Order ID": "Order 24", 165 | "Customer Name": "Jonathan Lee", 166 | "Status": "Cancelled", 167 | "Total Price": 156.88, 168 | "Order Date": "2025-04-22" 169 | }, 170 | { 171 | "Order ID": "Order 25", 172 | "Customer Name": "David Clark", 173 | "Status": "Completed", 174 | "Total Price": 232.4, 175 | "Order Date": "2025-04-10" 176 | }, 177 | { 178 | "Order ID": "Order 26", 179 | "Customer Name": "Laura Robinson", 180 | "Status": "Pending", 181 | "Total Price": 384.6, 182 | "Order Date": "2025-04-17" 183 | }, 184 | { 185 | "Order ID": "Order 27", 186 | "Customer Name": "Emily Adams", 187 | "Status": "Cancelled", 188 | "Total Price": 139.11, 189 | "Order Date": "2025-04-25" 190 | }, 191 | { 192 | "Order ID": "Order 28", 193 | "Customer Name": "John Harris", 194 | "Status": "Completed", 195 | "Total Price": 219.32, 196 | "Order Date": "2025-05-01" 197 | }, 198 | { 199 | "Order ID": "Order 29", 200 | "Customer Name": "Paula Mitchell", 201 | "Status": "Cancelled", 202 | "Total Price": 123.55, 203 | "Order Date": "2025-05-04" 204 | }, 205 | { 206 | "Order ID": "Order 30", 207 | "Customer Name": "Samuel Fisher", 208 | "Status": "Completed", 209 | "Total Price": 99.4, 210 | "Order Date": "2025-04-14" 211 | } 212 | ] 213 | -------------------------------------------------------------------------------- /src/components/AnimatedList.jsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState, useEffect } from 'react'; 2 | import { motion, useInView } from 'framer-motion'; 3 | 4 | const AnimatedItem = ({ children, delay = 0, index, onMouseEnter, onClick }) => { 5 | const ref = useRef(null); 6 | const inView = useInView(ref, { amount: 0.5, triggerOnce: false }); 7 | return ( 8 | 18 | {children} 19 | 20 | ); 21 | }; 22 | 23 | const AnimatedList = ({ 24 | items = [ 25 | 'Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 26 | 'Item 6', 'Item 7', 'Item 8', 'Item 9', 'Item 10', 27 | 'Item 11', 'Item 12', 'Item 13', 'Item 14', 'Item 15' 28 | ], 29 | items= [], 30 | onItemSelect, 31 | showGradients = true, 32 | enableArrowNavigation = true, 33 | className = '', 34 | itemClassName = '', 35 | displayScrollbar = true, 36 | initialSelectedIndex = -1, 37 | }) => { 38 | const listRef = useRef(null); 39 | const [selectedIndex, setSelectedIndex] = useState(initialSelectedIndex); 40 | const [keyboardNav, setKeyboardNav] = useState(false); 41 | const [topGradientOpacity, setTopGradientOpacity] = useState(0); 42 | const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1); 43 | 44 | const handleScroll = (e) => { 45 | const { scrollTop, scrollHeight, clientHeight } = e.target; 46 | setTopGradientOpacity(Math.min(scrollTop / 50, 1)); 47 | const bottomDistance = scrollHeight - (scrollTop + clientHeight); 48 | setBottomGradientOpacity( 49 | scrollHeight <= clientHeight ? 0 : Math.min(bottomDistance / 50, 1) 50 | ); 51 | }; 52 | 53 | // Keyboard navigation: arrow keys, tab, and enter selection 54 | useEffect(() => { 55 | if (!enableArrowNavigation) return; 56 | const handleKeyDown = (e) => { 57 | if (e.key === 'ArrowDown' || (e.key === 'Tab' && !e.shiftKey)) { 58 | e.preventDefault(); 59 | setKeyboardNav(true); 60 | setSelectedIndex((prev) => Math.min(prev + 1, items.length - 1)); 61 | } else if (e.key === 'ArrowUp' || (e.key === 'Tab' && e.shiftKey)) { 62 | e.preventDefault(); 63 | setKeyboardNav(true); 64 | setSelectedIndex((prev) => Math.max(prev - 1, 0)); 65 | } else if (e.key === 'Enter') { 66 | if (selectedIndex >= 0 && selectedIndex < items.length) { 67 | e.preventDefault(); 68 | if (onItemSelect) { 69 | onItemSelect(items[selectedIndex], selectedIndex); 70 | } 71 | } 72 | } 73 | }; 74 | 75 | window.addEventListener('keydown', handleKeyDown); 76 | return () => window.removeEventListener('keydown', handleKeyDown); 77 | }, [items, selectedIndex, onItemSelect, enableArrowNavigation]); 78 | 79 | // Scroll the selected item into view if needed 80 | useEffect(() => { 81 | if (!keyboardNav || selectedIndex < 0 || !listRef.current) return; 82 | const container = listRef.current; 83 | const selectedItem = container.querySelector(`[data-index="${selectedIndex}"]`); 84 | if (selectedItem) { 85 | const extraMargin = 50; 86 | const containerScrollTop = container.scrollTop; 87 | const containerHeight = container.clientHeight; 88 | const itemTop = selectedItem.offsetTop; 89 | const itemBottom = itemTop + selectedItem.offsetHeight; 90 | if (itemTop < containerScrollTop + extraMargin) { 91 | container.scrollTo({ top: itemTop - extraMargin, behavior: 'smooth' }); 92 | } else if (itemBottom > containerScrollTop + containerHeight - extraMargin) { 93 | container.scrollTo({ 94 | top: itemBottom - containerHeight + extraMargin, 95 | behavior: 'smooth', 96 | }); 97 | } 98 | } 99 | setKeyboardNav(false); 100 | }, [selectedIndex, keyboardNav]); 101 | 102 | return ( 103 |
104 |
117 | {items.map((item, index) => ( 118 | setSelectedIndex(index)} 123 | onClick={() => { 124 | setSelectedIndex(index); 125 | if (onItemSelect) { 126 | onItemSelect(item, index); 127 | } 128 | }} 129 | > 130 |
131 |

{item}

132 |
133 |
134 | ))} 135 |
136 | {showGradients && ( 137 | <> 138 |
142 |
146 | 147 | )} 148 |
149 | ); 150 | }; 151 | 152 | export default AnimatedList; 153 | -------------------------------------------------------------------------------- /src/components/VariableProximity.jsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, useMemo, useRef, useEffect } from "react"; 2 | import { motion } from "framer-motion"; 3 | 4 | function useAnimationFrame(callback) { 5 | useEffect(() => { 6 | let frameId; 7 | const loop = () => { 8 | callback(); 9 | frameId = requestAnimationFrame(loop); 10 | }; 11 | frameId = requestAnimationFrame(loop); 12 | return () => cancelAnimationFrame(frameId); 13 | }, [callback]); 14 | } 15 | 16 | function useMousePositionRef(containerRef) { 17 | const positionRef = useRef({ x: 0, y: 0 }); 18 | 19 | useEffect(() => { 20 | const updatePosition = (x, y) => { 21 | if (containerRef?.current) { 22 | const rect = containerRef.current.getBoundingClientRect(); 23 | positionRef.current = { x: x - rect.left, y: y - rect.top }; 24 | } else { 25 | positionRef.current = { x, y }; 26 | } 27 | }; 28 | 29 | const handleMouseMove = (ev) => updatePosition(ev.clientX, ev.clientY); 30 | const handleTouchMove = (ev) => { 31 | const touch = ev.touches[0]; 32 | updatePosition(touch.clientX, touch.clientY); 33 | }; 34 | 35 | window.addEventListener("mousemove", handleMouseMove); 36 | window.addEventListener("touchmove", handleTouchMove); 37 | return () => { 38 | window.removeEventListener("mousemove", handleMouseMove); 39 | window.removeEventListener("touchmove", handleTouchMove); 40 | }; 41 | }, [containerRef]); 42 | 43 | return positionRef; 44 | } 45 | 46 | const VariableProximity = forwardRef((props, ref) => { 47 | const { 48 | label, 49 | fromFontVariationSettings, 50 | toFontVariationSettings, 51 | containerRef, 52 | radius = 50, 53 | falloff = "linear", 54 | className = "", 55 | onClick, 56 | style, 57 | ...restProps 58 | } = props; 59 | 60 | const letterRefs = useRef([]); 61 | const interpolatedSettingsRef = useRef([]); 62 | const mousePositionRef = useMousePositionRef(containerRef); 63 | const lastPositionRef = useRef({ x: null, y: null }); 64 | 65 | const parsedSettings = useMemo(() => { 66 | const parseSettings = (settingsStr) => 67 | new Map( 68 | settingsStr.split(",") 69 | .map(s => s.trim()) 70 | .map(s => { 71 | const [name, value] = s.split(" "); 72 | return [name.replace(/['"]/g, ""), parseFloat(value)]; 73 | }) 74 | ); 75 | 76 | const fromSettings = parseSettings(fromFontVariationSettings); 77 | const toSettings = parseSettings(toFontVariationSettings); 78 | 79 | return Array.from(fromSettings.entries()).map(([axis, fromValue]) => ({ 80 | axis, 81 | fromValue, 82 | toValue: toSettings.get(axis) ?? fromValue, 83 | })); 84 | }, [fromFontVariationSettings, toFontVariationSettings]); 85 | 86 | const calculateDistance = (x1, y1, x2, y2) => 87 | Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); 88 | 89 | const calculateFalloff = (distance) => { 90 | const norm = Math.min(Math.max(1 - distance / radius, 0), 1); 91 | switch (falloff) { 92 | case "exponential": return norm ** 2; 93 | case "gaussian": return Math.exp(-((distance / (radius / 2)) ** 2) / 2); 94 | case "linear": 95 | default: return norm; 96 | } 97 | }; 98 | 99 | useAnimationFrame(() => { 100 | if (!containerRef?.current) return; 101 | const { x, y } = mousePositionRef.current; 102 | if (lastPositionRef.current.x === x && lastPositionRef.current.y === y) { 103 | return; 104 | } 105 | lastPositionRef.current = { x, y }; 106 | 107 | const containerRect = containerRef.current.getBoundingClientRect(); 108 | 109 | letterRefs.current.forEach((letterRef, index) => { 110 | if (!letterRef) return; 111 | 112 | const rect = letterRef.getBoundingClientRect(); 113 | const letterCenterX = rect.left + rect.width / 2 - containerRect.left; 114 | const letterCenterY = rect.top + rect.height / 2 - containerRect.top; 115 | 116 | const distance = calculateDistance( 117 | mousePositionRef.current.x, 118 | mousePositionRef.current.y, 119 | letterCenterX, 120 | letterCenterY 121 | ); 122 | 123 | if (distance >= radius) { 124 | letterRef.style.fontVariationSettings = fromFontVariationSettings; 125 | return; 126 | } 127 | 128 | const falloffValue = calculateFalloff(distance); 129 | const newSettings = parsedSettings 130 | .map(({ axis, fromValue, toValue }) => { 131 | const interpolatedValue = fromValue + (toValue - fromValue) * falloffValue; 132 | return `'${axis}' ${interpolatedValue}`; 133 | }) 134 | .join(", "); 135 | 136 | interpolatedSettingsRef.current[index] = newSettings; 137 | letterRef.style.fontVariationSettings = newSettings; 138 | }); 139 | }); 140 | 141 | const words = label.split(" "); 142 | let letterIndex = 0; 143 | 144 | return ( 145 | 156 | {words.map((word, wordIndex) => ( 157 | 161 | {word.split("").map((letter) => { 162 | const currentLetterIndex = letterIndex++; 163 | return ( 164 | { letterRefs.current[currentLetterIndex] = el; }} 167 | style={{ 168 | display: "inline-block", 169 | fontVariationSettings: 170 | interpolatedSettingsRef.current[currentLetterIndex], 171 | }} 172 | aria-hidden="true" 173 | > 174 | {letter} 175 | 176 | ); 177 | })} 178 | {wordIndex < words.length - 1 && ( 179 |   180 | )} 181 | 182 | ))} 183 | {label} 184 | 185 | ); 186 | }); 187 | 188 | VariableProximity.displayName = "VariableProximity"; 189 | export default VariableProximity; 190 | -------------------------------------------------------------------------------- /src/pages/Food.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useBreadcrumb } from "../context/BreadcrumbContext"; 3 | 4 | export default function Food() { 5 | const [recipes, setRecipes] = useState([]); 6 | const [loading, setLoading] = useState(true); 7 | const [selectedFilter, setSelectedFilter] = useState("7 Day's"); 8 | const [searchTerm, setSearchTerm] = useState(""); 9 | const [currentPage, setCurrentPage] = useState(1); 10 | const recipesPerPage = 12; 11 | const { breadcrumb } = useBreadcrumb(); 12 | 13 | useEffect(() => { 14 | const fetchRecipes = async () => { 15 | try { 16 | const response = await fetch("https://dummyjson.com/recipes"); 17 | const data = await response.json(); 18 | setRecipes(data.recipes); 19 | setLoading(false); 20 | } catch (error) { 21 | console.error("Failed to fetch recipes:", error); 22 | setLoading(false); 23 | } 24 | }; 25 | 26 | fetchRecipes(); 27 | }, []); 28 | 29 | const filterOptions = ["1 Day", "7 Day's", "15 Day's", "30 Day's"]; 30 | 31 | const filteredRecipes = recipes.filter((recipe) => 32 | recipe.name.toLowerCase().includes(searchTerm.toLowerCase()) 33 | ); 34 | 35 | const totalPages = Math.ceil(filteredRecipes.length / recipesPerPage); 36 | const indexOfLastRecipe = currentPage * recipesPerPage; 37 | const indexOfFirstRecipe = indexOfLastRecipe - recipesPerPage; 38 | const currentRecipes = filteredRecipes.slice( 39 | indexOfFirstRecipe, 40 | indexOfLastRecipe 41 | ); 42 | 43 | const goToNextPage = () => { 44 | if (currentPage < totalPages) setCurrentPage((prev) => prev + 1); 45 | }; 46 | 47 | const goToPrevPage = () => { 48 | if (currentPage > 1) setCurrentPage((prev) => prev - 1); 49 | }; 50 | 51 | const handleSearchChange = (e) => { 52 | setSearchTerm(e.target.value); 53 | setCurrentPage(1); 54 | }; 55 | 56 | if (loading) { 57 | return
Loading...
; 58 | } 59 | 60 | return ( 61 |
62 |
71 |
72 |

73 | Top Recipes in {selectedFilter} 74 |

75 |
76 | 87 | 94 |
95 |
96 | 97 |
98 | {currentRecipes.map((recipe, index) => ( 99 |
103 |
104 | {indexOfFirstRecipe + index + 1} 105 |
106 | 107 |
108 | {recipe.name} 113 |
114 |

{recipe.name}

115 |

116 | Cuisine: {recipe.cuisine} 117 |

118 |
119 |
120 | 121 |
122 |

Prep Time: {recipe.prepTimeMinutes} mins

123 |

Cook Time: {recipe.cookTimeMinutes} mins

124 |

Servings: {recipe.servings}

125 |

Calories: {recipe.caloriesPerServing}

126 |

Rating: ⭐ {recipe.rating} / 5

127 |
128 |
129 | ))} 130 |
131 | 132 | {filteredRecipes.length > 0 && ( 133 |
134 | 145 | 146 | Page {currentPage} of {totalPages} 147 | 148 | 159 |
160 | )} 161 |
162 | ); 163 | } 164 | -------------------------------------------------------------------------------- /src/pages/Customers.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useBreadcrumb } from "../context/BreadcrumbContext"; 3 | 4 | export default function Customers({ customers }) { 5 | const { breadcrumb } = useBreadcrumb(); 6 | 7 | const [currentPage, setCurrentPage] = useState(1); 8 | const [searchQuery, setSearchQuery] = useState(""); 9 | const itemsPerPage = 12; 10 | 11 | // Filter customers berdasarkan pencarian 12 | const filteredCustomers = customers.filter((customer) => 13 | `${customer["Customer Name"]} 14 | ${customer["Customer ID"]} 15 | ${customer.Email} 16 | ${customer.Loyalty} 17 | ${customer.Phone}` 18 | .toLowerCase() 19 | .includes(searchQuery.toLowerCase()) 20 | ); 21 | 22 | const totalPages = Math.ceil(filteredCustomers.length / itemsPerPage); 23 | const startIndex = (currentPage - 1) * itemsPerPage; 24 | const currentCustomers = filteredCustomers.slice(startIndex, startIndex + itemsPerPage); 25 | 26 | const handlePrev = () => setCurrentPage((prev) => Math.max(prev - 1, 1)); 27 | const handleNext = () => setCurrentPage((prev) => Math.min(prev + 1, totalPages)); 28 | 29 | return ( 30 |
31 | {/* Background noise */} 32 |
41 | 42 |
43 | {/* Section Title & Search */} 44 |
45 |

Customer List

46 | { 51 | setSearchQuery(e.target.value); 52 | setCurrentPage(1); 53 | }} 54 | className="bg-white/5 border border-white/10 text-sm text-white px-4 py-2 rounded-md w-full sm:w-80 backdrop-blur-md" 55 | /> 56 |
57 | 58 | {/* Validasi jika tidak ada hasil pencarian */} 59 | {filteredCustomers.length === 0 ? ( 60 |
61 |
62 | Error Illustration 67 |

400 - Bad Request

68 |

69 | Maaf, Data yang kamu cari tidak terdaftar. 70 |

71 | 75 | Kembali ke Beranda 76 | 77 |
78 |
79 | ) : ( 80 | <> 81 | {/* Customer Cards */} 82 |
    83 | {currentCustomers.map((customer, index) => ( 84 |
  • 88 |
    89 |

    90 | Customer ID: {customer["Customer ID"]} 91 |

    92 |

    93 | Name: {customer["Customer Name"]} 94 |

    95 |

    96 | Email: {customer.Email} 97 |

    98 |

    99 | Phone: {customer.Phone} 100 |

    101 |

    102 | Loyalty:{" "} 103 | 114 | {customer.Loyalty} 115 | 116 |

    117 |
    118 |
  • 119 | ))} 120 |
121 | 122 | {/* Pagination */} 123 |
124 | 135 | 136 | Page {currentPage} of {totalPages} 137 | 138 | 149 |
150 | 151 | )} 152 |
153 |
154 | ); 155 | } 156 | -------------------------------------------------------------------------------- /src/pages/User1.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useBreadcrumb } from "../context/BreadcrumbContext"; 3 | import PageHeader from "../components/PageHeader"; 4 | 5 | export default function User1() { 6 | const [users, setUsers] = useState([]); 7 | const [loading, setLoading] = useState(true); 8 | const [searchTerm, setSearchTerm] = useState(""); 9 | const [currentPage, setCurrentPage] = useState(1); 10 | const usersPerPage = 12; 11 | const { breadcrumb } = useBreadcrumb(); 12 | 13 | useEffect(() => { 14 | const fetchUsers = async () => { 15 | try { 16 | const response = await fetch("https://dummyjson.com/users"); 17 | const data = await response.json(); 18 | setUsers(data.users); 19 | setLoading(false); 20 | } catch (error) { 21 | console.error("Failed to fetch users:", error); 22 | setLoading(false); 23 | } 24 | }; 25 | 26 | fetchUsers(); 27 | }, []); 28 | 29 | // Filter user berdasarkan search term 30 | const filteredUsers = users.filter((user) => { 31 | const fullText = ` 32 | ${user.firstName} ${user.lastName} 33 | ${user.gender} ${user.university} 34 | ${user.role} ${user.company.title} 35 | ${user.email} ${user.phone} 36 | `.toLowerCase(); 37 | return fullText.includes(searchTerm.toLowerCase()); 38 | }); 39 | 40 | const totalPages = Math.ceil(filteredUsers.length / usersPerPage); 41 | const startIndex = (currentPage - 1) * usersPerPage; 42 | const currentUsers = filteredUsers.slice(startIndex, startIndex + usersPerPage); 43 | 44 | const handlePrev = () => setCurrentPage((prev) => Math.max(prev - 1, 1)); 45 | const handleNext = () => setCurrentPage((prev) => Math.min(prev + 1, totalPages)); 46 | 47 | if (loading) { 48 | return ( 49 |
50 | Loading users... 51 |
52 | ); 53 | } 54 | 55 | return ( 56 |
57 |
58 |
59 | {/* Header Section */} 60 | 61 | 62 | {/* Search Input */} 63 |
64 | { 69 | setSearchTerm(e.target.value); 70 | setCurrentPage(1); 71 | }} 72 | className="w-full sm:w-96 px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-400" 73 | /> 74 |
75 | 76 | {/* Empty State */} 77 | {filteredUsers.length === 0 ? ( 78 |
79 | Not Found 84 |

No Data Found

85 |

Sorry, no users matched your search.

86 | 92 |
93 | ) : ( 94 | <> 95 | {/* User List */} 96 |
    97 | {currentUsers.map((user) => ( 98 |
  • 102 |
    103 | {`${user.firstName} 108 |
    109 |

    110 | {user.firstName} {user.lastName} 111 |

    112 |

    113 | {user.company.title} | {user.role} 114 |

    115 |
    116 |
    117 |
    118 |

    🎓 {user.university}

    119 |

    👤 {user.gender}

    120 |

    🎂 {user.age} years old

    121 |

    📧 {user.email}

    122 |

    📞 {user.phone}

    123 |
    124 |
  • 125 | ))} 126 |
127 | 128 | {/* Pagination */} 129 |
130 | 141 | 142 | Page {currentPage} of {totalPages} 143 | 144 | 155 |
156 | 157 | )} 158 |
159 |
160 |
161 | ); 162 | } 163 | -------------------------------------------------------------------------------- /src/Data/customers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Customer ID": "CUST001", 4 | "Customer Name": "Dione Tweedlie", 5 | "Email": "dione.tweedlie@lazz.com", 6 | "Phone": "374-(954)488-9771", 7 | "Loyalty": "Silver" 8 | }, 9 | { 10 | "Customer ID": "CUST999", 11 | "Customer Name": "Ananta Firdaus", 12 | "Email": "anantafirdaus14@gmail.com", 13 | "Phone": "082170659282", 14 | "Loyalty": "Master" 15 | }, 16 | { 17 | "Customer ID": "CUST002", 18 | "Customer Name": "Dalton Hatherill", 19 | "Email": "dalton.hatherill@centizu.com", 20 | "Phone": "231-(544)896-2818", 21 | "Loyalty": "Gold" 22 | }, 23 | { 24 | "Customer ID": "CUST003", 25 | "Customer Name": "Mason Cattini", 26 | "Email": "mason.cattini@fivechat.com", 27 | "Phone": "39-(979)617-5679", 28 | "Loyalty": "Bronze" 29 | }, 30 | { 31 | "Customer ID": "CUST004", 32 | "Customer Name": "Norah Couvert", 33 | "Email": "norah.couvert@thoughtbeat.com", 34 | "Phone": "92-(968)758-1958", 35 | "Loyalty": "Silver" 36 | }, 37 | { 38 | "Customer ID": "CUST005", 39 | "Customer Name": "Nessy Kinny", 40 | "Email": "nessy.kinny@avamm.com", 41 | "Phone": "1-(516)240-2366", 42 | "Loyalty": "Gold" 43 | }, 44 | { 45 | "Customer ID": "CUST006", 46 | "Customer Name": "Daisey Swinney", 47 | "Email": "daisey.swinney@blogpad.com", 48 | "Phone": "86-(833)329-4949", 49 | "Loyalty": "Silver" 50 | }, 51 | { 52 | "Customer ID": "CUST007", 53 | "Customer Name": "Randie Crocetti", 54 | "Email": "randie.crocetti@agivu.com", 55 | "Phone": "46-(487)185-6109", 56 | "Loyalty": "Bronze" 57 | }, 58 | { 59 | "Customer ID": "CUST008", 60 | "Customer Name": "Whitney Delgardillo", 61 | "Email": "whitney.delgardillo@zoomdog.com", 62 | "Phone": "86-(124)664-6824", 63 | "Loyalty": "Gold" 64 | }, 65 | { 66 | "Customer ID": "CUST009", 67 | "Customer Name": "Maridel Dangerfield", 68 | "Email": "maridel.dangerfield@twitterbeat.com", 69 | "Phone": "965-(428)772-4402", 70 | "Loyalty": "Bronze" 71 | }, 72 | { 73 | "Customer ID": "CUST010", 74 | "Customer Name": "Mathilde Harmon", 75 | "Email": "mathilde.harmon@dazzlesphere.com", 76 | "Phone": "63-(423)260-6268", 77 | "Loyalty": "Silver" 78 | }, 79 | { 80 | "Customer ID": "CUST011", 81 | "Customer Name": "Dione Tweedlie", 82 | "Email": "dione.tweedlie@lazz.com", 83 | "Phone": "374-(954)488-9771", 84 | "Loyalty": "Gold" 85 | }, 86 | { 87 | "Customer ID": "CUST012", 88 | "Customer Name": "Dalton Hatherill", 89 | "Email": "dalton.hatherill@centizu.com", 90 | "Phone": "231-(544)896-2818", 91 | "Loyalty": "Silver" 92 | }, 93 | { 94 | "Customer ID": "CUST013", 95 | "Customer Name": "Mason Cattini", 96 | "Email": "mason.cattini@fivechat.com", 97 | "Phone": "39-(979)617-5679", 98 | "Loyalty": "Silver" 99 | }, 100 | { 101 | "Customer ID": "CUST014", 102 | "Customer Name": "Norah Couvert", 103 | "Email": "norah.couvert@thoughtbeat.com", 104 | "Phone": "92-(968)758-1958", 105 | "Loyalty": "Gold" 106 | }, 107 | { 108 | "Customer ID": "CUST015", 109 | "Customer Name": "Nessy Kinny", 110 | "Email": "nessy.kinny@avamm.com", 111 | "Phone": "1-(516)240-2366", 112 | "Loyalty": "Bronze" 113 | }, 114 | { 115 | "Customer ID": "CUST016", 116 | "Customer Name": "Daisey Swinney", 117 | "Email": "daisey.swinney@blogpad.com", 118 | "Phone": "86-(833)329-4949", 119 | "Loyalty": "Silver" 120 | }, 121 | { 122 | "Customer ID": "CUST017", 123 | "Customer Name": "Randie Crocetti", 124 | "Email": "randie.crocetti@agivu.com", 125 | "Phone": "46-(487)185-6109", 126 | "Loyalty": "Silver" 127 | }, 128 | { 129 | "Customer ID": "CUST018", 130 | "Customer Name": "Whitney Delgardillo", 131 | "Email": "whitney.delgardillo@zoomdog.com", 132 | "Phone": "86-(124)664-6824", 133 | "Loyalty": "Bronze" 134 | }, 135 | { 136 | "Customer ID": "CUST019", 137 | "Customer Name": "Maridel Dangerfield", 138 | "Email": "maridel.dangerfield@twitterbeat.com", 139 | "Phone": "965-(428)772-4402", 140 | "Loyalty": "Gold" 141 | }, 142 | { 143 | "Customer ID": "CUST020", 144 | "Customer Name": "Mathilde Harmon", 145 | "Email": "mathilde.harmon@dazzlesphere.com", 146 | "Phone": "63-(423)260-6268", 147 | "Loyalty": "Bronze" 148 | }, 149 | { 150 | "Customer ID": "CUST021", 151 | "Customer Name": "Dione Tweedlie", 152 | "Email": "dione.tweedlie@lazz.com", 153 | "Phone": "374-(954)488-9771", 154 | "Loyalty": "Silver" 155 | }, 156 | { 157 | "Customer ID": "CUST022", 158 | "Customer Name": "Dalton Hatherill", 159 | "Email": "dalton.hatherill@centizu.com", 160 | "Phone": "231-(544)896-2818", 161 | "Loyalty": "Bronze" 162 | }, 163 | { 164 | "Customer ID": "CUST023", 165 | "Customer Name": "Mason Cattini", 166 | "Email": "mason.cattini@fivechat.com", 167 | "Phone": "39-(979)617-5679", 168 | "Loyalty": "Gold" 169 | }, 170 | { 171 | "Customer ID": "CUST024", 172 | "Customer Name": "Norah Couvert", 173 | "Email": "norah.couvert@thoughtbeat.com", 174 | "Phone": "92-(968)758-1958", 175 | "Loyalty": "Silver" 176 | }, 177 | { 178 | "Customer ID": "CUST025", 179 | "Customer Name": "Nessy Kinny", 180 | "Email": "nessy.kinny@avamm.com", 181 | "Phone": "1-(516)240-2366", 182 | "Loyalty": "Gold" 183 | }, 184 | { 185 | "Customer ID": "CUST026", 186 | "Customer Name": "Daisey Swinney", 187 | "Email": "daisey.swinney@blogpad.com", 188 | "Phone": "86-(833)329-4949", 189 | "Loyalty": "Bronze" 190 | }, 191 | { 192 | "Customer ID": "CUST027", 193 | "Customer Name": "Randie Crocetti", 194 | "Email": "randie.crocetti@agivu.com", 195 | "Phone": "46-(487)185-6109", 196 | "Loyalty": "Gold" 197 | }, 198 | { 199 | "Customer ID": "CUST028", 200 | "Customer Name": "Whitney Delgardillo", 201 | "Email": "whitney.delgardillo@zoomdog.com", 202 | "Phone": "86-(124)664-6824", 203 | "Loyalty": "Silver" 204 | }, 205 | { 206 | "Customer ID": "CUST029", 207 | "Customer Name": "Maridel Dangerfield", 208 | "Email": "maridel.dangerfield@twitterbeat.com", 209 | "Phone": "965-(428)772-4402", 210 | "Loyalty": "Silver" 211 | }, 212 | { 213 | "Customer ID": "CUST030", 214 | "Customer Name": "Mathilde Harmon", 215 | "Email": "mathilde.harmon@dazzlesphere.com", 216 | "Phone": "63-(423)260-6268", 217 | "Loyalty": "Gold" 218 | } 219 | ] 220 | -------------------------------------------------------------------------------- /src/pages/User.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useBreadcrumb } from "../context/BreadcrumbContext"; 3 | import { User } from "react-feather"; 4 | 5 | export default function Users() { 6 | const [users, setUsers] = useState([]); 7 | const [loading, setLoading] = useState(true); 8 | const [selectedFilter, setSelectedFilter] = useState("7 Day's"); 9 | const [searchTerm, setSearchTerm] = useState(""); // ✅ Tambahkan untuk search 10 | const [currentPage, setCurrentPage] = useState(1); 11 | const usersPerPage = 12; // ✅ 4 kolom × 3 baris 12 | const { breadcrumb } = useBreadcrumb(); 13 | 14 | useEffect(() => { 15 | const fetchUsers = async () => { 16 | try { 17 | const response = await fetch("https://dummyjson.com/users"); 18 | const data = await response.json(); 19 | setUsers(data.users); 20 | setLoading(false); 21 | } catch (error) { 22 | console.error("Failed to fetch users:", error); 23 | setLoading(false); 24 | } 25 | }; 26 | 27 | fetchUsers(); 28 | }, []); 29 | 30 | const filterOptions = ["1 Day", "7 Day's", "15 Day's", "30 Day's"]; 31 | 32 | // ✅ Filter berdasarkan search term 33 | const filteredUsers = users.filter((user) => { 34 | const fullName = `${user.firstName} 35 | ${user.lastName} 36 | ${user.gender} 37 | ${user.university} 38 | ${user.role} 39 | ${user.company.title} 40 | ${user.email}` 41 | 42 | .toLowerCase(); 43 | return fullName.includes(searchTerm.toLowerCase()); 44 | }); 45 | 46 | const totalPages = Math.ceil(filteredUsers.length / usersPerPage); 47 | const indexOfLastUser = currentPage * usersPerPage; 48 | const indexOfFirstUser = indexOfLastUser - usersPerPage; 49 | const currentUsers = filteredUsers.slice(indexOfFirstUser, indexOfLastUser); 50 | 51 | const goToNextPage = () => { 52 | if (currentPage < totalPages) setCurrentPage((prev) => prev + 1); 53 | }; 54 | 55 | const goToPrevPage = () => { 56 | if (currentPage > 1) setCurrentPage((prev) => prev - 1); 57 | }; 58 | 59 | const handleSearchChange = (e) => { 60 | setSearchTerm(e.target.value); 61 | setCurrentPage(1); // Reset ke halaman 1 saat cari 62 | }; 63 | 64 | if (loading) { 65 | return
Loading...
; 66 | } 67 | 68 | return ( 69 |
70 | {/* Background noise */} 71 |
80 | 81 | {/* Header: Title, Filter, Search */} 82 |
83 |

84 | Top Users in {selectedFilter} 85 |

86 |
87 | 98 | {/* ✅ Search Input */} 99 | 106 |
107 |
108 | 109 | {/* Users List */} 110 |
111 | {currentUsers.map((user, index) => ( 112 |
116 | {/* Rank */} 117 |
118 | {indexOfFirstUser + index + 1} 119 |
120 | 121 | {/* Avatar */} 122 |
123 | {user.firstName} 128 |
129 |

130 | {user.firstName} {user.lastName} 131 |

132 |

{user.company.title} | {user.role}

133 |
134 |
135 | 136 | {/* Info */} 137 |
138 |

University: {user.university}

139 |

Age: {user.age}

140 |

Gender: {user.gender}

141 |

📧 {user.email}

142 |

📞 {user.phone}

143 |
144 |
145 | ))} 146 |
147 | 148 | {/* Pagination */} 149 | {filteredUsers.length > 0 && ( 150 |
151 | 162 | 163 | Page {currentPage} of {totalPages} 164 | 165 | 176 |
177 | )} 178 |
179 | ); 180 | } 181 | -------------------------------------------------------------------------------- /src/pages/Customers1.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useBreadcrumb } from "../context/BreadcrumbContext"; 3 | import { FaUserPlus, FaDownload, FaArrowLeft } from "react-icons/fa"; 4 | import PageHeader from "../components/PageHeader"; 5 | 6 | export default function Customers1({ customers }) { 7 | const { breadcrumb } = useBreadcrumb(); 8 | 9 | const [currentPage, setCurrentPage] = useState(1); 10 | const [searchQuery, setSearchQuery] = useState(""); 11 | const itemsPerPage = 12; 12 | 13 | const filteredCustomers = customers.filter((customer) => 14 | `${customer["Customer Name"]} 15 | ${customer["Customer ID"]} 16 | ${customer.Email} 17 | ${customer.Loyalty} 18 | ${customer.Phone}` 19 | .toLowerCase() 20 | .includes(searchQuery.toLowerCase()) 21 | ); 22 | 23 | const totalPages = Math.ceil(filteredCustomers.length / itemsPerPage); 24 | const startIndex = (currentPage - 1) * itemsPerPage; 25 | const currentCustomers = filteredCustomers.slice(startIndex, startIndex + itemsPerPage); 26 | 27 | const handlePrev = () => setCurrentPage((prev) => Math.max(prev - 1, 1)); 28 | const handleNext = () => setCurrentPage((prev) => Math.min(prev + 1, totalPages)); 29 | 30 | return ( 31 |
32 |
33 |
34 | {/* Header Section */} 35 | 36 |
37 | 41 | 45 | 49 |
50 |
51 | 52 | {/* Search Input */} 53 |
54 | { 59 | setSearchQuery(e.target.value); 60 | setCurrentPage(1); 61 | }} 62 | className="w-full sm:w-96 px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-400" 63 | /> 64 |
65 | 66 | {/* Empty State */} 67 | {filteredCustomers.length === 0 ? ( 68 |
69 | Not Found 74 |

No Data Found

75 |

Maaf, data yang kamu cari tidak ditemukan.

76 | 80 | Kembali ke Beranda 81 | 82 |
83 | ) : ( 84 | <> 85 | {/* Customer List */} 86 |
    87 | {currentCustomers.map((customer, index) => ( 88 |
  • 92 |
    93 |

    Customer ID: 94 | {customer["Customer ID"]} 95 |

    96 |

    Name: 97 | {customer["Customer Name"]} 98 |

    99 |

    Email: 100 | {customer.Email} 101 |

    102 |

    Phone: 103 | {customer.Phone} 104 |

    105 |

    Loyalty: 106 | 115 | {customer.Loyalty} 116 | 117 |

    118 |
    119 |
  • 120 | ))} 121 |
122 | 123 | {/* Pagination */} 124 |
125 | 136 | 137 | Page {currentPage} of {totalPages} 138 | 139 | 150 |
151 | 152 | )} 153 |
154 |
155 |
156 | ); 157 | } 158 | -------------------------------------------------------------------------------- /src/components/Balatro.jsx: -------------------------------------------------------------------------------- 1 | import { Renderer, Program, Mesh, Triangle } from "ogl"; 2 | import { useEffect, useRef } from "react"; 3 | 4 | function hexToVec4(hex) { 5 | let hexStr = hex.replace("#", ""); 6 | let r = 0, 7 | g = 0, 8 | b = 0, 9 | a = 1; 10 | if (hexStr.length === 6) { 11 | r = parseInt(hexStr.slice(0, 2), 16) / 255; 12 | g = parseInt(hexStr.slice(2, 4), 16) / 255; 13 | b = parseInt(hexStr.slice(4, 6), 16) / 255; 14 | } else if (hexStr.length === 8) { 15 | r = parseInt(hexStr.slice(0, 2), 16) / 255; 16 | g = parseInt(hexStr.slice(2, 4), 16) / 255; 17 | b = parseInt(hexStr.slice(4, 6), 16) / 255; 18 | a = parseInt(hexStr.slice(6, 8), 16) / 255; 19 | } 20 | return [r, g, b, a]; 21 | } 22 | 23 | const vertexShader = ` 24 | attribute vec2 uv; 25 | attribute vec2 position; 26 | varying vec2 vUv; 27 | void main() { 28 | vUv = uv; 29 | gl_Position = vec4(position, 0, 1); 30 | } 31 | `; 32 | 33 | const fragmentShader = ` 34 | precision highp float; 35 | 36 | #define PI 3.14159265359 37 | 38 | uniform float iTime; 39 | uniform vec3 iResolution; 40 | uniform float uSpinRotation; 41 | uniform float uSpinSpeed; 42 | uniform vec2 uOffset; 43 | uniform vec4 uColor1; 44 | uniform vec4 uColor2; 45 | uniform vec4 uColor3; 46 | uniform float uContrast; 47 | uniform float uLighting; 48 | uniform float uSpinAmount; 49 | uniform float uPixelFilter; 50 | uniform float uSpinEase; 51 | uniform bool uIsRotate; 52 | uniform vec2 uMouse; 53 | 54 | varying vec2 vUv; 55 | 56 | vec4 effect(vec2 screenSize, vec2 screen_coords) { 57 | float pixel_size = length(screenSize.xy) / uPixelFilter; 58 | vec2 uv = (floor(screen_coords.xy * (1.0 / pixel_size)) * pixel_size - 0.5 * screenSize.xy) / length(screenSize.xy) - uOffset; 59 | float uv_len = length(uv); 60 | 61 | float speed = (uSpinRotation * uSpinEase * 0.2); 62 | if(uIsRotate){ 63 | speed = iTime * speed; 64 | } 65 | speed += 302.2; 66 | 67 | // Mouse influence for gentle rotation (applied additively) 68 | float mouseInfluence = (uMouse.x * 2.0 - 1.0); 69 | speed += mouseInfluence * 0.1; 70 | 71 | float new_pixel_angle = atan(uv.y, uv.x) + speed - uSpinEase * 20.0 * (uSpinAmount * uv_len + (1.0 - uSpinAmount)); 72 | vec2 mid = (screenSize.xy / length(screenSize.xy)) / 2.0; 73 | uv = (vec2(uv_len * cos(new_pixel_angle) + mid.x, uv_len * sin(new_pixel_angle) + mid.y) - mid); 74 | 75 | uv *= 30.0; 76 | // Fix: Apply mouse influence additively rather than scaling with time. 77 | float baseSpeed = iTime * uSpinSpeed; 78 | speed = baseSpeed + mouseInfluence * 2.0; 79 | 80 | vec2 uv2 = vec2(uv.x + uv.y); 81 | 82 | for(int i = 0; i < 5; i++) { 83 | uv2 += sin(max(uv.x, uv.y)) + uv; 84 | uv += 0.5 * vec2( 85 | cos(5.1123314 + 0.353 * uv2.y + speed * 0.131121), 86 | sin(uv2.x - 0.113 * speed) 87 | ); 88 | uv -= cos(uv.x + uv.y) - sin(uv.x * 0.711 - uv.y); 89 | } 90 | 91 | float contrast_mod = (0.25 * uContrast + 0.5 * uSpinAmount + 1.2); 92 | float paint_res = min(2.0, max(0.0, length(uv) * 0.035 * contrast_mod)); 93 | float c1p = max(0.0, 1.0 - contrast_mod * abs(1.0 - paint_res)); 94 | float c2p = max(0.0, 1.0 - contrast_mod * abs(paint_res)); 95 | float c3p = 1.0 - min(1.0, c1p + c2p); 96 | float light = (uLighting - 0.2) * max(c1p * 5.0 - 4.0, 0.0) + uLighting * max(c2p * 5.0 - 4.0, 0.0); 97 | 98 | return (0.3 / uContrast) * uColor1 + (1.0 - 0.3 / uContrast) * (uColor1 * c1p + uColor2 * c2p + vec4(c3p * uColor3.rgb, c3p * uColor1.a)) + light; 99 | } 100 | 101 | void main() { 102 | vec2 uv = vUv * iResolution.xy; 103 | gl_FragColor = effect(iResolution.xy, uv); 104 | } 105 | `; 106 | 107 | export default function Balatro({ 108 | spinRotation = -2.0, 109 | spinSpeed = 7.0, 110 | offset = [0.0, 0.0], 111 | color1 = "#DE443B", 112 | color2 = "#006BB4", 113 | color3 = "#162325", 114 | contrast = 3.5, 115 | lighting = 0.4, 116 | spinAmount = 0.25, 117 | pixelFilter = 745.0, 118 | spinEase = 1.0, 119 | isRotate = false, 120 | mouseInteraction = true, 121 | }) { 122 | const containerRef = useRef(null); 123 | 124 | useEffect(() => { 125 | if (!containerRef.current) return; 126 | const container = containerRef.current; 127 | const renderer = new Renderer(); 128 | const gl = renderer.gl; 129 | gl.clearColor(0, 0, 0, 1); 130 | 131 | let program; 132 | 133 | function resize() { 134 | renderer.setSize(container.offsetWidth, container.offsetHeight); 135 | if (program) { 136 | program.uniforms.iResolution.value = [ 137 | gl.canvas.width, 138 | gl.canvas.height, 139 | gl.canvas.width / gl.canvas.height, 140 | ]; 141 | } 142 | } 143 | window.addEventListener("resize", resize); 144 | resize(); 145 | 146 | const geometry = new Triangle(gl); 147 | program = new Program(gl, { 148 | vertex: vertexShader, 149 | fragment: fragmentShader, 150 | uniforms: { 151 | iTime: { value: 0 }, 152 | iResolution: { 153 | value: [ 154 | gl.canvas.width, 155 | gl.canvas.height, 156 | gl.canvas.width / gl.canvas.height, 157 | ], 158 | }, 159 | uSpinRotation: { value: spinRotation }, 160 | uSpinSpeed: { value: spinSpeed }, 161 | uOffset: { value: offset }, 162 | uColor1: { value: hexToVec4(color1) }, 163 | uColor2: { value: hexToVec4(color2) }, 164 | uColor3: { value: hexToVec4(color3) }, 165 | uContrast: { value: contrast }, 166 | uLighting: { value: lighting }, 167 | uSpinAmount: { value: spinAmount }, 168 | uPixelFilter: { value: pixelFilter }, 169 | uSpinEase: { value: spinEase }, 170 | uIsRotate: { value: isRotate }, 171 | uMouse: { value: [0.5, 0.5] }, 172 | }, 173 | }); 174 | 175 | const mesh = new Mesh(gl, { geometry, program }); 176 | let animationFrameId; 177 | 178 | function update(time) { 179 | animationFrameId = requestAnimationFrame(update); 180 | program.uniforms.iTime.value = time * 0.001; 181 | renderer.render({ scene: mesh }); 182 | } 183 | animationFrameId = requestAnimationFrame(update); 184 | container.appendChild(gl.canvas); 185 | 186 | function handleMouseMove(e) { 187 | if (!mouseInteraction) return; 188 | const rect = container.getBoundingClientRect(); 189 | const x = (e.clientX - rect.left) / rect.width; 190 | const y = 1.0 - (e.clientY - rect.top) / rect.height; 191 | program.uniforms.uMouse.value = [x, y]; 192 | } 193 | container.addEventListener("mousemove", handleMouseMove); 194 | 195 | return () => { 196 | cancelAnimationFrame(animationFrameId); 197 | window.removeEventListener("resize", resize); 198 | container.removeEventListener("mousemove", handleMouseMove); 199 | container.removeChild(gl.canvas); 200 | gl.getExtension("WEBGL_lose_context")?.loseContext(); 201 | }; 202 | }, [ 203 | spinRotation, 204 | spinSpeed, 205 | offset, 206 | color1, 207 | color2, 208 | color3, 209 | contrast, 210 | lighting, 211 | spinAmount, 212 | pixelFilter, 213 | spinEase, 214 | isRotate, 215 | mouseInteraction, 216 | containerRef 217 | ]); 218 | 219 | return
; 220 | } 221 | -------------------------------------------------------------------------------- /src/components/VertexShader.jsx: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect } from 'react'; 2 | import * as THREE from 'three'; 3 | 4 | const vertexShader = /* glsl */` 5 | varying vec2 v_texcoord; 6 | void main() { 7 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 8 | v_texcoord = uv; 9 | } 10 | `; 11 | 12 | const fragmentShader = /* glsl */` 13 | varying vec2 v_texcoord; 14 | 15 | uniform vec2 u_mouse; 16 | uniform vec2 u_resolution; 17 | uniform float u_pixelRatio; 18 | 19 | uniform float u_shapeSize; 20 | uniform float u_roundness; 21 | uniform float u_borderSize; 22 | uniform float u_circleSize; 23 | uniform float u_circleEdge; 24 | 25 | #ifndef PI 26 | #define PI 3.1415926535897932384626433832795 27 | #endif 28 | #ifndef TWO_PI 29 | #define TWO_PI 6.2831853071795864769252867665590 30 | #endif 31 | 32 | #ifndef VAR 33 | #define VAR 0 34 | #endif 35 | 36 | #ifndef FNC_COORD 37 | #define FNC_COORD 38 | vec2 coord(in vec2 p) { 39 | p = p / u_resolution.xy; 40 | if (u_resolution.x > u_resolution.y) { 41 | p.x *= u_resolution.x / u_resolution.y; 42 | p.x += (u_resolution.y - u_resolution.x) / u_resolution.y / 2.0; 43 | } else { 44 | p.y *= u_resolution.y / u_resolution.x; 45 | p.y += (u_resolution.x - u_resolution.y) / u_resolution.x / 2.0; 46 | } 47 | p -= 0.5; 48 | p *= vec2(-1.0, 1.0); 49 | return p; 50 | } 51 | #endif 52 | 53 | #define st0 coord(gl_FragCoord.xy) 54 | #define mx coord(u_mouse * u_pixelRatio) 55 | 56 | float sdRoundRect(vec2 p, vec2 b, float r) { 57 | vec2 d = abs(p - 0.5) * 4.2 - b + vec2(r); 58 | return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r; 59 | } 60 | float sdCircle(in vec2 st, in vec2 center) { 61 | return length(st - center) * 2.0; 62 | } 63 | float sdPoly(in vec2 p, in float w, in int sides) { 64 | float a = atan(p.x, p.y) + PI; 65 | float r = TWO_PI / float(sides); 66 | float d = cos(floor(0.5 + a / r) * r - a) * length(max(abs(p) * 1.0, 0.0)); 67 | return d * 2.0 - w; 68 | } 69 | 70 | float aastep(float threshold, float value) { 71 | float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; 72 | return smoothstep(threshold - afwidth, threshold + afwidth, value); 73 | } 74 | float fill(in float x) { return 1.0 - aastep(0.0, x); } 75 | float fill(float x, float size, float edge) { 76 | return 1.0 - smoothstep(size - edge, size + edge, x); 77 | } 78 | float stroke(in float d, in float t) { return (1.0 - aastep(t, abs(d))); } 79 | float stroke(float x, float size, float w, float edge) { 80 | float d = smoothstep(size - edge, size + edge, x + w * 0.5) - smoothstep(size - edge, size + edge, x - w * 0.5); 81 | return clamp(d, 0.0, 1.0); 82 | } 83 | 84 | float strokeAA(float x, float size, float w, float edge) { 85 | float afwidth = length(vec2(dFdx(x), dFdy(x))) * 0.70710678; 86 | float d = smoothstep(size - edge - afwidth, size + edge + afwidth, x + w * 0.5) 87 | - smoothstep(size - edge - afwidth, size + edge + afwidth, x - w * 0.5); 88 | return clamp(d, 0.0, 1.0); 89 | } 90 | 91 | void main() { 92 | vec2 st = st0 + 0.5; 93 | vec2 posMouse = mx * vec2(1., -1.) + 0.5; 94 | 95 | float size = u_shapeSize; 96 | float roundness = u_roundness; 97 | float borderSize = u_borderSize; 98 | float circleSize = u_circleSize; 99 | float circleEdge = u_circleEdge; 100 | 101 | float sdfCircle = fill( 102 | sdCircle(st, posMouse), 103 | circleSize, 104 | circleEdge 105 | ); 106 | 107 | float sdf; 108 | if (VAR == 0) { 109 | sdf = sdRoundRect(st, vec2(size), roundness); 110 | sdf = strokeAA(sdf, 0.0, borderSize, sdfCircle) * 4.0; 111 | } else if (VAR == 1) { 112 | sdf = sdCircle(st, vec2(0.5)); 113 | sdf = fill(sdf, 0.6, sdfCircle) * 1.2; 114 | } else if (VAR == 2) { 115 | sdf = sdCircle(st, vec2(0.5)); 116 | sdf = strokeAA(sdf, 0.58, 0.02, sdfCircle) * 4.0; 117 | } else if (VAR == 3) { 118 | sdf = sdPoly(st - vec2(0.5, 0.45), 0.3, 3); 119 | sdf = fill(sdf, 0.05, sdfCircle) * 1.4; 120 | } 121 | 122 | vec3 color = vec3(sdf); 123 | float alpha = step(0.01, sdf); 124 | gl_FragColor = vec4(color.rgb, alpha); 125 | } 126 | `; 127 | 128 | 129 | const ShapeBlur = ({ 130 | className = '', 131 | variation = 0, 132 | pixelRatioProp = 2, 133 | shapeSize = 1.2, 134 | roundness = 0.4, 135 | borderSize = 0.05, 136 | circleSize = 0.3, 137 | circleEdge = 0.5 138 | }) => { 139 | const mountRef = useRef(); 140 | 141 | useEffect(() => { 142 | const mount = mountRef.current; 143 | let animationFrameId; 144 | let time = 0, lastTime = 0; 145 | 146 | const vMouse = new THREE.Vector2(); 147 | const vMouseDamp = new THREE.Vector2(); 148 | const vResolution = new THREE.Vector2(); 149 | 150 | let w = 1, h = 1; 151 | 152 | const scene = new THREE.Scene(); 153 | const camera = new THREE.OrthographicCamera(); 154 | camera.position.z = 1; 155 | 156 | const renderer = new THREE.WebGLRenderer({ alpha: true }); 157 | renderer.setClearColor(0x000000, 0); 158 | mount.appendChild(renderer.domElement); 159 | 160 | const geo = new THREE.PlaneGeometry(1, 1); 161 | const material = new THREE.ShaderMaterial({ 162 | vertexShader, 163 | fragmentShader, 164 | uniforms: { 165 | u_mouse: { value: vMouseDamp }, 166 | u_resolution: { value: vResolution }, 167 | u_pixelRatio: { value: pixelRatioProp }, 168 | u_shapeSize: { value: shapeSize }, 169 | u_roundness: { value: roundness }, 170 | u_borderSize: { value: borderSize }, 171 | u_circleSize: { value: circleSize }, 172 | u_circleEdge: { value: circleEdge } 173 | }, 174 | defines: { VAR: variation }, 175 | transparent: true 176 | }); 177 | 178 | const quad = new THREE.Mesh(geo, material); 179 | scene.add(quad); 180 | 181 | const onPointerMove = (e) => { 182 | const rect = mount.getBoundingClientRect(); 183 | vMouse.set(e.clientX - rect.left, e.clientY - rect.top); 184 | }; 185 | 186 | document.addEventListener('mousemove', onPointerMove); 187 | document.addEventListener('pointermove', onPointerMove); 188 | 189 | const resize = () => { 190 | const container = mountRef.current; 191 | w = container.clientWidth; 192 | h = container.clientHeight; 193 | const dpr = Math.min(window.devicePixelRatio, 2); 194 | 195 | renderer.setSize(w, h); 196 | renderer.setPixelRatio(dpr); 197 | 198 | camera.left = -w / 2; 199 | camera.right = w / 2; 200 | camera.top = h / 2; 201 | camera.bottom = -h / 2; 202 | camera.updateProjectionMatrix(); 203 | 204 | quad.scale.set(w, h, 1); 205 | vResolution.set(w, h).multiplyScalar(dpr); 206 | material.uniforms.u_pixelRatio.value = dpr; 207 | }; 208 | 209 | resize(); 210 | window.addEventListener('resize', resize); 211 | 212 | const ro = new ResizeObserver(() => resize()); 213 | if (mountRef.current) ro.observe(mountRef.current); 214 | 215 | const update = () => { 216 | time = performance.now() * 0.001; 217 | const dt = time - lastTime; 218 | lastTime = time; 219 | 220 | ['x', 'y'].forEach(k => { 221 | vMouseDamp[k] = THREE.MathUtils.damp(vMouseDamp[k], vMouse[k], 8, dt); 222 | }); 223 | 224 | renderer.render(scene, camera); 225 | animationFrameId = requestAnimationFrame(update); 226 | }; 227 | update(); 228 | 229 | return () => { 230 | cancelAnimationFrame(animationFrameId); 231 | window.removeEventListener('resize', resize); 232 | if (ro) ro.disconnect(); 233 | document.removeEventListener('mousemove', onPointerMove); 234 | document.removeEventListener('pointermove', onPointerMove); 235 | mount.removeChild(renderer.domElement); 236 | renderer.dispose(); 237 | }; 238 | }, [ 239 | variation, 240 | pixelRatioProp, 241 | shapeSize, 242 | roundness, 243 | borderSize, 244 | circleSize, 245 | circleEdge 246 | ]); 247 | 248 | return
; 249 | }; 250 | 251 | export default ShapeBlur; 252 | --------------------------------------------------------------------------------