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 && (
)}
{/* 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