feat: live class sessions, CE workflow, and docs (#683)
Adds live-class session scheduling on lessons (join link, date/time, timezone) with automatic 24h and 1h reminder emails plus a calendar invite (.ics), a time-change notification with an updated invite, a teacher-authored welcome email and session invite on enrollment, and a "Notify students" quiz nudge. LMS gets an upcoming live-sessions card and Join buttons, plus a reusable live-session card component (3 states) in @cio/ui with a Storybook story. The sidebar content tree now reflects sequential progression locking like the content list, and the course-completion confetti regression is fixed. Org-based emails no longer reference ClassroomIO by name. Docs add live-class, welcome-email, and course-progression guides, expand the certificates guide (design, signatures, corrected labels), move plan-required callouts to the top, flatten how-to-guides to the docs root with redirects, and add screenshots. Includes an add-docs-image skill.
This commit is contained in:
@@ -1,12 +1,46 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
interface Props {
|
||||
children?: import('svelte').Snippet;
|
||||
}
|
||||
|
||||
let { children }: Props = $props();
|
||||
import * as Page from '@cio/ui/base/page';
|
||||
import { OrgSettingsInlineTabs } from '$features/settings/components';
|
||||
import { settingsHeaderAction } from '$features/settings/utils/store';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
let { children }: Props = $props();
|
||||
|
||||
const showOrgSettingsTabs = $derived(
|
||||
page.url.pathname.endsWith('/settings/org') ||
|
||||
page.url.pathname.endsWith('/settings/customize-lms') ||
|
||||
page.url.pathname.endsWith('/settings/domains') ||
|
||||
page.url.pathname.endsWith('/settings/teams')
|
||||
);
|
||||
</script>
|
||||
|
||||
<Page.Root class="w-full pb-10 md:max-w-3xl lg:mx-auto">
|
||||
{#if showOrgSettingsTabs}
|
||||
<Page.Header>
|
||||
<Page.HeaderContent>
|
||||
<Page.Title>{$t('settings.organization.organization_profile.heading')}</Page.Title>
|
||||
<Page.Subtitle>{$t('settings.organization.page_subtitle')}</Page.Subtitle>
|
||||
</Page.HeaderContent>
|
||||
<Page.Action>
|
||||
<Button
|
||||
variant="secondary"
|
||||
loading={$settingsHeaderAction.loading}
|
||||
disabled={$settingsHeaderAction.disabled || !$settingsHeaderAction.onClick}
|
||||
onclick={() => $settingsHeaderAction.onClick?.()}
|
||||
>
|
||||
{$settingsHeaderAction.label}
|
||||
</Button>
|
||||
</Page.Action>
|
||||
</Page.Header>
|
||||
|
||||
<OrgSettingsInlineTabs />
|
||||
{/if}
|
||||
|
||||
{@render children?.()}
|
||||
</Page.Root>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy } from 'svelte';
|
||||
import { CustomizeLmsPage } from '$features/settings/pages';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
import * as Page from '@cio/ui/base/page';
|
||||
import { settingsHeaderAction } from '$features/settings/utils/store';
|
||||
|
||||
let customizeLmsComponent: CustomizeLmsPage | null = $state(null);
|
||||
let isSaving = $state(false);
|
||||
@@ -15,27 +14,28 @@
|
||||
isSaving = false;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
settingsHeaderAction.set({
|
||||
label: 'Save',
|
||||
disabled: isSaving,
|
||||
loading: isSaving,
|
||||
onClick: handleSave
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
settingsHeaderAction.set({
|
||||
label: 'Save',
|
||||
disabled: true,
|
||||
loading: false,
|
||||
onClick: null
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Customize LMS - ClassroomIO</title>
|
||||
</svelte:head>
|
||||
|
||||
<Page.Root class="w-full md:max-w-4xl lg:mx-auto">
|
||||
<Page.Header>
|
||||
<Page.HeaderContent>
|
||||
<Page.Title>{$t('components.settings.customize_lms.title')}</Page.Title>
|
||||
<Page.Subtitle>{$t('components.settings.customize_lms.page_subtitle')}</Page.Subtitle>
|
||||
</Page.HeaderContent>
|
||||
<Page.Action>
|
||||
<Button variant="default" loading={isSaving} disabled={isSaving} onclick={handleSave}>
|
||||
{$t('components.settings.customize_lms.save')}
|
||||
</Button>
|
||||
</Page.Action>
|
||||
</Page.Header>
|
||||
<Page.Body>
|
||||
{#snippet child()}
|
||||
<CustomizeLmsPage bind:this={customizeLmsComponent} />
|
||||
{/snippet}
|
||||
</Page.Body>
|
||||
</Page.Root>
|
||||
<CustomizeLmsPage bind:this={customizeLmsComponent} />
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
<script>
|
||||
import { onDestroy } from 'svelte';
|
||||
import { DomainsPage } from '$features/settings/pages';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import * as Page from '@cio/ui/base/page';
|
||||
import { settingsHeaderAction } from '$features/settings/utils/store';
|
||||
|
||||
$effect(() => {
|
||||
settingsHeaderAction.set({
|
||||
label: 'Save',
|
||||
disabled: true,
|
||||
loading: false,
|
||||
onClick: null
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
settingsHeaderAction.set({
|
||||
label: 'Save',
|
||||
disabled: true,
|
||||
loading: false,
|
||||
onClick: null
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Custom domain - ClassroomIO</title>
|
||||
</svelte:head>
|
||||
|
||||
<Page.Header>
|
||||
<Page.HeaderContent>
|
||||
<Page.Title>{$t('components.settings.domains.title')}</Page.Title>
|
||||
<Page.Subtitle>{$t('components.settings.domains.page_subtitle')}</Page.Subtitle>
|
||||
</Page.HeaderContent>
|
||||
</Page.Header>
|
||||
<Page.Body>
|
||||
{#snippet child()}
|
||||
<DomainsPage />
|
||||
{/snippet}
|
||||
</Page.Body>
|
||||
<DomainsPage />
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy } from 'svelte';
|
||||
import { OrgPage } from '$features/settings/pages';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
import * as Page from '@cio/ui/base/page';
|
||||
import { settingsHeaderAction } from '$features/settings/utils/store';
|
||||
|
||||
let orgComponent: OrgPage | null = $state(null);
|
||||
let loading = $state(false);
|
||||
@@ -15,25 +14,28 @@
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
settingsHeaderAction.set({
|
||||
label: 'Save',
|
||||
disabled: loading,
|
||||
loading,
|
||||
onClick: handleUpdate
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
settingsHeaderAction.set({
|
||||
label: 'Save',
|
||||
disabled: true,
|
||||
loading: false,
|
||||
onClick: null
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Organization Settings - ClassroomIO</title>
|
||||
</svelte:head>
|
||||
|
||||
<Page.Header isSticky>
|
||||
<Page.HeaderContent>
|
||||
<Page.Title>{$t('settings.organization.organization_profile.heading')}</Page.Title>
|
||||
<Page.Subtitle>{$t('settings.organization.page_subtitle')}</Page.Subtitle>
|
||||
</Page.HeaderContent>
|
||||
<Page.Action>
|
||||
<Button variant="default" {loading} disabled={loading} onclick={handleUpdate}>
|
||||
{$t('settings.organization.organization_profile.update_organization')}
|
||||
</Button>
|
||||
</Page.Action>
|
||||
</Page.Header>
|
||||
<Page.Body>
|
||||
{#snippet child()}
|
||||
<OrgPage bind:this={orgComponent} />
|
||||
{/snippet}
|
||||
</Page.Body>
|
||||
<OrgPage bind:this={orgComponent} />
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
<script>
|
||||
import { onDestroy } from 'svelte';
|
||||
import { TeamsPage } from '$features/settings/pages';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import * as Page from '@cio/ui/base/page';
|
||||
import { settingsHeaderAction } from '$features/settings/utils/store';
|
||||
|
||||
$effect(() => {
|
||||
settingsHeaderAction.set({
|
||||
label: 'Save',
|
||||
disabled: true,
|
||||
loading: false,
|
||||
onClick: null
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
settingsHeaderAction.set({
|
||||
label: 'Save',
|
||||
disabled: true,
|
||||
loading: false,
|
||||
onClick: null
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Teams - ClassroomIO</title>
|
||||
</svelte:head>
|
||||
|
||||
<Page.Header>
|
||||
<Page.HeaderContent>
|
||||
<Page.Title>{$t('upgrade.team_members')}</Page.Title>
|
||||
<Page.Subtitle>{$t('settings.teams.page_subtitle')}</Page.Subtitle>
|
||||
</Page.HeaderContent>
|
||||
</Page.Header>
|
||||
<Page.Body>
|
||||
{#snippet child()}
|
||||
<TeamsPage />
|
||||
{/snippet}
|
||||
</Page.Body>
|
||||
<TeamsPage />
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
const pageTitle = $derived(
|
||||
data.isOrgSite && data.org
|
||||
? data.org.name
|
||||
: 'ClassroomIO - The Most Customizable Platform for Customer, Partner, and Employee Education'
|
||||
: 'ClassroomIO - One Platform for Customer, Partner, and Employee Training'
|
||||
);
|
||||
|
||||
const authAction = $derived(
|
||||
|
||||
Reference in New Issue
Block a user