diff --git a/next.config.mjs b/next.config.mjs index 8c8bab67b..d948ed61e 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,6 +1,17 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - output: "standalone", + output: 'standalone', + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 'lh3.googleusercontent.com', + port: '', + pathname: '/**', + }, + ], + }, + transpilePackages: ["lucide-react"], }; -export default nextConfig; +export default nextConfig; \ No newline at end of file diff --git a/package.json b/package.json index 13cd018d8..d1788c73d 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cookies-next": "^4.2.1", + "date-fns": "^3.6.0", "framer-motion": "^11.3.8", "input-otp": "^1.2.4", "jest-axe": "^9.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b10f461a..78477e3b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,9 +83,12 @@ importers: cookies-next: specifier: ^4.2.1 version: 4.2.1 + date-fns: + specifier: ^3.6.0 + version: 3.6.0 framer-motion: specifier: ^11.3.8 - version: 11.3.17(@emotion/is-prop-valid@0.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 11.3.19(@emotion/is-prop-valid@0.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) input-otp: specifier: ^1.2.4 version: 1.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -3232,8 +3235,8 @@ packages: react-dom: optional: true - framer-motion@11.3.17: - resolution: {integrity: sha512-LZcckvZL8Rjod03bud8LQcp+R0PLmWIlOSu+NVc+v6Uh43fQr4IBsEAX7sSn7CdBQ1L0fZ/IqSXZVPnGFSMxHw==} + framer-motion@11.3.19: + resolution: {integrity: sha512-+luuQdx4AsamyMcvzW7jUAJYIKvQs1KE7oHvKkW3eNzmo0S+3PSDWjBuQkuIP9WyneGnKGMLUSuHs8OP7jKpQg==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 @@ -8695,7 +8698,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - framer-motion@11.3.17(@emotion/is-prop-valid@0.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + framer-motion@11.3.19(@emotion/is-prop-valid@0.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: tslib: 2.6.3 optionalDependencies: diff --git a/public/admin-dashboard/icons/arrowUp.svg b/public/admin-dashboard/icons/arrowUp.svg deleted file mode 100644 index 8d1856ede..000000000 --- a/public/admin-dashboard/icons/arrowUp.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/public/admin-dashboard/icons/box.svg b/public/admin-dashboard/icons/box.svg deleted file mode 100644 index a07b9e2de..000000000 --- a/public/admin-dashboard/icons/box.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/public/admin-dashboard/icons/dollarSign.svg b/public/admin-dashboard/icons/dollarSign.svg deleted file mode 100644 index a6c75e629..000000000 --- a/public/admin-dashboard/icons/dollarSign.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/public/admin-dashboard/icons/user.svg b/public/admin-dashboard/icons/user.svg deleted file mode 100644 index 5a3075414..000000000 --- a/public/admin-dashboard/icons/user.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/app/dashboard/(admin)/_components/layout/navbar/index.tsx b/src/app/dashboard/(admin)/_components/layout/navbar/index.tsx index 2b46a6860..3b6f810b7 100644 --- a/src/app/dashboard/(admin)/_components/layout/navbar/index.tsx +++ b/src/app/dashboard/(admin)/_components/layout/navbar/index.tsx @@ -1,6 +1,11 @@ +"use client"; + import { BellIcon, ChevronDown, HelpCircle, SearchIcon } from "lucide-react"; +import { useSession } from "next-auth/react"; +import { useRouter } from "next/navigation"; +import { useEffect } from "react"; -import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar"; +import UserCard from "~/components/card/user-card"; import { Popover, PopoverContent, @@ -8,7 +13,21 @@ import { } from "~/components/ui/popover"; import UnreadNotificationCard from "../../unread-notification-card/UnreadNotificationCard"; +interface User { + email: string; + image: string; + name: string; +} + const DashboardNavbar = () => { + const { data: session, status } = useSession(); + const router = useRouter(); + useEffect(() => { + if (status === "unauthenticated") { + router.push("/login"); + } + }, [status, router]); + return ( + ); +} diff --git a/src/app/dashboard/(user-dashboard)/_components/overview-chart/index.tsx b/src/app/dashboard/(user-dashboard)/_components/overview-chart/index.tsx new file mode 100644 index 000000000..fa370d191 --- /dev/null +++ b/src/app/dashboard/(user-dashboard)/_components/overview-chart/index.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { Chart } from "~/components/userDashboard/Chart"; +import { chartConfig, chartData } from "~/components/userDashboard/chartData"; + +type OverviewChartProperties = { + className?: string; +}; +export function OverviewChart({ className }: OverviewChartProperties) { + return ( + + ); +} diff --git a/src/app/dashboard/(user-dashboard)/_components/overview-revenue/index.tsx b/src/app/dashboard/(user-dashboard)/_components/overview-revenue/index.tsx new file mode 100644 index 000000000..226902537 --- /dev/null +++ b/src/app/dashboard/(user-dashboard)/_components/overview-revenue/index.tsx @@ -0,0 +1,18 @@ +import { revenueData } from "~/app/dashboard/(user-dashboard)/_components/overview-revenue/revenueData"; +import CardComponent from "~/components/common/DashboardCard/CardComponent"; + +export function OverviewRevenue() { + return ( +
+ {revenueData.map((card, index) => ( + + ))} +
+ ); +} diff --git a/src/app/dashboard/(user-dashboard)/_components/overview-revenue/revenueData.ts b/src/app/dashboard/(user-dashboard)/_components/overview-revenue/revenueData.ts new file mode 100644 index 000000000..584661a32 --- /dev/null +++ b/src/app/dashboard/(user-dashboard)/_components/overview-revenue/revenueData.ts @@ -0,0 +1,35 @@ +import type { LucideIconName } from "~/components/common/lucide-icon"; + +type CardData = { + title: string; + value: string; + description: string; + icon: LucideIconName; +}; + +export const revenueData: CardData[] = [ + { + title: "Total Revenue", + value: "$45,000.00", + description: "+20% from last month", + icon: "dollar-sign", + }, + { + title: "Subscriptions", + value: "+2,350", + description: "+150% from last month", + icon: "users", + }, + { + title: "Sales", + value: "15,000", + description: "+10% from last month", + icon: "credit-card", + }, + { + title: "Active Now", + value: "574", + description: "+201 since last hour", + icon: "trending-up", + }, +]; diff --git a/src/app/dashboard/(user-dashboard)/_components/overview-sales/index.tsx b/src/app/dashboard/(user-dashboard)/_components/overview-sales/index.tsx new file mode 100644 index 000000000..95952ac66 --- /dev/null +++ b/src/app/dashboard/(user-dashboard)/_components/overview-sales/index.tsx @@ -0,0 +1,11 @@ +import RecentSales from "~/app/dashboard/(user-dashboard)/_components/RecentSales"; +import { + data, + gradients, +} from "~/app/dashboard/(user-dashboard)/_components/salesData"; + +export function OverviewSales() { + return ( + + ); +} diff --git a/src/app/dashboard/(user-dashboard)/_components/salesData.ts b/src/app/dashboard/(user-dashboard)/_components/salesData.ts new file mode 100644 index 000000000..c8143118a --- /dev/null +++ b/src/app/dashboard/(user-dashboard)/_components/salesData.ts @@ -0,0 +1,43 @@ +const data = [ + { + name: "Jackson Lee", + amount: "2999", + email: "jackson.lee@gmail.com", + }, + { + name: "Olivia Martin", + amount: "7999", + email: "olivia.martin@gmail.com", + }, + { + name: "Joseph Chernysuck", + amount: "5999", + email: "olivia.martin@gmail.com", + }, + { + name: "Paul Halland", + amount: "12999", + email: "olivia.martin@gmail.com", + }, + { + name: "Eden Hazard", + amount: "3999", + email: "jackson.lee@gmail.com", + }, + { + name: "Ronaldo Messi", + amount: "4999", + email: "jackson.lee@gmail.com", + }, +]; + +const gradients = [ + "linear-gradient(180deg, #F6C790 0%, #E77F1E 100%)", + "linear-gradient(180deg, #F81404 0%, #0F172A 100%)", + "linear-gradient(180deg, rgba(4, 190, 248, 0.20) 0%, #0AB025 100%)", + "linear-gradient(180deg, #FFF 0%, #7F838D 20.86%, #0F172A 100%)", + "linear-gradient(180deg, #1E1D1C 0%, #3A1EE7 100%)", + "linear-gradient(180deg, #EF9B38 0%, #7EA7D9 100%)", +]; + +export { data, gradients }; diff --git a/src/app/dashboard/(user-dashboard)/layout.tsx b/src/app/dashboard/(user-dashboard)/layout.tsx index 472a3d467..9518eb265 100644 --- a/src/app/dashboard/(user-dashboard)/layout.tsx +++ b/src/app/dashboard/(user-dashboard)/layout.tsx @@ -8,7 +8,7 @@ export default function AdminLayout({ children: React.ReactNode; }) { return ( -
+
{children} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 38fe4626a..94cfbdaa2 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,6 +5,7 @@ import "./globals.css"; import Providers from "~/components/providers"; import { Toaster } from "~/components/ui/toaster"; +import AuthProvider from "~/contexts/authContext"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { @@ -20,9 +21,9 @@ export default function RootLayout({ return ( -
+
- {children} + {children}
diff --git a/src/components/adminDashboard/CardComponent.tsx b/src/components/adminDashboard/CardComponent.tsx deleted file mode 100644 index df4594d85..000000000 --- a/src/components/adminDashboard/CardComponent.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import Image from "next/image"; -import { FC } from "react"; - -interface CardProperties { - title: string; - value: string | number; - description: string; - icon: string; -} - -const CardComponent: FC = ({ - title, - value, - description, - icon, -}) => { - return ( - <> -
-
-

- {title} -

- - {title} -
- -
-

- {value} -

- - {description} - -
-
- - ); -}; - -export default CardComponent; diff --git a/src/components/adminDashboard/TopProductsComponent.tsx b/src/components/adminDashboard/TopProductsComponent.tsx index c2444f180..418e533d0 100644 --- a/src/components/adminDashboard/TopProductsComponent.tsx +++ b/src/components/adminDashboard/TopProductsComponent.tsx @@ -1,5 +1,5 @@ -import Image from "next/image"; -import React from "react"; +import { ArrowUpRightIcon } from "lucide-react"; +import { FC } from "react"; import { Card } from "../ui/card"; @@ -13,21 +13,7 @@ type TopProductsProperties = { gradients: string[]; }; -const ExternalLinkIcon = () => { - return ( - <> - external link icon - - ); -}; - -const TopProductsComponent: React.FC = ({ +const TopProductsComponent: FC = ({ data, gradients, }) => { @@ -45,7 +31,7 @@ const TopProductsComponent: React.FC = ({
    diff --git a/src/components/adminDashboard/cardData.ts b/src/components/adminDashboard/cardData.ts index 96692068e..0ecfe1135 100644 --- a/src/components/adminDashboard/cardData.ts +++ b/src/components/adminDashboard/cardData.ts @@ -1,8 +1,10 @@ +import type { LucideIconName } from "~/components/common/lucide-icon"; + type CardData = { title: string; value: string; description: string; - icon: string; + icon: LucideIconName; }; export const cardData: CardData[] = [ @@ -10,24 +12,24 @@ export const cardData: CardData[] = [ title: "Total Revenue", value: "$45,000.00", description: "+20% from last month", - icon: `/admin-dashboard/icons/dollarSign.svg`, + icon: "dollar-sign", }, { title: "Total Users", value: "+4,000", description: "+10% from last month", - icon: `/admin-dashboard/icons/user.svg`, + icon: "user", }, { title: "Total Products", value: "1,000", description: "+20% from last month", - icon: `/admin-dashboard/icons/box.svg`, + icon: "box", }, { title: "Lifetime Sales", value: "$450,000.00", description: "+150% from last month", - icon: `/admin-dashboard/icons/arrowUp.svg`, + icon: "arrow-up-right", }, ]; diff --git a/src/components/card/user-card.tsx b/src/components/card/user-card.tsx index d75b65cc1..d22ae6620 100644 --- a/src/components/card/user-card.tsx +++ b/src/components/card/user-card.tsx @@ -1,26 +1,27 @@ import { AnimatePresence, motion } from "framer-motion"; +import { signOut } from "next-auth/react"; +import Image from "next/image"; import { useEffect, useState } from "react"; -import { useUser } from "~/hooks/user/use-user"; import { cn } from "~/lib/utils"; -import { Button } from "../ui/button"; +import CustomButton from "../common/common-button/common-button"; -const UserCard = ({ email }: { email: string }) => { - const { updateUser } = useUser(); - const [isLogout, setIsLogout] = useState(false); - - const handleLogout = () => { - updateUser({ email: "", name: "" }); - setIsLogout(false); - }; +interface User { + email: string; + image: string; + name: string; +} - const handleEscapeClick = (event: KeyboardEvent) => { - if (event.key === "Escape") { - setIsLogout(false); - } - }; +const UserCard = ({ user }: { user: User }) => { + const [isLogout, setIsLogout] = useState(false); useEffect(() => { + const handleEscapeClick = (event: KeyboardEvent) => { + if (event.key === "Escape") { + setIsLogout(false); + } + }; + document.addEventListener("keydown", handleEscapeClick); return () => { document.removeEventListener("keydown", handleEscapeClick); @@ -29,23 +30,32 @@ const UserCard = ({ email }: { email: string }) => { return (
    - + {user?.image ? ( +
    setIsLogout(!isLogout)} + > + {`${user.name}'s +
    + ) : ( + setIsLogout(!isLogout)} + className="flex h-full w-full items-center justify-center overflow-hidden rounded-full text-center text-xl capitalize" + > + {user?.email[0]} + + )} {isLogout && ( <>
    { - setIsLogout(false); - }} + onClick={() => setIsLogout(false)} className={cn( "fixed left-0 top-0 z-[99] min-h-screen w-full overflow-hidden bg-neutral-700/0 transition-all duration-300 lg:hidden", isLogout @@ -60,15 +70,9 @@ const UserCard = ({ email }: { email: string }) => { transition={{ type: "spring", stiffness: 300, damping: 30 }} className="absolute -bottom-16 -right-2 z-[999] flex w-[150px] flex-col gap-y-2 rounded-xl bg-white p-2 shadow-lg" > - + )} diff --git a/src/components/common/DashboardCard/CardComponent.tsx b/src/components/common/DashboardCard/CardComponent.tsx new file mode 100644 index 000000000..4e108b042 --- /dev/null +++ b/src/components/common/DashboardCard/CardComponent.tsx @@ -0,0 +1,39 @@ +import { ComponentProps, FC } from "react"; + +import Icon from "~/components/common/lucide-icon"; + +interface CardProperties { + title: string; + value: string | number; + description: string; + icon: ComponentProps["name"]; +} + +const CardComponent: FC = ({ + title, + value, + description, + icon, +}) => { + return ( +
    +
    +

    + {title} +

    + +
    + +
    +

    + {value} +

    + + {description} + +
    +
    + ); +}; + +export default CardComponent; diff --git a/src/components/common/lucide-icon/index.tsx b/src/components/common/lucide-icon/index.tsx new file mode 100644 index 000000000..f07fbf21c --- /dev/null +++ b/src/components/common/lucide-icon/index.tsx @@ -0,0 +1,19 @@ +import { LucideProps } from "lucide-react"; +import dynamicIconImports from "lucide-react/dynamicIconImports"; +import dynamic from "next/dynamic"; + +interface ILucideIcon { + name: keyof typeof dynamicIconImports; +} + +export type LucideIconName = ILucideIcon["name"]; + +interface IconProperties extends Omit, ILucideIcon {} + +const Icon = ({ name, ...properties }: IconProperties) => { + const LucideIcon = dynamic(dynamicIconImports[name]); + + return ; +}; + +export default Icon; diff --git a/src/components/layouts/navbar/index.tsx b/src/components/layouts/navbar/index.tsx index 91b9c3fdb..8365b3bc8 100644 --- a/src/components/layouts/navbar/index.tsx +++ b/src/components/layouts/navbar/index.tsx @@ -1,18 +1,24 @@ "use client"; +import { useSession } from "next-auth/react"; import Link from "next/link"; import { useEffect, useState } from "react"; import UserCard from "~/components/card/user-card"; import Logo from "~/components/common/logo"; -import { useUser } from "~/hooks/user/use-user"; import { cn } from "~/lib/utils"; import { NAV_LINKS } from "./links"; import MobileNav from "./mobile-navbar"; +interface User { + email: string; + image: string; + name: string; +} + const Navbar = () => { const [scrolling, setIsScrolling] = useState(false); - const { user } = useUser(); + const { data: session, status } = useSession(); const handleScrollEvent = () => { if (window.scrollY > 1) { @@ -36,7 +42,7 @@ const Navbar = () => { className={cn( `relative mx-auto flex w-full max-w-[1200px] items-center gap-x-4 transition-all duration-500 md:justify-between`, scrolling ? "py-2" : "py-4 md:py-9", - user.email && "justify-between", + status === "authenticated" && "justify-between md:justify-between", )} > @@ -55,7 +61,7 @@ const Navbar = () => { ); })}
    - {!user.email && ( + {status !== "authenticated" && (
    {
    )} - {user.email && } + {status === "authenticated" && ( + + )}
    ); diff --git a/src/components/layouts/navbar/layout.tsx b/src/components/layouts/navbar/layout.tsx new file mode 100644 index 000000000..79c270274 --- /dev/null +++ b/src/components/layouts/navbar/layout.tsx @@ -0,0 +1,11 @@ +"use client"; + +import AuthProvider from "~/contexts/authContext"; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return {children}; +} diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx new file mode 100644 index 000000000..f73e53e23 --- /dev/null +++ b/src/components/ui/calendar.tsx @@ -0,0 +1,66 @@ +"use client"; + +import { ChevronLeft, ChevronRight } from "lucide-react"; +import * as React from "react"; +import { DayPicker } from "react-day-picker"; + +import { buttonVariants } from "~/components/common/common-button"; +import { cn } from "~/lib/utils"; + +export type CalendarProperties = React.ComponentProps; + +function Calendar({ + className, + classNames, + showOutsideDays = true, + ...properties +}: CalendarProperties) { + return ( + , + IconRight: () => , + }} + {...properties} + /> + ); +} +Calendar.displayName = "Calendar"; + +export { Calendar }; diff --git a/src/components/userDashboard/Chart.tsx b/src/components/userDashboard/Chart.tsx index 900aaa765..fe552232b 100644 --- a/src/components/userDashboard/Chart.tsx +++ b/src/components/userDashboard/Chart.tsx @@ -7,66 +7,72 @@ import { ChartTooltip, ChartTooltipContent, } from "~/components/ui/chart"; +import { cn } from "~/lib/utils"; type ChartProperties = { chartData: { month: string; revenue: number }[]; chartConfig: ChartConfig; chartTitle: string; + className?: string; }; export function Chart({ chartData = [], chartConfig, chartTitle, + className, }: ChartProperties) { return ( - <> - -

    - {chartTitle} -

    + +

    + {chartTitle} +

    -
    - - + + + - - - value.slice(0, 3)} - /> - `$${value}`} - /> - } - /> - - - - -
    -
    - + + value.slice(0, 3)} + /> + `$${value}`} + /> + } + /> + + + + +
+ ); } diff --git a/src/config/auth.config.ts b/src/config/auth.config.ts index 5121d4088..3153a0851 100644 --- a/src/config/auth.config.ts +++ b/src/config/auth.config.ts @@ -56,23 +56,29 @@ export default { if (account && account.provider === "google" && profile?.email) { return profile.email.endsWith("@gmail.com"); } + return false; }, async jwt({ token, user, account }) { if (account && account.provider !== "google") { - return { ...token, ...user }; + return { token, user }; } const response: ApiResponse = (await googleAuth( account as Profile, )) as ApiResponse; - return { ...token, ...response }; + user = response?.data?.user; + + return { ...token }; + }, + async session({ session }) { + return session; }, async redirect({ url, baseUrl }) { if (url === "/login") { return baseUrl; } - return "/register/organisation"; + return "/dashboard/admin"; }, }, pages: { diff --git a/src/contexts/authContext.tsx b/src/contexts/authContext.tsx new file mode 100644 index 000000000..743bc444f --- /dev/null +++ b/src/contexts/authContext.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { SessionProvider } from "next-auth/react"; +import { ReactNode } from "react"; + +interface AuthProviderProperties { + children: ReactNode; +} + +export default function AuthProvider({ children }: AuthProviderProperties) { + return {children}; +} diff --git a/src/utils/googleAuth.ts b/src/utils/googleAuth.ts index 2b0cd0817..835e676bd 100644 --- a/src/utils/googleAuth.ts +++ b/src/utils/googleAuth.ts @@ -22,9 +22,7 @@ const googleAuth = async (profile: Profile) => { id_token: profile.id_token, }); - return { - response, - }; + return response.data; } catch (error) { return error; }