mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 10:26:41 +02:00
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:
parent
725db638ea
commit
ef70a1af0b
198 changed files with 11113 additions and 3656 deletions
81
packages/shared-auth/src/adapters/device.ts
Normal file
81
packages/shared-auth/src/adapters/device.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import type { DeviceManagerAdapter, DeviceInfo } from '../types';
|
||||
|
||||
let deviceAdapter: DeviceManagerAdapter | null = null;
|
||||
|
||||
/**
|
||||
* Set the device manager adapter for the auth service
|
||||
*/
|
||||
export function setDeviceAdapter(adapter: DeviceManagerAdapter): void {
|
||||
deviceAdapter = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current device adapter
|
||||
*/
|
||||
export function getDeviceAdapter(): DeviceManagerAdapter {
|
||||
if (!deviceAdapter) {
|
||||
throw new Error(
|
||||
'Device adapter not initialized. Call setDeviceAdapter() before using auth services.'
|
||||
);
|
||||
}
|
||||
return deviceAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if device adapter is initialized
|
||||
*/
|
||||
export function isDeviceInitialized(): boolean {
|
||||
return deviceAdapter !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a web-based device manager adapter
|
||||
*/
|
||||
export function createWebDeviceAdapter(): DeviceManagerAdapter {
|
||||
// Generate a persistent device ID for web
|
||||
const getOrCreateDeviceId = (): string => {
|
||||
const storageKey = '@manacore/deviceId';
|
||||
let deviceId = localStorage.getItem(storageKey);
|
||||
if (!deviceId) {
|
||||
deviceId = crypto.randomUUID();
|
||||
localStorage.setItem(storageKey, deviceId);
|
||||
}
|
||||
return deviceId;
|
||||
};
|
||||
|
||||
return {
|
||||
async getDeviceInfo(): Promise<DeviceInfo> {
|
||||
const userAgent = navigator.userAgent;
|
||||
let deviceName = 'Web Browser';
|
||||
let deviceType = 'web';
|
||||
|
||||
// Try to extract browser name
|
||||
if (userAgent.includes('Chrome')) {
|
||||
deviceName = 'Chrome Browser';
|
||||
} else if (userAgent.includes('Safari')) {
|
||||
deviceName = 'Safari Browser';
|
||||
} else if (userAgent.includes('Firefox')) {
|
||||
deviceName = 'Firefox Browser';
|
||||
} else if (userAgent.includes('Edge')) {
|
||||
deviceName = 'Edge Browser';
|
||||
}
|
||||
|
||||
// Detect device type
|
||||
if (/Mobi|Android/i.test(userAgent)) {
|
||||
deviceType = 'mobile_web';
|
||||
} else if (/Tablet|iPad/i.test(userAgent)) {
|
||||
deviceType = 'tablet_web';
|
||||
}
|
||||
|
||||
return {
|
||||
deviceId: getOrCreateDeviceId(),
|
||||
deviceName,
|
||||
deviceType,
|
||||
platform: 'web',
|
||||
};
|
||||
},
|
||||
async getStoredDeviceId(): Promise<string | null> {
|
||||
return localStorage.getItem('@manacore/deviceId');
|
||||
},
|
||||
};
|
||||
}
|
||||
55
packages/shared-auth/src/adapters/network.ts
Normal file
55
packages/shared-auth/src/adapters/network.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import type { NetworkAdapter } from '../types';
|
||||
|
||||
let networkAdapter: NetworkAdapter | null = null;
|
||||
|
||||
/**
|
||||
* Set the network adapter for the auth service
|
||||
*/
|
||||
export function setNetworkAdapter(adapter: NetworkAdapter): void {
|
||||
networkAdapter = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current network adapter
|
||||
*/
|
||||
export function getNetworkAdapter(): NetworkAdapter | null {
|
||||
return networkAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if device is connected to the network
|
||||
*/
|
||||
export async function isDeviceConnected(): Promise<boolean> {
|
||||
if (!networkAdapter) {
|
||||
// Default to true if no adapter is set
|
||||
return true;
|
||||
}
|
||||
return networkAdapter.isDeviceConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if device has a stable connection
|
||||
*/
|
||||
export async function hasStableConnection(): Promise<boolean> {
|
||||
if (!networkAdapter || !networkAdapter.hasStableConnection) {
|
||||
// Default to basic connectivity check
|
||||
return isDeviceConnected();
|
||||
}
|
||||
return networkAdapter.hasStableConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a web-based network adapter
|
||||
*/
|
||||
export function createWebNetworkAdapter(): NetworkAdapter {
|
||||
return {
|
||||
async isDeviceConnected(): Promise<boolean> {
|
||||
return navigator.onLine;
|
||||
},
|
||||
async hasStableConnection(): Promise<boolean> {
|
||||
// For web, we just check online status
|
||||
// More sophisticated checks could be added
|
||||
return navigator.onLine;
|
||||
},
|
||||
};
|
||||
}
|
||||
89
packages/shared-auth/src/adapters/storage.ts
Normal file
89
packages/shared-auth/src/adapters/storage.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import type { StorageAdapter } from '../types';
|
||||
|
||||
/**
|
||||
* Storage adapter that must be implemented by the consuming app.
|
||||
*
|
||||
* For React Native (Expo):
|
||||
* - Use expo-secure-store for sensitive data
|
||||
* - Use @react-native-async-storage/async-storage for non-sensitive data
|
||||
*
|
||||
* For Web:
|
||||
* - Use localStorage or sessionStorage
|
||||
* - Consider using encrypted storage for sensitive data
|
||||
*/
|
||||
|
||||
let storageAdapter: StorageAdapter | null = null;
|
||||
|
||||
/**
|
||||
* Set the storage adapter for the auth service
|
||||
*/
|
||||
export function setStorageAdapter(adapter: StorageAdapter): void {
|
||||
storageAdapter = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current storage adapter
|
||||
*/
|
||||
export function getStorageAdapter(): StorageAdapter {
|
||||
if (!storageAdapter) {
|
||||
throw new Error(
|
||||
'Storage adapter not initialized. Call setStorageAdapter() before using auth services.'
|
||||
);
|
||||
}
|
||||
return storageAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if storage adapter is initialized
|
||||
*/
|
||||
export function isStorageInitialized(): boolean {
|
||||
return storageAdapter !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a localStorage-based storage adapter (for web)
|
||||
*/
|
||||
export function createLocalStorageAdapter(): StorageAdapter {
|
||||
return {
|
||||
async getItem<T = string>(key: string): Promise<T | null> {
|
||||
const value = localStorage.getItem(key);
|
||||
if (value === null) return null;
|
||||
try {
|
||||
return JSON.parse(value) as T;
|
||||
} catch {
|
||||
return value as T;
|
||||
}
|
||||
},
|
||||
async setItem(key: string, value: string): Promise<void> {
|
||||
localStorage.setItem(key, typeof value === 'string' ? value : JSON.stringify(value));
|
||||
},
|
||||
async removeItem(key: string): Promise<void> {
|
||||
localStorage.removeItem(key);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an in-memory storage adapter (for testing)
|
||||
*/
|
||||
export function createMemoryStorageAdapter(): StorageAdapter {
|
||||
const storage = new Map<string, string>();
|
||||
|
||||
return {
|
||||
async getItem<T = string>(key: string): Promise<T | null> {
|
||||
const value = storage.get(key);
|
||||
if (value === undefined) return null;
|
||||
try {
|
||||
return JSON.parse(value) as T;
|
||||
} catch {
|
||||
return value as T;
|
||||
}
|
||||
},
|
||||
async setItem(key: string, value: string): Promise<void> {
|
||||
storage.set(key, typeof value === 'string' ? value : JSON.stringify(value));
|
||||
},
|
||||
async removeItem(key: string): Promise<void> {
|
||||
storage.delete(key);
|
||||
},
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue