355b14faef
Enable App Store builds without EAS, iOS 18 App Intents plugins, and signing fixes for distribution export. Add mobile invoice PDF preview, compact line items, and more reliable shortcut deep-link handling. Co-authored-by: Cursor <cursoragent@cursor.com>
126 lines
3.4 KiB
TypeScript
126 lines
3.4 KiB
TypeScript
import { Stack } from "expo-router";
|
|
import {
|
|
Inter_400Regular,
|
|
Inter_500Medium,
|
|
Inter_600SemiBold,
|
|
Inter_700Bold,
|
|
} from "@expo-google-fonts/inter";
|
|
import {
|
|
PlayfairDisplay_600SemiBold,
|
|
PlayfairDisplay_700Bold,
|
|
} from "@expo-google-fonts/playfair-display";
|
|
import { useFonts } from "expo-font";
|
|
import * as SplashScreen from "expo-splash-screen";
|
|
import { useEffect, type ReactNode } from "react";
|
|
import { View } from "react-native";
|
|
import { StatusBar } from "expo-status-bar";
|
|
import "react-native-reanimated";
|
|
import { SafeAreaProvider } from "react-native-safe-area-context";
|
|
|
|
import { BrandBackground } from "@/components/BrandBackground";
|
|
import { LoadingScreen } from "@/components/LoadingScreen";
|
|
import { SessionSync } from "@/components/SessionSync";
|
|
import { ShortcutLinkCapture } from "@/components/ShortcutLinkCapture";
|
|
import { AccountsProvider, useAccounts } from "@/contexts/AccountsContext";
|
|
import { AuthProvider, useSession } from "@/contexts/AuthContext";
|
|
import { ThemeProvider, useAppTheme } from "@/contexts/ThemeContext";
|
|
import { TRPCProvider } from "@/lib/trpc";
|
|
export { ErrorBoundary } from "expo-router";
|
|
|
|
SplashScreen.preventAutoHideAsync();
|
|
|
|
function AppServices({ children }: { children: ReactNode }) {
|
|
const { apiUrl, authStoragePrefix, activeAccountId } = useAccounts();
|
|
const remountKey = `${activeAccountId ?? "guest"}:${apiUrl}`;
|
|
|
|
return (
|
|
<AuthProvider apiUrl={apiUrl} storagePrefix={authStoragePrefix} key={remountKey}>
|
|
<TRPCProvider apiUrl={apiUrl} key={remountKey}>
|
|
<SessionSync />
|
|
<ShortcutLinkCapture />
|
|
{children}
|
|
</TRPCProvider>
|
|
</AuthProvider>
|
|
);
|
|
}
|
|
|
|
function ThemedChrome({ children }: { children: ReactNode }) {
|
|
const { isDark } = useAppTheme();
|
|
|
|
return (
|
|
<View style={{ flex: 1, backgroundColor: "transparent" }}>
|
|
<BrandBackground />
|
|
<View style={{ flex: 1, zIndex: 1 }}>
|
|
<StatusBar style={isDark ? "light" : "dark"} />
|
|
{children}
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
export default function RootLayout() {
|
|
const [loaded, error] = useFonts({
|
|
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
|
|
Inter_400Regular,
|
|
Inter_500Medium,
|
|
Inter_600SemiBold,
|
|
Inter_700Bold,
|
|
PlayfairDisplay_600SemiBold,
|
|
PlayfairDisplay_700Bold,
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (error) throw error;
|
|
}, [error]);
|
|
|
|
useEffect(() => {
|
|
if (loaded) {
|
|
SplashScreen.hideAsync();
|
|
}
|
|
}, [loaded]);
|
|
|
|
if (!loaded) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<SafeAreaProvider>
|
|
<ThemeProvider>
|
|
<ThemedChrome>
|
|
<AccountsProvider>
|
|
<AppServices>
|
|
<RootNavigator />
|
|
</AppServices>
|
|
</AccountsProvider>
|
|
</ThemedChrome>
|
|
</ThemeProvider>
|
|
</SafeAreaProvider>
|
|
);
|
|
}
|
|
|
|
function RootNavigator() {
|
|
const { data: session, isPending, error } = useSession();
|
|
|
|
if (isPending) {
|
|
return <LoadingScreen message="Checking session…" />;
|
|
}
|
|
|
|
const isAuthenticated = Boolean(session?.user) && !error;
|
|
|
|
return (
|
|
<Stack
|
|
screenOptions={{
|
|
headerShown: false,
|
|
contentStyle: { backgroundColor: "transparent" },
|
|
}}
|
|
>
|
|
<Stack.Protected guard={!isAuthenticated}>
|
|
<Stack.Screen name="(auth)" />
|
|
</Stack.Protected>
|
|
<Stack.Protected guard={isAuthenticated}>
|
|
<Stack.Screen name="(app)" />
|
|
</Stack.Protected>
|
|
</Stack>
|
|
);
|
|
}
|