Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creeland/egg 239 re create lesson layout #1428

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/app/(content)/courses/[course]/[lesson]/Description.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use client'
import Markdown from 'react-markdown'

export default function Description({description}: {description: string}) {
return (
<Markdown className="font-medium prose prose-lg dark:prose-dark dark:prose-a:text-blue-300 prose-a:text-blue-500 max-w-none text-gray-1000 dark:text-white">
{description}
</Markdown>
)
}
112 changes: 112 additions & 0 deletions src/app/(content)/courses/[course]/[lesson]/LessonHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React from 'react'
import {LessonResource} from '@/types'
import {get} from 'lodash'
import Link from 'next/link'
import {notFound} from 'next/navigation'
import Image from 'next/image'
import Eggo from '@/components/icons/eggo'
import Share from '@/components/share'
import Markdown from 'react-markdown'
import Tabs from './Tabs'
import Title from './Title'

const LessonHeader = async ({
userToken,
lessonLoader,
courseLoader,
}: {
userToken?: string
lessonLoader: Promise<LessonResource>
courseLoader: Promise<any>
}) => {
const lesson = await lessonLoader

console.log('USER TOKEN: ', userToken)

if (!lesson) {
return notFound()
}

const {description, instructor, slug, transcript_url, transcript, comments} =
lesson

const instructorPagePath = `/q/resources-by-${get(instructor, 'slug', '#')}`

console.log('SERVER?: ', typeof window === 'undefined')
return (
<div className="container max-w-screen-lg py-8 md:py-12 lg:py-16">
<div className="grid grid-cols-1 gap-8 divide-y lg:grid-cols-1 lg:gap-12 md:divide-transparent divide-gray-50">
<div className="row-start-1 space-y-6 md:col-span-8 md:row-start-1 md:space-y-8 lg:space-y-10">
<div className="pb-2 space-y-4 sm:pb-8">
<Title
courseLoader={courseLoader}
lesson={lesson}
userToken={userToken}
/>
<div className="flex flex-col flex-wrap justify-between w-full pt-4 space-y-5 lg:flex-row lg:space-x-8 lg:space-y-0 lg:items-center">
<div className="flex items-center justify-between w-full space-x-5 md:w-auto">
{instructor && (
<div className="flex items-center flex-shrink-0">
<Link
href={instructorPagePath}
className="flex mr-2 itemes-center"
>
{get(instructor, 'avatar_64_url') ? (
<Image
width={48}
height={48}
src={instructor.avatar_64_url}
alt={instructor.full_name}
className="m-0 rounded-full"
/>
) : (
<Eggo className="w-8 rounded-full" />
)}
</Link>
<div className="flex flex-col">
<span className="text-xs">Instructor</span>
{get(instructor, 'full_name') && (
<Link
href={instructorPagePath}
className="font-semibold leading-tighter hover:underline"
>
{instructor.full_name}
</Link>
)}
</div>
</div>
)}
</div>
<div className="flex items-center space-x-8">
<div className="flex flex-col items-center space-y-2 md:flex-row md:space-y-0 md:space-x-2">
<Share
className="flex flex-col items-end "
resource={{
path: lesson.path,
title: lesson.title,
type: 'lesson',
}}
instructor={instructor}
/>
</div>
</div>
</div>
{description && (
<Markdown className="font-medium prose prose-lg dark:prose-dark dark:prose-a:text-blue-300 prose-a:text-blue-500 max-w-none text-gray-1000 dark:text-white">
{description}
</Markdown>
)}
</div>
<Tabs
lessonSlug={slug}
transcript={transcript}
transcript_url={transcript_url}
comments={comments}
/>
</div>
</div>
</div>
)
}

export default LessonHeader
55 changes: 55 additions & 0 deletions src/app/(content)/courses/[course]/[lesson]/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use client'
import React from 'react'
import {Tab, TabList, TabPanel, TabPanels, Tabs} from '@reach/tabs'
import {filter} from 'lodash'
import Transcript from '@/components/pages/lessons/transcript'
import Comments from '@/components/pages/lessons/comments/comments'
import {useEnhancedTranscript} from '@/hooks/use-enhanced-transcript'

export default function LessonTabs({
transcript,
transcript_url,
lessonSlug,
comments,
}: {
transcript?: any
transcript_url: string
lessonSlug: string
comments?: any
}) {
const [tabIndex, setTabIndex] = React.useState(0)

const numberOfComments = filter(
comments,
(comment: any) => comment.state !== 'hidden',
).length

const enhancedTranscript = useEnhancedTranscript(transcript_url)
const transcriptAvailable = transcript || enhancedTranscript

return (
<Tabs index={tabIndex} onChange={(index) => setTabIndex(index)}>
<TabList>
<Tab>
Comments <span className="text-sm">({numberOfComments})</span>
</Tab>
{transcriptAvailable && <Tab>Transcript</Tab>}
</TabList>
<TabPanels className="p-5 rounded-lg rounded-tl-none bg-gray-50 dark:bg-gray-1000 sm:p-8">
<TabPanel>
<div className="space-y-6 sm:space-y-8 break-[break-word]">
<Comments slug={lessonSlug} comments={comments} />
</div>
</TabPanel>
{transcriptAvailable && (
<TabPanel>
<Transcript
initialTranscript={transcript}
enhancedTranscript={enhancedTranscript}
/>
</TabPanel>
)}
</TabPanels>
</Tabs>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client'
import React, {use} from 'react'
import {CheckCircleIcon} from '@heroicons/react/solid'
import {CheckCircleIcon as CheckCircleIconOutline} from '@heroicons/react/outline'

export default function LessonCompletedClient({
lessonProgressLoader,
markLessonComplete,
}: {
lessonProgressLoader: Promise<any>
markLessonComplete: () => Promise<void>
}) {
const [lessonCompleted, setLessonCompleted] = React.useState<boolean>(false)
const lessonProgress = use(lessonProgressLoader)
const {completed} = lessonProgress.lessonProgress

React.useEffect(() => {
if (completed) {
setLessonCompleted(completed)
}
}, [completed])

return lessonCompleted ? (
<span className="self-center">
<CheckCircleIcon className="h-5 w-5 text-green-500 rounded-full" />
</span>
) : (
<span
className="self-center"
onClick={() => {
markLessonComplete()
setLessonCompleted(true)
}}
>
<CheckCircleIconOutline className="h-5 w-5 text-gray-300 hover:text-green-500 hover:cursor-pointer " />
</span>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react'
import LessonCompletedClient from './LessonCompletedClient'

export default async function LessonCompleted({
lessonProgressLoader,
courseLoader,
userToken,
lessonId,
}: {
lessonProgressLoader: Promise<any>
courseLoader: Promise<any>
userToken?: string
lessonId: string | number
}) {
const course = await courseLoader
const markLessonComplete = async () => {
'use server'

await fetch(
`${process.env.NEXT_PUBLIC_AUTH_DOMAIN}/watch/manual_complete`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${userToken}`,
'X-SITE-CLIENT': process.env.NEXT_PUBLIC_CLIENT_ID as string,
'Content-Type': 'application/json',
},

body: JSON.stringify({
lesson_view: {
lesson_id: lessonId,
collection_id: course.id,
collection_type: 'playlist',
},
}),
},
)
}

return (
<LessonCompletedClient
lessonProgressLoader={lessonProgressLoader}
markLessonComplete={markLessonComplete}
/>
)
}
41 changes: 41 additions & 0 deletions src/app/(content)/courses/[course]/[lesson]/Title/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, {Suspense} from 'react'
import {LessonResource} from '@/types'
import LessonCompleted from './LessonCompleted'
import {loadLessonProgress} from '@/lib/progress'

export default async function Title({
lesson,
userToken,
courseLoader,
}: {
lesson: LessonResource
courseLoader: Promise<any>
userToken?: string
}) {
const {title, slug} = lesson

const lessonProgressLoader = loadLessonProgress({
token: userToken,
slug: slug,
})

return title && userToken ? (
<div className="flex space-x-2 -ml-7">
<Suspense>
<LessonCompleted
userToken={userToken}
courseLoader={courseLoader}
lessonProgressLoader={lessonProgressLoader}
lessonId={lesson.id}
/>
</Suspense>
<h1 className="text-xl font-extrabold leading-tight lg:text-3xl">
{title}
</h1>
</div>
) : (
<h1 className="text-xl font-extrabold leading-tight lg:text-3xl">
{title}
</h1>
)
}
53 changes: 16 additions & 37 deletions src/app/(content)/courses/[course]/[lesson]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import {loadLesson} from '@/lib/lessons'
import {loadCourse} from '@/lib/courses'
import {LessonResource} from '@/types'
import {notFound} from 'next/navigation'
import {PlayerTwo} from '@/app/(content)/courses/[course]/[lesson]/Player'
import {Suspense} from 'react'
import {ACCESS_TOKEN_KEY} from '@/utils/auth'
import {getAbilityFromToken} from '@/server/ability'
import {redirect} from 'next/navigation'
import {cookies} from 'next/headers'
import LessonHeader from './LessonHeader'

export default async function LessonPage({
searchParams,
Expand All @@ -21,47 +20,27 @@ export default async function LessonPage({
}) {
const cookieStore = cookies()
const userToken = cookieStore?.get(ACCESS_TOKEN_KEY ?? '')?.value
const ability = await getAbilityFromToken(userToken)
// const ability = await getAbilityFromToken(userToken)

if (!ability.can('create', 'Content')) {
redirect('/')
}
// if (!ability.can('create', 'Content')) {
// redirect('/')
// }

const lessonLoader = loadLesson(params.lesson)
const courseLoader = loadCourse(params.course)

return (
<div>
<Suspense>
<div className="bg-black w-full lg:grid lg:grid-cols-12 lg:space-y-0 relative">
<div className="relative before:float-left after:clear-both after:table col-span-9">
<PlayerTwo
lessonLoader={lessonLoader}
courseLoader={courseLoader}
/>
</div>
<Suspense>
<div className="bg-black w-full lg:grid lg:grid-cols-12 lg:space-y-0 relative">
<div className="relative before:float-left after:clear-both after:table col-span-9">
<PlayerTwo lessonLoader={lessonLoader} courseLoader={courseLoader} />
</div>
</Suspense>
<LessonHeader lessonLoader={lessonLoader} />
</div>
)
}

const LessonHeader = async ({
lessonLoader,
}: {
lessonLoader: Promise<LessonResource>
}) => {
const lesson = await lessonLoader

if (!lesson) {
return notFound()
}

return (
<div>
<h1>{lesson.title}</h1>
<p>{lesson.description}</p>
</div>
</div>
<LessonHeader
lessonLoader={lessonLoader}
courseLoader={courseLoader}
userToken={userToken}
/>
</Suspense>
)
}
Loading
Loading