feat: redo settings and all org pages. (#601)
* feat: new components, deploy storybook * refactor: simplify component field definitions and enhance button loading state - Removed unnecessary type imports and simplified the FIELDS array definitions across multiple components in Storybook. - Updated button component to use a loading state instead of isLoading, improving clarity and consistency in props. - Adjusted button stories to reflect the new loading prop and enhanced the button's loading visual feedback. - Minor CSS adjustments for better layout and styling consistency. * chore: add field, card, password components * feat: improve page component & update AuthUI path * chore: update bits-ui to version 2.14.4 and all org related UI's - Updated bits-ui dependency in pnpm-lock.yaml and package.json to version 2.14.4. - Refactored imports for RoleBasedSecurity and UpgradeBanner components to use the new ui feature directory. - Removed unused Course components and related files to clean up the codebase. - Enhanced various components to improve consistency and maintainability. * chore: move all setting buttons to the top and update auth ui styles * chore: remove svelte-loading-spinners and replace with @cio/ui/base/spinner - Removed all instances of svelte-loading-spinners from the project. - Updated components to use the new Spinner from @cio/ui/base/spinner. - Cleaned up pnpm-lock.yaml and package.json to reflect the removal of svelte-loading-spinners. - Added new CSS animations for rocket launch effects in app.css. - Enhanced email change flow with updated verification messages in translations. * chore: remove supabase file upload from dashboard and move to r2 * chore: update organization API and UI components - Added new API methods for organization plan operations and team management. - Introduced middleware for API key authentication and organization admin checks. - Refactored organization-related components to utilize the new API structure. - Updated various UI components to improve consistency and maintainability. - Enhanced error handling and validation for organization-related actions. - Removed deprecated services and streamlined the codebase.
This commit is contained in:
@@ -4,14 +4,10 @@
|
||||
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 { 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';
|
||||
@@ -19,22 +15,19 @@
|
||||
import { currentOrg, currentOrgPath } from '$lib/utils/store/org';
|
||||
import type { OrganisationAnalytics } from '$lib/utils/types/analytics';
|
||||
|
||||
import { CreateCourseButton } from '$lib/features/course/components';
|
||||
import Avatar from '$lib/components/Avatar/index.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';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
import { WelcomeModal } from '$lib/features/onboarding/components';
|
||||
import VisitOrgSiteButton from '$lib/components/Buttons/VisitOrgSite.svelte';
|
||||
import * as Page from '@cio/ui/base/page';
|
||||
import { Empty } from '@cio/ui/custom/empty';
|
||||
|
||||
let dashAnalytics: OrganisationAnalytics | undefined = $state();
|
||||
let hasFetched = $state(false);
|
||||
|
||||
function createCourse() {
|
||||
goto(`${$currentOrgPath}/courses?create=true`);
|
||||
}
|
||||
|
||||
async function fetchDashAnalytics(orgId: string) {
|
||||
if (!orgId || hasFetched) return;
|
||||
|
||||
@@ -98,153 +91,135 @@
|
||||
|
||||
<WelcomeModal />
|
||||
|
||||
<div class="w-full">
|
||||
<div class="mb-5 flex items-center justify-between">
|
||||
<h1 class="m-0 text-xl font-medium dark:text-white">
|
||||
{$t(getGreeting())}
|
||||
{$profile.fullname}!
|
||||
</h1>
|
||||
<div class="flex items-center">
|
||||
<PrimaryButton
|
||||
variant={VARIANTS.OUTLINED}
|
||||
onClick={createCourse}
|
||||
isDisabled={!$isOrgAdmin}
|
||||
className="min-h-[36px]"
|
||||
>
|
||||
{#if $isMobile}
|
||||
<PlusIcon size={16} />
|
||||
{:else}
|
||||
{$t('dashboard.create_course')}
|
||||
{/if}
|
||||
</PrimaryButton>
|
||||
<Page.Root class="w-full">
|
||||
<Page.Header>
|
||||
<Page.HeaderContent>
|
||||
<Page.Title>
|
||||
{$t(getGreeting())}
|
||||
{$profile.fullname}!
|
||||
</Page.Title>
|
||||
</Page.HeaderContent>
|
||||
<Page.Action>
|
||||
<CreateCourseButton variant="outline" isResponsive />
|
||||
|
||||
<VisitOrgSiteButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-10 flex flex-wrap items-start">
|
||||
<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}
|
||||
<Skeleton style="width: 100%; min-width: 300px; height: 10rem;" class="rounded-md" />
|
||||
{:else}
|
||||
<ActivityCard activity={card} />
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</Grid>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
{$t('dashboard.top_courses')}
|
||||
</h3>
|
||||
|
||||
<div class="h-full space-y-6">
|
||||
{#if !dashAnalytics}
|
||||
{#each Array(5) as _}
|
||||
<Skeleton class="h-10 w-full rounded-md" />
|
||||
</Page.Action>
|
||||
</Page.Header>
|
||||
<Page.Body>
|
||||
{#snippet child()}
|
||||
<div class="mb-10 flex flex-wrap items-start">
|
||||
<div class="grid w-full grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{#each cards as card}
|
||||
{#if !dashAnalytics}
|
||||
<Skeleton style="width: 100%; height: 10rem;" class="rounded-md" />
|
||||
{:else}
|
||||
<ActivityCard activity={card} />
|
||||
{/if}
|
||||
{/each}
|
||||
{:else}
|
||||
{#each dashAnalytics.topCourses as course}
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-4/6 space-y-1">
|
||||
<a href={`/courses/${course.id}`}>
|
||||
<p class="line-clamp-2 pb-[0.1rem] text-sm font-medium leading-none">
|
||||
{course.title}
|
||||
</p>
|
||||
</a>
|
||||
<p class="text-sm">
|
||||
{course.enrollments}
|
||||
{$t(course.enrollments === 1 ? 'dashboard.student' : 'dashboard.students')}
|
||||
</p>
|
||||
</div>
|
||||
<div class="ml-auto w-2/6">
|
||||
<Progress value={course.completion} />
|
||||
<div class="text-sm font-medium">
|
||||
{course.completion}%
|
||||
{$t('dashboard.completion')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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-lg">
|
||||
{$t('dashboard.top_courses')}
|
||||
</h3>
|
||||
|
||||
<div class="h-full space-y-6">
|
||||
{#if !dashAnalytics}
|
||||
{#each Array(5) as _}
|
||||
<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">
|
||||
<a href={`/courses/${course.id}`}>
|
||||
<p class="line-clamp-2 pb-[0.1rem] text-sm font-medium leading-none">
|
||||
{course.title}
|
||||
</p>
|
||||
</a>
|
||||
<p class="text-sm">
|
||||
{course.enrollments}
|
||||
{$t(course.enrollments === 1 ? 'dashboard.student' : 'dashboard.students')}
|
||||
</p>
|
||||
</div>
|
||||
<div class="ml-auto w-2/6">
|
||||
<Progress value={course.completion} />
|
||||
<div class="text-sm font-medium">
|
||||
{course.completion}%
|
||||
{$t('dashboard.completion')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col h-full items-center justify-center p-3">
|
||||
<div class="bg-primary-200 w-fit rounded-full p-4 text-black">
|
||||
<LibraryBigIcon size={30} />
|
||||
</div>
|
||||
<div class="my-4 text-center">
|
||||
<p class=" text-xl font-semibold">
|
||||
{$t('dashboard.create_first_course')}
|
||||
</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-300">
|
||||
{$t('dashboard.create_first_course_description')}
|
||||
</p>
|
||||
</div>
|
||||
<PrimaryButton variant={VARIANTS.OUTLINED} onClick={createCourse} label={$t('dashboard.create_course')} />
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<Empty
|
||||
title={$t('dashboard.create_first_course')}
|
||||
description={$t('dashboard.create_first_course_description')}
|
||||
icon={BookIcon}
|
||||
class="h-full"
|
||||
>
|
||||
<CreateCourseButton variant="outline" />
|
||||
</Empty>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</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">
|
||||
{$t('dashboard.recent_enrollments')}
|
||||
</h3>
|
||||
<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-lg">
|
||||
{$t('dashboard.recent_enrollments')}
|
||||
</h3>
|
||||
|
||||
<div class="h-full space-y-6">
|
||||
{#if !dashAnalytics}
|
||||
{#each Array(5) as _}
|
||||
<Skeleton class="h-10 w-full rounded-md" />
|
||||
{/each}
|
||||
{:else}
|
||||
{#each dashAnalytics.enrollments as enrollment}
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<Avatar src={enrollment.avatarUrl} name={enrollment.name} width="w-6" height="h-6" />
|
||||
<div class="h-full space-y-6">
|
||||
{#if !dashAnalytics}
|
||||
{#each Array(5) as _}
|
||||
<Skeleton class="h-10 w-full rounded-md" />
|
||||
{/each}
|
||||
{:else}
|
||||
{#each dashAnalytics.enrollments as enrollment}
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<Avatar src={enrollment.avatarUrl} name={enrollment.name} width="w-6" height="h-6" />
|
||||
|
||||
<div class="min-h-[45px] space-y-1">
|
||||
<p class="text-sm font-medium capitalize leading-none">{enrollment.name}</p>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
<span class="italic">
|
||||
{calDateDiff(enrollment.date)}
|
||||
</span>
|
||||
</p>
|
||||
<div class="min-h-[45px] space-y-1">
|
||||
<p class="text-sm font-medium capitalize leading-none">{enrollment.name}</p>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
<span class="italic">
|
||||
{calDateDiff(enrollment.date)}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-2/4">
|
||||
<a href={`/courses/${enrollment.courseId}`}>
|
||||
<p class="line-clamp-2 pb-[0.1rem] text-sm font-medium leading-none">
|
||||
{enrollment.course}
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-2/4">
|
||||
<a href={`/courses/${enrollment.courseId}`}>
|
||||
<p class="line-clamp-2 pb-[0.1rem] text-sm font-medium leading-none">
|
||||
{enrollment.course}
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col h-full items-center justify-center p-3">
|
||||
<div class="bg-primary-200 w-fit rounded-full p-4 text-black">
|
||||
<UserIcon size={16} />
|
||||
</div>
|
||||
<div class="my-4 text-center">
|
||||
<p class=" text-xl font-semibold">
|
||||
{$t('dashboard.publish_first_course')}
|
||||
</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-300">
|
||||
{$t('dashboard.publish_first_course_description')}
|
||||
</p>
|
||||
</div>
|
||||
<PrimaryButton
|
||||
variant={VARIANTS.OUTLINED}
|
||||
onClick={() => goto(`${$currentOrgPath}/courses`)}
|
||||
label={$t('dashboard.publish_course')}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{:else}
|
||||
<Empty
|
||||
title={$t('dashboard.publish_first_course')}
|
||||
description={$t('dashboard.publish_first_course_description')}
|
||||
icon={UserIcon}
|
||||
class="h-full"
|
||||
>
|
||||
<Button variant="outline" onclick={() => goto(`${$currentOrgPath}/courses`)}>
|
||||
{$t('dashboard.publish_course')}
|
||||
</Button>
|
||||
</Empty>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Page.Body>
|
||||
</Page.Root>
|
||||
|
||||
Reference in New Issue
Block a user