- {activity.activity_type ===
- 'TYPE_DYNAMIC' && (
-
-
+
+
+
+ {isActivityDone(activity) ? (
+
+
+
+
+ ) : (
+
+
)}
- {activity.activity_type === 'TYPE_VIDEO' && (
-
-
-
- )}
- {activity.activity_type ===
- 'TYPE_DOCUMENT' && (
-
-
-
- )}
- {activity.activity_type ===
- 'TYPE_ASSIGNMENT' && (
-
-
-
- )}
-
-
-
{activity.name}
-
-
- {activity.activity_type ===
- 'TYPE_DYNAMIC' && (
-
- )}
- {activity.activity_type === 'TYPE_VIDEO' && (
-
-
-
+
+
+
{activity.name}
+ {isActivityCurrent(activity) && (
+
+ Current
-
+ )}
- )}
- {activity.activity_type ===
- 'TYPE_DOCUMENT' && (
-
- )}
- {activity.activity_type ===
- 'TYPE_ASSIGNMENT' && (
-
- )}
+
+ {activity.activity_type === 'TYPE_DYNAMIC' && (
+
+ )}
+ {activity.activity_type === 'TYPE_VIDEO' && (
+
+ )}
+ {activity.activity_type === 'TYPE_DOCUMENT' && (
+
+ )}
+ {activity.activity_type === 'TYPE_ASSIGNMENT' && (
+
+ )}
+ {getActivityTypeLabel(activity.activity_type)}
+
+
+
@@ -348,8 +301,16 @@ const CourseClient = (props: any) => {
})}
-
+
+ {/* Actions Box */}
+
+ {/* Authors & Updates Box */}
+
+
+
+
+
diff --git a/apps/web/components/Objects/Courses/CourseActions/CoursesActions.tsx b/apps/web/components/Objects/Courses/CourseActions/CoursesActions.tsx
index 49453edb..f2f45b3c 100644
--- a/apps/web/components/Objects/Courses/CourseActions/CoursesActions.tsx
+++ b/apps/web/components/Objects/Courses/CourseActions/CoursesActions.tsx
@@ -1,42 +1,31 @@
import React, { useState, useEffect } from 'react'
-import UserAvatar from '../../UserAvatar'
-import { getUserAvatarMediaDirectory } from '@services/media/media'
import { removeCourse, startCourse } from '@services/courses/activity'
import { revalidateTags } from '@services/utils/ts/requests'
import { useRouter } from 'next/navigation'
import { useLHSession } from '@components/Contexts/LHSessionContext'
-import { useMediaQuery } from 'usehooks-ts'
import { getUriWithOrg, getUriWithoutOrg } from '@services/config/config'
import { getProductsByCourse } from '@services/payments/products'
-import { LogIn, LogOut, ShoppingCart, AlertCircle, UserPen, ClockIcon } from 'lucide-react'
+import { LogIn, LogOut, ShoppingCart, AlertCircle, UserPen, ClockIcon, ArrowRight, Sparkles, BookOpen } from 'lucide-react'
import Modal from '@components/Objects/StyledElements/Modal/Modal'
import CoursePaidOptions from './CoursePaidOptions'
import { checkPaidAccess } from '@services/payments/payments'
import { applyForContributor } from '@services/courses/courses'
import toast from 'react-hot-toast'
import { useContributorStatus } from '../../../../hooks/useContributorStatus'
-
-interface Author {
- user: {
- id: string
- user_uuid: string
- avatar_image: string
- first_name: string
- last_name: string
- username: string
- }
- authorship: 'CREATOR' | 'CONTRIBUTOR' | 'MAINTAINER' | 'REPORTER'
- authorship_status: 'ACTIVE' | 'INACTIVE' | 'PENDING'
-}
+import CourseProgress from '../CourseProgress/CourseProgress'
+import UserAvatar from '@components/Objects/UserAvatar'
interface CourseRun {
status: string
course_id: string
+ steps: Array<{
+ activity_id: string
+ complete: boolean
+ }>
}
interface Course {
id: string
- authors: Author[]
trail?: {
runs: CourseRun[]
}
@@ -59,137 +48,7 @@ interface CourseActionsProps {
}
}
-// Separate component for author display
-const AuthorInfo = ({ author, isMobile }: { author: Author, isMobile: boolean }) => (
-
-
-
-
Author
-
- {(author.user.first_name && author.user.last_name) ? (
-
-
{`${author.user.first_name} ${author.user.last_name}`}
-
- @{author.user.username}
-
-
- ) : (
-
-
@{author.user.username}
-
- )}
-
-
-
-)
-
-const MultipleAuthors = ({ authors, isMobile }: { authors: Author[], isMobile: boolean }) => {
- const displayedAvatars = authors.slice(0, 3)
- const displayedNames = authors.slice(0, 2)
- const remainingCount = Math.max(0, authors.length - 3)
-
- // Consistent sizes for both avatars and badge
- const avatarSize = isMobile ? 72 : 86
- const borderSize = "border-4"
-
- return (
-
-
Authors
-
- {/* Avatars row */}
-
- {displayedAvatars.map((author, index) => (
-
- ))}
- {remainingCount > 0 && (
-
-
- +{remainingCount}
-
-
- )}
-
-
- {/* Names row - improved display logic */}
-
-
- {authors.length === 1 ? (
-
- {authors[0].user.first_name && authors[0].user.last_name
- ? `${authors[0].user.first_name} ${authors[0].user.last_name}`
- : `@${authors[0].user.username}`}
-
- ) : (
- <>
- {displayedNames.map((author, index) => (
-
- {author.user.first_name && author.user.last_name
- ? `${author.user.first_name} ${author.user.last_name}`
- : `@${author.user.username}`}
- {index === 0 && authors.length > 1 && index < displayedNames.length - 1 && " & "}
-
- ))}
- {authors.length > 2 && (
-
- & {authors.length - 2} more
-
- )}
- >
- )}
-
-
- {authors.length === 1 ? (
- @{authors[0].user.username}
- ) : (
- <>
- {displayedNames.map((author, index) => (
-
- @{author.user.username}
- {index === 0 && authors.length > 1 && index < displayedNames.length - 1 && " & "}
-
- ))}
- >
- )}
-
-
-
- )
-}
-
-const Actions = ({ courseuuid, orgslug, course }: CourseActionsProps) => {
+function CoursesActions({ courseuuid, orgslug, course }: CourseActionsProps) {
const router = useRouter()
const session = useLHSession() as any
const [linkedProducts, setLinkedProducts] = useState
([])
@@ -198,7 +57,8 @@ const Actions = ({ courseuuid, orgslug, course }: CourseActionsProps) => {
const [isContributeLoading, setIsContributeLoading] = useState(false)
const [isModalOpen, setIsModalOpen] = useState(false)
const [hasAccess, setHasAccess] = useState(null)
- const { contributorStatus, refetch } = useContributorStatus(courseuuid);
+ const { contributorStatus, refetch } = useContributorStatus(courseuuid)
+ const [isProgressOpen, setIsProgressOpen] = useState(false)
const isStarted = course.trail?.runs?.some(
(run) => run.status === 'STATUS_IN_PROGRESS' && run.course_id === course.id
@@ -321,12 +181,33 @@ const Actions = ({ courseuuid, orgslug, course }: CourseActionsProps) => {
}
}
- if (isLoading) {
- return
- }
+ const renderActionButton = (action: 'start' | 'leave') => {
+ if (!session.data?.user) {
+ return (
+ <>
+
+ {action === 'start' ? 'Start Course' : 'Leave Course'}
+
+ >
+ );
+ }
+
+ return (
+ <>
+
+ {action === 'start' ? 'Start Course' : 'Leave Course'}
+
+ >
+ );
+ };
const renderContributorButton = () => {
- // Don't render anything if the course is not open to contributors or if the user status is INACTIVE
if (contributorStatus === 'INACTIVE' || course.open_to_contributors !== true) {
return null;
}
@@ -379,139 +260,219 @@ const Actions = ({ courseuuid, orgslug, course }: CourseActionsProps) => {
);
};
+ const renderProgressSection = () => {
+ const totalActivities = course.chapters?.reduce((acc: number, chapter: any) => acc + chapter.activities.length, 0) || 0;
+ const completedActivities = course.trail?.runs?.find(
+ (run: CourseRun) => run.course_id === course.id
+ )?.steps?.filter((step) => step.complete)?.length || 0;
+
+ const progressPercentage = Math.round((completedActivities / totalActivities) * 100);
+
+ if (!isStarted) {
+ return (
+
+
+
+
+
+
+
+
+
Ready to Begin?
+
+ Start your learning journey with {totalActivities} exciting {totalActivities === 1 ? 'activity' : 'activities'}
+
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {progressPercentage}%
+
+
+
+
+
+
+
+
+
+ );
+ };
+
+ if (isLoading) {
+ return
+ }
+
if (linkedProducts.length > 0) {
return (
-
- {hasAccess ? (
- <>
-
-
-
-
You Own This Course
+
+
+ {hasAccess ? (
+ <>
+
+
+
+
You Own This Course
+
+
+ You have purchased this course and have full access to all content.
+
-
- You have purchased this course and have full access to all content.
-
-
-
- {renderContributorButton()}
- >
- ) : (
-
-
-
- This course requires purchase to access its content.
-
-
- )}
-
- {!hasAccess && (
- <>
-
}
- dialogTitle="Purchase Course"
- dialogDescription="Select a payment option to access this course"
- minWidth="sm"
- />
-
- {renderContributorButton()}
- >
- )}
+
+ {renderContributorButton()}
+ >
+ ) : (
+ <>
+
+
+
+ This course requires purchase to access its content.
+
+
+
}
+ dialogTitle="Purchase Course"
+ dialogDescription="Select a payment option to access this course"
+ minWidth="sm"
+ />
+
+ {renderContributorButton()}
+ >
+ )}
+
)
}
return (
-
-
- {renderContributorButton()}
-
- )
-}
+
+
+ {/* Progress Section */}
+ {renderProgressSection()}
-function CoursesActions({ courseuuid, orgslug, course }: CourseActionsProps) {
- const router = useRouter()
- const session = useLHSession() as any
- const isMobile = useMediaQuery('(max-width: 768px)')
+ {/* Start/Leave Course Button */}
+
- // Filter active authors and sort by role priority
- const sortedAuthors = [...course.authors]
- .filter(author => author.authorship_status === 'ACTIVE')
- .sort((a, b) => {
- const rolePriority: Record
= {
- 'CREATOR': 0,
- 'MAINTAINER': 1,
- 'CONTRIBUTOR': 2,
- 'REPORTER': 3
- };
- return rolePriority[a.authorship] - rolePriority[b.authorship];
- });
+ {/* Contributor Button */}
+ {renderContributorButton()}
- return (
-
-
-
-
+ {/* Course Progress Modal */}
+
setIsProgressOpen(false)}
+ />
)
diff --git a/apps/web/components/Objects/Courses/CourseAuthors/CourseAuthors.tsx b/apps/web/components/Objects/Courses/CourseAuthors/CourseAuthors.tsx
new file mode 100644
index 00000000..1ed560df
--- /dev/null
+++ b/apps/web/components/Objects/Courses/CourseAuthors/CourseAuthors.tsx
@@ -0,0 +1,410 @@
+import React, { useState } from 'react'
+import UserAvatar from '../../UserAvatar'
+import { getUserAvatarMediaDirectory } from '@services/media/media'
+import { useMediaQuery } from 'usehooks-ts'
+import { Rss, PencilLine, TentTree } from 'lucide-react'
+import { useCourse } from '@components/Contexts/CourseContext'
+import { useLHSession } from '@components/Contexts/LHSessionContext'
+import useSWR, { mutate } from 'swr'
+import { getAPIUrl } from '@services/config/config'
+import { swrFetcher } from '@services/utils/ts/requests'
+import useAdminStatus from '@components/Hooks/useAdminStatus'
+import { useOrg } from '@components/Contexts/OrgContext'
+import { createCourseUpdate, deleteCourseUpdate } from '@services/courses/updates'
+import toast from 'react-hot-toast'
+import ConfirmationModal from '@components/Objects/StyledElements/ConfirmationModal/ConfirmationModal'
+import dayjs from 'dayjs'
+import relativeTime from 'dayjs/plugin/relativeTime'
+import * as Form from '@radix-ui/react-form'
+import FormLayout, {
+ FormField,
+ FormLabelAndMessage,
+ Input,
+ Textarea,
+} from '@components/Objects/StyledElements/Form/Form'
+import { useFormik } from 'formik'
+import { motion } from 'framer-motion'
+
+dayjs.extend(relativeTime)
+
+interface Author {
+ user: {
+ id: string
+ user_uuid: string
+ avatar_image: string
+ first_name: string
+ last_name: string
+ username: string
+ }
+ authorship: 'CREATOR' | 'CONTRIBUTOR' | 'MAINTAINER' | 'REPORTER'
+ authorship_status: 'ACTIVE' | 'INACTIVE' | 'PENDING'
+}
+
+interface CourseAuthorsProps {
+ authors: Author[]
+}
+
+const MultipleAuthors = ({ authors, isMobile }: { authors: Author[], isMobile: boolean }) => {
+ const displayedAvatars = authors.slice(0, 3)
+ const displayedNames = authors.slice(0, 2)
+ const remainingCount = Math.max(0, authors.length - 3)
+
+ // Consistent sizes for both avatars and badge
+ const avatarSize = isMobile ? 72 : 86
+ const borderSize = "border-4"
+
+ return (
+
+
Authors & Updates
+
+ {/* Avatars row */}
+
+ {displayedAvatars.map((author, index) => (
+
+ ))}
+ {remainingCount > 0 && (
+
+
+ +{remainingCount}
+
+
+ )}
+
+
+ {/* Names row - improved display logic */}
+
+
+ {authors.length === 1 ? (
+
+ {authors[0].user.first_name && authors[0].user.last_name
+ ? `${authors[0].user.first_name} ${authors[0].user.last_name}`
+ : `@${authors[0].user.username}`}
+
+ ) : (
+ <>
+ {displayedNames.map((author, index) => (
+
+ {author.user.first_name && author.user.last_name
+ ? `${author.user.first_name} ${author.user.last_name}`
+ : `@${author.user.username}`}
+ {index === 0 && authors.length > 1 && index < displayedNames.length - 1 && " & "}
+
+ ))}
+ {authors.length > 2 && (
+
+ & {authors.length - 2} more
+
+ )}
+ >
+ )}
+
+
+ {authors.length === 1 ? (
+ @{authors[0].user.username}
+ ) : (
+ <>
+ {displayedNames.map((author, index) => (
+
+ @{author.user.username}
+ {index === 0 && authors.length > 1 && index < displayedNames.length - 1 && " & "}
+
+ ))}
+ >
+ )}
+
+
+
+ )
+}
+
+const UpdatesSection = () => {
+ const [selectedView, setSelectedView] = React.useState('list')
+ const adminStatus = useAdminStatus()
+ const course = useCourse() as any
+ const session = useLHSession() as any
+ const access_token = session?.data?.tokens?.access_token
+ const { data: updates } = useSWR(
+ `${getAPIUrl()}courses/${course?.courseStructure.course_uuid}/updates`,
+ (url) => swrFetcher(url, access_token)
+ )
+
+ return (
+
+
+
+
+
+ Course Updates
+
+ {updates && updates.length > 0 && (
+
+ {updates.length} {updates.length === 1 ? 'update' : 'updates'}
+
+ )}
+
+ {adminStatus.isAdmin && (
+
+ )}
+
+
+
+
+ {selectedView === 'list' ? (
+
+ ) : (
+
+ )}
+
+
+
+ )
+}
+
+const NewUpdateForm = ({ setSelectedView }: { setSelectedView: (view: string) => void }) => {
+ const org = useOrg() as any
+ const course = useCourse() as any
+ const session = useLHSession() as any
+
+ const formik = useFormik({
+ initialValues: {
+ title: '',
+ content: ''
+ },
+ validate: (values) => {
+ const errors: any = {}
+ if (!values.title) errors.title = 'Title is required'
+ if (!values.content) errors.content = 'Content is required'
+ return errors
+ },
+ onSubmit: async (values) => {
+ const body = {
+ title: values.title,
+ content: values.content,
+ course_uuid: course.courseStructure.course_uuid,
+ org_id: org.id
+ }
+ const res = await createCourseUpdate(body, session.data?.tokens?.access_token)
+ if (res.status === 200) {
+ toast.success('Update added successfully')
+ setSelectedView('list')
+ mutate(`${getAPIUrl()}courses/${course?.courseStructure.course_uuid}/updates`)
+ } else {
+ toast.error('Failed to add update')
+ }
+ }
+ })
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+const UpdatesListView = () => {
+ const course = useCourse() as any
+ const adminStatus = useAdminStatus()
+ const session = useLHSession() as any
+ const access_token = session?.data?.tokens?.access_token
+ const { data: updates } = useSWR(
+ `${getAPIUrl()}courses/${course?.courseStructure?.course_uuid}/updates`,
+ (url) => swrFetcher(url, access_token)
+ )
+
+ if (!updates || updates.length === 0) {
+ return (
+
+
+
No updates yet
+
Updates about this course will appear here
+
+ )
+ }
+
+ return (
+
+ {updates.map((update: any) => (
+
+
+
+
+
{update.title}
+
+ {dayjs(update.creation_date).fromNow()}
+
+
+
{update.content}
+
+ {adminStatus.isAdmin && !adminStatus.loading && (
+
+
+
+ )}
+
+
+ ))}
+
+ )
+}
+
+const DeleteUpdateButton = ({ update }: any) => {
+ const session = useLHSession() as any
+ const course = useCourse() as any
+
+ const handleDelete = async () => {
+ const toast_loading = toast.loading('Deleting update...')
+ const res = await deleteCourseUpdate(
+ course.courseStructure.course_uuid,
+ update.courseupdate_uuid,
+ session.data?.tokens?.access_token
+ )
+
+ if (res.status === 200) {
+ toast.dismiss(toast_loading)
+ toast.success('Update deleted successfully')
+ mutate(`${getAPIUrl()}courses/${course?.courseStructure.course_uuid}/updates`)
+ } else {
+ toast.error('Failed to delete update')
+ }
+ }
+
+ return (
+
+
+
+ }
+ functionToExecute={handleDelete}
+ status="warning"
+ />
+ )
+}
+
+const CourseAuthors = ({ authors }: CourseAuthorsProps) => {
+ const isMobile = useMediaQuery('(max-width: 768px)')
+
+ // Filter active authors and sort by role priority
+ const sortedAuthors = [...authors]
+ .filter(author => author.authorship_status === 'ACTIVE')
+ .sort((a, b) => {
+ const rolePriority: Record = {
+ 'CREATOR': 0,
+ 'MAINTAINER': 1,
+ 'CONTRIBUTOR': 2,
+ 'REPORTER': 3
+ };
+ return rolePriority[a.authorship] - rolePriority[b.authorship];
+ });
+
+ return (
+
+
+
+
+ )
+}
+
+export default CourseAuthors
\ No newline at end of file
diff --git a/apps/web/components/Objects/Courses/CourseProgress/CourseProgress.tsx b/apps/web/components/Objects/Courses/CourseProgress/CourseProgress.tsx
new file mode 100644
index 00000000..4b697d18
--- /dev/null
+++ b/apps/web/components/Objects/Courses/CourseProgress/CourseProgress.tsx
@@ -0,0 +1,122 @@
+import React, { useState, useEffect } from 'react'
+import { Check, Square, ArrowRight, Folder, FileText, Video, Layers, BookOpenCheck } from 'lucide-react'
+import { getUriWithOrg } from '@services/config/config'
+import Link from 'next/link'
+import Modal from '@components/Objects/StyledElements/Modal/Modal'
+
+interface CourseProgressProps {
+ course: any
+ orgslug: string
+ isOpen: boolean
+ onClose: () => void
+}
+
+const CourseProgress: React.FC = ({ course, orgslug, isOpen, onClose }) => {
+ const [completedActivities, setCompletedActivities] = useState(0)
+ const [totalActivities, setTotalActivities] = useState(0)
+
+ useEffect(() => {
+ let total = 0
+ let completed = 0
+
+ course.chapters.forEach((chapter: any) => {
+ chapter.activities.forEach((activity: any) => {
+ total++
+ if (isActivityDone(activity)) {
+ completed++
+ }
+ })
+ })
+
+ setTotalActivities(total)
+ setCompletedActivities(completed)
+ }, [course])
+
+ const isActivityDone = (activity: any) => {
+ const run = course?.trail?.runs?.find(
+ (run: any) => run.course_id === course.id
+ )
+ if (run) {
+ return run.steps.find((step: any) => step.activity_id === activity.id)
+ }
+ return false
+ }
+
+ const getActivityTypeIcon = (activityType: string) => {
+ switch (activityType) {
+ case 'TYPE_VIDEO':
+ return
+ case 'TYPE_DOCUMENT':
+ return
+ case 'TYPE_DYNAMIC':
+ return
+ case 'TYPE_ASSIGNMENT':
+ return
+ default:
+ return
+ }
+ }
+
+ const progressPercentage = totalActivities > 0 ? (completedActivities / totalActivities) * 100 : 0
+ const radius = 40
+ const circumference = 2 * Math.PI * radius
+ const strokeDashoffset = circumference - (progressPercentage / 100) * circumference
+
+ const dialogContent = (
+
+ {course.chapters.map((chapter: any) => (
+
+
+
+ {chapter.name}
+
+
+ {chapter.activities.map((activity: any) => {
+ const activityId = activity.activity_uuid.replace('activity_', '')
+ const courseId = course.course_uuid.replace('course_', '')
+ return (
+
+
+
+ {isActivityDone(activity) ? (
+
+
+
+
+ ) : (
+
+ )}
+
+ {getActivityTypeIcon(activity.activity_type)}
+
+ {activity.name}
+
+
+
+
+
+
+ )
+ })}
+
+
+ ))}
+
+ )
+
+ return (
+
+ )
+}
+
+export default CourseProgress
\ No newline at end of file
diff --git a/apps/web/components/Objects/Editor/Extensions/Video/VideoBlockComponent.tsx b/apps/web/components/Objects/Editor/Extensions/Video/VideoBlockComponent.tsx
index ad3603c3..3a4e64ce 100644
--- a/apps/web/components/Objects/Editor/Extensions/Video/VideoBlockComponent.tsx
+++ b/apps/web/components/Objects/Editor/Extensions/Video/VideoBlockComponent.tsx
@@ -235,7 +235,9 @@ function VideoBlockComponent(props: ExtendedNodeViewProps) {
setIsDragging(false)
const file = e.dataTransfer.files[0]
- if (file && SUPPORTED_FILES.split(',').some(format => file.name.toLowerCase().endsWith(format.trim()))) {
+ const fileExtension = file?.name.split('.').pop()?.toLowerCase()
+
+ if (file && fileExtension && ['mp4', 'webm'].includes(fileExtension)) {
setVideo(file)
setError(null)
handleUpload(file)
diff --git a/apps/web/components/Objects/StyledElements/Form/Form.tsx b/apps/web/components/Objects/StyledElements/Form/Form.tsx
index 818b04f1..d752e458 100644
--- a/apps/web/components/Objects/StyledElements/Form/Form.tsx
+++ b/apps/web/components/Objects/StyledElements/Form/Form.tsx
@@ -4,11 +4,19 @@ import { styled } from '@stitches/react'
import { blackA } from '@radix-ui/colors'
import { Info } from 'lucide-react'
-const FormLayout: React.FC<{ children: React.ReactNode; onSubmit: (e: any) => void }> = (props) => (
-
- {props.children}
-
-)
+interface FormLayoutProps {
+ children: React.ReactNode
+ onSubmit: (e: any) => void
+ className?: string
+}
+
+const FormLayout = ({ children, onSubmit, className }: FormLayoutProps) => {
+ return (
+
+ {children}
+
+ )
+}
export const FormLabelAndMessage = (props: {
label: string
diff --git a/apps/web/components/Objects/StyledElements/Tooltip/Tooltip.tsx b/apps/web/components/Objects/StyledElements/Tooltip/Tooltip.tsx
index 91495910..173f804f 100644
--- a/apps/web/components/Objects/StyledElements/Tooltip/Tooltip.tsx
+++ b/apps/web/components/Objects/StyledElements/Tooltip/Tooltip.tsx
@@ -9,6 +9,7 @@ type TooltipProps = {
children: React.ReactNode
side?: 'top' | 'right' | 'bottom' | 'left' // default is bottom
slateBlack?: boolean
+ unstyled?: boolean // new prop to remove default styling
}
const ToolTip = (props: TooltipProps) => {
@@ -19,6 +20,7 @@ const ToolTip = (props: TooltipProps) => {
@@ -63,6 +65,17 @@ const TooltipContent = styled(Tooltip.Content, {
color: 'white',
},
},
+ unstyled: {
+ true: {
+ padding: 0,
+ backgroundColor: 'transparent',
+ boxShadow: 'none',
+ borderRadius: 0,
+ fontSize: 'inherit',
+ lineHeight: 'inherit',
+ color: 'inherit',
+ },
+ },
},
borderRadius: 4,
diff --git a/apps/web/components/Pages/Courses/ActivityIndicators.tsx b/apps/web/components/Pages/Courses/ActivityIndicators.tsx
index 230624c8..8e47f7f7 100644
--- a/apps/web/components/Pages/Courses/ActivityIndicators.tsx
+++ b/apps/web/components/Pages/Courses/ActivityIndicators.tsx
@@ -2,6 +2,7 @@ import ToolTip from '@components/Objects/StyledElements/Tooltip/Tooltip'
import { getUriWithOrg } from '@services/config/config'
import Link from 'next/link'
import React from 'react'
+import { Video, FileText, Layers, BookOpenCheck, Check } from 'lucide-react'
interface Props {
course: any
@@ -16,7 +17,7 @@ function ActivityIndicators(props: Props) {
const courseid = props.course_uuid.replace('course_', '')
const done_activity_style = 'bg-teal-600 hover:bg-teal-700'
- const black_activity_style = 'bg-black hover:bg-gray-700'
+ const black_activity_style = 'bg-zinc-300 hover:bg-zinc-400'
const current_activity_style = 'bg-gray-600 animate-pulse hover:bg-gray-700'
const trail = props.course.trail
@@ -50,6 +51,51 @@ function ActivityIndicators(props: Props) {
return black_activity_style
}
+ const getActivityTypeIcon = (activityType: string) => {
+ switch (activityType) {
+ case 'TYPE_VIDEO':
+ return
+ case 'TYPE_DOCUMENT':
+ return
+ case 'TYPE_DYNAMIC':
+ return
+ case 'TYPE_ASSIGNMENT':
+ return
+ default:
+ return
+ }
+ }
+
+ const getActivityTypeLabel = (activityType: string) => {
+ switch (activityType) {
+ case 'TYPE_VIDEO':
+ return 'Video'
+ case 'TYPE_DOCUMENT':
+ return 'Document'
+ case 'TYPE_DYNAMIC':
+ return 'Page'
+ case 'TYPE_ASSIGNMENT':
+ return 'Assignment'
+ default:
+ return 'Learning Material'
+ }
+ }
+
+ const getActivityTypeBadgeColor = (activityType: string) => {
+ switch (activityType) {
+ case 'TYPE_VIDEO':
+ return 'bg-gray-100 text-gray-700 font-bold'
+ case 'TYPE_DOCUMENT':
+ return 'bg-gray-100 text-gray-700 font-bold'
+ case 'TYPE_DYNAMIC':
+ return 'bg-gray-100 text-gray-700 font-bold'
+ case 'TYPE_ASSIGNMENT':
+ return 'bg-gray-100 text-gray-700 font-bold'
+ default:
+ return 'bg-gray-100 text-gray-700 font-bold'
+ }
+ }
+
return (
{course.chapters.map((chapter: any) => {
@@ -57,11 +103,33 @@ function ActivityIndicators(props: Props) {
{chapter.activities.map((activity: any) => {
+ const isDone = isActivityDone(activity)
+ const isCurrent = isActivityCurrent(activity)
return (
+
+ {getActivityTypeIcon(activity.activity_type)}
+ {activity.name}
+ {isDone && (
+
+
+
+ )}
+
+
+
+ {getActivityTypeLabel(activity.activity_type)}
+
+
+ {isCurrent ? 'Current Activity' : isDone ? 'Completed' : 'Not Started'}
+
+
+
+ }
key={activity.activity_uuid}
>