Files
classroomio/apps/dashboard/src/routes/courses/[id]/+layout.svelte
T

121 lines
3.5 KiB
Svelte
Raw Normal View History

2023-11-07 08:51:49 +00:00
<script lang="ts">
2026-03-09 11:18:22 +01:00
import { goto } from '$app/navigation';
import * as Sidebar from '@cio/ui/base/sidebar';
2026-03-09 11:18:22 +01:00
import { Empty } from '@cio/ui/custom/empty';
import { Spinner } from '@cio/ui/base/spinner';
import { CourseSidebar } from '$features/course/components/sidebar';
import { CourseHeader } from '$features/course/components';
2026-03-09 11:18:22 +01:00
import type { Course } from '$features/course/types';
import * as Dialog from '@cio/ui/base/dialog';
import { Button } from '@cio/ui/base/button';
import { Confetti } from '$features/ui';
import { courseApi } from '$features/course/api';
import ContentCreateModal from '$features/course/components/content/content-create-modal.svelte';
import { profile } from '$lib/utils/store/user';
import { isOrgAdmin } from '$lib/utils/store/org';
import { t } from '$lib/utils/functions/translations';
import type { CourseMember } from '$features/course/utils/types';
2025-11-01 15:49:29 +01:00
interface Props {
children?: import('svelte').Snippet;
path: string;
2026-03-09 11:18:22 +01:00
data: {
course?: Course;
courseId: string;
exerciseId?: string;
};
2023-11-07 08:51:49 +00:00
}
2026-03-09 11:18:22 +01:00
let { children, path, data }: Props = $props();
// Initialize course store on the client (fetch once, then reuse store data).
$effect(() => {
if (!data.courseId || !$profile.id) return;
if (data.course) {
courseApi.setCourse(data.course, $profile.id);
return;
}
courseApi.ensureCourse(data.courseId, $profile.id);
});
const isCourseReady = $derived.by(() => {
return courseApi.course?.id === data.courseId && !!courseApi.group.id;
});
const user: CourseMember | undefined = $derived(
courseApi.group.people.find((person) => person.profileId === $profile.id)
);
const canCheck = $derived(!!$profile.id && isCourseReady);
const isPermitted = $derived.by(() => {
if (!isCourseReady) return false;
if (!canCheck) return true;
if ($isOrgAdmin === null) return true;
return $isOrgAdmin || user;
});
const isExercisePage = $derived(!!data.exerciseId);
2023-11-07 08:51:49 +00:00
</script>
2026-03-09 11:18:22 +01:00
<svelte:head>
<title>{courseApi.course?.title || 'ClassroomIO Course'}</title>
</svelte:head>
{#if isCourseReady}
<Dialog.Root open={!isPermitted}>
<Dialog.Content class="w-96">
<Dialog.Header>
<Dialog.Title>{$t('course.not_permitted.header')}</Dialog.Title>
</Dialog.Header>
<div>
<p class="text-md text-center dark:text-white">
{$t('course.not_permitted.body')}
</p>
<div class="mt-5 flex justify-center">
<Button
onclick={() => {
goto('/org/*');
}}
>
{$t('course.not_permitted.button')}
</Button>
</div>
</div>
</Dialog.Content>
</Dialog.Root>
{/if}
<Sidebar.Provider data-sveltekit-preload-data="off">
<CourseSidebar {path} id={data.courseId} {isCourseReady} />
2025-12-25 09:01:51 +00:00
<Sidebar.Inset
2026-03-09 11:18:22 +01:00
class="w-[calc(100vw-var(--sidebar-width))] group-data-[collapsible=icon]:w-[calc(100vw-var(--sidebar-width-icon))]"
2025-12-25 09:01:51 +00:00
>
<CourseHeader />
2026-03-09 11:18:22 +01:00
<ContentCreateModal />
{#if !isCourseReady}
<div class="mx-auto flex h-[calc(100vh-56px)] w-full items-center justify-center">
<Empty
title="Loading course…"
description="Please wait while we load your course data."
icon={Spinner}
iconClass="h-8 w-8"
variant="page"
/>
</div>
{:else}
{#if isExercisePage}
<Confetti />
{/if}
{@render children?.()}
2026-03-09 11:18:22 +01:00
{/if}
</Sidebar.Inset>
</Sidebar.Provider>