Commit Message feat: implement comprehensive shared packages architecture for monorepo SUMMARY: Introduce 10 shared packages to unify common code across all 4 web apps, reducing ~3,000 lines of duplicated code and establishing consistent patterns for authentication, UI components, theming, and utilities. NEW SHARED PACKAGES: - @manacore/shared-auth: Unified auth logic (token management, JWT utils, fetch interceptor, storage/device/network adapters) - @manacore/shared-auth-ui: Reusable auth UI (LoginPage, RegisterPage, OAuth buttons for Google/Apple) - @manacore/shared-tailwind: Unified Tailwind config with 4 themes (lume, nature, stone, ocean) and light/dark mode support - @manacore/shared-icons: Phosphor-based icon library (40+ icons) - @manacore/shared-ui: Atomic design system (Text, Button, Badge, Toggle, Input, Modal) - @manacore/shared-i18n: Unified i18n setup with locale detection - @manacore/shared-config: Environment validation with Zod - @manacore/shared-subscriptio n-types: Subscription type definitions - @manacore/shared-subscriptio n-ui: Subscription UI components (planned) EXTENDED PACKAGES: - @manacore/shared-types: Added auth.ts, theme.ts, ui.ts, common.ts - @manacore/shared-utils: Added format.ts, validation.ts APP MIGRATIONS: - memoro/web: Migrated login (549→46 LOC), tailwind (165→12 LOC), removed 15+ duplicate components - manacore/web: Migrated to client-side auth with shared-auth, added new components (Icon, ThemeToggle, Logo) - manadeck/web: Replaced local authService/tokenManager with shared-auth, migrated auth pages - maerchenzauber/web: Added auth setup, stores, components, routes DELETED FILES (migrated to shared packages): - OAuth buttons (Google/Apple) from memoro, manacore, manadeck - Local authService, tokenManager, deviceManager, jwt utils - Duplicate Modal, Toggle, Text components - iconPaths and ManaIcon components - Subscription-related components (CostCard, PackageCard, etc.) BENEFITS: - 92% reduction in login page code - 93% reduction in tailwind config code - Consistent theming across all apps - Single source of truth for auth logic - Easier maintenance and updates BREAKING CHANGES: - Icon imports now from @manacore/shared-icons - Modal imports from @manacore/shared-ui - OAuth config via setGoogleCl ientId()/setAppleConfig()

This commit is contained in:
Till-JS 2025-11-24 21:09:20 +01:00
parent 725db638ea
commit ef70a1af0b
198 changed files with 11113 additions and 3656 deletions

View file

@ -0,0 +1,216 @@
/**
* Apple Sign-In integration for web
* Uses redirect flow (not popup)
*/
// TypeScript definitions for Apple ID SDK
declare global {
interface Window {
AppleID?: {
auth: {
init: (config: AppleIDInitConfig) => void;
signIn: () => Promise<AppleIDSignInResponse>;
};
};
}
}
interface AppleIDInitConfig {
clientId: string;
scope: string;
redirectURI: string;
state?: string;
nonce?: string;
usePopup?: boolean;
responseType?: string;
responseMode?: string;
}
interface AppleIDSignInResponse {
authorization: {
code: string;
id_token?: string;
state?: string;
};
user?: {
email?: string;
name?: {
firstName?: string;
lastName?: string;
};
};
}
export interface AppleAuthorizationResponse {
code: string;
id_token?: string;
state?: string;
user?: string;
}
let appleClientId: string | null = null;
let appleRedirectUri: string | null = null;
/**
* Set Apple Sign-In configuration
*/
export function setAppleConfig(clientId: string, redirectUri: string) {
appleClientId = clientId;
appleRedirectUri = redirectUri;
}
/**
* Check if running in browser
*/
function isBrowser(): boolean {
return typeof window !== 'undefined';
}
/**
* Initialize Apple ID SDK
*/
export function initializeAppleAuth(): boolean {
if (!isBrowser() || !window.AppleID) {
console.warn('Apple ID SDK not loaded');
return false;
}
if (!appleClientId || !appleRedirectUri) {
console.error('Apple Sign-In not configured. Call setAppleConfig() first.');
return false;
}
try {
window.AppleID.auth.init({
clientId: appleClientId,
scope: 'name email',
redirectURI: appleRedirectUri,
state: generateState(),
usePopup: false,
responseType: 'code id_token',
responseMode: 'form_post'
});
console.log('Apple ID SDK initialized successfully');
return true;
} catch (error) {
console.error('Error initializing Apple ID SDK:', error);
return false;
}
}
/**
* Initiate Apple Sign-In (redirect flow)
*/
export async function signInWithApple(): Promise<void> {
if (!isBrowser()) {
throw new Error('Apple Sign-In only available in browser');
}
if (!window.AppleID) {
throw new Error('Apple ID SDK not loaded');
}
try {
const returnTo = window.location.pathname + window.location.search;
sessionStorage.setItem('apple_signin_return_to', returnTo);
await window.AppleID.auth.signIn();
} catch (error) {
console.error('Error initiating Apple Sign-In:', error);
throw error;
}
}
/**
* Parse Apple authorization response from URL
*/
export function parseAppleAuthorizationResponse(
urlParams: URLSearchParams
): AppleAuthorizationResponse | null {
const code = urlParams.get('code');
const id_token = urlParams.get('id_token');
const state = urlParams.get('state');
const user = urlParams.get('user');
const error = urlParams.get('error');
if (error) {
console.error('Apple Sign-In error:', error);
return null;
}
const storedState = sessionStorage.getItem('apple_signin_state');
if (state !== storedState) {
console.error('State mismatch - possible CSRF attack');
return null;
}
if (!id_token && !code) {
console.error('No id_token or authorization code in Apple response');
return null;
}
return {
code: code || '',
id_token: id_token || undefined,
state: state || undefined,
user: user || undefined
};
}
/**
* Generate random state for CSRF protection
*/
function generateState(): string {
const state = Math.random().toString(36).substring(2, 15);
if (isBrowser()) {
sessionStorage.setItem('apple_signin_state', state);
}
return state;
}
/**
* Get stored return URL
*/
export function getStoredReturnUrl(): string {
if (!isBrowser()) return '/dashboard';
return sessionStorage.getItem('apple_signin_return_to') || '/dashboard';
}
/**
* Clear Apple Sign-In session data
*/
export function clearAppleSignInSession() {
if (!isBrowser()) return;
sessionStorage.removeItem('apple_signin_state');
sessionStorage.removeItem('apple_signin_return_to');
}
/**
* Check if Apple ID SDK is loaded
*/
export function isAppleAuthLoaded(): boolean {
return isBrowser() && !!window.AppleID?.auth;
}
/**
* Wait for Apple ID SDK to load
*/
export function waitForAppleAuth(timeout = 10000): Promise<void> {
return new Promise((resolve, reject) => {
if (isAppleAuthLoaded()) {
resolve();
return;
}
const startTime = Date.now();
const interval = setInterval(() => {
if (isAppleAuthLoaded()) {
clearInterval(interval);
resolve();
} else if (Date.now() - startTime > timeout) {
clearInterval(interval);
reject(new Error('Apple ID SDK failed to load'));
}
}, 100);
});
}