feat: move away from carbon components and TinyMCE (#597)
* feat: remove tinymce & replace org sidebar * chore: PR Fixes * chore: move ui font into src dir * remove files in vite config * use google font import for loading font * remove custom font * more fonts update * remove font bold * more removals of font bold * fix import * checkpoint * chore: checkpoint (add course & lms shadcn sidebar) * minor css improvements and try out font sans * chore: replace carbon components with shadcn's * chore: replace carbon components with shadcn _ v3 * chore: ui fixes & storybook update * feat: UI cleanup * fix: quick ui fixes * chore: update pnpm-lock.yaml with new dependencies and versions; fix output.css formatting * chore: add isloading to button * feat: add loading state to button stories * chore: feedback fixes * fix: feedback fixes * chore: remove console * fix: PR comments * fix: PR comments --------- Co-authored-by: Rotimi Best <best@classroomio.com>
This commit is contained in:
co-authored by
Rotimi Best
parent
2145782385
commit
cbf6ae7e63
@@ -1,12 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import AddOrgModal from '$lib/components/Org/AddOrgModal/AddOrgModal.svelte';
|
||||
import { isQuizPage } from '$lib/utils/functions/app';
|
||||
import OrgSideBar from '$lib/components/Org/SideBar.svelte';
|
||||
import VerifyEmailModal from '$lib/components/Org/VerifyEmail/VerifyEmailModal.svelte';
|
||||
import Box from '$lib/components/Box/index.svelte';
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Skeleton } from '@cio/ui/base/skeleton';
|
||||
import UsersIcon from '@lucide/svelte/icons/users';
|
||||
import Settings2Icon from '@lucide/svelte/icons/settings-2';
|
||||
import LibraryBigIcon from '@lucide/svelte/icons/library-big';
|
||||
import LayoutDashboardIcon from '@lucide/svelte/icons/layout-dashboard';
|
||||
import MessageSquareMoreIcon from '@lucide/svelte/icons/message-square-more';
|
||||
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
import { isQuizPage } from '$lib/utils/functions/app';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { orgs, isOrgAdmin } from '$lib/utils/store/org';
|
||||
|
||||
import Box from '$lib/components/Box/index.svelte';
|
||||
import OrgSideBar from '$lib/components/Sidebar/index.svelte';
|
||||
import AddOrgModal from '$lib/components/Org/AddOrgModal/AddOrgModal.svelte';
|
||||
import VerifyEmailModal from '$lib/components/Org/VerifyEmail/VerifyEmailModal.svelte';
|
||||
|
||||
let { data, children } = $props();
|
||||
|
||||
@@ -15,6 +26,64 @@
|
||||
goto(newUrl + page.url.search);
|
||||
}
|
||||
|
||||
function isActive(pagePath: string, itemPath: string) {
|
||||
const pageLinkItems = pagePath.split('/');
|
||||
const itemLinkItems = itemPath.split('/');
|
||||
|
||||
if (itemLinkItems.length !== pageLinkItems.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return pagePath.includes(itemPath);
|
||||
}
|
||||
|
||||
const sidebarData = $derived({
|
||||
teams: $orgs,
|
||||
navMain: [
|
||||
{
|
||||
title: $t('org_navigation.dashboard'),
|
||||
url: '/',
|
||||
icon: LayoutDashboardIcon,
|
||||
isActive: isActive(page.url.pathname, `/org/${data.orgName}`)
|
||||
},
|
||||
{
|
||||
title: $t('org_navigation.courses'),
|
||||
url: `/courses`,
|
||||
icon: LibraryBigIcon,
|
||||
isActive: isActive(page.url.pathname, `/org/${data.orgName}/courses`)
|
||||
},
|
||||
{
|
||||
title: $t('org_navigation.community'),
|
||||
url: `/community`,
|
||||
icon: MessageSquareMoreIcon,
|
||||
isActive: isActive(page.url.pathname, `/org/${data.orgName}/community`)
|
||||
},
|
||||
{
|
||||
title: $t('org_navigation.audience'),
|
||||
url: `/audience`,
|
||||
icon: UsersIcon,
|
||||
isActive: isActive(page.url.pathname, `/org/${data.orgName}/audience`)
|
||||
},
|
||||
...(!!$isOrgAdmin
|
||||
? [
|
||||
{
|
||||
title: $t('org_navigation.setup'),
|
||||
url: `/setup`,
|
||||
icon: Settings2Icon,
|
||||
isActive: isActive(page.url.pathname, `/org/${data.orgName}/setup`)
|
||||
}
|
||||
]
|
||||
: [])
|
||||
],
|
||||
user: {
|
||||
name: $profile.fullname,
|
||||
email: $profile.email,
|
||||
avatar: $profile.avatar_url
|
||||
}
|
||||
});
|
||||
|
||||
const isSidebarReady = $derived($orgs.length > 0 && $profile.fullname && $profile.email);
|
||||
|
||||
$effect(() => {
|
||||
data.orgName === '*' && redirect($currentOrg.siteName);
|
||||
});
|
||||
@@ -25,14 +94,75 @@
|
||||
<VerifyEmailModal />
|
||||
|
||||
<div class="org-root flex w-full items-center justify-between">
|
||||
{#if !isQuizPage(page.url?.pathname)}
|
||||
<OrgSideBar />
|
||||
{/if}
|
||||
<div class="org-slot w-full bg-white dark:bg-black">
|
||||
{#if data.orgName === '*'}
|
||||
<Box>Taking you to your organization...</Box>
|
||||
<div class="org-slot flex w-full items-start bg-white dark:bg-black">
|
||||
{#if !isQuizPage(page.url?.pathname)}
|
||||
{#if isSidebarReady}
|
||||
<OrgSideBar data={sidebarData} />
|
||||
{:else}
|
||||
<div class="bg-sidebar flex h-[calc(100vh-48px)] w-64 flex-col gap-2 border-r p-2">
|
||||
<!-- org switcher -->
|
||||
<div class="flex items-center gap-2 rounded-md p-2">
|
||||
<Skeleton class="h-8 w-8 rounded-lg" />
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<Skeleton class="h-4 w-24" />
|
||||
<Skeleton class="h-3 w-16" />
|
||||
</div>
|
||||
<Skeleton class="h-4 w-4" />
|
||||
</div>
|
||||
|
||||
<!-- nav main -->
|
||||
<div class="flex flex-col gap-1 py-2">
|
||||
<Skeleton class="mb-2 h-3 w-16 px-2" />
|
||||
{#each Array(5) as _, i (i)}
|
||||
<div class="flex items-center gap-2 rounded-md p-2">
|
||||
<Skeleton class="h-4 w-4" />
|
||||
<Skeleton class="h-4 w-full" />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- spacer -->
|
||||
<div class="flex-1"></div>
|
||||
|
||||
<!-- upgrade banner -->
|
||||
<div class="mx-4 mb-2 flex flex-col items-center gap-3 rounded-md border p-4">
|
||||
<Skeleton class="h-16 w-16 rounded-md" />
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<Skeleton class="h-4 w-32" />
|
||||
<Skeleton class="h-3 w-40" />
|
||||
</div>
|
||||
<Skeleton class="h-9 w-24 rounded-md" />
|
||||
</div>
|
||||
|
||||
<!-- nav user -->
|
||||
<div class="flex items-center gap-2 rounded-md p-2">
|
||||
<Skeleton class="h-8 w-8 rounded-lg" />
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<Skeleton class="h-4 w-24" />
|
||||
<Skeleton class="h-3 w-20" />
|
||||
</div>
|
||||
<Skeleton class="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
{@render children?.()}
|
||||
<div class="flex-1">
|
||||
{#if data.orgName === '*'}
|
||||
<Box>Taking you to your organization...</Box>
|
||||
{:else}
|
||||
{@render children?.()}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !isQuizPage(page.url?.pathname)}
|
||||
<div class="flex-1">
|
||||
{#if data.orgName === '*'}
|
||||
<Box>Taking you to your organization...</Box>
|
||||
{:else}
|
||||
{@render children?.()}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { ActivityCard } from '$lib/components/Analytics';
|
||||
import Progress from '$lib/components/Progress/index.svelte';
|
||||
import LibraryBigIcon from '@lucide/svelte/icons/library-big';
|
||||
import BookIcon from '@lucide/svelte/icons/book';
|
||||
import { Skeleton } from '@cio/ui/base/skeleton';
|
||||
import UserIcon from '@lucide/svelte/icons/user';
|
||||
import UsersIcon from '@lucide/svelte/icons/users';
|
||||
import LibraryBigIcon from '@lucide/svelte/icons/library-big';
|
||||
import DollarSignIcon from '@lucide/svelte/icons/dollar-sign';
|
||||
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import PlusIcon from '@lucide/svelte/icons/plus';
|
||||
import { isOrgAdmin } from '$lib/utils/store/org';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { isMobile } from '$lib/utils/store/useMobile';
|
||||
import { calDateDiff } from '$lib/utils/functions/date';
|
||||
import { getGreeting } from '$lib/utils/functions/date';
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import { getAccessToken } from '$lib/utils/functions/supabase';
|
||||
import { currentOrg, currentOrgPath } from '$lib/utils/store/org';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import type { OrganisationAnalytics } from '$lib/utils/types/analytics';
|
||||
import PlusIcon from '@lucide/svelte/icons/plus';
|
||||
|
||||
import BookIcon from '@lucide/svelte/icons/book';
|
||||
import DollarSignIcon from '@lucide/svelte/icons/dollar-sign';
|
||||
import UsersIcon from '@lucide/svelte/icons/users';
|
||||
|
||||
import Avatar from '$lib/components/Avatar/index.svelte';
|
||||
import VisitOrgSiteButton from '$lib/components/Buttons/VisitOrgSite.svelte';
|
||||
import { ActivityCard } from '$lib/components/Analytics';
|
||||
import Progress from '$lib/components/Progress/index.svelte';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import Grid from '$lib/components/Org/Settings/Layout/Grid.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import WelcomeModal from '$lib/components/WelcomeModal/WelcomeModal.svelte';
|
||||
import { getGreeting } from '$lib/utils/functions/date';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { isOrgAdmin } from '$lib/utils/store/org';
|
||||
import { isMobile } from '$lib/utils/store/useMobile';
|
||||
import { Grid, Link, SkeletonPlaceholder } from 'carbon-components-svelte';
|
||||
import VisitOrgSiteButton from '$lib/components/Buttons/VisitOrgSite.svelte';
|
||||
|
||||
let dashAnalytics: OrganisationAnalytics | undefined = $state();
|
||||
let hasFetched = $state(false);
|
||||
@@ -98,7 +98,7 @@
|
||||
|
||||
<WelcomeModal />
|
||||
|
||||
<div class="w-full max-w-5xl px-5 py-10 md:mx-auto">
|
||||
<div class="w-full px-5 py-10 md:mx-auto">
|
||||
<div class="mb-5 flex items-center justify-between">
|
||||
<h1 class="mb-3 text-2xl dark:text-white">
|
||||
{$t(getGreeting())}
|
||||
@@ -123,11 +123,11 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-10 flex flex-wrap items-start">
|
||||
<Grid class="px-0" fullWidth>
|
||||
<Grid class="px-0">
|
||||
<div class="mt-8 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{#each cards as card}
|
||||
{#if !dashAnalytics}
|
||||
<SkeletonPlaceholder style="width: 100%; min-width: 300px; height: 10rem;" class="rounded-md" />
|
||||
<Skeleton style="width: 100%; min-width: 300px; height: 10rem;" class="rounded-md" />
|
||||
{:else}
|
||||
<ActivityCard activity={card} />
|
||||
{/if}
|
||||
@@ -138,25 +138,25 @@
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<div class="flex min-h-[45vh] w-full flex-col rounded-md border p-3 md:p-5 dark:border-neutral-600 dark:text-white">
|
||||
<h3 class="mt-0 text-2xl font-bold">
|
||||
<h3 class="mt-0 text-2xl">
|
||||
{$t('dashboard.top_courses')}
|
||||
</h3>
|
||||
|
||||
<div class="h-full space-y-6">
|
||||
{#if !dashAnalytics}
|
||||
{#each Array(5) as _}
|
||||
<SkeletonPlaceholder style="width: 100%; height: 40px;" class="rounded-md" />
|
||||
<Skeleton class="h-10 w-full rounded-md" />
|
||||
{/each}
|
||||
{:else}
|
||||
{#each dashAnalytics.topCourses as course}
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-4/6 space-y-1">
|
||||
<Link href={`/courses/${course.id}`}>
|
||||
<a href={`/courses/${course.id}`}>
|
||||
<p class="line-clamp-2 pb-[0.1rem] text-sm font-medium leading-none">
|
||||
{course.title}
|
||||
</p>
|
||||
</Link>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
</a>
|
||||
<p class="text-sm">
|
||||
{course.enrollments}
|
||||
{$t(course.enrollments === 1 ? 'dashboard.student' : 'dashboard.students')}
|
||||
</p>
|
||||
@@ -190,14 +190,14 @@
|
||||
</div>
|
||||
|
||||
<div class="flex min-h-[45vh] w-full flex-col rounded-md border p-3 md:p-5 dark:border-neutral-600 dark:text-white">
|
||||
<h3 class="mt-0 text-2xl font-bold">
|
||||
<h3 class="mt-0 text-2xl">
|
||||
{$t('dashboard.recent_enrollments')}
|
||||
</h3>
|
||||
|
||||
<div class="h-full space-y-6">
|
||||
{#if !dashAnalytics}
|
||||
{#each Array(5) as _}
|
||||
<SkeletonPlaceholder style="width: 100%; height: 40px;" class="rounded-md" />
|
||||
<Skeleton class="h-10 w-full rounded-md" />
|
||||
{/each}
|
||||
{:else}
|
||||
{#each dashAnalytics.enrollments as enrollment}
|
||||
@@ -216,11 +216,11 @@
|
||||
</div>
|
||||
|
||||
<div class="w-2/4">
|
||||
<Link href={`/courses/${enrollment.courseId}`}>
|
||||
<a href={`/courses/${enrollment.courseId}`}>
|
||||
<p class="line-clamp-2 pb-[0.1rem] text-sm font-medium leading-none">
|
||||
{enrollment.course}
|
||||
</p>
|
||||
</Link>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
<title>Audience - ClassroomIO</title>
|
||||
</svelte:head>
|
||||
|
||||
<section class="mx-auto w-full max-w-4xl">
|
||||
<section class="mx-auto w-full max-w-6xl">
|
||||
<div class="px-5 py-10">
|
||||
<div class="mb-10 flex items-center justify-between">
|
||||
<div class="flex items-end">
|
||||
<h1 class="m-0 text-2xl font-bold md:text-3xl dark:text-white">{$t('audience.title')}</h1>
|
||||
<h1 class="m-0 text-2xl md:text-3xl dark:text-white">{$t('audience.title')}</h1>
|
||||
{#if $currentOrgPlan?.plan_name !== PLAN.ENTERPRISE}
|
||||
<span class="ml-2">
|
||||
({$orgAudience.length} / {$currentOrgMaxAudience})
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { ActivityCard, HeroProfileCard, LoadingPage } from '$lib/components/Analytics';
|
||||
import Progress from '$lib/components/Progress/index.svelte';
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import { getAccessToken } from '$lib/utils/functions/supabase';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { currentOrgPath } from '$lib/utils/store/org';
|
||||
import type { UserAnalytics } from '$lib/utils/types/analytics';
|
||||
import { Grid, Tag } from 'carbon-components-svelte';
|
||||
import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
|
||||
import BookOpenIcon from '@lucide/svelte/icons/book-open';
|
||||
import ChartLineIcon from '@lucide/svelte/icons/chart-line';
|
||||
import UnfoldVerticalIcon from '@lucide/svelte/icons/unfold-vertical';
|
||||
import { onMount } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { Badge } from '@cio/ui/base/badge';
|
||||
import UnfoldVerticalIcon from '@lucide/svelte/icons/unfold-vertical';
|
||||
|
||||
import BookOpenIcon from '@lucide/svelte/icons/book-open';
|
||||
import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
|
||||
import ChartLineIcon from '@lucide/svelte/icons/chart-line';
|
||||
import Progress from '$lib/components/Progress/index.svelte';
|
||||
import Grid from '$lib/components/Org/Settings/Layout/Grid.svelte';
|
||||
import { ActivityCard, HeroProfileCard, LoadingPage } from '$lib/components/Analytics';
|
||||
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { currentOrgPath } from '$lib/utils/store/org';
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import { getAccessToken } from '$lib/utils/functions/supabase';
|
||||
import type { UserAnalytics } from '$lib/utils/types/analytics';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
@@ -101,7 +104,7 @@
|
||||
<div class="px-5 py-1">
|
||||
<HeroProfileCard user={userAnalytics.user} />
|
||||
|
||||
<Grid class="mt-5 px-0" fullWidth>
|
||||
<Grid class="mt-5 px-0">
|
||||
<div class="mt-8 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{#each userMetrics as activity}
|
||||
<ActivityCard {activity} />
|
||||
@@ -110,7 +113,7 @@
|
||||
</Grid>
|
||||
|
||||
<div class="mt-5 rounded-md border p-3 md:p-5 dark:border-neutral-600">
|
||||
<h3 class="text-2xl font-bold">
|
||||
<h3 class="text-2xl">
|
||||
{$t('analytics.courses')}
|
||||
</h3>
|
||||
|
||||
@@ -128,26 +131,22 @@
|
||||
</div>
|
||||
<Progress value={userAnalytics.overallCourseProgress} />
|
||||
<div class="flex items-center justify-between">
|
||||
<Tag
|
||||
interactive
|
||||
filter={courseFilter === 'incomplete'}
|
||||
type={courseFilter === 'incomplete' ? 'gray' : 'outline'}
|
||||
<Badge
|
||||
type={courseFilter === 'incomplete' ? 'secondary' : 'outline'}
|
||||
class="text-yellow-700 dark:text-yellow-500"
|
||||
on:click={() => toggleCourseFilter('incomplete')}
|
||||
onclick={() => toggleCourseFilter('incomplete')}
|
||||
>
|
||||
{incompleteCourses}
|
||||
{$t('analytics.incomplete')}
|
||||
</Tag>
|
||||
<Tag
|
||||
interactive
|
||||
filter={courseFilter === 'completed'}
|
||||
type={courseFilter === 'completed' ? 'gray' : 'outline'}
|
||||
</Badge>
|
||||
<Badge
|
||||
type={courseFilter === 'completed' ? 'secondary' : 'outline'}
|
||||
class="text-green-700 dark:text-green-500"
|
||||
on:click={() => toggleCourseFilter('completed')}
|
||||
onclick={() => toggleCourseFilter('completed')}
|
||||
>
|
||||
{completedCourses}
|
||||
{$t('analytics.complete')}
|
||||
</Tag>
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -181,7 +180,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tag
|
||||
<Badge
|
||||
class={`${
|
||||
course.lessons_count === course.lessons_completed
|
||||
? 'bg-green-200 text-green-700'
|
||||
@@ -191,7 +190,7 @@
|
||||
{course.lessons_count === course.lessons_completed
|
||||
? $t('analytics.completed')
|
||||
: $t('analytics.incomplete')}
|
||||
</Tag>
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full items-center gap-1">
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
<title>Community - ClassroomIO</title>
|
||||
</svelte:head>
|
||||
|
||||
<section class="w-full md:mx-auto md:max-w-4xl">
|
||||
<section class="w-full md:mx-auto md:max-w-6xl">
|
||||
<div class="w-full px-5 py-10">
|
||||
<div class="mb-10 flex w-full items-center justify-between">
|
||||
<h1 class="text-2xl font-bold md:text-3xl dark:text-white">{$t('community.title')}</h1>
|
||||
<h1 class="text-2xl md:text-3xl dark:text-white">{$t('community.title')}</h1>
|
||||
{#if $isMobile}
|
||||
<PrimaryButton onClick={askCommunity}>
|
||||
<PlusIcon size={16} />
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { untrack } from 'svelte';
|
||||
import Avatar from '$lib/components/Avatar/index.svelte';
|
||||
import pluralize from 'pluralize';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Skeleton } from '@cio/ui/base/skeleton';
|
||||
import * as Select from '@cio/ui/base/select';
|
||||
import TrashIcon from '@lucide/svelte/icons/trash';
|
||||
import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
|
||||
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { courses } from '$lib/components/Courses/store';
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import { IconButton } from '$lib/components/IconButton';
|
||||
import DeleteModal from '$lib/components/Org/Community/DeleteModal.svelte';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import TextEditor from '$lib/components/TextEditor/index.svelte';
|
||||
import Vote from '$lib/components/Vote/index.svelte';
|
||||
import { calDateDiff } from '$lib/utils/functions/date';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { askCommunityValidation, commentInCommunityValidation } from '$lib/utils/functions/validator';
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import { fetchCourses } from '$lib/utils/services/courses';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import { currentOrg, currentOrgPath, isOrgAdmin } from '$lib/utils/store/org';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import type { Course } from '$lib/utils/types';
|
||||
import { Dropdown, SkeletonPlaceholder, SkeletonText } from 'carbon-components-svelte';
|
||||
import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
|
||||
import CircleCheckIcon from '$lib/components/Icons/CircleCheckIcon.svelte';
|
||||
import TrashIcon from '@lucide/svelte/icons/trash';
|
||||
import pluralize from 'pluralize';
|
||||
import { askCommunityValidation, commentInCommunityValidation } from '$lib/utils/functions/validator';
|
||||
|
||||
let { data } = $props();
|
||||
const { slug } = data;
|
||||
import type { Course } from '$lib/utils/types';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import Vote from '$lib/components/Vote/index.svelte';
|
||||
import { IconButton } from '$lib/components/IconButton';
|
||||
import Avatar from '$lib/components/Avatar/index.svelte';
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import TextEditor from '$lib/components/TextEditor/index.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import DeleteModal from '$lib/components/Org/Community/DeleteModal.svelte';
|
||||
import CircleCheckIcon from '$lib/components/Icons/CircleCheckIcon.svelte';
|
||||
|
||||
interface Comment {
|
||||
id: string;
|
||||
@@ -53,6 +53,9 @@
|
||||
courseId: string;
|
||||
}
|
||||
|
||||
let { data } = $props();
|
||||
const { slug } = data;
|
||||
|
||||
let question: Question | undefined = $state();
|
||||
let comment = $state('');
|
||||
let errors: {
|
||||
@@ -397,9 +400,9 @@
|
||||
<section class="mx-auto max-w-3xl md:mx-10 lg:mb-20">
|
||||
{#if !question}
|
||||
<div class="mb-3 px-5 py-10">
|
||||
<SkeletonText style="width: 25%;" />
|
||||
<SkeletonText style="width: 100%; margin-bottom: 2rem" />
|
||||
<SkeletonPlaceholder style="width: 100%; height: 20rem;" />
|
||||
<Skeleton class="h-4 w-[25%]" />
|
||||
<Skeleton class="mb-2 h-4 w-full" />
|
||||
<Skeleton class="h-80 w-full" />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="px-5 py-10">
|
||||
@@ -410,13 +413,20 @@
|
||||
<div class="my-5 flex items-center justify-between">
|
||||
{#if isEditMode}
|
||||
<TextField bind:value={editContent.title} className="w-full mr-2" errorMessage={errors.title} />
|
||||
<Dropdown
|
||||
class="h-full w-[25%]"
|
||||
size="xl"
|
||||
label={$t('community.ask.select_course')}
|
||||
items={fetchedCourses.map((course) => ({ id: course.id, text: course.title }))}
|
||||
bind:selectedId={editContent.courseId}
|
||||
/>
|
||||
<Select.Root type="single" bind:value={editContent.courseId}>
|
||||
<Select.Trigger class="h-full w-[25%]">
|
||||
<p>
|
||||
{editContent.courseId
|
||||
? fetchedCourses.find((course) => course.id === editContent.courseId)?.title
|
||||
: $t('community.ask.select_course')}
|
||||
</p>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each fetchedCourses as course}
|
||||
<Select.Item value={course.id || ''}>{course.title}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
{:else}
|
||||
<div class="flex items-center">
|
||||
<Vote value={question.votes} upVote={() => upvoteQuestion('question')} disabled={voted.question} />
|
||||
@@ -442,7 +452,7 @@
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="border-1 border-gray my-1 rounded-lg border px-1">
|
||||
<div class="border-gray my-1 rounded-lg border px-1">
|
||||
<header class="flex items-center justify-between p-2 leading-none">
|
||||
<div class="flex items-center text-black no-underline hover:underline">
|
||||
<Avatar src={question.author.avatar} name={question.author.name} width="w-7" height="h-7" />
|
||||
@@ -468,19 +478,19 @@
|
||||
{#if isEditMode && editorInstance}
|
||||
<div class="my-2">
|
||||
<TextEditor
|
||||
bind:value={editContent.body}
|
||||
placeholder={$t('community.ask.give')}
|
||||
onChange={(html) => (editContent.body = html)}
|
||||
placeholder="Give an answer"
|
||||
content={editContent.body}
|
||||
onChange={(content) => (editContent.body = content)}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<section class="prose prose-sm sm:prose p-2">
|
||||
<section class="prose prose-sm p-2 sm:prose">
|
||||
{@html question.body}
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="my-8 font-bold">
|
||||
<div class="my-8">
|
||||
{pluralize($t('community.answers'), question.totalComments, true)}
|
||||
</div>
|
||||
|
||||
@@ -491,7 +501,7 @@
|
||||
upVote={() => upvoteQuestion('comment', comment.id)}
|
||||
disabled={voted.comment[comment.id]}
|
||||
/>
|
||||
<div class="border-1 border-gray w-full rounded-lg border">
|
||||
<div class="border-gray w-full rounded-lg border">
|
||||
<header class="flex items-center justify-between p-2 leading-none">
|
||||
<div class="flex items-center text-black">
|
||||
<Avatar src={comment.avatar} name={comment.name} width="w-7" height="h-7" />
|
||||
@@ -517,7 +527,7 @@
|
||||
</IconButton>
|
||||
{/if}
|
||||
</header>
|
||||
<article class="prose prose-sm sm:prose p-2">
|
||||
<article class="prose prose-sm p-2 sm:prose">
|
||||
{@html comment.comment}
|
||||
</article>
|
||||
</div>
|
||||
@@ -528,11 +538,7 @@
|
||||
|
||||
<div class="mt-4">
|
||||
{#if !editorInstance}
|
||||
<TextEditor
|
||||
bind:value={comment}
|
||||
placeholder={$t('community.ask.give')}
|
||||
onChange={(html) => (comment = html)}
|
||||
/>
|
||||
<TextEditor placeholder="Give an answer" content={comment} onChange={(content) => (comment = content)} />
|
||||
{/if}
|
||||
|
||||
<div class="mt-2 flex justify-end">
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { courses } from '$lib/components/Courses/store';
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import TextEditor from '$lib/components/TextEditor/index.svelte';
|
||||
import generateSlug from '$lib/utils/functions/generateSlug';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { askCommunityValidation } from '$lib/utils/functions/validator';
|
||||
import { fetchCourses } from '$lib/utils/services/courses';
|
||||
import { currentOrg, currentOrgPath } from '$lib/utils/store/org';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import type { Course } from '$lib/utils/types';
|
||||
import { Dropdown } from 'carbon-components-svelte';
|
||||
import * as Select from '@cio/ui/base/select';
|
||||
import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
|
||||
|
||||
import type { Course } from '$lib/utils/types';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { courses } from '$lib/components/Courses/store';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import { fetchCourses } from '$lib/utils/services/courses';
|
||||
import generateSlug from '$lib/utils/functions/generateSlug';
|
||||
import { currentOrg, currentOrgPath } from '$lib/utils/store/org';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import { askCommunityValidation } from '$lib/utils/functions/validator';
|
||||
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import TextEditor from '$lib/components/TextEditor/index.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
|
||||
let errors: {
|
||||
title?: string;
|
||||
courseId?: string;
|
||||
@@ -98,7 +100,7 @@
|
||||
{$t('community.ask.go_back')}
|
||||
</a>
|
||||
<div class="flex items-center justify-between gap-12">
|
||||
<h1 class="text-2xl font-bold md:text-3xl dark:text-white">{$t('community.ask.ask_the')}</h1>
|
||||
<h1 class="text-2xl md:text-3xl dark:text-white">{$t('community.ask.ask_the')}</h1>
|
||||
<PrimaryButton label={$t('community.ask.publish')} variant={VARIANTS.CONTAINED_DARK} onClick={handleSave} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -110,22 +112,30 @@
|
||||
errorMessage={errors.title}
|
||||
className="w-[75%]"
|
||||
/>
|
||||
<Dropdown
|
||||
class="h-full w-[25%]"
|
||||
size="xl"
|
||||
label={$t('community.ask.select_course')}
|
||||
invalid={!!errors.courseId}
|
||||
invalidText={errors.courseId}
|
||||
items={fetchedCourses.map((course) => ({ id: course.id, text: course.title }))}
|
||||
bind:selectedId={fields.courseId}
|
||||
/>
|
||||
<div class="w-[25%]">
|
||||
<Select.Root type="single" bind:value={fields.courseId}>
|
||||
<Select.Trigger class="h-full w-full">
|
||||
<p>
|
||||
{fields.courseId
|
||||
? fetchedCourses.find((course) => course.id === fields.courseId)?.title
|
||||
: $t('community.ask.select_course')}
|
||||
</p>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each fetchedCourses as course}
|
||||
<Select.Item value={course.id}>{course.title}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
{#if errors.courseId}
|
||||
<p class="mt-1 text-sm text-red-500">
|
||||
{errors.courseId}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-2">
|
||||
<TextEditor
|
||||
bind:value={fields.body}
|
||||
placeholder={$t('community.ask.ask_community')}
|
||||
onChange={(html) => (fields.body = html)}
|
||||
/>
|
||||
<TextEditor placeholder="Give an answer" content={fields.body} onChange={(content) => (fields.body = content)} />
|
||||
|
||||
{#if errors.body}
|
||||
<p class="mt-2 text-sm text-red-500">{errors.body}</p>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Search, Dropdown } from 'carbon-components-svelte';
|
||||
import { Input } from '@cio/ui/base/input';
|
||||
import * as Select from '@cio/ui/base/select';
|
||||
import SearchIcon from '@lucide/svelte/icons/search';
|
||||
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import { fetchCourses } from '$lib/utils/services/courses';
|
||||
import Courses from '$lib/components/Courses/index.svelte';
|
||||
@@ -27,6 +30,16 @@
|
||||
let selectedId: string = $state('0');
|
||||
let hasFetched = false;
|
||||
|
||||
const filterOptions = $derived([
|
||||
{ value: '0', label: $t('courses.course_filter.date_created') },
|
||||
{ value: '1', label: $t('courses.course_filter.published') },
|
||||
{ value: '2', label: $t('courses.course_filter.lessons') }
|
||||
]);
|
||||
|
||||
const selectedLabel = $derived(
|
||||
filterOptions.find((opt) => opt.value === selectedId)?.label || filterOptions[0].label
|
||||
);
|
||||
|
||||
const filteredCourses: Course[] = $derived(filterCourses(searchValue, selectedId, $courses));
|
||||
|
||||
async function getCourses(userId: string | undefined, orgId: string) {
|
||||
@@ -105,7 +118,7 @@
|
||||
<section class="w-full md:mx-auto md:max-w-6xl">
|
||||
<div class="px-2 py-2 md:px-5 md:py-10">
|
||||
<div class="mb-5 flex items-center justify-between">
|
||||
<h1 class="text-2xl font-bold md:text-3xl dark:text-white">{$t('courses.heading')}</h1>
|
||||
<h1 class="text-2xl md:text-3xl dark:text-white">{$t('courses.heading')}</h1>
|
||||
{#if $isMobile}
|
||||
<PrimaryButton isDisabled={!$isOrgAdmin} onClick={openNewCourseModal}>
|
||||
<PlusIcon size={16} />
|
||||
@@ -120,22 +133,22 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mb-5 flex flex-row-reverse">
|
||||
<div class="filter-containter flex items-end justify-start">
|
||||
<Search
|
||||
placeholder={$t('courses.search_placeholder')}
|
||||
bind:value={searchValue}
|
||||
searchClass="mr-2"
|
||||
class=" bg-gray-100 dark:bg-neutral-800"
|
||||
/>
|
||||
<Dropdown
|
||||
class="h-full min-w-[150px]"
|
||||
bind:selectedId
|
||||
items={[
|
||||
{ id: '0', text: $t('courses.course_filter.date_created') },
|
||||
{ id: '1', text: $t('courses.course_filter.published') },
|
||||
{ id: '2', text: $t('courses.course_filter.lessons') }
|
||||
]}
|
||||
/>
|
||||
<div class="filter-containter flex items-end justify-start gap-2">
|
||||
<div class="relative">
|
||||
<Input type="text" placeholder={$t('courses.search_placeholder')} bind:value={searchValue} class="pl-9" />
|
||||
|
||||
<SearchIcon class="absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2" />
|
||||
</div>
|
||||
<Select.Root type="single" bind:value={selectedId}>
|
||||
<Select.Trigger class="min-w-[150px]">
|
||||
<p>{selectedId ? selectedLabel : filterOptions[0].label}</p>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each filterOptions as option}
|
||||
<Select.Item value={option.value}>{option.label}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
{#if $courseMetaDeta.view === 'list'}
|
||||
<IconButton onClick={() => setViewPreference('grid')}>
|
||||
<GridIcon size={16} />
|
||||
@@ -152,10 +165,3 @@
|
||||
<Courses courses={filteredCourses} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.filter-containter :global(.bx--dropdown) {
|
||||
max-height: unset;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<section class="mx-auto w-full max-w-6xl">
|
||||
<div class="px-5 py-10">
|
||||
<div class="mb-10 flex items-center justify-between">
|
||||
<h1 class="text-2xl font-bold md:text-3xl dark:text-white">Interactive Quizzes</h1>
|
||||
<h1 class="text-2xl md:text-3xl dark:text-white">Interactive Quizzes</h1>
|
||||
{#if $isMobile}
|
||||
<PrimaryButton onClick={() => ($createQuizModal.open = true)}>
|
||||
<PlusIcon size={16} />
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
|
||||
import CircleCheckIcon from '$lib/components/Icons/CircleCheckIcon.svelte';
|
||||
import CircleAlertIcon from '@lucide/svelte/icons/circle-alert';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import isBoolean from 'lodash/isBoolean';
|
||||
import { onMount } from 'svelte';
|
||||
import { Label } from '@cio/ui/base/label';
|
||||
import * as Select from '@cio/ui/base/select';
|
||||
import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
|
||||
import CircleAlertIcon from '@lucide/svelte/icons/circle-alert';
|
||||
import CircleCheckIcon from '$lib/components/Icons/CircleCheckIcon.svelte';
|
||||
|
||||
import DeleteModal from '$lib/components/Org/Quiz/DeleteModal.svelte';
|
||||
import Preview from '$lib/components/Org/Quiz/Play/Preview.svelte';
|
||||
import QuizQuestion from '$lib/components/Org/Quiz/QuizQuestion.svelte';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import DeleteModal from '$lib/components/Org/Quiz/DeleteModal.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import { allOptions, allThemes, booleanOptions, themeImages } from '$lib/utils/constants/quiz';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
import QuizQuestion from '$lib/components/Org/Quiz/QuizQuestion.svelte';
|
||||
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import { currentOrgPath, deleteModal, quizStore, quizesStore } from '$lib/utils/store/org';
|
||||
import { Select, SelectItem } from 'carbon-components-svelte';
|
||||
import { allOptions, allThemes, booleanOptions, themeImages } from '$lib/utils/constants/quiz';
|
||||
|
||||
interface QuizOption {
|
||||
id: number;
|
||||
@@ -58,10 +60,9 @@
|
||||
isLabelEmpty?: boolean;
|
||||
hasOneAnswer?: boolean;
|
||||
id?: number;
|
||||
options?: Array<{ id: number; error: boolean }>;
|
||||
options: Array<{ id: number; error: boolean }>;
|
||||
} = $state({});
|
||||
let isFocused = $state(false);
|
||||
let selectEl: Select | null = $state(null);
|
||||
|
||||
function activeClass(q, cq) {
|
||||
if (q.id === cq.id) {
|
||||
@@ -248,7 +249,7 @@
|
||||
<div class="mb-3">
|
||||
{#each $quizStore.questions as question, i}
|
||||
<button
|
||||
class="mb-3 flex w-full justify-between rounded p-3 text-left font-bold text-gray-500 dark:text-white {activeClass(
|
||||
class="mb-3 flex w-full justify-between rounded p-3 text-left text-gray-500 dark:text-white {activeClass(
|
||||
question,
|
||||
currentQuestion
|
||||
)}"
|
||||
@@ -285,7 +286,7 @@
|
||||
?.editor}) no-repeat center center fixed; -webkit-background-size: cover;-moz-background-size: cover;-o-background-size: cover;background-size: cover;"
|
||||
>
|
||||
<div class="content m-auto">
|
||||
<h1 class="my-5 font-bold text-white">{$quizStore.title}</h1>
|
||||
<h1 class="my-5 text-white">{$quizStore.title}</h1>
|
||||
|
||||
<QuizQuestion {currentQuestion} {optionHasError} {currentError} />
|
||||
|
||||
@@ -339,39 +340,44 @@
|
||||
<div class="flex flex-col justify-evenly">
|
||||
<div class="my-3">
|
||||
<!-- Question type -->
|
||||
<Select
|
||||
labelText="Question type"
|
||||
bind:this={selectEl}
|
||||
bind:selected={type}
|
||||
class="mb-3 flex items-center"
|
||||
on:focus={() => (isFocused = true)}
|
||||
on:blur={() => (isFocused = false)}
|
||||
on:change={() => {
|
||||
if (!isFocused) return;
|
||||
// Blur after change
|
||||
const onBlur = selectEl?.$$?.callbacks?.blur?.[0];
|
||||
const onFocus = selectEl?.$$?.callbacks?.focus?.[0];
|
||||
setTimeout(() => {
|
||||
onBlur();
|
||||
onFocus();
|
||||
}, 1000);
|
||||
|
||||
handleQuestionTypeChange(type);
|
||||
}}
|
||||
>
|
||||
<SelectItem value="multichoice" text="Multi-choice answers" />
|
||||
<SelectItem value="boolean" text="True or False" />
|
||||
</Select>
|
||||
<div class="mb-3">
|
||||
<Label class="mb-2">Question type</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
bind:value={type}
|
||||
onValueChange={(value) => {
|
||||
if (value) {
|
||||
handleQuestionTypeChange(value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Select.Trigger class="w-full">
|
||||
<p>{type === 'multichoice' ? 'Multi-choice answers' : 'True or False'}</p>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="multichoice">Multi-choice answers</Select.Item>
|
||||
<Select.Item value="boolean">True or False</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- -->
|
||||
<Select labelText="Time limit" bind:selected={$quizStore.timelimit} class="mb-3 flex items-center">
|
||||
<SelectItem value="10 seconds" text="10s" />
|
||||
<SelectItem value="20 seconds" text="20s" />
|
||||
<SelectItem value="30 seconds" text="30s" />
|
||||
<SelectItem value="1 minute" text="1m" />
|
||||
<SelectItem value="2 minute" text="2m" />
|
||||
<SelectItem value="3 minute" text="3m" />
|
||||
</Select>
|
||||
<div class="mb-3">
|
||||
<Label class="mb-2">Time limit</Label>
|
||||
<Select.Root type="single" bind:value={$quizStore.timelimit}>
|
||||
<Select.Trigger class="w-full">
|
||||
<p>{$quizStore.timelimit || 'Select time limit'}</p>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="10 seconds">10s</Select.Item>
|
||||
<Select.Item value="20 seconds">20s</Select.Item>
|
||||
<Select.Item value="30 seconds">30s</Select.Item>
|
||||
<Select.Item value="1 minute">1m</Select.Item>
|
||||
<Select.Item value="2 minute">2m</Select.Item>
|
||||
<Select.Item value="3 minute">3m</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Theme settings -->
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<title>Settings - ClassroomIO</title>
|
||||
</svelte:head>
|
||||
|
||||
<section class="mx-auto w-full md:max-w-4xl">
|
||||
<div class="px-5 py-10">
|
||||
<section class="w-full md:max-w-6xl lg:mx-auto">
|
||||
<div class="px-5 md:py-10">
|
||||
<div class="mb-10 flex items-center justify-between">
|
||||
<h1 class="text-3xl font-bold dark:text-white">{$t('settings.heading')}</h1>
|
||||
<h1 class="text-3xl dark:text-white">{$t('settings.heading')}</h1>
|
||||
|
||||
<ViewSiteBtn />
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
>{$t('upgrade.back')}</a
|
||||
>
|
||||
<div class="mb-10 flex items-center justify-between">
|
||||
<h1 class="text-3xl font-bold dark:text-white">
|
||||
<h1 class="text-3xl dark:text-white">
|
||||
{$t('components.settings.customize_lms.title')}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
>{$t('upgrade.back')}</a
|
||||
>
|
||||
<div class="mb-10 flex items-center justify-between">
|
||||
<h1 class="text-3xl font-bold dark:text-white">{$t('components.settings.domains.title')}</h1>
|
||||
<h1 class="text-3xl dark:text-white">{$t('components.settings.domains.title')}</h1>
|
||||
</div>
|
||||
|
||||
<Domains />
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
>{$t('upgrade.back')}</a
|
||||
>
|
||||
<div class="mb-10 flex items-center justify-between">
|
||||
<h1 class="text-3xl font-bold dark:text-white">{$t('upgrade.team_members')}</h1>
|
||||
<h1 class="text-3xl dark:text-white">{$t('upgrade.team_members')}</h1>
|
||||
</div>
|
||||
|
||||
<UpgradeBanner>{$t('upgrade.team')}</UpgradeBanner>
|
||||
|
||||
@@ -80,10 +80,10 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<section class="mx-auto w-full md:max-w-4xl">
|
||||
<section class="mx-auto w-full md:max-w-3xl">
|
||||
<div class="px-2 py-2 md:px-5 md:py-10">
|
||||
<div class="flex items-center gap-2">
|
||||
<h1 class="text-2xl font-bold md:text-3xl dark:text-white">{$t('setup.get_started')}</h1>
|
||||
<h1 class="text-2xl md:text-3xl dark:text-white">{$t('setup.get_started')}</h1>
|
||||
<Chip
|
||||
value={`${completed}/${setupList.length}`}
|
||||
className="text-[10px] font-semibold px-3 !py-1"
|
||||
|
||||
Reference in New Issue
Block a user