diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/course.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/course.tsx
index c3ebb3a0..ecfa804a 100644
--- a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/course.tsx
+++ b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/course.tsx
@@ -29,6 +29,8 @@ const CourseClient = (props: any) => {
const router = useRouter()
const isMobile = useMediaQuery('(max-width: 768px)')
+ console.log(course)
+
function getLearningTags() {
if (!course?.learnings) {
setLearnings([])
@@ -333,7 +335,7 @@ const CourseClient = (props: any) => {
{isMobile && (
-
+
)}
diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/page.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/page.tsx
index d0c721a4..e3216016 100644
--- a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/page.tsx
+++ b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/page.tsx
@@ -72,7 +72,7 @@ const CoursePage = async (params: any) => {
// Fetch course metadata once
const course_meta = await getCourseMetadata(
params.params.courseuuid,
- { revalidate: 1800, tags: ['courses'] },
+ { revalidate: 0, tags: ['courses'] },
access_token ? access_token : null
)
diff --git a/apps/web/components/Objects/Courses/CourseActions/CourseActionsMobile.tsx b/apps/web/components/Objects/Courses/CourseActions/CourseActionsMobile.tsx
index 702b0ec4..c559f377 100644
--- a/apps/web/components/Objects/Courses/CourseActions/CourseActionsMobile.tsx
+++ b/apps/web/components/Objects/Courses/CourseActions/CourseActionsMobile.tsx
@@ -3,7 +3,7 @@ import { useRouter } from 'next/navigation'
import { useLHSession } from '@components/Contexts/LHSessionContext'
import { getUriWithoutOrg, getUriWithOrg } from '@services/config/config'
import { getProductsByCourse } from '@services/payments/products'
-import { LogIn, LogOut, ShoppingCart } from 'lucide-react'
+import { LogIn, LogOut, ShoppingCart, AlertCircle } from 'lucide-react'
import Modal from '@components/Objects/StyledElements/Modal/Modal'
import CoursePaidOptions from './CoursePaidOptions'
import { checkPaidAccess } from '@services/payments/payments'
@@ -13,11 +13,15 @@ import UserAvatar from '../../UserAvatar'
import { getUserAvatarMediaDirectory } from '@services/media/media'
interface Author {
- user_uuid: string
- avatar_image: string
- first_name: string
- last_name: string
- username: string
+ user: {
+ 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 CourseRun {
@@ -49,11 +53,81 @@ interface CourseActionsMobileProps {
}
}
+// Component for displaying multiple authors
+const MultipleAuthors = ({ authors }: { authors: Author[] }) => {
+ const displayedAvatars = authors.slice(0, 3)
+ const remainingCount = Math.max(0, authors.length - 3)
+
+ // Avatar size for mobile
+ const avatarSize = 36
+ const borderSize = "border-2"
+
+ return (
+
+
+ {displayedAvatars.map((author, index) => (
+
+
+
+ ))}
+ {remainingCount > 0 && (
+
+
+ +{remainingCount}
+
+
+ )}
+
+
+
+
+ {authors.length > 1 ? 'Authors' : 'Author'}
+
+ {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}`}
+
+ ) : (
+
+ {authors[0].user.first_name && authors[0].user.last_name
+ ? `${authors[0].user.first_name} ${authors[0].user.last_name}`
+ : `@${authors[0].user.username}`}
+ {authors.length > 1 && ` & ${authors.length - 1} more`}
+
+ )}
+
+
+ )
+}
+
const CourseActionsMobile = ({ courseuuid, orgslug, course }: CourseActionsMobileProps) => {
const router = useRouter()
const session = useLHSession() as any
const [linkedProducts, setLinkedProducts] = useState
([])
const [isLoading, setIsLoading] = useState(true)
+ const [isActionLoading, setIsActionLoading] = useState(false)
const [isModalOpen, setIsModalOpen] = useState(false)
const [hasAccess, setHasAccess] = useState(null)
@@ -107,106 +181,141 @@ const CourseActionsMobile = ({ courseuuid, orgslug, course }: CourseActionsMobil
return
}
- if (isStarted) {
- await removeCourse('course_' + courseuuid, orgslug, session.data?.tokens?.access_token)
- await revalidateTags(['courses'], orgslug)
- router.refresh()
- } else {
- await startCourse('course_' + courseuuid, orgslug, session.data?.tokens?.access_token)
- await revalidateTags(['courses'], orgslug)
-
- // Get the first activity from the first chapter
- const firstChapter = course.chapters?.[0]
- const firstActivity = firstChapter?.activities?.[0]
-
- if (firstActivity) {
- // Redirect to the first activity
- router.push(
- getUriWithOrg(orgslug, '') +
- `/course/${courseuuid}/activity/${firstActivity.activity_uuid.replace('activity_', '')}`
- )
- } else {
+ setIsActionLoading(true)
+ try {
+ if (isStarted) {
+ await removeCourse('course_' + courseuuid, orgslug, session.data?.tokens?.access_token)
+ await revalidateTags(['courses'], orgslug)
router.refresh()
+ } else {
+ await startCourse('course_' + courseuuid, orgslug, session.data?.tokens?.access_token)
+ await revalidateTags(['courses'], orgslug)
+
+ // Get the first activity from the first chapter
+ const firstChapter = course.chapters?.[0]
+ const firstActivity = firstChapter?.activities?.[0]
+
+ if (firstActivity) {
+ // Redirect to the first activity
+ router.push(
+ getUriWithOrg(orgslug, '') +
+ `/course/${courseuuid}/activity/${firstActivity.activity_uuid.replace('activity_', '')}`
+ )
+ } else {
+ router.refresh()
+ }
}
+ } catch (error) {
+ console.error('Failed to perform course action:', error)
+ } finally {
+ setIsActionLoading(false)
}
}
if (isLoading) {
- return
+ return
}
- const author = course.authors[0]
- const authorName = author.first_name && author.last_name
- ? `${author.first_name} ${author.last_name}`
- : `@${author.username}`
+ // 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];
+ });
return (
-
-
-
-
- Author
- {authorName}
-
-
-
-
+
+
+
+
{linkedProducts.length > 0 ? (
- hasAccess ? (
-
- ) : (
- <>
-
}
- dialogTitle="Purchase Course"
- dialogDescription="Select a payment option to access this course"
- minWidth="sm"
- />
+
+ {hasAccess ? (
+
+
+
+
You Own This Course
+
+
+ ) : (
+
+ )}
+
+ {hasAccess ? (
- >
- )
+ ) : (
+ <>
+
}
+ dialogTitle="Purchase Course"
+ dialogDescription="Select a payment option to access this course"
+ minWidth="sm"
+ />
+
+ >
+ )}
+
) : (
)
+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) => {
const router = useRouter()
const session = useLHSession() as any
const [linkedProducts, setLinkedProducts] = useState
([])
const [isLoading, setIsLoading] = useState(true)
+ const [isActionLoading, setIsActionLoading] = useState(false)
const [isModalOpen, setIsModalOpen] = useState(false)
const [hasAccess, setHasAccess] = useState(null)
@@ -141,27 +240,34 @@ const Actions = ({ courseuuid, orgslug, course }: CourseActionsProps) => {
return
}
- if (isStarted) {
- await removeCourse('course_' + courseuuid, orgslug, session.data?.tokens?.access_token)
- await revalidateTags(['courses'], orgslug)
- router.refresh()
- } else {
- await startCourse('course_' + courseuuid, orgslug, session.data?.tokens?.access_token)
- await revalidateTags(['courses'], orgslug)
-
- // Get the first activity from the first chapter
- const firstChapter = course.chapters?.[0]
- const firstActivity = firstChapter?.activities?.[0]
-
- if (firstActivity) {
- // Redirect to the first activity
- router.push(
- getUriWithOrg(orgslug, '') +
- `/course/${courseuuid}/activity/${firstActivity.activity_uuid.replace('activity_', '')}`
- )
- } else {
+ setIsActionLoading(true)
+ try {
+ if (isStarted) {
+ await removeCourse('course_' + courseuuid, orgslug, session.data?.tokens?.access_token)
+ await revalidateTags(['courses'], orgslug)
router.refresh()
+ } else {
+ await startCourse('course_' + courseuuid, orgslug, session.data?.tokens?.access_token)
+ await revalidateTags(['courses'], orgslug)
+
+ // Get the first activity from the first chapter
+ const firstChapter = course.chapters?.[0]
+ const firstActivity = firstChapter?.activities?.[0]
+
+ if (firstActivity) {
+ // Redirect to the first activity
+ router.push(
+ getUriWithOrg(orgslug, '') +
+ `/course/${courseuuid}/activity/${firstActivity.activity_uuid.replace('activity_', '')}`
+ )
+ } else {
+ router.refresh()
+ }
}
+ } catch (error) {
+ console.error('Failed to perform course action:', error)
+ } finally {
+ setIsActionLoading(false)
}
}
@@ -185,13 +291,16 @@ const Actions = ({ courseuuid, orgslug, course }: CourseActionsProps) => {