-
Notifications
You must be signed in to change notification settings - Fork 0
Feat: Update and introduce new course content renderings #295
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
Merged
Marty-Byrde
merged 25 commits into
canary
from
feat/introduce-content-accessibility-features
Apr 2, 2026
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
ba67ddd
feat: drafted `CourseContentOverview` component
Marty-Byrde 5c22859
ref: added course contents to `OverviewSection`
Marty-Byrde 97a6bfc
feat: drafted `/courses/contents/[courseId]` page
Marty-Byrde 57740ba
feat: added "show contents" item to `CourseActionMenu`
Marty-Byrde f5a34b4
ref: modularized `ContentSection` into subcomponents
Marty-Byrde 510a43f
feat: redesigned `ContentSection` layout
Marty-Byrde 26bc7a1
ref: re-used `ContentSection` in `OverviewSection`
Marty-Byrde d5ff949
ref: added course content count to `CourseCard`
Marty-Byrde 5248f24
feat: drafted course contents navigation panel
Marty-Byrde 5d0e918
feat: created `ContentProvider` component
Marty-Byrde 7028762
feat: introduced `ContentSwitchButton` component
Marty-Byrde 9b53b64
feat: added `ContentRenderer` component
Marty-Byrde 3ca2726
feat: created `ContentsPageBreadcrumbs` component
Marty-Byrde a7f3978
feat: added `ContentNavigationButtons` components
Marty-Byrde 45fe45c
ref: updated course contents page layout
Marty-Byrde f6dfcf7
feat: redesigned course content navigation btn appearances
Marty-Byrde da8c70a
ref: added `editorContainerClassname` arg to `RichTextEditor`
Marty-Byrde 470cb20
ref: adjusted `RichTextEditor` height in course content page
Marty-Byrde d698946
ref: localized course contents page and components
Marty-Byrde 62daab8
ref: removed contents navigation on small screens
Marty-Byrde 5f628ba
Revert "ref: adjusted `RichTextEditor` height in course content page"
Marty-Byrde 2ca081b
ref: added empty course page safeguard redirect
Marty-Byrde b8815d0
ref: added safeguard against invalid index in `ContentSBtn`
Marty-Byrde 1156afa
ref: added missing translation in `ContentSection`
Marty-Byrde 7005501
ref: restructured internal `CreateContentButton` component
Marty-Byrde File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import { notFound, redirect } from 'next/navigation' | ||
| import { getCourseById } from '@/database/course/select' | ||
| import { NextContentButton, PreviousContentButton } from '@/src/components/courses/contents/[courseId]/ContentNavigationButtons' | ||
| import { ContentProvider } from '@/src/components/courses/contents/[courseId]/ContentProvider' | ||
| import { ContentRenderer } from '@/src/components/courses/contents/[courseId]/ContentRenderer' | ||
| import { ContentPageBreadcrumbs } from '@/src/components/courses/contents/[courseId]/ContentsPageBreadcrumbs' | ||
| import { ContentSwitchButton } from '@/src/components/courses/contents/[courseId]/ContentSwitchButton' | ||
| import PageHeading from '@/src/components/Shared/PageHeading' | ||
| import { getScopedI18n } from '@/src/i18n/server-localization' | ||
| import _logger from '@/src/lib/log/Logger' | ||
| import getReferer from '@/src/lib/Shared/getReferer' | ||
|
|
||
| const logger = _logger.createModuleLogger('/' + import.meta.url.split('/').reverse().slice(0, 2).reverse().join('/')!) | ||
|
|
||
| export default async function CourseContentsPage({ params }: { params: Promise<{ courseId: string }> }) { | ||
| const t = await getScopedI18n('Courses.Contents') | ||
| const { courseId } = await params | ||
| const course = await getCourseById(courseId) | ||
| const referer = await getReferer() | ||
|
|
||
| if (!course) notFound() | ||
| if (course.contents.length === 0) { | ||
| logger.info(`Course (id: ${course.id}) has no contents, redirecting user back to ${referer ?? '/courses'} page.`) | ||
|
|
||
| // redirects users to where they came from when redirected by the app, or to the /courses page if user navigated manually. | ||
| redirect(referer ?? '/courses') | ||
| } | ||
|
|
||
| return ( | ||
| <> | ||
| <ContentPageBreadcrumbs course={course} /> | ||
| <PageHeading title={t('title')} description={t('description')} /> | ||
|
|
||
| <ContentProvider initialProps={{ contents: course.contents }}> | ||
| <div className='flex flex-1 gap-8'> | ||
| <div className='flex flex-1 flex-col gap-6'> | ||
| <ContentRenderer /> | ||
| <div className='flex justify-between'> | ||
| <PreviousContentButton /> | ||
| <NextContentButton /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className='hidden flex-col gap-2 @4xl:flex'> | ||
| <h2 className='font-semibold'>{t('Navigation.title')}</h2> | ||
| {course.contents.map((c) => ( | ||
| <ContentSwitchButton content={c} key={c.categoryId} /> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </ContentProvider> | ||
| </> | ||
| ) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
src/components/courses/contents/[courseId]/ContentNavigationButtons.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| 'use client' | ||
|
|
||
| import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react' | ||
| import { useContentContext } from '@/src/components/courses/contents/[courseId]/ContentProvider' | ||
| import { Button } from '@/src/components/shadcn/button' | ||
| import { useScopedI18n } from '@/src/i18n/client-localization' | ||
|
|
||
| export function NextContentButton() { | ||
| const t = useScopedI18n('Courses.Contents.Navigation') | ||
| const { contents, currentContentIndex, setCurrentIndex } = useContentContext() | ||
|
|
||
| if (contents.length <= currentContentIndex + 1) return <div /> | ||
| return ( | ||
| <Button onClick={() => setCurrentIndex((prev) => prev + 1)} variant='ghost' className='group flex h-fit max-w-56 flex-col gap-1'> | ||
| <span className='self-start text-sm text-muted-foreground'>{t('next_btn_label')}</span> | ||
| <div className='flex items-center gap-1 group-hover:text-primary group-hover:underline'> | ||
| {contents.at(currentContentIndex + 1)?.title} | ||
| <ChevronRightIcon className='transition-transform group-hover:scale-125' /> | ||
| </div> | ||
| </Button> | ||
| ) | ||
| } | ||
|
|
||
| export function PreviousContentButton() { | ||
| const t = useScopedI18n('Courses.Contents.Navigation') | ||
| const { contents, currentContentIndex, setCurrentIndex } = useContentContext() | ||
|
|
||
| if (currentContentIndex - 1 < 0) return <div /> | ||
|
|
||
| return ( | ||
| <Button onClick={() => setCurrentIndex((prev) => prev - 1)} variant='ghost' className='group flex h-fit max-w-56 flex-col gap-1'> | ||
| <span className='self-end text-right text-sm text-muted-foreground'>{t('previous_btn_label')}</span> | ||
| <div className='flex items-center gap-1 group-hover:text-primary group-hover:underline'> | ||
| <ChevronLeftIcon className='transition-transform group-hover:scale-125' /> | ||
| {contents.at(currentContentIndex - 1)?.title} | ||
| </div> | ||
| </Button> | ||
| ) | ||
| } |
26 changes: 26 additions & 0 deletions
26
src/components/courses/contents/[courseId]/ContentProvider.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| 'use client' | ||
|
|
||
| import { createContext, SetStateAction, useContext, useState } from 'react' | ||
| import { Course } from '@/src/schemas/CourseSchema' | ||
|
|
||
| type ContextProps = { | ||
| currentContentIndex: number | ||
| setCurrentIndex: React.Dispatch<SetStateAction<number>> | ||
| contents: Course['contents'] | ||
| } | ||
|
|
||
| const Context = createContext<ContextProps | null>(null) | ||
|
|
||
| export function ContentProvider({ children, initialProps }: { children: React.ReactNode; initialProps?: Partial<ContextProps> }) { | ||
| const [currentIndex, setCurrentIndex] = useState(initialProps?.currentContentIndex ?? 0) | ||
|
|
||
| return <Context.Provider value={{ currentContentIndex: currentIndex, setCurrentIndex, contents: [], ...initialProps }}>{children}</Context.Provider> | ||
| } | ||
|
|
||
| export function useContentContext() { | ||
| const ctx = useContext(Context) | ||
|
|
||
| if (!ctx) throw new Error('useContentContext may only be used within a <ContentProvider />') | ||
|
|
||
| return ctx | ||
| } |
11 changes: 11 additions & 0 deletions
11
src/components/courses/contents/[courseId]/ContentRenderer.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| 'use client' | ||
|
|
||
| import { useContentContext } from '@/src/components/courses/contents/[courseId]/ContentProvider' | ||
| import { RichTextEditor } from '@/src/components/tiptap-examples/RichTextEditor' | ||
| import { cn } from '@/src/lib/Shared/utils' | ||
|
|
||
| export function ContentRenderer() { | ||
| const { contents, currentContentIndex } = useContentContext() | ||
|
|
||
| return <RichTextEditor readOnly defaultContent={contents.at(currentContentIndex)?.content} editorPaneClassname={cn('border-dashed p-4')} /> | ||
| } |
26 changes: 26 additions & 0 deletions
26
src/components/courses/contents/[courseId]/ContentSwitchButton.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| 'use client' | ||
| import { useMemo } from 'react' | ||
| import { BookTextIcon } from 'lucide-react' | ||
| import { useContentContext } from '@/src/components/courses/contents/[courseId]/ContentProvider' | ||
| import { Button } from '@/src/components/shadcn/button' | ||
| import { cn } from '@/src/lib/Shared/utils' | ||
| import { Course } from '@/src/schemas/CourseSchema' | ||
|
|
||
| /** | ||
| * Used to switch between available contents. Shows the title of a given content as the button-label in combination with an icon. | ||
| */ | ||
| export function ContentSwitchButton({ content }: { content: Course['contents'][number] }) { | ||
| const { contents, currentContentIndex, setCurrentIndex } = useContentContext() | ||
| const index = useMemo(() => contents.findIndex((c) => c.categoryId === content.categoryId), [content, contents]) | ||
|
|
||
| return ( | ||
| <Button | ||
| disabled={index === -1} // index retrieval was unsuccesful -> switching not possible | ||
| variant='link' | ||
| className={cn('w-fit text-ellipsis', index === currentContentIndex ? 'text-primary underline' : 'text-muted-foreground')} | ||
| onClick={() => setCurrentIndex(index)}> | ||
| <BookTextIcon /> | ||
| {content.title} | ||
| </Button> | ||
| ) | ||
| } |
32 changes: 32 additions & 0 deletions
32
src/components/courses/contents/[courseId]/ContentsPageBreadcrumbs.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import Link from 'next/link' | ||
| import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from '@/src/components/shadcn/breadcrumb' | ||
| import { Course } from '@/src/schemas/CourseSchema' | ||
|
|
||
| export function ContentPageBreadcrumbs({ course }: { course: Course }) { | ||
| return ( | ||
| <Breadcrumb> | ||
| <BreadcrumbList> | ||
| <BreadcrumbItem> | ||
| <BreadcrumbLink asChild> | ||
| <Link href={`/`}>Home</Link> | ||
| </BreadcrumbLink> | ||
| </BreadcrumbItem> | ||
| <BreadcrumbSeparator /> | ||
| <BreadcrumbItem> | ||
| <BreadcrumbLink asChild> | ||
| <Link href={`/courses`}>Courses</Link> | ||
| </BreadcrumbLink> | ||
| </BreadcrumbItem> | ||
| <BreadcrumbSeparator /> | ||
| <BreadcrumbItem> | ||
| <BreadcrumbLink asChild> | ||
| <Link href={`/courses/edit/${course.id}`}>{course.name}</Link> | ||
| </BreadcrumbLink> | ||
| </BreadcrumbItem> | ||
| <BreadcrumbSeparator /> | ||
|
|
||
| <BreadcrumbPage>Contents</BreadcrumbPage> | ||
| </BreadcrumbList> | ||
| </Breadcrumb> | ||
| ) | ||
| } |
138 changes: 88 additions & 50 deletions
138
src/components/courses/create/(sections)/ContentSection.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,68 +1,106 @@ | ||
| 'use client' | ||
|
|
||
| import { generateHTML } from '@tiptap/core' | ||
| import { PenIcon, PlusCircleIcon, TrashIcon } from 'lucide-react' | ||
| import { Folder, Info, Pen, Plus, Trash2 } from 'lucide-react' | ||
| import CourseContentDialog from '@/src/components/courses/create/(sections)/CourseContentDialog' | ||
| import { useCourseStore } from '@/src/components/courses/create/CreateCourseProvider' | ||
| import { Button } from '@/src/components/shadcn/button' | ||
| import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle } from '@/src/components/shadcn/card' | ||
| import GenericCard from '@/src/components/Shared/Card' | ||
| import { CardStageJumpButton } from '@/src/components/Shared/CardStageJumpButton' | ||
| import ConfirmationDialog from '@/src/components/Shared/ConfirmationDialog/ConfirmationDialog' | ||
| import { RichTextEditor, RichTextEditorExtensions } from '@/src/components/tiptap-examples/RichTextEditor' | ||
| import { useScopedI18n } from '@/src/i18n/client-localization' | ||
| import { cn } from '@/src/lib/Shared/utils' | ||
|
|
||
| export default function ContentSection() { | ||
| const { contents, removeCourseContent } = useCourseStore((store) => store) | ||
| export default function ContentSection({ jumpBackButton, disabled }: { jumpBackButton?: boolean; disabled?: boolean }) { | ||
| const { contents } = useCourseStore((store) => store) | ||
| const t = useScopedI18n('Courses.Create.ContentSection') | ||
|
|
||
| return ( | ||
| <div className='flex flex-1 flex-col gap-10'> | ||
| <div className='flex flex-col gap-1'> | ||
| <h2 className='h-fit text-xl font-semibold'>{t('title')}</h2> | ||
| <span className='text-muted-foreground'>{t('description')}</span> | ||
| <GenericCard disableInteractions className='relative flex break-inside-avoid flex-col p-3'> | ||
| {jumpBackButton && <CardStageJumpButton targetStage={2} />} | ||
| <div className='-mx-3 -mt-3 flex flex-col rounded-t-md border-b border-neutral-400 bg-neutral-300 p-2 px-3 text-neutral-600 dark:border-neutral-500 dark:bg-neutral-700/60 dark:text-neutral-300'> | ||
| <div className='flex flex-col gap-0'> | ||
| <h2 className=''>{t('title')}</h2> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className='grid grid-cols-[repeat(auto-fill,minmax(380px,1fr))] gap-12'> | ||
| <CourseContentDialog mode='create'> | ||
| <Card className='flex h-full items-center justify-center'> | ||
| <CardContent className='flex gap-4 text-primary'> | ||
| <PlusCircleIcon /> {t('Actions.create_new_button_label')} | ||
| </CardContent> | ||
| </Card> | ||
| </CourseContentDialog> | ||
| {contents.map((content) => { | ||
| const htmlContent = content.content ? generateHTML(content.content, RichTextEditorExtensions) : '' | ||
| {contents.length > 0 ? <CourseContentRenderer disabled={disabled} /> : <EmptyCourseContentBody />} | ||
|
|
||
| return ( | ||
| <Card key={content.categoryId} className=''> | ||
| <CardHeader> | ||
| <CardTitle>{content.title}</CardTitle> | ||
| <CardDescription>{content.description}</CardDescription> | ||
| <CardAction> | ||
| <CourseContentDialog mode='edit' courseContent={content}> | ||
| <Button variant='link' asChild aria-label={t('Actions.edit_content_button_aria_label')} className='enabled:text-orange-400 dark:enabled:text-orange-300/80'> | ||
| <PenIcon /> | ||
| {t('Actions.edit_content_button_label')} | ||
| </Button> | ||
| </CourseContentDialog> | ||
| <CreateContentButton disabled={disabled} /> | ||
| </GenericCard> | ||
| ) | ||
| } | ||
|
|
||
| <ConfirmationDialog | ||
| confirmAction={() => removeCourseContent(content.categoryId)} | ||
| confirmLabel={t('Actions.delete_content_confirm_label')} | ||
| body={t('Actions.delete_content_dialog_body')}> | ||
| <Button variant='link' asChild aria-label={t('Actions.delete_content_button_aria_label')} className='enabled:text-destructive/80'> | ||
| <TrashIcon /> | ||
| {t('Actions.delete_content_button_label')} | ||
| </Button> | ||
| </ConfirmationDialog> | ||
| </CardAction> | ||
| </CardHeader> | ||
| <CardContent className='flex h-full px-4.5 **:[div]:[[role=presentation]]:max-h-42 **:[div]:[[role=presentation]]:min-h-auto **:[div]:[[role=presentation]]:cursor-default **:[div]:[[role=presentation]]:border-ring-subtle **:[div]:[[role=presentation]]:p-2.5'> | ||
| <RichTextEditor key={htmlContent} defaultContent={content.content} readOnly size='sm' /> | ||
| </CardContent> | ||
| </Card> | ||
| ) | ||
| })} | ||
| </div> | ||
| function CreateContentButton({ disabled }: { disabled?: boolean }) { | ||
| const t = useScopedI18n('Courses.Create.ContentSection') | ||
|
|
||
| return ( | ||
| <div className='flex justify-center gap-8'> | ||
| <CourseContentDialog mode='create'> | ||
| <Button variant='outline' size='lg' disabled={disabled}> | ||
| <Plus className='size-5' /> | ||
| {t('Actions.create_new_button_label')} | ||
| </Button> | ||
|
Marty-Byrde marked this conversation as resolved.
|
||
| </CourseContentDialog> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| function CourseContentRenderer({ disabled }: { disabled?: boolean }) { | ||
| const t = useScopedI18n('Courses.Create.ContentSection') | ||
| const { contents, removeCourseContent, questionCategories } = useCourseStore((store) => store) | ||
|
|
||
| return ( | ||
| <div className={cn('my-4 grid flex-1 grid-cols-1 gap-6')}> | ||
| {contents.map((content, i) => ( | ||
| <GenericCard disableInteractions key={i + content.categoryId} className={cn('relative flex h-fit gap-3 p-2 first:mt-3 hover:bg-none')}> | ||
|
Marty-Byrde marked this conversation as resolved.
|
||
| <div className='flex flex-1 flex-col p-1'> | ||
| <div className='flex items-center justify-between'> | ||
| <h2 className='text-neutral-700 dark:text-neutral-300'>{content.title}</h2> | ||
| <span className='text-neutral-700 dark:text-neutral-300'>{}</span> | ||
| </div> | ||
|
Marty-Byrde marked this conversation as resolved.
|
||
| <div className='flex justify-between text-xs'> | ||
| <span className='text-neutral-500 lowercase dark:text-neutral-400'>{content.description}</span> | ||
| <div className='flex items-center gap-1 text-neutral-500 dark:text-neutral-400'> | ||
| <Folder className='size-3' /> | ||
| <span className='lowercase'>{questionCategories.find((c) => c.id === content.categoryId)?.name}</span> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <CourseContentDialog mode='edit' courseContent={content}> | ||
| <Button | ||
| aria-label={t('Actions.edit_content_button_aria_label')} | ||
| size='icon' | ||
| variant='base' | ||
| type='button' | ||
| disabled={disabled} | ||
| className='group my-auto flex size-7.5 items-center gap-4 rounded-lg bg-neutral-300/50 p-1.5 ring-1 ring-neutral-400 hover:cursor-pointer hover:ring-[1.5px] hover:ring-ring-hover dark:bg-neutral-700 dark:ring-neutral-600 dark:hover:ring-ring-hover'> | ||
| <Pen className='size-4 text-orange-600/70 group-hover:stroke-3 dark:text-orange-400/70' /> | ||
| </Button> | ||
| </CourseContentDialog> | ||
|
|
||
| <ConfirmationDialog confirmAction={() => removeCourseContent(content.categoryId)} confirmLabel={t('Actions.delete_content_confirm_label')} body={t('Actions.delete_content_dialog_body')}> | ||
| <Button | ||
| size='icon' | ||
| variant='base' | ||
| aria-label={t('Actions.delete_content_button_aria_label')} | ||
| type='button' | ||
| disabled={disabled} | ||
| className='group my-auto flex size-7.5 items-center gap-4 rounded-lg bg-neutral-300/50 ring-1 ring-neutral-400 hover:cursor-pointer hover:ring-[1.5px] hover:ring-ring-hover dark:bg-neutral-700 dark:ring-neutral-600 dark:hover:ring-ring-hover'> | ||
| <Trash2 className='size-4 text-red-600/70 group-hover:stroke-[2.5] dark:text-red-400/70' /> | ||
| </Button> | ||
| </ConfirmationDialog> | ||
| </GenericCard> | ||
| ))} | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| function EmptyCourseContentBody() { | ||
| const t = useScopedI18n('Courses.Create.ContentSection') | ||
| return ( | ||
| <div className={cn('flex min-h-60 flex-1 flex-col items-center justify-center gap-6')}> | ||
| <Info className='size-16 text-neutral-400 dark:text-neutral-500' /> | ||
| <span className='text-center tracking-wide text-balance text-neutral-500 dark:text-neutral-400'>{t('empty_content_text')}</span> | ||
| </div> | ||
|
Marty-Byrde marked this conversation as resolved.
|
||
| ) | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.