Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Subheader1,
} from "@courselit/page-primitives";
import Link from "next/link";
import { UNNAMED_USER } from "@ui-config/strings";

export function BlogContentCard({ product }: { product: Course }) {
const { theme: uiTheme } = useContext(ThemeContext);
Expand Down Expand Up @@ -50,7 +51,10 @@ export function BlogContentCard({ product }: { product: Course }) {
height="h-8"
/>
<Subheader1 theme={theme}>
{truncate(product.user?.name || "Unnamed", 20)}
{truncate(
product.user?.name || UNNAMED_USER,
20,
)}
</Subheader1>
</div>
</div>
Expand Down
41 changes: 41 additions & 0 deletions apps/web/app/(with-contexts)/(with-layout)/first-run-popup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"use client";

import {
AlertDialog,
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@components/ui/alert-dialog";
import { AlertDialogCancel } from "@radix-ui/react-alert-dialog";
import Link from "next/link";
import { useState } from "react";

export default function FirstRunPopup() {
const [open, setOpen] = useState(true);

return (
<AlertDialog open={open}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Welcome to your new school! 🎉
</AlertDialogTitle>
<AlertDialogDescription>
You are almost ready to monetize your knowledge.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => setOpen(false)}>
I&apos;ll do it on my own
</AlertDialogCancel>
<AlertDialogAction asChild>
<Link href="/dashboard/get-set-up">Continue setup</Link>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}
39 changes: 27 additions & 12 deletions apps/web/app/(with-contexts)/(with-layout)/login/login-form.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"use client";

import { ServerConfigContext, ThemeContext } from "@components/contexts";
import {
AddressContext,
ServerConfigContext,
ThemeContext,
} from "@components/contexts";
import {
Button,
Caption,
Expand All @@ -13,12 +17,7 @@ import {
import { useContext, useState } from "react";
import { FormEvent } from "react";
import { signIn } from "next-auth/react";
import {
Form,
// FormField,
// FormSubmit,
useToast,
} from "@courselit/components-library";
import { Form, useToast } from "@courselit/components-library";
import {
BTN_LOGIN,
BTN_LOGIN_GET_CODE,
Expand All @@ -34,6 +33,10 @@ import Link from "next/link";
import { TriangleAlert } from "lucide-react";
import { useRecaptcha } from "@/hooks/use-recaptcha";
import RecaptchaScriptLoader from "@/components/recaptcha-script-loader";
import { checkPermission } from "@courselit/utils";
import { Profile } from "@courselit/common-models";
import { getUserProfile } from "../../helpers";
import { ADMIN_PERMISSIONS } from "@ui-config/constants";

export default function LoginForm({ redirectTo }: { redirectTo?: string }) {
const { theme } = useContext(ThemeContext);
Expand All @@ -45,6 +48,7 @@ export default function LoginForm({ redirectTo }: { redirectTo?: string }) {
const { toast } = useToast();
const serverConfig = useContext(ServerConfigContext);
const { executeRecaptcha } = useRecaptcha();
const address = useContext(AddressContext);

const requestCode = async function (e: FormEvent) {
e.preventDefault();
Expand Down Expand Up @@ -152,13 +156,28 @@ export default function LoginForm({ redirectTo }: { redirectTo?: string }) {
if (response?.error) {
setError(`Can't sign you in at this time`);
} else {
window.location.href = redirectTo || "/dashboard/my-content";
window.location.href =
redirectTo ||
getRedirectURLBasedOnProfile(
await getUserProfile(address.backend),
);
}
} finally {
setLoading(false);
}
};

const getRedirectURLBasedOnProfile = (profile: Profile) => {
if (
profile?.userId &&
checkPermission(profile.permissions!, ADMIN_PERMISSIONS)
) {
return "/dashboard/overview";
} else {
return "/dashboard/my-content";
}
};

return (
<Section theme={theme.theme}>
<div className="flex flex-col gap-4 min-h-[80vh]">
Expand Down Expand Up @@ -251,10 +270,6 @@ export default function LoginForm({ redirectTo }: { redirectTo?: string }) {
theme={theme.theme}
/>
<div className="flex justify-center">
{/* <FormSubmit
text={loading ? LOADING : BTN_LOGIN}
disabled={loading}
/> */}
<Button
theme={theme.theme}
disabled={loading}
Expand Down
10 changes: 3 additions & 7 deletions apps/web/app/(with-contexts)/(with-layout)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,11 @@ export default async function LoginPage({
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}) {
const session = await auth();
const redirectTo = (await searchParams).redirect;
const redirectTo = (await searchParams).redirect as string | undefined;

if (session) {
redirect(
typeof redirectTo === "string"
? redirectTo
: "/dashboard/my-content",
);
redirect(redirectTo || "/dashboard");
}

return <LoginForm />;
return <LoginForm redirectTo={redirectTo} />;
}
21 changes: 15 additions & 6 deletions apps/web/app/(with-contexts)/(with-layout)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getAddressFromHeaders } from "@/app/actions";
import ClientSidePage from "./p/[id]/client-side-page";
import { headers } from "next/headers";
import type { Metadata, ResolvingMetadata } from "next";
import FirstRunPopup from "./first-run-popup";

type Props = {
params: {
Expand Down Expand Up @@ -61,18 +62,26 @@ export async function generateMetadata(
};
}

export default async function Page() {
export default async function Page({
searchParams,
}: {
searchParams: Promise<{ firstrun?: string }>;
}) {
const address = await getAddressFromHeaders(headers);
const siteInfo = await getFullSiteSetup(address, "homepage");
if (!siteInfo) {
return null;
}
const firstRun = (await searchParams).firstrun === "1";

return (
<ClientSidePage
page={siteInfo.page}
siteinfo={siteInfo.settings}
theme={siteInfo.theme}
/>
<>
<ClientSidePage
page={siteInfo.page}
siteinfo={siteInfo.settings}
theme={siteInfo.theme}
/>
{firstRun && <FirstRunPopup />}
</>
);
}
46 changes: 46 additions & 0 deletions apps/web/app/(with-contexts)/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"use server";

import { auth } from "@/auth";
import { getUser } from "@/graphql/users/logic";
import { Profile, User } from "@courselit/common-models";
import GQLContext from "@models/GQLContext";
import { Types } from "mongoose";

export async function getProfile(): Promise<Profile | null> {
const session = await auth();
if (!session) {
return null;
}

const userId = (session?.user as any)?.userId;
const domainId = (session?.user as any)?.domain;
const user = await getUser(userId, {
user: {
userId,
},
subdomain: {
_id: domainId,
},
} as GQLContext);

if (!isSelf(user)) {
return null;
}

return {
name: user.name || "",
id: user._id.toString(),
fetched: true,
purchases: user.purchases,
email: user.email,
bio: user.bio,
permissions: user.permissions,
userId: user.userId,
subscribedToUpdates: user.subscribedToUpdates,
avatar: user.avatar,
};
}

function isSelf(user: any): user is User & { _id: Types.ObjectId } {
return !!user?.userId;
}
93 changes: 93 additions & 0 deletions apps/web/app/(with-contexts)/dashboard/(sidebar)/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"use server";

import { auth } from "@/auth";
import { Domain, Page } from "@courselit/common-models";
import DomainModel from "@models/Domain";
import { ObjectId } from "mongodb";
import { getProfile } from "../../action";
import { hasPermissionToAccessSetupChecklist } from "@/lib/utils";
import CourseModel from "@models/Course";
import PageModel from "@models/Page";
import constants from "@config/constants";

const DEFAULT_PAGE_CONTENT =
"This is the default page created for you by CourseLit";

export async function getSetupChecklist(): Promise<{
checklist: string[];
total: number;
} | null> {
const session = await auth();
if (!session) {
return null;
}

try {
const domain = await DomainModel.findOne<Domain>(
{
_id: new ObjectId((session.user as any)?.domain!),
},
{
_id: 1,
name: 1,
"settings.title": 1,
"settings.currencyISOCode": 1,
"settings.paymentMethod": 1,
},
).lean();
if (!domain) {
return null;
}

const user = await getProfile();
if (!user) {
return null;
}

if (!hasPermissionToAccessSetupChecklist(user.permissions)) {
return null;
}

const [publishedProducts, homePage] = await Promise.all([
CourseModel.countDocuments({
domain: domain._id,
published: true,
}),
PageModel.findOne(
{
domain: domain._id,
pageId: "homepage",
},
{
"layout.name": 1,
"layout.settings": 1,
},
).lean() as unknown as Page,
]);

const checklist = {
branding: constants.multitenant
? domain.settings.title === domain.name
: domain.settings.title ===
constants.schoolNameForSingleTenancy,
payment: !(
domain.settings.currencyISOCode && domain.settings.paymentMethod
),
product: publishedProducts === 0,
page: homePage?.layout
.filter((x) => x.name === "rich-text")
.some((block) =>
JSON.stringify(block.settings?.text || "").includes(
DEFAULT_PAGE_CONTENT,
),
),
};

return {
checklist: Object.keys(checklist).filter((x) => checklist[x]),
total: Object.keys(checklist).length,
};
} catch (err: any) {
return null;
}
}
17 changes: 8 additions & 9 deletions apps/web/app/(with-contexts)/dashboard/(sidebar)/blogs/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import DashboardContent from "@components/admin/dashboard-content";
import LoadingScreen from "@components/admin/loading-screen";
import { ProfileContext } from "@components/contexts";
import { UIConstants } from "@courselit/common-models";
import { checkPermission } from "@courselit/utils";
import { MANAGE_BLOG_PAGE_HEADING } from "@ui-config/strings";
import { useContext } from "react";
const { permissions } = UIConstants;
Expand All @@ -15,18 +14,18 @@ const breadcrumbs = [{ label: MANAGE_BLOG_PAGE_HEADING, href: "#" }];
export default function Page() {
const { profile } = useContext(ProfileContext);

if (
!profile ||
!checkPermission(profile?.permissions!, [
permissions.manageAnyCourse,
permissions.manageCourse,
])
) {
if (!profile) {
return <LoadingScreen />;
}

return (
<DashboardContent breadcrumbs={breadcrumbs}>
<DashboardContent
breadcrumbs={breadcrumbs}
permissions={[
permissions.manageAnyCourse,
permissions.manageCourse,
]}
>
<Blogs />
</DashboardContent>
);
Expand Down
Loading
Loading