mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-21 03:06:42 +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
|
|
@ -0,0 +1,69 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { initializeAppleAuth, signInWithApple, waitForAppleAuth } from '../utils/appleAuth';
|
||||
|
||||
interface Props {
|
||||
onError?: (error: Error) => void;
|
||||
}
|
||||
|
||||
let { onError }: Props = $props();
|
||||
|
||||
let isLoading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let sdkLoaded = $state(false);
|
||||
|
||||
async function handleAppleSignIn() {
|
||||
isLoading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
await signInWithApple();
|
||||
} catch (err) {
|
||||
console.error('Error initiating Apple Sign-In:', err);
|
||||
error = err instanceof Error ? err.message : 'Failed to initiate Apple Sign-In';
|
||||
onError?.(err instanceof Error ? err : new Error('Unknown error during Apple Sign-In'));
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await waitForAppleAuth();
|
||||
const initialized = initializeAppleAuth();
|
||||
if (initialized) {
|
||||
sdkLoaded = true;
|
||||
} else {
|
||||
console.warn('Apple Sign-In not configured - hiding button');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading Apple Sign-In:', err);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if sdkLoaded}
|
||||
<div class="space-y-3">
|
||||
{#if error}
|
||||
<div class="rounded-xl bg-red-500/20 border border-red-500/30 p-3 text-sm text-red-500">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
onclick={handleAppleSignIn}
|
||||
disabled={isLoading}
|
||||
class="flex h-14 w-full items-center justify-center gap-2 rounded-xl bg-black border border-gray-800 px-4 font-medium text-white transition-all hover:bg-gray-900 disabled:opacity-50"
|
||||
>
|
||||
{#if isLoading}
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-white border-t-transparent"></div>
|
||||
{:else}
|
||||
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
<span>{isLoading ? 'Signing in...' : 'Continue with Apple'}</span>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { initializeGoogleAuth, renderGoogleButton, waitForGoogleAuth } from '../utils/googleAuth';
|
||||
|
||||
interface Props {
|
||||
onSuccess: (idToken: string) => Promise<void>;
|
||||
onError?: (error: Error) => void;
|
||||
}
|
||||
|
||||
let { onSuccess, onError }: Props = $props();
|
||||
|
||||
let buttonContainer: HTMLDivElement;
|
||||
let isLoading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
async function handleGoogleSignIn(idToken: string) {
|
||||
isLoading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
await onSuccess(idToken);
|
||||
} catch (err) {
|
||||
console.error('Error during Google Sign-In:', err);
|
||||
error = err instanceof Error ? err.message : 'Google Sign-In failed';
|
||||
onError?.(err instanceof Error ? err : new Error('Unknown error during Google Sign-In'));
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await waitForGoogleAuth();
|
||||
initializeGoogleAuth(handleGoogleSignIn);
|
||||
|
||||
if (buttonContainer) {
|
||||
renderGoogleButton(buttonContainer, {
|
||||
type: 'standard',
|
||||
theme: 'outline',
|
||||
size: 'large',
|
||||
text: 'signin_with',
|
||||
shape: 'pill'
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error initializing Google Sign-In:', err);
|
||||
error = 'Failed to load Google Sign-In';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if error}
|
||||
<div class="rounded-xl bg-red-500/20 border border-red-500/30 p-3 text-sm text-red-500 mb-2">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div bind:this={buttonContainer} class="relative w-full google-btn-wrapper" style="min-height: 56px;">
|
||||
{#if isLoading}
|
||||
<div class="absolute inset-0 flex items-center justify-center rounded-xl bg-white/80 dark:bg-black/80 backdrop-blur-sm z-10">
|
||||
<div class="h-6 w-6 animate-spin rounded-full border-2 border-indigo-500 border-t-transparent"></div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(.google-btn-wrapper > div) {
|
||||
width: 100% !important;
|
||||
height: 56px !important;
|
||||
}
|
||||
|
||||
:global(.google-btn-wrapper iframe) {
|
||||
height: 56px !important;
|
||||
border-radius: 0.75rem !important;
|
||||
}
|
||||
</style>
|
||||
30
packages/shared-auth-ui/src/components/Icon.svelte
Normal file
30
packages/shared-auth-ui/src/components/Icon.svelte
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<script lang="ts">
|
||||
import { iconPaths, type IconName } from '../icons/iconPaths';
|
||||
|
||||
interface Props {
|
||||
name: IconName;
|
||||
size?: number;
|
||||
class?: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
let { name, size = 24, class: className = '', color }: Props = $props();
|
||||
|
||||
const path = $derived(iconPaths[name]);
|
||||
</script>
|
||||
|
||||
{#if path}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
fill={color || 'currentColor'}
|
||||
viewBox="0 0 256 256"
|
||||
class={className}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{@html path}
|
||||
</svg>
|
||||
{:else}
|
||||
<span class="text-red-500" title="Icon '{name}' not found">⚠</span>
|
||||
{/if}
|
||||
Loading…
Add table
Add a link
Reference in a new issue