Skip to content

Commit 8101b5a

Browse files
committed
Charts, Template fix
1 parent b088122 commit 8101b5a

15 files changed

Lines changed: 658 additions & 487 deletions

File tree

RELEASE_NOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
### ✨ Features
22

3+
- Added subscriber growth chart to the dashboard
4+
- Can send emails without selecting a template
35
- New features and improvements.
46

57
### 🐛 Bug Fixes

apps/web/src/index.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@
3434
--sidebar-accent-foreground: 240 5.9% 10%;
3535
--sidebar-border: 220 13% 91%;
3636
--sidebar-ring: 217.2 91.2% 59.8%;
37+
38+
--chart-1: 12 76% 61%;
39+
--chart-2: 173 58% 39%;
40+
--chart-3: 197 37% 24%;
41+
--chart-4: 43 74% 66%;
42+
--chart-5: 27 87% 67%;
3743
}
3844

3945
.dark {
@@ -66,6 +72,12 @@
6672
--sidebar-accent-foreground: 240 4.8% 95.9%;
6773
--sidebar-border: 240 3.7% 15.9%;
6874
--sidebar-ring: 217.2 91.2% 59.8%;
75+
76+
--chart-1: 220 70% 50%;
77+
--chart-2: 160 60% 45%;
78+
--chart-3: 30 80% 55%;
79+
--chart-4: 280 65% 60%;
80+
--chart-5: 340 75% 55%;
6981
}
7082

7183
/* Hide autofill background color */

apps/web/src/pages/dashboard/campaigns/[id]/layout.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const EditCampaignLayout: React.FC<{
3333
title: campaignQuery.data?.campaign?.title ?? "",
3434
description: campaignQuery.data?.campaign?.description ?? "",
3535
subject: campaignQuery.data?.campaign?.subject ?? "",
36-
templateId: campaignQuery.data?.campaign?.templateId ?? "",
36+
templateId: campaignQuery.data?.campaign?.templateId ?? "none",
3737
listIds:
3838
campaignQuery.data?.campaign?.CampaignLists?.map(
3939
(list) => list.listId
@@ -58,7 +58,10 @@ export const EditCampaignLayout: React.FC<{
5858
id,
5959
organizationId: orgId,
6060
...values,
61-
templateId: values.templateId || null,
61+
templateId:
62+
values.templateId === "none" || values.templateId === ""
63+
? null
64+
: values.templateId,
6265
},
6366
{
6467
onSuccess({ campaign }) {
@@ -68,7 +71,7 @@ export const EditCampaignLayout: React.FC<{
6871
listIds: campaign.CampaignLists.map((list) => list.listId),
6972
openTracking: campaign.openTracking,
7073
subject: campaign.subject || "",
71-
templateId: campaign.templateId || "",
74+
templateId: campaign.templateId || "none",
7275
title: campaign.title,
7376
})
7477

apps/web/src/pages/dashboard/campaigns/[id]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function EditCampaignPage() {
3434
title: campaign.campaign.title,
3535
description: campaign.campaign.description ?? "",
3636
subject: campaign.campaign.subject ?? "",
37-
templateId: campaign.campaign.templateId ?? "",
37+
templateId: campaign.campaign.templateId ?? "none",
3838
listIds: campaign.campaign.CampaignLists?.map((cl) => cl.List.id) ?? [],
3939
openTracking: campaign.campaign.openTracking ?? true,
4040
content: campaign.campaign.content ?? "",

apps/web/src/pages/dashboard/campaigns/[id]/schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const campaignSchema = z.object({
77
templateId: z
88
.string()
99
.optional()
10-
.transform((val) => (val === "" ? null : val)),
10+
.transform((val) => (val === "none" ? null : val)),
1111
listIds: z.array(z.string()),
1212
content: z.string().optional(),
1313
openTracking: z.boolean().optional(),

apps/web/src/pages/dashboard/campaigns/[id]/tabs/settings-tab.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export const SettingsTab = () => {
5757
title: campaignQuery.data?.campaign?.title || "",
5858
description: campaignQuery.data?.campaign?.description || "",
5959
subject: campaignQuery.data?.campaign?.subject || "",
60-
templateId: campaignQuery.data?.campaign?.templateId || "",
60+
templateId: campaignQuery.data?.campaign?.templateId || "none",
6161
openTracking: campaignQuery.data?.campaign?.openTracking || false,
6262
listIds:
6363
campaignQuery.data?.campaign?.CampaignLists?.map(
@@ -174,6 +174,11 @@ export const SettingsTab = () => {
174174
</SelectTrigger>
175175
</FormControl>
176176
<SelectContent>
177+
<SelectItem value="none">
178+
<div className="flex items-center gap-2">
179+
No Template
180+
</div>
181+
</SelectItem>
177182
{templates?.templates.map((template) => (
178183
<SelectItem key={template.id} value={template.id}>
179184
<div className="flex items-center gap-2">

apps/web/src/pages/dashboard/page.tsx renamed to apps/web/src/pages/dashboard/dashboard/dashboard.tsx

Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
MousePointer,
1414
RefreshCw,
1515
} from "lucide-react"
16-
import { Line, LineChart, ResponsiveContainer, Tooltip, XAxis } from "recharts"
1716
import {
1817
Card,
1918
CardContent,
@@ -29,6 +28,7 @@ import { Link } from "react-router"
2928
import { CardSkeleton, WithTooltip, CenteredLoader } from "@/components"
3029
import dayjs from "dayjs"
3130
import { IconExclamationCircle } from "@tabler/icons-react"
31+
import { SubscriberGrowthChart } from "./subscriber-growth-chart"
3232

3333
const statusConfig = {
3434
QUEUED: {
@@ -248,48 +248,9 @@ export function DashboardPage() {
248248

249249
{/* Charts */}
250250
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
251-
<Card hoverEffect className="col-span-4">
252-
<CardHeader>
253-
<CardTitle>Subscriber Growth</CardTitle>
254-
<CardDescription>
255-
New subscribers over time{" "}
256-
<span className="text-xs text-muted-foreground">(Daily)</span>
257-
</CardDescription>
258-
</CardHeader>
259-
<CardContent>
260-
<ResponsiveContainer width="100%" height={350}>
261-
<LineChart data={dashboard.subscriberGrowth}>
262-
<XAxis
263-
dataKey="date"
264-
stroke="#888888"
265-
fontSize={12}
266-
tickLine={false}
267-
axisLine={false}
268-
tickFormatter={(value) => {
269-
return new Date(value).toLocaleDateString()
270-
}}
271-
/>
272-
<Tooltip
273-
formatter={(value: number) => [
274-
value.toLocaleString(),
275-
"Subscribers",
276-
]}
277-
labelFormatter={(label) =>
278-
new Date(label).toLocaleDateString()
279-
}
280-
/>
281-
<Line
282-
type="monotone"
283-
dataKey="count"
284-
stroke="#8884d8"
285-
strokeWidth={2}
286-
/>
287-
</LineChart>
288-
</ResponsiveContainer>
289-
</CardContent>
290-
</Card>
251+
<SubscriberGrowthChart />
291252

292-
<Card hoverEffect className="col-span-3">
253+
<Card hoverEffect className="col-span-4 md:col-span-3">
293254
<CardHeader>
294255
<CardTitle>Message Status</CardTitle>
295256
<CardDescription>Delivery status of your messages</CardDescription>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./dashboard"
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { useSession } from "@/hooks"
2+
import { trpc } from "@/trpc"
3+
import {
4+
CardContent,
5+
CardDescription,
6+
CardTitle,
7+
CardHeader,
8+
ChartContainer,
9+
ChartTooltip,
10+
ChartTooltipContent,
11+
ChartConfig,
12+
CardFooter,
13+
Card,
14+
Skeleton,
15+
} from "@repo/ui"
16+
import dayjs from "dayjs"
17+
import { ArrowDown, TrendingUp } from "lucide-react"
18+
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
19+
20+
const chartConfig = {
21+
totalSubscribers: {
22+
label: "Total Subscribers",
23+
color: "hsl(var(--chart-1))",
24+
},
25+
} satisfies ChartConfig
26+
27+
export const SubscriberGrowthChart = () => {
28+
const { organization } = useSession()
29+
30+
const { data: analytics, isLoading: analyticsLoading } =
31+
trpc.stats.getStats.useQuery(
32+
{
33+
organizationId: organization?.id ?? "",
34+
},
35+
{
36+
enabled: !!organization?.id,
37+
}
38+
)
39+
40+
const { data: dashboard, isLoading: dashboardLoading } =
41+
trpc.dashboard.getStats.useQuery(
42+
{
43+
organizationId: organization?.id ?? "",
44+
},
45+
{
46+
enabled: !!organization?.id,
47+
}
48+
)
49+
50+
const isLoading = analyticsLoading || dashboardLoading
51+
52+
return (
53+
<Card hoverEffect className="col-span-4">
54+
<CardHeader>
55+
<CardTitle>Subscriber Growth</CardTitle>
56+
<CardDescription>
57+
New subscribers over time{" "}
58+
<span className="text-xs text-muted-foreground">(Daily)</span>
59+
</CardDescription>
60+
</CardHeader>
61+
<CardContent>
62+
{isLoading ? (
63+
<Skeleton className="h-[200px] w-full" />
64+
) : (
65+
<ChartContainer config={chartConfig}>
66+
<AreaChart
67+
accessibilityLayer
68+
data={dashboard?.subscriberGrowth || []}
69+
margin={{
70+
left: 12,
71+
right: 12,
72+
}}
73+
>
74+
<CartesianGrid vertical={false} />
75+
<XAxis
76+
dataKey="date"
77+
tickLine={false}
78+
axisLine={false}
79+
tickMargin={8}
80+
tickFormatter={(value) => dayjs(value).format("DD MMM")}
81+
/>
82+
<ChartTooltip
83+
cursor={false}
84+
content={<ChartTooltipContent indicator="dot" />}
85+
/>
86+
<Area
87+
dataKey="count"
88+
type="natural"
89+
fill="var(--color-totalSubscribers)"
90+
fillOpacity={0.4}
91+
stroke="var(--color-totalSubscribers)"
92+
stackId="a"
93+
/>
94+
</AreaChart>
95+
</ChartContainer>
96+
)}
97+
</CardContent>
98+
<CardFooter>
99+
{isLoading ? (
100+
<Skeleton className="h-10 w-full" />
101+
) : (
102+
<div className="flex w-full items-start gap-2 text-sm">
103+
<div className="grid gap-2">
104+
<div className="flex items-center gap-2 font-medium leading-none">
105+
{(analytics?.subscribers?.newThisMonth || 0) >= 0 ? (
106+
<>
107+
Trending up by {analytics?.subscribers.newThisMonth}% this
108+
month <TrendingUp className="h-4 w-4" />
109+
</>
110+
) : (
111+
<>
112+
Trending down by{" "}
113+
{Math.abs(analytics?.subscribers.newThisMonth || 0)}% this
114+
month <ArrowDown className="h-4 w-4" />
115+
</>
116+
)}
117+
</div>
118+
<div className="flex items-center gap-2 leading-none text-muted-foreground">
119+
Last 30 days
120+
</div>
121+
</div>
122+
</div>
123+
)}
124+
</CardFooter>
125+
</Card>
126+
)
127+
}

apps/web/src/pages/dashboard/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export * from "./layout"
2-
export * from "./page"
2+
export * from "./dashboard"
33
export * from "./subscribers"
44
export * from "./campaigns"
55
export * from "./templates"

0 commit comments

Comments
 (0)