diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/activity/[activityid]/activity.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/activity/[activityid]/activity.tsx index 56f9fb90..885d4ab4 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/activity/[activityid]/activity.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/activity/[activityid]/activity.tsx @@ -29,9 +29,10 @@ import { useMediaQuery } from 'usehooks-ts' import PaidCourseActivityDisclaimer from '@components/Objects/Courses/CourseActions/PaidCourseActivityDisclaimer' import { useContributorStatus } from '../../../../../../../../hooks/useContributorStatus' import ToolTip from '@components/Objects/StyledElements/Tooltip/Tooltip' -import ActivityNavigation from '@components/Dashboard/Pages/Activity/ActivityNavigation' -import ActivityChapterDropdown from '@components/Dashboard/Pages/Activity/ActivityChapterDropdown' -import FixedActivitySecondaryBar from '@components/Dashboard/Pages/Activity/FixedActivitySecondaryBar' +import ActivityNavigation from '@components/Pages/Activity/ActivityNavigation' +import ActivityChapterDropdown from '@components/Pages/Activity/ActivityChapterDropdown' +import FixedActivitySecondaryBar from '@components/Pages/Activity/FixedActivitySecondaryBar' +import CourseEndView from '@components/Pages/Activity/CourseEndView' interface ActivityClientProps { activityid: string @@ -95,168 +96,175 @@ function ActivityClient(props: ActivityClientProps) { -
- - -
-
-
-
-
- - - -
-
-

Course

-

- {course.name} -

+ {activityid === 'end' ? ( + + ) : ( +
+
+
+
+
+
+ + + +
+
+

Course

+

+ {course.name} +

+
-
- + -
-
- -
-

- Chapter : {getChapterNameByActivityId(course, activity.id)} -

-

- {activity.name} -

+
+
+ +
+

+ Chapter : {getChapterNameByActivityId(course, activity.id)} +

+

+ {activity.name} +

+
-
-
- {activity && activity.published == true && activity.content.paid_access != false && ( - - {activity.activity_type != 'TYPE_ASSIGNMENT' && ( - <> - - {contributorStatus === 'ACTIVE' && activity.activity_type == 'TYPE_DYNAMIC' && ( - - - Contribute to Activity - - )} - - - - )} - {activity.activity_type == 'TYPE_ASSIGNMENT' && ( - <> - - - + {activity && activity.published == true && activity.content.paid_access != false && ( + + {activity.activity_type != 'TYPE_ASSIGNMENT' && ( + <> + + {contributorStatus === 'ACTIVE' && activity.activity_type == 'TYPE_DYNAMIC' && ( + + + Contribute to Activity + + )} + + - - - )} - - )} -
-
-
- {activity && activity.published == false && ( -
-
-

- This activity is not published yet -

-
-
- )} - - {activity && activity.published == true && ( - <> - {activity.content.paid_access == false ? ( - - ) : ( -
- {/* Activity Types */} -
- {activity.activity_type == 'TYPE_DYNAMIC' && ( - - )} - {activity.activity_type == 'TYPE_VIDEO' && ( - - )} - {activity.activity_type == 'TYPE_DOCUMENT' && ( - - )} - {activity.activity_type == 'TYPE_ASSIGNMENT' && ( -
- {assignment ? ( - - - - - - - - ) : ( -
- )} -
- )} -
+ + )} + {activity.activity_type == 'TYPE_ASSIGNMENT' && ( + <> + + + + + + )} + + )}
- )} - - )} - +
+
+ {activity && activity.published == false && ( +
+
+

+ This activity is not published yet +

+
+
+ )} - {/* Fixed Activity Secondary Bar */} - {activity && activity.published == true && activity.content.paid_access != false && ( - - )} + {activity && activity.published == true && ( + <> + {activity.content.paid_access == false ? ( + + ) : ( +
+ {/* Activity Types */} +
+ {activity.activity_type == 'TYPE_DYNAMIC' && ( + + )} + {activity.activity_type == 'TYPE_VIDEO' && ( + + )} + {activity.activity_type == 'TYPE_DOCUMENT' && ( + + )} + {activity.activity_type == 'TYPE_ASSIGNMENT' && ( +
+ {assignment ? ( + + + + + + + + ) : ( +
+ )} +
+ )} +
+
+ )} + + )} -
+ + {/* Fixed Activity Secondary Bar */} + {activity && activity.published == true && activity.content.paid_access != false && ( + + )} + +
+
-
+ )} @@ -275,20 +283,63 @@ export function MarkStatus(props: { const isMobile = useMediaQuery('(max-width: 768px)') const [isLoading, setIsLoading] = React.useState(false); + const areAllActivitiesCompleted = () => { + const run = props.course.trail.runs.find( + (run: any) => run.course_id == props.course.id + ); + if (!run) return false; + + let totalActivities = 0; + let completedActivities = 0; + + // Count all activities and completed activities + props.course.chapters.forEach((chapter: any) => { + chapter.activities.forEach((activity: any) => { + totalActivities++; + const isCompleted = run.steps.find( + (step: any) => step.activity_id === activity.id && step.complete === true + ); + if (isCompleted) { + completedActivities++; + } + }); + }); + + console.log('Total activities:', totalActivities); + console.log('Completed activities:', completedActivities); + console.log('All completed?', completedActivities >= totalActivities - 1); + + // We check for totalActivities - 1 because the current activity completion + // hasn't been counted yet (it's in progress) + return completedActivities >= totalActivities - 1; + }; + async function markActivityAsCompleteFront() { try { + // Check if this will be the last activity to complete + const willCompleteAll = areAllActivitiesCompleted(); + console.log('Will complete all?', willCompleteAll); + setIsLoading(true); - const trail = await markActivityAsComplete( + await markActivityAsComplete( props.orgslug, props.course.course_uuid, props.activity.activity_uuid, session.data?.tokens?.access_token ); - // Mutate the course data to trigger re-render + // Mutate the course data await mutate(`${getAPIUrl()}courses/${props.course.course_uuid}/meta`); - router.refresh(); + + if (willCompleteAll) { + console.log('Redirecting to end page...'); + const cleanCourseUuid = props.course.course_uuid.replace('course_', ''); + router.push(getUriWithOrg(props.orgslug, '') + `/course/${cleanCourseUuid}/activity/end`); + } else { + router.refresh(); + } } catch (error) { + console.error('Error marking activity as complete:', error); toast.error('Failed to mark activity as complete'); } finally { setIsLoading(false); diff --git a/apps/web/components/Dashboard/Pages/Activity/ActivityChapterDropdown.tsx b/apps/web/components/Pages/Activity/ActivityChapterDropdown.tsx similarity index 100% rename from apps/web/components/Dashboard/Pages/Activity/ActivityChapterDropdown.tsx rename to apps/web/components/Pages/Activity/ActivityChapterDropdown.tsx diff --git a/apps/web/components/Dashboard/Pages/Activity/ActivityNavigation.tsx b/apps/web/components/Pages/Activity/ActivityNavigation.tsx similarity index 100% rename from apps/web/components/Dashboard/Pages/Activity/ActivityNavigation.tsx rename to apps/web/components/Pages/Activity/ActivityNavigation.tsx diff --git a/apps/web/components/Pages/Activity/CourseEndView.tsx b/apps/web/components/Pages/Activity/CourseEndView.tsx new file mode 100644 index 00000000..027f1a65 --- /dev/null +++ b/apps/web/components/Pages/Activity/CourseEndView.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import ReactConfetti from 'react-confetti'; +import { Trophy, ArrowLeft } from 'lucide-react'; +import Link from 'next/link'; +import { getUriWithOrg } from '@services/config/config'; +import { getCourseThumbnailMediaDirectory } from '@services/media/media'; +import { useWindowSize } from 'usehooks-ts'; +import { useOrg } from '@components/Contexts/OrgContext'; + +interface CourseEndViewProps { + courseName: string; + orgslug: string; + courseUuid: string; + thumbnailImage: string; +} + +const CourseEndView: React.FC = ({ courseName, orgslug, courseUuid, thumbnailImage }) => { + const { width, height } = useWindowSize(); + const org = useOrg() as any; + + return ( +
+
+ +
+ +
+
+ {thumbnailImage && ( + {courseName} + )} + +
+ +
+
+ +

+ Congratulations! 🎉 +

+ +

+ You've successfully completed + {courseName} +

+ +

+ Your dedication and hard work have paid off. You've mastered all the content in this course. +

+ +
+ + + Back to Course + +
+
+
+ ); +}; + +export default CourseEndView; \ No newline at end of file diff --git a/apps/web/components/Dashboard/Pages/Activity/FixedActivitySecondaryBar.tsx b/apps/web/components/Pages/Activity/FixedActivitySecondaryBar.tsx similarity index 100% rename from apps/web/components/Dashboard/Pages/Activity/FixedActivitySecondaryBar.tsx rename to apps/web/components/Pages/Activity/FixedActivitySecondaryBar.tsx