├── 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 |
11 |
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 |
13 |
14 |
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 |
13 |
14 |
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 |
11 |
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 |
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 |
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 |
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 |
28 |
29 |
30 |
31 | {/*
*/}
32 | {/*
*/}
33 | {/*
*/}
34 |
35 |
36 |
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 |
33 |
34 |
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 |
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 |
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 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
39 |
40 | {isDarkMode ? : }
41 |
42 |
43 |
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 |
34 | MyPanel
35 |
36 | {menuItems.map((item) => (
37 | handleNavigation(item.path, item.key)}
40 | className="flex items-center gap-3 px-4 py-2 text-sm font-medium rounded-md hover:bg-blue-600/30 transition"
41 | >
42 | {item.icon}
43 | {item.label}
44 |
45 | ))}
46 |
47 |
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 |
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 |
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 |
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 |
40 |
41 | {recipes.map((recipe) => (
42 |
46 |
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 |
58 |
59 | ))}
60 |
61 |
62 | lihat produk
63 |
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 |
35 | {/* Logo & Subtitle */}
36 |
37 |
38 | TAK Sedap .
39 |
40 | Modern Admin Dashboard
41 |
42 |
43 | {/* Menu List */}
44 |
45 | {menuItems.map((item) => (
46 | handleNavigation(item.path, item.key)}
49 | className="flex items-center gap-3 px-4 py-2 text-sm font-medium rounded-md hover:bg-blue-600/30 transition text-gray-700"
50 | >
51 | {item.icon}
52 | {item.label}
53 |
54 | ))}
55 |
56 |
57 | {/* Footer */}
58 |
59 |
60 |
61 | Please organize your menus through button below!
62 |
68 |
69 |
74 |
75 |
Sedap Restaurant Admin Dashboard
76 |
© 2025 All Right Reserved
77 |
78 |
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 |
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 |
69 |
70 |
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 |
17 |
18 | Add Order
19 |
20 |
21 |
22 | Export
23 |
24 |
25 |
26 | Back
27 |
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 |
17 |
18 | Add Order
19 |
20 |
21 |
22 | Export
23 |
24 |
25 |
26 | Back
27 |
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 |
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 |
48 | {/* Grid */}
49 |
50 | {collections.map((col, idx) => (
51 |
55 |
56 |
60 |
61 |
62 | {col.smallImgs.map((img, i) => (
63 |
68 | ))}
69 |
70 |
71 |
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 |
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 |
77 | Cek
78 |
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 | setSelectedFilter(e.target.value)}
93 | className="bg-white/5 text-white/80 text-sm border border-white/10 px-4 py-2 rounded-md backdrop-blur-md"
94 | >
95 | {filterOptions.map((option, idx) => (
96 |
97 | {option}
98 |
99 | ))}
100 |
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 |
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 |
8 | {items.map((item, idx) => (
9 |
10 | ))}
11 |
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 |
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 |
34 | Subscribe
35 |
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 |
105 |
106 |
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 |
58 |
59 |
60 |
61 |
62 |
63 | {item.title}
64 |
65 |
{item.description}
66 |
67 |
68 |
84 |
85 |
86 | ))}
87 |
88 |
89 |
90 |
91 | Mulai Jualan Sekarang
92 |
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 |
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 |
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 | setSelectedFilter(e.target.value)}
79 | className="bg-white/5 text-white/80 text-sm border border-white/10 px-4 py-2 rounded-md backdrop-blur-md"
80 | >
81 | {filterOptions.map((option, idx) => (
82 |
83 | {option}
84 |
85 | ))}
86 |
87 |
94 |
95 |
96 |
97 |
98 | {currentRecipes.map((recipe, index) => (
99 |
103 |
104 | {indexOfFirstRecipe + index + 1}
105 |
106 |
107 |
108 |
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 |
143 | Previous
144 |
145 |
146 | Page {currentPage} of {totalPages}
147 |
148 |
157 | Next
158 |
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 |
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 |
133 | Previous
134 |
135 |
136 | Page {currentPage} of {totalPages}
137 |
138 |
147 | Next
148 |
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 |
84 |
No Data Found
85 |
Sorry, no users matched your search.
86 |
setSearchTerm("")}
88 | className="px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-md transition"
89 | >
90 | Clear Search
91 |
92 |
93 | ) : (
94 | <>
95 | {/* User List */}
96 |
97 | {currentUsers.map((user) => (
98 |
102 |
103 |
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 |
139 | Previous
140 |
141 |
142 | Page {currentPage} of {totalPages}
143 |
144 |
153 | Next
154 |
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 | setSelectedFilter(e.target.value)}
90 | className="bg-white/5 text-white/80 text-sm border border-white/10 px-4 py-2 rounded-md backdrop-blur-md"
91 | >
92 | {filterOptions.map((option, idx) => (
93 |
94 | {option}
95 |
96 | ))}
97 |
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 |
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 |
160 | Previous
161 |
162 |
163 | Page {currentPage} of {totalPages}
164 |
165 |
174 | Next
175 |
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 |
38 |
39 | Add Customer
40 |
41 |
42 |
43 | Export
44 |
45 |
46 |
47 | Back
48 |
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 |
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 |
134 | Previous
135 |
136 |
137 | Page {currentPage} of {totalPages}
138 |
139 |
148 | Next
149 |
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 |
--------------------------------------------------------------------------------