diff --git a/frontend/.env b/frontend/.env index a3598449..6b063f03 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1,3 +1,2 @@ NEXTAUTH_SECRET=8ce5be9dcc4eb9b0314cc3fe296d231ba5e786439f8c4a6e272cad29f8dda8c3 - NODE_ENV = development \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore index 8f322f0d..6b9e91af 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -27,6 +27,7 @@ yarn-error.log* # local env files .env*.local + # vercel .vercel diff --git a/frontend/app/(auth)/login/page.tsx b/frontend/app/(auth)/login/page.tsx index 814db708..a59e9ec3 100644 --- a/frontend/app/(auth)/login/page.tsx +++ b/frontend/app/(auth)/login/page.tsx @@ -4,53 +4,77 @@ import Button from "@/app/components/button/Button" import Input from "@/app/components/input/Input" import useLogin from "./useLogin" import Link from "next/link" +import Modal from "@/app/components/Modal" +import { useWallet } from "@/app/store/WalletStore" +import ConnectToMetamaskModal from "@/app/components/ConnectToMetamaskModal" +import { formatWalletAddress } from "@/app/utils/formatWalletAddress" -export default function page() { +export default function Login() { const { actions, states } = useLogin() + const { isConnected, walletAddress } = useWallet() return ( -
-
-

Welcome to Meta market! 👋

-

- Please login with email address and password to continue . -

- -
-
- -
+ <> +
+
+

Welcome to Meta market! 👋

+

+ Please login with email address and password to continue . +

-
- -
+ +
+ +
+ +
+ +
- - + {walletAddress && ( +
+ Connected wallet address +

{formatWalletAddress(walletAddress)}

+
+ )} -
- Dont have an account yet ?{" "} - - Create an account - + + + +
+ Dont have an account yet ?{" "} + + Create an account + +
-
-
+
+ + + + + ) } diff --git a/frontend/app/(auth)/login/useLogin.ts b/frontend/app/(auth)/login/useLogin.ts index cb4a3103..5ea29ffe 100644 --- a/frontend/app/(auth)/login/useLogin.ts +++ b/frontend/app/(auth)/login/useLogin.ts @@ -1,6 +1,9 @@ +import { IUserSession } from "@/app/interfaces/IUser" +import { AxiosError } from "axios" import { useFormik } from "formik" -import { signIn } from "next-auth/react" +import { signIn, useSession } from "next-auth/react" import { useRouter } from "next/navigation" +import { useEffect, useState } from "react" import { toast } from "react-hot-toast" import * as y from "yup" @@ -17,16 +20,46 @@ const loginSchema = y.object({ export default function useLogin() { const navigate = useRouter() + const { data } = useSession() + const user = data?.user as IUserSession + + useEffect(() => { + if (user) redirectToPage() + }, [user]) + + const [isLoading, setIsLoading] = useState(false) const initialValues = { email: "", password: "", } + // redirect to route + const redirectToPage = () => { + switch (user.data.role.name) { + case "Authenticated": + navigate.push("/") + break + case "Admin": + navigate.push("/admin") + break + case "Brand": + navigate.push("/brand") + break + case "Seller": + navigate.push("/seller") + break + default: + navigate.push("/") + break + } + } + const { values, errors, handleChange, handleSubmit } = useFormik({ initialValues, validationSchema: loginSchema, onSubmit: async (values) => { + setIsLoading(true) try { const res = await signIn("credentials", { ...values, @@ -36,15 +69,21 @@ export default function useLogin() { if (res?.error) throw new Error(res.error) // base on user role navigate to particular page - - console.log(res) - - navigate.push("/") + redirectToPage() } catch (error: any) { - toast.error(error.message) + if (error instanceof AxiosError) { + toast.error(error.response?.data.error.message) + } else { + toast.error("Someting went wrong please try again!") + } + } finally { + setIsLoading(false) } }, }) - return { actions: { handleChange, handleSubmit }, states: { values, errors } } + return { + actions: { handleChange, handleSubmit }, + states: { values, errors, isLoading }, + } } diff --git a/frontend/app/(auth)/register/page.tsx b/frontend/app/(auth)/register/page.tsx index b8b61243..f9487861 100644 --- a/frontend/app/(auth)/register/page.tsx +++ b/frontend/app/(auth)/register/page.tsx @@ -1,71 +1,125 @@ "use client" - import Button from "@/app/components/button/Button" -import React from "react" +import React, { useState } from "react" import useRegister from "./useRegister" import Input from "@/app/components/input/Input" import Link from "next/link" +import ConnectToMetamaskModal from "@/app/components/ConnectToMetamaskModal" +import Modal from "@/app/components/Modal" +import { useWallet } from "@/app/store/WalletStore" +import { formatWalletAddress } from "@/app/utils/formatWalletAddress" +import { useMutation, useQuery } from "react-query" +import qs from "qs" +import ReferralService from "@/app/services/referral.service" +import { ISingleReferral } from "@/app/services/ISingleReferral" -export default function page() { +export default function Register() { const { actions, states } = useRegister() + const { isConnected, walletAddress } = useWallet() return ( -
-
-

Hey new to Meta market! 👋

-

- Create an account and start using now. -

+ <> +
+
+

Hey new to Meta market! 👋

+

+ Create an account and start using now. +

-
-
- -
+ +
+ +
-
- -
+
+ +
-
- -
+
+ +
+ + {states.hasId && ( +
+ { + actions.setUserReferralId(e.target.value) + }} + value={states.userReferralId} + error={states.referralError} + /> +
+ )} - -
+ -
- Already have an account?{" "} - - Sign Up - + {walletAddress && ( +
+ Connected wallet address +

{formatWalletAddress(walletAddress)}

+
+ )} + + + + +
+ Already have an account?{" "} + + Sign Up + +
-
-
+
+ + + + + ) } diff --git a/frontend/app/(auth)/register/useReferral.ts b/frontend/app/(auth)/register/useReferral.ts new file mode 100644 index 00000000..9545de29 --- /dev/null +++ b/frontend/app/(auth)/register/useReferral.ts @@ -0,0 +1,4 @@ +import { useQuery } from "react-query"; +import qs from 'qs' +import * as y from 'yup' +import ReferralService from "@/app/services/referral.service"; diff --git a/frontend/app/(auth)/register/useRegister.ts b/frontend/app/(auth)/register/useRegister.ts index 9e1186a1..b1f0f25a 100644 --- a/frontend/app/(auth)/register/useRegister.ts +++ b/frontend/app/(auth)/register/useRegister.ts @@ -1,11 +1,18 @@ import AuthService from "@/app/services/auth.service" import { useFormik } from "formik" -import { signIn } from "next-auth/react" -import { useMutation } from "react-query" +import { signIn, useSession } from "next-auth/react" +import { useMutation, useQuery } from "react-query" import * as y from "yup" import { AxiosError } from "axios" import { toast } from "react-hot-toast" import { useRouter } from "next/navigation" +import { IUserSession } from "@/app/interfaces/IUser" +import { useEffect, useState } from "react" +import { useWallet } from "@/app/store/WalletStore" +import { ISingleReferral } from "@/app/services/ISingleReferral" +import ReferralService from "@/app/services/referral.service" +import QueryString from "qs" +import useDebounce from "@/app/hooks/useDebounce" const singUpSchema = y.object({ email: y @@ -21,6 +28,37 @@ const singUpSchema = y.object({ export default function useRegister() { const navigate = useRouter() + const { walletAddress } = useWallet() + + const [hasId, setHasId] = useState(false) + const [userReferralId, setUserReferralId] = useState("") + + const referalQuery = useDebounce(userReferralId, 300) + + const [foundReferral, setFoundReferral] = useState(null) + const [referralError, setReferralError] = useState("") + + const { data } = useSession() + const user = data?.user as IUserSession + + useEffect(() => { + if (user) redirectToPage() + }, [user]) + + // redirect to route + const redirectToPage = () => { + switch (user.data.role.name) { + case "Authenticated": + navigate.push("/") + break + case "Admin": + navigate.push("/admin") + break + default: + navigate.push("/") + break + } + } const initialValues = { email: "", @@ -28,41 +66,99 @@ export default function useRegister() { name: "", } + const { mutateAsync: updateReferral } = useMutation((data: any) => + ReferralService.updateReferral(foundReferral!!, data) + ) + + const query = QueryString.stringify({ + fields: ["refferId", "isAccountCreated"], + filters: { + $and: [ + { + refferId: { + $eq: referalQuery, + }, + }, + { + isAccountCreated: false, + }, + ], + }, + }) + + useQuery( + ["referral", referalQuery], + () => ReferralService.checkReferral(query), + { + onSuccess({ data }) { + if (data.length === 0) { + setReferralError("No referral found with given code!") + return + } + setFoundReferral(data[0].id) + }, + onError(error) { + toast.error( + "Someting went wrong while checking referral please try again!" + ) + }, + enabled: referalQuery === "" ? false : true, + } + ) + + const handleRegister = async (data: any) => { + try { + if (!walletAddress) { + toast.error("Please connect your meta mask wallet account") + return + } + + await Promise.all([ + register({ + username: data.name, + walletAddress, + ...data, + }), + + foundReferral && + updateReferral({ + data: { + isAccountCreated: true, + }, + }), + ]) + + // to create the cookie in next auth for further authentication + const res = await signIn("credentials", { + ...values, + redirect: false, + }) + + if (res?.error) throw new Error(res.error) + + // base on user role navigate to particular page + redirectToPage() + } catch (error: any) { + if (error instanceof AxiosError) { + toast.error(error.response?.data.error.message) + } else { + toast.error(error.message) + } + } + } + const { values, errors, handleChange, handleSubmit } = useFormik({ initialValues, validationSchema: singUpSchema, - onSubmit: async (values) => { - try { - await register({ - username: values.name, - walletAddress: "temp", - ...values, - }) - - // to create the cookie in next auth for further authentication - const res = await signIn("credentials", { - ...values, - redirect: false, - }) - - if (res?.error) throw new Error(res.error) - - // set the user data in global state and redirect user - navigate.push("/") - } catch (error: any) { - if (error instanceof AxiosError) { - toast.error(error.response?.data.error.message) - } else { - toast.error(error.message) - } - } + onSubmit: (values) => { + handleRegister(values) }, }) const { mutateAsync: register, isLoading } = useMutation(AuthService.register) return { - actions: { handleChange, handleSubmit }, - states: { values, errors, isLoading }, + actions: { handleChange, handleSubmit, setUserReferralId, setHasId }, + states: { values, errors, isLoading, hasId, userReferralId, referralError }, } } diff --git a/frontend/app/admin/brands/[id]/page.tsx b/frontend/app/admin/brands/[id]/page.tsx new file mode 100644 index 00000000..fbe9eddf --- /dev/null +++ b/frontend/app/admin/brands/[id]/page.tsx @@ -0,0 +1,218 @@ +"use client" + +import Button from "@/app/components/button/Button" +import useLoyaltyContract from "@/app/hooks/useLoyaltyContract" +import { IBrandDetails } from "@/app/interfaces/IBrandDetails" +import { IUserSession } from "@/app/interfaces/IUser" +import BrandService from "@/app/services/brand.service" +import { useWallet } from "@/app/store/WalletStore" +import { formatWalletAddress } from "@/app/utils/formatWalletAddress" +import { ethers } from "ethers" +import { useSession } from "next-auth/react" +import Image from "next/image" +import Link from "next/link" +import qs from "qs" +import { useEffect, useState } from "react" +import toast from "react-hot-toast" +import { CiUser } from "react-icons/ci" +import { FaCoins, FaEthereum, FaBitcoin } from "react-icons/fa" +import { useQuery } from "react-query" + +interface IProps { + params: { + id: string + } +} + +interface IIssuerTransaction { + user: string + brandAddress: string + tokens: string + timestamp: string + hash: string +} + +export default function BrandDetails({ params }: IProps) { + const { data: session } = useSession() + const user = session?.user as IUserSession + + const { walletAddress } = useWallet() + const { getAccountBalance, totalSupply, getTokenTransferedTransactions } = + useLoyaltyContract() + + const [transactions, setTransactions] = useState([]) + const [loading, setLoading] = useState(true) + + const [stats, setStats] = useState({ + accountBalance: "", + supply: "", + }) + + const query = qs.stringify( + { + fields: ["name", "brandLogo"], + populate: { + user: { + fields: ["username", "walletAddress"], + }, + }, + }, + { encodeValuesOnly: true } + ) + + const { data: brand } = useQuery( + ["brand", params.id], + () => + BrandService.getBrandByID(params.id, user?.token!!, query), + { + enabled: user ? true : false, + } + ) + + useEffect(() => { + if (!walletAddress || !brand) return + ;(async () => { + try { + const [balance, supply, transactions] = await Promise.all([ + getAccountBalance( + brand.data.attributes.user.data.attributes.walletAddress + ), + totalSupply(), + getTokenTransferedTransactions(), + ]) + + setStats({ + accountBalance: ethers.formatEther(`${balance}`), + supply: ethers.formatEther(supply.toString()), + }) + + const brandTransactions = transactions.filter( + (tx) => + tx.args[1].toLowerCase() === user.data.walletAddress.toLowerCase() + ) + + setTransactions( + brandTransactions.map(({ args, blockHash }) => { + return { + user: args[0], + brandAddress: args[1], + tokens: ethers.formatEther(args[2].toString()), + timestamp: args[3].toString(), + hash: blockHash, + } + }) + ) + } catch (error: any) { + toast.error("Something went wrong") + } finally { + setLoading(false) + } + })() + }, [walletAddress, brand]) + + if (loading) + return ( +
+
+
+ ) + + return ( +
+
+ + +

+ {brand?.data.attributes.name} +

+ +

+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Provident eum + quibusdam minus quasi, sunt blanditiis quae neque iusto dolorem + expedita, explicabo alias voluptate dolor perspiciatis repellat + laudantium culpa animi commodi. +

+ +
+ + {brand?.data.attributes.user.data.attributes.username} +
+
+ +
+
+

Loyalty Program

+
+ +
+
+ +

{stats.accountBalance}

+ Number of tokens +
+ +
+ +

{stats.supply}

+ Total Supply +
+
+
+ +
+

Latest Transactions

+ +
+ + + + + + + + + + + + {transactions.map( + ({ brandAddress, hash, timestamp, tokens, user }) => ( + + + + + + + ) + )} + +
+ Transaction Hash + + User Address + + Number of tokens + + Status +
+ {formatWalletAddress(hash)} + {formatWalletAddress(user)}{tokens} + + Recevied + +
+
+
+
+ ) +} diff --git a/frontend/app/admin/brands/create/page.tsx b/frontend/app/admin/brands/create/page.tsx new file mode 100644 index 00000000..7e738d0d --- /dev/null +++ b/frontend/app/admin/brands/create/page.tsx @@ -0,0 +1,195 @@ +"use client" + +import Button from "@/app/components/button/Button" +import Input from "@/app/components/input/Input" +import { IUserSession } from "@/app/interfaces/IUser" +import UserService from "@/app/services/user.service" +import { useFormik } from "formik" +import { useSession } from "next-auth/react" +import { ChangeEvent, useState } from "react" +import { toast } from "react-hot-toast" +import { useMutation, useQuery } from "react-query" +import * as y from "yup" +import qs from "qs" +import { ISelectUsers } from "@/app/interfaces/ISelectUser" +import BrandService from "@/app/services/brand.service" +import UploadService from "@/app/services/upload.service" +import { IUploadFile } from "@/app/interfaces/IUplodFile" +import { useRouter } from "next/navigation" + +const brandSchema = y.object({ + name: y.string().required("brand name is required"), + description: y.string().required("brand description is required"), +}) + +export default function Create() { + const { data: session } = useSession() + const user = session?.user as IUserSession + const router = useRouter() + + const initialValues = { + name: "", + description: "", + } + + const [logo, setLogo] = useState(null) + const [selectedUser, setSelectedUser] = useState("") + const [isLoading, setIsLoding] = useState(false) + + const handleFileChange = (e: ChangeEvent) => { + const files = e.target.files + if (!files || files.length === 0) return + setLogo(files[0]) + } + + const usersQuery = qs.stringify( + { + fields: ["id", "username"], + }, + { encodeValuesOnly: true } + ) + + const { data } = useQuery( + ["users", user], + () => UserService.getAllUsers(user.token, usersQuery), + { + onError: (error: any) => { + toast.error(error.response.data.error.message) + }, + enabled: user ? true : false, + } + ) + + const { mutateAsync: createBrand } = useMutation( + (data: any) => BrandService.createBrand(data, user.token), + { + onSuccess: (data) => { + toast.success("Brand created successfully!!") + setIsLoding(false) + router.push("/admin/brands") + }, + onError: (error: any) => { + toast.error(error.response.data.error.message) + setIsLoding(false) + }, + } + ) + + const { mutateAsync: uploadBrandLogo } = useMutation( + (data: any) => UploadService.upload(data), + { + onError: (error: any) => { + toast.error(error.response.data.error.message) + setIsLoding(false) + }, + } + ) + + const { handleChange, handleSubmit, values, errors } = useFormik({ + initialValues, + validationSchema: brandSchema, + onSubmit: async (values) => { + if (!logo || !selectedUser) { + toast.error("all fields are required!") + return + } + + setIsLoding(true) + + // uplod brand logo + const formData = new FormData() + formData.append("api_key", process.env.NEXT_PUBLIC_API_KEY!!) + formData.append("upload_preset", process.env.NEXT_PUBLIC_UPLOAD_PRESET!!) + formData.append("public_id", process.env.NEXT_PUBLIC_PUBLIC_ID!!) + formData.append("file", logo) + + const uploadedFileResponse = await uploadBrandLogo(formData) + + await createBrand({ + data: { + name: values.name, + description: values.description, + brandLogo: uploadedFileResponse.secure_url, + user: selectedUser, + }, + }) + }, + }) + + return ( +
+
+

Create new brand

+

+ Add the following required information to add a new brand +

+
+ +
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ + +
+
+ ) +} diff --git a/frontend/app/admin/brands/page.tsx b/frontend/app/admin/brands/page.tsx new file mode 100644 index 00000000..2cfffcb6 --- /dev/null +++ b/frontend/app/admin/brands/page.tsx @@ -0,0 +1,164 @@ +"use client" +import Button from "@/app/components/button/Button" +import { IUserSession } from "@/app/interfaces/IUser" +import BrandService from "@/app/services/brand.service" +import { useSession } from "next-auth/react" +import Link from "next/link" +import React, { useState } from "react" +import { useQuery } from "react-query" +import qs from "qs" +import { IBrands } from "@/app/interfaces/IBrands" +import Image from "next/image" +import { formatWalletAddress } from "@/app/utils/formatWalletAddress" +import { useWallet } from "@/app/store/WalletStore" +import useLoyaltyContract from "@/app/hooks/useLoyaltyContract" +import { toast } from "react-hot-toast" +import Modal from "@/app/components/Modal" + +export default function Brands() { + const { walletAddress } = useWallet() + const { addIssuer } = useLoyaltyContract() + const [loading, setLoading] = useState(false) + + const { data: session } = useSession() + const user = session?.user as IUserSession + + const query = qs.stringify( + { + fields: ["name", "brandLogo"], + populate: { + user: { + fields: ["username", "walletAddress"], + }, + }, + }, + { encodeValuesOnly: true } + ) + + const { data: brands, isLoading } = useQuery( + ["brands"], + () => BrandService.getBrands(user.token, query), + { + enabled: user ? true : false, + } + ) + + const handleAddIssuer = async (address: string) => { + setLoading(true) + try { + await addIssuer(address, walletAddress) + toast.success("Issuer added successfully!") + } catch (err) { + toast.error("Something went wrong") + } finally { + setLoading(false) + } + } + + if (isLoading) + return ( +
+
+
+ ) + + return ( + <> +
+
+
+

Brands

+

Create and manage brands

+
+ + + +
+ +
+
+ + + + + + + + + + + + {brands?.data.map((brand) => ( + + + + + + + + ))} + +
+ Brand Name + + Logo + + Username + + Wallet address +
+ {brand.attributes.name} + + + + {brand.attributes.user.data.attributes.username} + + {formatWalletAddress( + brand.attributes.user.data.attributes.walletAddress + )} + + + Edit + + +
+
+
+
+ + +
+

Adding issuer please wait...

+

Issuer will be added and fund will be transfered!

+ +
+
+
+
+
+ + ) +} diff --git a/frontend/app/admin/layout.tsx b/frontend/app/admin/layout.tsx new file mode 100644 index 00000000..9f64950b --- /dev/null +++ b/frontend/app/admin/layout.tsx @@ -0,0 +1,56 @@ +import React from "react" +import { AdminSidebar } from "../components/admin" +import { redirect } from "next/navigation" +import { getCurrentUser } from "../utils/auth.utils" +import { BsCoin } from "react-icons/bs" +import { LiaSellsy } from "react-icons/lia" +import { LuStore } from "react-icons/lu" +import { PiUsersLight } from "react-icons/pi" + +export default async function AdminLayout({ + children, +}: { + children: React.ReactNode +}) { + const session = await getCurrentUser() + + if (session?.data.role.name !== "Admin") redirect("/login") + + const routes = [ + { + icon: , + title: "Loylty Program", + redirectURL: "/admin", + isHomePage: true, + optionalURL: "/admin/transfer", + }, + { + icon: , + title: "Brands", + redirectURL: "/admin/brands", + isHomePage: false, + optionalURL: undefined, + }, + { + icon: , + title: "Sellers", + redirectURL: "/admin/sellers", + isHomePage: false, + optionalURL: undefined, + }, + { + icon: , + title: "Referrals", + redirectURL: "/admin/referrals", + isHomePage: false, + optionalURL: undefined, + }, + ] + + return ( +
+ +
{children}
+
+ ) +} diff --git a/frontend/app/admin/page.tsx b/frontend/app/admin/page.tsx new file mode 100644 index 00000000..efe8313a --- /dev/null +++ b/frontend/app/admin/page.tsx @@ -0,0 +1,298 @@ +"use client" + +import React, { useEffect, useState } from "react" +import useLoyaltyContract from "../hooks/useLoyaltyContract" +import { toast } from "react-hot-toast" +import { useWallet } from "../store/WalletStore" +import { ethers } from "ethers" +import { formatWalletAddress } from "../utils/formatWalletAddress" +import moment from "moment" +import { BsPercent } from "react-icons/bs" +import { CiCoins1, CiMoneyBill, CiTimer } from "react-icons/ci" +import { LiaBitcoin } from "react-icons/lia" +import { AiOutlineShop, AiOutlineTags } from "react-icons/ai" +import AdminCard from "../components/admin/AdminCard" +import { GoCrossReference } from "react-icons/go" + +interface IIssuerTransaction { + issuer: string + tokens: string + timestamp: string + isAdded: boolean +} + +interface ISettlement { + transferFrom: string + tokens: string + timestamp: string + hash: string +} + +export default function Admin() { + const { walletAddress } = useWallet() + const { + getAccountBalance, + totalSupply, + issuerTransactions, + numberOfIssuers, + getSettlementsTransactions, + getInitialIssuerTokens, + getDecayPeriod, + updateSettlementRate, + getSettlementRate, + getRefferalRewardAmount, + updateRefferalReward, + } = useLoyaltyContract() + + const [loading, setLoading] = useState(true) + + const [stats, setStats] = useState({ + accountBalance: "", + supply: "", + issuersCount: "", + initialIssuerTokens: "", + decayRate: "", + settelmentRate: "", + referralRewardRate: "", + }) + + const [issuers, setIssuers] = useState([]) + const [settlements, setSettlements] = useState([]) + + useEffect(() => { + if (!walletAddress) return + ;(async () => { + try { + const [ + balance, + supply, + issuersCount, + settlementTransactions, + initialIssuerTokens, + decayPeriod, + settlementRate, + referralRewardRate, + logs, + ] = await Promise.all([ + getAccountBalance(walletAddress), + totalSupply(), + numberOfIssuers(), + getSettlementsTransactions(), + getInitialIssuerTokens(), + getDecayPeriod(), + getSettlementRate(), + getRefferalRewardAmount(), + issuerTransactions(), + ]) + + setIssuers( + logs.map(({ args }) => { + return { + issuer: formatWalletAddress(args[0]), + tokens: ethers.formatEther(args[1].toString()), + timestamp: args[2].toString(), + isAdded: args[3], + } + }) + ) + + setSettlements( + settlementTransactions.map(({ args, blockHash }) => { + return { + transferFrom: args[0], + tokens: ethers.formatEther(args[1].toString()), + timestamp: args[2].toString(), + hash: blockHash, + } + }) + ) + + setStats({ + accountBalance: ethers.formatEther(`${balance}`), + supply: ethers.formatEther(supply.toString()), + issuersCount: issuersCount.toString(), + initialIssuerTokens: ethers.formatEther( + initialIssuerTokens.toString() + ), + decayRate: decayPeriod.toString(), + settelmentRate: settlementRate.toString(), + referralRewardRate: ethers.formatEther(referralRewardRate.toString()), + }) + } catch (error: any) { + toast.error("Something went wrong") + } finally { + setLoading(false) + } + })() + }, [walletAddress]) + + if (loading) + return ( +
+
+
+ ) + + return ( +
+
+

Loyalty Program

+

Manage the activity of the loyalty program

+
+ +
+ } + /> + + } + /> + + } + /> + + } + /> + + } + isEditable + modalTitle="Update settlement rate" + modalDescription="Enter the new settlement rate to be used for transactions between brand and platform" + updateFunction={updateSettlementRate} + /> + + } + /> + + } + /> + + } + isEditable + modalTitle="Update referral reward rate" + modalDescription="Enter the new referral reward rate to be used for transfer tokens for referrals" + updateFunction={updateRefferalReward} + /> +
+ +
+

Settlements Transactions

+ +
+ + + + + + + + + + + + {settlements.map((settlement) => ( + + + + + + + ))} + +
+ Transaction Hash + + Transfer From + + Tokens Transfered + + Timestamp +
+ {formatWalletAddress(settlement.hash)} + + {formatWalletAddress(settlement.transferFrom)} + {settlement.tokens} FC + {moment.unix(+settlement.timestamp).format("DD MMMM YYYY")} +
+
+
+ +
+

Issuers

+ +
+ + + + + + + + + + + + {issuers.map((issuer) => ( + + + + + + + ))} + +
+ Issuer Wallet Address + + Tokens Transfered + + Timestamp + + Wallet address +
+ {issuer.issuer} + {issuer.tokens} FC + {moment.unix(+issuer.timestamp).format("DD MMMM YYYY")} + + {issuer.isAdded ? "true" : "false"} +
+
+
+
+ ) +} diff --git a/frontend/app/admin/referrals/page.tsx b/frontend/app/admin/referrals/page.tsx new file mode 100644 index 00000000..a1107f62 --- /dev/null +++ b/frontend/app/admin/referrals/page.tsx @@ -0,0 +1,133 @@ +"use client" + +import Modal from "@/app/components/Modal" +import Button from "@/app/components/button/Button" +import useLoyaltyContract from "@/app/hooks/useLoyaltyContract" +import { IAdminReferrals } from "@/app/interfaces/IAdminReferrals" +import { IUserSession } from "@/app/interfaces/IUser" +import ReferralService from "@/app/services/referral.service" +import { formatWalletAddress } from "@/app/utils/formatWalletAddress" +import { useSession } from "next-auth/react" +import qs from "qs" +import { toast } from "react-hot-toast" +import { useMutation, useQuery } from "react-query" + +export default function Referrals() { + const { data: session } = useSession() + const user = session?.user as IUserSession + + const { giveRewardOnReferral } = useLoyaltyContract() + + const { mutateAsync: reward, isLoading: giveRewardLoading } = useMutation( + (userAddress: string) => giveRewardOnReferral(userAddress) + ) + + const query = qs.stringify( + { + filters: { + isAccountCreated: true, + }, + populate: { + user_id: { + fields: ["username", "walletAddress"], + }, + }, + fields: ["id"], + }, + { encodeValuesOnly: true } + ) + + const { data: referrals, isLoading } = useQuery( + ["admin-referrals"], + () => ReferralService.getRefferals(user.token, query), + { + enabled: user ? true : false, + } + ) + + const handleSendReward = async (address: string) => { + try { + await reward(address) + toast.success("Reward sent to user successfully!") + } catch (e: any) { + toast.error("Someting went wrong please try again!") + } + } + + if (isLoading) + return ( +
+
+
+ ) + + return ( + <> +
+
+
+

Referrls

+

Approve the referral reward for users

+
+
+ +
+
+ + + + + + + + + + {referrals?.data.map(({ attributes, id }) => ( + + + + + + + ))} + +
+ UserName + + Wallet address +
+ {attributes.user_id.data.attributes.username} + + {formatWalletAddress( + attributes.user_id.data.attributes.walletAddress + )} + + +
+
+
+
+ + +
+
+
+
+ + ) +} diff --git a/frontend/app/admin/sellers/[id]/page.tsx b/frontend/app/admin/sellers/[id]/page.tsx new file mode 100644 index 00000000..cd40e356 --- /dev/null +++ b/frontend/app/admin/sellers/[id]/page.tsx @@ -0,0 +1,175 @@ +"use client" + +import Button from "@/app/components/button/Button" +import useLoyaltyContract from "@/app/hooks/useLoyaltyContract" +import { ISellerDetails } from "@/app/interfaces/ISellerDetails" +import { IUserSession } from "@/app/interfaces/IUser" +import SellerService from "@/app/services/sellers.service" +import { useWallet } from "@/app/store/WalletStore" +import { ethers } from "ethers" +import { useSession } from "next-auth/react" +import Image from "next/image" +import Link from "next/link" +import qs from "qs" +import { useEffect, useState } from "react" +import toast from "react-hot-toast" +import { CiUser } from "react-icons/ci" +import { FaCoins, FaBitcoin } from "react-icons/fa" +import { useQuery } from "react-query" + +interface IProps { + params: { + id: string + } +} + +export default function SellerDetails({ params }: IProps) { + const { data: session } = useSession() + const user = session?.user as IUserSession + + const { walletAddress } = useWallet() + const { getAccountBalance, totalSupply } = useLoyaltyContract() + const [loading, setLoading] = useState(true) + + const [stats, setStats] = useState({ + accountBalance: "", + supply: "", + }) + + const query = qs.stringify( + { + fields: ["name", "location"], + populate: { + user: { + fields: ["username", "walletAddress"], + }, + brands: { + fields: ["name", "brandLogo"], + }, + }, + }, + { encodeValuesOnly: true } + ) + + const { data: seller } = useQuery( + ["seller", params.id], + () => + SellerService.getSellerByID( + params.id, + user?.token, + query + ), + { + enabled: user ? true : false, + } + ) + + useEffect(() => { + if (!walletAddress || !seller) return + ;(async () => { + try { + const [balance, supply] = await Promise.all([ + getAccountBalance( + seller.data.attributes.user.data.attributes.walletAddress + ), + totalSupply(), + ]) + + setStats({ + accountBalance: ethers.formatEther(`${balance}`), + supply: ethers.formatEther(supply.toString()), + }) + } catch (error: any) { + toast.error("Something went wrong") + } finally { + setLoading(false) + } + })() + }, [walletAddress, seller]) + + if (loading) + return ( +
+
+
+ ) + + return ( +
+
+

+ {seller?.data.attributes.name} +

+ +

{seller?.data.attributes.location}

+ +
+ + {seller?.data.attributes.user.data.attributes.username} +
+
+ +
+
+

Loyalty Program

+
+ +
+
+ +

{stats.accountBalance}

+ Number of tokens +
+ +
+ +

{stats.supply}

+ Total Supply +
+
+
+ +
+

Brands

+ +
+ + + + + + + + + {seller?.data.attributes.brands?.data.map((brand) => ( + + + + + ))} + +
+ Brand Name + + Logo +
+ {brand.attributes.name} + + +
+
+
+
+ ) +} diff --git a/frontend/app/admin/sellers/page.tsx b/frontend/app/admin/sellers/page.tsx new file mode 100644 index 00000000..71772a35 --- /dev/null +++ b/frontend/app/admin/sellers/page.tsx @@ -0,0 +1,150 @@ +"use client" + +import brand from "@/app/brand/page" +import Modal from "@/app/components/Modal" +import Button from "@/app/components/button/Button" +import useLoyaltyContract from "@/app/hooks/useLoyaltyContract" +import { ISellers } from "@/app/interfaces/ISellers" +import { IUserSession } from "@/app/interfaces/IUser" +import SellerService from "@/app/services/sellers.service" +import { useWallet } from "@/app/store/WalletStore" +import { useSession } from "next-auth/react" +import Link from "next/link" +import qs from "qs" +import React, { useState } from "react" +import toast from "react-hot-toast" +import { useQuery } from "react-query" + +export default function Sellers() { + const { data: session } = useSession() + const user = session?.user as IUserSession + + const { walletAddress } = useWallet() + const { addIssuer } = useLoyaltyContract() + const [loading, setLoading] = useState(false) + + const query = qs.stringify( + { + fields: ["name", "location"], + populate: { + user: { + fields: ["username", "walletAddress"], + }, + }, + }, + { encodeValuesOnly: true } + ) + + const { data: sellers, isLoading } = useQuery( + ["sellers"], + () => SellerService.getSellers(user.token, query), + { + enabled: user ? true : false, + } + ) + + const handleAddIssuer = async (address: string) => { + setLoading(true) + try { + await addIssuer(address, walletAddress) + toast.success("Issuer added successfully!") + } catch (err) { + toast.error("Something went wrong") + } finally { + setLoading(false) + } + } + + if (isLoading) + return ( +
+
+
+ ) + + return ( + <> +
+
+
+

Sellers

+

Manage sellers on platform

+
+ + + +
+ +
+
+ + + + + + + + + + + + {sellers?.data.map((seller) => ( + + + + + + + ))} + +
+ Seller Name + + Location + + Username +
+ {seller.attributes.name} + {seller.attributes.location} + {seller.attributes.user.data.attributes.username} + + + Edit + + +
+
+
+
+ + +
+

Adding issuer please wait...

+

Issuer will be added and fund will be transfered!

+ +
+
+
+
+
+ + ) +} diff --git a/frontend/app/admin/transfer/ISelectBrand.ts b/frontend/app/admin/transfer/ISelectBrand.ts new file mode 100644 index 00000000..9a81faf9 --- /dev/null +++ b/frontend/app/admin/transfer/ISelectBrand.ts @@ -0,0 +1,38 @@ +export interface ISelectBrand { + data: Daum[] + meta: Meta +} + +export interface Daum { + id: number + attributes: Attributes +} + +export interface Attributes { + name: string + user: User +} + +export interface User { + data: Data +} + +export interface Data { + id: number + attributes: Attributes2 +} + +export interface Attributes2 { + walletAddress: string +} + +export interface Meta { + pagination: Pagination +} + +export interface Pagination { + page: number + pageSize: number + pageCount: number + total: number +} diff --git a/frontend/app/admin/transfer/ISelectSeller.ts b/frontend/app/admin/transfer/ISelectSeller.ts new file mode 100644 index 00000000..de51cd8b --- /dev/null +++ b/frontend/app/admin/transfer/ISelectSeller.ts @@ -0,0 +1,38 @@ +export interface ISelectSeller { + data: Daum[] + meta: Meta +} + +export interface Daum { + id: number + attributes: Attributes +} + +export interface Attributes { + name: string + user: User +} + +export interface User { + data: Data +} + +export interface Data { + id: number + attributes: Attributes2 +} + +export interface Attributes2 { + walletAddress: string +} + +export interface Meta { + pagination: Pagination +} + +export interface Pagination { + page: number + pageSize: number + pageCount: number + total: number +} diff --git a/frontend/app/admin/transfer/page.tsx b/frontend/app/admin/transfer/page.tsx new file mode 100644 index 00000000..73bc6470 --- /dev/null +++ b/frontend/app/admin/transfer/page.tsx @@ -0,0 +1,133 @@ +"use client" + +import Button from "@/app/components/button/Button" +import Input from "@/app/components/input/Input" +import BrandService from "@/app/services/brand.service" +import SellerService from "@/app/services/sellers.service" +import qs from "qs" +import React, { useState } from "react" +import { ISelectBrand } from "./ISelectBrand" +import { ISelectSeller } from "./ISelectSeller" +import { useSession } from "next-auth/react" +import { IUserSession } from "@/app/interfaces/IUser" +import { useQuery } from "react-query" + +interface ISelectOptions { + id: number + name: string + walletAddress: string +} + +export default function TransferTokens() { + const { data: session } = useSession() + const user = session?.user as IUserSession + + const [options, setOptions] = useState([]) + const [selectedOption, setSelectedOption] = useState("") + + useQuery( + ["select-brands"], + () => + BrandService.getBrands( + user.token, + qs.stringify( + { + fields: ["name", "id"], + populate: { + user: { + fields: ["walletAddress"], + }, + }, + }, + { encodeValuesOnly: true } + ) + ), + { + onSuccess: ({ data }) => { + setOptions([ + ...options, + ...data.map((v) => ({ + id: v.id, + name: v.attributes.name, + walletAddress: v.attributes.user.data.attributes.walletAddress, + })), + ]) + }, + enabled: user ? true : false, + staleTime: Infinity, + } + ) + + useQuery( + ["select-sellers"], + () => + SellerService.getSellers( + user.token, + qs.stringify( + { + fields: ["name", "id"], + populate: { + user: { + fields: ["walletAddress"], + }, + }, + }, + { encodeValuesOnly: true } + ) + ), + { + onSuccess: ({ data }) => { + setOptions([ + ...options, + ...data.map((v) => ({ + id: v.id, + name: v.attributes.name, + walletAddress: v.attributes.user.data.attributes.walletAddress, + })), + ]) + }, + enabled: user ? true : false, + staleTime: Infinity, + } + ) + + return ( +
+

Transfer tokens

+

+ Tokens will be transfered to brand or sellers account, they will able to + use this token to give to the loyal users +

+ +
+
+ + +
+ +
+ +
+ + +
+
+ ) +} diff --git a/frontend/app/api/auth/[...nextauth]/route.ts b/frontend/app/api/auth/[...nextauth]/route.ts index 8e967799..28c27087 100644 --- a/frontend/app/api/auth/[...nextauth]/route.ts +++ b/frontend/app/api/auth/[...nextauth]/route.ts @@ -4,6 +4,7 @@ import { AxiosError } from "axios" import CredentialsProvider from "next-auth/providers/credentials" import NextAuth from "next-auth/next" import AuthService from "@/app/services/auth.service" +import qs from "qs" const authOptions: AuthOptions = { providers: [ @@ -23,16 +24,37 @@ const authOptions: AuthOptions = { password: credentials?.password, }) + const query = qs.stringify( + { + populate: { + role: { + fields: ["name", "type"], + }, + brandId: { + fields: ["id", "name"], + }, + seller: { + fields: ["id", "name"], + }, + }, + }, + { encodeValuesOnly: true } + ) + // find role of current user - const { data: roleData } = await api.get(`/users/me?populate=role`, { + const { data: roleData } = await api.get(`/users/me?${query}`, { headers: { Authorization: `Bearer ${res.data.jwt}`, }, }) - return { - email: res.data.user.email, + const data = { ...roleData, + jwt: res.data.jwt, + } + + return { + ...data, } } catch (err: any) { if (err instanceof AxiosError) { @@ -49,9 +71,17 @@ const authOptions: AuthOptions = { return Promise.resolve({ ...token, ...user }) }, session: ({ session, token, user }) => { - console.log(user) // @ts-ignore - session.user.data = token.user + session.user.data = { + id: token.id, + username: token.username, + role: token.role, + walletAddress: token.walletAddress, + addresses: token.addresses, + email: token.email!!, + brandId: token.brandId, + seller: token.seller, + } // @ts-ignore session.user.token = token.jwt @@ -62,7 +92,7 @@ const authOptions: AuthOptions = { signIn: "/login", }, secret: process.env.NEXTAUTH_SECRET, - debug: process.env.NODE_ENV === "production" ? false : true, + debug: true, session: { strategy: "jwt", }, diff --git a/frontend/app/brand/layout.tsx b/frontend/app/brand/layout.tsx new file mode 100644 index 00000000..f753b160 --- /dev/null +++ b/frontend/app/brand/layout.tsx @@ -0,0 +1,55 @@ +import React from "react" +import { AdminSidebar } from "../components/admin" +import { redirect } from "next/navigation" +import { getCurrentUser } from "../utils/auth.utils" +import { BsCoin, BsBox, BsTruck } from "react-icons/bs" +import { LiaSellsy } from "react-icons/lia" +import { CiDeliveryTruck } from "react-icons/ci" + +export default async function BrandLayout({ + children, +}: { + children: React.ReactNode +}) { + const session = await getCurrentUser() + + if (session?.data.role.name !== "Brand") redirect("/") + + const routes = [ + { + icon: , + title: "Loylty Program", + redirectURL: "/brand", + isHomePage: true, + optionalURL: "/brand/transfer", + }, + { + icon: , + title: "Products", + redirectURL: "/brand/products", + isHomePage: false, + optionalURL: undefined, + }, + { + icon: , + title: "Sellers", + redirectURL: "/brand/sellers", + isHomePage: false, + optionalURL: undefined, + }, + { + icon: , + title: "Orders", + redirectURL: "/brand/orders", + isHomePage: false, + optionalURL: undefined, + }, + ] + + return ( +
+ +
{children}
+
+ ) +} diff --git a/frontend/app/brand/orders/page.tsx b/frontend/app/brand/orders/page.tsx new file mode 100644 index 00000000..3df732fe --- /dev/null +++ b/frontend/app/brand/orders/page.tsx @@ -0,0 +1,142 @@ +"use client" + +import LoyalUserToken from "@/app/components/LoyalUserToken" +import Modal from "@/app/components/Modal" +import Button from "@/app/components/button/Button" +import { IBrandsOrders } from "@/app/interfaces/IBrandsOrders" +import { ISellerOrders } from "@/app/interfaces/ISellerOrder" +import { IUserSession } from "@/app/interfaces/IUser" +import OrderService from "@/app/services/order.service" +import { useSession } from "next-auth/react" +import qs from "qs" +import { useState } from "react" +import { useQuery } from "react-query" + +export default function Orders() { + const { data: session } = useSession() + const user = session?.user as IUserSession + + const [walletAddress, setWalletAddress] = useState("") + + const query = qs.stringify( + { + filters: { + order_items: { + brandId: { + id: { + $eq: user?.data.brandId?.id, + }, + }, + }, + }, + fields: ["totalAmount", "numberOfTokens"], + populate: { + userId: { + fields: ["walletAddress", "username"], + }, + }, + }, + { encodeValuesOnly: true } + ) + + const { data: orders, isLoading } = useQuery( + ["brand-orders"], + () => OrderService.getOrders(user.token, query), + { + enabled: user !== undefined ? true : false, + } + ) + + if (isLoading) + return ( +
+
+
+ ) + + return ( + <> +
+
+
+

Orders

+

Manage the orders

+
+
+ +
+
+ + + + + + + + + + + + + {orders?.data?.map((order) => ( + + + + + + + + + + + + ))} + +
+ Order ID + + Total amounts + + Number of tokens + + Username +
+ {order?.id} + + ₹ {order?.attributes.totalAmount} + + {order?.attributes?.numberOfTokens} + + {order?.attributes?.userId?.data.attributes.username} + + +
+
+
+
+ + + setWalletAddress("")} + /> + + + ) +} diff --git a/frontend/app/brand/page.tsx b/frontend/app/brand/page.tsx new file mode 100644 index 00000000..8c18c0d1 --- /dev/null +++ b/frontend/app/brand/page.tsx @@ -0,0 +1,157 @@ +"use client" + +import React, { useEffect, useState } from "react" +import { FaCoins, FaBitcoin } from "react-icons/fa" +import useLoyaltyContract from "../hooks/useLoyaltyContract" +import { useWallet } from "../store/WalletStore" +import { ethers } from "ethers" +import toast from "react-hot-toast" +import AdminCard from "../components/admin/AdminCard" +import moment from "moment" +import { formatWalletAddress } from "../utils/formatWalletAddress" + +interface ILoyaltyTokenHistory { + issuer: string + user: string + tokens: string + timestamp: string + hash: string +} + +export default function Brand() { + const { getAccountBalance, totalSupply, getLoyalUserTokenHistory } = + useLoyaltyContract() + const [loading, setLoding] = useState(true) + const { walletAddress } = useWallet() + + const [loyalTokensHistory, setLoyalTokensHistory] = useState< + ILoyaltyTokenHistory[] + >([]) + + const [stats, setStats] = useState({ + balance: "", + supply: "", + }) + + useEffect(() => { + if (!walletAddress) return + ;(async () => { + try { + const [balance, supply, loyalUserTokensHistory] = await Promise.all([ + getAccountBalance(walletAddress), + totalSupply(), + getLoyalUserTokenHistory(), + ]) + + setStats({ + balance: ethers.formatEther(balance), + supply: ethers.formatEther(supply), + }) + + const transactions = loyalUserTokensHistory.filter( + (item) => item.args[0].toLowerCase() === walletAddress + ) + + setLoyalTokensHistory( + transactions.map(({ args, blockHash }) => { + return { + issuer: args[0], + user: args[1], + tokens: ethers.formatEther(args[2].toString()), + timestamp: args[3].toString(), + hash: blockHash, + } + }) + ) + } catch (err: any) { + toast.error("Someting went wrong please try again!") + } finally { + setLoding(false) + } + })() + }, [walletAddress]) + + if (loading) + return ( +
+
+
+ ) + + return ( +
+
+

Loyalty Program

+

Manage the activity of the loyalty program

+
+ +
+ } + title="Number of tokens" + value={stats.balance} + /> + + } + title="Total Supply" + value={stats.supply} + /> +
+ +
+

+ Tokens Trasfered To Loyal User Transactions +

+ +
+ + + + + + + + + + + + {loyalTokensHistory.length === 0 ? ( + + ) : ( + loyalTokensHistory.map( + ({ timestamp, hash, issuer, tokens, user }) => ( + + + + + + + ) + ) + )} + +
+ Transaction Hash + + User + + Tokens + + Timestamp +
No previous transactions found
+ {formatWalletAddress(hash)} + {formatWalletAddress(user)}{tokens} FC + {moment.unix(+timestamp).format("DD MMMM YYYY")} +
+
+
+
+ ) +} diff --git a/frontend/app/brand/products/page.tsx b/frontend/app/brand/products/page.tsx new file mode 100644 index 00000000..d2122368 --- /dev/null +++ b/frontend/app/brand/products/page.tsx @@ -0,0 +1,116 @@ +"use client" + +import Button from "@/app/components/button/Button" +import { api } from "@/app/config/axios" +import { IBrandProducts } from "@/app/interfaces/IBrandProducts" +import { IUserSession } from "@/app/interfaces/IUser" +import ProductService from "@/app/services/product.service" +import { useSession } from "next-auth/react" +import Link from "next/link" +import qs from "qs" +import { useQuery } from "react-query" + +export default function Products() { + const { data: session } = useSession() + const user = session?.user as IUserSession + + const query = qs.stringify( + { + fields: ["name", "price", "discount"], + populate: { + user: { + fields: ["username"], + }, + }, + filters: { + brandId: { + id: { + $eq: user?.data.brandId?.id, + }, + }, + }, + }, + { encodeValuesOnly: true } + ) + + const { data: products, isLoading } = useQuery( + ["brand-products", user?.data.brandId?.id], + () => ProductService.getProducts(user.token, query), + { + enabled: user?.data.brandId !== undefined ? true : false, + } + ) + + if (isLoading) + return ( +
+
+
+ ) + + return ( +
+
+
+

Products

+

Create and manage products

+
+ + + +
+ +
+
+ + + + + + + + + + + {products?.data.map((product,index) => ( + + + + + + + ))} + +
+ Product Name + + Price + + Discount +
+ {product.attributes.name} + {product.attributes.price} + {" "} + {product.attributes.discount || 0} + + + Edit + + + Remove + +
+
+
+
+ ) +} diff --git a/frontend/app/brand/sellers/page.tsx b/frontend/app/brand/sellers/page.tsx new file mode 100644 index 00000000..d57fcbef --- /dev/null +++ b/frontend/app/brand/sellers/page.tsx @@ -0,0 +1,118 @@ +"use client" + +import Button from "@/app/components/button/Button" +import { ISellers } from "@/app/interfaces/ISellers" +import { IUserSession } from "@/app/interfaces/IUser" +import SellerService from "@/app/services/sellers.service" +import { useSession } from "next-auth/react" +import Link from "next/link" +import qs from "qs" +import { useQuery } from "react-query" + +export default function Seller() { + const { data: session } = useSession() + const user = session?.user as IUserSession + + const query = qs.stringify( + { + fields: ["name", "location"], + populate: { + user: { + fields: ["username"], + }, + }, + filters: { + brands: { + id: { + $in: user?.data.brandId?.id, + }, + }, + }, + }, + { encodeValuesOnly: true } + ) + + const { data: sellers, isLoading } = useQuery( + ["brand-sellers"], + () => SellerService.getSellers(user.token, query), + { + enabled: user !== undefined ? true : false, + } + ) + + if (isLoading) + return ( +
+
+
+ ) + + return ( +
+
+
+

Seller

+

Manage the sellers

+
+ + + +
+ +
+
+ + + + + + + + + + + + {sellers?.data?.map((seller) => ( + + + + + + + ))} + +
+ Seller Name + + Location + + Username +
+ {seller.attributes.name} + {seller.attributes.location} + {seller?.attributes?.user?.data.attributes.username} + + + Edit + + + Remove + +
+
+
+
+ ) +} diff --git a/frontend/app/cart/page.tsx b/frontend/app/cart/page.tsx new file mode 100644 index 00000000..7d79e375 --- /dev/null +++ b/frontend/app/cart/page.tsx @@ -0,0 +1,261 @@ +"use client" + +import React, { useState } from "react" +import Image from "next/image" +import Wrapper from "../components/Wrapper" +import Link from "next/link" +import { useCartStore } from "../store/CartStore" +import { RiDeleteBin6Line } from "react-icons/ri" +import { IUserSession } from "../interfaces/IUser" +import { useSession } from "next-auth/react" +import { toast } from "react-hot-toast" +import useLoyaltyContract from "../hooks/useLoyaltyContract" +import Button from "../components/button/Button" +import { useMutation } from "react-query" +import OrderService from "../services/order.service" +import Modal from "../components/Modal" +import { useRouter } from "next/navigation" + +const Cart = () => { + const { data: session } = useSession() + const user = session?.user as IUserSession + + const router = useRouter() + + const { getTokenOnOrder } = useLoyaltyContract() + const [loading, setLoading] = useState(false) + const [numberOfTokens, setNumberOfTokens] = useState(0) + + const { + incrementQuantity, + decrementQuantity, + total: subTotal, + removeFromCart, + cartItems, + clearCart, + numberOfTokens: wonedNumberOfTokens, + } = useCartStore() + + const transferTokensToUserAccount = async (numberOfTokens: number) => { + try { + await getTokenOnOrder(user.data.walletAddress, numberOfTokens) + } catch (e: any) { + toast.error("Couldn't transfer tokens to your account") + } + } + + const { mutateAsync: createOrder } = useMutation((data: any) => + OrderService.createOrder(user.token, data) + ) + + const { mutateAsync: createOrderItem } = useMutation((data: any) => + OrderService.createOrderItem(user.token, data) + ) + + const handleOrder = async () => { + setLoading(true) + try { + const tokens = (subTotal * 0.5) / 100 + setNumberOfTokens(tokens) + + const order = await createOrder({ + data: { + userId: user.data.id, + totalAmount: subTotal, + numberOfTokens: parseInt(`${tokens}`), + }, + }) + + const orderItems = cartItems.map((item) => { + return createOrderItem({ + data: { + orderId: order.data.id, + productId: item.productId, + sellerId: item.sellerId, + brandId: item.brandId, + quantity: item.quantity, + }, + }) + }) + + await Promise.all([...orderItems, transferTokensToUserAccount(tokens)]) + + clearCart() + + router.push("/orders") + toast.success("Order placed successfully!!") + } catch (err: any) { + toast.error("Someting unexpected error occured please try again") + } finally { + setLoading(false) + } + } + + return ( + <> +
+ <> +
+
Shopping Cart
+
+ +
+
+ {cartItems?.map((item, index) => { + return ( +
+
+
+ {item.images && ( +
+ +
+ )} +
+ +
+
+
+ {item.name} +
+ +
+ ₹{item.price} +
+
+ +
+ removeFromCart(item.productId)} + className="cursor-pointer text-black/[0.5] hover:text-black text-[16px] md:text-[20px]" + /> +
+ +
+
+
+
Quantity:
+
+ + {item.quantity} + +
+
+
+
+
+
+
+ ) + })} +
+ + {cartItems.length !== 0 && ( +
+
Summary
+ +
+
+
+ Tokens reward +
+
+ : {wonedNumberOfTokens} FC +
+
+
+
+ Number of products +
+
+ : {cartItems.length} +
+
+
+
+ Subtotal +
+
+ : ₹{subTotal} +
+
+ {user ? ( + + ) : ( + + + + )} + + * You can won upto 100 FC token on minimum order value of Rs + 1000, this tokens can be used to earn rewards later + +
+
+ )} + {/* Summary */} +
+ +
+ {cartItems.length === 0 && ( +
+

Your cart is empty

+

+ Looks like you have not anything added anything to your cart.{" "} +
+ Go ahead and explore! +

+
+ )} + + + +
+ +
+ + +
+

Processing your order...

+

+ Congratulations you won{" "} + {wonedNumberOfTokens} FC reward + coins. Please confirm the meta mask transaction to awail rewards +

+
+
+
+ + ) +} + +export default Cart diff --git a/frontend/app/components/BrandCard.tsx b/frontend/app/components/BrandCard.tsx new file mode 100644 index 00000000..6cff3221 --- /dev/null +++ b/frontend/app/components/BrandCard.tsx @@ -0,0 +1,70 @@ +import React from "react" +import Image from "next/image" +import { Poppins } from "next/font/google" + +const brands = [ + { + name: "H&M", + logo: "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514023/Metamarket/HomePageUI/b1_r0jhxc.png", + }, + { + name: "Calvin Klein", + logo: "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514023/Metamarket/HomePageUI/b2_ytvl6l.png", + }, + { + name: "Nike", + logo: "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514023/Metamarket/HomePageUI/b3_znyfm8.png", + }, + { + name: "Champion", + logo: "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514023/Metamarket/HomePageUI/b4_nkp7tb.png", + }, + { + name: "Sapphire", + logo: "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514023/Metamarket/HomePageUI/b5_egxd9i.png", + }, + { + name: "Levi's", + logo: "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514024/Metamarket/HomePageUI/b6_msucys.png", + }, + { + name: "Adidas", + logo: "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514024/Metamarket/HomePageUI/b7_vmwagq.png", + }, + { + name: "Nautica", + logo: "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514024/Metamarket/HomePageUI/b8_lmadv5.png", + }, + { + name: "Puma", + logo: "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514024/Metamarket/HomePageUI/b9_rsrpsz.png", + }, +]; + +const BrandCard = () => { + return ( +
+
+ {brands.map((brand, index) => { + return ( +
+ brandlogo +
+ ) + })} +
+
+ ) +} + +export default BrandCard diff --git a/frontend/app/components/Carasoul.tsx b/frontend/app/components/Carasoul.tsx new file mode 100644 index 00000000..3b03a8c4 --- /dev/null +++ b/frontend/app/components/Carasoul.tsx @@ -0,0 +1,77 @@ +"use client" +import Image from "next/image" +import { useState, useEffect } from "react" +import Swipe from "react-easy-swipe" +import { AiOutlineLeft, AiOutlineRight } from "react-icons/ai" + +const images = [ + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514040/Metamarket/HomePageUI/img4_pjng7f.jpg", + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514037/Metamarket/HomePageUI/img3_isyrte.jpg", + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514037/Metamarket/HomePageUI/img2_v6pocq.jpg", + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514034/Metamarket/HomePageUI/img1_hbpxfr.jpg", +]; + +export default function Carousel() { + const [currentSlide, setCurrentSlide] = useState(0) + const handleNextSlide = () => { + let newSlide = currentSlide === images.length - 1 ? 0 : currentSlide + 1 + setCurrentSlide(newSlide) + } + + const handlePrevSlide = () => { + const newSlide = (currentSlide + 1) % images.length + setCurrentSlide(newSlide) + } + + return ( +
+ + +
+ + {images.map((image, index) => { + if (index === currentSlide) { + return ( +
+ image not present +
{ + setCurrentSlide(index) + }} + /> +
+ ) + } + })} + +
+
+ ) +} diff --git a/frontend/app/components/CartItem.tsx b/frontend/app/components/CartItem.tsx new file mode 100644 index 00000000..bb4682df --- /dev/null +++ b/frontend/app/components/CartItem.tsx @@ -0,0 +1,75 @@ +import React from "react"; +import { RiDeleteBin6Line } from "react-icons/ri"; +import { useState } from "react"; + +const CartItem = () => { + const [quantity, setQuantity] = useState(1); + + const handleIncrement = () => { + // Increase the quantity, but not beyond a maximum value (if needed) + setQuantity(Math.min(quantity + 1, 999)); // Adjust 999 to your desired maximum value + }; + + const handleDecrement = () => { + // Decrease the quantity, but not below the minimum value (1) + setQuantity(Math.max(quantity - 1, 1)); + }; + return ( +
+
+ +
+ +
+
+
+ Jorden retro 5g +
+ +
+ Men&apos s Golf shoes +
+
+ $2000 +
+
+ +
+
+ brandLogo and Name +
+
+ Seller info +
+ + +
+ +
+
+
+
Quantity:
+
+ + {quantity} + +
+
+
+
+
+
+ ); +}; + +export default CartItem; diff --git a/frontend/app/components/CategoryCard.tsx b/frontend/app/components/CategoryCard.tsx new file mode 100644 index 00000000..c087f30e --- /dev/null +++ b/frontend/app/components/CategoryCard.tsx @@ -0,0 +1,85 @@ +import React from "react" +import Image from "next/image" + +const category = [ + { + name: "Fashion", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514028/Metamarket/HomePageUI/c1_peymex.jpg", + isDark: false, + }, + { + name: "Smartphones", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514033/Metamarket/HomePageUI/c2_ryz2et.jpg", + isDark: true, + }, + { + name: "Electronics", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514037/Metamarket/HomePageUI/c3_nvxsqb.jpg", + isDark: true, + }, + { + name: "Grocery", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514034/Metamarket/HomePageUI/c4_j8mo8a.jpg", + isDark: false, + }, + { + name: "Home & Furniture", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514030/Metamarket/HomePageUI/c6_m2gpsj.jpg", + isDark: true, + }, + { + name: "Clothing", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514037/Metamarket/HomePageUI/c7_zvnwha.jpg", + isDark: true, + }, + { + name: "Books", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514033/Metamarket/HomePageUI/c8_f1qmbt.jpg", + isDark: true, + }, + { + name: "Smartphones", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514042/Metamarket/HomePageUI/c9_dcjodj.jpg", + isDark: true, + }, +]; + +const CategoryCard = () => { + return ( +
+

Shop by category

+
+ {category.map((cat, index) => { + return ( +
+
+ brandlogo + +

+ {cat.name} +

+
+
+ ) + })} +
+
+ ) +} + +export default CategoryCard diff --git a/frontend/app/components/ConnectToMetamaskModal.tsx b/frontend/app/components/ConnectToMetamaskModal.tsx new file mode 100644 index 00000000..6ef08e33 --- /dev/null +++ b/frontend/app/components/ConnectToMetamaskModal.tsx @@ -0,0 +1,24 @@ +import Image from "next/image" +import React from "react" +import Button from "./button/Button" +import { useWallet } from "../store/WalletStore" + +export default function ConnectToMetamaskModal() { + const { connectWallet, isConnected, walletAddress } = useWallet() + + return ( +
+

Let's connect your metamask

+

+ To get access of rewards and purchase product through the tokens you + will require to connect your meta mask +

+ + + + +
+ ) +} diff --git a/frontend/app/components/ImageSlideshow.tsx b/frontend/app/components/ImageSlideshow.tsx new file mode 100644 index 00000000..a9eb4323 --- /dev/null +++ b/frontend/app/components/ImageSlideshow.tsx @@ -0,0 +1,38 @@ +import React, { useState, useEffect } from "react"; +import Image from "next/image"; + +type ImageSlideshowProps = { + images: Array +} + +const ImageSlideshow:React.FC = ({ images }) => { + const [currentImage, setCurrentImage] = useState(0); + + useEffect(() => { + const interval = setInterval(() => { + setCurrentImage((prevImage) => (prevImage + 1) % images.length); + }, 3000); // Change image every 3 seconds + + return () => clearInterval(interval); + }, [images.length]); + + return ( +
+ {images.map((image, index) => ( +
+ {`Slideshow +
+ ))} +
+ ); +}; + +export default ImageSlideshow; diff --git a/frontend/app/components/Loading.tsx b/frontend/app/components/Loading.tsx new file mode 100644 index 00000000..c530a234 --- /dev/null +++ b/frontend/app/components/Loading.tsx @@ -0,0 +1,19 @@ +import React from "react"; + +const Loading = () => { + return ( +
+

Loading...

+ {/* You can add a loading spinner or animation here */} +
+ ); +}; + +export default Loading; diff --git a/frontend/app/components/LoyalUserToken.tsx b/frontend/app/components/LoyalUserToken.tsx new file mode 100644 index 00000000..af0be0e6 --- /dev/null +++ b/frontend/app/components/LoyalUserToken.tsx @@ -0,0 +1,69 @@ +"use client" + +import { useState, FormEvent } from "react" +import toast from "react-hot-toast" +import useLoyaltyContract from "../hooks/useLoyaltyContract" +import Button from "./button/Button" +import Input from "./input/Input" + +interface IProps { + onClose: () => void + walletAddress: string +} + +export default function LoyalUserToken({ onClose, walletAddress }: IProps) { + const { issueTokenToLoaylUser, approveTokens } = useLoyaltyContract() + const [amount, setAmount] = useState("") + const [loading, setLoading] = useState(false) + + const send = async (e: FormEvent) => { + e.preventDefault() + try { + setLoading(true) + const tx = await approveTokens(+amount) + await tx.wait() + await issueTokenToLoaylUser(amount, walletAddress) + toast.success("Issued token to user succesfully!") + onClose() + } catch (e: any) { + toast.error("Enable to transfer token to user please try again!") + } finally { + setLoading(false) + } + } + + return ( +
+

Send tokens to loyal users

+

+ You can send the tokens to the loyal users that frequently purchase from + you brands +

+ +
+
+ +
+
+ setAmount(e.target.value)} + title="Number of token" + /> +
+
+ + +
+
+
+ ) +} diff --git a/frontend/app/components/Modal.tsx b/frontend/app/components/Modal.tsx new file mode 100644 index 00000000..df1b421a --- /dev/null +++ b/frontend/app/components/Modal.tsx @@ -0,0 +1,24 @@ +import { ReactNode } from "react" +import tw from "../utils/tw" + +interface IProps { + children: ReactNode + open: boolean +} + +export default function Modal({ children, open }: IProps) { + return ( +
+
+ {children} +
+
+ ) +} diff --git a/frontend/app/components/Navbar.tsx b/frontend/app/components/Navbar.tsx index ae6b93f9..5c4d3a8f 100644 --- a/frontend/app/components/Navbar.tsx +++ b/frontend/app/components/Navbar.tsx @@ -1,71 +1,124 @@ "use client"; import React, { useState } from "react"; +import { FiShoppingCart } from "react-icons/fi"; +import { FaUserAlt } from "react-icons/fa"; +import { signOut, useSession } from "next-auth/react"; +import { IUserSession } from "../interfaces/IUser"; +import Link from "next/link"; +import { toast } from "react-hot-toast"; +import { useRouter } from "next/navigation"; +import { GoSearch } from "react-icons/go"; +import { CiShoppingCart, CiUser } from "react-icons/ci"; +import Button from "./button/Button"; +import { useCartStore } from "../store/CartStore"; +import { Poppins } from "next/font/google"; + +const poppins = Poppins({ + subsets: ["latin"], + weight: "400", +}); const Navbar = () => { - const [isLoggedIn, setIsLoggedIn] = useState(false); + const { data } = useSession(); + const user = data?.user as IUserSession; + + const router = useRouter(); + const [showProfileDropdown, setShowProfileDropdown] = useState(false); + const { cartItems } = useCartStore(); const toggleProfileDropdown = () => { setShowProfileDropdown(!showProfileDropdown); }; + const logout = async () => { + try { + await signOut(); + toast.success("Logged out successfully!"); + router.push("/"); + } catch (e) { + toast.error("Something went wrong while logging you out!"); + } + }; + return ( -
+
); }; diff --git a/frontend/app/components/Orders.tsx b/frontend/app/components/Orders.tsx new file mode 100644 index 00000000..4ce71b86 --- /dev/null +++ b/frontend/app/components/Orders.tsx @@ -0,0 +1,65 @@ +"use client"; +import React from "react"; +import { useRouter } from "next/navigation"; + +const items = [ + { + name: "Jorden retro 5g", + category: "Men's Golf shoes", + orderedAt: "July 15, 2023", + deliveredAt: "July 20, 2023", + imageSrc: "/img1.png", + } +]; + +const Orders = () => { + const router = useRouter(); + + const buyagain = async () => { + router.push("/singleProduct"); + }; + return ( +
+ {items.map((item, index) => ( +
+
+
+
+ {item.name} +
+
+
+ {item.name} +
+
+ {item.category} +
+ {/* "Ordered at" and "Delivered at" text moved below */} +
+ Ordered at: {item.orderedAt} +
+
+ Delivered at: {item.deliveredAt} +
+
+
+
+ {/* Buy Again button */} + +
+
+
+ ))} +
+ ); +}; + +export default Orders; diff --git a/frontend/app/components/ProductDetailCarousel.tsx b/frontend/app/components/ProductDetailCarousel.tsx new file mode 100644 index 00000000..985181a7 --- /dev/null +++ b/frontend/app/components/ProductDetailCarousel.tsx @@ -0,0 +1,40 @@ +"use client" +import Image from "next/image" +import React from "react" +import { Carousel } from "react-responsive-carousel" +import "react-responsive-carousel/lib/styles/carousel.min.css" + +type ProductDetailCarouselProps = { + images: Array +} + +const ProductDetailCarousel: React.FC = ({ + images, +}) => { + return ( +
+ + {images.map((image, index) => { + return ( +
+ +
+ ) + })} +
+
+ ) +} + +export default ProductDetailCarousel diff --git a/frontend/app/components/ProductList.tsx b/frontend/app/components/ProductList.tsx new file mode 100644 index 00000000..fc4699c9 --- /dev/null +++ b/frontend/app/components/ProductList.tsx @@ -0,0 +1,161 @@ +import React from "react" +import Image from "next/image" +import { MdOutlineKeyboardArrowRight } from "react-icons/md" +import Link from "next/link" + +const beautyProducts = [ + { + tag: "Eye Makeup", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514027/Metamarket/HomePageUI/bt5_ep9kk3.jpg", + }, + { + tag: "Face Makeup", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514028/Metamarket/HomePageUI/bt3_alktal.jpg", + }, + { + tag: "Hair Care", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514026/Metamarket/HomePageUI/bt4_wzh2vj.jpg", + }, +]; + +const clothProducts = [ + { + tag: "T-Shirts", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514043/Metamarket/HomePageUI/j1_wz6otm.jpg", + }, + { + tag: "Tinted Jeans", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514040/Metamarket/HomePageUI/j2_dojy1l.jpg", + }, + { + tag: "Cargos", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514041/Metamarket/HomePageUI/j3_jpbwlz.jpg", + }, + { + tag: "Trousers", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514043/Metamarket/HomePageUI/j4_sme50g.jpg", + }, +]; + +const shoeProducts = [ + { + tag: "Sneakers", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514044/Metamarket/HomePageUI/s1_s2l4gn.jpg", + }, + { + tag: "Sports", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514043/Metamarket/HomePageUI/s2_oujfqo.jpg ", + }, + { + tag: "Formal", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514049/Metamarket/HomePageUI/s3_sjb9wh.jpg", + }, + { + tag: "Casual", + image: + "https://res.cloudinary.com/dq4vpg3fh/image/upload/v1692514045/Metamarket/HomePageUI/s4_e7umoe.jpg", + }, +]; + +const ProductList = () => { + return ( +
+
+

Clothing

+ +
+ {clothProducts.map((cat, index) => { + return ( +
+
+ brandlogo + +

+ {cat.tag} +

+
+
+ ) + })} +
+
+ +
+

Shoes

+ +
+ {shoeProducts.map((cat, index) => { + return ( +
+
+ brandlogo + +

+ {cat.tag} +

+
+
+ ) + })} +
+
+ +

Beauty Products

+
+ {beautyProducts.map((cat, index) => { + return ( +
+
+ brandlogo + +

+ {cat.tag} +

+
+
+ ) + })} +
+ + +
+

Explore Products

+ +
+ +
+ ) +} + +export default ProductList diff --git a/frontend/app/components/Wrapper.tsx b/frontend/app/components/Wrapper.tsx new file mode 100644 index 00000000..4bef3daf --- /dev/null +++ b/frontend/app/components/Wrapper.tsx @@ -0,0 +1,24 @@ +import React from "react"; +// interface Props{ +// children: ReactNode +// } + +type WrapperProps = { + children: React.ReactNode; + className: string; +}; + +const Wrapper: React.FC = ({ children, className }) => { + return ( +
+ {children} +
+ ); +}; + +export default Wrapper; +`` \ No newline at end of file diff --git a/frontend/app/components/admin/AdminCard.tsx b/frontend/app/components/admin/AdminCard.tsx new file mode 100644 index 00000000..0b07bf53 --- /dev/null +++ b/frontend/app/components/admin/AdminCard.tsx @@ -0,0 +1,82 @@ +import { ReactElement, useState } from "react" +import { LuEdit2 } from "react-icons/lu" +import Modal from "../Modal" +import Input from "../input/Input" +import Button from "../button/Button" +import { toast } from "react-hot-toast" + +interface IProps { + title: string + value: string + icon: ReactElement + isEditable?: boolean + modalTitle?: string + modalDescription?: string + updateFunction?: (value: string) => Promise +} + +export default function AdminCard({ + title, + value, + icon, + isEditable, + modalDescription, + modalTitle, + updateFunction, +}: IProps) { + const [open, setOpen] = useState(false) + const [loading, setLoading] = useState(false) + const [updatedValue, setUpdatedValue] = useState(value) + + const onClick = async () => { + if (!updatedValue || !updateFunction) return + setLoading(true) + try { + await updateFunction(updatedValue) + setOpen(false) + } catch (e: any) { + toast.error("Something went wrong please try again!") + } finally { + setLoading(false) + } + } + + return ( + <> +
+ {isEditable && ( +
+ setOpen(true)} className="cursor-pointer" /> +
+ )} + +
{icon}
+

{value}

+ {title} +
+ + +
+

{modalTitle}

+

{modalDescription}

+ setUpdatedValue(e.target.value)} + /> +
+ + +
+
+
+ + ) +} diff --git a/frontend/app/components/admin/index.ts b/frontend/app/components/admin/index.ts new file mode 100644 index 00000000..78725c23 --- /dev/null +++ b/frontend/app/components/admin/index.ts @@ -0,0 +1,3 @@ +import AdminSidebar from "./sidebar/AdminSidebar" + +export { AdminSidebar } diff --git a/frontend/app/components/admin/sidebar/AdminSidebar.tsx b/frontend/app/components/admin/sidebar/AdminSidebar.tsx new file mode 100644 index 00000000..b7c0ee62 --- /dev/null +++ b/frontend/app/components/admin/sidebar/AdminSidebar.tsx @@ -0,0 +1,60 @@ +"use client" + +import React from "react" +import { LuStore } from "react-icons/lu" +import { LiaSellsy } from "react-icons/lia" +import Link from "next/link" +import { usePathname } from "next/navigation" +import { BsCoin } from "react-icons/bs" +import { useSession } from "next-auth/react" +import { IUserSession } from "@/app/interfaces/IUser" + +interface IProps { + routes: { + icon: React.JSX.Element + title: string + redirectURL: string + isHomePage: boolean + optionalURL?: string + }[] +} + +export default function AdminSidebar({ routes }: IProps) { + const { data: session } = useSession() + const user = session?.user as IUserSession + + const usePath = usePathname() + + const isActive = ( + path: string, + isHomePage: boolean = false, + optionalURL?: string + ) => { + if (isHomePage) + return usePath === path || (optionalURL && usePath.includes(optionalURL)) + return ( + usePath.includes(path) || (optionalURL && usePath.includes(optionalURL)) + ) + } + + return ( +
+
    + {routes.map((route, key) => ( + +
  • +
    {route.icon}
    +

    {route.title}

    +
  • + + ))} +
+
+ ) +} diff --git a/frontend/app/components/button/Button.tsx b/frontend/app/components/button/Button.tsx index 7768cb5e..fb071389 100644 --- a/frontend/app/components/button/Button.tsx +++ b/frontend/app/components/button/Button.tsx @@ -1,3 +1,4 @@ +import tw from "@/app/utils/tw" import React from "react" interface IProps @@ -9,14 +10,26 @@ interface IProps loading?: boolean } -export default function Button({ children, loading, ...props }: IProps) { +export default function Button({ + children, + className, + loading, + ...props +}: IProps) { return ( ) } diff --git a/frontend/app/components/input/Input.tsx b/frontend/app/components/input/Input.tsx index f13d913f..d146d594 100644 --- a/frontend/app/components/input/Input.tsx +++ b/frontend/app/components/input/Input.tsx @@ -7,18 +7,29 @@ interface IProps > { title: string error?: string + isMultiline?: boolean } -export default function Input({ title, error, ...props }: IProps) { +export default function Input({ title, error, isMultiline, ...props }: IProps) { return (
- + {isMultiline ? ( +