Skip to content
Closed
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
136 changes: 127 additions & 9 deletions apps/web/app/(with-contexts)/course/[slug]/[id]/[lesson]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"use client";

import { LessonViewer } from "@components/public/lesson-viewer";
import { LessonDiscussionPanel } from "@components/public/lesson-discussion-panel";
import { redirect } from "next/navigation";
import { useContext, use } from "react";
import { useContext, use, useEffect, useState } from "react";
import { ProfileContext, AddressContext } from "@components/contexts";
import { Profile } from "@courselit/common-models";
import { FetchBuilder } from "@courselit/utils";

export default function LessonPage(props: {
params: Promise<{
Expand All @@ -17,19 +19,135 @@ export default function LessonPage(props: {
const { slug, id, lesson } = params;
const { profile, setProfile } = useContext(ProfileContext);
const address = useContext(AddressContext);
const [discussionsEnabled, setDiscussionsEnabled] = useState(false);

useEffect(() => {
if (id) {
loadCourseDiscussionsStatus();
}
}, [id]);

const loadCourseDiscussionsStatus = async () => {
try {
const query = `
query {
course: getCourse(id: "${id}") {
discussions
}
}
`;
const fetch = new FetchBuilder()
.setUrl(`${address.backend}/api/graph`)
.setPayload(query)
.setIsGraphQLEndpoint(true)
.build();
const response = await fetch.exec();
if (response.course) {
setDiscussionsEnabled(!!response.course.discussions);
}
} catch (err) {
// ignore
}
};

if (!lesson) {
redirect(`/course/${slug}/${id}`);
}

return (
<LessonViewer
lessonId={lesson as string}
slug={slug}
profile={profile as Profile}
setProfile={setProfile}
address={address}
productId={id}
/>
<div
className={`flex gap-0 ${discussionsEnabled ? "lg:pr-[340px]" : ""}`}
>
<div className="flex-1 min-w-0">
<LessonViewer
lessonId={lesson as string}
slug={slug}
profile={profile as Profile}
setProfile={setProfile}
address={address}
productId={id}
/>
</div>
{discussionsEnabled && (
<>
<div className="hidden lg:block fixed right-0 top-16 bottom-0 w-[340px] border-l bg-background overflow-hidden">
<LessonDiscussionPanel
courseId={id}
lessonId={lesson as string}
slug={slug}
/>
</div>
<div className="lg:hidden fixed bottom-6 right-6 z-50">
<MobileDiscussionDrawer>
<LessonDiscussionPanel
courseId={id}
lessonId={lesson as string}
slug={slug}
/>
</MobileDiscussionDrawer>
</div>
</>
)}
</div>
);
}

function MobileDiscussionDrawer({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(false);

return (
<>
<button
onClick={() => setOpen(!open)}
className="bg-primary text-primary-foreground rounded-full w-14 h-14 flex items-center justify-center shadow-lg hover:bg-primary/90 transition-colors"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
</svg>
</button>
{open && (
<div className="fixed inset-0 z-50">
<div
className="absolute inset-0 bg-black/50"
onClick={() => setOpen(false)}
/>
<div className="absolute right-0 top-0 bottom-0 w-full max-w-sm bg-background shadow-xl">
<div className="flex items-center justify-between px-4 py-3 border-b">
<h3 className="font-semibold">Discussions</h3>
<button
onClick={() => setOpen(false)}
className="text-muted-foreground hover:text-foreground"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
</button>
</div>
<div className="h-[calc(100%-49px)]">{children}</div>
</div>
</div>
)}
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"use client";

import { useState } from "react";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Separator } from "@/components/ui/separator";
import { useToast } from "@courselit/components-library";
import {
APP_MESSAGE_COURSE_SAVED,

Check failure on line 9 in apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/manage/components/course-discussions.tsx

View workflow job for this annotation

GitHub Actions / lint

'APP_MESSAGE_COURSE_SAVED' is defined but never used

Check failure on line 9 in apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/manage/components/course-discussions.tsx

View workflow job for this annotation

GitHub Actions / lint

'APP_MESSAGE_COURSE_SAVED' is defined but never used
TOAST_TITLE_ERROR,
TOAST_TITLE_SUCCESS,
} from "@ui-config/strings";
import { responses } from "@/config/strings";
import { useGraphQLFetch } from "@/hooks/use-graphql-fetch";

const MUTATION_UPDATE_DISCUSSIONS = `
mutation UpdateDiscussions($courseId: String!, $discussions: Boolean!) {
updateCourse(courseData: { id: $courseId, discussions: $discussions }) {
courseId
}
}
`;

interface CourseDiscussionsProps {
product: any;
}

export default function CourseDiscussions({ product }: CourseDiscussionsProps) {
const { toast } = useToast();
const fetch = useGraphQLFetch();
const [loading, setLoading] = useState(false);
const [discussionsEnabled, setDiscussionsEnabled] = useState(
product?.discussions || false,
);

const handleDiscussionsChange = async () => {
const newValue = !discussionsEnabled;
const previousValue = discussionsEnabled;
setDiscussionsEnabled(newValue);

if (!product?.courseId) return;

try {
setLoading(true);
const response = await fetch
.setPayload({
query: MUTATION_UPDATE_DISCUSSIONS,
variables: {
courseId: product.courseId,
discussions: newValue,
},
})
.build()
.exec();

if (response?.updateCourse) {
toast({
title: TOAST_TITLE_SUCCESS,
description: newValue
? responses.discussions_enabled
: responses.discussions_disabled,
});
}
} catch (err: any) {
setDiscussionsEnabled(previousValue);
toast({
title: TOAST_TITLE_ERROR,
description: err.message,
variant: "destructive",
});
} finally {
setLoading(false);
}
};

return (
<div className="space-y-8">
<div className="space-y-6" id="discussions">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label className="text-base font-semibold">
{responses.discussions_toggle_label}
</Label>
<p className="text-sm text-muted-foreground">
{responses.discussions_toggle_description}
</p>
</div>
<Switch
checked={discussionsEnabled}
onCheckedChange={handleDiscussionsChange}
disabled={loading}
/>
</div>
</div>
<Separator />
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import PaymentPlans from "./components/payment-plans";
import DownloadOptions from "./components/download-options";
import ProductPublishing from "./components/product-publishing";
import Certificates from "./components/certificates";
import CourseDiscussions from "./components/course-discussions";
import ProductDeletion from "./components/product-deletion";

const { permissions } = UIConstants;
Expand Down Expand Up @@ -109,6 +110,7 @@ export default function SettingsPage() {
/>
<ProductPublishing product={product} />
<Certificates product={product} productId={productId || ""} />
<CourseDiscussions product={product} />
<ProductDeletion product={product} />
</div>
</DashboardContent>
Expand Down
7 changes: 4 additions & 3 deletions apps/web/components/admin/settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,10 @@ const Settings = (props: SettingsProps) => {
newSettings.paymentMethod || PAYMENT_METHOD_NONE
}
options={[
{
label: SITE_SETTINGS_PAYMENT_METHOD_NONE_LABEL,
value: PAYMENT_METHOD_NONE,
},
{
label: capitalize(
PAYMENT_METHOD_STRIPE.toLowerCase(),
Expand Down Expand Up @@ -857,9 +861,6 @@ const Settings = (props: SettingsProps) => {
}),
)
}
placeholderMessage={
SITE_SETTINGS_PAYMENT_METHOD_NONE_LABEL
}
disabled={!newSettings.currencyISOCode}
/>

Expand Down
Loading
Loading