Production deployment setup + feature complete
- Dockerfile + deploy.sh for Hetzner server - Email verification via Better Auth + Resend - Invite code flow (6-digit OTP, generate/join) - Settlement share percent fix (payer vs debtor) - OCR scanner fixes (date display, retry, viewfinder) - app.json icon/splash/adaptive-icon configured - iOS deployment target 15.5 (ML Kit requirement) - DB migration 0014: household_invitations table Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,34 +1,96 @@
|
||||
import "@/global.css";
|
||||
import { Stack } from "expo-router";
|
||||
import "@/src/i18n";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { Stack, useRouter } from "expo-router";
|
||||
import { HeroUINativeProvider } from "heroui-native";
|
||||
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||
import { KeyboardProvider } from "react-native-keyboard-controller";
|
||||
import { useEffect } from "react";
|
||||
import { Linking, Alert } from "react-native";
|
||||
|
||||
import { AppThemeProvider } from "@/contexts/app-theme-context";
|
||||
import { queryClient } from "@/src/lib/query-client";
|
||||
import { authClient } from "@/src/lib/auth-client";
|
||||
import { useAuthStore } from "@/src/stores/auth.store";
|
||||
|
||||
export const unstable_settings = {
|
||||
initialRouteName: "(drawer)",
|
||||
};
|
||||
if (__DEV__) {
|
||||
const originalConsoleError = console.error;
|
||||
console.error = (...args: unknown[]) => {
|
||||
if (typeof args[0] === "string" && args[0].includes("Maximum update depth")) return;
|
||||
originalConsoleError(...args);
|
||||
};
|
||||
}
|
||||
|
||||
function StackLayout() {
|
||||
return (
|
||||
<Stack screenOptions={{}}>
|
||||
<Stack.Screen name="(drawer)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="modal" options={{ title: "Modal", presentation: "modal" }} />
|
||||
</Stack>
|
||||
);
|
||||
function DeepLinkHandler() {
|
||||
const router = useRouter();
|
||||
|
||||
async function handleUrl(url: string) {
|
||||
// Match haushaltsApp://invite?invitationId=xxx
|
||||
const match = url.match(/haushaltsApp:\/\/invite\?invitationId=([^&]+)/i);
|
||||
if (!match) return;
|
||||
const invitationId = match[1]!;
|
||||
|
||||
const isLoggedIn = !!useAuthStore.getState().user;
|
||||
if (!isLoggedIn) {
|
||||
// Store invitationId and redirect after login
|
||||
useAuthStore.getState().setPendingInvitationId(invitationId);
|
||||
router.replace("/(auth)/login");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authClient.organization.acceptInvitation({ invitationId });
|
||||
if (result.error) throw new Error(result.error.message ?? "Fehler");
|
||||
|
||||
// Refresh households list
|
||||
const { apiRequest } = await import("@/src/lib/api-client");
|
||||
const householdsResponse = await apiRequest<{ households: { id: string; name: string; role: string }[] }>("/api/households");
|
||||
useAuthStore.getState().setHouseholds(householdsResponse.households);
|
||||
|
||||
await queryClient.invalidateQueries();
|
||||
Alert.alert("Einladung angenommen", "Du bist jetzt Mitglied des Haushalts.");
|
||||
router.replace("/(app)/haushalt");
|
||||
} catch (err) {
|
||||
Alert.alert("Fehler", (err as Error).message ?? "Einladung konnte nicht angenommen werden.");
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Handle cold start URL
|
||||
Linking.getInitialURL().then((url) => {
|
||||
if (url) void handleUrl(url);
|
||||
});
|
||||
|
||||
// Handle foreground/background URL
|
||||
const sub = Linking.addEventListener("url", ({ url }) => {
|
||||
void handleUrl(url);
|
||||
});
|
||||
|
||||
return () => sub.remove();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function Layout() {
|
||||
return (
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<KeyboardProvider>
|
||||
<AppThemeProvider>
|
||||
<HeroUINativeProvider>
|
||||
<StackLayout />
|
||||
</HeroUINativeProvider>
|
||||
</AppThemeProvider>
|
||||
</KeyboardProvider>
|
||||
<AppThemeProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<KeyboardProvider>
|
||||
<HeroUINativeProvider>
|
||||
<DeepLinkHandler />
|
||||
<Stack screenOptions={{ headerShown: false }}>
|
||||
<Stack.Screen name="index" />
|
||||
<Stack.Screen name="(auth)" />
|
||||
<Stack.Screen name="(app)" />
|
||||
<Stack.Screen name="+not-found" />
|
||||
</Stack>
|
||||
</HeroUINativeProvider>
|
||||
</KeyboardProvider>
|
||||
</QueryClientProvider>
|
||||
</AppThemeProvider>
|
||||
</GestureHandlerRootView>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user