Skip to content

Commit 9c093b8

Browse files
authored
re-designed overview screen (#565)
* re-designed overview screen * Lint fixes
1 parent 24102cc commit 9c093b8

14 files changed

Lines changed: 265 additions & 304 deletions

File tree

.husky/pre-commit

100644100755
File mode changed.

apps/web/app/(with-contexts)/dashboard/(sidebar)/overview/page.tsx

Lines changed: 137 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,82 @@
11
"use client";
22

3-
import DashboardContent from "@components/admin/dashboard-content";
4-
import { Metric } from "@components/admin/dashboard/metric";
5-
import { Todo } from "@components/admin/dashboard/to-do";
6-
import LoadingScreen from "@components/admin/loading-screen";
3+
import { useContext, useState } from "react";
74
import {
85
AddressContext,
96
ProfileContext,
107
SiteInfoContext,
118
} from "@components/contexts";
12-
import { UIConstants } from "@courselit/common-models";
9+
import { UIConstants, Constants } from "@courselit/common-models";
1310
import { checkPermission } from "@courselit/utils";
14-
import { DASHBOARD_PAGE_HEADER, OVERVIEW_HEADER } from "@ui-config/strings";
15-
import { useContext } from "react";
11+
import {
12+
DASHBOARD_PAGE_HEADER,
13+
OVERVIEW_HEADER,
14+
UNNAMED_USER,
15+
} from "@ui-config/strings";
16+
import { TIME_RANGES } from "@ui-config/constants";
17+
import { useActivities } from "@/hooks/use-activities";
18+
import dynamic from "next/dynamic";
19+
import DashboardContent from "@components/admin/dashboard-content";
20+
const Todo = dynamic(() =>
21+
import("@components/admin/dashboard/to-do").then((mod) => ({
22+
default: mod.Todo,
23+
})),
24+
);
25+
const LoadingScreen = dynamic(() => import("@components/admin/loading-screen"));
26+
const MetricCard = dynamic(() => import("../product/[id]/metric-card"));
27+
const SalesCard = dynamic(() => import("./sales-card"));
1628

29+
// Dynamically import UI components
30+
const Select = dynamic(() =>
31+
import("@/components/ui/select").then((mod) => ({ default: mod.Select })),
32+
);
33+
const SelectContent = dynamic(() =>
34+
import("@/components/ui/select").then((mod) => ({
35+
default: mod.SelectContent,
36+
})),
37+
);
38+
const SelectItem = dynamic(() =>
39+
import("@/components/ui/select").then((mod) => ({
40+
default: mod.SelectItem,
41+
})),
42+
);
43+
const SelectTrigger = dynamic(() =>
44+
import("@/components/ui/select").then((mod) => ({
45+
default: mod.SelectTrigger,
46+
})),
47+
);
48+
const SelectValue = dynamic(() =>
49+
import("@/components/ui/select").then((mod) => ({
50+
default: mod.SelectValue,
51+
})),
52+
);
53+
54+
// Dynamically import icons
55+
const DollarSign = dynamic(() =>
56+
import("lucide-react").then((mod) => ({ default: mod.DollarSign })),
57+
);
58+
const UserPlus = dynamic(() =>
59+
import("lucide-react").then((mod) => ({ default: mod.UserPlus })),
60+
);
61+
const Users = dynamic(() =>
62+
import("lucide-react").then((mod) => ({ default: mod.Users })),
63+
);
64+
const Mail = dynamic(() =>
65+
import("lucide-react").then((mod) => ({ default: mod.Mail })),
66+
);
1767
const breadcrumbs = [{ label: OVERVIEW_HEADER, href: "#" }];
1868

1969
export default function Page() {
2070
const siteInfo = useContext(SiteInfoContext);
2171
const address = useContext(AddressContext);
2272
const { profile } = useContext(ProfileContext);
73+
const [timeRange, setTimeRange] = useState("7d");
74+
const { data: salesData, loading: salesLoading } = useActivities(
75+
Constants.ActivityType.PURCHASED,
76+
timeRange,
77+
undefined,
78+
true,
79+
);
2380

2481
if (
2582
!checkPermission(profile.permissions!, [
@@ -36,13 +93,79 @@ export default function Page() {
3693

3794
return (
3895
<DashboardContent breadcrumbs={breadcrumbs}>
39-
<h1 className="text-4xl font-semibold mb-8">
40-
{DASHBOARD_PAGE_HEADER}
96+
<div className="flex justify-between items-center mb-8">
97+
<h1 className="text-4xl font-semibold mb-4">
98+
{DASHBOARD_PAGE_HEADER},{" "}
99+
{profile.name ? profile.name.split(" ")[0] : UNNAMED_USER}
100+
</h1>
101+
<div>
102+
<Select value={timeRange} onValueChange={setTimeRange}>
103+
<SelectTrigger className="w-[140px]">
104+
<SelectValue placeholder="Select time range" />
105+
</SelectTrigger>
106+
<SelectContent>
107+
{TIME_RANGES.map((range) => (
108+
<SelectItem
109+
key={range.value}
110+
value={range.value}
111+
>
112+
{range.label}
113+
</SelectItem>
114+
))}
115+
</SelectContent>
116+
</Select>
117+
</div>
118+
</div>
119+
{/* <h1 className="text-4xl font-semibold mb-8">
120+
{DASHBOARD_PAGE_HEADER}, {profile.name ? profile.name.split(" ")[0] : ""}
41121
</h1>
42-
<div className="mb-8">
43-
<Todo siteinfo={siteInfo} />
122+
<Select value={timeRange} onValueChange={setTimeRange}>
123+
<SelectTrigger className="w-[140px]">
124+
<SelectValue placeholder="Select time range" />
125+
</SelectTrigger>
126+
<SelectContent>
127+
{TIME_RANGES.map((range) => (
128+
<SelectItem
129+
key={range.value}
130+
value={range.value}
131+
>
132+
{range.label}
133+
</SelectItem>
134+
))}
135+
</SelectContent>
136+
</Select> */}
137+
<Todo siteinfo={siteInfo} />
138+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-2">
139+
<MetricCard
140+
title="Sales"
141+
icon={
142+
<DollarSign className="h-4 w-4 text-muted-foreground" />
143+
}
144+
type={Constants.ActivityType.PURCHASED}
145+
duration={timeRange}
146+
/>
147+
<MetricCard
148+
title="Customers"
149+
icon={
150+
<UserPlus className="h-4 w-4 text-muted-foreground" />
151+
}
152+
type={Constants.ActivityType.ENROLLED}
153+
duration={timeRange}
154+
/>
155+
<MetricCard
156+
title="New community members"
157+
icon={<Users className="h-4 w-4 text-muted-foreground" />}
158+
type={Constants.ActivityType.COMMUNITY_JOINED}
159+
duration={timeRange}
160+
/>
161+
<MetricCard
162+
title="Subscribers"
163+
icon={<Mail className="h-4 w-4 text-muted-foreground" />}
164+
type={Constants.ActivityType.NEWSLETTER_SUBSCRIBED}
165+
duration={timeRange}
166+
/>
44167
</div>
45-
<div className="grid xs:grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
168+
{/* <div className="grid xs:grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
46169
<Metric
47170
title="Revenue"
48171
type="purchased"
@@ -67,7 +190,8 @@ export default function Page() {
67190
duration="7d"
68191
address={address}
69192
/>
70-
</div>
193+
</div> */}
194+
<SalesCard data={salesData} loading={salesLoading} />
71195
</DashboardContent>
72196
);
73197
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { SiteInfoContext } from "@components/contexts";
2+
import { Card, CardContent, CardTitle, CardHeader } from "@components/ui/card";
3+
import { Skeleton } from "@components/ui/skeleton";
4+
import { getSymbolFromCurrency } from "@courselit/components-library";
5+
import { useContext } from "react";
6+
import {
7+
CartesianGrid,
8+
Line,
9+
LineChart,
10+
ResponsiveContainer,
11+
Tooltip,
12+
XAxis,
13+
YAxis,
14+
} from "recharts";
15+
16+
export default function SalesCard({
17+
data,
18+
loading,
19+
}: {
20+
data: any;
21+
loading: boolean;
22+
}) {
23+
const siteinfo = useContext(SiteInfoContext);
24+
25+
return (
26+
<div className="mt-4">
27+
<Card>
28+
<CardHeader>
29+
<CardTitle>Sales</CardTitle>
30+
</CardHeader>
31+
<CardContent>
32+
{loading ? (
33+
<Skeleton className="h-[240px] w-full" />
34+
) : (
35+
<div className="">
36+
<ResponsiveContainer width="100%" height={200}>
37+
<LineChart
38+
width={300}
39+
height={200}
40+
data={data?.points}
41+
>
42+
<CartesianGrid
43+
strokeDasharray="3 3"
44+
stroke="#e5e7eb"
45+
/>
46+
<Line
47+
type="monotone"
48+
dataKey="count"
49+
strokeWidth={2}
50+
stroke="#000000"
51+
dot={false}
52+
/>
53+
<XAxis
54+
dataKey="date"
55+
className="text-xs"
56+
axisLine={false}
57+
tickLine={false}
58+
/>
59+
<YAxis
60+
tickFormatter={(value) =>
61+
`${getSymbolFromCurrency(siteinfo.currencyISOCode || "USD")}${value}`
62+
}
63+
className="text-xs"
64+
axisLine={false}
65+
tickLine={false}
66+
/>
67+
<Tooltip
68+
contentStyle={{
69+
backgroundColor: "#333",
70+
border: "none",
71+
borderRadius: "4px",
72+
padding: "4px 8px",
73+
fontSize: "12px",
74+
color: "white",
75+
}}
76+
itemStyle={{ color: "white" }}
77+
formatter={(value) => [
78+
`Sales: ${value}`,
79+
]}
80+
/>
81+
</LineChart>
82+
</ResponsiveContainer>
83+
</div>
84+
)}
85+
</CardContent>
86+
</Card>
87+
</div>
88+
);
89+
}

apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/metric-card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ interface MetricCardProps {
77
icon: React.ReactNode;
88
type: string;
99
duration: string;
10-
entityId: string;
10+
entityId?: string;
1111
}
1212

1313
const MetricCard = ({

apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/page.tsx

Lines changed: 7 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,11 @@ import {
7070
import { useActivities } from "@/hooks/use-activities";
7171
import { Constants } from "@courselit/common-models";
7272
import Resources from "@components/resources";
73+
import { TIME_RANGES } from "@ui-config/constants";
74+
import SalesCard from "../../overview/sales-card";
7375

7476
const { ActivityType } = Constants;
7577

76-
const timeRanges = [
77-
{ value: "1d", label: "1 day" },
78-
{ value: "7d", label: "1 week" },
79-
{ value: "30d", label: "30 days" },
80-
{ value: "90d", label: "90 days" },
81-
{ value: "1y", label: "1 year" },
82-
{ value: "lifetime", label: "Lifetime" },
83-
];
84-
8578
export default function DashboardPage() {
8679
const params = useParams();
8780
const productId = params.id as string;
@@ -189,7 +182,7 @@ export default function DashboardPage() {
189182
<SelectValue placeholder="Select time range" />
190183
</SelectTrigger>
191184
<SelectContent>
192-
{timeRanges.map((range) => (
185+
{TIME_RANGES.map((range) => (
193186
<SelectItem
194187
key={range.value}
195188
value={range.value}
@@ -366,7 +359,9 @@ export default function DashboardPage() {
366359
)}
367360
</div>
368361

369-
<div className="mt-4">
362+
<SalesCard data={salesData} loading={salesLoading} />
363+
364+
{/* <div className="mt-4">
370365
<Card>
371366
<CardHeader>
372367
<CardTitle>Sales</CardTitle>
@@ -376,36 +371,6 @@ export default function DashboardPage() {
376371
<Skeleton className="h-[240px] w-full" />
377372
) : (
378373
<div className="">
379-
{/* <LineChart
380-
data={productData.salesData}
381-
categories={["Sales"]}
382-
index="name"
383-
colors={["#16a34a"]}
384-
valueFormatter={(value: number) =>
385-
`${getSymbolFromCurrency(
386-
siteinfo.currencyISOCode || "USD",
387-
)}${value}`
388-
}
389-
className="h-full w-full"
390-
/> */}
391-
392-
{/* <ResponsiveContainer width="100%" height={200}>
393-
<LineChart
394-
width={300}
395-
height={200}
396-
data={salesData?.points}
397-
>
398-
<Line
399-
type="monotone"
400-
dataKey="count"
401-
strokeWidth={2}
402-
stroke="#000"
403-
/>
404-
<XAxis className="text-xs" dataKey="date" />
405-
<YAxis className="text-xs" tickFormatter={(value) => `${getSymbolFromCurrency(siteinfo.currencyISOCode || "USD")}${value}`} />
406-
<Tooltip formatter={(value) => `${getSymbolFromCurrency(siteinfo.currencyISOCode || "USD")}${value}`} />
407-
</LineChart>
408-
</ResponsiveContainer> */}
409374
<ResponsiveContainer width="100%" height={200}>
410375
<LineChart
411376
width={300}
@@ -457,7 +422,7 @@ export default function DashboardPage() {
457422
)}
458423
</CardContent>
459424
</Card>
460-
</div>
425+
</div> */}
461426

462427
<Resources
463428
links={[

apps/web/app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ export async function generateMetadata(): Promise<Metadata> {
7070
"/courselit_backdrop_square.webp",
7171
],
7272
},
73+
generator: "CourseLit",
74+
applicationName: "CourseLit",
7375
};
7476
}
7577

0 commit comments

Comments
 (0)