From 79e00ad81dcfbbf295e9373455e3263e515c1957 Mon Sep 17 00:00:00 2001 From: Rajat Saxena Date: Sat, 29 Mar 2025 23:38:33 +0530 Subject: [PATCH 1/2] re-designed overview screen --- .../dashboard/(sidebar)/overview/page.tsx | 119 ++++++++++-- .../(sidebar)/overview/sales-card.tsx | 89 +++++++++ .../(sidebar)/product/[id]/metric-card.tsx | 2 +- .../dashboard/(sidebar)/product/[id]/page.tsx | 49 +---- apps/web/app/layout.tsx | 2 + apps/web/components/admin/dashboard/index.tsx | 52 ------ .../web/components/admin/dashboard/metric.tsx | 174 ------------------ apps/web/components/admin/dashboard/to-do.tsx | 18 +- apps/web/ui-config/constants.ts | 9 + apps/web/ui-config/strings.ts | 1 + 10 files changed, 224 insertions(+), 291 deletions(-) create mode 100644 apps/web/app/(with-contexts)/dashboard/(sidebar)/overview/sales-card.tsx delete mode 100644 apps/web/components/admin/dashboard/index.tsx delete mode 100644 apps/web/components/admin/dashboard/metric.tsx diff --git a/apps/web/app/(with-contexts)/dashboard/(sidebar)/overview/page.tsx b/apps/web/app/(with-contexts)/dashboard/(sidebar)/overview/page.tsx index 39f8513e2..61b4527d0 100644 --- a/apps/web/app/(with-contexts)/dashboard/(sidebar)/overview/page.tsx +++ b/apps/web/app/(with-contexts)/dashboard/(sidebar)/overview/page.tsx @@ -1,25 +1,48 @@ "use client"; -import DashboardContent from "@components/admin/dashboard-content"; -import { Metric } from "@components/admin/dashboard/metric"; -import { Todo } from "@components/admin/dashboard/to-do"; -import LoadingScreen from "@components/admin/loading-screen"; +import { useContext, useState } from "react"; import { AddressContext, ProfileContext, SiteInfoContext, } from "@components/contexts"; -import { UIConstants } from "@courselit/common-models"; +import { UIConstants, Constants } from "@courselit/common-models"; import { checkPermission } from "@courselit/utils"; -import { DASHBOARD_PAGE_HEADER, OVERVIEW_HEADER } from "@ui-config/strings"; -import { useContext } from "react"; +import { DASHBOARD_PAGE_HEADER, OVERVIEW_HEADER, UNNAMED_USER } from "@ui-config/strings"; +import { TIME_RANGES } from "@ui-config/constants"; +import { useActivities } from "@/hooks/use-activities"; +import dynamic from "next/dynamic"; +import DashboardContent from "@components/admin/dashboard-content"; +const Todo = dynamic(() => import("@components/admin/dashboard/to-do").then(mod => ({ default: mod.Todo }))); +const LoadingScreen = dynamic(() => import("@components/admin/loading-screen")); +const MetricCard = dynamic(() => import("../product/[id]/metric-card")); +const SalesCard = dynamic(() => import("./sales-card")); + +// Dynamically import UI components +const Select = dynamic(() => import("@/components/ui/select").then(mod => ({ default: mod.Select }))); +const SelectContent = dynamic(() => import("@/components/ui/select").then(mod => ({ default: mod.SelectContent }))); +const SelectItem = dynamic(() => import("@/components/ui/select").then(mod => ({ default: mod.SelectItem }))); +const SelectTrigger = dynamic(() => import("@/components/ui/select").then(mod => ({ default: mod.SelectTrigger }))); +const SelectValue = dynamic(() => import("@/components/ui/select").then(mod => ({ default: mod.SelectValue }))); +// Dynamically import icons +const DollarSign = dynamic(() => import("lucide-react").then(mod => ({ default: mod.DollarSign }))); +const UserPlus = dynamic(() => import("lucide-react").then(mod => ({ default: mod.UserPlus }))); +const Users = dynamic(() => import("lucide-react").then(mod => ({ default: mod.Users }))); +const Mail = dynamic(() => import("lucide-react").then(mod => ({ default: mod.Mail }))); const breadcrumbs = [{ label: OVERVIEW_HEADER, href: "#" }]; export default function Page() { const siteInfo = useContext(SiteInfoContext); const address = useContext(AddressContext); const { profile } = useContext(ProfileContext); + const [timeRange, setTimeRange] = useState("7d"); + const { data: salesData, loading: salesLoading } = useActivities( + Constants.ActivityType.PURCHASED, + timeRange, + undefined, + true, + ); if ( !checkPermission(profile.permissions!, [ @@ -36,13 +59,82 @@ export default function Page() { return ( -

- {DASHBOARD_PAGE_HEADER} +
+

+ {DASHBOARD_PAGE_HEADER}, {profile.name ? profile.name.split(" ")[0] : UNNAMED_USER} +

+
+ +
+
+ {/*

+ {DASHBOARD_PAGE_HEADER}, {profile.name ? profile.name.split(" ")[0] : ""}

-
- + */} + +
+ + } + type={Constants.ActivityType.PURCHASED} + duration={timeRange} + /> + + } + type={Constants.ActivityType.ENROLLED} + duration={timeRange} + /> + + } + type={Constants.ActivityType.COMMUNITY_JOINED} + duration={timeRange} + /> + + } + type={Constants.ActivityType.NEWSLETTER_SUBSCRIBED} + duration={timeRange} + />
-
+ {/*
-
+
*/} + ); } diff --git a/apps/web/app/(with-contexts)/dashboard/(sidebar)/overview/sales-card.tsx b/apps/web/app/(with-contexts)/dashboard/(sidebar)/overview/sales-card.tsx new file mode 100644 index 000000000..5f515ddd6 --- /dev/null +++ b/apps/web/app/(with-contexts)/dashboard/(sidebar)/overview/sales-card.tsx @@ -0,0 +1,89 @@ +import { SiteInfoContext } from "@components/contexts"; +import { Card, CardContent, CardTitle, CardHeader } from "@components/ui/card"; +import { Skeleton } from "@components/ui/skeleton"; +import { getSymbolFromCurrency } from "@courselit/components-library"; +import { useContext } from "react"; +import { + CartesianGrid, + Line, + LineChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; + +export default function SalesCard({ + data, + loading, +}: { + data: any; + loading: boolean; +}) { + const siteinfo = useContext(SiteInfoContext); + + return ( +
+ + + Sales + + + {loading ? ( + + ) : ( +
+ + + + + + + `${getSymbolFromCurrency(siteinfo.currencyISOCode || "USD")}${value}` + } + className="text-xs" + axisLine={false} + tickLine={false} + /> + [ + `Sales: ${value}`, + ]} + /> + + +
+ )} +
+
+
+ ); +} diff --git a/apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/metric-card.tsx b/apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/metric-card.tsx index 1626759aa..f8e5533b0 100644 --- a/apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/metric-card.tsx +++ b/apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/metric-card.tsx @@ -7,7 +7,7 @@ interface MetricCardProps { icon: React.ReactNode; type: string; duration: string; - entityId: string; + entityId?: string; } const MetricCard = ({ diff --git a/apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/page.tsx b/apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/page.tsx index 12d653f5c..bb84bddb4 100644 --- a/apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/page.tsx +++ b/apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/page.tsx @@ -70,18 +70,11 @@ import { import { useActivities } from "@/hooks/use-activities"; import { Constants } from "@courselit/common-models"; import Resources from "@components/resources"; +import { TIME_RANGES } from "@ui-config/constants"; +import SalesCard from "../../overview/sales-card"; const { ActivityType } = Constants; -const timeRanges = [ - { value: "1d", label: "1 day" }, - { value: "7d", label: "1 week" }, - { value: "30d", label: "30 days" }, - { value: "90d", label: "90 days" }, - { value: "1y", label: "1 year" }, - { value: "lifetime", label: "Lifetime" }, -]; - export default function DashboardPage() { const params = useParams(); const productId = params.id as string; @@ -189,7 +182,7 @@ export default function DashboardPage() { - {timeRanges.map((range) => ( + {TIME_RANGES.map((range) => ( -
+ + + {/*
Sales @@ -376,36 +371,6 @@ export default function DashboardPage() { ) : (
- {/* - `${getSymbolFromCurrency( - siteinfo.currencyISOCode || "USD", - )}${value}` - } - className="h-full w-full" - /> */} - - {/* - - - - `${getSymbolFromCurrency(siteinfo.currencyISOCode || "USD")}${value}`} /> - `${getSymbolFromCurrency(siteinfo.currencyISOCode || "USD")}${value}`} /> - - */} -
+
*/} { "/courselit_backdrop_square.webp", ], }, + generator: "CourseLit", + applicationName: "CourseLit", }; } diff --git a/apps/web/components/admin/dashboard/index.tsx b/apps/web/components/admin/dashboard/index.tsx deleted file mode 100644 index 7bf858cd4..000000000 --- a/apps/web/components/admin/dashboard/index.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Address, SiteInfo } from "@courselit/common-models"; -import { AppDispatch, AppState } from "@courselit/state-management"; -import { DASHBOARD_PAGE_HEADER } from "@ui-config/strings"; -import { connect } from "react-redux"; -import ToDo from "./to-do"; -import Metric from "./metric"; - -interface IndexProps { - dispatch: AppDispatch; - address: Address; - loading: boolean; - siteinfo: SiteInfo; -} - -const Index = ({ loading, address, dispatch, siteinfo }: IndexProps) => { - return ( -
-

- {DASHBOARD_PAGE_HEADER} -

-
- -
-
- - - - -
-
- ); -}; - -const mapStateToProps = (state: AppState) => ({ - address: state.address, - loading: state.networkAction, - siteinfo: state.siteinfo, -}); - -const mapDispatchToProps = (dispatch: AppDispatch) => ({ - dispatch, -}); - -export default connect(mapStateToProps, mapDispatchToProps)(Index); diff --git a/apps/web/components/admin/dashboard/metric.tsx b/apps/web/components/admin/dashboard/metric.tsx deleted file mode 100644 index 3580525cf..000000000 --- a/apps/web/components/admin/dashboard/metric.tsx +++ /dev/null @@ -1,174 +0,0 @@ -"use client"; - -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, - Select, - Skeleton, -} from "@courselit/components-library"; -import constants from "@config/constants"; -import { useEffect, useState } from "react"; -import { FetchBuilder } from "@courselit/utils"; -import { AppState } from "@courselit/state-management"; -import { Address } from "@courselit/common-models"; -import { connect } from "react-redux"; -import { - Line, - LineChart, - ResponsiveContainer, - Tooltip, - XAxis, - YAxis, -} from "recharts"; -import { DASHBOARD_SELECT_HEADER } from "@ui-config/strings"; -const { analyticsDurations, activityTypes } = constants; - -type Duration = (typeof analyticsDurations)[number]; - -interface MetricProps { - title: string; - type: (typeof activityTypes)[number]; - duration?: Duration; - description?: string; - address: Address; -} - -export const Metric = ({ - title, - duration = "7d", - type, - description, - address, -}: MetricProps) => { - const [data, setData] = useState<{ - count: number; - points: { date: string; count: number }[]; - }>(); - const [internalDuration, setInternalDuration] = useState(duration); - const [loading, setLoading] = useState(false); - - useEffect(() => { - const getData = async () => { - const query = ` - query { - activities: getActivities( - type: ${type.toUpperCase()}, - duration: _${internalDuration.toUpperCase()} - ) { - count, - points { - date, - count - } - } - } - `; - - const fetch = new FetchBuilder() - .setUrl(`${address.backend}/api/graph`) - .setPayload(query) - .setIsGraphQLEndpoint(true) - .build(); - - try { - setLoading(true); - const response = await fetch.exec(); - if (response.activities) { - const pointsWithDate = response.activities.points.map( - (point: { date: string; count: number }) => { - return { - date: new Date( - +point.date, - ).toLocaleDateString(), - count: point.count, - }; - }, - ); - - setData({ - count: response.activities.count, - points: pointsWithDate, - }); - } - } catch (err: any) { - console.log("Error in fetching activities"); // eslint-disable-line - } finally { - setLoading(false); - } - }; - - getData(); - }, [internalDuration]); - - return ( - - -
- {title} -
- - - - - - {TIME_RANGES.map((range) => ( - - {range.label} - - ))} - - +
{/*

@@ -119,17 +154,13 @@ export default function Page() { /> - } + icon={} type={Constants.ActivityType.COMMUNITY_JOINED} duration={timeRange} /> - } + icon={} type={Constants.ActivityType.NEWSLETTER_SUBSCRIBED} duration={timeRange} /> diff --git a/apps/web/components/admin/dashboard/to-do.tsx b/apps/web/components/admin/dashboard/to-do.tsx index 89ed95c77..06d5e3791 100644 --- a/apps/web/components/admin/dashboard/to-do.tsx +++ b/apps/web/components/admin/dashboard/to-do.tsx @@ -1,7 +1,6 @@ "use client"; import { SiteInfoContext } from "@components/contexts"; -import { SiteInfo } from "@courselit/common-models"; import { Link } from "@courselit/components-library"; import { AppState } from "@courselit/state-management"; import { diff --git a/apps/web/ui-config/strings.ts b/apps/web/ui-config/strings.ts index 25870a9cd..378ddbf6b 100644 --- a/apps/web/ui-config/strings.ts +++ b/apps/web/ui-config/strings.ts @@ -589,7 +589,7 @@ export const APP_MESSAGE_MAIL_DELETED = "Mail deleted"; export const NEW_PAGE_FORM_WARNING = "These settings cannot be changed later on, so proceed with caution."; export const DASHBOARD_PAGE_HEADER = "Welcome"; -export const UNNAMED_USER = "Unnamed"; +export const UNNAMED_USER = "Unnamed"; export const MAIL_REQUEST_FORM_REASON_FIELD = "Reason"; export const MAIL_REQUEST_FORM_REASON_PLACEHOLDER = "Please be as detailed as possible. This will help us review your application better."; diff --git a/packages/common-widgets/src/featured/admin-widget.tsx b/packages/common-widgets/src/featured/admin-widget.tsx index bb600df18..70a2e6697 100644 --- a/packages/common-widgets/src/featured/admin-widget.tsx +++ b/packages/common-widgets/src/featured/admin-widget.tsx @@ -114,7 +114,14 @@ export default function AdminWidget({ `; const fetch = new FetchBuilder() .setUrl(`${address.backend}/api/graph`) - .setPayload({ query, variables: { limit: 1000, publicView: true, filter: ["COURSE", "DOWNLOAD"] } }) + .setPayload({ + query, + variables: { + limit: 1000, + publicView: true, + filter: ["COURSE", "DOWNLOAD"], + }, + }) .setIsGraphQLEndpoint(true) .build(); try { diff --git a/packages/components-library/src/content-card.tsx b/packages/components-library/src/content-card.tsx index 03063d802..a739aa7c0 100644 --- a/packages/components-library/src/content-card.tsx +++ b/packages/components-library/src/content-card.tsx @@ -16,11 +16,7 @@ export function ContentCardImage({ }: ContentCardImageProps) { return (
- {alt} + {alt}
); } diff --git a/packages/components-library/tsup.config.ts b/packages/components-library/tsup.config.ts index c225d887f..19e665459 100644 --- a/packages/components-library/tsup.config.ts +++ b/packages/components-library/tsup.config.ts @@ -8,11 +8,6 @@ export default defineConfig((options: Options) => ({ dts: true, minify: true, clean: true, - external: [ - "react", - "next/link", - "next/legacy/image", - "next/navigation", - ], + external: ["react", "next/link", "next/legacy/image", "next/navigation"], ...options, }));