feat: migrate auth from supabase to better auth (#596)

* feat: add README for @cio/db package and remove existingAccounts.json

* load session, load profile and organization, add create profile hook

* chore: cache session request

The trick was passing all the cookies to the backend. Previously I was only passing the better-auth.session_token and not better-auth.session_data. Read about the difference here https://www.better-auth.com/docs/concepts/cookies#custom-cookies

* feat: login, logout, onboarding, verify email, reset password.

* feat: complete change email and email verification

* feat: standardise how we make rpc calls and create org endpoints

* fix ui errors

* fix: handle potential undefined customerId and standardize avatar URL property

* refactor: standardize custom domain handling and update telegram chat ID references

* chore: add tailwind prefix to @cio/ui

this fixed the media query issues because  @cio/ui classes were overriding this dashboard tailwind build

* chore: clean up org sidebar

* chore: persist mode state

* feat: implement order breadcrumbs

* chore: upgrade to better auth 1.4

* chore: address common auth flow bugs and setup app for others to setup locally.
This commit is contained in:
Rotimi Best
2025-11-24 01:00:50 +01:00
committed by GitHub
parent 24741644d6
commit a3924100dd
567 changed files with 22364 additions and 13437 deletions
@@ -0,0 +1,162 @@
import type { AccountOrg } from '$lib/features/app/types';
import CogIcon from '@lucide/svelte/icons/cog';
import type { Component } from 'svelte';
import LayoutDashboardIcon from '@lucide/svelte/icons/layout-dashboard';
import LibraryBigIcon from '@lucide/svelte/icons/library-big';
import MessageSquareMoreIcon from '@lucide/svelte/icons/message-square-more';
import Settings2Icon from '@lucide/svelte/icons/settings-2';
import UsersIcon from '@lucide/svelte/icons/users';
import { isActive } from '$lib/utils/functions/app';
export interface NavItem {
title: string; // translation key
url: string;
path: string; // Actual path (e.g., '/settings') for breadcrumb generation
icon?: Component;
isActive?: boolean;
isExpanded?: boolean;
items?: NavItem[]; // for nested items like settings
// Metadata for breadcrumb generation
useHashUrl?: boolean; // Use '#' as URL (for collapsible items like settings)
nestedRoutes?: NestedRouteConfig[]; // Static nested routes (like community/ask, settings/customize-lms)
supportsDynamicSegment?: boolean; // Supports dynamic segments (like [slug])
}
export interface NavItemConfig {
titleKey: string;
path: string;
icon?: Component;
requiresAdmin?: boolean;
items?: NavItemConfig[];
useHashUrl?: boolean; // Use '#' as URL (for collapsible items like settings)
nestedRoutes?: NestedRouteConfig[]; // Static nested routes
supportsDynamicSegment?: boolean; // Supports dynamic segments (like [slug])
}
export interface NestedRouteConfig {
path: string; // Relative to parent (e.g., 'ask', 'customize-lms')
titleKey: string; // Translation key or plain text
}
// Base navigation configuration structure
const baseNavConfig: NavItemConfig[] = [
{
titleKey: 'org_navigation.dashboard',
path: '',
icon: LayoutDashboardIcon
},
{
titleKey: 'org_navigation.courses',
path: '/courses',
icon: LibraryBigIcon
},
{
titleKey: 'org_navigation.community',
path: '/community',
icon: MessageSquareMoreIcon,
supportsDynamicSegment: true, // Supports /community/[slug]
nestedRoutes: [
{
path: 'ask',
titleKey: 'Ask Question' // Could be translated
}
]
},
{
titleKey: 'org_navigation.audience',
path: '/audience',
icon: UsersIcon
},
{
titleKey: 'org_navigation.setup',
path: '/setup',
icon: Settings2Icon,
requiresAdmin: true
},
{
titleKey: 'org_navigation.settings',
path: '/settings',
icon: CogIcon,
useHashUrl: true, // Use '#' for collapsible parent
items: [
{
titleKey: 'Profile',
path: '/settings'
},
{
titleKey: 'Organization',
path: '/settings?tab=org'
},
{
titleKey: 'Landing Page',
path: '/settings?tab=landingpage'
},
{
titleKey: 'Billing',
path: '/settings?tab=billing'
}
],
nestedRoutes: [
{
path: 'customize-lms',
titleKey: 'Customize LMS'
},
{
path: 'domains',
titleKey: 'Domains'
},
{
path: 'teams',
titleKey: 'Teams'
}
]
}
];
/**
* Get navigation items based on organization context and permissions
*/
export function getOrgNavigationItems(
currentOrgPath: string,
currentOrg: AccountOrg,
isOrgAdmin: boolean | null,
t: (key: string) => string,
pagePathname: string
): NavItem[] {
const items: NavItem[] = [];
for (const config of baseNavConfig) {
// Skip admin-only items if user is not admin
if (config.requiresAdmin && !isOrgAdmin) {
continue;
}
const url = config.path === '' ? currentOrgPath : `${currentOrgPath}${config.path}`;
const fullPath = config.path === '' ? `/org/${currentOrg.siteName}` : `/org/${currentOrg.siteName}${config.path}`;
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,
useHashUrl: config.useHashUrl,
nestedRoutes: config.nestedRoutes,
supportsDynamicSegment: config.supportsDynamicSegment
};
// Handle nested items (like settings sub-items)
if (config.items) {
item.items = config.items.map((subConfig) => ({
title: subConfig.titleKey,
url: `${currentOrgPath}${subConfig.path}`,
path: subConfig.path
}));
}
items.push(item);
}
return items;
}