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:
Rotimi Best
2025-12-02 09:54:38 +01:00
committed by GitHub
parent 531516a8f7
commit c7854efae3
350 changed files with 13928 additions and 3895 deletions
+127 -152
View File
@@ -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>