Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions locales/en/learn.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"prev-lesson": "Previous Lesson",
"reflection-description-1": "Clicking \"Add Reflection\" will take you to QuranReflect, a platform for sharing personal reflections on the Quran. Unlike Tafsir (scholarly interpretation), reflections are personal insights and experiences related to the verses.",
"reflection-description-2": "If you post publicly, your reflection will be reviewed by the moderation team and become visible to the QuranReflect community.",
"start-here": "Start here",
"start-learning": "Start Learning",
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The translation key 'start-learning' is still defined in the locale file but is no longer used anywhere in the code after the simplification of the StartOrContinueLearning component. This is dead code that should be removed from locales/en/learn.json.

Suggested change
"start-learning": "Start Learning",

Copilot uses AI. Check for mistakes.
"tabs": {
"main": "Main Details",
Expand Down
26 changes: 5 additions & 21 deletions src/components/Course/Buttons/StartOrContinueLearning/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,11 @@ const StartOrContinueLearning: React.FC<Props> = ({ course, isHeaderButton = tru
const { t } = useTranslation('learn');
const { lessons, continueFromLesson, id, slug } = course;
const userType = getUserType();
/**
* there is a corner case when the user enrolls,
* goes back to main page then clicks start learning again,
* continueFromLesson is undefined since it has been cached from
* before the user enrolled.
*/
const redirectToLessonSlug = continueFromLesson || lessons?.[0]?.slug;
const router = useRouter();
const userCompletedAnyLesson = lessons.some((lesson) => lesson.isCompleted === true);

// Navigate to the lesson user should continue from, or first lesson if not set
const redirectToLessonSlug = continueFromLesson || lessons?.[0]?.slug;

const onContinueLearningClicked = () => {
logButtonClick('continue_learning', {
courseId: id,
Expand All @@ -36,19 +32,7 @@ const StartOrContinueLearning: React.FC<Props> = ({ course, isHeaderButton = tru
router.push(getLessonNavigationUrl(slug, redirectToLessonSlug));
};

const onStartLearningClicked = () => {
logButtonClick('start_learning', {
courseId: id,
isHeaderButton,
userType,
});
router.push(getLessonNavigationUrl(slug, redirectToLessonSlug));
};

if (userCompletedAnyLesson) {
return <Button onClick={onContinueLearningClicked}>{t('continue-learning')}</Button>;
}
return <Button onClick={onStartLearningClicked}>{t('start-learning')}</Button>;
return <Button onClick={onContinueLearningClicked}>{t('continue-learning')}</Button>;
};

export default StartOrContinueLearning;
84 changes: 38 additions & 46 deletions src/components/Course/CourseDetails/StatusHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ import CourseFeedback, { FeedbackSource } from '@/components/Course/CourseFeedba
import Button from '@/dls/Button/Button';
import Pill from '@/dls/Pill';
import { ToastStatus, useToast } from '@/dls/Toast/Toast';
import { useEnrollGuest, useIsEnrolled } from '@/hooks/auth/useGuestEnrollment';
import useEnrollUser from '@/hooks/auth/useEnrollUser';
import useMutateWithoutRevalidation from '@/hooks/useMutateWithoutRevalidation';
import { logErrorToSentry } from '@/lib/sentry';
import { Course } from '@/types/auth/Course';
import { enrollUser } from '@/utils/auth/api';
import EnrollmentMethod from '@/types/auth/EnrollmentMethod';
import { makeGetCourseUrl } from '@/utils/auth/apiPaths';
import { getUserType, isLoggedIn } from '@/utils/auth/login';
import { logButtonClick } from '@/utils/eventLogger';
Expand All @@ -31,31 +30,16 @@ type Props = {
};

const StatusHeader: React.FC<Props> = ({ course, isCTA = false }) => {
const { title, id, isUserEnrolled, slug, isCompleted, lessons, allowGuestAccess } = course;
const { id, isUserEnrolled, slug, isCompleted, lessons, allowGuestAccess } = course;
const [isLoading, setIsLoading] = useState(false);
const toast = useToast();
const router = useRouter();
const { t } = useTranslation('learn');
const mutate = useMutateWithoutRevalidation();
const enrollGuest = useEnrollGuest();
const isEnrolled = useIsEnrolled(id, isUserEnrolled);
const userLoggedIn = isLoggedIn();
const enrollUserInCourse = useEnrollUser();

const handleEnrollmentSuccess = async (loggedIn: boolean): Promise<void> => {
toast(t('enroll-success', { title }), { status: ToastStatus.Success });
if (loggedIn) {
mutate(makeGetCourseUrl(slug), (currentCourse: Course) => ({
...currentCourse,
isUserEnrolled: true,
}));
}

if (lessons?.length > 0) {
await router.replace(getLessonNavigationUrl(slug, lessons[0].slug));
}
};

const onEnrollClicked = async (): Promise<void> => {
const onStartHereClicked = async (): Promise<void> => {
const userType = getUserType(userLoggedIn);

logButtonClick('course_enroll', {
Expand All @@ -64,46 +48,51 @@ const StatusHeader: React.FC<Props> = ({ course, isCTA = false }) => {
userType,
});

if (!userLoggedIn && !allowGuestAccess) {
const redirectUrl = getCourseNavigationUrl(slug);
router.replace(getLoginNavigationUrl(redirectUrl));
// Guest user handling
if (!userLoggedIn) {
if (allowGuestAccess && lessons?.length > 0) {
router.push(getLessonNavigationUrl(slug, lessons[0].slug));
} else {
router.push(getLoginNavigationUrl(getCourseNavigationUrl(slug)));
}
return;
}

// Logged-in user - enroll with MANUAL method
setIsLoading(true);
try {
if (userLoggedIn) {
await enrollUser(id);
} else {
enrollGuest(id);
const { success } = await enrollUserInCourse(id, EnrollmentMethod.Manual);

if (success) {
mutate(makeGetCourseUrl(slug), (currentCourse: Course) => ({
...currentCourse,
isUserEnrolled: true,
}));

if (lessons?.length > 0) {
await router.push(getLessonNavigationUrl(slug, lessons[0].slug));
}
await handleEnrollmentSuccess(userLoggedIn);
} catch (error) {
logErrorToSentry(error, {
metadata: {
context: `${userType}_course_enrollment`,
courseId: id,
courseSlug: slug,
},
});
} else {
toast(t('common:error.general'), {
status: ToastStatus.Error,
});
} finally {
setIsLoading(false);
}

setIsLoading(false);
};

// CTA mode - only show button if not enrolled
if (isCTA) {
if (isEnrolled) {
if (isUserEnrolled) {
return <></>;
}
return (
<Button isDisabled={isLoading} isLoading={isLoading} onClick={onEnrollClicked}>
{t('enroll')}
<Button isDisabled={isLoading} isLoading={isLoading} onClick={onStartHereClicked}>
{t('start-here')}
</Button>
);
}

// Course is completed
if (isCompleted) {
return (
<div className={styles.completedContainer}>
Expand All @@ -114,13 +103,16 @@ const StatusHeader: React.FC<Props> = ({ course, isCTA = false }) => {
</div>
);
}
if (isEnrolled) {

// User is enrolled - show StartOrContinueLearning component
if (isUserEnrolled) {
return <StartOrContinueLearning course={course} />;
}

// Not enrolled - show "Start here" button
return (
<Button isDisabled={isLoading} isLoading={isLoading} onClick={onEnrollClicked}>
{t('enroll')}
<Button isDisabled={isLoading} isLoading={isLoading} onClick={onStartHereClicked}>
{t('start-here')}
</Button>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,3 @@
.day {
font-weight: var(--font-weight-bold);
}

.notEnrolledLink {
background: none;
border: none;
padding: 0;
color: var(--color-text-link);
block-size: auto;

&:hover {
text-decoration: underline;
color: var(--color-text-link);
}
}
51 changes: 8 additions & 43 deletions src/components/Course/CourseDetails/Tabs/Syllabus/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,22 @@ import useTranslation from 'next-translate/useTranslation';
import styles from './Syllabus.module.scss';

import CompletedTick from '@/components/Course/CompletedTick';
import Button from '@/components/dls/Button/Button';
import Link, { LinkVariant } from '@/dls/Link/Link';
import { ToastStatus, useToast } from '@/dls/Toast/Toast';
import { useIsEnrolled } from '@/hooks/auth/useGuestEnrollment';
import { Course } from '@/types/auth/Course';
import { getUserType } from '@/utils/auth/login';
import { logButtonClick } from '@/utils/eventLogger';
import { toLocalizedNumber } from '@/utils/locale';
import { getLessonNavigationUrl } from '@/utils/navigation';
import { stripHTMLTags } from '@/utils/string';

type Props = {
course: Course;
};

const Syllabus: React.FC<Props> = ({ course }) => {
const { lessons = [], slug: courseSlug, id: courseId, isUserEnrolled } = course;
const { lessons = [], slug: courseSlug, id: courseId } = course;
const { t, lang } = useTranslation('learn');
const toast = useToast();

const userType = getUserType();
const isEnrolled = useIsEnrolled(courseId, isUserEnrolled);

/**
* Log syllabus lesson click for analytics
* @param {number} dayNumber - The day number of the lesson
* @param {string} lessonId - The ID of the lesson
*/
const logSyllabusClick = (dayNumber: number, lessonId: string) => {
logButtonClick('course_syllabus_day', {
courseId,
Expand All @@ -43,18 +31,6 @@ const Syllabus: React.FC<Props> = ({ course }) => {
});
};

/**
* Handle lesson click for non-enrolled users, shows toast message
* @param {number} dayNumber - The day number of the lesson
* @param {string} lessonId - The ID of the lesson
*/
const onNonEnrolledDayClick = (dayNumber: number, lessonId: string) => {
logSyllabusClick(dayNumber, lessonId);
toast(stripHTMLTags(t('not-enrolled')), {
status: ToastStatus.Warning,
});
};

return (
<div className={styles.syllabusContainer}>
{lessons.map((lesson, index) => {
Expand All @@ -70,24 +46,13 @@ const Syllabus: React.FC<Props> = ({ course }) => {
)}`}</span>
<span>
{`: `}
{isEnrolled ? (
<Link
onClick={() => logSyllabusClick(dayNumber, id)}
href={url}
variant={LinkVariant.Highlight}
>
{title}
</Link>
) : (
<Button
htmlType="button"
className={styles.notEnrolledLink}
onClick={() => onNonEnrolledDayClick(dayNumber, id)}
ariaLabel={title}
>
{title}
</Button>
)}
<Link
onClick={() => logSyllabusClick(dayNumber, id)}
href={url}
variant={LinkVariant.Highlight}
>
{title}
</Link>
{isCompleted ? <CompletedTick /> : null}
</span>
</p>
Expand Down
7 changes: 0 additions & 7 deletions src/components/Course/LessonContent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import React from 'react';
import useTranslation from 'next-translate/useTranslation';

import LessonView from '@/components/Course/LessonView';
import NotEnrolledNotice from '@/components/Course/NotEnrolledNotice';
import NextSeoWrapper from '@/components/NextSeoWrapper';
import { useIsEnrolled } from '@/hooks/auth/useGuestEnrollment';
import { Lesson } from '@/types/auth/Course';
import { getCanonicalUrl, getLessonNavigationUrl } from '@/utils/navigation';

Expand All @@ -17,11 +15,6 @@ interface Props {

const LessonContent: React.FC<Props> = ({ lesson, lessonSlugOrId, courseSlug }) => {
const { lang } = useTranslation('learn');
const isEnrolled = useIsEnrolled(lesson.course.id, lesson.course.isUserEnrolled);

if (isEnrolled === false) {
return <NotEnrolledNotice courseSlug={courseSlug} lessonSlugOrId={lessonSlugOrId} />;
}

return (
<>
Expand Down

This file was deleted.

58 changes: 0 additions & 58 deletions src/components/Course/NotEnrolledNotice/index.tsx

This file was deleted.

Loading