@@ -237,193 +237,6 @@ export default function PlusScreen() {
237237
238238## 👽 Bonus
239239
240- ### Add the Bottom Tab Navigator
241-
242- - [ ] Create `app/(app)/(tabs)/_layout.tsx` for the Spacecraft tabs:
243-
244- ```javascript
245- // app/(app)/(tabs)/_layout.tsx
246- import { Tabs } from "expo-router";
247- import { useTheme } from "react-native-paper";
248- import { Ionicons } from "@expo/vector-icons";
249-
250- export default function TabLayout() {
251- const theme = useTheme();
252-
253- return (
254- <Tabs
255- screenOptions={{
256- headerShown: false,
257- tabBarActiveTintColor: theme.colors.primary,
258- }}
259- >
260- <Tabs.Screen
261- name="starships"
262- options={{
263- title: "Starships",
264- tabBarIcon: ({ color }) => (
265- <Ionicons name="rocket" size={24} color={color} />
266- ),
267- }}
268- />
269- <Tabs.Screen
270- name="pilots"
271- options={{
272- title: "Pilots",
273- tabBarIcon: ({ color }) => (
274- <Ionicons name="people" size={24} color={color} />
275- ),
276- }}
277- />
278- <Tabs.Screen
279- name="planets"
280- options={{
281- title: "Planets",
282- tabBarIcon: ({ color }) => (
283- <Ionicons name="planet" size={24} color={color} />
284- ),
285- }}
286- />
287- <Tabs.Screen
288- name="plus"
289- options={{
290- title: "Plus",
291- tabBarIcon: ({ color }) => (
292- <Ionicons name="add-circle" size={24} color={color} />
293- ),
294- }}
295- />
296- </Tabs>
297- );
298- }
299- ```
300-
301- ### Nested stack for each tab
302-
303- - [ ] Create a nested layout for the starships tab with details modal:
304-
305- ```javascript
306- // app/(app)/(tabs)/starships/_layout.tsx
307- import { Stack } from "expo-router";
308-
309- export default function StarshipsLayout() {
310- return (
311- <Stack>
312- <Stack.Screen
313- name="index"
314- options={{ title: "Starships" }}
315- />
316- <Stack.Screen
317- name="[id]"
318- options={{
319- presentation: "modal",
320- title: "Starship Details",
321- }}
322- />
323- </Stack>
324- );
325- }
326- ```
327-
328- ### Persist authentication with expo-secure-store
329-
330- - [ ] Install `expo-secure-store` and persist the user token:
331-
332- ```bash
333- npx expo install expo-secure-store
334- ```
335-
336- ```javascript
337- // src/context/AuthContext.tsx
338- import * as SecureStore from "expo-secure-store";
339- import { useEffect } from "react";
340-
341- export function AuthProvider({ children }: { children: ReactNode }) {
342- const [user, setUser] = useState<boolean | null>(null); // null = loading
343-
344- const signIn = async () => {
345- await SecureStore.setItemAsync("userToken", "authenticated");
346- setUser(true);
347- };
348-
349- const signOut = async () => {
350- await SecureStore.deleteItemAsync("userToken");
351- setUser(false);
352- };
353-
354- // Check for existing token on app load
355- useEffect(() => {
356- const checkToken = async () => {
357- const token = await SecureStore.getItemAsync("userToken");
358- setUser(!!token);
359- };
360- checkToken();
361- }, []);
362-
363- // Don't render until we know the auth state
364- if (user === null) {
365- return null; // Or a loading spinner
366- }
367-
368- return (
369- <AuthContext.Provider value={{ user, signIn, signOut }}>
370- {children}
371- </AuthContext.Provider>
372- );
373- }
374- ```
375-
376- ### Add a splash screen during auth check
377-
378- - [ ] Show a splash screen while checking authentication:
379-
380- ```javascript
381- // app/_layout.tsx
382- import { Slot, SplashScreen } from "expo-router";
383- import { useEffect, useState } from "react";
384- import * as SecureStore from "expo-secure-store";
385-
386- // Prevent the splash screen from auto-hiding
387- SplashScreen.preventAutoHideAsync();
388-
389- export default function RootLayout() {
390- const [isReady, setIsReady] = useState(false);
391-
392- useEffect(() => {
393- async function prepare() {
394- try {
395- // Check auth state, load fonts, fetch initial data...
396- await SecureStore.getItemAsync("userToken");
397- } finally {
398- setIsReady(true);
399- SplashScreen.hideAsync();
400- }
401- }
402- prepare();
403- }, []);
404-
405- if (!isReady) {
406- return null;
407- }
408-
409- return (
410- <QueryClientProvider client={queryClient}>
411- <PaperProvider theme={theme}>
412- <AuthProvider>
413- <Slot />
414- </AuthProvider>
415- </PaperProvider>
416- </QueryClientProvider>
417- );
418- }
419- ```
420-
421- ### Test the protected routes
422-
423- - [ ] Verify these scenarios work correctly:
424-
425- 1. **Unauthenticated user** visits `/starships` → redirected to `/`
426- 2. **Unauthenticated user** visits `/pilots` → redirected to `/`
427- 3. **Authenticated user** visits `/` → redirected to `/starships`
428- 4. **Authenticated user** signs out → redirected to `/`
429- 5. **Refresh the app** → stays logged in (with persist)
240+ - [ ] Add a "Remember me" checkbox on the login screen that stores the preference.
241+ - [ ] Create a `/forgot-password` screen in the `(auth)` group with a simple form.
242+ - [ ] Add a loading spinner on the login button while `signIn` is in progress.
0 commit comments