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 710f70f9f..d1788c73d 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-select": "^2.1.1", + "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0", @@ -43,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 6f0543369..78477e3b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: '@radix-ui/react-select': specifier: ^2.1.1 version: 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-separator': + specifier: ^1.1.0 + version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': specifier: ^1.1.0 version: 1.1.0(@types/react@18.3.3)(react@18.3.1) @@ -80,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) @@ -3229,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 @@ -8294,8 +8300,8 @@ snapshots: '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.5.4) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) @@ -8326,13 +8332,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): dependencies: debug: 4.3.5 enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.6 is-core-module: 2.15.0 @@ -8343,18 +8349,28 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 7.17.0(eslint@8.57.0)(typescript@5.5.4) + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.5.4) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -8364,7 +8380,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.0 is-glob: 4.0.3 @@ -8375,7 +8391,7 @@ snapshots: semver: 6.3.1 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.5.4) + '@typescript-eslint/parser': 7.17.0(eslint@8.57.0)(typescript@5.5.4) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -8682,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/dashboard/(user-dashboard)/settings/layout.tsx b/src/app/dashboard/(user-dashboard)/settings/layout.tsx index bef3e0f8d..4d0896c29 100644 --- a/src/app/dashboard/(user-dashboard)/settings/layout.tsx +++ b/src/app/dashboard/(user-dashboard)/settings/layout.tsx @@ -9,7 +9,7 @@ const layout: FC = ({ children }) => { return (
-
+
{children}
diff --git a/src/app/dashboard/(user-dashboard)/settings/notification/_components/header.tsx b/src/app/dashboard/(user-dashboard)/settings/notification/_components/header.tsx index b66562fe0..986f39353 100644 --- a/src/app/dashboard/(user-dashboard)/settings/notification/_components/header.tsx +++ b/src/app/dashboard/(user-dashboard)/settings/notification/_components/header.tsx @@ -1,10 +1,15 @@ +import { Separator } from "~/components/ui/separator"; + interface IProperties { notificationTitle: string; } const NotificationHeader = ({ notificationTitle }: IProperties) => { return ( -

{notificationTitle}

+
+

{notificationTitle}

+ +
); }; diff --git a/src/app/dashboard/(user-dashboard)/settings/notification/_components/notification-switch-box.tsx b/src/app/dashboard/(user-dashboard)/settings/notification/_components/notification-switch-box.tsx index 9ef72b82d..2e5c5de9d 100644 --- a/src/app/dashboard/(user-dashboard)/settings/notification/_components/notification-switch-box.tsx +++ b/src/app/dashboard/(user-dashboard)/settings/notification/_components/notification-switch-box.tsx @@ -1,22 +1,35 @@ import { Switch } from "~/components/ui/switch"; +import { notificationSettingsProperties } from "../types/notification-settings.types"; interface IProperties { title: string; description: string; + name: keyof notificationSettingsProperties; + isChecked: boolean; + onToggle: (name: keyof notificationSettingsProperties) => void; + className?: string; // Add className as an optional property } -const NotificationSwitchBox = ({ title, description }: IProperties) => { +export const NotificationSwitchBox = ({ + title, + description, + name, + isChecked, + onToggle, +}: IProperties) => { return ( -
-
-

{title}

-

{description}

+
+
+

+ {title} +

+

+ {description} +

- + onToggle(name)} />
); }; - -export default NotificationSwitchBox; diff --git a/src/app/dashboard/(user-dashboard)/settings/notification/action/notification-store.ts b/src/app/dashboard/(user-dashboard)/settings/notification/action/notification-store.ts new file mode 100644 index 000000000..2f6a8d1f4 --- /dev/null +++ b/src/app/dashboard/(user-dashboard)/settings/notification/action/notification-store.ts @@ -0,0 +1,28 @@ +import create from "zustand"; + +import { notificationSettingsProperties } from "../types/notification-settings.types"; + +// Define the Zustand store +interface NotificationStore { + settings: notificationSettingsProperties; + updateSettings: ( + newSettings: Partial, + ) => void; +} + +export const useNotificationStore = create((set) => ({ + settings: { + mobile_push_notifications: false, + email_notification_activity_in_workspace: false, + email_notification_always_send_email_notifications: false, + email_notification_email_digest: false, + email_notification_announcement_and_update_emails: false, + slack_notifications_activity_on_your_workspace: false, + slack_notifications_always_send_email_notifications: false, + slack_notifications_announcement_and_update_emails: false, + }, + updateSettings: (newSettings: Partial) => + set((state) => ({ + settings: { ...state.settings, ...newSettings }, + })), +})); diff --git a/src/app/dashboard/(user-dashboard)/settings/notification/action/notification.ts b/src/app/dashboard/(user-dashboard)/settings/notification/action/notification.ts new file mode 100644 index 000000000..a86ecfe0a --- /dev/null +++ b/src/app/dashboard/(user-dashboard)/settings/notification/action/notification.ts @@ -0,0 +1,66 @@ +import axios from "axios"; + +/** + * THIS API IMPLEMENTATION ARE NOT WORKING CURRENTLY, THE BACKEND WOULD BE INTEGRTED SHORTLY. ☺️ + */ + +const notification_id = undefined; + +export const createNotification = async () => { + const data = { + message: `Welcome to HNGi8`, + }; + try { + await axios.post("/notifications", data); + } catch (error) { + return error; + } +}; + +export const RetrieveUserNotificationSettings = async () => { + try { + await axios.get("/notification-settings"); + } catch (error) { + return error; + } +}; + +export const updateUserNotificationSettings = async (settings: object) => { + try { + await axios.patch("/notification-settings", settings); + } catch (error) { + return error; + } +}; + +export const RetrieveUserNotificationAll = async () => { + try { + await axios.get("/notifications"); + } catch (error) { + return error; + } +}; + +export const RetrieveUserUnreadNotification = async () => { + try { + await axios.get("/notifications?_read=false"); + } catch (error) { + return error; + } +}; + +export const markNotificationAsRead = async () => { + try { + await axios.patch(`/notifications/${notification_id}`); + } catch (error) { + return error; + } +}; + +export const markAllNotificationAsRead = async () => { + try { + await axios.patch("/notifications"); + } catch (error) { + return error; + } +}; diff --git a/src/app/dashboard/(user-dashboard)/settings/notification/page.tsx b/src/app/dashboard/(user-dashboard)/settings/notification/page.tsx index e6f20ba1f..d486c9446 100644 --- a/src/app/dashboard/(user-dashboard)/settings/notification/page.tsx +++ b/src/app/dashboard/(user-dashboard)/settings/notification/page.tsx @@ -1,22 +1,32 @@ "use client"; -import { Check } from "lucide-react"; +import { Check, ChevronLeft } from "lucide-react"; import { useState } from "react"; import CustomButton from "~/components/common/common-button/common-button"; import NotificationSettingSavedModal from "~/components/common/modals/notification-settings-saved"; import NotificationHeader from "./_components/header"; -import NotificationSwitchBox from "./_components/notification-switch-box"; +import { NotificationSwitchBox } from "./_components/notification-switch-box"; +import { useNotificationStore } from "./action/notification-store"; const NotificationPage = () => { + const { settings, updateSettings } = useNotificationStore(); const [isOpen, setOpen] = useState(false); - const handleOpenModal = () => { + const handleToggleSwitch = (name: keyof typeof settings) => { + updateSettings({ [name]: !settings[name] }); + }; + + const handleSaveChanges = () => { setOpen(true); }; return (
+
+ + Notification +
{/* NOTIFICATION ALERT */}
@@ -25,101 +35,108 @@ const NotificationPage = () => { description={ "Receive push notifications on mentions and comments via your mobile app" } + name="mobile_push_notifications" + isChecked={settings.mobile_push_notifications} + onToggle={handleToggleSwitch} />
{/* EMAIL NOTIFICATION */}
- {/* option 1 */} -
- -
- {/* option 2 */} -
- -
- {/* option 3 */} -
- -
- {/* option 4 */} -
- -
+ + + +
{/* SLACK NOTIFICATIONS */}
- {/* option 1 */} -
- -
- {/* option 2 */} -
- -
- {/* option 3 */} -
- -
+ + +
-
+
} isLeftIconVisible={true} - isLoading={false} - isDisabled={false} - onClick={handleOpenModal} + onClick={handleSaveChanges} > Save Changes -
+ setOpen(false)} + />
); }; diff --git a/src/app/dashboard/(user-dashboard)/settings/notification/types/notification-settings.types.ts b/src/app/dashboard/(user-dashboard)/settings/notification/types/notification-settings.types.ts new file mode 100644 index 000000000..505ca8814 --- /dev/null +++ b/src/app/dashboard/(user-dashboard)/settings/notification/types/notification-settings.types.ts @@ -0,0 +1,10 @@ +export type notificationSettingsProperties = { + mobile_push_notifications: boolean; + email_notification_activity_in_workspace: boolean; + email_notification_always_send_email_notifications: boolean; + email_notification_email_digest: boolean; + email_notification_announcement_and_update_emails: boolean; + slack_notifications_activity_on_your_workspace: boolean; + slack_notifications_always_send_email_notifications: boolean; + slack_notifications_announcement_and_update_emails: boolean; +}; diff --git a/src/app/globals.css b/src/app/globals.css index 450351d4c..a5043fd7e 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -166,7 +166,7 @@ /* Custom Scrollbar Styling */ /* For Webkit Browsers */ ::-webkit-scrollbar { - width: 14px; /* Width of the scrollbar */ + width: 12px; /* Width of the scrollbar */ } ::selection { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3c252bb27..94cfbdaa2 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,10 +3,9 @@ import { Inter } from "next/font/google"; import "./globals.css"; -import { SessionProvider } from "next-auth/react"; - 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 = { @@ -22,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 1d46b02c8..d22ae6620 100644 --- a/src/components/card/user-card.tsx +++ b/src/components/card/user-card.tsx @@ -1,28 +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); - signOut({ callbackUrl: "/" }); - }; +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); @@ -31,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 @@ -62,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/common/modals/notification-settings-saved/index.tsx b/src/components/common/modals/notification-settings-saved/index.tsx index 843b23578..0f45bc3c8 100644 --- a/src/components/common/modals/notification-settings-saved/index.tsx +++ b/src/components/common/modals/notification-settings-saved/index.tsx @@ -35,17 +35,17 @@ const NotificationSettingSavedModal: React.FC = ({ className={`fixed inset-0 z-50 flex items-center justify-center ${isNotificationPath ? "bg-transparent backdrop-blur-sm" : "bg-black bg-opacity-50"}`} /> event.stopPropagation()} > Notification Updated! - + Notification preferences updated successfully. Remember, you can always adjust these settings later. -
    +
    Done
    diff --git a/src/components/layouts/navbar/index.tsx b/src/components/layouts/navbar/index.tsx index a78994c54..8365b3bc8 100644 --- a/src/components/layouts/navbar/index.tsx +++ b/src/components/layouts/navbar/index.tsx @@ -10,9 +10,15 @@ 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 { data: session } = useSession(); + 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", - session?.user?.email && "justify-between", + status === "authenticated" && "justify-between md:justify-between", )} > @@ -55,9 +61,7 @@ const Navbar = () => { ); })}
    - {session?.user?.email ? ( - - ) : ( + {status !== "authenticated" && (
    {
    )} + {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/ui/separator.tsx b/src/components/ui/separator.tsx new file mode 100644 index 000000000..ef26ace81 --- /dev/null +++ b/src/components/ui/separator.tsx @@ -0,0 +1,31 @@ +"use client"; + +import * as SeparatorPrimitive from "@radix-ui/react-separator"; +import * as React from "react"; + +import { cn } from "~/lib/utils"; + +const Separator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>( + ( + { className, orientation = "horizontal", decorative = true, ...properties }, + reference, + ) => ( + + ), +); +Separator.displayName = SeparatorPrimitive.Root.displayName; + +export { Separator }; 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; }