Skip to content

Commit 4f33e53

Browse files
author
Rajat
committed
WIP: added admin side settings for certificates; added routes to show certificate
1 parent 4126039 commit 4f33e53

28 files changed

Lines changed: 694 additions & 248 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"use client";
2+
3+
import { Section } from "@courselit/page-primitives";
4+
import { useParams } from "next/navigation";
5+
import { ThemeContext } from "@components/contexts";
6+
import { useContext } from "react";
7+
8+
export default function AccomplishmentPage() {
9+
const params = useParams();
10+
const certId = params.certId;
11+
const { theme } = useContext(ThemeContext);
12+
13+
return (
14+
<Section theme={theme.theme}>
15+
<iframe
16+
src={`/certificate/internal/${certId}`}
17+
className="w-full h-[900px]"
18+
/>
19+
</Section>
20+
);
21+
}

apps/web/app/(with-contexts)/course/[slug]/[id]/page.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
import { useContext, useEffect, useState, use } from "react";
44
import { isEnrolled } from "@ui-lib/utils";
55
import { ArrowRight } from "@courselit/icons";
6-
import { COURSE_PROGRESS_START, ENROLL_BUTTON_TEXT } from "@ui-config/strings";
6+
import {
7+
COURSE_PROGRESS_START,
8+
ENROLL_BUTTON_TEXT,
9+
BTN_VIEW_CERTIFICATE,
10+
} from "@ui-config/strings";
711
import { checkPermission } from "@courselit/utils";
812
import { Profile, UIConstants } from "@courselit/common-models";
913
import {
@@ -20,6 +24,7 @@ import {
2024
SiteInfoContext,
2125
} from "@components/contexts";
2226
import { getProduct } from "./helpers";
27+
import { getUserProfile } from "@/app/(with-contexts)/helpers";
2328
const { permissions } = UIConstants;
2429

2530
export default function ProductPage(props: {
@@ -28,9 +33,10 @@ export default function ProductPage(props: {
2833
const params = use(props.params);
2934
const { id } = params;
3035
const [product, setProduct] = useState<any>(null);
31-
const { profile } = useContext(ProfileContext);
36+
const { profile, setProfile } = useContext(ProfileContext);
3237
const siteInfo = useContext(SiteInfoContext);
3338
const address = useContext(AddressContext);
39+
const [progress, setProgress] = useState<any>(null);
3440

3541
useEffect(() => {
3642
if (id) {
@@ -40,6 +46,19 @@ export default function ProductPage(props: {
4046
}
4147
}, [id]);
4248

49+
useEffect(() => {
50+
if (product) {
51+
getUserProfile(address.backend).then((profile) => {
52+
setProfile(profile);
53+
setProgress(
54+
profile.purchases?.find(
55+
(purchase) => purchase.courseId === product.courseId,
56+
),
57+
);
58+
});
59+
}
60+
}, [product]);
61+
4362
if (!profile) {
4463
return null;
4564
}
@@ -51,6 +70,11 @@ export default function ProductPage(props: {
5170
return (
5271
<div className="flex flex-col pb-[100px] lg:max-w-[40rem] xl:max-w-[48rem] mx-auto">
5372
<h1 className="text-4xl font-semibold mb-8">{product.title}</h1>
73+
{progress?.certificateId && (
74+
<Link href={`/certificate/${progress.certificateId}`}>
75+
<Button2>{BTN_VIEW_CERTIFICATE}</Button2>
76+
</Link>
77+
)}
5478
{!isEnrolled(product.courseId, profile as Profile) &&
5579
checkPermission(profile.permissions ?? [], [
5680
permissions.enrollInCourse,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"use client";
2+
3+
import DashboardContent from "@components/admin/dashboard-content";
4+
import {
5+
COURSE_SETTINGS_CARD_HEADER,
6+
CUSTOMIZE_CERTIFICATE_TEMPLATE,
7+
MANAGE_COURSES_PAGE_HEADING,
8+
} from "@ui-config/strings";
9+
import { truncate } from "@ui-lib/utils";
10+
import useProduct from "@/hooks/use-product";
11+
import { redirect, useParams } from "next/navigation";
12+
import { useEffect } from "react";
13+
import { UIConstants } from "@courselit/common-models";
14+
15+
export default function CertificatePage() {
16+
const params = useParams();
17+
const productId = params?.id as string;
18+
const { product } = useProduct(productId);
19+
const breadcrumbs = [
20+
{ label: MANAGE_COURSES_PAGE_HEADING, href: "/dashboard/products" },
21+
{
22+
label: product ? truncate(product.title || "", 20) || "..." : "...",
23+
href: `/dashboard/product/${productId}`,
24+
},
25+
{
26+
label: COURSE_SETTINGS_CARD_HEADER,
27+
href: `/dashboard/product/${productId}/manage`,
28+
},
29+
{ label: CUSTOMIZE_CERTIFICATE_TEMPLATE, href: "#" },
30+
];
31+
32+
useEffect(() => {
33+
if (
34+
product &&
35+
(!product.certificate ||
36+
product.type?.toLowerCase() !== UIConstants.COURSE_TYPE_COURSE)
37+
) {
38+
redirect(`/dashboard/product/${productId}/manage`);
39+
}
40+
}, [product]);
41+
42+
return (
43+
<DashboardContent breadcrumbs={breadcrumbs}>
44+
<div className="space-y-8">
45+
<div className="flex justify-between items-center">
46+
<h1 className="text-4xl font-semibold">Certificate</h1>
47+
</div>
48+
</div>
49+
</DashboardContent>
50+
);
51+
}

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

Lines changed: 41 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Label } from "@/components/ui/label";
77
import { Switch } from "@/components/ui/switch";
88
import { Separator } from "@/components/ui/separator";
99
import { Trash2, Loader2 } from "lucide-react";
10+
import Link from "next/link";
1011

1112
import {
1213
AlertDialog,
@@ -24,6 +25,7 @@ import {
2425
APP_MESSAGE_COURSE_SAVED,
2526
BTN_DELETE_COURSE,
2627
COURSE_SETTINGS_CARD_HEADER,
28+
CUSTOMIZE_CERTIFICATE_TEMPLATE,
2729
DANGER_ZONE_HEADER,
2830
MANAGE_COURSES_PAGE_HEADING,
2931
PRICING_EMAIL,
@@ -60,6 +62,7 @@ import {
6062
ProductPriceType,
6163
Profile,
6264
Constants,
65+
UIConstants,
6366
} from "@courselit/common-models";
6467
import { COURSE_TYPE_DOWNLOAD, MIMETYPE_IMAGE } from "@ui-config/constants";
6568
import useProduct from "@/hooks/use-product";
@@ -126,6 +129,13 @@ const MUTATIONS = {
126129
}
127130
}
128131
`,
132+
UPDATE_CERTIFICATE: `
133+
mutation UpdateCertificate($courseId: String!, $certificate: Boolean!) {
134+
updateCourse(courseData: { id: $courseId, certificate: $certificate }) {
135+
courseId
136+
}
137+
}
138+
`,
129139
};
130140

131141
const updateCourse = async (query: string, variables: any, address: string) => {
@@ -183,6 +193,7 @@ export default function SettingsPage() {
183193
costType: ProductPriceType;
184194
cost: number;
185195
leadMagnet: boolean;
196+
certificate: boolean;
186197
}>({
187198
name: "",
188199
description: TextEditorEmptyDoc,
@@ -192,6 +203,7 @@ export default function SettingsPage() {
192203
costType: PRICING_FREE,
193204
cost: 0,
194205
leadMagnet: false,
206+
certificate: false,
195207
});
196208
const breadcrumbs = [
197209
{ label: MANAGE_COURSES_PAGE_HEADING, href: "/dashboard/products" },
@@ -235,6 +247,7 @@ export default function SettingsPage() {
235247
(PRICING_FREE.toUpperCase() as ProductPriceType),
236248
cost: product?.cost || 0,
237249
leadMagnet: product?.leadMagnet || false,
250+
certificate: product?.certificate || false,
238251
});
239252
setRefresh(refresh + 1);
240253
setPaymentPlans(product?.paymentPlans || []);
@@ -267,6 +280,10 @@ export default function SettingsPage() {
267280
mutation: MUTATIONS.UPDATE_LEAD_MAGNET,
268281
variables: { leadMagnet: value },
269282
},
283+
certificate: {
284+
mutation: MUTATIONS.UPDATE_CERTIFICATE,
285+
variables: { certificate: value },
286+
},
270287
}[field];
271288

272289
if (!fieldConfig) return;
@@ -334,58 +351,6 @@ export default function SettingsPage() {
334351
);
335352
};
336353

337-
// const handleCostTypeChange = async (val: string) => {
338-
// setFormData((prev) => ({
339-
// ...prev,
340-
// costType: val as ProductPriceType,
341-
// }));
342-
343-
// if (!product?.courseId) return;
344-
345-
// await withErrorHandling(
346-
// () =>
347-
// updateCourse(
348-
// MUTATIONS.UPDATE_COST_TYPE,
349-
// {
350-
// id: product.courseId,
351-
// costType: val,
352-
// },
353-
// address.backend,
354-
// ),
355-
// setLoading,
356-
// toast,
357-
// );
358-
// };
359-
360-
// const debouncedUpdateCost = useCallback(
361-
// debounce((value: number, productId: string) => {
362-
// withErrorHandling(
363-
// () =>
364-
// updateCourse(
365-
// MUTATIONS.UPDATE_COST,
366-
// {
367-
// id: productId,
368-
// cost: value,
369-
// },
370-
// address.backend,
371-
// ),
372-
// setLoading,
373-
// toast,
374-
// );
375-
// }, 1000), // 1 second delay
376-
// [address.backend, toast], // Dependencies
377-
// );
378-
379-
// const handleCostChange = (value: number) => {
380-
// setFormData((prev) => ({
381-
// ...prev,
382-
// cost: value,
383-
// }));
384-
385-
// if (!product?.courseId) return;
386-
// debouncedUpdateCost(value, product.courseId);
387-
// };
388-
389354
const options: {
390355
label: string;
391356
value: string;
@@ -741,6 +706,30 @@ export default function SettingsPage() {
741706
disabled={!formData.isPublished}
742707
/>
743708
</div>
709+
{product?.type?.toLowerCase() === UIConstants.COURSE_TYPE_COURSE && (
710+
<div className="flex items-center justify-between">
711+
<div className="space-y-0.5">
712+
<Label className="text-base font-semibold">
713+
Certificate
714+
</Label>
715+
<p className="text-sm text-muted-foreground">
716+
Enable certificate for this course. {product?.certificate && (
717+
<Link href={`/dashboard/product/${productId}/manage/certificate`}>
718+
<span className="underline text-black">
719+
{CUSTOMIZE_CERTIFICATE_TEMPLATE}
720+
</span>
721+
</Link>
722+
)}
723+
</p>
724+
</div>
725+
<Switch
726+
checked={formData.certificate}
727+
onCheckedChange={() =>
728+
handleSwitchChange("certificate")
729+
}
730+
/>
731+
</div>
732+
)}
744733
</div>
745734

746735
<Separator />

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

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export default function Page() {
9494
});
9595
}
9696
};
97-
if (profile.userId && address.backend) {
97+
if (profile?.userId && address.backend) {
9898
getUser(profile.userId);
9999
}
100100
}, [profile, address.backend]);
@@ -106,17 +106,6 @@ export default function Page() {
106106
id: $id
107107
avatar: $avatar
108108
}) {
109-
id,
110-
name,
111-
userId,
112-
email,
113-
permissions,
114-
purchases {
115-
courseId,
116-
completedLessons,
117-
accessibleGroups
118-
},
119-
bio,
120109
avatar {
121110
mediaId,
122111
originalFileName,
@@ -135,7 +124,7 @@ export default function Page() {
135124
.setPayload({
136125
query: mutation,
137126
variables: {
138-
id: profile.id,
127+
id: profile!.id,
139128
avatar: media || null,
140129
},
141130
})
@@ -145,7 +134,10 @@ export default function Page() {
145134
try {
146135
const response = await fetch.exec();
147136
if (response.user) {
148-
setProfile(response.user);
137+
setProfile({
138+
...profile!,
139+
avatar: response.user.avatar,
140+
});
149141
}
150142
} catch (err: any) {
151143
toast({
@@ -172,9 +164,10 @@ export default function Page() {
172164
email,
173165
permissions,
174166
purchases {
175-
courseId,
176-
completedLessons,
167+
courseId
168+
completedLessons
177169
accessibleGroups
170+
certificateId
178171
},
179172
bio,
180173
avatar {
@@ -195,7 +188,7 @@ export default function Page() {
195188
.setPayload({
196189
query: mutation,
197190
variables: {
198-
id: profile.id,
191+
id: profile!.id,
199192
name,
200193
bio,
201194
},

0 commit comments

Comments
 (0)