├── .env ├── .gitignore ├── README.md ├── components.json ├── componentsa.json ├── eslint.config.mjs ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public ├── file.svg ├── globe.svg ├── images │ ├── Xa.jpg │ └── img.jpg ├── next.svg ├── vercel.svg └── window.svg ├── src ├── actions │ └── auth │ │ ├── addCat.ts │ │ ├── addMenu.ts │ │ ├── agentLogin.ts │ │ ├── bans.ts │ │ ├── kyc.ts │ │ ├── kycAdmin.ts │ │ ├── login.ts │ │ ├── logo.ts │ │ ├── logout.ts │ │ ├── productsAdd.ts │ │ ├── profileUpdate.ts │ │ ├── register.ts │ │ ├── registerAgent.ts │ │ ├── registerVendor.ts │ │ ├── resetPassword.ts │ │ ├── search.ts │ │ ├── siteLogo.ts │ │ └── updatePassword.ts ├── app │ ├── (admin) │ │ └── admin │ │ │ ├── category │ │ │ ├── CatTabel.tsx │ │ │ ├── DeletCat.tsx │ │ │ ├── catForm.tsx │ │ │ ├── catModel.tsx │ │ │ └── page.tsx │ │ │ ├── dashboard │ │ │ └── page.tsx │ │ │ ├── kyc │ │ │ ├── modal.tsx │ │ │ ├── page.tsx │ │ │ └── tabel.tsx │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ ├── settings │ │ │ ├── GeneralSetting.tsx │ │ │ └── page.tsx │ │ │ └── users │ │ │ ├── deleteButton.tsx │ │ │ ├── page.jsx │ │ │ └── tabel.tsx │ ├── (agent) │ │ └── agent │ │ │ ├── addVendor │ │ │ └── page.tsx │ │ │ ├── aside.tsx │ │ │ ├── dashboard │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ └── useContext.tsx │ │ │ ├── login │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ │ ├── page.tsx │ │ │ └── register │ │ │ ├── layout.tsx │ │ │ ├── page copy.tsx │ │ │ └── page.tsx │ ├── (auth) │ │ ├── forgot │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── login │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── register │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ └── updateReset │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ ├── (users) │ │ └── dashboard │ │ │ ├── addproduct │ │ │ └── page.tsx │ │ │ ├── edit-profile │ │ │ └── page.jsx │ │ │ ├── edit │ │ │ └── page.jsx │ │ │ ├── kyc-record │ │ │ └── page.tsx │ │ │ ├── kyc-success │ │ │ └── page.tsx │ │ │ ├── kyc │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── message │ │ │ └── page.tsx │ │ │ ├── page.jsx │ │ │ ├── productlist │ │ │ └── page.jsx │ │ │ ├── profile │ │ │ └── page.jsx │ │ │ ├── success │ │ │ └── page.tsx │ │ │ └── upgrade │ │ │ └── page.tsx │ ├── api │ │ └── payment.ts │ ├── auth │ │ └── confirm │ │ │ └── route.ts │ ├── favicon.ico │ ├── filter │ │ ├── filterFetch.tsx │ │ ├── layout.tsx │ │ └── page.tsx │ ├── globals.css │ ├── layout.tsx │ ├── loading.tsx │ ├── not-found.tsx │ ├── page.jsx │ ├── products │ │ └── [product] │ │ │ ├── fetchCat.tsx │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ ├── search │ │ ├── layout.tsx │ │ ├── page.tsx │ │ └── searchFetch.tsx │ ├── sitemap.xml │ ├── sitemap │ │ └── page.tsx │ ├── store │ │ └── [store] │ │ │ ├── [contact] │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ └── shop.tsx │ ├── trades │ │ ├── [id] │ │ │ ├── feature.tsx │ │ │ ├── fetchSingle.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── page.tsx │ │ └── productFetch.tsx │ ├── types │ │ └── page.d.ts │ └── unauthorized │ │ └── page.tsx ├── components │ ├── ClientLayout.tsx │ ├── ClientNavbar.tsx │ ├── MarketAutocomplete.tsx │ ├── MenuTabel.tsx │ ├── MultiSelectCombobox.tsx │ ├── ProductForm.tsx │ ├── ProductNotFound.tsx │ ├── ProfileForm.tsx │ ├── TawkToScript.tsx │ ├── adminDashboard.tsx │ ├── agentTable.tsx │ ├── agentUser.tsx │ ├── app-sidebar.tsx │ ├── banM.tsx │ ├── banner.tsx │ ├── bannerModel.tsx │ ├── catSlider.tsx │ ├── category.tsx │ ├── catlist.tsx │ ├── clipboard.tsx │ ├── contactInfo.tsx │ ├── currency.ts │ ├── dashboard.tsx │ ├── deleteButton.tsx │ ├── editProfile.tsx │ ├── expanded_products.json │ ├── handyCat.ts │ ├── headerUser.tsx │ ├── hero.tsx │ ├── kyc.tsx │ ├── logo.tsx │ ├── logoModel.tsx │ ├── logoutButton.tsx │ ├── message.tsx │ ├── midTab.tsx │ ├── modal.tsx │ ├── navbar.tsx │ ├── productCategory.tsx │ ├── reviews.tsx │ ├── searchBar.tsx │ ├── selectedState.tsx │ ├── sendMessageModel.tsx │ ├── sheetAdmin.tsx │ ├── sheetMenu.tsx │ ├── siteLogo.tsx │ ├── siteLogoModel.tsx │ ├── slider.tsx │ ├── spinner.tsx │ ├── stateLga.ts │ ├── states.ts │ ├── tableList.tsx │ ├── tabsFetch.tsx │ ├── topbar.tsx │ ├── topnav.tsx │ ├── ui │ │ ├── alert.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── dialog.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── pagination.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── sidebar.tsx │ │ ├── skeleton.tsx │ │ ├── table.tsx │ │ ├── textarea.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ └── tooltip.tsx │ └── userdashboard.tsx ├── context │ └── userContext.tsx ├── hooks │ ├── use-mobile.tsx │ └── use-toast.ts ├── lib │ └── utils.ts ├── middleware.ts └── utils │ ├── supabase-db.ts │ ├── supabase-server.ts │ ├── supabase │ ├── client.ts │ └── server.ts │ └── supabaseSession.ts ├── tailwind.config.ts └── tsconfig.json /.env: -------------------------------------------------------------------------------- 1 | MONGO_URI=mongodb+srv://adampekolo31:holiday100@mstore.s0xvj.mongodb.net/mystore?retryWrites=true&w=majority&appName=mstore 2 | SESSION_SECRET=kodU/2Ap45QkKHp1NyzkjWMxiwwMNCL0BRH+xfG+uKk= 3 | 4 | NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZicGRiY3hqYXZpYW5hYm9hdm9vIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAyOTY4MDUsImV4cCI6MjA1NTg3MjgwNX0.XcdJHY7WOUDELgN0uZnoY1kS2m7u1IK8BlQ9eUNkJt0 5 | NEXT_PUBLIC_SUPABASE_URL=https://fbpdbcxjavianaboavoo.supabase.co 6 | 7 | 8 | 9 | NEXT_PUBLIC_IMAGE_URL = https://sxkmrpzbtqpraucnmnjm.supabase.co/storage/v1/object/public/products_image/ 10 | NEXT_PUBLIC_IMAGE_URL_KYC = https://sxkmrpzbtqpraucnmnjm.supabase.co/storage/v1/object/public/kyc_documents/ 11 | APP_NAME = AfriVendor - Home 12 | APP_DESCRIPTION =Discover a wide range of products from local sellers and online vendors near you. Shop conveniently and support your community by exploring unique items tailored to your needs.. 13 | NEXT_PUBLIC_FLW_PUBLIC_KEY = FLWPUBK-c258ad3d30ecb7579765af4c6ad22df4-X 14 | RESEND_API_KEY = re_As1e6CXw_5mu4psh32TPUiCcSDDKZUxwp 15 | APP_EMAIL = team@afrivendor.ng 16 | FLW_PUBLIC_KEY = FLWPUBK-9fbfe594212abb645780903215b7cc5a-X 17 | 18 | NEXT_PUBLIC_VIDEO_URL_KYC = https://sxkmrpzbtqpraucnmnjm.supabase.co/storage/v1/object/public/videos/ 19 | 20 | ALTOGETHER_AI_KEY = 6817a19bbe1375361754df7395881d675d9ae507e3c62fea2bab36b96eab5e88 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /componentsa.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | 23 | @layer base { 24 | :root { 25 | --sidebar-background: 0 0% 98%; 26 | --sidebar-foreground: 240 5.3% 26.1%; 27 | --sidebar-primary: 240 5.9% 10%; 28 | --sidebar-primary-foreground: 0 0% 98%; 29 | --sidebar-accent: 240 4.8% 95.9%; 30 | --sidebar-accent-foreground: 240 5.9% 10%; 31 | --sidebar-border: 220 13% 91%; 32 | --sidebar-ring: 217.2 91.2% 59.8%; 33 | } 34 | 35 | .dark { 36 | --sidebar-background: 240 5.9% 10%; 37 | --sidebar-foreground: 240 4.8% 95.9%; 38 | --sidebar-primary: 224.3 76.3% 48%; 39 | --sidebar-primary-foreground: 0 0% 100%; 40 | --sidebar-accent: 240 3.7% 15.9%; 41 | --sidebar-accent-foreground: 240 4.8% 95.9%; 42 | --sidebar-border: 240 3.7% 15.9%; 43 | --sidebar-ring: 217.2 91.2% 59.8%; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | export default compat.config({ 13 | extends: ["next/core-web-vitals", "next/typescript"], 14 | rules: { 15 | "@typescript-eslint/no-unused-vars": [ 16 | "error", 17 | { 18 | argsIgnorePattern: "^_", // Ignore unused arguments starting with "_" 19 | varsIgnorePattern: "^_", // Ignore unused variables starting with "_" 20 | }, 21 | ], 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | experimental: { 4 | serverActions: { 5 | bodySizeLimit: '20mb', 6 | }, 7 | }, 8 | eslint: { 9 | ignoreDuringBuilds: true, 10 | }, 11 | typescript: { 12 | ignoreBuildErrors: true, 13 | }, 14 | reactStrictMode: true, 15 | images: { 16 | domains: ['fbpdbcxjavianaboavoo.supabase.co', 'placehold.co'], // Fixed trailing slash 17 | remotePatterns: [ 18 | { 19 | protocol: 'https', 20 | hostname: 'fbpdbcxjavianaboavoo.supabase.co', 21 | port: '', 22 | pathname: '/storage/v1/object/public/**', // Adjusted for Supabase path 23 | }, 24 | { 25 | protocol: 'https', 26 | hostname: 'placehold.co', 27 | port: '', 28 | pathname: '/**', 29 | }, 30 | ], 31 | }, 32 | 33 | // // Add redirect rule for sitemap 34 | // async redirects() { 35 | // return [ 36 | // { 37 | // source: '/sitemap', // The URL path to be redirected 38 | // destination: '/sitemap.xml', // The destination URL (sitemap.xml) 39 | // permanent: true, // This will make it a permanent redirect (HTTP 301) 40 | // }, 41 | // ]; 42 | // }, 43 | 44 | // Add next-sitemap configuration 45 | sitemap: { 46 | siteUrl: 'https://afrivendor.ng', // Update with your actual site URL 47 | generateRobotsTxt: true, // Optional: generates a robots.txt file 48 | changefreq: 'daily', // Optional: set the change frequency 49 | priority: 0.7, // Optional: set the priority for all pages 50 | }, 51 | }; 52 | 53 | module.exports = nextConfig; 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mstore", 3 | "version": "0.1.0", 4 | "private": true, 5 | "description": "This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).", 6 | "license": "ISC", 7 | "author": "", 8 | "type": "commonjs", 9 | "main": "index.js", 10 | "scripts": { 11 | "dev": "next dev", 12 | "build": "next build", 13 | "start": "next start", 14 | "lint": "next lint", 15 | "postbuild": "next-sitemap" 16 | }, 17 | "dependencies": { 18 | "@ffmpeg/core": "^0.12.10", 19 | "@ffmpeg/ffmpeg": "^0.12.15", 20 | "@headlessui/react": "^2.2.0", 21 | "@heroicons/react": "^2.2.0", 22 | "@paystack/inline-js": "^2.22.2", 23 | "@radix-ui/react-dialog": "^1.1.5", 24 | "@radix-ui/react-label": "^2.1.1", 25 | "@radix-ui/react-select": "^2.1.4", 26 | "@radix-ui/react-separator": "^1.1.1", 27 | "@radix-ui/react-slot": "^1.1.1", 28 | "@radix-ui/react-toast": "^1.2.5", 29 | "@radix-ui/react-tooltip": "^1.1.7", 30 | "@supabase/auth-helpers-nextjs": "^0.10.0", 31 | "@supabase/ssr": "^0.5.2", 32 | "@supabase/supabase-js": "^2.47.10", 33 | "@tanstack/react-table": "^8.20.6", 34 | "bootstrap": "^5.3.3", 35 | "browser-image-compression": "^2.0.2", 36 | "class-variance-authority": "^0.7.1", 37 | "clsx": "^2.1.1", 38 | "lucide-react": "^0.469.0", 39 | "next": "^15.1.3", 40 | "next-sitemap": "^4.2.3", 41 | "react": "^19.0.0", 42 | "react-bootstrap": "^2.10.9", 43 | "react-dom": "^19.0.0", 44 | "react-icons": "^5.4.0", 45 | "react-image-file-resizer": "^0.4.8", 46 | "react-photo-view": "^1.2.7", 47 | "react-select": "^5.10.0", 48 | "react-slick": "^0.30.3", 49 | "react-toastify": "^11.0.2", 50 | "react-webcam": "^7.2.0", 51 | "resend": "^4.1.1", 52 | "sharp": "^0.33.5", 53 | "sim-ph": "^1.0.1", 54 | "slick-carousel": "^1.8.1", 55 | "tailwind-merge": "^2.6.0", 56 | "tailwindcss-animate": "^1.0.7", 57 | "zod": "^3.24.1" 58 | }, 59 | "devDependencies": { 60 | "@eslint/eslintrc": "^3", 61 | "@types/node": "^20", 62 | "@types/react": "^19", 63 | "@types/react-dom": "^19", 64 | "@types/react-slick": "^0.23.13", 65 | "eslint": "^9", 66 | "eslint-config-next": "15.1.3", 67 | "postcss": "^8", 68 | "tailwindcss": "^3.4.1", 69 | "typescript": "^5" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/Xa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pchukwuemeka424/backup/c2271a6f46ffad5814017a49553d7be1365aacf2/public/images/Xa.jpg -------------------------------------------------------------------------------- /public/images/img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pchukwuemeka424/backup/c2271a6f46ffad5814017a49553d7be1365aacf2/public/images/img.jpg -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/actions/auth/addCat.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { revalidatePath } from "next/cache"; 3 | import { createClient } from "@/utils/supabase/server"; 4 | import sharp from "sharp"; 5 | 6 | export default async function bannerAction(_: any, formData: FormData) { 7 | const supabase = await createClient(); 8 | 9 | if (!supabase) { 10 | return { errors: { message: "Supabase client failed to initialize." } }; 11 | } 12 | 13 | const { data: userDetails, error: userError } = await supabase.auth.getUser(); 14 | if (userError || !userDetails?.user) { 15 | return { errors: { message: "User authentication failed." } }; 16 | } 17 | 18 | const user_id = userDetails.user.id; 19 | const MAX_FILE_SIZE = 3 * 1024 * 1024; // 3 MB 20 | const allowedImageTypes = ["image/jpeg", "image/png", "image/gif"]; 21 | 22 | const errors: Record = {}; 23 | const documentFile = formData.get("bannerDocument") as File | null; 24 | 25 | if (!documentFile) { 26 | errors.document = "Document is required."; 27 | } else if (documentFile.size > MAX_FILE_SIZE) { 28 | errors.document = "Image is too large. Please upload a smaller image."; 29 | } else if (!allowedImageTypes.includes(documentFile.type)) { 30 | errors.document = "Invalid document format. Only JPG, PNG, and GIF are allowed."; 31 | } 32 | 33 | if (Object.keys(errors).length > 0) { 34 | return { errors }; 35 | } 36 | 37 | try { 38 | // Compress the image 39 | const buffer = await documentFile.arrayBuffer(); 40 | const compressedDocument = await sharp(Buffer.from(buffer)) 41 | .jpeg({ quality: 70 }) 42 | .toBuffer(); 43 | 44 | // Generate file path 45 | const fileName = `avatar_${user_id}_${Date.now()}.jpg`; 46 | const filePath = `public/${fileName}`; 47 | 48 | // Upload image to Supabase Storage 49 | const { error: uploadError } = await supabase.storage 50 | .from("logos") 51 | .upload(filePath, compressedDocument, { contentType: "image/jpeg" }); 52 | 53 | if (uploadError) { 54 | console.error("Error uploading document:", uploadError); 55 | return { errors: { document: `Error uploading document: ${uploadError.message}` } }; 56 | } 57 | 58 | // Get public URL 59 | const { data: publicUrlData } = supabase.storage.from("logos").getPublicUrl(filePath); 60 | const imageUrl = publicUrlData.publicUrl; 61 | 62 | // Extract the category ID from the form data 63 | const categoryId = formData.get("id"); 64 | 65 | // Update category with new banner and title 66 | const { error: updateError } = await supabase 67 | .from("category") 68 | .update({ 69 | banner: imageUrl, 70 | title: formData.get("title"), 71 | }) 72 | .eq("id", categoryId); 73 | 74 | if (updateError) { 75 | console.error("Error updating category:", updateError); 76 | return { errors: { message: `Error updating category: ${updateError.message}` } }; 77 | } 78 | 79 | revalidatePath("/category"); 80 | return { success: true, message: "Banner updated successfully!" }; 81 | } catch (error: any) { 82 | console.error("Error processing document:", error); 83 | return { errors: { document: `Error processing document: ${error.message}` } }; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/actions/auth/addMenu.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { createClient } from "@/utils/supabase/server"; 4 | 5 | export default async function addMenu(state: any, formData: FormData) { 6 | const supabase =await createClient(); 7 | const menu_name = formData.get("menu_name")?.toString() || ""; 8 | const menu_link = formData.get("menu_link")?.toString() || ""; 9 | 10 | const { error } = await supabase 11 | .from("menu") 12 | .insert([{ title: menu_name, url: menu_link }]); 13 | 14 | if (error) { 15 | console.error(error.message); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/actions/auth/agentLogin.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { createClient } from '@/utils/supabase/server'; 4 | import { redirect } from 'next/navigation'; 5 | import { z } from "zod"; 6 | 7 | interface FormData { 8 | get: (key: string) => string | null; 9 | } 10 | 11 | interface LoginState { 12 | errors: Record; 13 | isSubmitting: boolean; 14 | isValid: boolean; 15 | } 16 | 17 | export default async function login(prev: LoginState, formData: FormData) { 18 | const loginSchema = z.object({ 19 | email: z.string().email("Invalid email format"), 20 | password: z.string().min(8, "Password must be at least 8 characters"), 21 | }); 22 | 23 | // Parse and validate the form data 24 | const validated = loginSchema.safeParse({ 25 | email: formData.get("email"), 26 | password: formData.get("password"), 27 | }); 28 | 29 | if (!validated.success) { 30 | const errors = validated.error.flatten().fieldErrors; 31 | 32 | return { 33 | ...prev, 34 | errors, 35 | email: formData.get("email"), 36 | password: formData.get("password"), 37 | isSubmitting: false, 38 | isValid: false, 39 | }; 40 | } 41 | 42 | const supabase = await createClient(); 43 | 44 | // Authenticate the user 45 | const { data: authData, error: authError } = await supabase.auth.signInWithPassword({ 46 | email: validated.data.email, 47 | password: validated.data.password, 48 | }); 49 | 50 | if (authError || !authData?.user) { 51 | return { 52 | ...prev, 53 | errors: { 54 | general: authError?.message || "Invalid credentials", 55 | }, 56 | email: formData.get("email"), 57 | password: formData.get("password"), 58 | isSubmitting: false, 59 | isValid: false, 60 | }; 61 | } 62 | 63 | // Redirect all users to agent dashboard 64 | redirect('/agent/dashboard'); 65 | 66 | return { 67 | ...prev, 68 | errors: {}, 69 | isSubmitting: false, 70 | isValid: true, 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /src/actions/auth/bans.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { revalidatePath } from "next/cache"; 3 | import { createClient } from "@/utils/supabase/server"; 4 | import sharp from "sharp"; 5 | 6 | export default async function bannerAction(_: any, formData: FormData) { 7 | const supabase = await createClient(); 8 | 9 | if (!supabase) { 10 | return { errors: { message: "Supabase client failed to initialize." } }; 11 | } 12 | 13 | const { data: userDetails, error: userError } = await supabase.auth.getUser(); 14 | if (userError || !userDetails?.user) { 15 | return { errors: { message: "User authentication failed." } }; 16 | } 17 | 18 | const user_id = userDetails.user.id; 19 | const MAX_FILE_SIZE = 3 * 1024 * 1024; // 3 MB 20 | const allowedImageTypes = ["image/jpeg", "image/png", "image/gif"]; 21 | 22 | const errors: Record = {}; 23 | const documentFile = formData.get("bannerDocument") as File | null; 24 | 25 | if (!documentFile) { 26 | errors.document = "Document is required."; 27 | } else if (documentFile.size > MAX_FILE_SIZE) { 28 | errors.document = "Image is too large. Please upload a smaller image."; 29 | } else if (!allowedImageTypes.includes(documentFile.type)) { 30 | errors.document = "Invalid document format. Only JPG, PNG, and GIF are allowed."; 31 | } 32 | 33 | if (Object.keys(errors).length > 0) { 34 | return { errors }; 35 | } 36 | 37 | try { 38 | // Compress the image 39 | const buffer = await documentFile.arrayBuffer(); 40 | const compressedDocument = await sharp(Buffer.from(buffer)) 41 | .resize({ width: 600, height: 200, fit: "cover" }) 42 | .jpeg({ quality: 70 }) 43 | .toBuffer(); 44 | 45 | // Generate file path 46 | const fileName = `avatar_${user_id}_${Date.now()}.jpg`; 47 | const filePath = `public/${fileName}`; 48 | 49 | // Upload image to Supabase Storage 50 | const { error: uploadError } = await supabase.storage 51 | .from("logos") 52 | .upload(filePath, compressedDocument, { contentType: "image/jpeg" }); 53 | 54 | if (uploadError) { 55 | console.error("Error uploading document:", uploadError); 56 | return { errors: { document: `Error uploading document: ${uploadError.message}` } }; 57 | } 58 | 59 | // Get public URL 60 | const { data: publicUrlData } = supabase.storage.from("logos").getPublicUrl(filePath); 61 | const imageUrl = publicUrlData.publicUrl; 62 | 63 | // Update user profile with new avatar URL 64 | const { error: updateError } = await supabase 65 | .from("user_profile") 66 | .update({ banner: imageUrl}) // Fixed `avater` typo 67 | .eq("id", user_id); 68 | 69 | if (updateError) { 70 | console.error("Error updating user profile:", updateError); 71 | return { errors: { message: `Error updating user profile: ${updateError.message}` } }; 72 | } 73 | 74 | revalidatePath("/dashboard"); 75 | return { success: true, message: "Banner updated successfully!" }; 76 | } catch (error: any) { 77 | console.error("Error processing document:", error); 78 | return { errors: { document: `Error processing document: ${error.message}` } }; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/actions/auth/kyc.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { createClient } from "@/utils/supabase/server"; 3 | 4 | interface KYCFormInput { 5 | firstName: string; 6 | lastName: string; 7 | idNumber: string; 8 | verificationType: string; 9 | document: string | null; 10 | user_id: string | null; 11 | } 12 | 13 | export default async function handleKYCSubmission(state: any, formData: FormData) { 14 | const supabase = await createClient(); 15 | const userDetails = await supabase.auth.getUser(); 16 | const user_id = userDetails.data?.user?.id || null; 17 | 18 | const MAX_FILE_SIZE = 3 * 1024 * 1024; // 3 MB 19 | const allowedVerificationTypes = ["bank_statement", "international_passport", "nin"]; 20 | 21 | // Parse form data 22 | const formInput: KYCFormInput = { 23 | firstName: formData.get("firstName")?.toString() || "", 24 | lastName: formData.get("lastName")?.toString() || "", 25 | idNumber: formData.get("idNumber")?.toString() || "", 26 | verificationType: formData.get("verificationType")?.toString() || "", 27 | document: null, 28 | user_id: user_id, 29 | }; 30 | 31 | const errors: Record = {}; 32 | if (!formInput.firstName) errors.firstName = "First name is required."; 33 | if (!formInput.lastName) errors.lastName = "Last name is required."; 34 | if (!formInput.idNumber) errors.idNumber = "ID number is required."; 35 | if (!allowedVerificationTypes.includes(formInput.verificationType)) { 36 | errors.verificationType = "Invalid verification type."; 37 | } 38 | 39 | const documentFile = formData.get("document") as File | null; 40 | if (!documentFile) { 41 | errors.document = "Document is required."; 42 | } else if (documentFile.size > MAX_FILE_SIZE) { 43 | errors.document = "Image is too large. Please upload a smaller image."; 44 | } else { 45 | const allowedImageTypes = ['image/jpeg', 'image/png', 'image/gif']; 46 | if (!allowedImageTypes.includes(documentFile.type)) { 47 | errors.document = "Invalid document format. Only JPG, PNG, and GIF are allowed."; 48 | } 49 | } 50 | 51 | if (Object.keys(errors).length > 0) { 52 | return { errors }; 53 | } 54 | 55 | try { 56 | const fileName = `kyc_${Date.now()}_${documentFile.name}`; 57 | const filePath = `kyc_documents/${fileName}`; 58 | 59 | const { data: uploadData, error: uploadError } = await supabase.storage 60 | .from("kyc_documents") 61 | .upload(filePath, documentFile, { contentType: documentFile.type }); 62 | 63 | if (uploadError) { 64 | console.error("Error uploading document:", uploadError); 65 | return { errors: { document: `Error uploading document: ${uploadError.message}` } }; 66 | } 67 | 68 | formInput.document = filePath; 69 | 70 | const { data: kycData, error: insertError } = await supabase 71 | .from("kyc") 72 | .update({ 73 | first_name: formInput.firstName, 74 | last_name: formInput.lastName, 75 | id_number: formInput.idNumber, 76 | verification_type: formInput.verificationType, 77 | document: filePath, 78 | }) 79 | .eq("user_id", user_id); 80 | 81 | if (insertError) { 82 | console.error("Error inserting KYC data:", insertError); 83 | return { errors: { message: `Error submitting KYC data: ${insertError.message}` } }; 84 | } 85 | 86 | // Return a success flag instead of redirecting 87 | return { success: true, data: kycData }; 88 | } catch (error) { 89 | console.error("Error processing document:", error); 90 | return { errors: { document: `Error processing document: ${error.message}` } }; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/actions/auth/kycAdmin.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { createClient } from "@/utils/supabase/server"; 3 | import { revalidatePath } from "next/cache"; 4 | import { Resend } from "resend"; 5 | 6 | export async function updateKYC(data: any) { 7 | const supabase = await createClient(); 8 | const resend = new Resend(process.env.RESEND_API_KEY); // Ensure API key is set 9 | 10 | // Update KYC Table 11 | const { error } = await supabase 12 | .from("kyc") 13 | .update({ 14 | first_name: data.first_name, 15 | last_name: data.last_name, 16 | verification_type: data.verification_type, 17 | id_number: data.id_number, 18 | kyc_status: data.kyc_status, 19 | }) 20 | .eq("id", data.id); 21 | 22 | if (error) return { error: error.message }; 23 | 24 | // Update User Profile Table 25 | const { error: profileError } = await supabase 26 | .from("user_profile") 27 | .update({ 28 | kyc_status: data.kyc_status, 29 | }) 30 | .eq("id", data.user_id); 31 | 32 | if (profileError) return { error: profileError.message }; 33 | 34 | // Fetch User Email 35 | const { data: userData, error: userError } = await supabase 36 | .from("user_profile") 37 | .select("email, username") 38 | .eq("id", data.user_id) 39 | .single(); 40 | 41 | if (userError || !userData) { 42 | return { error: "User not found or email missing." }; 43 | } 44 | 45 | // Determine Email Subject and Content Based on KYC Status 46 | let subject = ""; 47 | let message = ""; 48 | 49 | if (data.kyc_status === "Approved") { 50 | subject = "🎉 KYC Approved - Welcome Aboard!"; 51 | message = ` 52 |

Dear ${userData.username},

53 |

Congratulations! Your KYC verification has been successfully approved.

54 |

You now have full access to our platform.

55 |

Thank you for choosing ${process.env.APP_NAME}!

56 | `; 57 | } else if (data.kyc_status === "Rejected") { 58 | subject = "❌ KYC Rejected - Action Required"; 59 | message = ` 60 |

Dear ${userData.username},

61 |

Unfortunately, your KYC verification was rejected. Please check your submitted details and try again.

62 |

For further assistance, contact our support team.

63 | `; 64 | } 65 | 66 | // Send Email If KYC is Approved or Rejected 67 | if (subject && message) { 68 | await resend.emails.send({ 69 | from: `${process.env.APP_NAME} <${process.env.APP_EMAIL}>`, 70 | to: userData.email, 71 | subject, 72 | html: ` 73 |
74 | ${message} 75 |

Best Regards,

76 |

The ${process.env.APP_NAME} Team

77 |
78 | `, 79 | }); 80 | } 81 | // delete video using [video_path] 82 | const { error: videoError } = await supabase 83 | .storage 84 | .from("videos") 85 | .remove([data.video]); 86 | if (videoError) { 87 | console.error("Error deleting video from bucket:", videoError); 88 | alert("Failed to delete the video from the storage bucket."); 89 | return; 90 | } 91 | 92 | revalidatePath("/admin/kyc", "page"); // Ensure cache refresh 93 | return { success: true }; 94 | } 95 | -------------------------------------------------------------------------------- /src/actions/auth/login.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { createClient } from '@/utils/supabase/server'; 4 | import { redirect } from 'next/navigation'; 5 | import { z } from "zod"; 6 | 7 | interface FormData { 8 | get: (key: string) => string | null; 9 | } 10 | 11 | interface LoginState { 12 | errors: Record; 13 | isSubmitting: boolean; 14 | isValid: boolean; 15 | } 16 | 17 | export default async function login(prev: LoginState, formData: FormData) { 18 | const loginSchema = z.object({ 19 | email: z.string().email("Invalid email format"), 20 | password: z.string().min(8, "Password must be at least 8 characters"), 21 | }); 22 | 23 | // Parse and validate the form data 24 | const validated = loginSchema.safeParse({ 25 | email: formData.get("email"), 26 | password: formData.get("password"), 27 | }); 28 | 29 | if (!validated.success) { 30 | const errors = validated.error.flatten().fieldErrors; 31 | 32 | return { 33 | ...prev, 34 | errors, 35 | email: formData.get("email"), 36 | password: formData.get("password"), 37 | isSubmitting: false, 38 | isValid: false, 39 | }; 40 | } 41 | 42 | const supabase = await createClient(); 43 | 44 | // Authenticate the user 45 | const { data: authData, error: authError } = await supabase.auth.signInWithPassword({ 46 | email: validated.data.email, 47 | password: validated.data.password, 48 | }); 49 | 50 | if (authError || !authData?.user) { 51 | return { 52 | ...prev, 53 | errors: { 54 | general: authError?.message || "Invalid credentials", 55 | }, 56 | email: formData.get("email"), 57 | password: formData.get("password"), 58 | isSubmitting: false, 59 | isValid: false, 60 | }; 61 | } 62 | 63 | // Fetch user role from the `user_profile` table 64 | const { data: userProfile, error: profileError } = await supabase 65 | .from("user_profile") 66 | .select("role") 67 | .eq("user_id", authData.user.id) 68 | .single(); 69 | 70 | if (profileError || !userProfile) { 71 | return { 72 | ...prev, 73 | errors: { 74 | general: profileError?.message || "User profile not found", 75 | }, 76 | isSubmitting: false, 77 | isValid: false, 78 | }; 79 | } 80 | 81 | // Redirect based on role 82 | if (userProfile.role === "admin") { 83 | redirect('/admin/'); 84 | } else { 85 | redirect('/dashboard'); 86 | } 87 | 88 | return { 89 | ...prev, 90 | errors: {}, 91 | isSubmitting: false, 92 | isValid: true, 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /src/actions/auth/logo.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { revalidatePath } from "next/cache"; 3 | import { createClient } from "@/utils/supabase/server"; 4 | import sharp from "sharp"; 5 | 6 | export default async function handleLogoSubmission(_: any, formData: FormData) { 7 | const supabase = await createClient(); 8 | 9 | if (!supabase) { 10 | return { errors: { message: "Supabase client failed to initialize." } }; 11 | } 12 | 13 | const { data: userDetails, error: userError } = await supabase.auth.getUser(); 14 | if (userError || !userDetails?.user) { 15 | return { errors: { message: "User authentication failed." } }; 16 | } 17 | 18 | const user_id = userDetails.user.id; 19 | const MAX_FILE_SIZE = 3 * 1024 * 1024; // 3 MB 20 | const allowedImageTypes = ["image/jpeg", "image/png", "image/gif"]; 21 | 22 | const errors: Record = {}; 23 | const documentFile = formData.get("document") as File | null; 24 | 25 | if (!documentFile) { 26 | errors.document = "Document is required."; 27 | } else if (documentFile.size > MAX_FILE_SIZE) { 28 | errors.document = "Image is too large. Please upload a smaller image."; 29 | } else if (!allowedImageTypes.includes(documentFile.type)) { 30 | errors.document = "Invalid document format. Only JPG, PNG, and GIF are allowed."; 31 | } 32 | 33 | if (Object.keys(errors).length > 0) { 34 | return { errors }; 35 | } 36 | 37 | try { 38 | // Compress the image 39 | const buffer = await documentFile.arrayBuffer(); 40 | const compressedDocument = await sharp(Buffer.from(buffer)) 41 | // .resize({ width: 300, height: 300, fit: "cover" }) 42 | .png({ quality: 70 }) 43 | .toBuffer(); 44 | 45 | // Generate file path 46 | const fileName = `avatar_${user_id}_${Date.now()}.jpg`; 47 | const filePath = `public/${fileName}`; 48 | 49 | // Upload image to Supabase Storage 50 | const { error: uploadError } = await supabase.storage 51 | .from("logos") 52 | .upload(filePath, compressedDocument, { contentType: "image/jpeg" }); 53 | 54 | if (uploadError) { 55 | console.error("Error uploading document:", uploadError); 56 | return { errors: { document: `Error uploading document: ${uploadError.message}` } }; 57 | } 58 | 59 | // Get public URL 60 | const { data: publicUrlData } = supabase.storage.from("logos").getPublicUrl(filePath); 61 | const imageUrl = publicUrlData.publicUrl; 62 | 63 | // Update user profile with new avatar URL 64 | const { error: updateError } = await supabase 65 | .from("user_profile") 66 | .update({ avater: imageUrl, avater: imageUrl }) // Fixed `avater` typo 67 | .eq("id", user_id); 68 | 69 | if (updateError) { 70 | console.error("Error updating user profile:", updateError); 71 | return { errors: { message: `Error updating user profile: ${updateError.message}` } }; 72 | } 73 | 74 | revalidatePath("/dashboard"); 75 | return { success: true, message: "Logo uploaded successfully!" }; 76 | } catch (error: any) { 77 | console.error("Error processing document:", error); 78 | return { errors: { document: `Error processing document: ${error.message}` } }; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/actions/auth/logout.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { createClient } from "@/utils/supabase/client"; 3 | import { redirect } from "next/navigation"; 4 | 5 | export default async function logout() { 6 | const supabase = createClient(); 7 | const { error } = await supabase.auth.signOut(); 8 | 9 | if (error) { 10 | return { success: false, message: error.message }; // Return error state 11 | } 12 | redirect('/login'); 13 | } 14 | -------------------------------------------------------------------------------- /src/actions/auth/profileUpdate.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { z } from "zod"; 4 | import { redirect } from "next/navigation"; 5 | import { createClient } from "@/utils/supabase/server"; 6 | 7 | export default async function profileUpdate(state: any, formData: FormData) { 8 | const supabase = await createClient(); 9 | 10 | // Authenticate user 11 | const { data: { user }, error: authError } = await supabase.auth.getUser(); 12 | if (authError || !user) { 13 | console.error("Authentication error:", authError?.message); 14 | return { errors: { message: "Authentication error" } }; 15 | } 16 | 17 | // Validation schema 18 | const schema = z.object({ 19 | username: z.string().min(3, "Username must be at least 3 characters long"), 20 | shopname: z.string().min(3, "Shop name must be at least 3 characters long"), 21 | phone: z.string().regex(/^\d{11}$/, "Phone must be 11 digits"), 22 | address: z.string().optional(), 23 | stat: z.string().min(2, "State is required"), 24 | city: z.string().min(2, "City is required"), 25 | facebook: z.string().optional(), 26 | instagram: z.string().optional(), 27 | tiktok: z.string().optional(), 28 | twitter: z.string().optional(), 29 | about: z.string().optional(), 30 | }); 31 | 32 | const profileData = Object.fromEntries(formData); 33 | const validation = schema.safeParse(profileData); 34 | 35 | if (!validation.success) { 36 | return { errors: validation.error.flatten().fieldErrors }; 37 | } 38 | 39 | const { error } = await supabase 40 | .from("user_profile") 41 | .update(validation.data) 42 | .eq("id", user.id); 43 | 44 | if (error) { 45 | console.error("Profile update error:", error); 46 | return { errors: { message: "Failed to update profile" } }; 47 | } 48 | 49 | // Redirect and ensure function ends 50 | redirect("/dashboard"); 51 | return null; // Ensure no further execution happens after the redirect 52 | } 53 | -------------------------------------------------------------------------------- /src/actions/auth/registerVendor.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { z } from "zod"; 4 | import { redirect } from "next/navigation"; 5 | import { createClient } from "@/utils/supabase/server"; 6 | 7 | export default async function RegisterVendor(state: any, formData: FormData) { 8 | const supabase = await createClient(); 9 | 10 | // Validation schema 11 | const schema = z.object({ 12 | shopname: z.string().min(3, "Shop name must be at least 3 characters long"), 13 | phone: z.string().regex(/^\d{11}$/, "Phone must be 11 digits"), 14 | address: z.string().optional(), 15 | stat: z.string().min(2, "State is required"), 16 | about: z.string().optional(), 17 | marketname: z.string().min(3, "Market name must be at least 3 characters long"), 18 | city: z.string().min(2, "City is required"), 19 | line: z.string().optional(), 20 | agentId: z.string().optional(), // Assuming this is optional 21 | latitude: z.string().optional(), 22 | longitude: z.string().optional(), 23 | email: z.string().email("Invalid email format"), 24 | password: z.string().min(6, "Password must be at least 6 characters long"), 25 | username: z.string().optional(), 26 | 27 | }); 28 | 29 | // Convert formData to a usable object 30 | const profileData = Object.fromEntries(formData.entries()); 31 | 32 | // Validate data 33 | const validation = schema.safeParse(profileData); 34 | if (!validation.success) { 35 | return { errors: validation.error.flatten().fieldErrors }; 36 | } 37 | 38 | // Sign up user with email and password in Supabase 39 | const { data: authData, error: authError } = await supabase.auth.signUp({ 40 | email: validation.data.email, 41 | password: validation.data.password, 42 | }); 43 | 44 | if (authError) { 45 | console.error("Sign up error:", authError); 46 | return { errors: { message: authError.message || "Failed to sign up" } }; 47 | } 48 | 49 | // Insert vendor profile into Supabase 50 | const { error: profileError } = await supabase.from("user_profile").insert([ 51 | { id: authData.user?.id, 52 | shopname: validation.data.shopname, 53 | phone: validation.data.phone, 54 | location: validation.data.address, 55 | stat: validation.data.stat, 56 | about: validation.data.about, 57 | marketname: validation.data.marketname, 58 | city: validation.data.city, 59 | line: validation.data.line, 60 | agentId: validation.data.agentId, 61 | latitude: validation.data.latitude, 62 | longitude: validation.data.longitude, 63 | username: validation.data.username, 64 | kyc_status: "Approved", 65 | pd: validation.data.password, 66 | email: validation.data.email, 67 | }, 68 | ]); 69 | 70 | if (profileError) { 71 | console.error("Profile insert error:", profileError); 72 | return { errors: { message: profileError.message || "Failed to update profile" } }; 73 | } 74 | 75 | // Redirect after successful registration 76 | redirect("/dashboard"); 77 | return null; 78 | } 79 | -------------------------------------------------------------------------------- /src/actions/auth/resetPassword.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { createClient } from "@/utils/supabase/server"; 4 | import { z } from "zod"; 5 | 6 | interface FormData { 7 | get: (key: string) => string | null; 8 | } 9 | 10 | interface ResetState { 11 | errors: Record; 12 | isSubmitting: boolean; 13 | isValid: boolean; 14 | email?: string; 15 | } 16 | 17 | export default async function resetPassword( 18 | prev: ResetState, 19 | formData: FormData 20 | ): Promise { 21 | // Define the schema for email validation 22 | const resetSchema = z.object({ 23 | email: z.string().email("Invalid email format"), 24 | }); 25 | 26 | // Extract and validate form data 27 | const email = formData.get("email") || ""; // Default to an empty string if null 28 | const validation = resetSchema.safeParse({ email }); 29 | 30 | if (!validation.success) { 31 | // Validation failed: return errors 32 | const errors = Object.fromEntries( 33 | Object.entries(validation.error.flatten().fieldErrors).map(([key, value]) => [ 34 | key, 35 | value.join(", "), 36 | ]) 37 | ); 38 | 39 | return { 40 | ...prev, 41 | errors, 42 | email, 43 | isSubmitting: false, 44 | isValid: false, 45 | }; 46 | } 47 | 48 | const supabase = await createClient(); 49 | 50 | // Attempt to send the reset email 51 | try { 52 | const { error } = await supabase.auth.resetPasswordForEmail(validation.data.email); 53 | 54 | if (error) { 55 | // Supabase-specific error occurred 56 | return { 57 | ...prev, 58 | errors: { general: error.message || "Unable to send reset email." }, 59 | email, 60 | isSubmitting: false, 61 | isValid: false, 62 | }; 63 | } 64 | 65 | // Reset email sent successfully 66 | return { 67 | ...prev, 68 | errors: {}, 69 | email, 70 | isSubmitting: false, 71 | isValid: true, 72 | }; 73 | } catch (err) { 74 | // Handle unexpected errors 75 | return { 76 | ...prev, 77 | errors: { general: "An unexpected error occurred. Please try again later." }, 78 | email, 79 | isSubmitting: false, 80 | isValid: false, 81 | }; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/actions/auth/search.ts: -------------------------------------------------------------------------------- 1 | // actions/search.ts 2 | "use server"; 3 | 4 | import { redirect } from "next/navigation"; 5 | 6 | export async function search(formData: FormData) { 7 | const search = formData.get('search'); 8 | const state = formData.get('state'); 9 | 10 | // Perform search logic here 11 | redirect(`/filter/?q=${search}&state=${state}`); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/actions/auth/siteLogo.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { revalidatePath } from "next/cache"; 3 | import { createClient } from "@/utils/supabase/server"; 4 | import sharp from "sharp"; 5 | 6 | export default async function handleLogoSubmission(_: any, formData: FormData) { 7 | const supabase = await createClient(); 8 | 9 | if (!supabase) { 10 | return { errors: { message: "Supabase client failed to initialize." } }; 11 | } 12 | 13 | const { data: userDetails, error: userError } = await supabase.auth.getUser(); 14 | if (userError || !userDetails?.user) { 15 | return { errors: { message: "User authentication failed." } }; 16 | } 17 | 18 | const user_id = userDetails.user.id; 19 | const MAX_FILE_SIZE = 3 * 1024 * 1024; // 3 MB 20 | const allowedImageTypes = ["image/jpeg", "image/png", "image/gif"]; 21 | 22 | const errors: Record = {}; 23 | const documentFile = formData.get("document") as File | null; 24 | 25 | if (!documentFile) { 26 | errors.document = "Document is required."; 27 | } else if (documentFile.size > MAX_FILE_SIZE) { 28 | errors.document = "Image is too large. Please upload a smaller image."; 29 | } else if (!allowedImageTypes.includes(documentFile.type)) { 30 | errors.document = "Invalid document format. Only JPG, PNG, and GIF are allowed."; 31 | } 32 | 33 | if (Object.keys(errors).length > 0) { 34 | return { errors }; 35 | } 36 | 37 | try { 38 | // Compress the image 39 | const buffer = await documentFile.arrayBuffer(); 40 | const compressedDocument = await sharp(Buffer.from(buffer)) 41 | // .resize({ width: 300, height: 300, fit: "cover" }) 42 | .jpeg({ quality: 70 }) 43 | .toBuffer(); 44 | 45 | 46 | 47 | 48 | // Generate file path 49 | const fileName = `avatar_${user_id}_${Date.now()}.jpg`; 50 | const filePath = `public/${fileName}`; 51 | 52 | // Upload image to Supabase Storage 53 | const { error: uploadError } = await supabase.storage 54 | .from("logos") 55 | .upload(filePath, compressedDocument, { contentType: "image/jpeg" }); 56 | 57 | if (uploadError) { 58 | console.error("Error uploading document:", uploadError); 59 | return { errors: { document: `Error uploading document: ${uploadError.message}` } }; 60 | } 61 | 62 | // Get public URL 63 | const { data: publicUrlData } = supabase.storage.from("logos").getPublicUrl(filePath); 64 | const imageUrl = publicUrlData.publicUrl; 65 | 66 | // Update user profile with new avatar URL 67 | const { error: updateError } = await supabase 68 | .from("site_info") 69 | .update({ avatar: imageUrl }) 70 | .eq("user_id", user_id); 71 | 72 | if (updateError) { 73 | console.error("Error updating user profile:", updateError); 74 | return { errors: { message: `Error updating user profile: ${updateError.message}` } }; 75 | } 76 | 77 | revalidatePath("/dashboard"); 78 | return { success: true, message: "Logo uploaded successfully!" }; 79 | } catch (error: any) { 80 | console.error("Error processing document:", error); 81 | return { errors: { document: `Error processing document: ${error.message}` } }; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/actions/auth/updatePassword.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { createClient } from "@/utils/supabase/server"; 4 | import { z } from "zod"; 5 | 6 | interface FormData { 7 | get: (key: string) => string | null; 8 | } 9 | 10 | interface UpdateState { 11 | errors: Record; 12 | isSubmitting: boolean; 13 | isValid: boolean; 14 | successMessage?: string; // Optional success message 15 | } 16 | 17 | export default async function updatePassword( 18 | prev: UpdateState, 19 | formData: FormData 20 | ): Promise { 21 | // Define schema for password, email, and token validation 22 | const updateSchema = z.object({ 23 | password: z 24 | .string() 25 | .min(8, "Password must be at least 8 characters long"), 26 | confirmPassword: z.string(), 27 | token: z.string().min(1, "Invalid or missing reset token"), 28 | email: z.string().email("Invalid email address"), // Validate email 29 | }).refine((data) => data.password === data.confirmPassword, { 30 | message: "Passwords do not match", 31 | path: ["confirmPassword"], 32 | }); 33 | 34 | // Extract form data 35 | const password = formData.get("password") || ""; 36 | const confirmPassword = formData.get("confirmPassword") || ""; 37 | const token = formData.get("token") || ""; 38 | const email = formData.get("email") || ""; // Get email 39 | 40 | // Validate the input 41 | const validation = updateSchema.safeParse({ password, confirmPassword, token, email }); 42 | 43 | if (!validation.success) { 44 | const errors = Object.fromEntries( 45 | Object.entries(validation.error.flatten().fieldErrors).map(([key, value]) => [ 46 | key, 47 | value.join(", "), 48 | ]) 49 | ); 50 | 51 | return { 52 | ...prev, 53 | errors, 54 | isSubmitting: false, 55 | isValid: false, 56 | }; 57 | } 58 | 59 | const supabase = await createClient(); 60 | 61 | // Attempt to update the password 62 | try { 63 | const { error } = await supabase.auth.updateUser({ 64 | password: validation.data.password, 65 | token: validation.data.token, 66 | email: validation.data.email, // Update email as well 67 | }); 68 | 69 | if (error) { 70 | return { 71 | ...prev, 72 | errors: { general: error.message || "Failed to update password." }, 73 | isSubmitting: false, 74 | isValid: false, 75 | }; 76 | } 77 | 78 | // Password update was successful 79 | return { 80 | ...prev, 81 | errors: {}, 82 | isSubmitting: false, 83 | isValid: true, 84 | successMessage: "Password updated successfully. You can now login with your new password.", // Success message 85 | }; 86 | } catch (err) { 87 | return { 88 | ...prev, 89 | errors: { general: "An unexpected error occurred. Please try again later." }, 90 | isSubmitting: false, 91 | isValid: false, 92 | }; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/app/(admin)/admin/category/DeletCat.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import { createClient } from "@/utils/supabase/client"; 3 | import React, { useState } from "react"; 4 | import { FaTrash } from "react-icons/fa"; 5 | 6 | interface DeletCatProps { 7 | record: any; 8 | refreshData: () => void; // Function to refresh category list 9 | } 10 | 11 | export default function DeletCat({ record, refreshData }: DeletCatProps) { 12 | const supabase = createClient(); 13 | const [loading, setLoading] = useState(false); 14 | 15 | const handleDelete = async (id: number) => { 16 | if (!window.confirm("Are you sure you want to delete this category?")) return; 17 | 18 | setLoading(true); 19 | try { 20 | const { error } = await supabase.from("category").delete().eq("id", id); 21 | if (error) { 22 | console.error("Error deleting category:", error); 23 | } else { 24 | console.log("Category deleted successfully!"); 25 | refreshData(); // Refresh the category list 26 | } 27 | } catch (error) { 28 | console.error("Error deleting category:", error); 29 | } finally { 30 | setLoading(false); 31 | } 32 | }; 33 | 34 | return ( 35 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/app/(admin)/admin/category/catModel.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState } from "react"; 4 | 5 | import { 6 | Dialog, 7 | DialogContent, 8 | DialogDescription, 9 | DialogHeader, 10 | DialogTitle, 11 | DialogTrigger, 12 | } from "@/components/ui/dialog"; 13 | import { Button } from "@/components/ui/button"; 14 | import { FaEye, FaFileAlt } from "react-icons/fa"; 15 | import { Input } from "@/components/ui/input"; 16 | import { Label } from "@/components/ui/label"; 17 | import { createClient } from "@/utils/supabase/client"; 18 | import { FaF } from "react-icons/fa6"; 19 | import CatForm from "./catForm"; 20 | 21 | 22 | 23 | export default function BannwerModalLogo({record}) { 24 | 25 | 26 | return ( 27 | 28 | 29 | 30 | 34 | 35 | 36 | 37 | 38 | Update Category 39 | 40 | Update Category 41 | 42 | 43 | 44 | 45 | 46 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/app/(admin)/admin/category/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import CatTabel from './CatTabel' 3 | export default function page() { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /src/app/(admin)/admin/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function admin() { 4 | return ( 5 |
admin
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/app/(admin)/admin/kyc/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import KycTable from './tabel' 3 | 4 | export default function Kyc() { 5 | return ( 6 |
7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /src/app/(admin)/admin/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AppSidebar } from '@/components/app-sidebar' 3 | import { SheetAdmin } from '@/components/sheetAdmin' 4 | 5 | export default function layout({children}:{children:React.ReactNode}) { 6 | return ( 7 |
8 | 9 | {children} 10 | 11 |
12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /src/app/(admin)/admin/page.tsx: -------------------------------------------------------------------------------- 1 | import AdminDashboard from '@/components/adminDashboard' 2 | import AdminMenu from '@/components/app-sidebar' 3 | import React from 'react' 4 | 5 | export default function Dashboard() { 6 | return ( 7 |
8 | 9 | 10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/app/(admin)/admin/settings/GeneralSetting.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Input } from "@/components/ui/input"; 4 | import { Button } from "@/components/ui/button"; 5 | import { Card, CardContent, CardHeader } from "@/components/ui/card"; 6 | import { Plus, Trash } from "lucide-react"; 7 | import SiteLogoModel from "@/components/siteLogoModel"; 8 | import Image from "next/image"; 9 | import AddMenu from "@/actions/auth/addMenu"; 10 | import AddCat from "@/actions/auth/addCat"; 11 | import { useActionState } from "react"; 12 | import MenuTabel from "@/components/MenuTabel"; 13 | import CatTabel from "@/app/(admin)/admin/category/CatTabel"; 14 | export default function SettingsPage({ userDetails, siteInfo }) { 15 | const [state, action, isPending] = useActionState(AddMenu, undefined); 16 | const[state1, action1, isPending1] = useActionState(AddCat, undefined); 17 | 18 | return ( 19 |
20 | 21 | Site Logo 22 | 23 |
24 | Site Logo 31 | 32 |
33 |
34 |
35 | 36 | 37 | Menus 38 | 39 |
40 |
41 | 46 | 51 | 54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 | 62 | 63 | Add Category 64 | 65 |
66 |
67 | 72 | 75 |
76 |
77 | 78 |
79 |
80 |
81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /src/app/(admin)/admin/settings/page.tsx: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import React from "react"; 4 | import GeneralSetting from "./GeneralSetting"; 5 | import { createClient } from "@/utils/supabase/server"; 6 | 7 | export default async function Settings() { 8 | 9 | 10 | 11 | 12 | const supabase =await createClient(); // No need for await here 13 | 14 | // Fetch user details 15 | const { data: userDetails, error: userError } = await supabase.auth.getUser(); 16 | 17 | if (userError || !userDetails?.user) { 18 | console.error("Error fetching user:", userError); 19 | return
Error loading user details
; 20 | } 21 | 22 | // Fetch site information if user exists 23 | const { data: siteInfo, error: siteError } = await supabase 24 | .from("site_info") 25 | .select("*") 26 | .single(); 27 | 28 | if (siteError) { 29 | console.error("Error fetching site info:", siteError); 30 | } 31 | 32 | return ( 33 | 34 |
35 | 36 | 37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/app/(admin)/admin/users/deleteButton.tsx: -------------------------------------------------------------------------------- 1 | import { createClient } from "@/utils/supabase/client"; // Adjust import based on your project structure 2 | 3 | // Initialize the Supabase client 4 | const supabase = createClient(); 5 | 6 | const DeleteButton = ({ productId, imagePath }: { productId: string; imagePath: string }) => { 7 | const handleDelete = async () => { 8 | try { 9 | // Delete the product from the "products" table by productId 10 | const { error: deleteError } = await supabase 11 | .from("products") 12 | .delete() 13 | .eq("user_id", productId); 14 | if (deleteError) { 15 | console.error("Error deleting product:", deleteError); 16 | alert("Failed to delete the product from the database."); 17 | return; 18 | } 19 | 20 | // Delete the image from the Supabase bucket 21 | const { error: storageError } = await supabase 22 | .storage 23 | .from("products_image") 24 | .remove([imagePath.replace(/.*\/public\//, "")]); 25 | if (storageError) { 26 | console.error("Error deleting image from bucket:", storageError); 27 | alert("Failed to delete the image from the storage bucket."); 28 | return; 29 | } 30 | 31 | console.log("Product and associated image deleted successfully!"); 32 | alert("Product and associated image deleted successfully!"); 33 | // Refresh page automatically 34 | window.location.reload(); 35 | 36 | } catch (err) { 37 | console.error("Error during deletion process:", err); 38 | alert("An unexpected error occurred while deleting."); 39 | } 40 | }; 41 | 42 | return ( 43 | 46 | ); 47 | }; 48 | 49 | export default DeleteButton; 50 | -------------------------------------------------------------------------------- /src/app/(admin)/admin/users/page.jsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState, useEffect } from "react"; 4 | import Userdashboard from "@/components/userdashboard"; 5 | import Topbar from "@/components/topbar"; 6 | import Link from "next/link"; 7 | import TableComponent from "@/app/(admin)/admin/users/tabel"; 8 | 9 | export default function UserList() { 10 | 11 | return ( 12 |
13 | 14 | 15 | {/* Main Content Area */} 16 |
17 | {/* Topbar */} 18 | 19 | 20 | 21 |
22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/app/(agent)/agent/aside.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState } from 'react'; 3 | import React from "react"; 4 | import { X } from "lucide-react"; // Ensure you have lucide-react installed 5 | 6 | interface Agent { 7 | name: string; 8 | agentId: string; 9 | } 10 | 11 | interface AsideProps { 12 | isSidebarOpen: boolean; 13 | setIsSidebarOpen: (open: boolean) => void; 14 | agent: Agent; 15 | } 16 | const [isSidebarOpen, setIsSidebarOpen] = useState(false); 17 | export default function Aside({ isSidebarOpen, setIsSidebarOpen, agent }: AsideProps) { 18 | return ( 19 | 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /src/app/(agent)/agent/dashboard/layout.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { Menu, User, X } from "lucide-react"; 4 | import { UserProvider } from "./useContext"; 5 | 6 | export default function Layout({ children }: { children: React.ReactNode }) { 7 | const [agent, setAgent] = React.useState(null); 8 | const [isSidebarOpen, setIsSidebarOpen] = React.useState(false); // Added missing state 9 | 10 | return ( 11 | 12 |
13 | {/* Sidebar */} 14 | 54 | 55 | {/* Main Content */} 56 |
57 | 63 | 64 |

65 | This is your dashboard where you can manage your tasks. 66 |

67 | 68 | {/* Dashboard Stats */} 69 | {children} 70 |
71 |
72 |
73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /src/app/(agent)/agent/dashboard/useContext.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { createContext, useContext, useEffect, useState } from "react"; 3 | import { createClient } from "@/utils/supabase/client"; 4 | 5 | const supabase = createClient(); 6 | 7 | const UserContext = createContext(null); 8 | 9 | export function UserProvider({ children }: { children: React.ReactNode }) { 10 | const [user, setUser] = useState(null); 11 | 12 | useEffect(() => { 13 | const getUserProfile = async () => { 14 | try { 15 | const { data: authUser, error: authError } = await supabase.auth.getUser(); 16 | if (authError) { 17 | console.error("Error getting user from auth:", authError); 18 | return; 19 | } 20 | console.log("Auth User:", authUser); 21 | 22 | if (!authUser?.user) { 23 | console.log("No authenticated user found."); 24 | return; 25 | } 26 | 27 | const { data: profile, error } = await supabase 28 | .from("agents") 29 | .select("*") 30 | .eq("email", authUser.user.email) 31 | .single(); 32 | 33 | if (error) { 34 | console.error("Error fetching profile:", error); 35 | return; 36 | } 37 | 38 | console.log("Fetched profile:", profile); 39 | setUser(profile); 40 | } catch (error) { 41 | console.error("Unexpected error:", error); 42 | } 43 | }; 44 | 45 | getUserProfile(); 46 | }, []); 47 | 48 | return {children}; 49 | } 50 | 51 | export function useUser() { 52 | return useContext(UserContext); 53 | } 54 | -------------------------------------------------------------------------------- /src/app/(agent)/agent/login/layout.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | import { Metadata } from 'next' 3 | 4 | export const metadata: Metadata = { 5 | title: `${process.env.APP_NAME} - Login`, 6 | description: `${process.env.APP_DESCRIPTION}`, 7 | } 8 | 9 | interface LayoutProps { 10 | children: ReactNode; 11 | } 12 | 13 | export default function Layout({ children }: LayoutProps) { 14 | return ( 15 |
16 | {children} 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/app/(agent)/agent/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import React from 'react'; 3 | import { FaUserAlt, FaStore, FaSignInAlt } from 'react-icons/fa'; // Importing icons 4 | 5 | export default function AgentPage() { 6 | return ( 7 |
8 |
9 | {/* Register Agent Section */} 10 |
11 |

12 | Register as an Agent 13 |

14 |

Join our platform and start offering your services today.

15 | 16 | 17 | 20 | 21 | 22 |
23 | 24 | {/* Register Vendor Section */} 25 |
26 |

27 | Register a Vendor 28 |

29 |

Start selling your products on our platform today.

30 | 31 | 32 | 35 | 36 | 37 |
38 | 39 | {/* Login Section */} 40 |
41 |

42 | Agent Login 43 |

44 |

Access your dashboard and manage your services efficiently.

45 | 46 | 47 | 50 | 51 | 52 |
53 |
54 |
55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/app/(agent)/agent/register/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Metadata } from 'next' 3 | 4 | 5 | export const metadata: Metadata = { 6 | title: `${process.env.APP_NAME} - Open Account`, 7 | description: `${process.env.APP_DESCRIPTION}`, 8 | } 9 | 10 | export default function layout({ children }) { 11 | return ( 12 |
13 | 14 | {children} 15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/app/(auth)/forgot/layout.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | import { Metadata } from 'next' 3 | 4 | export const metadata: Metadata = { 5 | title: `${process.env.APP_NAME} - Login`, 6 | description: `${process.env.APP_DESCRIPTION}`, 7 | } 8 | 9 | interface LayoutProps { 10 | children: ReactNode; 11 | } 12 | 13 | export default function Layout({ children }: LayoutProps) { 14 | return ( 15 |
16 | {children} 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/app/(auth)/login/layout.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | import { Metadata } from 'next' 3 | 4 | export const metadata: Metadata = { 5 | title: `${process.env.APP_NAME} - Login`, 6 | description: `${process.env.APP_DESCRIPTION}`, 7 | } 8 | 9 | interface LayoutProps { 10 | children: ReactNode; 11 | } 12 | 13 | export default function Layout({ children }: LayoutProps) { 14 | return ( 15 |
16 | {children} 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/app/(auth)/register/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Metadata } from 'next' 3 | 4 | 5 | export const metadata: Metadata = { 6 | title: `${process.env.APP_NAME} - Open Account`, 7 | description: `${process.env.APP_DESCRIPTION}`, 8 | } 9 | 10 | export default function layout({ children }) { 11 | return ( 12 |
13 | 14 | {children} 15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/app/(auth)/register/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useActionState, useEffect } from "react"; 4 | import register from "@/actions/auth/register"; 5 | import { Button } from "@/components/ui/button"; 6 | import Link from "next/link"; 7 | import { Input } from "@/components/ui/input"; 8 | import { AiOutlineArrowLeft } from "react-icons/ai"; 9 | import SelectedState from "@/components/selectedState"; 10 | import MultiSelect from "@/components/MultiSelectCombobox"; 11 | 12 | export default function Register() { 13 | const initialState = { 14 | errors: undefined, 15 | isSubmitting: false, 16 | isValid: false, 17 | successMessage: undefined, 18 | }; 19 | 20 | const [prev, action, isPending] = useActionState(register, initialState); 21 | 22 | useEffect(() => { 23 | if (prev?.successMessage) { 24 | setTimeout(() => { 25 | window.location.href = "/dashboard"; 26 | }, 3000); 27 | } 28 | }, [prev?.successMessage]); 29 | 30 | return ( 31 |
32 |
33 |
34 | 35 | 36 | 37 |

Join as a Handyman

38 |
39 |

40 | Sign up as a skilled professional to connect with clients and offer handyman services. 41 |

42 | {prev?.successMessage && ( 43 |
44 | {prev.successMessage} 45 |
46 | )} 47 | 48 |
49 | 50 | {prev?.errors?.fname && {prev.errors.fname}} 51 | 52 | 53 | {prev?.errors?.lname && {prev.errors.lname}} 54 | 55 | 56 | {prev?.errors?.serviceType && {prev.errors.serviceType}} 57 | 58 | 59 | {prev?.errors?.phone && {prev.errors.phone}} 60 | 61 | 62 | {prev?.errors?.email && {prev.errors.email}} 63 | 64 | 65 | {prev?.errors?.password && {prev.errors.password}} 66 | 67 | 68 | 69 | 70 | 73 | 74 |
75 |
76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /src/app/(auth)/updateReset/layout.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | import { Metadata } from 'next' 3 | 4 | export const metadata: Metadata = { 5 | title: `${process.env.APP_NAME} - Login`, 6 | description: `${process.env.APP_DESCRIPTION}`, 7 | } 8 | 9 | interface LayoutProps { 10 | children: ReactNode; 11 | } 12 | 13 | export default function Layout({ children }: LayoutProps) { 14 | return ( 15 |
16 | {children} 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/app/(users)/dashboard/addproduct/page.tsx: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import React from 'react'; 3 | import Topbar from '@/components/topbar'; 4 | import ProductForm from '@/components/ProductForm'; 5 | import addProduct from '@/actions/auth/productsAdd'; 6 | import { createClient } from "@/utils/supabase/server"; 7 | 8 | export default async function Product() { 9 | const supabase = await createClient(); 10 | 11 | // Get authenticated user 12 | const { data: { user }, error: userError } = await supabase.auth.getUser(); 13 | if (userError || !user) { 14 | console.error("Error fetching user:", userError); 15 | return
Error fetching user data.
; 16 | } 17 | 18 | // Fetch total product uploads 19 | const { count: totalUploads = 0, error: totalUploadsError } = await supabase 20 | .from("products") 21 | .select("*", { count: "exact" }) 22 | .eq("id", user.id); 23 | 24 | if (totalUploadsError) { 25 | console.error("Error fetching total uploads:", totalUploadsError); 26 | return
Error fetching total uploads.
; 27 | } 28 | 29 | // Fetch user profile 30 | const { data: profile, error: profileError } = await supabase 31 | .from("user_profile") 32 | .select("*") 33 | .eq("id", user.id) 34 | .single(); 35 | 36 | if (profileError || !profile || !profile.plan) { 37 | console.error("Error fetching profile or plan:", profileError, profile); 38 | return
Error fetching profile data or plan is missing.
; 39 | } 40 | 41 | 42 | const maxUploads = profile.plan; // Fallback to 0 if plan is invalid 43 | const canUpload = totalUploads < maxUploads; 44 | 45 | console.log({ 46 | userId: user.id, 47 | plan: profile.plan, 48 | totalUploads, 49 | maxUploads, 50 | canUpload 51 | }); 52 | 53 | return ( 54 |
55 |
56 | 57 | 58 | {/* Debugging logs */} 59 |
60 |           Total Uploads: {totalUploads}| Max Uploads: {maxUploads === Infinity ? "Unlimited" : maxUploads}
61 |         
62 | 63 | {maxUploads === Infinity || canUpload ? ( 64 |
65 | {maxUploads === Infinity 66 | ? "You have unlimited product uploads." 67 | : `You can upload ${maxUploads - totalUploads} more products.`} 68 |
69 | ) : ( 70 |
71 | You have reached the maximum number of product uploads. Please upgrade your plan to add more products. 72 |
73 | )} 74 | 75 | 76 |
77 |
78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /src/app/(users)/dashboard/edit-profile/page.jsx: -------------------------------------------------------------------------------- 1 | import Userdashboard from "@/components/userdashboard"; 2 | import Topbar from "@/components/topbar"; 3 | import profileUpdate from "@/actions/auth/profileUpdate"; 4 | import EditProfile from "@/components/editProfile"; 5 | import { createClient } from "@/utils/supabase/server"; 6 | import { redirect } from "next/navigation"; 7 | import { Edit, FastForwardIcon } from "lucide-react"; 8 | import { FaUserAltSlash } from "react-icons/fa"; 9 | 10 | export default async function Profile() { 11 | // Create Supabase client 12 | const supabase = await createClient(); 13 | 14 | // Authenticate the user 15 | const { 16 | data: { user }, 17 | } = await supabase.auth.getUser(); 18 | 19 | if (!user) { 20 | redirect("/login"); 21 | return null; // Early return to avoid further execution 22 | } 23 | 24 | // Fetch the user's profile 25 | const { data: profile, error } = await supabase 26 | .from("user_profile") 27 | .select("*") 28 | .eq("id", user.id) 29 | .single(); 30 | 31 | if (error) { 32 | console.error("Error fetching profile:", error); 33 | return
Error loading profile. Please try again later.
; 34 | } 35 | 36 | // Convert profile to JSON 37 | const profileJson = JSON.parse(JSON.stringify(profile)); 38 | 39 | return ( 40 |
41 | {/* Main Content Area */} 42 |
43 | {/* Dashboard & Topbar */} 44 |
45 | 46 | Profile Page 47 |
48 | 49 |
50 | {/* Profile Form */} 51 | 52 |
53 |
54 |
55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/app/(users)/dashboard/kyc-success/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import { useRouter } from "next/navigation"; 5 | 6 | export default function KYCSuccess() { 7 | const router = useRouter(); 8 | 9 | return ( 10 |
11 |
12 |

KYC Submission Successful!

13 |

14 | Your KYC information has been submitted successfully. We will review your details and get back to you within 24-48 hours. 15 |

16 | 22 |
23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/app/(users)/dashboard/kyc/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation"; 2 | import { createClient } from "@/utils/supabase/server"; 3 | import UserDashboard from "@/components/userdashboard"; 4 | import KycStatus from "@/components/kyc"; 5 | import handleKYCSubmission from "@/actions/auth/kyc"; 6 | import TopBar from "@/components/topbar"; 7 | export default async function KycPage() { 8 | 9 | const supabase = await createClient(); 10 | const { data: { user } } = await supabase.auth.getUser(); 11 | 12 | if (!user) { 13 | redirect("/"); 14 | } 15 | 16 | const { data: profile, error } = await supabase 17 | .from("user_profile") 18 | .select("*") 19 | .eq("user_id", user.id) 20 | .single(); 21 | 22 | if (error) { 23 | console.error("Error fetching profile:", error); 24 | return null; 25 | } 26 | 27 | 28 | 29 | return ( 30 |
31 | 32 | 33 | 34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/app/(users)/dashboard/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createClient } from "@/utils/supabase/server"; 3 | import { redirect } from "next/navigation"; 4 | 5 | import ClientLayout from "@/components/ClientLayout"; 6 | import TopBar from "@/components/topbar"; 7 | import { UserProvider } from "@/context/userContext"; 8 | 9 | export default async function Layout({ children }: { children: React.ReactNode }) { 10 | 11 | return ( 12 | 13 | 14 | {children} 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/app/(users)/dashboard/page.jsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { redirect } from "next/navigation"; 3 | 4 | import Dashboard from "@/components/dashboard"; 5 | import { Alert } from "@/components/ui/alert"; // ShadCN Alert 6 | import { User } from "lucide-react"; 7 | import UserDashboard from "@/components/userdashboard"; 8 | import { useUser } from "@/context/userContext"; 9 | import Link from "next/link"; 10 | 11 | export default function DashboardPage() { 12 | 13 | const profile = useUser(); 14 | 15 | return ( 16 |
17 | {/* Show error message if KYC status is "Pending" */} 18 | {profile?.kyc_status === "Pending" && ( 19 | 20 | Your KYC verification is pending. Please complete the verification to access all features. 21 | 22 | )} 23 | 24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/app/(users)/dashboard/productlist/page.jsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState, useEffect } from "react"; 4 | import Userdashboard from "@/components/userdashboard"; 5 | import Topbar from "@/components/topbar"; 6 | import Link from "next/link"; 7 | 8 | import Table from "@/components/tableList"; 9 | 10 | export default function ProductList() { 11 | 12 | return ( 13 |
14 | 15 | 16 | {/* Main Content Area */} 17 |
18 | {/* Topbar */} 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/app/(users)/dashboard/profile/page.jsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import Userdashboard from "@/components/userdashboard"; 3 | import Topbar from "@/components/topbar"; 4 | import profileUpdate from "@/actions/auth/profileUpdate"; 5 | import ProfileForm from "@/components/ProfileForm"; 6 | import { redirect } from "next/navigation"; 7 | import { FastForwardIcon } from "lucide-react"; 8 | import { FaUserAltSlash } from "react-icons/fa"; 9 | import { useUser } from "@/context/userContext"; 10 | 11 | export default function Profile() { 12 | 13 | const profileJson = useUser(); 14 | 15 | return ( 16 |
17 | {/* Main Content Area */} 18 |
19 | {/* Dashboard & Topbar */} 20 |
21 | 22 | Profile Page 23 |
24 | 25 |
26 | {/* Profile Form */} 27 | 28 |
29 |
30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/app/(users)/dashboard/success/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useEffect, useState } from "react"; 4 | import { CheckCircleIcon } from "lucide-react"; 5 | import { Button } from "@/components/ui/button"; 6 | import { createClient } from "@/utils/supabase/client"; 7 | import Link from "next/link"; 8 | 9 | export default function PaymentSuccess() { 10 | const [loading, setLoading] = useState(true); 11 | const [error, setError] = useState(null); 12 | const [profile, setProfile] = useState(null); 13 | 14 | useEffect(() => { 15 | const fetchUserData = async () => { 16 | const supabase = createClient(); 17 | const { data: { user }, error: userError } = await supabase.auth.getUser(); 18 | 19 | if (userError || !user) { 20 | setError("Failed to fetch user."); 21 | setLoading(false); 22 | return; 23 | } 24 | 25 | const { data: profileData, error: profileError } = await supabase 26 | .from("user_profile") 27 | .select("*") 28 | .eq("id", user.id) 29 | .single(); 30 | 31 | if (profileError) { 32 | setError("Failed to fetch profile."); 33 | setLoading(false); 34 | return; 35 | } 36 | 37 | const upgradedPlan = profileData?.plan * 2; 38 | 39 | const { error: updateError } = await supabase 40 | .from("user_profile") 41 | .update({ subscription_plan: "Standard", plan: upgradedPlan }) 42 | .eq("id", user.id); 43 | 44 | if (updateError) { 45 | setError("Failed to update plan."); 46 | } else { 47 | setProfile({ ...profileData, plan: upgradedPlan }); 48 | } 49 | 50 | setLoading(false); 51 | }; 52 | 53 | fetchUserData(); 54 | }, []); 55 | 56 | if (loading) { 57 | return
Loading...
; 58 | } 59 | 60 | if (error) { 61 | return
{error}
; 62 | } 63 | 64 | return ( 65 |
66 |
67 | 68 |

Payment Successful!

69 |

70 | Your upgrade has been successfully processed. You now have access to all premium features. 71 |

72 |
73 | 74 | 77 | 78 |
79 |
80 |
81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /src/app/api/payment.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export default async function handler(req, res) { 4 | // Only allow POST requests 5 | if (req.method !== 'POST') { 6 | return res.status(405).json({ error: 'Method not allowed' }); 7 | } 8 | 9 | try { 10 | // Generate a unique transaction reference 11 | const txRef = `T-${Date.now()}`; 12 | 13 | // Prepare the payment payload 14 | const paymentData = { 15 | tx_ref: txRef, // Unique reference per transaction 16 | amount: 7500, // Numeric amount for the payment 17 | currency: 'NGN', 18 | redirect_url: 'https://example_company.com/success', // Redirect after payment 19 | customer: { 20 | email: 'developers@flutterwavego.com', // Replace with customer email 21 | name: 'Flutterwave Developers', // Replace with customer name 22 | phonenumber: '09012345678', // Replace with customer phone number 23 | }, 24 | customizations: { 25 | title: 'Flutterwave Standard Payment', 26 | }, 27 | }; 28 | 29 | // Set the request headers 30 | const headers = { 31 | Authorization: `Bearer ${process.env.FLW_SECRET_KEY}`, // Ensure your secret key is set in env 32 | 'Content-Type': 'application/json', 33 | }; 34 | 35 | // Send the POST request to Flutterwave API 36 | const response = await axios.post( 37 | 'https://api.flutterwave.com/v3/payments', 38 | paymentData, 39 | { headers } 40 | ); 41 | 42 | // Check if the request was successful 43 | if (response.status === 200 && response.data.status === 'success') { 44 | return res.status(200).json({ status: 'success', data: response.data }); 45 | } else { 46 | return res.status(400).json({ 47 | status: 'failure', 48 | message: response.data.message || 'Payment failed', 49 | }); 50 | } 51 | } catch (error) { 52 | // Log error details for debugging 53 | console.error('Error with payment API:', error.response?.data || error.message); 54 | return res.status(500).json({ error: 'Payment request failed' }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/app/auth/confirm/route.ts: -------------------------------------------------------------------------------- 1 | import { type EmailOtpType } from '@supabase/supabase-js' 2 | import { type NextRequest, NextResponse } from 'next/server' 3 | 4 | import { createClient } from '@/utils/supabase/server' 5 | 6 | // Creating a handler to a GET request to route /auth/confirm 7 | export async function GET(request: NextRequest) { 8 | const { searchParams } = new URL(request.url) 9 | const token_hash = searchParams.get('token_hash') 10 | const type = searchParams.get('type') as EmailOtpType | null 11 | const next = '/account' 12 | 13 | // Create redirect link without the secret token 14 | const redirectTo = request.nextUrl.clone() 15 | redirectTo.pathname = next 16 | redirectTo.searchParams.delete('token_hash') 17 | redirectTo.searchParams.delete('type') 18 | 19 | if (token_hash && type) { 20 | const supabase = await createClient() 21 | 22 | const { error } = await supabase.auth.verifyOtp({ 23 | type, 24 | token_hash, 25 | }) 26 | if (!error) { 27 | redirectTo.searchParams.delete('next') 28 | return NextResponse.redirect(redirectTo) 29 | } 30 | } 31 | 32 | // return the user to an error page with some instructions 33 | redirectTo.pathname = '/error' 34 | return NextResponse.redirect(redirectTo) 35 | } -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pchukwuemeka424/backup/c2271a6f46ffad5814017a49553d7be1365aacf2/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/filter/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Topnav from '@/components/topnav'; 3 | import { Metadata } from 'next'; 4 | 5 | export const metadata: Metadata = { 6 | title: `${process.env.APP_NAME}`, 7 | description: `${process.env.APP_DESCRIPTION}`, 8 | } 9 | export default function Layout({ children }) { 10 | return ( 11 |
12 | 13 |
{children}
14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/app/filter/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | import CategoryList from '@/components/category'; 5 | import SearchProduct from './filterFetch'; 6 | 7 | export default function Page() { 8 | 9 | return ( 10 |
11 | 12 |
13 | 14 | 15 | {/* Product list */} 16 | 17 |
18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 0 0% 3.9%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 0 0% 3.9%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 0 0% 3.9%; 13 | --primary: 0 0% 9%; 14 | --primary-foreground: 0 0% 98%; 15 | --secondary: 0 0% 96.1%; 16 | --secondary-foreground: 0 0% 9%; 17 | --muted: 0 0% 96.1%; 18 | --muted-foreground: 0 0% 45.1%; 19 | --accent: 0 0% 96.1%; 20 | --accent-foreground: 0 0% 9%; 21 | --destructive: 0 84.2% 60.2%; 22 | --destructive-foreground: 0 0% 98%; 23 | --border: 0 0% 89.8%; 24 | --input: 0 0% 89.8%; 25 | --ring: 0 0% 3.9%; 26 | --chart-1: 12 76% 61%; 27 | --chart-2: 173 58% 39%; 28 | --chart-3: 197 37% 24%; 29 | --chart-4: 43 74% 66%; 30 | --chart-5: 27 87% 67%; 31 | --radius: 0.5rem; 32 | --sidebar-background: 0 0% 98%; 33 | --sidebar-foreground: 240 5.3% 26.1%; 34 | --sidebar-primary: 240 5.9% 10%; 35 | --sidebar-primary-foreground: 0 0% 98%; 36 | --sidebar-accent: 240 4.8% 95.9%; 37 | --sidebar-accent-foreground: 240 5.9% 10%; 38 | --sidebar-border: 220 13% 91%; 39 | --sidebar-ring: 217.2 91.2% 59.8%; 40 | } 41 | .dark { 42 | --background: 0 0% 3.9%; 43 | --foreground: 0 0% 98%; 44 | --card: 0 0% 3.9%; 45 | --card-foreground: 0 0% 98%; 46 | --popover: 0 0% 3.9%; 47 | --popover-foreground: 0 0% 98%; 48 | --primary: 0 0% 98%; 49 | --primary-foreground: 0 0% 9%; 50 | --secondary: 0 0% 14.9%; 51 | --secondary-foreground: 0 0% 98%; 52 | --muted: 0 0% 14.9%; 53 | --muted-foreground: 0 0% 63.9%; 54 | --accent: 0 0% 14.9%; 55 | --accent-foreground: 0 0% 98%; 56 | --destructive: 0 62.8% 30.6%; 57 | --destructive-foreground: 0 0% 98%; 58 | --border: 0 0% 14.9%; 59 | --input: 0 0% 14.9%; 60 | --ring: 0 0% 83.1%; 61 | --chart-1: 220 70% 50%; 62 | --chart-2: 160 60% 45%; 63 | --chart-3: 30 80% 55%; 64 | --chart-4: 280 65% 60%; 65 | --chart-5: 340 75% 55%; 66 | --sidebar-background: 240 5.9% 10%; 67 | --sidebar-foreground: 240 4.8% 95.9%; 68 | --sidebar-primary: 224.3 76.3% 48%; 69 | --sidebar-primary-foreground: 0 0% 100%; 70 | --sidebar-accent: 240 3.7% 15.9%; 71 | --sidebar-accent-foreground: 240 4.8% 95.9%; 72 | --sidebar-border: 240 3.7% 15.9%; 73 | --sidebar-ring: 217.2 91.2% 59.8%; 74 | } 75 | } 76 | 77 | @layer base { 78 | * { 79 | @apply border-border; 80 | } 81 | body { 82 | @apply bg-background text-foreground; 83 | } 84 | } 85 | 86 | 87 | 88 | @layer base { 89 | * { 90 | @apply border-border outline-ring/50; 91 | } 92 | body { 93 | @apply bg-background text-foreground; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "./globals.css"; 3 | import TawkToScript from "@/components/TawkToScript"; // Import the Tawk.to script component 4 | 5 | export const metadata: Metadata = { 6 | title: `${process.env.APP_NAME}`, 7 | description: `${process.env.APP_DESCRIPTION}`, 8 | keywords: "Afrivendor, Nigeria vendor, afrivendor.ng, online marketplace, Nigeria businesses,wigs hair online vendors, Nigeria e-commerce, vendor platform, buy from Africa, shop Nigeria products, Nigeria goods, local Nigeria businesses, online vendors", 9 | author: "Acehub Technologies", 10 | viewport: "width=device-width, initial-scale=1.0", 11 | robots: "index, follow", 12 | openGraph: { 13 | title: `${process.env.APP_NAME}`, 14 | description: `${process.env.APP_DESCRIPTION}`, 15 | url: process.env.APP_URL || "default-url.com", 16 | images: [{ url: "https://sxkmrpzbtqpraucnmnjm.supabase.co/storage/v1/object/public/logos/White-and-Blue-Shopping-Cart-Logo-DesignEvo-Logo-Maker-02-14-2025_08_47_PM.png" }], 17 | }, 18 | twitter: { 19 | card: "Afrivendor", 20 | title: `${process.env.APP_NAME}`, 21 | description: `${process.env.APP_DESCRIPTION}`, 22 | images: ["https://sxkmrpzbtqpraucnmnjm.supabase.co/storage/v1/object/public/logos/White-and-Blue-Shopping-Cart-Logo-DesignEvo-Logo-Maker-02-14-2025_08_47_PM.png"], 23 | }, 24 | }; 25 | 26 | export default function RootLayout({ 27 | children, 28 | }: Readonly<{ 29 | children: React.ReactNode; 30 | }>) { 31 | return ( 32 | 33 | 34 | {/* Other meta tags can go here */} 35 | 36 | 37 | {children} 38 | {/* Load the Tawk.to script dynamically */} 39 | 40 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/app/loading.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import Spinner from '@/components/spinner' 3 | import React from 'react' 4 | 5 | export default function adam() { 6 | return ( 7 | 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function notFound() { 4 | return ( 5 |
not-found
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/app/page.jsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import Navbar from "../components/navbar"; 3 | import Hero from "../components/hero"; 4 | import CatSlider from "../components/catSlider"; 5 | 6 | import Midtab from "../components/midTab"; 7 | 8 | 9 | 10 | 11 | export default function Home() { 12 | return ( 13 |
14 | 15 | 16 | 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/app/products/[product]/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Topnav from '@/components/topnav'; 3 | import { Metadata } from 'next'; 4 | 5 | export const metadata: Metadata = { 6 | title: `${process.env.APP_NAME}`, 7 | description: `${process.env.APP_DESCRIPTION}`, 8 | } 9 | 10 | export default function Layout({ children }: { children: React.ReactNode }) { 11 | return ( 12 |
13 | 14 |
{children}
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/app/products/[product]/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | import CategoryList from '@/components/category'; 5 | import FetchCat from './fetchCat'; 6 | import Slider from '@/components/slider'; 7 | 8 | export default function Page() { 9 | return ( 10 |
11 | {/* Full-width Slider */} 12 | 13 | 14 | {/* Responsive Grid */} 15 |
16 | {/* Sidebar for product categories */} 17 |
18 | 19 |
20 | 21 | {/* Product list */} 22 |
23 | 24 | 25 |
26 |
27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/app/search/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Topnav from '@/components/topnav'; 3 | import { Metadata } from 'next'; 4 | 5 | export const metadata: Metadata = { 6 | title: `${process.env.APP_NAME}`, 7 | description: `${process.env.APP_DESCRIPTION}`, 8 | } 9 | export default function Layout({ children }) { 10 | return ( 11 |
12 | 13 |
{children}
14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/app/search/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | import CategoryList from '@/components/category'; 5 | import SearchProduct from './searchFetch'; 6 | 7 | export default function Page() { 8 | 9 | return ( 10 |
11 | 12 |
13 | {/* Sidebar for product categories */} 14 | 15 | 16 | 17 | {/* Product list */} 18 | 19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/app/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://afrivendor.ng/ 5 | 2025-02-15T06:33:54+00:00 6 | 1.00 7 | 8 | 9 | https://afrivendor.ng/register 10 | 2025-02-15T06:33:54+00:00 11 | 0.80 12 | 13 | 14 | https://afrivendor.ng/login 15 | 2025-02-15T06:33:54+00:00 16 | 0.80 17 | 18 | 19 | https://afrivendor.ng/vendor 20 | 2025-02-15T06:33:54+00:00 21 | 0.80 22 | 23 | 24 | https://afrivendor.ng/product 25 | 2025-02-15T06:33:54+00:00 26 | 0.80 27 | 28 | 29 | https://afrivendor.ng/forgot 30 | 2025-02-15T06:33:54+00:00 31 | 0.64 32 | 33 | -------------------------------------------------------------------------------- /src/app/sitemap/page.tsx: -------------------------------------------------------------------------------- 1 | // /pages/sitemap/page.tsx 2 | import { NextPage } from 'next'; 3 | 4 | const Sitemap: NextPage = () => { 5 | const pages = [ 6 | '/', 7 | '/contact', 8 | '/login', 9 | '/register', 10 | '/vendor', 11 | '/product', 12 | '/store', 13 | ]; 14 | 15 | const generateSitemap = (pages: string[]) => { 16 | return pages 17 | .map((page) => { 18 | return ` 19 | 20 | ${`https://afrivendor.ng${page}`} 21 | ${new Date().toISOString()} 22 | daily 23 | 0.7 24 | 25 | `; 26 | }) 27 | .join(''); 28 | }; 29 | 30 | return ( 31 | <> 32 | {` 33 | 34 | ${generateSitemap(pages)} 35 | `} 36 | 37 | ); 38 | }; 39 | 40 | export default Sitemap; 41 | -------------------------------------------------------------------------------- /src/app/store/[store]/[contact]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useEffect, useState } from 'react'; 3 | import { createClient } from '@/utils/supabase/client'; 4 | import { useParams } from 'next/navigation'; 5 | import Header from '@/components/headerUser'; 6 | import ContactInfo from '@/components/contactInfo'; 7 | 8 | export default function Page() { 9 | const [shopDetails, setShopDetails] = useState(null); 10 | const [error, setError] = useState(null); 11 | const supabase = createClient(); 12 | const { contact } = useParams(); 13 | 14 | useEffect(() => { 15 | async function fetchShopDetails() { 16 | const { data, error } = await supabase 17 | .from('user_profile') 18 | .select("*") 19 | .eq('username', contact) 20 | .single(); 21 | 22 | if (error) { 23 | console.error('Error fetching shop details:', error); 24 | setError(error); 25 | } else { 26 | setShopDetails(data); 27 | } 28 | } 29 | 30 | if (contact) { 31 | fetchShopDetails(); 32 | } 33 | }, [contact, supabase]); 34 | 35 | if (error) { 36 | return
Error loading shop details.
; 37 | } 38 | 39 | return ( 40 |
41 | {shopDetails && ( 42 |
43 |
44 | 45 |
46 | )} 47 |
48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /src/app/store/[store]/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | 4 | export default function layout({ children }) { 5 | return ( 6 | 7 |
8 |
{children}
9 |
10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/app/store/[store]/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Shop from './shop'; 3 | import { Metadata } from 'next'; 4 | import { createClient } from '@/utils/supabase/client'; 5 | 6 | export async function generateMetadata({ params }: { params: { store: string } }): Promise { 7 | const supabase = createClient(); 8 | 9 | const { data, error } = await supabase 10 | .from('user_profile') 11 | .select('*') 12 | .eq('username', params.store) 13 | .single(); 14 | 15 | const user = data ?? {}; // Ensure user is always an object 16 | 17 | return { 18 | title: `${process.env.APP_NAME} - ${user.shopname || 'Store'}`, 19 | description: process.env.APP_DESCRIPTION || '', 20 | openGraph: { 21 | title: `${process.env.APP_NAME} - ${user.shopname || params.store}`, 22 | description: process.env.APP_DESCRIPTION || '', 23 | images: [ 24 | { 25 | url: `${user.avater}`, 26 | width: 800, 27 | height: 600, 28 | alt: 'Og Image Alt', 29 | }, 30 | ], 31 | }, 32 | }; 33 | } 34 | 35 | export default function Page() { 36 | return ( 37 |
38 | 39 |
40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/app/trades/[id]/loading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function loading() { 4 | return ( 5 |
6 | {/* Skeleton for Product Image */} 7 |
8 |
9 |
10 | 11 | {/* Skeleton for Product Details */} 12 |
13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 |
22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /src/app/trades/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import FetchSingleProduct from './fetchSingle'; 4 | import { Metadata } from 'next'; 5 | import { createClient } from '@/utils/supabase/client'; 6 | 7 | export default function Page() { 8 | 9 | 10 | return ( 11 |
12 | 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/app/trades/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Topnav from '@/components/topnav'; 3 | 4 | import { Metadata } from 'next'; 5 | 6 | export const metadata: Metadata = { 7 | title: process.env.APP_NAME || 'Default App Name', 8 | description: process.env.APP_DESCRIPTION || 'Default App Description', 9 | }; 10 | 11 | export default function Layout({ children }: { children: React.ReactNode }) { 12 | return ( 13 | <> 14 | 15 |
16 | 17 |
{children}
18 |
19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/app/trades/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | import CategoryList from '@/components/category'; 5 | import ProductFetch from './productFetch'; 6 | import Slider from '@/components/slider'; 7 | 8 | export default function Page() { 9 | return ( 10 |
11 | {/* Full-width Slider */} 12 | 13 | 14 | {/* Responsive Grid */} 15 |
16 | {/* Sidebar for product categories */} 17 |
18 | 19 |
20 | 21 | {/* Product list */} 22 |
23 |
24 | 25 |
26 | 27 |
28 |
29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/app/types/page.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pchukwuemeka424/backup/c2271a6f46ffad5814017a49553d7be1365aacf2/src/app/types/page.d.ts -------------------------------------------------------------------------------- /src/app/unauthorized/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from 'react'; 3 | import { Lock } from 'lucide-react'; // React Icon for a lock 4 | 5 | export default function Unauthorized() { 6 | return ( 7 |
8 |
9 |
10 | 11 |
12 |

Access Denied

13 |

14 | You do not have permission to view this page. Please contact your administrator or go back to the previous page. 15 |

16 | 22 |
23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/components/ClientNavbar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; // Add this at the very top 2 | 3 | import React from 'react'; 4 | import Link from 'next/link'; 5 | import { FaSignInAlt, FaUserPlus } from 'react-icons/fa'; 6 | import { createClient } from '@/utils/supabase-db'; 7 | import { useRouter } from 'next/navigation'; // For client-side navigation 8 | 9 | // Define the type for authUser 10 | type ClientNavbarProps = { 11 | authUser: { id: string | null } | null; // Ensure that authUser can be null 12 | }; 13 | 14 | const ClientNavbar: React.FC = ({ authUser }) => { 15 | const router = useRouter(); // Hook to navigate after logout 16 | 17 | const handleLogout = async () => { 18 | const supabase = createClient(); 19 | const { error } = await supabase.auth.signOut(); 20 | if (error) { 21 | console.error('Error logging out:', error.message); 22 | } else { 23 | router.push('/'); // Using next/router for navigation 24 | } 25 | }; 26 | 27 | return ( 28 |
29 | {authUser ? ( 30 |
31 | Dashboard 32 | Stores 33 | Products 34 | 37 |
38 | ) : ( 39 |
40 | 41 | 44 | 45 | 46 | 49 | 50 | 51 | 54 | 55 |
56 | )} 57 |
58 | ); 59 | }; 60 | 61 | export default ClientNavbar; 62 | -------------------------------------------------------------------------------- /src/components/MarketAutocomplete.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useEffect } from "react"; 4 | import { createClient } from "@/utils/supabase/client"; 5 | 6 | export default function MarketAutocomplete({ value, onChange }) { 7 | const supabase = createClient(); 8 | const [markets, setMarkets] = useState(""); 9 | const [suggestions, setSuggestions] = useState([]); 10 | 11 | useEffect(() => { 12 | const fetchMarkets = async () => { 13 | try { 14 | const { data, error } = await supabase.from("markettable").select("marketname"); 15 | if (error) { 16 | console.error("Supabase Error:", error.message); 17 | } else if (data) { 18 | setMarkets(data.map((market) => market.marketname)); 19 | } 20 | } catch (err) { 21 | console.error("Fetch Markets Error:", err); 22 | } 23 | }; 24 | 25 | fetchMarkets(); 26 | }, []); 27 | 28 | const handleInputChange = (e) => { 29 | const inputValue = e.target.value; 30 | onChange(inputValue); 31 | 32 | if (inputValue.length > 1) { 33 | setSuggestions( 34 | markets.filter((market) => 35 | market.toLowerCase().includes(inputValue.toLowerCase()) 36 | ) 37 | ); 38 | } else { 39 | setSuggestions([]); 40 | } 41 | }; 42 | 43 | const handleSelect = (market) => { 44 | onChange(market); 45 | setSuggestions([]); 46 | }; 47 | 48 | return ( 49 |
50 | 51 | 59 | {suggestions.length > 0 && ( 60 |
    61 | {suggestions.map((market, index) => ( 62 |
  • handleSelect(market)} 66 | > 67 | {market} 68 |
  • 69 | ))} 70 |
71 | )} 72 |
73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /src/components/MultiSelectCombobox.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import dynamic from "next/dynamic"; 3 | import { createClient } from "@/utils/supabase/client"; 4 | 5 | // Dynamically import React Select with SSR disabled 6 | const Select = dynamic(() => import("react-select"), { ssr: false }); 7 | 8 | function MultiSelectCombobox({profile}) { 9 | const supabase = createClient(); 10 | const [options, setOptions] = useState([]); 11 | const [selectedOptions, setSelectedOptions] = useState([]); 12 | 13 | useEffect(() => { 14 | const fetchSkills = async () => { 15 | const { data, error } = await supabase.from("skills").select("*"); 16 | if (error) { 17 | console.error("Error fetching skills:", error); 18 | return; 19 | } 20 | if (data) { 21 | setOptions(data.map((item) => ({ label: item.label, value: item.value }))); 22 | } 23 | }; 24 | 25 | fetchSkills(); 26 | }, []); 27 | 28 | // Convert selected options to text separated by commas 29 | const selectedText = selectedOptions.map((option) => option.label).join(", "); 30 | 31 | return ( 32 |
33 | 44 |
45 | eg: Plumber, Carpenter, Electrician,Teacher etc 46 |
47 |
48 | ); 49 | } 50 | 51 | export default MultiSelectCombobox; 52 | -------------------------------------------------------------------------------- /src/components/ProductNotFound.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import React from 'react'; 3 | import { FaShopLock } from 'react-icons/fa6'; 4 | 5 | const ProductNotFound = () => { 6 | return ( 7 |
8 |
9 |
10 | 11 |
12 |

Product Not Found

13 |

We couldn’t find the product you’re looking for. Try searching again or browse other categories.

14 | 15 | 16 | 17 |
18 |
19 | ); 20 | }; 21 | 22 | export default ProductNotFound; -------------------------------------------------------------------------------- /src/components/TawkToScript.tsx: -------------------------------------------------------------------------------- 1 | "use client"; // Ensure this runs only on the client side 2 | import { useEffect } from "react"; 3 | 4 | export default function TawkToScript() { 5 | useEffect(() => { 6 | // Prevent duplicate script injection 7 | if (document.getElementById("tawk-script")) return; 8 | 9 | const script = document.createElement("script"); 10 | script.id = "tawk-script"; 11 | script.async = true; 12 | script.src = "https://embed.tawk.to/67b87470c3759e190c785cd7/1ikk8dmbs"; 13 | script.charset = "UTF-8"; 14 | script.setAttribute("crossorigin", "*"); 15 | document.body.appendChild(script); 16 | }, []); 17 | 18 | return null; // This component does not render anything visible 19 | } 20 | -------------------------------------------------------------------------------- /src/components/adminDashboard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FaUsers, FaUpload, FaUserClock, FaExclamationCircle } from 'react-icons/fa'; 3 | 4 | const AdminDashboard = () => { 5 | return ( 6 |
7 |
8 | {/* Total Users */} 9 |
10 |
11 | 12 |
13 |
14 |

Total Users

15 |

1,200

16 |
17 |
18 | 19 | {/* Total Unverified Users */} 20 |
21 |
22 | 23 |
24 |
25 |

Total Unverified Users

26 |

450

27 |
28 |
29 | 30 | {/* Total Uploads */} 31 |
32 |
33 | 34 |
35 |
36 |

Total Uploads

37 |

3,700

38 |
39 |
40 | 41 | {/* More Cards */} 42 |
43 |
44 | 45 |
46 |
47 |

Critical Alerts

48 |

2

49 |
50 |
51 | 52 | {/* Add more stats/cards as needed */} 53 |
54 |
55 | ); 56 | }; 57 | 58 | export default AdminDashboard; 59 | -------------------------------------------------------------------------------- /src/components/agentUser.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { createClient } from "@/utils/supabase/client"; 3 | 4 | export default function AgentUser() { 5 | const [agents, setAgents] = useState([]); // Store fetched agents 6 | const [selectedAgentId, setSelectedAgentId] = useState(""); // Track selected agent ID 7 | 8 | useEffect(() => { 9 | const fetchAgents = async () => { 10 | const supabase = await createClient(); 11 | const { data, error } = await supabase.from("agents").select("*"); 12 | 13 | if (error) { 14 | console.error("Error fetching agents:", error); 15 | return; 16 | } 17 | 18 | setAgents(data); 19 | }; 20 | 21 | fetchAgents(); 22 | }, []); 23 | 24 | return ( 25 |
26 | 40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/components/app-sidebar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from 'react'; 3 | import { Calendar, Home, Inbox, Search, Settings } from "lucide-react"; 4 | import Link from "next/link"; 5 | import { createClient } from "@/utils/supabase/client"; 6 | 7 | export function AppSidebar() { 8 | const supabase = createClient(); 9 | 10 | // Fetch menu data using useEffect 11 | const [menu, setMenu] = React.useState([]); 12 | 13 | React.useEffect(() => { 14 | const fetchData = async () => { 15 | const { data, error } = await supabase 16 | .from('menu') 17 | .select('*'); 18 | 19 | if (error) { 20 | console.error('Error fetching menu data:', error); 21 | } else { 22 | setMenu(data); 23 | } 24 | }; 25 | 26 | fetchData(); 27 | }, []); 28 | 29 | return ( 30 |
31 | {/* Sidebar */} 32 |
33 |
34 |

Sidebar

35 | 36 |
37 | 38 |
39 | {menu.map((menuItem) => { 40 | const Icon = iconMap[menuItem.icon?.toLowerCase()]; 41 | return ( 42 |
43 | {Icon && } 44 | 45 | {menuItem.title} 46 | 47 |
48 | ); 49 | })} 50 |
51 |
52 | 53 | {/* Overlay */} 54 |
55 |
56 | ); 57 | } 58 | 59 | const iconMap = { 60 | calendar: Calendar, 61 | home: Home, 62 | inbox: Inbox, 63 | search: Search, 64 | settings: Settings, 65 | }; 66 | -------------------------------------------------------------------------------- /src/components/banM.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState, useEffect } from "react"; 3 | import { useActionState } from "react"; 4 | import bannerAction from "@/actions/auth/bans"; 5 | 6 | 7 | export default function Logo() { 8 | 9 | const [prev, action, isPending] = useActionState(bannerAction, { 10 | document: null, 11 | user_id: null, 12 | }); 13 | 14 | const [selectedFile, setSelectedFile] = useState(null); 15 | const [previewUrl, setPreviewUrl] = useState(null); 16 | const [documentError, setDocumentError] = useState(""); 17 | const [successMessage, setSuccessMessage] = useState(""); 18 | const MAX_FILE_SIZE = 3 * 1024 * 1024; // 3 MB 19 | 20 | useEffect(() => { 21 | if (prev?.success) { 22 | setSuccessMessage(prev.message); 23 | } 24 | }, [prev]); 25 | 26 | useEffect(() => { 27 | if (!selectedFile) { 28 | setPreviewUrl(null); 29 | return; 30 | } 31 | 32 | const objectUrl = URL.createObjectURL(selectedFile); 33 | setPreviewUrl(objectUrl); 34 | 35 | return () => URL.revokeObjectURL(objectUrl); 36 | }, [selectedFile]); 37 | 38 | const handleFileChange = (event: React.ChangeEvent) => { 39 | const file = event.target.files?.[0]; 40 | if (file) { 41 | if (file.size > MAX_FILE_SIZE) { 42 | setDocumentError("Image is too large. Please upload a file smaller than 3MB."); 43 | setSelectedFile(null); 44 | } else { 45 | setDocumentError(""); 46 | setSelectedFile(file); 47 | } 48 | } 49 | }; 50 | 51 | const BannerhandleSubmit = (event: React.FormEvent) => { 52 | if (!selectedFile) { 53 | event.preventDefault(); 54 | setDocumentError("Please upload a valid document before submitting."); 55 | } 56 | }; 57 | 58 | return ( 59 |
60 |
61 |
62 | 70 | {documentError &&

{documentError}

} 71 |
72 | 73 | {previewUrl && ( 74 |
75 |

Image Preview:

76 | Preview 77 |
78 | )} 79 | 80 | 87 | 88 | {/* Success message display */} 89 | {successMessage && ( 90 |

{successMessage}

91 | )} 92 |
93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /src/components/banner.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useState } from 'react'; 4 | 5 | export default function Banner() { 6 | const [visible, setVisible] = useState(true); 7 | 8 | useEffect(() => { 9 | const timer = setTimeout(() => { 10 | setVisible(false); 11 | }, 3000); 12 | 13 | return () => clearTimeout(timer); 14 | }, []); 15 | 16 | return ( 17 | visible && ( 18 |
24 |
25 |

Your text here

26 |
27 |
28 | ) 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/components/bannerModel.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState } from "react"; 4 | 5 | import { 6 | Dialog, 7 | DialogContent, 8 | DialogDescription, 9 | DialogHeader, 10 | DialogTitle, 11 | DialogTrigger, 12 | } from "@/components/ui/dialog"; 13 | import { Button } from "@/components/ui/button"; 14 | import { FaEye, FaFileAlt } from "react-icons/fa"; 15 | import { Input } from "@/components/ui/input"; 16 | import { Label } from "@/components/ui/label"; 17 | import { createClient } from "@/utils/supabase/client"; 18 | import BanM from "./banM"; 19 | import { FaF } from "react-icons/fa6"; 20 | 21 | 22 | 23 | export default function BannwerModalLogo() { 24 | 25 | 26 | return ( 27 | 28 | 29 | 30 | 34 | 35 | 36 | 37 | 38 | Chnage Banner 39 | 40 | Chnage Banner 41 | 42 | 43 | 44 | 45 | 46 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/components/catSlider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useEffect, useState } from "react"; 3 | import "slick-carousel/slick/slick.css"; 4 | import "slick-carousel/slick/slick-theme.css"; 5 | import Slider from "react-slick"; 6 | import { createClient } from "@/utils/supabase/client"; 7 | import Image from "next/image"; 8 | import Link from "next/link"; 9 | 10 | export default function SliderComponent() { 11 | const [categorys, setcategorys] = useState([]); 12 | 13 | useEffect(() => { 14 | // Fetch categories from Supabase 15 | const fetchCategories = async () => { 16 | const supabase = createClient(); 17 | const { data: categories, error } = await supabase.from("category").select("*"); 18 | if (error) console.error("Error fetching categories:", error); 19 | else setcategorys(categories); 20 | }; 21 | 22 | fetchCategories(); 23 | }, []); 24 | 25 | const settings = { 26 | infinite: true, 27 | slidesToShow: 7, 28 | slidesToScroll: 1, 29 | autoplay: true, 30 | speed: 2000, 31 | autoplaySpeed: 3000, 32 | cssEase: "linear", 33 | adaptiveHeight: true, 34 | responsive: [ 35 | { breakpoint: 1024, settings: { slidesToShow: 4 } }, 36 | { breakpoint: 768, settings: { slidesToShow: 3 } }, 37 | { breakpoint: 480, settings: { slidesToShow: 3 } }, 38 | ], 39 | }; 40 | 41 | return ( 42 |
43 |
Categories
44 | 45 | {categorys.map((category) => ( 46 |
47 | 48 |
49 | 57 |
58 | {category.title} 59 |
60 |
61 | 62 |
63 | ))} 64 |
65 |
66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /src/components/catlist.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { createClient } from "@/utils/supabase/client"; 4 | import React, { useEffect, useState } from "react"; 5 | 6 | export default function Catlist({ product }) { 7 | const supabase = createClient(); 8 | const [kycRecords, setKycRecords] = useState([]); 9 | const [loading, setLoading] = useState(true); 10 | const [error, setError] = useState(null); 11 | 12 | useEffect(() => { 13 | const fetchCategories = async () => { 14 | try { 15 | const { data, error } = await supabase.from("category").select("*"); 16 | if (error) throw error; 17 | setKycRecords(data); 18 | } catch (err) { 19 | setError(err.message); 20 | } finally { 21 | setLoading(false); 22 | } 23 | }; 24 | 25 | fetchCategories(); 26 | }, [supabase]); 27 | 28 | if (loading) return
Loading...
; 29 | if (error) return
Error: {error}
; 30 | 31 | return ( 32 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/components/clipboard.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState } from "react"; 3 | import { FaShareAlt, FaCopy } from "react-icons/fa"; 4 | import { Button } from "./ui/button"; 5 | 6 | const ShopUrlDisplay = ({ siteData, profile }) => { 7 | const [copySuccess, setCopySuccess] = useState(false); 8 | 9 | const handleCopyClick = () => { 10 | const url = `${siteData.siteUrl}store/${profile?.username}`; 11 | navigator.clipboard.writeText(url) 12 | .then(() => setCopySuccess(true)) 13 | .catch((err) => console.error('Error copying text: ', err)); 14 | }; 15 | 16 | const handleShareClick = () => { 17 | const url = `${siteData.siteUrl}store/${profile?.username}`; 18 | if (navigator.share) { 19 | navigator.share({ 20 | title: "Check out my shop!", 21 | url: url, 22 | }) 23 | .catch((err) => console.error("Error sharing:", err)); 24 | } else { 25 | // Fallback if the browser doesn't support the Web Share API 26 | window.open(`https://twitter.com/share?url=${url}`, "_blank"); 27 | } 28 | }; 29 | 30 | return ( 31 | siteData?.siteUrl && ( 32 |
33 | Shop URL: 34 | 35 | {siteData.siteUrl}store/{profile?.username} 36 | 37 | 38 |
39 | 43 | 47 |
48 |
49 | ) 50 | ); 51 | }; 52 | 53 | export default ShopUrlDisplay; 54 | -------------------------------------------------------------------------------- /src/components/currency.ts: -------------------------------------------------------------------------------- 1 | export const formatCurrency = (value, currency = 'NGN') => { 2 | return new Intl.NumberFormat('en-NG', { 3 | style: 'currency', 4 | currency: currency, 5 | minimumFractionDigits: 0, 6 | }).format(value); 7 | }; 8 | -------------------------------------------------------------------------------- /src/components/deleteButton.tsx: -------------------------------------------------------------------------------- 1 | import { createClient } from "@/utils/supabase/client"; // Adjust import based on your project structure 2 | 3 | // Initialize the Supabase client 4 | const supabase = createClient(); 5 | 6 | const DeleteButton = ({ productId, imagePath }: { productId: string; imagePath: string }) => { 7 | const handleDelete = async () => { 8 | try { 9 | // Delete the product from the "products" table by productId 10 | const { error: deleteError } = await supabase 11 | .from("products") 12 | .delete() 13 | .eq("user_id", productId); 14 | if (deleteError) { 15 | console.error("Error deleting product:", deleteError); 16 | alert("Failed to delete the product from the database."); 17 | return; 18 | } 19 | 20 | // Delete the image from the Supabase bucket 21 | const { error: storageError } = await supabase 22 | .storage 23 | .from("products_image") 24 | .remove([imagePath.replace(/.*\/public\//, "")]); 25 | if (storageError) { 26 | console.error("Error deleting image from bucket:", storageError); 27 | alert("Failed to delete the image from the storage bucket."); 28 | return; 29 | } 30 | 31 | console.log("Product and associated image deleted successfully!"); 32 | alert("Product and associated image deleted successfully!"); 33 | // Refresh page automatically 34 | window.location.reload(); 35 | 36 | } catch (err) { 37 | console.error("Error during deletion process:", err); 38 | alert("An unexpected error occurred while deleting."); 39 | } 40 | }; 41 | 42 | return ( 43 | 46 | ); 47 | }; 48 | 49 | export default DeleteButton; 50 | -------------------------------------------------------------------------------- /src/components/expanded_products.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "productName": "Leather Women Bag", 4 | "description": "Elegant leather bag with multiple compartments, perfect for work or casual outings." 5 | }, 6 | { 7 | "productName": "Men's Messenger Bag", 8 | "description": "Durable and stylish messenger bag ideal for laptops and daily essentials." 9 | }, 10 | { 11 | "productName": "Luxury Face Cream", 12 | "description": "Nourishing cream that hydrates and rejuvenates your skin for a youthful glow." 13 | }, 14 | { 15 | "productName": "Women's Summer Dress", 16 | "description": "Lightweight, floral dress perfect for warm weather and casual outings." 17 | }, 18 | { 19 | "productName": "iPhone 15 Pro Max", 20 | "description": "Experience unparalleled performance with cutting-edge technology and stunning design." 21 | }, 22 | { 23 | "productName": "Men's Classic Wallet", 24 | "description": "Compact leather wallet with ample space for cards, cash, and coins." 25 | }, 26 | { 27 | "productName": "Laptop Backpack", 28 | "description": "Comfortable and secure backpack with padded compartments for your laptop and accessories." 29 | }, 30 | { 31 | "productName": "Women's Tote Bag", 32 | "description": "Stylish and spacious tote bag for shopping or casual use." 33 | }, 34 | { 35 | "productName": "Synthetic Lace Front Wig", 36 | "description": "Natural-looking, heat-resistant wig with adjustable straps for a perfect fit." 37 | }, 38 | { 39 | "productName": "Gaming Laptop", 40 | "description": "High-performance laptop with a powerful GPU for gaming and multitasking." 41 | } 42 | 43 | ] -------------------------------------------------------------------------------- /src/components/headerUser.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState } from "react"; 3 | import { FaHome } from "react-icons/fa"; 4 | import Image from "next/image"; 5 | import Link from "next/link"; 6 | import { Button } from "./ui/button"; 7 | 8 | export default function Header({ shopDetails, totalProducts }) { 9 | return ( 10 |
11 |
12 | {/* Logo and Name */} 13 |
14 | 15 |

{shopDetails.shopname}

16 |
17 | 18 | {/* Navigation Links (Desktop) */} 19 |
20 | Home 21 | 22 | Contact Us 23 |
24 | 25 | {/* Mobile Menu (Home Icon + Products and Contact Us) */} 26 |
27 | 28 | 29 | 30 | 31 | 32 | Contact Us 33 | 34 |
35 |
36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/components/hero.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useState, useEffect } from "react"; 3 | import { AiOutlineSearch } from "react-icons/ai"; 4 | import { FaMapMarkerAlt } from "react-icons/fa"; 5 | import { search } from "@/actions/auth/search"; 6 | import { nigeriaStates } from "./states"; 7 | import SearchBar from "./searchBar"; 8 | 9 | const images = [ 10 | "https://fbpdbcxjavianaboavoo.supabase.co/storage/v1/object/public/images//360_F_90919557_cgaQl5J8FAbTV8JTSBDu7IbD0sladNO6.jpg", 11 | "https://fbpdbcxjavianaboavoo.supabase.co/storage/v1/object/public/images//COLOURBOX65831534.webp", 12 | "https://fbpdbcxjavianaboavoo.supabase.co/storage/v1/object/public/images//sa.jpg", 13 | ]; 14 | 15 | export default function HeroHandyman() { 16 | const [marketInput, setMarketInput] = useState(""); 17 | const [currentImageIndex, setCurrentImageIndex] = useState(0); 18 | 19 | useEffect(() => { 20 | const interval = setInterval(() => { 21 | setCurrentImageIndex((prevIndex) => (prevIndex + 1) % images.length); 22 | }, 5000); 23 | return () => clearInterval(interval); 24 | }, []); 25 | 26 | return ( 27 |
31 | {/* Black Overlay */} 32 |
33 | 34 | {/* Content Container */} 35 |
36 |

37 | Discover Skilled Professionals 38 |

39 |

40 | Easily connect with experienced experts for repairs, maintenance, and installations. 41 |

42 | 43 | {/* Search Bar Form */} 44 |
45 | {/* Search Input */} 46 |
47 | 48 |
49 | 50 | {/* Location Select */} 51 |
52 | 53 | 64 |
65 | 66 | {/* Search Button */} 67 | 74 | 75 | 76 |
77 | Popular: House Cleaning, Web Design, Personal Trainers 78 |
79 |
80 |
81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /src/components/logo.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState, useEffect } from "react"; 3 | import { useActionState } from "react"; 4 | import LogoAction from "@/actions/auth/logo"; 5 | 6 | 7 | export default function Logo() { 8 | 9 | const [prev, action, isPending] = useActionState(LogoAction, { 10 | document: null, 11 | user_id: null, 12 | }); 13 | 14 | const [selectedFile, setSelectedFile] = useState(null); 15 | const [previewUrl, setPreviewUrl] = useState(null); 16 | const [documentError, setDocumentError] = useState(""); 17 | const [successMessage, setSuccessMessage] = useState(""); 18 | const MAX_FILE_SIZE = 3 * 1024 * 1024; // 3 MB 19 | 20 | useEffect(() => { 21 | if (prev?.success) { 22 | setSuccessMessage(prev.message); 23 | } 24 | }, [prev]); 25 | 26 | useEffect(() => { 27 | if (!selectedFile) { 28 | setPreviewUrl(null); 29 | return; 30 | } 31 | 32 | const objectUrl = URL.createObjectURL(selectedFile); 33 | setPreviewUrl(objectUrl); 34 | 35 | return () => URL.revokeObjectURL(objectUrl); 36 | }, [selectedFile]); 37 | 38 | const handleFileChange = (event: React.ChangeEvent) => { 39 | const file = event.target.files?.[0]; 40 | if (file) { 41 | if (file.size > MAX_FILE_SIZE) { 42 | setDocumentError("Image is too large. Please upload a file smaller than 3MB."); 43 | setSelectedFile(null); 44 | } else { 45 | setDocumentError(""); 46 | setSelectedFile(file); 47 | } 48 | } 49 | }; 50 | 51 | const handleSubmit = (event: React.FormEvent) => { 52 | if (!selectedFile) { 53 | event.preventDefault(); 54 | setDocumentError("Please upload a valid document before submitting."); 55 | } 56 | }; 57 | 58 | return ( 59 |
60 |
61 |
62 | 70 | {documentError &&

{documentError}

} 71 |
72 | 73 | {previewUrl && ( 74 |
75 |

Image Preview:

76 | Preview 77 |
78 | )} 79 | 80 | 87 | 88 | {/* Success message display */} 89 | {successMessage && ( 90 |

{successMessage}

91 | )} 92 |
93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /src/components/logoModel.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState } from "react"; 4 | 5 | import { 6 | Dialog, 7 | DialogContent, 8 | DialogDescription, 9 | DialogHeader, 10 | DialogTitle, 11 | DialogTrigger, 12 | } from "@/components/ui/dialog"; 13 | import { Button } from "@/components/ui/button"; 14 | import { FaEye, FaFileAlt } from "react-icons/fa"; 15 | import { Input } from "@/components/ui/input"; 16 | import { Label } from "@/components/ui/label"; 17 | import { createClient } from "@/utils/supabase/client"; 18 | import Logo from "./logo"; 19 | import { FaF } from "react-icons/fa6"; 20 | 21 | 22 | 23 | export default function ModalLogo() { 24 | 25 | 26 | return ( 27 | 28 | 29 | 30 | 34 | 35 | 36 | 37 | 38 | Upadate Logo 39 | 40 | Upload Business Logo 41 | 42 | 43 | 44 | 45 | 46 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/components/logoutButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useEffect } from 'react'; 3 | import { Button } from './ui/button'; 4 | import { redirect } from 'next/navigation'; 5 | import logout from '@/actions/auth/logout.ts'; 6 | import { useActionState } from 'react'; 7 | import { FaS } from 'react-icons/fa6'; 8 | import { FaSignOutAlt } from 'react-icons/fa'; 9 | 10 | export default function LogoutButton() { 11 | const [state, action, isPending] = useActionState(logout, undefined, null); 12 | 13 | useEffect(() => { 14 | if (state?.success) { 15 | console.log('Logged out successfully'); 16 | redirect('/login'); 17 | } else if (state?.error) { 18 | console.error('Error logging out:', state.error.message); 19 | redirect('/login'); 20 | } 21 | }, [state]); 22 | 23 | return ( 24 |
25 | 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/components/navbar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useState, useEffect } from 'react'; 3 | import Link from 'next/link'; 4 | import { createClient } from '@/utils/supabase/client'; 5 | import LogoutButton from './logoutButton'; 6 | import Image from 'next/image'; 7 | import { ShoppingCart } from 'lucide-react'; 8 | import { FaL } from 'react-icons/fa6'; 9 | import { FaLock } from 'react-icons/fa'; 10 | 11 | export default function Navbar() { 12 | const [user, setUser] = useState(null); 13 | const [logo, setLogo] = useState(null); 14 | 15 | useEffect(() => { 16 | const supabase = createClient(); // Initialize once 17 | 18 | async function fetchUser() { 19 | const { data: { user } } = await supabase.auth.getUser(); 20 | setUser(user); 21 | } 22 | 23 | async function fetchLogo() { 24 | const { data, error } = await supabase 25 | .from('site_info') 26 | .select("logo") 27 | .single(); 28 | 29 | if (error) console.error("Error fetching logo:", error); 30 | else setLogo(data?.logo); 31 | } 32 | 33 | fetchUser(); 34 | fetchLogo(); 35 | }, []); 36 | 37 | return ( 38 |
39 | 86 |
87 | ); 88 | } 89 | -------------------------------------------------------------------------------- /src/components/productCategory.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'next/link' 3 | 4 | export default function ProductCategory() { 5 | return ( 6 |
7 | {/* Product Categories */} 8 |

Product Categories

9 |
    10 |
  • Electronics
  • 11 |
  • Clothing
  • 12 |
  • Home Appliances
  • 13 |
  • Books
  • 14 |
  • Sports
  • 15 |
  • Toys
  • 16 |
  • Beauty
  • 17 |
  • Groceries
  • 18 |
  • Furniture
  • 19 |
  • Jewelry
  • 20 |
  • Automotive
  • 21 |
  • Garden
  • 22 |
  • Office Supplies
  • 23 |
  • Baby Products
  • 24 |
  • Pet Supplies
  • 25 |
  • Music
  • 26 |
  • Movies
  • 27 |
  • Video Games
  • 28 |
  • Health
  • 29 |
  • Travel
  • 30 |
31 |
32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /src/components/searchBar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useEffect } from "react"; 4 | import { createClient } from "@/utils/supabase/client"; 5 | 6 | export default function SearchBar({ value, onChange }) { 7 | const [skills, setSkills] = useState([]); 8 | const [suggestions, setSuggestions] = useState([]); 9 | const supabase = createClient(); 10 | 11 | useEffect(() => { 12 | // Fetch skills from Supabase 13 | const fetchSkills = async () => { 14 | const { data: skills, error } = await supabase.from("skills").select(); 15 | 16 | if (error) { 17 | console.error("Error fetching skills:", error); 18 | return; 19 | } 20 | 21 | setSkills(skills); 22 | }; 23 | 24 | fetchSkills(); 25 | }, []); 26 | 27 | const handleInputChange = (e) => { 28 | const inputValue = e.target.value; 29 | onChange(inputValue); 30 | 31 | if (inputValue.length > 1) { 32 | setSuggestions( 33 | skills.filter((item) => 34 | item.label.toLowerCase().includes(inputValue.toLowerCase()) 35 | ) 36 | ); 37 | } else { 38 | setSuggestions([]); // Clear suggestions if input is too short 39 | } 40 | }; 41 | 42 | const handleSelect = (label) => { 43 | onChange(label); 44 | setSuggestions([]); // Clear suggestions when a selection is made 45 | }; 46 | 47 | return ( 48 |
49 | 57 | {suggestions.length > 0 && ( 58 |
    59 | {suggestions.map((item) => ( 60 |
  • handleSelect(item.label)} 64 | > 65 | {item.label} 66 |
  • 67 | ))} 68 |
69 | )} 70 |
71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /src/components/selectedState.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { states } from './stateLga' 3 | 4 | export default function selectedState() { 5 | const [selectedState, setSelectedState] = useState(''); 6 | const [lga, setLga] = useState(''); 7 | const stateLgas = states.find((s) => s.name === selectedState)?.lgas || []; 8 | const [formData, setFormData] = useState({ 9 | state: '', 10 | city: '', 11 | }); 12 | 13 | const handleStateChange = (e) => { 14 | setSelectedState(e.target.value); 15 | setLga(''); // Reset LGA when state changes 16 | }; 17 | 18 | const handleLgaChange = (e) => { 19 | setLga(e.target.value); 20 | setFormData((prevData) => ({ ...prevData, city: e.target.value })); 21 | }; 22 | 23 | return ( 24 |
25 | 26 |
27 |
28 | 29 | 44 |
45 | 46 |
47 | 48 | 63 |
64 |
65 |
66 | ) 67 | } 68 | -------------------------------------------------------------------------------- /src/components/sendMessageModel.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState } from "react"; 4 | 5 | import { 6 | Dialog, 7 | DialogContent, 8 | DialogDescription, 9 | DialogHeader, 10 | DialogTitle, 11 | DialogTrigger, 12 | } from "@/components/ui/dialog"; 13 | import { Button } from "@/components/ui/button"; 14 | import { FaEye, FaFileAlt } from "react-icons/fa"; 15 | import { Input } from "@/components/ui/input"; 16 | import { Label } from "@/components/ui/label"; 17 | import { createClient } from "@/utils/supabase/client"; 18 | 19 | import { FaF } from "react-icons/fa6"; 20 | import Message from "./message"; 21 | 22 | 23 | 24 | export default function SendMessageModel({product}:any) { 25 | 26 | 27 | return ( 28 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | Send Message 40 | 41 | Send Message to Vendor 42 | 43 | 44 | 45 | 46 | 47 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /src/components/sheetAdmin.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { Button } from "@/components/ui/button"; 3 | import { 4 | Sheet, 5 | SheetContent, 6 | SheetHeader, 7 | SheetTitle, 8 | SheetTrigger, 9 | } from "@/components/ui/sheet"; 10 | import Link from "next/link"; 11 | import { LuList } from "react-icons/lu"; 12 | import { createClient } from "@/utils/supabase/client"; 13 | import { useEffect, useState } from "react"; 14 | import LogoutButton from "./logoutButton"; 15 | 16 | export function SheetAdmin() { 17 | const supabase = createClient(); 18 | const [menus, setMenus] = useState([]); 19 | 20 | useEffect(() => { 21 | async function fetchMenus() { 22 | const { data, error } = await supabase.from("menu").select("*"); 23 | if (error) { 24 | console.error("Error fetching menus:", error.message); 25 | } else { 26 | setMenus(data); 27 | } 28 | } 29 | 30 | fetchMenus(); 31 | }, [supabase]); 32 | 33 | return ( 34 | 35 | 36 | 39 | 40 | 41 | 42 | Menus 43 | 44 | 45 |
    46 | {menus.map((menu) => ( 47 | 48 |
  • 49 | {menu.title} 50 |
  • 51 | 52 | ))} 53 | 54 |
55 |
56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /src/components/sheetMenu.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button" 2 | 3 | import { 4 | Sheet, 5 | SheetClose, 6 | SheetContent, 7 | SheetDescription, 8 | SheetFooter, 9 | SheetHeader, 10 | SheetTitle, 11 | SheetTrigger, 12 | } from "@/components/ui/sheet" 13 | import Link from "next/link" 14 | import { LuList } from "react-icons/lu"; 15 | import CategoryList from "@/components/category"; 16 | import { FaTv, FaTshirt, FaHome, FaDumbbell, FaPaw, FaGem, FaBaby, FaGamepad, FaHeart } from "react-icons/fa"; 17 | import { createClient } from "@/utils/supabase/client"; 18 | import { useEffect, useState } from "react"; 19 | 20 | export function SheetMenu() { 21 | 22 | const supabase = createClient(); 23 | const [categorys, setcategorys] = useState([]); 24 | 25 | useEffect(()=>{ 26 | 27 | const fetchCategories = async () => { 28 | const { data: categories, error } = await supabase 29 | .from("category") 30 | .select("*"); 31 | if (error) console.error("Error fetching categories:", error); 32 | else setcategorys(categories); 33 | }; 34 | 35 | fetchCategories(); 36 | 37 | },[]) 38 | return ( 39 | 40 | 41 | 42 | 43 | 44 | 45 | Products Categories 46 | 47 | 48 | 49 |
    50 | {categorys.map((category) => ( 51 |
  • 52 | 53 | 54 | {category.title} 55 | 56 |
  • 57 | ))} 58 | 59 | 60 |
61 | 62 |
63 |
64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /src/components/siteLogo.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState, useEffect } from "react"; 3 | import { useActionState } from "react"; 4 | import SiteLogoAction from "@/actions/auth/siteLogo"; 5 | 6 | 7 | export default function Logo() { 8 | 9 | const [prev, action, isPending] = useActionState(SiteLogoAction, { 10 | document: null, 11 | user_id: null, 12 | }); 13 | 14 | const [selectedFile, setSelectedFile] = useState(null); 15 | const [previewUrl, setPreviewUrl] = useState(null); 16 | const [documentError, setDocumentError] = useState(""); 17 | const [successMessage, setSuccessMessage] = useState(""); 18 | const MAX_FILE_SIZE = 3 * 1024 * 1024; // 3 MB 19 | 20 | useEffect(() => { 21 | if (prev?.success) { 22 | setSuccessMessage(prev.message); 23 | } 24 | }, [prev]); 25 | 26 | useEffect(() => { 27 | if (!selectedFile) { 28 | setPreviewUrl(null); 29 | return; 30 | } 31 | 32 | const objectUrl = URL.createObjectURL(selectedFile); 33 | setPreviewUrl(objectUrl); 34 | 35 | return () => URL.revokeObjectURL(objectUrl); 36 | }, [selectedFile]); 37 | 38 | const handleFileChange = (event: React.ChangeEvent) => { 39 | const file = event.target.files?.[0]; 40 | if (file) { 41 | if (file.size > MAX_FILE_SIZE) { 42 | setDocumentError("Image is too large. Please upload a file smaller than 3MB."); 43 | setSelectedFile(null); 44 | } else { 45 | setDocumentError(""); 46 | setSelectedFile(file); 47 | } 48 | } 49 | }; 50 | 51 | const handleSubmit = (event: React.FormEvent) => { 52 | if (!selectedFile) { 53 | event.preventDefault(); 54 | setDocumentError("Please upload a valid document before submitting."); 55 | } 56 | }; 57 | 58 | return ( 59 |
60 |
61 |
62 | 70 | {documentError &&

{documentError}

} 71 |
72 | 73 | {previewUrl && ( 74 |
75 |

Image Preview:

76 | Preview 77 |
78 | )} 79 | 80 | 87 | 88 | {/* Success message display */} 89 | {successMessage && ( 90 |

{successMessage}

91 | )} 92 |
93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /src/components/siteLogoModel.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState } from "react"; 4 | 5 | import { 6 | Dialog, 7 | DialogContent, 8 | DialogDescription, 9 | DialogHeader, 10 | DialogTitle, 11 | DialogTrigger, 12 | } from "@/components/ui/dialog"; 13 | import { Button } from "@/components/ui/button"; 14 | import { FaEye, FaFileAlt } from "react-icons/fa"; 15 | import { Input } from "@/components/ui/input"; 16 | import { Label } from "@/components/ui/label"; 17 | import { createClient } from "@/utils/supabase/client"; 18 | import SiteLogo from "./siteLogo"; 19 | import { FaF } from "react-icons/fa6"; 20 | 21 | 22 | 23 | export default function siteLogoModel() { 24 | 25 | 26 | return ( 27 | 28 | 29 | 30 | 34 | 35 | 36 | 37 | 38 | Upadate Logo 39 | 40 | Upload Site Logo 41 | 42 | 43 | 44 | 45 | 46 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/components/slider.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import "slick-carousel/slick/slick.css"; 3 | import "slick-carousel/slick/slick-theme.css"; 4 | import Slider from "react-slick"; 5 | import { createClient } from '@/utils/supabase/client'; 6 | import Image from 'next/image'; 7 | 8 | export default function SliderComponent() { 9 | const [avatars, setAvatars] = useState([]); 10 | 11 | useEffect(() => { 12 | // Fetch avatars from supabase 13 | const fetchAvatars = async () => { 14 | const supabase = createClient(); 15 | const { data: avatars, error } = await supabase.from('banner').select("*"); 16 | if (error) console.error('Error fetching avatars:', error); 17 | else setAvatars(avatars); 18 | }; 19 | 20 | fetchAvatars(); 21 | }, []); 22 | 23 | const settings = { 24 | infinite: true, 25 | slidesToShow: 1, 26 | slidesToScroll: 1, 27 | autoplay: true, 28 | speed: 2000, 29 | autoplaySpeed: 6000, 30 | cssEase: "linear", 31 | adaptiveHeight: true 32 | 33 | 34 | }; 35 | 36 | return ( 37 | 38 | {/* Map avatars */} 39 | {avatars.map((avatar) => ( 40 |
41 | 49 | 50 |
51 | ))} 52 |
53 | 54 | 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/components/spinner.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from 'react'; 3 | 4 | export default function Spinner() { 5 | return ( 6 |
20 |
30 | 31 | 41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/components/states.ts: -------------------------------------------------------------------------------- 1 | // states.js 2 | export const nigeriaStates = [ 3 | "Abia", 4 | "Adamawa", 5 | "Akwa Ibom", 6 | "Anambra", 7 | "Bauchi", 8 | "Bayelsa", 9 | "Benue", 10 | "Borno", 11 | "Cross River", 12 | "Delta", 13 | "Ebonyi", 14 | "Edo", 15 | "Ekiti", 16 | "Enugu", 17 | "Gombe", 18 | "Imo", 19 | "Jigawa", 20 | "Kaduna", 21 | "Kano", 22 | "Katsina", 23 | "Kebbi", 24 | "Kogi", 25 | "Kwara", 26 | "Lagos", 27 | "Nasarawa", 28 | "Niger", 29 | "Ogun", 30 | "Ondo", 31 | "Osun", 32 | "Oyo", 33 | "Plateau", 34 | "Rivers", 35 | "Sokoto", 36 | "Taraba", 37 | "Yobe", 38 | "Zamfara", 39 | "FCT", 40 | ]; 41 | -------------------------------------------------------------------------------- /src/components/tabsFetch.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import Tab from 'react-bootstrap/Tab'; 3 | import Tabs from 'react-bootstrap/Tabs'; 4 | import 'bootstrap/dist/css/bootstrap.min.css'; 5 | import Reviews from './reviews'; 6 | // use param 7 | 8 | function TabsFetch({ userDetail }: { userDetail: any }) { 9 | const [key, setKey] = useState('home'); 10 | 11 | return ( 12 |
13 | 14 | setKey(k || 'home')} 18 | className="mb-3" 19 | fill 20 | > 21 | 22 |

Services Offered

23 | {userDetail.skills && userDetail.skills.length > 0 ? ( 24 |
    25 | {userDetail.skills.split(',').map((skill: string, index: number) => ( 26 |
  • {skill}
  • 27 | ))} 28 |
29 | ) : ( 30 |

No services available.

31 | )} 32 | 33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 |

Work Samples

41 |
42 | Sample 1 43 | Sample 2 44 | Sample 3 45 |
46 |
47 |
48 |
49 | ); 50 | } 51 | 52 | export default TabsFetch; 53 | -------------------------------------------------------------------------------- /src/components/topbar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FaBell, FaUserAlt } from 'react-icons/fa'; // Importing icons 3 | import Link from 'next/link'; 4 | import Userdashboard from '@/components/userdashboard'; 5 | 6 | export default function Topbar() { 7 | return ( 8 |
9 | 10 |
11 | {/* */} 12 | 13 | {/* 14 | 18 | */} 19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/topnav.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { FaMapMarkerAlt, FaSearch } from "react-icons/fa"; 4 | import { nigeriaStates } from "./states"; 5 | import Image from "next/image"; 6 | import { Button } from "./ui/button"; 7 | 8 | export default function Topnav() { 9 | return ( 10 |
11 |
12 | 19 | 20 | 21 |
22 |
23 | 24 |
25 |
26 |
27 | {/* Search Input */} 28 | 29 | 35 | 36 | {/* Location Dropdown */} 37 |
38 | 39 | 50 |
51 | 52 | {/* Search Button */} 53 | 56 |
57 | 58 |
59 |
60 |
61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /src/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const alertVariants = cva( 7 | "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-background text-foreground", 12 | destructive: 13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", 14 | }, 15 | }, 16 | defaultVariants: { 17 | variant: "default", 18 | }, 19 | } 20 | ) 21 | 22 | const Alert = React.forwardRef< 23 | HTMLDivElement, 24 | React.HTMLAttributes & VariantProps 25 | >(({ className, variant, ...props }, ref) => ( 26 |
32 | )) 33 | Alert.displayName = "Alert" 34 | 35 | const AlertTitle = React.forwardRef< 36 | HTMLParagraphElement, 37 | React.HTMLAttributes 38 | >(({ className, ...props }, ref) => ( 39 |
44 | )) 45 | AlertTitle.displayName = "AlertTitle" 46 | 47 | const AlertDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |
56 | )) 57 | AlertDescription.displayName = "AlertDescription" 58 | 59 | export { Alert, AlertTitle, AlertDescription } 60 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | -------------------------------------------------------------------------------- /src/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLDivElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |
41 | )) 42 | CardTitle.displayName = "CardTitle" 43 | 44 | const CardDescription = React.forwardRef< 45 | HTMLDivElement, 46 | React.HTMLAttributes 47 | >(({ className, ...props }, ref) => ( 48 |
53 | )) 54 | CardDescription.displayName = "CardDescription" 55 | 56 | const CardContent = React.forwardRef< 57 | HTMLDivElement, 58 | React.HTMLAttributes 59 | >(({ className, ...props }, ref) => ( 60 |
61 | )) 62 | CardContent.displayName = "CardContent" 63 | 64 | const CardFooter = React.forwardRef< 65 | HTMLDivElement, 66 | React.HTMLAttributes 67 | >(({ className, ...props }, ref) => ( 68 |
73 | )) 74 | CardFooter.displayName = "CardFooter" 75 | 76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 77 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | } 19 | ) 20 | Input.displayName = "Input" 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /src/components/ui/pagination.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react" 3 | 4 | import { cn } from "@/lib/utils" 5 | import { ButtonProps, buttonVariants } from "@/components/ui/button" 6 | 7 | const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => ( 8 |
15 | 16 | )) 17 | Table.displayName = "Table" 18 | 19 | const TableHeader = React.forwardRef< 20 | HTMLTableSectionElement, 21 | React.HTMLAttributes 22 | >(({ className, ...props }, ref) => ( 23 | 24 | )) 25 | TableHeader.displayName = "TableHeader" 26 | 27 | const TableBody = React.forwardRef< 28 | HTMLTableSectionElement, 29 | React.HTMLAttributes 30 | >(({ className, ...props }, ref) => ( 31 | 36 | )) 37 | TableBody.displayName = "TableBody" 38 | 39 | const TableFooter = React.forwardRef< 40 | HTMLTableSectionElement, 41 | React.HTMLAttributes 42 | >(({ className, ...props }, ref) => ( 43 | tr]:last:border-b-0", 47 | className 48 | )} 49 | {...props} 50 | /> 51 | )) 52 | TableFooter.displayName = "TableFooter" 53 | 54 | const TableRow = React.forwardRef< 55 | HTMLTableRowElement, 56 | React.HTMLAttributes 57 | >(({ className, ...props }, ref) => ( 58 | 66 | )) 67 | TableRow.displayName = "TableRow" 68 | 69 | const TableHead = React.forwardRef< 70 | HTMLTableCellElement, 71 | React.ThHTMLAttributes 72 | >(({ className, ...props }, ref) => ( 73 |
[role=checkbox]]:translate-y-[2px]", 77 | className 78 | )} 79 | {...props} 80 | /> 81 | )) 82 | TableHead.displayName = "TableHead" 83 | 84 | const TableCell = React.forwardRef< 85 | HTMLTableCellElement, 86 | React.TdHTMLAttributes 87 | >(({ className, ...props }, ref) => ( 88 | [role=checkbox]]:translate-y-[2px]", 92 | className 93 | )} 94 | {...props} 95 | /> 96 | )) 97 | TableCell.displayName = "TableCell" 98 | 99 | const TableCaption = React.forwardRef< 100 | HTMLTableCaptionElement, 101 | React.HTMLAttributes 102 | >(({ className, ...props }, ref) => ( 103 |
108 | )) 109 | TableCaption.displayName = "TableCaption" 110 | 111 | export { 112 | Table, 113 | TableHeader, 114 | TableBody, 115 | TableFooter, 116 | TableHead, 117 | TableRow, 118 | TableCell, 119 | TableCaption, 120 | } 121 | -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Textarea = React.forwardRef< 6 | HTMLTextAreaElement, 7 | React.ComponentProps<"textarea"> 8 | >(({ className, ...props }, ref) => { 9 | return ( 10 |