7aacb9a53e
- Extract shared extractError utility (lib/extractError.ts), remove 3 duplicate copies - Export DEFAULT_BASE_URL from PostizContext, remove duplicate in settings - Add 401 interceptor in axios client: fires UnauthorizedHandler in _layout → alert + redirect to Settings - Calendar day items now tappable: tap opens context menu (Copy / Edit / Repost) - Persist sort order (newest/oldest) across sessions via AsyncStorage - Filter chips show post count per status (Queue 3, Error 1, etc.) - Copy text action now shows a brief "Copied" toast + haptic feedback - PostCard: swipe right → Reschedule action (QUEUE posts only, amber color) - Compose: per-network char limit (Twitter 280, Instagram 2200…) with color warning at 90% - Compose: local draft save/restore via AsyncStorage with restore banner on open - Compose: prefillImagePath/prefillImageId params allow Edit/Repost to carry over existing media Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
101 lines
2.6 KiB
TypeScript
101 lines
2.6 KiB
TypeScript
import {
|
|
Inter_400Regular,
|
|
Inter_500Medium,
|
|
Inter_600SemiBold,
|
|
Inter_700Bold,
|
|
useFonts,
|
|
} from "@expo-google-fonts/inter";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { router, Stack } from "expo-router";
|
|
import * as SplashScreen from "expo-splash-screen";
|
|
import React, { useEffect } from "react";
|
|
import { Alert } from "react-native";
|
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
import { KeyboardProvider } from "react-native-keyboard-controller";
|
|
import { SafeAreaProvider } from "react-native-safe-area-context";
|
|
|
|
import { ErrorBoundary } from "@/components/ErrorBoundary";
|
|
import { PostizProvider, usePostiz } from "@/context/PostizContext";
|
|
import { useNotifications } from "@/hooks/useNotifications";
|
|
|
|
SplashScreen.preventAutoHideAsync();
|
|
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: {
|
|
retry: 1,
|
|
staleTime: 30000,
|
|
},
|
|
},
|
|
});
|
|
|
|
function NotificationBootstrap() {
|
|
useNotifications();
|
|
return null;
|
|
}
|
|
|
|
function UnauthorizedHandler() {
|
|
const { unauthorized, clearUnauthorized } = usePostiz();
|
|
useEffect(() => {
|
|
if (!unauthorized) return;
|
|
Alert.alert(
|
|
"API key invalid",
|
|
"Your API key was rejected (401). Update it in Settings.",
|
|
[
|
|
{
|
|
text: "Open Settings",
|
|
onPress: () => {
|
|
clearUnauthorized();
|
|
router.push("/(tabs)/settings");
|
|
},
|
|
},
|
|
{ text: "Dismiss", style: "cancel", onPress: clearUnauthorized },
|
|
]
|
|
);
|
|
}, [unauthorized, clearUnauthorized]);
|
|
return null;
|
|
}
|
|
|
|
function RootLayoutNav() {
|
|
return (
|
|
<Stack screenOptions={{ headerBackTitle: "Back" }}>
|
|
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
|
</Stack>
|
|
);
|
|
}
|
|
|
|
export default function RootLayout() {
|
|
const [fontsLoaded, fontError] = useFonts({
|
|
Inter_400Regular,
|
|
Inter_500Medium,
|
|
Inter_600SemiBold,
|
|
Inter_700Bold,
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (fontsLoaded || fontError) {
|
|
SplashScreen.hideAsync();
|
|
}
|
|
}, [fontsLoaded, fontError]);
|
|
|
|
if (!fontsLoaded && !fontError) return null;
|
|
|
|
return (
|
|
<SafeAreaProvider>
|
|
<ErrorBoundary>
|
|
<QueryClientProvider client={queryClient}>
|
|
<PostizProvider>
|
|
<NotificationBootstrap />
|
|
<UnauthorizedHandler />
|
|
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
<KeyboardProvider>
|
|
<RootLayoutNav />
|
|
</KeyboardProvider>
|
|
</GestureHandlerRootView>
|
|
</PostizProvider>
|
|
</QueryClientProvider>
|
|
</ErrorBoundary>
|
|
</SafeAreaProvider>
|
|
);
|
|
}
|