Initial commit

This commit is contained in:
2025-10-05 13:55:28 +02:00
commit 68d521677a
767 changed files with 46947 additions and 0 deletions
@@ -0,0 +1,82 @@
import React from "react";
import { BookMarked, Globe, Home, User } from "@tamagui/lucide-icons";
import { Tabs } from "expo-router";
import { useColorScheme } from "react-native";
import { Paragraph } from "tamagui";
export default function TabLayout() {
const colorScheme = useColorScheme();
return (
<Tabs
initialRouteName="articles"
screenOptions={{
headerShown: false,
tabBarShowLabel: true,
tabBarActiveTintColor: "$accent5",
tabBarHideOnKeyboard: true,
tabBarStyle: {
backgroundColor: colorScheme === "dark" ? "black" : "white",
borderTopWidth: 0,
paddingBottom: 5,
paddingTop: 5,
},
tabBarLabelStyle: {
fontSize: 12,
fontWeight: "600",
textTransform: "none",
},
}}
>
<Tabs.Screen
name="articles"
options={{
href: "/(authed)/(tabs)/articles",
tabBarLabel: ({ color }) => (
<Paragraph size="$2" color={color}>
Actualités
</Paragraph>
),
tabBarIcon: ({ color, size }) => <Home size={size} color={color} />,
}}
/>
<Tabs.Screen
name="sources"
options={{
href: "/(authed)/(tabs)/sources",
tabBarLabel: ({ color }) => (
<Paragraph size="$2" color={color}>
Sources
</Paragraph>
),
tabBarIcon: ({ color, size }) => <Globe size={size} color={color} />,
}}
/>
<Tabs.Screen
name="bookmarks"
options={{
href: "/(authed)/(tabs)/bookmarks",
tabBarLabel: ({ color }) => (
<Paragraph size="$2" color={color}>
Signets
</Paragraph>
),
tabBarIcon: ({ color, size }) => <BookMarked size={size} color={color} />,
}}
/>
<Tabs.Screen
name="account"
options={{
href: "/(authed)/(tabs)/account",
tabBarLabel: ({ color }) => (
<Paragraph size="$2" color={color}>
Profil
</Paragraph>
),
tabBarIcon: ({ color, size }) => <User size={size} color={color} />,
}}
/>
</Tabs>
);
}
@@ -0,0 +1,5 @@
import { Stack } from "expo-router";
export default function Layout() {
return <Stack screenOptions={{ headerShown: false }} />;
}
@@ -0,0 +1,30 @@
import { ChevronRight, Settings } from "@tamagui/lucide-icons";
import { useRouter } from "expo-router";
import { Label, ListItem, ScrollView, Separator, YGroup } from "tamagui";
import { ScreenView } from "@/ui/components/layout";
export default function Index() {
const router = useRouter();
return (
<ScreenView>
<ScreenView.Heading title="Profile" />
<ScrollView width="100%">
<Label>Settings</Label>
<YGroup alignSelf="center" bordered size="$4">
<YGroup.Item>
<ListItem
onPress={() => router.push("/account/settings")}
icon={Settings}
iconAfter={ChevronRight}
title="Settings"
/>
</YGroup.Item>
<Separator />
</YGroup>
</ScrollView>
</ScreenView>
);
}
@@ -0,0 +1,35 @@
import { ActivityIndicator } from "react-native";
import { Button, YStack } from "tamagui";
import { useLogout } from "@/api/request/identity-and-access/login";
import { useAuth } from "@/providers/auth-provider";
import { ScreenView } from "@/ui/components/layout";
export default function Index() {
const authState = useAuth();
const { mutate, isPending } = useLogout();
const handleLogout = async () => {
mutate(undefined, {
onSuccess: () => authState.logout(),
onError: () => authState.logout(),
});
};
return (
<ScreenView>
<ScreenView.Heading title="Paramètres" />
<YStack width="100%">
<Button
disabled={isPending}
onPress={handleLogout}
theme={isPending ? "disabled" : "accent"}
fontWeight="bold"
>
{isPending ? <ActivityIndicator /> : "Déconnexion"}
</Button>
</YStack>
</ScreenView>
);
}
@@ -0,0 +1,90 @@
import { Bookmark, MoreVertical, Share } from "@tamagui/lucide-icons";
import { useLocalSearchParams, useRouter } from "expo-router";
import * as WebBrowser from "expo-web-browser";
import Toast from "react-native-toast-message";
import { Button, H5, ScrollView, Separator, XStack, YStack } from "tamagui";
import { useArticleDetails } from "@/api/request/feed-management/article";
import { Article } from "@/api/schema/feed-management/article";
import { safeMessage } from "@/api/shared";
import { useRelativeTime } from "@/hooks/use-relative-time";
import { ArticleCategoryPill, ArticleCoverImage } from "@/ui/components/content/article";
import { SourceReferencePill } from "@/ui/components/content/source";
import { BackButton } from "@/ui/components/controls/BackButton";
import { IconButton } from "@/ui/components/controls/IconButton";
import { ScreenView } from "@/ui/components/layout";
import { LoadingView } from "@/ui/components/LoadingView";
import { Caption, Text } from "@/ui/components/typography";
export default function ArticleDetails() {
const router = useRouter();
const { id } = useLocalSearchParams();
const { data, isLoading, error } = useArticleDetails(id as string);
const article: Article | undefined = data ?? undefined;
const relativeTime = useRelativeTime(article?.publishedAt);
const handleReadIntegrality = async () => {
await WebBrowser.openBrowserAsync(article!.link);
};
if (error) {
Toast.show({
type: "error",
text1: "Erreur",
text2: safeMessage(error),
});
router.replace("/(authed)/(tabs)/articles");
}
if (isLoading || article === undefined) {
return <LoadingView />;
}
return (
<ScreenView>
<ScreenView.Heading
leadingAction={<BackButton onPress={() => router.dismissTo("/(authed)/(tabs)/articles")} />}
trailingActions={
<>
<IconButton onPress={() => {}} icon={<Bookmark size="$1" />} />
<IconButton onPress={() => {}} icon={<Share size="$1" />} />
<IconButton onPress={() => {}} icon={<MoreVertical size="$1" />} />
</>
}
/>
<ScrollView>
<YStack>
{article.metadata?.image && (
<ArticleCoverImage uri={article.metadata.image} width="100%" height={225} marginBottom="$4" />
)}
</YStack>
<YStack gap="$4" backgroundColor="$background">
<XStack gap="$2" flexWrap="wrap">
{article.categories.map((category, index) => (
<ArticleCategoryPill key={index} category={category.toLowerCase()} />
))}
</XStack>
<H5 fontWeight="bold" marginBottom="$1">
{article.title}
</H5>
<YStack gap="$2">
<SourceReferencePill data={article.source} />
<XStack height={20} alignItems="center">
<Caption>{relativeTime}</Caption>
<Separator alignSelf="stretch" vertical marginHorizontal={16} />
<Caption>{article.readingTime} minutes de lecture</Caption>
</XStack>
</YStack>
<Text size="$3" marginTop="$2">
{article.body.trim()}
</Text>
</YStack>
<Button width="100%" onPress={handleReadIntegrality} theme="accent" fontWeight="bold">
Consulter l&#39;article
</Button>
</ScrollView>
</ScreenView>
);
}
@@ -0,0 +1,5 @@
import { Stack } from "expo-router";
export default function Layout() {
return <Stack screenOptions={{ headerShown: false }} />;
}
@@ -0,0 +1,51 @@
import React from "react";
import { ScrollView, YStack } from "tamagui";
import { useArticleOverviewList } from "@/api/request/feed-management/article";
import { useSourceOverviewList } from "@/api/request/feed-management/source";
import { ArticleOverview } from "@/api/schema/feed-management/article";
import { SourceOverview } from "@/api/schema/feed-management/source";
import { useFlattenedItems } from "@/hooks/use-flattened-items";
import { ArticleList, ArticleSkeletonList } from "@/ui/components/content/article";
import { SourceList, SourceSkeletonList } from "@/ui/components/content/source";
import { ScreenView } from "@/ui/components/layout";
import { Heading } from "@/ui/components/typography";
export default function Index() {
const { data: articles, isLoading: articlesLoading } = useArticleOverviewList({ limit: 10 });
const { data: sources, isLoading: sourcesLoading } = useSourceOverviewList();
const articleOverviews: ArticleOverview[] = useFlattenedItems(articles);
const sourcesOverviews: SourceOverview[] = useFlattenedItems(sources);
return (
<ScreenView paddingBottom={0}>
<Heading>Actualités</Heading>
<ScrollView contentContainerStyle={{ paddingBottom: 0 }}>
<YStack gap="$4">
<YStack gap="$2">
<ScreenView.Section title="Tendances" forwardLink="/(authed)/(tabs)/articles/trending" />
{articlesLoading && <ArticleSkeletonList displayMode="card" horizontal={true} />}
{!articlesLoading && (
<ArticleList
data={articleOverviews}
refreshing={articlesLoading}
displayMode="card"
horizontal={true}
/>
)}
</YStack>
<YStack gap="$2">
<ScreenView.Section title="Nos sources" forwardLink="/(authed)/(tabs)/sources" />
{sourcesLoading && <SourceSkeletonList horizontal={true} />}
{!sourcesLoading && (
<SourceList data={sourcesOverviews} refreshing={sourcesLoading} horizontal={true} />
)}
</YStack>
</YStack>
</ScrollView>
</ScreenView>
);
}
@@ -0,0 +1,40 @@
import React from "react";
import { useRouter } from "expo-router";
import { useInfiniteArticleOverviewList } from "@/api/request/feed-management/article";
import { TrendingArticle } from "@/api/schema/feed-management/article";
import { useFlattenedItems } from "@/hooks/use-flattened-items";
import { ArticleList, ArticleSkeletonList } from "@/ui/components/content/article";
import { BackButton } from "@/ui/components/controls/BackButton";
import { ScreenView } from "@/ui/components/layout";
export default function Trending() {
const router = useRouter();
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, refetch } = useInfiniteArticleOverviewList(
{ limit: 20 }
);
const articles: TrendingArticle[] = useFlattenedItems(data);
return (
<ScreenView paddingBottom={0}>
<ScreenView.Heading
leadingAction={<BackButton onPress={() => router.dismissTo("/(authed)/(tabs)/articles")} />}
title="Actualités"
/>
{isLoading && <ArticleSkeletonList displayMode="magazine" />}
{!isLoading && (
<ArticleList
data={articles}
fetchNextPage={fetchNextPage}
hasNextPage={hasNextPage}
isFetchingNextPage={isFetchingNextPage}
refreshing={isLoading}
onRefresh={refetch}
infiniteScroll={true}
/>
)}
</ScreenView>
);
}
@@ -0,0 +1,18 @@
import { useLocalSearchParams } from "expo-router";
import { Paragraph } from "tamagui";
import { ScreenView } from "@/ui/components/layout";
import { Heading } from "@/ui/components/typography";
export default function Details() {
const { id } = useLocalSearchParams();
// const { data, isLoading } = useBookmarkedArticlesList(id as string);
// const articles: BookmarkedArticle[] = useFlattenedItems(data);
return (
<ScreenView>
<Heading>Bookmark Infos</Heading>
<Paragraph>{id}</Paragraph>
</ScreenView>
);
}
@@ -0,0 +1,5 @@
import { Stack } from "expo-router";
export default function Layout() {
return <Stack screenOptions={{ headerShown: false }} />;
}
@@ -0,0 +1,42 @@
import React from "react";
import { Plus, Search } from "@tamagui/lucide-icons";
import { YStack } from "tamagui";
import { useBookmarkList } from "@/api/request/feed-management/bookmark";
import { Bookmark } from "@/api/schema/feed-management/bookmark";
import { useFlattenedItems } from "@/hooks/use-flattened-items";
import { BookmarkList } from "@/ui/components/content/bookmark";
import { IconButton } from "@/ui/components/controls/IconButton";
import { ScreenView } from "@/ui/components/layout";
import { LoadingView } from "@/ui/components/LoadingView";
export default function Index() {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, refetch } = useBookmarkList();
const bookmarks: Bookmark[] = useFlattenedItems(data);
return (
<ScreenView>
<ScreenView.Heading
title="Bookmarks"
leadingAction={<IconButton onPress={() => {}} icon={<Plus size="$1" />} />}
trailingActions={<IconButton onPress={() => {}} icon={<Search size="$1" />} />}
/>
<YStack width="100%">
{isLoading && <LoadingView />}
{!isLoading && (
<BookmarkList
data={bookmarks}
refreshing={isLoading}
onRefresh={refetch}
infiniteScroll={true}
hasNextPage={hasNextPage}
isFetchingNextPage={isFetchingNextPage}
fetchNextPage={fetchNextPage}
/>
)}
</YStack>
</ScreenView>
);
}
@@ -0,0 +1,15 @@
import { useLocalSearchParams } from "expo-router";
import { ScreenView } from "@/ui/components/layout";
import { Heading, Text } from "@/ui/components/typography";
export default function SourceDetails() {
const { name } = useLocalSearchParams();
return (
<ScreenView>
<Heading>Source Details</Heading>
<Text>{name}</Text>
</ScreenView>
);
}
@@ -0,0 +1,5 @@
import { Stack } from "expo-router";
export default function Layout() {
return <Stack screenOptions={{ headerShown: false }} />;
}
@@ -0,0 +1,19 @@
import { useSourceOverviewList } from "@/api/request/feed-management/source";
import { SourceOverview } from "@/api/schema/feed-management/source";
import { useFlattenedItems } from "@/hooks/use-flattened-items";
import { SourceList, SourceSkeletonList } from "@/ui/components/content/source";
import { ScreenView } from "@/ui/components/layout";
export default function Sources() {
const { data, isLoading } = useSourceOverviewList();
const sources: SourceOverview[] = useFlattenedItems(data);
return (
<ScreenView>
<ScreenView.Heading title="Sources" />
{isLoading && <SourceSkeletonList horizontal={false} />}
{!isLoading && <SourceList data={sources} horizontal={false} />}
</ScreenView>
);
}
@@ -0,0 +1,17 @@
import { Redirect, Stack } from "expo-router";
import { useAuth } from "@/providers/auth-provider";
export default function AuthedLayout() {
const auth = useAuth();
if (!auth.isReady) {
return null;
}
if (!auth.isLoggedIn) {
return <Redirect href="/signin" />;
}
return <Stack screenOptions={{ headerShown: false }} />;
}
@@ -0,0 +1,17 @@
import { Redirect, Stack } from "expo-router";
import { useAuth } from "@/providers/auth-provider";
export default function AuthedLayout() {
const auth = useAuth();
if (!auth.isReady) {
return null;
}
if (auth.isLoggedIn) {
return <Redirect href="/(authed)/(tabs)/articles" />;
}
return <Stack screenOptions={{ headerShown: false }} />;
}
@@ -0,0 +1,69 @@
import React from "react";
import { joiResolver } from "@hookform/resolvers/joi";
import { Link, useRouter } from "expo-router";
import { useForm } from "react-hook-form";
import Toast from "react-native-toast-message";
import { YStack } from "tamagui";
import { usePasswordForgotten } from "@/api/request/identity-and-access/password";
import { RequestPasswordPayload, RequestPasswordPayloadSchema } from "@/api/schema/identity-and-access/password";
import { ErrorResponse, safeMessage } from "@/api/shared";
import { FormEmailInput } from "@/ui/components/controls/forms";
import { SubmitButton } from "@/ui/components/controls/SubmitButton";
import { ScreenView } from "@/ui/components/layout";
import { Heading, Text } from "@/ui/components/typography";
export default function PasswordRequest() {
const { mutate, isPending } = usePasswordForgotten();
const router = useRouter();
const { control, handleSubmit, formState } = useForm<RequestPasswordPayload>({
resolver: joiResolver(RequestPasswordPayloadSchema),
});
const onSubmit = (data: RequestPasswordPayload) => {
mutate(data, {
onSuccess: () => {
Toast.show({
text1: "Succès",
text2: "Un mail avec les instructions vous a été envoyé",
type: "success",
});
router.push("/(unauthed)/signin");
},
onError: (error: ErrorResponse) => {
Toast.show({
text1: "Erreur de connexion",
text2: safeMessage(error),
type: "error",
});
},
});
};
return (
<ScreenView>
<YStack flex={1} gap="$4" width="100%" justifyContent="flex-start">
<YStack gap="$4">
<Heading>Mot de passe oublié ?</Heading>
<Text>
Veuillez entrer votre adresse e-mail pour recevoir un lien de réinitialisation de mot de passe.
</Text>
</YStack>
<FormEmailInput control={control} name="email" />
<Link href="/signin" asChild>
<Text>Vous avez pas de compte ? Se connecter</Text>
</Link>
</YStack>
<SubmitButton
label="Réinitialiser le mot de passe"
onPress={handleSubmit(onSubmit)}
isPending={isPending}
isValid={formState.isValid}
/>
</ScreenView>
);
}
@@ -0,0 +1,81 @@
import React from "react";
import { joiResolver } from "@hookform/resolvers/joi";
import { Link, useRouter } from "expo-router";
import { useForm } from "react-hook-form";
import Toast from "react-native-toast-message";
import { YStack } from "tamagui";
import { useLogin } from "@/api/request/identity-and-access/login";
import { LoginPayload, LoginPayloadSchema, LoginResponse } from "@/api/schema/identity-and-access/login";
import { ErrorResponse, safeMessage } from "@/api/shared";
import { useAuth } from "@/providers/auth-provider";
import { FormEmailInput, FormPasswordInput } from "@/ui/components/controls/forms";
import { SubmitButton } from "@/ui/components/controls/SubmitButton";
import { ScreenView } from "@/ui/components/layout";
import { Caption, Heading, Text } from "@/ui/components/typography";
export default function SignIn() {
const { mutate, isPending } = useLogin();
const auth = useAuth();
const router = useRouter();
if (auth.isLoggedIn) {
router.replace("/(authed)/(tabs)/articles");
}
const { control, handleSubmit, formState } = useForm<LoginPayload>({
resolver: joiResolver(LoginPayloadSchema),
});
const onSubmit = (data: LoginPayload) => {
mutate(data, {
onSuccess: async (data: LoginResponse) => {
auth.login(data.token, data.refresh_token);
Toast.show({ text1: "Connexion réussie", type: "success" });
},
onError: (error: ErrorResponse) => {
Toast.show({
text1: "Erreur de connexion",
text2: safeMessage(error),
type: "error",
});
},
});
};
return (
<ScreenView>
<YStack flex={1} gap="$4" width="100%" justifyContent="flex-start">
<YStack gap="$4">
<Heading>Connexion</Heading>
<Text>Bienvenue sur CongoNews, la plateforme d&#39;actualités intelligente</Text>
</YStack>
<YStack gap="$2">
<FormEmailInput control={control} name="username" />
<YStack gap="$2">
<FormPasswordInput control={control} name="password" />
<Link href="/password-request" asChild>
<Text color="$accent6"> Mot de passe oublié ?</Text>
</Link>
</YStack>
</YStack>
<Caption>
En continuant, vous acceptez les conditions d&#39;utilisation de CongoNews et reconnaissez avoir lu
notre politique de confidentialité.
</Caption>
<Link href="/signup" asChild>
<Text>Vous n&#39;avez pas de compte ? Créer un compte</Text>
</Link>
</YStack>
<SubmitButton
label="Se connecter"
isPending={isPending}
isValid={formState.isValid}
onPress={handleSubmit(onSubmit)}
/>
</ScreenView>
);
}
@@ -0,0 +1,81 @@
import React from "react";
import { joiResolver } from "@hookform/resolvers/joi";
import { User } from "@tamagui/lucide-icons";
import { Link, useRouter } from "expo-router";
import { useForm } from "react-hook-form";
import Toast from "react-native-toast-message";
import { YStack } from "tamagui";
import { useRegister } from "@/api/request/identity-and-access/register";
import { RegisterPayload, RegisterPayloadSchema } from "@/api/schema/identity-and-access/register";
import { ErrorResponse, safeMessage } from "@/api/shared";
import { FormEmailInput, FormPasswordInput, FormTextInput } from "@/ui/components/controls/forms";
import { SubmitButton } from "@/ui/components/controls/SubmitButton";
import { ScreenView } from "@/ui/components/layout";
import { Caption, Heading, Text } from "@/ui/components/typography";
export default function SingUp() {
const router = useRouter();
const { mutate, isPending } = useRegister();
const { control, handleSubmit, formState } = useForm<RegisterPayload>({
resolver: joiResolver(RegisterPayloadSchema),
});
const onSubmit = (data: RegisterPayload) => {
mutate(data, {
onSuccess: () => {
Toast.show({
text1: "Félicitations !",
text2: "les détails de votre compte vous ont été envoyés par e-mail.",
type: "success",
});
router.replace("/(unauthed)/signin");
},
onError: (error: ErrorResponse) => {
Toast.show({
text1: "Erreur",
text2: safeMessage(error),
type: "error",
});
},
});
};
return (
<ScreenView>
<YStack flex={1} gap="$4" width="100%" justifyContent="flex-start">
<YStack gap="$4">
<Heading>Inscription</Heading>
<Text>Rejoignez la communauté CongoNews et restez informé des dernières actualités</Text>
</YStack>
<YStack gap="$2">
<FormTextInput
control={control}
name="name"
leadingAdornment={User}
label="Nom complet"
placeholder="John Doe"
/>
<FormEmailInput control={control} name="email" />
<FormPasswordInput control={control} name="password" />
</YStack>
<Caption>
En continuant, vous acceptez les conditions d&#39;utilisation de CongoNews et reconnaissez avoir lu
notre politique de confidentialité.
</Caption>
<Link href="/signin">
<Text>Vous avez un compte ? Connectez-vous</Text>
</Link>
</YStack>
<SubmitButton
label="Créer un compte"
onPress={handleSubmit(onSubmit)}
isPending={isPending}
isValid={formState.isValid}
/>
</ScreenView>
);
}
@@ -0,0 +1,39 @@
import { Link, useRouter } from "expo-router";
import { Button, YStack } from "tamagui";
import { AppIcon } from "@/ui/components/AppIcon";
import { ScreenView } from "@/ui/components/layout";
import { Caption, Display, Text } from "@/ui/components/typography";
export default function Welcome() {
const router = useRouter();
return (
<ScreenView justifyContent="center">
<AppIcon width={120} height={120} />
<YStack width="100%" gap="$6">
<YStack gap="$3">
<Display textAlign="center">Bienvenue sur CongoNews</Display>
<Text textAlign="center" lineHeight="$1" marginTop="auto">
La première plateforme d&#39;actualités intelligente qui vous aide à rester informé sur
congolaise et internationale.
</Text>
</YStack>
<YStack gap="$4">
<Button onPress={() => router.push("/signin")} theme="accent" fontWeight="bold">
Se connecter
</Button>
<Link href="/signup" asChild>
<Text textAlign="center">Ouvrir un compte</Text>
</Link>
</YStack>
<Caption textAlign="center">
En continuant, vous acceptez les conditions d&#39;utilisation de CongoNews et reconnaissez avoir lu
notre politique de confidentialité.
</Caption>
</YStack>
</ScreenView>
);
}
+33
View File
@@ -0,0 +1,33 @@
import { Link, Stack } from "expo-router";
import { View, YStack } from "tamagui";
import { AppIcon } from "@/ui/components/AppIcon";
import { ScreenView } from "@/ui/components/layout";
import { Heading, Text } from "@/ui/components/typography";
export default function NotFoundScreen() {
return (
<ScreenView>
<Stack.Screen options={{ title: "Oops !" }} />
<View flex={1} backgroundColor="$background" padding="$4">
<YStack alignItems="center" justifyContent="center" flex={1} gap="$4">
<AppIcon width={100} height={100} />
<YStack width="100%" gap="$6" alignItems="center" paddingHorizontal="$4">
<YStack>
<Heading fontWeight="bold" lineHeight="$8" textAlign="center">
Une erreur s&#39;est produite
</Heading>
<Text textAlign="center" lineHeight="$1" marginTop="auto">
Nous avons une difficulté à charger la page que vous recherchez.
</Text>
</YStack>
<Link href="/(unauthed)/welcome">
<Text>Recommencer</Text>
</Link>
</YStack>
</YStack>
</View>
</ScreenView>
);
}
+39
View File
@@ -0,0 +1,39 @@
import React from "react";
import * as Sentry from "@sentry/react-native";
import { Stack } from "expo-router";
import { useColorScheme } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import Toast from "react-native-toast-message";
import { Theme } from "tamagui";
import { RootProviders } from "@/providers/root-providers";
export { ErrorBoundary } from "expo-router";
Sentry.init({
dsn: process.env.EXPO_PUBLIC_SENTRY_DSN,
sendDefaultPii: true,
debug: __DEV__,
tracesSampleRate: 1.0,
tracePropagationTargets: [/.*?/],
spotlight: __DEV__,
});
function RootLayout() {
const colorScheme = useColorScheme();
const insets = useSafeAreaInsets();
return (
<React.StrictMode>
<RootProviders>
<Theme name={colorScheme || "dark"}>
<Stack screenOptions={{ headerShown: false }} />
<Toast topOffset={insets.top + 10} position="top" visibilityTime={6_000} />
</Theme>
</RootProviders>
</React.StrictMode>
);
}
export default Sentry.wrap(RootLayout);
+13
View File
@@ -0,0 +1,13 @@
import { Redirect } from "expo-router";
import { useAuth } from "@/providers/auth-provider";
export default function Index() {
const auth = useAuth();
if (!auth.isReady) {
return null;
}
return auth.isLoggedIn ? <Redirect href="/(authed)/(tabs)/articles" /> : <Redirect href="/(unauthed)/welcome" />;
}