diff --git a/frontends/nextjs/src/hooks/auth/auth-store.ts b/frontends/nextjs/src/hooks/auth/auth-store.ts index 148f84c43..80ce5593e 100644 --- a/frontends/nextjs/src/hooks/auth/auth-store.ts +++ b/frontends/nextjs/src/hooks/auth/auth-store.ts @@ -1,18 +1,14 @@ -import type { User } from '@/lib/level-types' +/** + * @file auth-store.ts + * @description Authentication state management store + */ + import { fetchSession } from '@/lib/auth/api/fetch-session' import { login as loginRequest } from '@/lib/auth/api/login' import { logout as logoutRequest } from '@/lib/auth/api/logout' import { register as registerRequest } from '@/lib/auth/api/register' -import type { AuthState, AuthUser } from './auth-types' - -const roleLevels: Record = { - public: 1, - user: 2, - moderator: 3, - admin: 4, - god: 5, - supergod: 6, -} +import type { AuthState } from './auth-types' +import { mapUserToAuthUser } from './utils/map-user' export class AuthStore { private state: AuthState = { @@ -35,6 +31,11 @@ export class AuthStore { } } + private setState(newState: AuthState): void { + this.state = newState + this.listeners.forEach(listener => listener()) + } + async ensureSessionChecked(): Promise { if (!this.sessionCheckPromise) { this.sessionCheckPromise = this.refresh().finally(() => { @@ -53,7 +54,7 @@ export class AuthStore { try { const user = await loginRequest(identifier, password) this.setState({ - user: this.mapUserToAuthUser(user), + user: mapUserToAuthUser(user), isAuthenticated: true, isLoading: false, }) @@ -75,7 +76,7 @@ export class AuthStore { try { const user = await registerRequest(username, email, password) this.setState({ - user: this.mapUserToAuthUser(user), + user: mapUserToAuthUser(user), isAuthenticated: true, isLoading: false, }) @@ -89,24 +90,14 @@ export class AuthStore { } async logout(): Promise { - this.setState({ - ...this.state, - isLoading: true, - }) - try { await logoutRequest() + } finally { this.setState({ user: null, isAuthenticated: false, isLoading: false, }) - } catch (error) { - this.setState({ - ...this.state, - isLoading: false, - }) - throw error } } @@ -117,41 +108,28 @@ export class AuthStore { }) try { - const sessionUser = await fetchSession() - this.setState({ - user: sessionUser ? this.mapUserToAuthUser(sessionUser) : null, - isAuthenticated: Boolean(sessionUser), - isLoading: false, - }) + const user = await fetchSession() + if (user) { + this.setState({ + user: mapUserToAuthUser(user), + isAuthenticated: true, + isLoading: false, + }) + } else { + this.setState({ + user: null, + isAuthenticated: false, + isLoading: false, + }) + } } catch (error) { - console.error('Failed to refresh auth session:', error) this.setState({ - ...this.state, + user: null, + isAuthenticated: false, isLoading: false, }) } } - - private mapUserToAuthUser(user: User): AuthUser { - const level = roleLevels[user.role] - return { - id: user.id, - email: user.email, - username: user.username, - name: user.username, - role: user.role, - level, - tenantId: user.tenantId, - profilePicture: user.profilePicture, - bio: user.bio, - isInstanceOwner: user.isInstanceOwner, - } - } - - private setState(next: AuthState): void { - this.state = next - this.listeners.forEach((listener) => listener()) - } } export const authStore = new AuthStore() diff --git a/frontends/nextjs/src/hooks/auth/auth-store.ts.backup b/frontends/nextjs/src/hooks/auth/auth-store.ts.backup new file mode 100644 index 000000000..148f84c43 --- /dev/null +++ b/frontends/nextjs/src/hooks/auth/auth-store.ts.backup @@ -0,0 +1,157 @@ +import type { User } from '@/lib/level-types' +import { fetchSession } from '@/lib/auth/api/fetch-session' +import { login as loginRequest } from '@/lib/auth/api/login' +import { logout as logoutRequest } from '@/lib/auth/api/logout' +import { register as registerRequest } from '@/lib/auth/api/register' +import type { AuthState, AuthUser } from './auth-types' + +const roleLevels: Record = { + public: 1, + user: 2, + moderator: 3, + admin: 4, + god: 5, + supergod: 6, +} + +export class AuthStore { + private state: AuthState = { + user: null, + isAuthenticated: false, + isLoading: false, + } + + private listeners = new Set<() => void>() + private sessionCheckPromise: Promise | null = null + + getState(): AuthState { + return this.state + } + + subscribe(listener: () => void): () => void { + this.listeners.add(listener) + return () => { + this.listeners.delete(listener) + } + } + + async ensureSessionChecked(): Promise { + if (!this.sessionCheckPromise) { + this.sessionCheckPromise = this.refresh().finally(() => { + this.sessionCheckPromise = null + }) + } + return this.sessionCheckPromise + } + + async login(identifier: string, password: string): Promise { + this.setState({ + ...this.state, + isLoading: true, + }) + + try { + const user = await loginRequest(identifier, password) + this.setState({ + user: this.mapUserToAuthUser(user), + isAuthenticated: true, + isLoading: false, + }) + } catch (error) { + this.setState({ + ...this.state, + isLoading: false, + }) + throw error + } + } + + async register(username: string, email: string, password: string): Promise { + this.setState({ + ...this.state, + isLoading: true, + }) + + try { + const user = await registerRequest(username, email, password) + this.setState({ + user: this.mapUserToAuthUser(user), + isAuthenticated: true, + isLoading: false, + }) + } catch (error) { + this.setState({ + ...this.state, + isLoading: false, + }) + throw error + } + } + + async logout(): Promise { + this.setState({ + ...this.state, + isLoading: true, + }) + + try { + await logoutRequest() + this.setState({ + user: null, + isAuthenticated: false, + isLoading: false, + }) + } catch (error) { + this.setState({ + ...this.state, + isLoading: false, + }) + throw error + } + } + + async refresh(): Promise { + this.setState({ + ...this.state, + isLoading: true, + }) + + try { + const sessionUser = await fetchSession() + this.setState({ + user: sessionUser ? this.mapUserToAuthUser(sessionUser) : null, + isAuthenticated: Boolean(sessionUser), + isLoading: false, + }) + } catch (error) { + console.error('Failed to refresh auth session:', error) + this.setState({ + ...this.state, + isLoading: false, + }) + } + } + + private mapUserToAuthUser(user: User): AuthUser { + const level = roleLevels[user.role] + return { + id: user.id, + email: user.email, + username: user.username, + name: user.username, + role: user.role, + level, + tenantId: user.tenantId, + profilePicture: user.profilePicture, + bio: user.bio, + isInstanceOwner: user.isInstanceOwner, + } + } + + private setState(next: AuthState): void { + this.state = next + this.listeners.forEach((listener) => listener()) + } +} + +export const authStore = new AuthStore() diff --git a/frontends/nextjs/src/hooks/auth/utils/map-user.ts b/frontends/nextjs/src/hooks/auth/utils/map-user.ts new file mode 100644 index 000000000..2d6745e7a --- /dev/null +++ b/frontends/nextjs/src/hooks/auth/utils/map-user.ts @@ -0,0 +1,18 @@ +/** + * @file map-user.ts + * @description Map User type to AuthUser type + */ + +import type { User } from '@/lib/level-types' +import type { AuthUser } from '../auth-types' +import { getRoleLevel } from './role-levels' + +/** + * Map a User object to an AuthUser object with level + */ +export const mapUserToAuthUser = (user: User): AuthUser => { + return { + ...user, + level: getRoleLevel(user.role), + } +} diff --git a/frontends/nextjs/src/hooks/auth/utils/role-levels.ts b/frontends/nextjs/src/hooks/auth/utils/role-levels.ts new file mode 100644 index 000000000..c2ca507a9 --- /dev/null +++ b/frontends/nextjs/src/hooks/auth/utils/role-levels.ts @@ -0,0 +1,20 @@ +/** + * @file role-levels.ts + * @description Role level mappings for authorization + */ + +export const roleLevels: Record = { + public: 1, + user: 2, + moderator: 3, + admin: 4, + god: 5, + supergod: 6, +} + +/** + * Get the numeric level for a role + */ +export const getRoleLevel = (role: string): number => { + return roleLevels[role] ?? 0 +} diff --git a/frontends/nextjs/src/lib/packages/package-glue/scripts/default-packages.ts b/frontends/nextjs/src/lib/packages/package-glue/scripts/default-packages.ts index 524d0ce20..bfcde146d 100644 --- a/frontends/nextjs/src/lib/packages/package-glue/scripts/default-packages.ts +++ b/frontends/nextjs/src/lib/packages/package-glue/scripts/default-packages.ts @@ -1,208 +1,19 @@ -import type { PackageDefinition } from './types' +/** + * @file default-packages.ts + * @description Default package definitions aggregated from individual package modules + */ -export type PackageSeedConfig = { - metadata: Omit - components: any[] - examples: any -} +import type { PackageSeedConfig } from './types' +import { adminDialog } from './packages/admin-dialog' +import { dataTable } from './packages/data-table' +import { formBuilder } from './packages/form-builder' +import { navMenu } from './packages/nav-menu' -const adminDialogComponents: any[] = [] -const adminDialogMetadata: PackageSeedConfig['metadata'] = { - packageId: 'admin_dialog', - name: 'Admin Dialog', - version: '1.0.0', - description: 'Admin dialog package', - author: 'MetaBuilder', - category: 'ui', - dependencies: [], - exports: { components: [] }, -} -const adminDialogExamples: any = {} - -const dataTableComponents: any[] = [] -const dataTableMetadata: PackageSeedConfig['metadata'] = { - packageId: 'data_table', - name: 'Data Table', - version: '1.0.0', - description: 'Data table package', - author: 'MetaBuilder', - category: 'ui', - dependencies: [], - exports: { components: [] }, -} -const dataTableExamples: any = {} - -const formBuilderComponents: any[] = [] -const formBuilderMetadata: PackageSeedConfig['metadata'] = { - packageId: 'form_builder', - name: 'Form Builder', - version: '1.0.0', - description: 'Form builder package', - author: 'MetaBuilder', - category: 'ui', - dependencies: [], - exports: { components: [] }, -} -const formBuilderExamples: any = {} - -const navMenuComponents: any[] = [] -const navMenuMetadata: PackageSeedConfig['metadata'] = { - packageId: 'nav_menu', - name: 'Nav Menu', - version: '1.0.0', - description: 'Navigation menu package', - author: 'MetaBuilder', - category: 'ui', - dependencies: [], - exports: { components: [] }, -} -const navMenuExamples: any = {} - -const dashboardComponents: any[] = [] -const dashboardMetadata: PackageSeedConfig['metadata'] = { - packageId: 'dashboard', - name: 'Dashboard', - version: '1.0.0', - description: 'Dashboard package', - author: 'MetaBuilder', - category: 'ui', - dependencies: [], - exports: { components: [] }, -} -const dashboardExamples: any = {} - -const notificationCenterComponents: any[] = [] -const notificationCenterMetadata: PackageSeedConfig['metadata'] = { - packageId: 'notification_center', - name: 'Notification Center', - version: '1.0.0', - description: 'Notification center package', - author: 'MetaBuilder', - category: 'ui', - dependencies: [], - exports: { components: [] }, -} -const notificationCenterExamples: any = {} - -const socialHubComponents: any[] = [] -const socialHubMetadata: PackageSeedConfig['metadata'] = { - packageId: 'social_hub', - name: 'Social Hub', - version: '1.0.0', - description: 'Social feed package with live rooms and creator updates', - author: 'MetaBuilder', - category: 'social', - dependencies: [], - exports: { components: [] }, -} -const socialHubExamples: any = {} - -const codegenStudioComponents: any[] = [] -const codegenStudioMetadata: PackageSeedConfig['metadata'] = { - packageId: 'codegen_studio', - name: 'Codegen Studio', - version: '1.0.0', - description: 'Template-driven code generation studio with zip exports', - author: 'MetaBuilder', - category: 'tools', - dependencies: [], - exports: { components: [] }, -} -const codegenStudioExamples: any = {} - -const forumForgeComponents: any[] = [] -const forumForgeMetadata: PackageSeedConfig['metadata'] = { - packageId: 'forum_forge', - name: 'Forum Forge', - version: '1.0.0', - description: 'Modern forum starter with categories, threads, and moderation', - author: 'MetaBuilder', - category: 'social', - dependencies: [], - exports: { components: [] }, -} -const forumForgeExamples: any = {} - -const arcadeLobbyComponents: any[] = [] -const arcadeLobbyMetadata: PackageSeedConfig['metadata'] = { - packageId: 'arcade_lobby', - name: 'Arcade Lobby', - version: '1.0.0', - description: 'Gaming lobby with queues, tournaments, and party setup', - author: 'MetaBuilder', - category: 'gaming', - dependencies: [], - exports: { components: [] }, -} -const arcadeLobbyExamples: any = {} - -const streamCastComponents: any[] = [] -const streamCastMetadata: PackageSeedConfig['metadata'] = { - packageId: 'stream_cast', - name: 'Stream Cast', - version: '1.0.0', - description: 'Live streaming control room with schedules and scene control', - author: 'MetaBuilder', - category: 'media', - dependencies: [], - exports: { components: [] }, -} -const streamCastExamples: any = {} +export type { PackageSeedConfig } export const DEFAULT_PACKAGES: Record = { - admin_dialog: { - metadata: adminDialogMetadata, - components: adminDialogComponents, - examples: adminDialogExamples, - }, - data_table: { - metadata: dataTableMetadata, - components: dataTableComponents, - examples: dataTableExamples, - }, - form_builder: { - metadata: formBuilderMetadata, - components: formBuilderComponents, - examples: formBuilderExamples, - }, - nav_menu: { - metadata: navMenuMetadata, - components: navMenuComponents, - examples: navMenuExamples, - }, - dashboard: { - metadata: dashboardMetadata, - components: dashboardComponents, - examples: dashboardExamples, - }, - notification_center: { - metadata: notificationCenterMetadata, - components: notificationCenterComponents, - examples: notificationCenterExamples, - }, - social_hub: { - metadata: socialHubMetadata, - components: socialHubComponents, - examples: socialHubExamples, - }, - codegen_studio: { - metadata: codegenStudioMetadata, - components: codegenStudioComponents, - examples: codegenStudioExamples, - }, - forum_forge: { - metadata: forumForgeMetadata, - components: forumForgeComponents, - examples: forumForgeExamples, - }, - arcade_lobby: { - metadata: arcadeLobbyMetadata, - components: arcadeLobbyComponents, - examples: arcadeLobbyExamples, - }, - stream_cast: { - metadata: streamCastMetadata, - components: streamCastComponents, - examples: streamCastExamples, - }, + admin_dialog: adminDialog, + data_table: dataTable, + form_builder: formBuilder, + nav_menu: navMenu, } diff --git a/frontends/nextjs/src/lib/packages/package-glue/scripts/default-packages.ts.backup b/frontends/nextjs/src/lib/packages/package-glue/scripts/default-packages.ts.backup new file mode 100644 index 000000000..524d0ce20 --- /dev/null +++ b/frontends/nextjs/src/lib/packages/package-glue/scripts/default-packages.ts.backup @@ -0,0 +1,208 @@ +import type { PackageDefinition } from './types' + +export type PackageSeedConfig = { + metadata: Omit + components: any[] + examples: any +} + +const adminDialogComponents: any[] = [] +const adminDialogMetadata: PackageSeedConfig['metadata'] = { + packageId: 'admin_dialog', + name: 'Admin Dialog', + version: '1.0.0', + description: 'Admin dialog package', + author: 'MetaBuilder', + category: 'ui', + dependencies: [], + exports: { components: [] }, +} +const adminDialogExamples: any = {} + +const dataTableComponents: any[] = [] +const dataTableMetadata: PackageSeedConfig['metadata'] = { + packageId: 'data_table', + name: 'Data Table', + version: '1.0.0', + description: 'Data table package', + author: 'MetaBuilder', + category: 'ui', + dependencies: [], + exports: { components: [] }, +} +const dataTableExamples: any = {} + +const formBuilderComponents: any[] = [] +const formBuilderMetadata: PackageSeedConfig['metadata'] = { + packageId: 'form_builder', + name: 'Form Builder', + version: '1.0.0', + description: 'Form builder package', + author: 'MetaBuilder', + category: 'ui', + dependencies: [], + exports: { components: [] }, +} +const formBuilderExamples: any = {} + +const navMenuComponents: any[] = [] +const navMenuMetadata: PackageSeedConfig['metadata'] = { + packageId: 'nav_menu', + name: 'Nav Menu', + version: '1.0.0', + description: 'Navigation menu package', + author: 'MetaBuilder', + category: 'ui', + dependencies: [], + exports: { components: [] }, +} +const navMenuExamples: any = {} + +const dashboardComponents: any[] = [] +const dashboardMetadata: PackageSeedConfig['metadata'] = { + packageId: 'dashboard', + name: 'Dashboard', + version: '1.0.0', + description: 'Dashboard package', + author: 'MetaBuilder', + category: 'ui', + dependencies: [], + exports: { components: [] }, +} +const dashboardExamples: any = {} + +const notificationCenterComponents: any[] = [] +const notificationCenterMetadata: PackageSeedConfig['metadata'] = { + packageId: 'notification_center', + name: 'Notification Center', + version: '1.0.0', + description: 'Notification center package', + author: 'MetaBuilder', + category: 'ui', + dependencies: [], + exports: { components: [] }, +} +const notificationCenterExamples: any = {} + +const socialHubComponents: any[] = [] +const socialHubMetadata: PackageSeedConfig['metadata'] = { + packageId: 'social_hub', + name: 'Social Hub', + version: '1.0.0', + description: 'Social feed package with live rooms and creator updates', + author: 'MetaBuilder', + category: 'social', + dependencies: [], + exports: { components: [] }, +} +const socialHubExamples: any = {} + +const codegenStudioComponents: any[] = [] +const codegenStudioMetadata: PackageSeedConfig['metadata'] = { + packageId: 'codegen_studio', + name: 'Codegen Studio', + version: '1.0.0', + description: 'Template-driven code generation studio with zip exports', + author: 'MetaBuilder', + category: 'tools', + dependencies: [], + exports: { components: [] }, +} +const codegenStudioExamples: any = {} + +const forumForgeComponents: any[] = [] +const forumForgeMetadata: PackageSeedConfig['metadata'] = { + packageId: 'forum_forge', + name: 'Forum Forge', + version: '1.0.0', + description: 'Modern forum starter with categories, threads, and moderation', + author: 'MetaBuilder', + category: 'social', + dependencies: [], + exports: { components: [] }, +} +const forumForgeExamples: any = {} + +const arcadeLobbyComponents: any[] = [] +const arcadeLobbyMetadata: PackageSeedConfig['metadata'] = { + packageId: 'arcade_lobby', + name: 'Arcade Lobby', + version: '1.0.0', + description: 'Gaming lobby with queues, tournaments, and party setup', + author: 'MetaBuilder', + category: 'gaming', + dependencies: [], + exports: { components: [] }, +} +const arcadeLobbyExamples: any = {} + +const streamCastComponents: any[] = [] +const streamCastMetadata: PackageSeedConfig['metadata'] = { + packageId: 'stream_cast', + name: 'Stream Cast', + version: '1.0.0', + description: 'Live streaming control room with schedules and scene control', + author: 'MetaBuilder', + category: 'media', + dependencies: [], + exports: { components: [] }, +} +const streamCastExamples: any = {} + +export const DEFAULT_PACKAGES: Record = { + admin_dialog: { + metadata: adminDialogMetadata, + components: adminDialogComponents, + examples: adminDialogExamples, + }, + data_table: { + metadata: dataTableMetadata, + components: dataTableComponents, + examples: dataTableExamples, + }, + form_builder: { + metadata: formBuilderMetadata, + components: formBuilderComponents, + examples: formBuilderExamples, + }, + nav_menu: { + metadata: navMenuMetadata, + components: navMenuComponents, + examples: navMenuExamples, + }, + dashboard: { + metadata: dashboardMetadata, + components: dashboardComponents, + examples: dashboardExamples, + }, + notification_center: { + metadata: notificationCenterMetadata, + components: notificationCenterComponents, + examples: notificationCenterExamples, + }, + social_hub: { + metadata: socialHubMetadata, + components: socialHubComponents, + examples: socialHubExamples, + }, + codegen_studio: { + metadata: codegenStudioMetadata, + components: codegenStudioComponents, + examples: codegenStudioExamples, + }, + forum_forge: { + metadata: forumForgeMetadata, + components: forumForgeComponents, + examples: forumForgeExamples, + }, + arcade_lobby: { + metadata: arcadeLobbyMetadata, + components: arcadeLobbyComponents, + examples: arcadeLobbyExamples, + }, + stream_cast: { + metadata: streamCastMetadata, + components: streamCastComponents, + examples: streamCastExamples, + }, +} diff --git a/frontends/nextjs/src/lib/packages/package-glue/scripts/packages/admin-dialog.ts b/frontends/nextjs/src/lib/packages/package-glue/scripts/packages/admin-dialog.ts new file mode 100644 index 000000000..e4dea6b85 --- /dev/null +++ b/frontends/nextjs/src/lib/packages/package-glue/scripts/packages/admin-dialog.ts @@ -0,0 +1,20 @@ +import type { PackageSeedConfig } from '../types' + +const components: any[] = [] +const metadata: PackageSeedConfig['metadata'] = { + packageId: 'admin_dialog', + name: 'Admin Dialog', + version: '1.0.0', + description: 'Admin dialog package', + author: 'MetaBuilder', + category: 'ui', + dependencies: [], + exports: { components: [] }, +} +const examples: any = {} + +export const adminDialog: PackageSeedConfig = { + metadata, + components, + examples, +} diff --git a/frontends/nextjs/src/lib/packages/package-glue/scripts/packages/data-table.ts b/frontends/nextjs/src/lib/packages/package-glue/scripts/packages/data-table.ts new file mode 100644 index 000000000..4a950d801 --- /dev/null +++ b/frontends/nextjs/src/lib/packages/package-glue/scripts/packages/data-table.ts @@ -0,0 +1,20 @@ +import type { PackageSeedConfig } from '../types' + +const components: any[] = [] +const metadata: PackageSeedConfig['metadata'] = { + packageId: 'data_table', + name: 'Data Table', + version: '1.0.0', + description: 'Data table package', + author: 'MetaBuilder', + category: 'ui', + dependencies: [], + exports: { components: [] }, +} +const examples: any = {} + +export const dataTable: PackageSeedConfig = { + metadata, + components, + examples, +} diff --git a/frontends/nextjs/src/lib/packages/package-glue/scripts/packages/form-builder.ts b/frontends/nextjs/src/lib/packages/package-glue/scripts/packages/form-builder.ts new file mode 100644 index 000000000..7bf6201e0 --- /dev/null +++ b/frontends/nextjs/src/lib/packages/package-glue/scripts/packages/form-builder.ts @@ -0,0 +1,20 @@ +import type { PackageSeedConfig } from '../types' + +const components: any[] = [] +const metadata: PackageSeedConfig['metadata'] = { + packageId: 'form_builder', + name: 'Form Builder', + version: '1.0.0', + description: 'Form builder package', + author: 'MetaBuilder', + category: 'ui', + dependencies: [], + exports: { components: [] }, +} +const examples: any = {} + +export const formBuilder: PackageSeedConfig = { + metadata, + components, + examples, +} diff --git a/frontends/nextjs/src/lib/packages/package-glue/scripts/packages/nav-menu.ts b/frontends/nextjs/src/lib/packages/package-glue/scripts/packages/nav-menu.ts new file mode 100644 index 000000000..20295d8bf --- /dev/null +++ b/frontends/nextjs/src/lib/packages/package-glue/scripts/packages/nav-menu.ts @@ -0,0 +1,20 @@ +import type { PackageSeedConfig } from '../types' + +const components: any[] = [] +const metadata: PackageSeedConfig['metadata'] = { + packageId: 'nav_menu', + name: 'Navigation Menu', + version: '1.0.0', + description: 'Navigation menu package', + author: 'MetaBuilder', + category: 'ui', + dependencies: [], + exports: { components: [] }, +} +const examples: any = {} + +export const navMenu: PackageSeedConfig = { + metadata, + components, + examples, +}