Skip to content

Commit 400f281

Browse files
author
Rajat Saxena
committed
Onboarding
1 parent 17a0b47 commit 400f281

File tree

45 files changed

+828
-305
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+828
-305
lines changed

apps/web/app/(with-contexts)/(with-layout)/blog/content-card.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
Subheader1,
1212
} from "@courselit/page-primitives";
1313
import Link from "next/link";
14+
import { UNNAMED_USER } from "@ui-config/strings";
1415

1516
export function BlogContentCard({ product }: { product: Course }) {
1617
const { theme: uiTheme } = useContext(ThemeContext);
@@ -50,7 +51,10 @@ export function BlogContentCard({ product }: { product: Course }) {
5051
height="h-8"
5152
/>
5253
<Subheader1 theme={theme}>
53-
{truncate(product.user?.name || "Unnamed", 20)}
54+
{truncate(
55+
product.user?.name || UNNAMED_USER,
56+
20,
57+
)}
5458
</Subheader1>
5559
</div>
5660
</div>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"use client";
2+
3+
import {
4+
AlertDialog,
5+
AlertDialogAction,
6+
AlertDialogContent,
7+
AlertDialogDescription,
8+
AlertDialogFooter,
9+
AlertDialogHeader,
10+
AlertDialogTitle,
11+
} from "@components/ui/alert-dialog";
12+
import { AlertDialogCancel } from "@radix-ui/react-alert-dialog";
13+
import Link from "next/link";
14+
import { useState } from "react";
15+
16+
export default function FirstRunPopup() {
17+
const [open, setOpen] = useState(true);
18+
19+
return (
20+
<AlertDialog open={open}>
21+
<AlertDialogContent>
22+
<AlertDialogHeader>
23+
<AlertDialogTitle>
24+
Welcome to your new school! 🎉
25+
</AlertDialogTitle>
26+
<AlertDialogDescription>
27+
You are almost ready to monetize your knowledge.
28+
</AlertDialogDescription>
29+
</AlertDialogHeader>
30+
<AlertDialogFooter>
31+
<AlertDialogCancel onClick={() => setOpen(false)}>
32+
I&apos;ll do it on my own
33+
</AlertDialogCancel>
34+
<AlertDialogAction asChild>
35+
<Link href="/dashboard/get-set-up">Continue setup</Link>
36+
</AlertDialogAction>
37+
</AlertDialogFooter>
38+
</AlertDialogContent>
39+
</AlertDialog>
40+
);
41+
}

apps/web/app/(with-contexts)/(with-layout)/login/login-form.tsx

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
"use client";
22

3-
import { ServerConfigContext, ThemeContext } from "@components/contexts";
3+
import {
4+
AddressContext,
5+
ServerConfigContext,
6+
ThemeContext,
7+
} from "@components/contexts";
48
import {
59
Button,
610
Caption,
@@ -13,12 +17,7 @@ import {
1317
import { useContext, useState } from "react";
1418
import { FormEvent } from "react";
1519
import { signIn } from "next-auth/react";
16-
import {
17-
Form,
18-
// FormField,
19-
// FormSubmit,
20-
useToast,
21-
} from "@courselit/components-library";
20+
import { Form, useToast } from "@courselit/components-library";
2221
import {
2322
BTN_LOGIN,
2423
BTN_LOGIN_GET_CODE,
@@ -34,6 +33,10 @@ import Link from "next/link";
3433
import { TriangleAlert } from "lucide-react";
3534
import { useRecaptcha } from "@/hooks/use-recaptcha";
3635
import RecaptchaScriptLoader from "@/components/recaptcha-script-loader";
36+
import { checkPermission } from "@courselit/utils";
37+
import { Profile } from "@courselit/common-models";
38+
import { getUserProfile } from "../../helpers";
39+
import { ADMIN_PERMISSIONS } from "@ui-config/constants";
3740

3841
export default function LoginForm({ redirectTo }: { redirectTo?: string }) {
3942
const { theme } = useContext(ThemeContext);
@@ -45,6 +48,7 @@ export default function LoginForm({ redirectTo }: { redirectTo?: string }) {
4548
const { toast } = useToast();
4649
const serverConfig = useContext(ServerConfigContext);
4750
const { executeRecaptcha } = useRecaptcha();
51+
const address = useContext(AddressContext);
4852

4953
const requestCode = async function (e: FormEvent) {
5054
e.preventDefault();
@@ -152,13 +156,28 @@ export default function LoginForm({ redirectTo }: { redirectTo?: string }) {
152156
if (response?.error) {
153157
setError(`Can't sign you in at this time`);
154158
} else {
155-
window.location.href = redirectTo || "/dashboard/my-content";
159+
window.location.href =
160+
redirectTo ||
161+
getRedirectURLBasedOnProfile(
162+
await getUserProfile(address.backend),
163+
);
156164
}
157165
} finally {
158166
setLoading(false);
159167
}
160168
};
161169

170+
const getRedirectURLBasedOnProfile = (profile: Profile) => {
171+
if (
172+
profile?.userId &&
173+
checkPermission(profile.permissions!, ADMIN_PERMISSIONS)
174+
) {
175+
return "/dashboard/overview";
176+
} else {
177+
return "/dashboard/my-content";
178+
}
179+
};
180+
162181
return (
163182
<Section theme={theme.theme}>
164183
<div className="flex flex-col gap-4 min-h-[80vh]">
@@ -251,10 +270,6 @@ export default function LoginForm({ redirectTo }: { redirectTo?: string }) {
251270
theme={theme.theme}
252271
/>
253272
<div className="flex justify-center">
254-
{/* <FormSubmit
255-
text={loading ? LOADING : BTN_LOGIN}
256-
disabled={loading}
257-
/> */}
258273
<Button
259274
theme={theme.theme}
260275
disabled={loading}

apps/web/app/(with-contexts)/(with-layout)/login/page.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,11 @@ export default async function LoginPage({
88
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
99
}) {
1010
const session = await auth();
11-
const redirectTo = (await searchParams).redirect;
11+
const redirectTo = (await searchParams).redirect as string | undefined;
1212

1313
if (session) {
14-
redirect(
15-
typeof redirectTo === "string"
16-
? redirectTo
17-
: "/dashboard/my-content",
18-
);
14+
redirect(redirectTo || "/dashboard");
1915
}
2016

21-
return <LoginForm />;
17+
return <LoginForm redirectTo={redirectTo} />;
2218
}

apps/web/app/(with-contexts)/(with-layout)/page.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { getAddressFromHeaders } from "@/app/actions";
33
import ClientSidePage from "./p/[id]/client-side-page";
44
import { headers } from "next/headers";
55
import type { Metadata, ResolvingMetadata } from "next";
6+
import FirstRunPopup from "./first-run-popup";
67

78
type Props = {
89
params: {
@@ -61,18 +62,26 @@ export async function generateMetadata(
6162
};
6263
}
6364

64-
export default async function Page() {
65+
export default async function Page({
66+
searchParams,
67+
}: {
68+
searchParams: Promise<{ firstrun?: string }>;
69+
}) {
6570
const address = await getAddressFromHeaders(headers);
6671
const siteInfo = await getFullSiteSetup(address, "homepage");
6772
if (!siteInfo) {
6873
return null;
6974
}
75+
const firstRun = (await searchParams).firstrun === "1";
7076

7177
return (
72-
<ClientSidePage
73-
page={siteInfo.page}
74-
siteinfo={siteInfo.settings}
75-
theme={siteInfo.theme}
76-
/>
78+
<>
79+
<ClientSidePage
80+
page={siteInfo.page}
81+
siteinfo={siteInfo.settings}
82+
theme={siteInfo.theme}
83+
/>
84+
{firstRun && <FirstRunPopup />}
85+
</>
7786
);
7887
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"use server";
2+
3+
import { auth } from "@/auth";
4+
import { getUser } from "@/graphql/users/logic";
5+
import { Profile, User } from "@courselit/common-models";
6+
import GQLContext from "@models/GQLContext";
7+
import { Types } from "mongoose";
8+
9+
export async function getProfile(): Promise<Profile | null> {
10+
const session = await auth();
11+
if (!session) {
12+
return null;
13+
}
14+
15+
const userId = (session?.user as any)?.userId;
16+
const domainId = (session?.user as any)?.domain;
17+
const user = await getUser(userId, {
18+
user: {
19+
userId,
20+
},
21+
subdomain: {
22+
_id: domainId,
23+
},
24+
} as GQLContext);
25+
26+
if (!isSelf(user)) {
27+
return null;
28+
}
29+
30+
return {
31+
name: user.name || "",
32+
id: user._id.toString(),
33+
fetched: true,
34+
purchases: user.purchases,
35+
email: user.email,
36+
bio: user.bio,
37+
permissions: user.permissions,
38+
userId: user.userId,
39+
subscribedToUpdates: user.subscribedToUpdates,
40+
avatar: user.avatar,
41+
};
42+
}
43+
44+
function isSelf(user: any): user is User & { _id: Types.ObjectId } {
45+
return !!user?.userId;
46+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"use server";
2+
3+
import { auth } from "@/auth";
4+
import { Domain, Page } from "@courselit/common-models";
5+
import DomainModel from "@models/Domain";
6+
import { ObjectId } from "mongodb";
7+
import { getProfile } from "../../action";
8+
import { hasPermissionToAccessSetupChecklist } from "@/lib/utils";
9+
import CourseModel from "@models/Course";
10+
import PageModel from "@models/Page";
11+
import constants from "@config/constants";
12+
13+
const DEFAULT_PAGE_CONTENT =
14+
"This is the default page created for you by CourseLit";
15+
16+
export async function getSetupChecklist(): Promise<{
17+
checklist: string[];
18+
total: number;
19+
} | null> {
20+
const session = await auth();
21+
if (!session) {
22+
return null;
23+
}
24+
25+
try {
26+
const domain = await DomainModel.findOne<Domain>(
27+
{
28+
_id: new ObjectId((session.user as any)?.domain!),
29+
},
30+
{
31+
_id: 1,
32+
name: 1,
33+
"settings.title": 1,
34+
"settings.currencyISOCode": 1,
35+
"settings.paymentMethod": 1,
36+
},
37+
).lean();
38+
if (!domain) {
39+
return null;
40+
}
41+
42+
const user = await getProfile();
43+
if (!user) {
44+
return null;
45+
}
46+
47+
if (!hasPermissionToAccessSetupChecklist(user.permissions)) {
48+
return null;
49+
}
50+
51+
const [publishedProducts, homePage] = await Promise.all([
52+
CourseModel.countDocuments({
53+
domain: domain._id,
54+
published: true,
55+
}),
56+
PageModel.findOne(
57+
{
58+
domain: domain._id,
59+
pageId: "homepage",
60+
},
61+
{
62+
"layout.name": 1,
63+
"layout.settings": 1,
64+
},
65+
).lean() as unknown as Page,
66+
]);
67+
68+
const checklist = {
69+
branding: constants.multitenant
70+
? domain.settings.title === domain.name
71+
: domain.settings.title ===
72+
constants.schoolNameForSingleTenancy,
73+
payment: !(
74+
domain.settings.currencyISOCode && domain.settings.paymentMethod
75+
),
76+
product: publishedProducts === 0,
77+
page: homePage?.layout
78+
.filter((x) => x.name === "rich-text")
79+
.some((block) =>
80+
JSON.stringify(block.settings?.text || "").includes(
81+
DEFAULT_PAGE_CONTENT,
82+
),
83+
),
84+
};
85+
86+
return {
87+
checklist: Object.keys(checklist).filter((x) => checklist[x]),
88+
total: Object.keys(checklist).length,
89+
};
90+
} catch (err: any) {
91+
return null;
92+
}
93+
}

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import DashboardContent from "@components/admin/dashboard-content";
55
import LoadingScreen from "@components/admin/loading-screen";
66
import { ProfileContext } from "@components/contexts";
77
import { UIConstants } from "@courselit/common-models";
8-
import { checkPermission } from "@courselit/utils";
98
import { MANAGE_BLOG_PAGE_HEADING } from "@ui-config/strings";
109
import { useContext } from "react";
1110
const { permissions } = UIConstants;
@@ -15,18 +14,18 @@ const breadcrumbs = [{ label: MANAGE_BLOG_PAGE_HEADING, href: "#" }];
1514
export default function Page() {
1615
const { profile } = useContext(ProfileContext);
1716

18-
if (
19-
!profile ||
20-
!checkPermission(profile?.permissions!, [
21-
permissions.manageAnyCourse,
22-
permissions.manageCourse,
23-
])
24-
) {
17+
if (!profile) {
2518
return <LoadingScreen />;
2619
}
2720

2821
return (
29-
<DashboardContent breadcrumbs={breadcrumbs}>
22+
<DashboardContent
23+
breadcrumbs={breadcrumbs}
24+
permissions={[
25+
permissions.manageAnyCourse,
26+
permissions.manageCourse,
27+
]}
28+
>
3029
<Blogs />
3130
</DashboardContent>
3231
);

0 commit comments

Comments
 (0)