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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user