diff --git a/apps/web/components/Objects/Courses/CourseActions/CoursesActions.tsx b/apps/web/components/Objects/Courses/CourseActions/CoursesActions.tsx index 73fc3349..fe4e7023 100644 --- a/apps/web/components/Objects/Courses/CourseActions/CoursesActions.tsx +++ b/apps/web/components/Objects/Courses/CourseActions/CoursesActions.tsx @@ -8,10 +8,12 @@ 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 } from 'lucide-react' +import { LogIn, LogOut, ShoppingCart, AlertCircle, UserPen, ClockIcon } from 'lucide-react' import Modal from '@components/Objects/StyledElements/Modal/Modal' import CoursePaidOptions from './CoursePaidOptions' import { checkPaidAccess } from '@services/payments/payments' +import { applyForContributor, getCourseContributors } from '@services/courses/courses' +import toast from 'react-hot-toast' interface Author { user: { @@ -44,6 +46,7 @@ interface Course { activity_type: string }> }> + open_to_contributors?: boolean } interface CourseActionsProps { @@ -186,8 +189,10 @@ const Actions = ({ courseuuid, orgslug, course }: CourseActionsProps) => { const [linkedProducts, setLinkedProducts] = useState([]) const [isLoading, setIsLoading] = useState(true) const [isActionLoading, setIsActionLoading] = useState(false) + const [isContributeLoading, setIsContributeLoading] = useState(false) const [isModalOpen, setIsModalOpen] = useState(false) const [hasAccess, setHasAccess] = useState(null) + const [contributorStatus, setContributorStatus] = useState<'NONE' | 'PENDING' | 'ACTIVE' | 'INACTIVE'>('NONE') const isStarted = course.trail?.runs?.some( (run) => run.status === 'STATUS_IN_PROGRESS' && run.course_id === course.id @@ -212,6 +217,39 @@ const Actions = ({ courseuuid, orgslug, course }: CourseActionsProps) => { fetchLinkedProducts() }, [course.id, course.org_id, session.data?.tokens?.access_token]) + // Check if the current user is already a contributor + useEffect(() => { + const checkContributorStatus = async () => { + if (!session.data?.user) return + + try { + const response = await getCourseContributors( + 'course_' + courseuuid, + session.data?.tokens?.access_token + ) + + if (response && response.data) { + const currentUser = response.data.find( + (contributor: any) => contributor.user_id === session.data.user.id + ) + + if (currentUser) { + setContributorStatus(currentUser.authorship_status as 'PENDING' | 'ACTIVE' | 'INACTIVE') + } else { + setContributorStatus('NONE') + } + } + } catch (error) { + console.error('Failed to check contributor status:', error) + toast.error('Failed to check contributor status. Please try again later.') + } + } + + if (session.data?.user) { + checkContributorStatus() + } + }, [courseuuid, session.data?.tokens?.access_token, session.data?.user]) + useEffect(() => { const checkAccess = async () => { if (!session.data?.user) return @@ -225,6 +263,7 @@ const Actions = ({ courseuuid, orgslug, course }: CourseActionsProps) => { } catch (error) { console.error('Failed to check course access') + toast.error('Failed to check course access. Please try again later.') setHasAccess(false) } } @@ -241,14 +280,20 @@ const Actions = ({ courseuuid, orgslug, course }: CourseActionsProps) => { } setIsActionLoading(true) + const loadingToast = toast.loading( + isStarted ? 'Leaving course...' : 'Starting course...' + ) + try { if (isStarted) { await removeCourse('course_' + courseuuid, orgslug, session.data?.tokens?.access_token) await revalidateTags(['courses'], orgslug) + toast.success('Successfully left the course', { id: loadingToast }) router.refresh() } else { await startCourse('course_' + courseuuid, orgslug, session.data?.tokens?.access_token) await revalidateTags(['courses'], orgslug) + toast.success('Successfully started the course', { id: loadingToast }) // Get the first activity from the first chapter const firstChapter = course.chapters?.[0] @@ -266,15 +311,101 @@ const Actions = ({ courseuuid, orgslug, course }: CourseActionsProps) => { } } catch (error) { console.error('Failed to perform course action:', error) + toast.error( + isStarted + ? 'Failed to leave the course. Please try again later.' + : 'Failed to start the course. Please try again later.', + { id: loadingToast } + ) } finally { setIsActionLoading(false) } } + const handleApplyToContribute = async () => { + if (!session.data?.user) { + router.push(getUriWithoutOrg(`/signup?orgslug=${orgslug}`)) + return + } + + setIsContributeLoading(true) + const loadingToast = toast.loading('Submitting contributor application...') + + try { + const data = { + message: "I would like to contribute to this course." + } + + await applyForContributor('course_' + courseuuid, data, session.data?.tokens?.access_token) + setContributorStatus('PENDING') + await revalidateTags(['courses'], orgslug) + toast.success('Your application to contribute has been submitted successfully', { id: loadingToast }) + } catch (error) { + console.error('Failed to apply as contributor:', error) + toast.error('Failed to submit your application. Please try again later.', { id: loadingToast }) + } finally { + setIsContributeLoading(false) + } + } + if (isLoading) { return
} + 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; + } + + if (!session.data?.user) { + return ( + + ); + } + + if (contributorStatus === 'ACTIVE') { + return ( +
+ + You are a contributor +
+ ); + } + + if (contributorStatus === 'PENDING') { + return ( +
+ + Contributor application pending +
+ ); + } + + return ( + + ); + }; + if (linkedProducts.length > 0) { return (
@@ -312,6 +443,7 @@ const Actions = ({ courseuuid, orgslug, course }: CourseActionsProps) => { )} + {renderContributorButton()} ) : (
@@ -342,6 +474,7 @@ const Actions = ({ courseuuid, orgslug, course }: CourseActionsProps) => { Purchase Course + {renderContributorButton()} )}
@@ -349,34 +482,37 @@ const Actions = ({ courseuuid, orgslug, course }: CourseActionsProps) => { } return ( - +
+ + {renderContributorButton()} +
) }