2024-05-21 00:14:47 +01:00
|
|
|
<script lang="ts">
|
2025-11-01 15:49:29 +01:00
|
|
|
import { preventDefault } from '$lib/utils/functions/svelte';
|
|
|
|
|
|
2024-12-30 02:59:10 +01:00
|
|
|
import { goto } from '$app/navigation';
|
2025-11-01 15:49:29 +01:00
|
|
|
import { page } from '$app/state';
|
2025-12-02 09:54:38 +01:00
|
|
|
import { validateForm } from '../utils/functions';
|
|
|
|
|
import { courses, createCourseModal } from '../utils/store';
|
2025-12-22 02:36:48 +01:00
|
|
|
import { TextareaField } from '@cio/ui/custom/textarea-field';
|
|
|
|
|
import { InputField } from '@cio/ui/custom/input-field';
|
|
|
|
|
import * as Dialog from '@cio/ui/base/dialog';
|
|
|
|
|
import { Button } from '@cio/ui/base/button';
|
2025-11-24 01:00:50 +01:00
|
|
|
import { ROLE } from '@cio/utils/constants';
|
2025-12-22 02:36:48 +01:00
|
|
|
import * as Field from '@cio/ui/base/field';
|
|
|
|
|
import * as RadioGroup from '@cio/ui/base/radio-group';
|
2024-12-30 02:59:10 +01:00
|
|
|
import { supabase } from '$lib/utils/functions/supabase';
|
|
|
|
|
import { t } from '$lib/utils/functions/translations';
|
|
|
|
|
import { addDefaultNewsFeed, addGroupMember } from '$lib/utils/services/courses';
|
|
|
|
|
import { capturePosthogEvent } from '$lib/utils/services/posthog';
|
|
|
|
|
import { currentOrg } from '$lib/utils/store/org';
|
|
|
|
|
import { profile } from '$lib/utils/store/user';
|
|
|
|
|
import { COURSE_TYPE, COURSE_VERSION } from '$lib/utils/types';
|
2021-12-30 22:30:30 +02:00
|
|
|
|
2025-11-01 15:49:29 +01:00
|
|
|
let isLoading = $state(false);
|
|
|
|
|
let errors = $state({
|
2023-09-18 17:03:56 +00:00
|
|
|
title: '',
|
|
|
|
|
description: ''
|
2025-11-01 15:49:29 +01:00
|
|
|
});
|
|
|
|
|
let step = $state(0);
|
2021-12-30 22:30:30 +02:00
|
|
|
|
2024-05-21 00:14:47 +01:00
|
|
|
const options = [
|
|
|
|
|
{
|
2025-12-22 02:36:48 +01:00
|
|
|
id: 'live-class',
|
2025-11-21 12:18:04 +01:00
|
|
|
title: $t('new_course_modal.live_class_label'),
|
|
|
|
|
subtitle: $t('new_course_modal.live_class_subtitle'),
|
2024-05-21 00:14:47 +01:00
|
|
|
type: COURSE_TYPE.LIVE_CLASS,
|
|
|
|
|
isDisabled: false
|
|
|
|
|
},
|
|
|
|
|
{
|
2025-12-22 02:36:48 +01:00
|
|
|
id: 'self-paced',
|
2025-11-21 12:18:04 +01:00
|
|
|
title: $t('new_course_modal.self_paced_label'),
|
|
|
|
|
subtitle: $t('new_course_modal.self_paced_subtitle'),
|
2024-05-21 00:14:47 +01:00
|
|
|
type: COURSE_TYPE.SELF_PACED,
|
|
|
|
|
isDisabled: false
|
|
|
|
|
}
|
|
|
|
|
];
|
2025-11-01 15:49:29 +01:00
|
|
|
let type = $state(options[0].type);
|
2023-09-24 07:08:37 +00:00
|
|
|
|
2024-07-11 09:03:35 +01:00
|
|
|
function onClose(redirectTo) {
|
2024-06-21 10:39:50 +01:00
|
|
|
goto(redirectTo);
|
2024-05-21 00:14:47 +01:00
|
|
|
|
2023-09-24 07:08:37 +00:00
|
|
|
createCourseModal.update(() => ({
|
|
|
|
|
title: '',
|
|
|
|
|
description: '',
|
2024-05-21 00:14:47 +01:00
|
|
|
type: '',
|
2023-09-24 07:08:37 +00:00
|
|
|
emails: '',
|
|
|
|
|
tutors: '',
|
|
|
|
|
students: ''
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-30 06:04:53 +00:00
|
|
|
async function createCourse() {
|
2023-10-03 12:29:06 +00:00
|
|
|
isLoading = true;
|
2023-09-18 17:03:56 +00:00
|
|
|
const { hasError, fieldErrors } = validateForm($createCourseModal);
|
2021-12-30 22:30:30 +02:00
|
|
|
|
|
|
|
|
errors = fieldErrors;
|
|
|
|
|
if (hasError) return;
|
|
|
|
|
|
|
|
|
|
const { title, description } = $createCourseModal;
|
|
|
|
|
// 1. Create group
|
|
|
|
|
const { data: newGroup } = await supabase
|
|
|
|
|
.from('group')
|
2023-08-02 19:35:27 +01:00
|
|
|
.insert({ name: title, description, organization_id: $currentOrg.id })
|
|
|
|
|
.select();
|
2021-12-30 22:30:30 +02:00
|
|
|
|
|
|
|
|
console.log(`newGroup`, newGroup);
|
2023-10-30 06:04:53 +00:00
|
|
|
|
|
|
|
|
if (!newGroup) return;
|
|
|
|
|
|
2021-12-30 22:30:30 +02:00
|
|
|
const { id: group_id } = newGroup[0];
|
|
|
|
|
|
|
|
|
|
// 2. Create course with group_id
|
2024-06-21 10:39:50 +01:00
|
|
|
const { data: newCourseData } = await supabase
|
2023-08-02 19:35:27 +01:00
|
|
|
.from('course')
|
|
|
|
|
.insert({
|
|
|
|
|
title,
|
|
|
|
|
description,
|
2024-05-21 00:14:47 +01:00
|
|
|
type: type,
|
2024-08-14 10:27:37 +01:00
|
|
|
version: COURSE_VERSION.V2,
|
2023-08-07 06:18:30 +00:00
|
|
|
group_id
|
2023-08-02 19:35:27 +01:00
|
|
|
})
|
|
|
|
|
.select();
|
2024-06-21 10:39:50 +01:00
|
|
|
console.log(`newCourse data`, newCourseData);
|
2021-12-30 22:30:30 +02:00
|
|
|
|
2024-06-21 10:39:50 +01:00
|
|
|
if (!newCourseData) return;
|
2023-10-30 06:04:53 +00:00
|
|
|
|
2024-06-21 10:39:50 +01:00
|
|
|
const newCourse = newCourseData[0];
|
|
|
|
|
courses.update((_courses) => [..._courses, newCourse]);
|
2021-12-30 22:30:30 +02:00
|
|
|
|
2023-10-30 06:04:53 +00:00
|
|
|
capturePosthogEvent('course_created', {
|
2024-06-21 10:39:50 +01:00
|
|
|
course_id: newCourse.id,
|
|
|
|
|
course_title: newCourse.title,
|
|
|
|
|
course_description: newCourse.description,
|
2023-10-30 06:04:53 +00:00
|
|
|
organization_id: $currentOrg.id,
|
|
|
|
|
organization_name: $currentOrg.name,
|
|
|
|
|
user_id: $profile.id,
|
|
|
|
|
user_email: $profile.email
|
|
|
|
|
});
|
2021-12-30 22:30:30 +02:00
|
|
|
|
2023-10-30 06:04:53 +00:00
|
|
|
// 3. Add group members
|
2024-02-28 03:00:31 +01:00
|
|
|
const { data } = await addGroupMember({
|
2021-12-30 22:30:30 +02:00
|
|
|
profile_id: $profile.id,
|
|
|
|
|
email: $profile.email,
|
|
|
|
|
group_id,
|
2023-08-07 06:18:30 +00:00
|
|
|
role_id: ROLE.TUTOR
|
2021-12-30 22:30:30 +02:00
|
|
|
});
|
2024-02-28 03:00:31 +01:00
|
|
|
|
|
|
|
|
// 4. Add default news feed.
|
|
|
|
|
if (Array.isArray(data) && data.length) {
|
|
|
|
|
const { id: authorId } = data[0];
|
|
|
|
|
console.log('Add news feed into course');
|
|
|
|
|
|
|
|
|
|
await addDefaultNewsFeed({
|
|
|
|
|
content: `<h2>Welcome to this course 🎉 </h2>
|
|
|
|
|
<p>Thank you for joining this course and I hope you get the best out of it.</p>`,
|
2024-06-21 10:39:50 +01:00
|
|
|
course_id: newCourse.id,
|
2024-02-28 03:00:31 +01:00
|
|
|
is_pinned: true,
|
|
|
|
|
author_id: authorId
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-21 10:39:50 +01:00
|
|
|
onClose(`/courses/${newCourse.id}`);
|
2023-10-03 12:29:06 +00:00
|
|
|
isLoading = false;
|
2021-12-30 22:30:30 +02:00
|
|
|
}
|
2024-05-21 00:14:47 +01:00
|
|
|
|
2025-12-22 02:36:48 +01:00
|
|
|
$inspect(type);
|
|
|
|
|
|
2025-11-01 15:49:29 +01:00
|
|
|
let open = $derived(new URLSearchParams(page.url.search).get('create') === 'true');
|
2021-12-30 22:30:30 +02:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<svelte:head>
|
2023-08-17 14:05:01 +01:00
|
|
|
<title>Create a new course</title>
|
2021-12-30 22:30:30 +02:00
|
|
|
</svelte:head>
|
|
|
|
|
|
2025-12-22 02:36:48 +01:00
|
|
|
{#snippet course_type_selector()}
|
|
|
|
|
<Field.Description>{$t('courses.new_course_modal.type_selector_title')}</Field.Description>
|
|
|
|
|
|
|
|
|
|
<RadioGroup.Root bind:value={type} class="grid-cols-2">
|
|
|
|
|
{#each options as option (option.id)}
|
|
|
|
|
<Field.Label for={option.id}>
|
|
|
|
|
<Field.Field orientation="horizontal">
|
|
|
|
|
<Field.Content>
|
|
|
|
|
<Field.Title>{option.title}</Field.Title>
|
|
|
|
|
<Field.Description>{option.subtitle}</Field.Description>
|
|
|
|
|
</Field.Content>
|
|
|
|
|
|
|
|
|
|
<RadioGroup.Item value={option.type} id={option.id} aria-label={option.title} />
|
|
|
|
|
</Field.Field>
|
|
|
|
|
</Field.Label>
|
|
|
|
|
{/each}
|
|
|
|
|
</RadioGroup.Root>
|
|
|
|
|
{/snippet}
|
|
|
|
|
|
|
|
|
|
<Dialog.Root
|
2024-05-21 00:14:47 +01:00
|
|
|
bind:open
|
2025-12-22 02:36:48 +01:00
|
|
|
onOpenChange={(isOpen) => {
|
|
|
|
|
if (!isOpen) onClose(page.url.pathname);
|
|
|
|
|
}}
|
2021-12-30 22:30:30 +02:00
|
|
|
>
|
2025-12-22 02:36:48 +01:00
|
|
|
<Dialog.Content class="mx-auto w-4/5 max-w-2xl md:w-2/5 md:min-w-[600px]">
|
|
|
|
|
<Dialog.Header>
|
|
|
|
|
<Dialog.Title>{$t('courses.new_course_modal.heading')}</Dialog.Title>
|
|
|
|
|
</Dialog.Header>
|
|
|
|
|
{#if step === 0}
|
|
|
|
|
<div class="my-4 space-y-4">
|
|
|
|
|
{@render course_type_selector()}
|
2023-09-24 07:08:37 +00:00
|
|
|
|
2025-12-22 02:36:48 +01:00
|
|
|
<Dialog.Footer>
|
|
|
|
|
<Button onclick={() => (step = 1)} disabled={!type}>
|
|
|
|
|
{$t('courses.new_course_modal.next')}
|
|
|
|
|
</Button>
|
|
|
|
|
</Dialog.Footer>
|
2024-05-21 00:14:47 +01:00
|
|
|
</div>
|
2025-12-22 02:36:48 +01:00
|
|
|
{:else}
|
|
|
|
|
<form onsubmit={preventDefault(createCourse)}>
|
|
|
|
|
<div class="mb-4 flex items-end space-x-2">
|
|
|
|
|
<InputField
|
|
|
|
|
label={$t('courses.new_course_modal.course_name')}
|
|
|
|
|
bind:value={$createCourseModal.title}
|
|
|
|
|
placeholder={$t('courses.new_course_modal.course_name_placeholder')}
|
|
|
|
|
className="w-full"
|
|
|
|
|
isRequired={true}
|
|
|
|
|
errorMessage={errors.title}
|
|
|
|
|
autoComplete={false}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2024-05-21 00:14:47 +01:00
|
|
|
|
2025-12-22 02:36:48 +01:00
|
|
|
<TextareaField
|
|
|
|
|
label={$t('courses.new_course_modal.short_description')}
|
|
|
|
|
bind:value={$createCourseModal.description}
|
|
|
|
|
rows={4}
|
|
|
|
|
placeholder={$t('courses.new_course_modal.short_description_placeholder')}
|
|
|
|
|
className="mb-4"
|
2024-05-21 00:14:47 +01:00
|
|
|
isRequired={true}
|
2025-12-22 02:36:48 +01:00
|
|
|
errorMessage={errors.description}
|
|
|
|
|
isAIEnabled={true}
|
|
|
|
|
initAIPrompt="Write a 30 word description for a course titled: {$createCourseModal.title}"
|
2024-05-21 00:14:47 +01:00
|
|
|
/>
|
|
|
|
|
|
2025-12-22 02:36:48 +01:00
|
|
|
<Dialog.Footer>
|
|
|
|
|
<Button variant="outline" onclick={() => (step = 0)}>
|
|
|
|
|
{$t('courses.new_course_modal.back')}
|
|
|
|
|
</Button>
|
|
|
|
|
<Button type="submit" disabled={isLoading} loading={isLoading}>
|
|
|
|
|
{$t('courses.new_course_modal.button')}
|
|
|
|
|
</Button>
|
|
|
|
|
</Dialog.Footer>
|
|
|
|
|
</form>
|
|
|
|
|
{/if}
|
|
|
|
|
</Dialog.Content>
|
|
|
|
|
</Dialog.Root>
|