Rebuild as InnungsApp project: replace stadtwerke analysis with full documentation

- PRD: vollständige Produktspezifikation (5 Module, Scope, Akzeptanzkriterien)
- ARCHITECTURE: Tech Stack, Ordnerstruktur, Multi-Tenancy, Push, Kosten
- DATABASE_SCHEMA: Vollständiges SQL-Schema mit RLS Policies und Views
- USER_STORIES: 40+ Stories nach Rolle (Admin, Mitglied, Azubi, Obermeister)
- PERSONAS: 5 detaillierte Nutzerprofile mit Alltag, Zitaten und Erwartungen
- BUSINESS_MODEL: Preistabellen, Unit Economics, Revenue-Projektionen, Distribution
- ROADMAP: 6 Phasen, Sprint-Planung, Meilensteine und KPIs
- COMPETITIVE_ANALYSIS: Wettbewerbsmatrix, USPs, Preispositionierung
- API_DESIGN: Supabase Query Patterns, Edge Functions, Realtime Subscriptions
- ONBOARDING_FLOWS: 7 User Flows von Setup bis Fehlerfall
- GTM_STRATEGY: 3-Phasen-Vertrieb, Outreach-Sequenz, Einwandbehandlung
- AZUBI_MODULE: Video-Feed, 1-Click-Apply, Chat, Berichtsheft, Quiz
- DSGVO_KONZEPT: Rechtsgrundlagen, TOMs, AVV, Minderjährige, Incident Response
- FEATURES_BACKLOG: 72 Features nach MoSCoW + Technische Schulden

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Timo Knuth
2026-02-18 19:03:37 +01:00
parent fc68285cf1
commit fca42db4d2
116 changed files with 9329 additions and 6479 deletions

View File

@@ -0,0 +1,12 @@
import { Stack } from 'expo-router'
export default function AuthLayout() {
return (
<Stack
screenOptions={{
headerShown: false,
animation: 'slide_from_right',
}}
/>
)
}

View File

@@ -0,0 +1,44 @@
import { View, Text, TouchableOpacity } from 'react-native'
import { useRouter, useLocalSearchParams } from 'expo-router'
import { SafeAreaView } from 'react-native-safe-area-context'
export default function CheckEmailScreen() {
const router = useRouter()
const { email } = useLocalSearchParams<{ email: string }>()
return (
<SafeAreaView className="flex-1 bg-white">
<View className="flex-1 justify-center items-center px-6">
{/* Envelope Illustration */}
<View className="w-24 h-24 bg-brand-50 rounded-full items-center justify-center mb-6">
<Text className="text-5xl">📧</Text>
</View>
<Text className="text-2xl font-bold text-gray-900 text-center mb-3">
Schau in dein Postfach
</Text>
<Text className="text-gray-500 text-center leading-6 mb-2">
Wir haben einen Login-Link an
</Text>
<Text className="font-semibold text-gray-900 text-center mb-6">
{email}
</Text>
<Text className="text-gray-500 text-center leading-6">
Klicken Sie auf den Link in der E-Mail, um sich einzuloggen.
Der Link ist 24 Stunden gültig.
</Text>
<View className="mt-10 space-y-3 w-full">
<TouchableOpacity
onPress={() => router.back()}
className="py-3 items-center"
>
<Text className="text-brand-500 font-medium">
Andere E-Mail verwenden
</Text>
</TouchableOpacity>
</View>
</View>
</SafeAreaView>
)
}

View File

@@ -0,0 +1,111 @@
import {
View,
Text,
TextInput,
TouchableOpacity,
KeyboardAvoidingView,
Platform,
ActivityIndicator,
} from 'react-native'
import { useState } from 'react'
import { useRouter } from 'expo-router'
import { authClient } from '@/lib/auth-client'
import { SafeAreaView } from 'react-native-safe-area-context'
export default function LoginScreen() {
const router = useRouter()
const [email, setEmail] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
async function handleSendLink() {
if (!email.trim()) return
setLoading(true)
setError('')
const result = await authClient.signIn.magicLink({
email: email.trim().toLowerCase(),
callbackURL: '/news',
})
setLoading(false)
if (result.error) {
setError(result.error.message ?? 'Ein Fehler ist aufgetreten.')
} else {
router.push({ pathname: '/(auth)/check-email', params: { email } })
}
}
return (
<SafeAreaView className="flex-1 bg-white">
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
className="flex-1"
>
<View className="flex-1 justify-center px-6">
{/* Logo */}
<View className="items-center mb-10">
<View className="w-20 h-20 bg-brand-500 rounded-2xl items-center justify-center mb-4">
<Text className="text-white font-bold text-3xl">I</Text>
</View>
<Text className="text-2xl font-bold text-gray-900">InnungsApp</Text>
<Text className="text-gray-500 mt-1 text-center">
Die digitale Plattform Ihrer Innung
</Text>
</View>
{/* Form */}
<View className="space-y-4">
<View>
<Text className="text-sm font-medium text-gray-700 mb-2">
E-Mail-Adresse
</Text>
<TextInput
className="border border-gray-300 rounded-xl px-4 py-3.5 text-base text-gray-900 bg-gray-50"
placeholder="ihre@email.de"
placeholderTextColor="#9ca3af"
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
value={email}
onChangeText={setEmail}
onSubmitEditing={handleSendLink}
returnKeyType="go"
/>
</View>
{error ? (
<View className="bg-red-50 rounded-xl px-4 py-3">
<Text className="text-red-600 text-sm">{error}</Text>
</View>
) : null}
<TouchableOpacity
onPress={handleSendLink}
disabled={loading || !email.trim()}
className={`rounded-xl py-4 items-center ${
loading || !email.trim() ? 'bg-gray-200' : 'bg-brand-500'
}`}
>
{loading ? (
<ActivityIndicator color="white" />
) : (
<Text
className={`font-semibold text-base ${
loading || !email.trim() ? 'text-gray-400' : 'text-white'
}`}
>
Magic Link senden
</Text>
)}
</TouchableOpacity>
<Text className="text-center text-sm text-gray-400">
Kein Passwort nötig Zugang nur per Admin-Einladung
</Text>
</View>
</View>
</KeyboardAvoidingView>
</SafeAreaView>
)
}