mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:41:09 +02:00
refactor: consolidate codebase — remove archived code, deduplicate packages, standardize middleware
- Delete 17 server-archived/ directories (consolidated into apps/api/) - Delete apps-archived/ (clock, wisekeep) and services-archived/ (it-landing, ollama-metrics-proxy) - Fix type safety: replace all `any` casts in cross-app-queries.ts with proper Local* types - Remove duplicate shared-auth-stores package (identical copy of shared-auth-ui/stores/) - Remove duplicate theme store from shared-stores (canonical version in shared-theme) - Migrate memoro-server rate-limiter to shared-hono/rateLimitMiddleware - Migrate uload-server JWT auth + error handler to shared-hono (authMiddleware, errorHandler) - Migrate arcade-server error handling to shared-hono - Merge shared-profile-ui and shared-app-onboarding into shared-ui - Unify /clock route into /times/clock, remove redirect stubs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7ee57b7afd
commit
d8ce4eaf34
309 changed files with 172 additions and 21667 deletions
|
|
@ -1,34 +0,0 @@
|
|||
{
|
||||
"name": "@manacore/shared-app-onboarding",
|
||||
"version": "0.1.0",
|
||||
"description": "App-specific mini-onboarding for ManaCore apps",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"svelte": "./src/index.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"svelte": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"type-check": "svelte-kit sync 2>/dev/null || true && svelte-check --tsconfig ./tsconfig.json"
|
||||
},
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"dependencies": {
|
||||
"@manacore/shared-theme": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
// Types
|
||||
export type {
|
||||
AppOnboardingOption,
|
||||
AppOnboardingStepType,
|
||||
AppOnboardingStepBase,
|
||||
AppOnboardingSelectStep,
|
||||
AppOnboardingToggleStep,
|
||||
AppOnboardingInfoStep,
|
||||
AppOnboardingStep,
|
||||
AppOnboardingConfig,
|
||||
AppOnboardingPreferences,
|
||||
AppOnboardingStore,
|
||||
MiniOnboardingModalProps,
|
||||
} from './types';
|
||||
|
||||
// Factory function
|
||||
export { createAppOnboardingStore } from './create-app-onboarding.svelte';
|
||||
|
||||
// Component
|
||||
export { default as MiniOnboardingModal } from './MiniOnboardingModal.svelte';
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
"types": ["svelte"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"name": "@manacore/shared-auth-stores",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"svelte": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"svelte": "./src/index.ts",
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"type-check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@manacore/shared-auth": "workspace:*",
|
||||
"@manacore/shared-types": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
/**
|
||||
* Svelte 5 Auth Store Factory
|
||||
*
|
||||
* Creates a reactive auth store using Svelte 5 runes.
|
||||
* Generic over user type to support app-specific user models.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // In your app's auth store file
|
||||
* import { createAuthStore } from '@manacore/shared-auth-stores';
|
||||
* import { authService } from '$lib/auth';
|
||||
* import type { AppUser } from '$lib/types';
|
||||
*
|
||||
* export const authStore = createAuthStore<AppUser>(authService);
|
||||
* ```
|
||||
*/
|
||||
|
||||
import type { AuthServiceAdapter, AuthResult, BaseUser } from './types';
|
||||
|
||||
/**
|
||||
* Create a Svelte 5 runes-based auth store
|
||||
*
|
||||
* @param authService - Auth service adapter implementing the AuthServiceAdapter interface
|
||||
* @returns Reactive auth store with state and actions
|
||||
*/
|
||||
export function createAuthStore<TUser extends BaseUser>(authService: AuthServiceAdapter<TUser>) {
|
||||
// Reactive state using Svelte 5 runes
|
||||
let user = $state<TUser | null>(null);
|
||||
let loading = $state(true);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
return {
|
||||
// Reactive getters
|
||||
get user() {
|
||||
return user;
|
||||
},
|
||||
get loading() {
|
||||
return loading;
|
||||
},
|
||||
get error() {
|
||||
return error;
|
||||
},
|
||||
get isAuthenticated() {
|
||||
return !!user;
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize auth state from stored tokens/session
|
||||
*/
|
||||
async initialize() {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const isAuth = await authService.isAuthenticated();
|
||||
if (isAuth) {
|
||||
user = await authService.getUserFromToken();
|
||||
} else {
|
||||
user = null;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to initialize auth:', e);
|
||||
error = e instanceof Error ? e.message : 'Failed to initialize authentication';
|
||||
user = null;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set user manually (useful for SSR hydration)
|
||||
*/
|
||||
setUser(newUser: TUser | null) {
|
||||
user = newUser;
|
||||
error = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with email and password
|
||||
*/
|
||||
async signIn(email: string, password: string): Promise<AuthResult<TUser>> {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const result = await authService.signIn(email, password);
|
||||
if (result.success) {
|
||||
user = await authService.getUserFromToken();
|
||||
} else {
|
||||
error = result.error || 'Sign in failed';
|
||||
}
|
||||
return result;
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : 'Sign in failed';
|
||||
error = errorMessage;
|
||||
return { success: false, error: errorMessage };
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign up with email and password
|
||||
*/
|
||||
async signUp(email: string, password: string): Promise<AuthResult<TUser>> {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const result = await authService.signUp(email, password);
|
||||
if (result.success && !result.needsVerification) {
|
||||
user = await authService.getUserFromToken();
|
||||
} else if (!result.success) {
|
||||
error = result.error || 'Sign up failed';
|
||||
}
|
||||
return result;
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : 'Sign up failed';
|
||||
error = errorMessage;
|
||||
return { success: false, error: errorMessage };
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Send password reset email
|
||||
*/
|
||||
async forgotPassword(email: string): Promise<{ success: boolean; error?: string }> {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const result = await authService.forgotPassword(email);
|
||||
if (!result.success) {
|
||||
error = result.error || 'Password reset failed';
|
||||
}
|
||||
return result;
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : 'Password reset failed';
|
||||
error = errorMessage;
|
||||
return { success: false, error: errorMessage };
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign out user
|
||||
*/
|
||||
async signOut() {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
await authService.signOut();
|
||||
user = null;
|
||||
} catch (e) {
|
||||
console.error('Sign out failed:', e);
|
||||
error = e instanceof Error ? e.message : 'Sign out failed';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check authentication status
|
||||
*/
|
||||
async checkAuth(): Promise<boolean> {
|
||||
try {
|
||||
const isAuth = await authService.isAuthenticated();
|
||||
if (!isAuth) {
|
||||
user = null;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Auth check failed:', e);
|
||||
user = null;
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear error state
|
||||
*/
|
||||
clearError() {
|
||||
error = null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,301 +0,0 @@
|
|||
/**
|
||||
* Mana Auth Store Factory
|
||||
*
|
||||
* Creates a complete auth store using @manacore/shared-auth.
|
||||
* Replaces the ~350 lines of duplicated auth.svelte.ts in each app
|
||||
* with a single factory call.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // apps/todo/apps/web/src/lib/stores/auth.svelte.ts
|
||||
* import { createManaAuthStore } from '@manacore/shared-auth-stores';
|
||||
*
|
||||
* export const authStore = createManaAuthStore({
|
||||
* devBackendPort: 3031,
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @example With post-login callback
|
||||
* ```ts
|
||||
* import { createManaAuthStore } from '@manacore/shared-auth-stores';
|
||||
* import { apiClient } from '$lib/api/client';
|
||||
*
|
||||
* export const authStore = createManaAuthStore({
|
||||
* devBackendPort: 3030,
|
||||
* onAuthenticated: async (authService) => {
|
||||
* const token = await authService.getAppToken();
|
||||
* if (token) apiClient.setAccessToken(token);
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
import {
|
||||
initializeWebAuth,
|
||||
type UserData,
|
||||
type AuthServiceInterface,
|
||||
} from '@manacore/shared-auth';
|
||||
|
||||
export interface ManaAuthStoreConfig {
|
||||
/** Dev backend port (e.g. 3031 for todo). Only used in development. */
|
||||
devBackendPort?: number;
|
||||
/** Dev auth port. Defaults to 3001. */
|
||||
devAuthPort?: number;
|
||||
/** Callback after successful authentication (sign in, SSO, 2FA). */
|
||||
onAuthenticated?: (authService: AuthServiceInterface) => void | Promise<void>;
|
||||
/** Callback after sign out. */
|
||||
onSignOut?: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
export function createManaAuthStore(config: ManaAuthStoreConfig = {}) {
|
||||
const devAuthUrl = `http://localhost:${config.devAuthPort ?? 3001}`;
|
||||
const devBackendUrl = config.devBackendPort
|
||||
? `http://localhost:${config.devBackendPort}`
|
||||
: '';
|
||||
|
||||
// URL resolution (runtime, not build-time)
|
||||
function getAuthUrl(): string {
|
||||
if (browser && typeof window !== 'undefined') {
|
||||
const injected = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
|
||||
.__PUBLIC_MANA_CORE_AUTH_URL__;
|
||||
if (injected) return injected;
|
||||
return import.meta.env.DEV ? devAuthUrl : '';
|
||||
}
|
||||
return process.env.PUBLIC_MANA_CORE_AUTH_URL || devAuthUrl;
|
||||
}
|
||||
|
||||
function getBackendUrl(): string {
|
||||
if (browser && typeof window !== 'undefined') {
|
||||
const injected = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
|
||||
.__PUBLIC_BACKEND_URL__;
|
||||
if (injected) return injected;
|
||||
return import.meta.env.DEV ? devBackendUrl : '';
|
||||
}
|
||||
return process.env.PUBLIC_BACKEND_URL || devBackendUrl;
|
||||
}
|
||||
|
||||
// Lazy init (SSR-safe)
|
||||
let _authService: AuthServiceInterface | null = null;
|
||||
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
|
||||
|
||||
function getAuthService() {
|
||||
if (!browser) return null;
|
||||
if (!_authService) {
|
||||
const backendUrl = getBackendUrl();
|
||||
const auth = initializeWebAuth({
|
||||
baseUrl: getAuthUrl(),
|
||||
...(backendUrl ? { backendUrl } : {}),
|
||||
});
|
||||
_authService = auth.authService;
|
||||
_tokenManager = auth.tokenManager;
|
||||
}
|
||||
return _authService;
|
||||
}
|
||||
|
||||
function getTokenManager() {
|
||||
if (!browser) return null;
|
||||
getAuthService();
|
||||
return _tokenManager;
|
||||
}
|
||||
|
||||
async function handleAuthenticated() {
|
||||
if (config.onAuthenticated) {
|
||||
const svc = getAuthService();
|
||||
if (svc) await config.onAuthenticated(svc);
|
||||
}
|
||||
}
|
||||
|
||||
// Reactive state
|
||||
let user = $state<UserData | null>(null);
|
||||
let loading = $state(true);
|
||||
let initialized = $state(false);
|
||||
|
||||
return {
|
||||
// Getters
|
||||
get user() { return user; },
|
||||
get loading() { return loading; },
|
||||
get isAuthenticated() { return !!user; },
|
||||
get initialized() { return initialized; },
|
||||
|
||||
async initialize() {
|
||||
if (initialized) return;
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
initialized = true;
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
try {
|
||||
let authenticated = await authService.isAuthenticated();
|
||||
if (!authenticated) {
|
||||
const ssoResult = await authService.trySSO();
|
||||
if (ssoResult.success) authenticated = true;
|
||||
}
|
||||
if (authenticated) {
|
||||
user = await authService.getUserFromToken();
|
||||
await handleAuthenticated();
|
||||
}
|
||||
initialized = true;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize auth:', error);
|
||||
user = null;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 2FA
|
||||
async verifyTwoFactor(code: string, trustDevice?: boolean) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return { success: false, error: 'Auth not available on server' };
|
||||
const result = await authService.verifyTwoFactor(code, trustDevice);
|
||||
if (result.success) {
|
||||
user = await authService.getUserFromToken();
|
||||
await handleAuthenticated();
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
async verifyBackupCode(code: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return { success: false, error: 'Auth not available on server' };
|
||||
const result = await authService.verifyBackupCode(code);
|
||||
if (result.success) {
|
||||
user = await authService.getUserFromToken();
|
||||
await handleAuthenticated();
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
// Magic Link
|
||||
async sendMagicLink(email: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return { success: false, error: 'Auth not available on server' };
|
||||
return authService.sendMagicLink(email);
|
||||
},
|
||||
|
||||
// Passkeys
|
||||
isPasskeyAvailable(): boolean {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return false;
|
||||
return authService.isPasskeyAvailable();
|
||||
},
|
||||
|
||||
async signInWithPasskey() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return { success: false, error: 'Auth not available on server' };
|
||||
try {
|
||||
const result = await authService.signInWithPasskey();
|
||||
if (!result.success) return { success: false, error: result.error || 'Passkey authentication failed' };
|
||||
user = await authService.getUserFromToken();
|
||||
await handleAuthenticated();
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
||||
}
|
||||
},
|
||||
|
||||
// Sign In
|
||||
async signIn(email: string, password: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return { success: false, error: 'Auth not available on server' };
|
||||
try {
|
||||
const result = await authService.signIn(email, password);
|
||||
if (!result.success) return { success: false, error: result.error || 'Login failed' };
|
||||
user = await authService.getUserFromToken();
|
||||
await handleAuthenticated();
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
||||
}
|
||||
},
|
||||
|
||||
// Sign Up
|
||||
async signUp(email: string, password: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return { success: false, error: 'Auth not available on server', needsVerification: false };
|
||||
try {
|
||||
const sourceAppUrl = browser ? window.location.origin : undefined;
|
||||
const result = await authService.signUp(email, password, sourceAppUrl);
|
||||
if (!result.success) return { success: false, error: result.error || 'Signup failed', needsVerification: false };
|
||||
if (result.needsVerification) return { success: true, needsVerification: true };
|
||||
const signInResult = await this.signIn(email, password);
|
||||
return { ...signInResult, needsVerification: false };
|
||||
} catch (error) {
|
||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error', needsVerification: false };
|
||||
}
|
||||
},
|
||||
|
||||
// Sign Out
|
||||
async signOut() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) { user = null; return; }
|
||||
try {
|
||||
await authService.signOut();
|
||||
user = null;
|
||||
if (config.onSignOut) await config.onSignOut();
|
||||
} catch (error) {
|
||||
console.error('Sign out error:', error);
|
||||
user = null;
|
||||
}
|
||||
},
|
||||
|
||||
// Password Reset
|
||||
async resetPassword(email: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return { success: false, error: 'Auth not available on server' };
|
||||
try {
|
||||
const redirectTo = browser ? window.location.origin : undefined;
|
||||
const result = await authService.forgotPassword(email, redirectTo);
|
||||
if (!result.success) return { success: false, error: result.error || 'Password reset failed' };
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
||||
}
|
||||
},
|
||||
|
||||
async resetPasswordWithToken(token: string, newPassword: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return { success: false, error: 'Auth not available on server' };
|
||||
try {
|
||||
const result = await authService.resetPassword(token, newPassword);
|
||||
if (!result.success) return { success: false, error: result.error || 'Failed to reset password' };
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
||||
}
|
||||
},
|
||||
|
||||
// Token access
|
||||
async getAccessToken() {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return null;
|
||||
return await authService.getAppToken();
|
||||
},
|
||||
|
||||
async getValidToken(): Promise<string | null> {
|
||||
const tokenManager = getTokenManager();
|
||||
if (!tokenManager) return null;
|
||||
return await tokenManager.getValidToken();
|
||||
},
|
||||
|
||||
// Email verification
|
||||
async resendVerificationEmail(email: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) return { success: false, error: 'Auth not available on server' };
|
||||
try {
|
||||
const sourceAppUrl = browser ? window.location.origin : undefined;
|
||||
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
|
||||
if (!result.success) return { success: false, error: result.error || 'Failed to send verification email' };
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type ManaAuthStore = ReturnType<typeof createManaAuthStore>;
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
/**
|
||||
* @manacore/shared-auth-stores
|
||||
*
|
||||
* Svelte 5 auth store factories for the ManaCore monorepo.
|
||||
*
|
||||
* @example ManaCore auth store (recommended)
|
||||
* ```ts
|
||||
* import { createManaAuthStore } from '@manacore/shared-auth-stores';
|
||||
* export const authStore = createManaAuthStore();
|
||||
* ```
|
||||
*
|
||||
* @example Generic auth store with custom adapter
|
||||
* ```ts
|
||||
* import { createAuthStore } from '@manacore/shared-auth-stores';
|
||||
* import { authService } from '$lib/auth';
|
||||
* export const authStore = createAuthStore<AppUser>(authService);
|
||||
* ```
|
||||
*/
|
||||
|
||||
// Factory functions
|
||||
export { createManaAuthStore } from './createManaAuthStore.svelte';
|
||||
export type { ManaAuthStoreConfig, ManaAuthStore } from './createManaAuthStore.svelte';
|
||||
export { createAuthStore } from './createAuthStore.svelte';
|
||||
|
||||
// Types
|
||||
export type {
|
||||
BaseUser,
|
||||
AuthResult,
|
||||
AuthServiceAdapter,
|
||||
AuthStoreState,
|
||||
AuthStoreActions,
|
||||
AuthStore,
|
||||
} from './types';
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
/**
|
||||
* Shared Auth Store Types
|
||||
* Generic types for creating auth stores with custom user types
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base user interface that all app-specific user types must extend
|
||||
*/
|
||||
export interface BaseUser {
|
||||
id: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth operation result with typed user
|
||||
*/
|
||||
export interface AuthResult<TUser extends BaseUser = BaseUser> {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
needsVerification?: boolean;
|
||||
user?: TUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth service interface that auth stores expect
|
||||
* Apps implement this to integrate with their auth backend
|
||||
*/
|
||||
export interface AuthServiceAdapter<TUser extends BaseUser = BaseUser> {
|
||||
/** Check if user is authenticated */
|
||||
isAuthenticated(): Promise<boolean>;
|
||||
|
||||
/** Get user data from stored token/session */
|
||||
getUserFromToken(): Promise<TUser | null>;
|
||||
|
||||
/** Sign in with email and password */
|
||||
signIn(email: string, password: string): Promise<AuthResult<TUser>>;
|
||||
|
||||
/** Sign up with email and password */
|
||||
signUp(email: string, password: string): Promise<AuthResult<TUser>>;
|
||||
|
||||
/** Send password reset email */
|
||||
forgotPassword(email: string): Promise<{ success: boolean; error?: string }>;
|
||||
|
||||
/** Sign out user */
|
||||
signOut(): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth store state interface
|
||||
*/
|
||||
export interface AuthStoreState<TUser extends BaseUser = BaseUser> {
|
||||
user: TUser | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth store actions interface
|
||||
*/
|
||||
export interface AuthStoreActions<TUser extends BaseUser = BaseUser> {
|
||||
/** Initialize auth state from stored tokens */
|
||||
initialize(): Promise<void>;
|
||||
|
||||
/** Set user manually */
|
||||
setUser(user: TUser | null): void;
|
||||
|
||||
/** Sign in with email and password */
|
||||
signIn(email: string, password: string): Promise<AuthResult<TUser>>;
|
||||
|
||||
/** Sign up with email and password */
|
||||
signUp(email: string, password: string): Promise<AuthResult<TUser>>;
|
||||
|
||||
/** Send password reset email */
|
||||
forgotPassword(email: string): Promise<{ success: boolean; error?: string }>;
|
||||
|
||||
/** Sign out user */
|
||||
signOut(): Promise<void>;
|
||||
|
||||
/** Check authentication status */
|
||||
checkAuth(): Promise<boolean>;
|
||||
|
||||
/** Clear error state */
|
||||
clearError(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete auth store interface
|
||||
*/
|
||||
export interface AuthStore<TUser extends BaseUser = BaseUser>
|
||||
extends AuthStoreState<TUser>,
|
||||
AuthStoreActions<TUser> {}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
"types": ["svelte"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
"name": "@manacore/shared-profile-ui",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"svelte": "./src/index.ts",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"svelte": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
/**
|
||||
* Shared profile UI components for Manacore monorepo
|
||||
*
|
||||
* This package contains Svelte 5 components for displaying
|
||||
* user profile information.
|
||||
*/
|
||||
|
||||
// Pages
|
||||
export { default as ProfilePage } from './ProfilePage.svelte';
|
||||
|
||||
// Types
|
||||
export type { UserProfile, ProfileActions } from './types';
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
"types": ["svelte"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ export {
|
|||
type NavigationItem,
|
||||
type NavigationStore,
|
||||
} from './navigation.svelte';
|
||||
export { createThemeStore, type ThemeStore, type ThemeMode } from './theme.svelte';
|
||||
export {
|
||||
createAppSettingsStore,
|
||||
type AppSettingsStore,
|
||||
|
|
|
|||
|
|
@ -1,125 +0,0 @@
|
|||
/**
|
||||
* Theme Store Factory
|
||||
* Creates a theme state store with Svelte 5 runes.
|
||||
*/
|
||||
|
||||
export type ThemeMode = 'light' | 'dark' | 'system';
|
||||
|
||||
export interface ThemeStore {
|
||||
readonly isDark: boolean;
|
||||
readonly mode: ThemeMode;
|
||||
readonly variant: string;
|
||||
initialize: () => () => void;
|
||||
setMode: (mode: ThemeMode) => void;
|
||||
setVariant: (variant: string) => void;
|
||||
toggle: () => void;
|
||||
}
|
||||
|
||||
export interface ThemeStoreConfig {
|
||||
/** Storage key prefix (default: 'theme') */
|
||||
storagePrefix?: string;
|
||||
/** Default theme mode */
|
||||
defaultMode?: ThemeMode;
|
||||
/** Default theme variant */
|
||||
defaultVariant?: string;
|
||||
/** CSS class to add/remove for dark mode */
|
||||
darkClass?: string;
|
||||
/** Data attribute for variant */
|
||||
variantAttribute?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a theme store with Svelte 5 runes.
|
||||
*/
|
||||
export function createThemeStore(config: ThemeStoreConfig = {}): ThemeStore {
|
||||
const {
|
||||
storagePrefix = 'theme',
|
||||
defaultMode = 'system',
|
||||
defaultVariant = 'default',
|
||||
darkClass = 'dark',
|
||||
variantAttribute = 'data-theme',
|
||||
} = config;
|
||||
|
||||
let isDark = $state(false);
|
||||
let mode = $state<ThemeMode>(defaultMode);
|
||||
let variant = $state(defaultVariant);
|
||||
|
||||
function updateTheme() {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
let shouldBeDark = false;
|
||||
if (mode === 'dark') {
|
||||
shouldBeDark = true;
|
||||
} else if (mode === 'system') {
|
||||
shouldBeDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
|
||||
isDark = shouldBeDark;
|
||||
document.documentElement.classList.toggle(darkClass, isDark);
|
||||
}
|
||||
|
||||
function initialize(): () => void {
|
||||
if (typeof window === 'undefined') return () => {};
|
||||
|
||||
// Load from localStorage
|
||||
const savedMode = localStorage.getItem(`${storagePrefix}-mode`) as ThemeMode | null;
|
||||
const savedVariant = localStorage.getItem(`${storagePrefix}-variant`);
|
||||
|
||||
if (savedMode) mode = savedMode;
|
||||
if (savedVariant) {
|
||||
variant = savedVariant;
|
||||
document.documentElement.setAttribute(variantAttribute, variant);
|
||||
}
|
||||
|
||||
updateTheme();
|
||||
|
||||
// Listen for system theme changes
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const handleChange = () => {
|
||||
if (mode === 'system') {
|
||||
updateTheme();
|
||||
}
|
||||
};
|
||||
|
||||
mediaQuery.addEventListener('change', handleChange);
|
||||
return () => mediaQuery.removeEventListener('change', handleChange);
|
||||
}
|
||||
|
||||
function setMode(newMode: ThemeMode) {
|
||||
mode = newMode;
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem(`${storagePrefix}-mode`, newMode);
|
||||
}
|
||||
updateTheme();
|
||||
}
|
||||
|
||||
function setVariant(newVariant: string) {
|
||||
variant = newVariant;
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem(`${storagePrefix}-variant`, newVariant);
|
||||
}
|
||||
if (typeof document !== 'undefined') {
|
||||
document.documentElement.setAttribute(variantAttribute, newVariant);
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
setMode(isDark ? 'light' : 'dark');
|
||||
}
|
||||
|
||||
return {
|
||||
get isDark() {
|
||||
return isDark;
|
||||
},
|
||||
get mode() {
|
||||
return mode;
|
||||
},
|
||||
get variant() {
|
||||
return variant;
|
||||
},
|
||||
initialize,
|
||||
setMode,
|
||||
setVariant,
|
||||
toggle,
|
||||
};
|
||||
}
|
||||
|
|
@ -188,6 +188,25 @@ export type { QuickInputItem, QuickAction, CreatePreview, InputBarSettings } fro
|
|||
// Pages
|
||||
export { default as AppsPage } from './pages/AppsPage.svelte';
|
||||
export { default as OfflinePage } from './pages/OfflinePage.svelte';
|
||||
export { default as ProfilePage } from './pages/ProfilePage.svelte';
|
||||
export type { UserProfile, ProfileActions } from './pages/profile-types';
|
||||
|
||||
// Onboarding
|
||||
export { createAppOnboardingStore } from './onboarding/create-app-onboarding.svelte';
|
||||
export { default as MiniOnboardingModal } from './onboarding/MiniOnboardingModal.svelte';
|
||||
export type {
|
||||
AppOnboardingOption,
|
||||
AppOnboardingStepType,
|
||||
AppOnboardingStepBase,
|
||||
AppOnboardingSelectStep,
|
||||
AppOnboardingToggleStep,
|
||||
AppOnboardingInfoStep,
|
||||
AppOnboardingStep,
|
||||
AppOnboardingConfig,
|
||||
AppOnboardingPreferences,
|
||||
AppOnboardingStore,
|
||||
MiniOnboardingModalProps,
|
||||
} from './onboarding/types';
|
||||
|
||||
// Charts - Statistics Visualization
|
||||
export {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import type { UserProfile, ProfileActions } from './types';
|
||||
import type { UserProfile, ProfileActions } from './profile-types';
|
||||
|
||||
interface Props {
|
||||
/** User profile data */
|
||||
Loading…
Add table
Add a link
Reference in a new issue