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
@@ -30,7 +30,7 @@
</script>
<Breadcrumb.Root>
<Breadcrumb.List>
<Breadcrumb.List class="flex-nowrap!">
<Breadcrumb.Item>
<OrgSwitcher variant="breadcrumb" />
</Breadcrumb.Item>
@@ -6,10 +6,21 @@
import * as Popover from '@cio/ui/base/popover';
import Search from '../search.svelte';
import AppBreadcrumbs from './app-breadcrumbs.svelte';
import RefreshCcwIcon from '@lucide/svelte/icons/refresh-ccw';
import * as Empty from '@cio/ui/base/empty';
import { currentOrg } from '$lib/utils/store/org';
import { setupProgressApi } from '$lib/features/setup/api/setup-progress.svelte';
import AppSetup from './app-setup.svelte';
$effect(() => {
if ($currentOrg.siteName) {
setupProgressApi.fetchSetupProgress($currentOrg.siteName);
}
});
</script>
<header
class="group-has-data-[collapsible=icon]/sidebar-wrapper:h-8 border-border flex h-12 w-full shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear"
class="group-has-data-[collapsible=icon]/sidebar-wrapper:h-8 border-border ui:bg-background bg-background sticky top-0 z-50 flex h-12 w-full shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear"
>
<div class="flex w-full items-center gap-2 px-4">
<Sidebar.Trigger />
@@ -22,6 +33,8 @@
<span class="grow"></span>
<AppSetup />
<Search />
<Popover.Root>
@@ -31,11 +44,21 @@
</Button>
</Popover.Trigger>
<Popover.Content>
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between gap-2">
<p class="text-sm">Your notifications will show here</p>
</div>
</div>
<Empty.Root class="ui:from-muted/50 ui:to-background ui:h-full ui:bg-gradient-to-b ui:from-30%">
<Empty.Header>
<Empty.Media variant="icon">
<BellIcon />
</Empty.Media>
<Empty.Title>No Notifications</Empty.Title>
<Empty.Description>You're all caught up. New notifications will appear here.</Empty.Description>
</Empty.Header>
<Empty.Content>
<Button variant="outline" size="sm">
<RefreshCcwIcon />
Refresh
</Button>
</Empty.Content>
</Empty.Root>
</Popover.Content>
</Popover.Root>
</div>
@@ -0,0 +1,64 @@
<script lang="ts">
import * as HoverCard from '@cio/ui/base/hover-card';
import { Button } from '@cio/ui/base/button';
import RefreshCcwIcon from '@lucide/svelte/icons/refresh-ccw';
import CircleIcon from '@lucide/svelte/icons/circle';
import SetupProgress from '$lib/features/setup/components/setup-progress.svelte';
import { currentOrg } from '$lib/utils/store/org';
import { profile } from '$lib/utils/store/user';
import { t } from '$lib/utils/functions/translations';
import BadgeCheckIcon from '@lucide/svelte/icons/badge-check';
import { calculateProgress, setupProgressApi } from '$lib/features/setup/api/setup-progress.svelte';
function handleRefresh() {
if ($currentOrg.siteName) {
setupProgressApi.fetchSetupProgress($currentOrg.siteName);
}
}
const setupList = $derived(
setupProgressApi.setupList.map((item) => {
if (item.id === 'profile') {
return {
...item,
is_completed: !$profile.avatarUrl?.includes('avatars/avatar.png')
};
}
return item;
})
);
const progressPercentage = $derived(calculateProgress(setupList));
</script>
{#if progressPercentage < 100}
<HoverCard.Root openDelay={200}>
<HoverCard.Trigger class="hidden md:block">
<SetupProgress size="small" setupItems={setupList} />
</HoverCard.Trigger>
<HoverCard.Content class="hidden w-80 md:block">
{#if $currentOrg.id}
<div class="space-y-2">
<div class="flex items-center justify-between">
<h4 class="text-sm font-semibold">{$t('setup.your_checklist')}</h4>
<Button variant="secondary" size="icon" onclick={handleRefresh} disabled={setupProgressApi.isLoading}>
<RefreshCcwIcon class="size-3 {setupProgressApi.isLoading ? 'animate-spin' : ''}" />
</Button>
</div>
{#each setupList as item}
<div class="flex items-center gap-2 text-sm">
{#if item.is_completed}
<BadgeCheckIcon class="custom text-green-600! size-4 shrink-0 opacity-50" />
<span class="text-muted-foreground line-through opacity-50">{$t(item.title)}</span>
{:else}
<CircleIcon class="size-4 shrink-0 text-gray-400" />
<span>{$t(item.title)}</span>
{/if}
</div>
{/each}
</div>
{/if}
</HoverCard.Content>
</HoverCard.Root>
{/if}
@@ -64,7 +64,7 @@ export function generateBreadcrumbs(
// Handle items with query params (like settings tabs)
if (matchedNavItem?.items && searchParams) {
const subItem = matchedNavItem.items.find((sub) => isActive(pathWithQuery, sub.url, true));
const subItem = matchedNavItem.items.find((sub) => isActive(pathWithQuery, sub.url, undefined, true));
if (subItem) {
breadcrumbs.push({
label: subItem.title,
@@ -31,6 +31,7 @@ export interface NavItemConfig {
useHashUrl?: boolean; // Use '#' as URL (for collapsible items like settings)
nestedRoutes?: NestedRouteConfig[]; // Static nested routes
supportsDynamicSegment?: boolean; // Supports dynamic segments (like [slug])
matchPattern?: string | ((orgSlug: string) => string); // Regex pattern for route matching
}
export interface NestedRouteConfig {
@@ -43,18 +44,21 @@ const baseNavConfig: NavItemConfig[] = [
{
titleKey: 'org_navigation.dashboard',
path: '',
icon: LayoutDashboardIcon
icon: LayoutDashboardIcon,
matchPattern: '^/org/[^/]+/?$' // Exact match only
},
{
titleKey: 'org_navigation.courses',
path: '/courses',
icon: LibraryBigIcon
icon: LibraryBigIcon,
matchPattern: '^/org/[^/]+/courses(/.*)?$' // Matches nested routes
},
{
titleKey: 'org_navigation.community',
path: '/community',
icon: MessageSquareMoreIcon,
supportsDynamicSegment: true, // Supports /community/[slug]
matchPattern: '^/org/[^/]+/community(/.*)?$', // Matches nested routes
nestedRoutes: [
{
path: 'ask',
@@ -65,19 +69,22 @@ const baseNavConfig: NavItemConfig[] = [
{
titleKey: 'org_navigation.audience',
path: '/audience',
icon: UsersIcon
icon: UsersIcon,
matchPattern: '^/org/[^/]+/audience(/.*)?$' // Matches nested routes
},
{
titleKey: 'org_navigation.setup',
path: '/setup',
icon: Settings2Icon,
requiresAdmin: true
requiresAdmin: true,
matchPattern: '^/org/[^/]+/setup(/.*)?$' // Matches nested routes
},
{
titleKey: 'org_navigation.settings',
path: '/settings',
icon: CogIcon,
useHashUrl: true, // Use '#' for collapsible parent
matchPattern: '^/org/[^/]+/settings(/.*)?$', // Matches nested routes
items: [
{
titleKey: 'Profile',
@@ -85,16 +92,20 @@ const baseNavConfig: NavItemConfig[] = [
},
{
titleKey: 'Organization',
path: '/settings?tab=org'
path: '/settings/org'
},
{
titleKey: 'Landing Page',
path: '/settings?tab=landingpage'
path: '/settings/landingpage'
},
{
titleKey: 'Billing',
path: '/settings?tab=billing'
path: '/settings/billing'
}
// {
// titleKey: 'Integrations',
// path: '/settings/integrations'
// }
],
nestedRoutes: [
{
@@ -134,13 +145,17 @@ export function getOrgNavigationItems(
const url = config.path === '' ? currentOrgPath : `${currentOrgPath}${config.path}`;
const fullPath = config.path === '' ? `/org/${currentOrg.siteName}` : `/org/${currentOrg.siteName}${config.path}`;
// Extract match pattern (handle function case)
const matchPattern =
typeof config.matchPattern === 'function' ? config.matchPattern(currentOrg.siteName) : config.matchPattern;
const item: NavItem = {
title: t(config.titleKey),
url: config.useHashUrl ? '#' : url,
path: config.path, // Store actual path for breadcrumb generation
icon: config.icon,
isActive: isActive(pagePathname, fullPath),
isExpanded: config.items ? isActive(pagePathname, fullPath) : undefined,
isActive: isActive(pagePathname, fullPath, matchPattern),
isExpanded: config.items ? isActive(pagePathname, fullPath, matchPattern) : undefined,
useHashUrl: config.useHashUrl,
nestedRoutes: config.nestedRoutes,
supportsDynamicSegment: config.supportsDynamicSegment