From 2a5f836b869bc54e2934a5a24c4980595e909192 Mon Sep 17 00:00:00 2001 From: byGalax Date: Wed, 13 May 2026 23:58:15 +0200 Subject: [PATCH] feat(mobile): magic-link login screen --- apps/mobile/app/index.tsx | 152 +++++++++++++++++++++++++++++++------- 1 file changed, 127 insertions(+), 25 deletions(-) diff --git a/apps/mobile/app/index.tsx b/apps/mobile/app/index.tsx index 4b6ee8e..9d6c432 100644 --- a/apps/mobile/app/index.tsx +++ b/apps/mobile/app/index.tsx @@ -1,29 +1,105 @@ -import * as Application from 'expo-application'; -import { Link } from 'expo-router'; -import { Pressable, StyleSheet, Text, View } from 'react-native'; +import { auth } from '@chat-app/shared'; +import { Redirect } from 'expo-router'; +import { useState } from 'react'; +import { + ActivityIndicator, + Pressable, + StyleSheet, + Text, + TextInput, + View, +} from 'react-native'; -// Phase 0 landing screen. Just enough to prove the app boots and routes: -// * Netralax brand wordmark (replaces the previous "ChatApp" string). -// * Runtime version from expo-application so we can spot stale builds -// on a TestFlight tester's device at a glance. -// * Placeholder nav link into (app)/chats — exercises Expo Router so a -// routing regression shows up here instead of mid-Phase-1. -// -// Phase 1 replaces this with the actual magic-link login form. +import { useAuth } from '../lib/authContext'; +import { env } from '../lib/env'; +import { supabase } from '../lib/supabase'; +import { colors } from '../theme/colors'; + +// Phase 1 landing — magic-link login. Signup with invite code is not +// part of this MVP (accounts come from desktop / admin). After a +// successful request the user sees a "Check your inbox" state until +// they tap the email link and Expo Router routes the deep link to +// app/auth/callback.tsx. export default function Landing() { - const version = Application.nativeApplicationVersion ?? '0.0.0'; + const { session, loading } = useAuth(); + const [email, setEmail] = useState(''); + const [submitting, setSubmitting] = useState(false); + const [sent, setSent] = useState(false); + const [error, setError] = useState(null); + + if (loading) return null; + if (session) return ; + + async function handleSubmit() { + if (!email.includes('@')) { + setError('Bitte gültige E-Mail eingeben'); + return; + } + setSubmitting(true); + setError(null); + try { + await auth.loginWithMagicLink(supabase, email.trim(), env.authRedirectUrl); + setSent(true); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : 'Login fehlgeschlagen'); + } finally { + setSubmitting(false); + } + } + + if (sent) { + return ( + + Netralax + Check deine Mails + + Wir haben dir einen Anmelde-Link an {email} geschickt. Tipp auf den Link, um + dich anzumelden. + + { + setSent(false); + setEmail(''); + }} + > + Andere E-Mail verwenden + + + ); + } return ( Netralax - Phase 0 — Deployment-Fundament - v{version} + Mit Magic-Link anmelden - - - Weiter (Smoke-Test) - - + + + {error && {error}} + + + {submitting ? ( + + ) : ( + Magic Link senden + )} + ); } @@ -33,17 +109,43 @@ const styles = StyleSheet.create({ flex: 1, alignItems: 'center', justifyContent: 'center', - backgroundColor: '#0b0b0f', + backgroundColor: colors.bg, padding: 24, }, - title: { color: '#fff', fontSize: 32, fontWeight: '700', letterSpacing: 1 }, - subtitle: { color: '#9ca3af', marginTop: 8, marginBottom: 4 }, - version: { color: '#6b7280', fontSize: 12, marginBottom: 32 }, + title: { color: colors.text, fontSize: 32, fontWeight: '700', letterSpacing: 1, marginBottom: 8 }, + subtitle: { color: colors.textMuted, fontSize: 14, marginBottom: 24 }, + help: { + color: colors.textMuted, + fontSize: 14, + textAlign: 'center', + marginHorizontal: 16, + marginBottom: 24, + lineHeight: 20, + }, + input: { + width: '100%', + maxWidth: 360, + backgroundColor: colors.surface, + borderColor: colors.border, + borderWidth: 1, + borderRadius: 12, + color: colors.text, + paddingHorizontal: 16, + paddingVertical: 14, + fontSize: 15, + marginBottom: 12, + }, + error: { color: colors.danger, fontSize: 13, marginBottom: 12 }, button: { - backgroundColor: '#5865f2', + width: '100%', + maxWidth: 360, + backgroundColor: colors.accent, paddingHorizontal: 24, paddingVertical: 14, borderRadius: 12, + alignItems: 'center', }, - buttonText: { color: '#fff', fontWeight: '600', fontSize: 15 }, + buttonGhost: { backgroundColor: 'transparent', borderWidth: 1, borderColor: colors.border }, + buttonDisabled: { opacity: 0.6 }, + buttonText: { color: colors.text, fontWeight: '600', fontSize: 15 }, });