diff --git a/mobile/app/(auth)/login.tsx b/mobile/app/(auth)/login.tsx index 02109ce..e2fb9dd 100644 --- a/mobile/app/(auth)/login.tsx +++ b/mobile/app/(auth)/login.tsx @@ -9,7 +9,7 @@ import { type LoginFormValues, loginSchema } from "../../src/validation/auth" export default function LoginScreen() { const router = useRouter() - const setToken = useAuthStore((state) => state.setToken) + const login = useAuthStore((state) => state.login) const { control, @@ -26,7 +26,14 @@ export default function LoginScreen() { const onSubmit = async ({ email }: LoginFormValues) => { const token = `session_${email.toLowerCase()}` - setToken(token) + await login({ + token, + user: { + id: "660000000000000000000001", + email, + role: "user", + }, + }) router.replace("/(tabs)/discover") } diff --git a/mobile/app/(auth)/register.tsx b/mobile/app/(auth)/register.tsx index e962011..a6d3e0f 100644 --- a/mobile/app/(auth)/register.tsx +++ b/mobile/app/(auth)/register.tsx @@ -9,7 +9,7 @@ import { type RegisterFormValues, registerSchema } from "../../src/validation/au export default function RegisterScreen() { const router = useRouter() - const setToken = useAuthStore((state) => state.setToken) + const login = useAuthStore((state) => state.login) const { control, @@ -28,7 +28,14 @@ export default function RegisterScreen() { const onSubmit = async ({ email }: RegisterFormValues) => { const token = `session_${email.toLowerCase()}` - setToken(token) + await login({ + token, + user: { + id: "660000000000000000000001", + email, + role: "user", + }, + }) router.replace("/(tabs)/discover") } diff --git a/mobile/app/(tabs)/profile.tsx b/mobile/app/(tabs)/profile.tsx index 915d3a6..ff25fa5 100644 --- a/mobile/app/(tabs)/profile.tsx +++ b/mobile/app/(tabs)/profile.tsx @@ -4,18 +4,22 @@ import { useAuthStore } from "../../src/store/useAuthStore" export default function ProfileScreen() { const router = useRouter() + const user = useAuthStore((state) => state.user) + const token = useAuthStore((state) => state.token) const logout = useAuthStore((state) => state.logout) - const onLogout = () => { - logout() + const onLogout = async () => { + await logout() router.replace("/(auth)/login") } return ( User Profile & Wallet. + {user?.email ?? "No user loaded"} + {token ? "Token hydrated" : "No token found"} void onLogout()} style={{ backgroundColor: "#E53935", borderRadius: 10, paddingHorizontal: 14, paddingVertical: 10 }} > Logout diff --git a/mobile/app/_layout.tsx b/mobile/app/_layout.tsx index 1d30f6b..3503b09 100644 --- a/mobile/app/_layout.tsx +++ b/mobile/app/_layout.tsx @@ -1,7 +1,27 @@ +import { useEffect } from "react" import { Stack } from "expo-router" +import { ActivityIndicator, View } from "react-native" import { GestureHandlerRootView } from "react-native-gesture-handler" +import { useAuthStore } from "../src/store/useAuthStore" export default function RootLayout() { + const hydrated = useAuthStore((state) => state.hydrated) + const hydrate = useAuthStore((state) => state.hydrate) + + useEffect(() => { + void hydrate() + }, [hydrate]) + + if (!hydrated) { + return ( + + + + + + ) + } + return ( diff --git a/mobile/app/index.tsx b/mobile/app/index.tsx index 5c2d44c..d9bb982 100644 --- a/mobile/app/index.tsx +++ b/mobile/app/index.tsx @@ -2,7 +2,12 @@ import { Redirect } from "expo-router" import { useAuthStore } from "../src/store/useAuthStore" export default function IndexScreen() { + const hydrated = useAuthStore((state) => state.hydrated) const token = useAuthStore((state) => state.token) + if (!hydrated) { + return null + } + return } diff --git a/mobile/src/store/useAuthStore.ts b/mobile/src/store/useAuthStore.ts index 4b39b85..69306dd 100644 --- a/mobile/src/store/useAuthStore.ts +++ b/mobile/src/store/useAuthStore.ts @@ -1,13 +1,80 @@ +import * as SecureStore from "expo-secure-store" import { create } from "zustand" +const TOKEN_KEY = "discoverly.auth.token" +const USER_KEY = "discoverly.auth.user" + +export type AuthUser = { + id: string + email: string + role: "user" | "restaurant" | "admin" +} + type AuthState = { token: string | null + user: AuthUser | null + hydrated: boolean + hydrate: () => Promise + login: (payload: { token: string; user: AuthUser }) => Promise setToken: (token: string | null) => void - logout: () => void + logout: () => Promise } export const useAuthStore = create((set) => ({ token: null, - setToken: (token) => set({ token }), - logout: () => set({ token: null }), + user: null, + hydrated: false, + hydrate: async () => { + try { + const [token, userRaw] = await Promise.all([ + SecureStore.getItemAsync(TOKEN_KEY), + SecureStore.getItemAsync(USER_KEY), + ]) + + let user: AuthUser | null = null + if (userRaw) { + try { + user = JSON.parse(userRaw) as AuthUser + } catch { + user = null + } + } + + set({ + token: token ?? null, + user, + hydrated: true, + }) + } catch { + set({ + token: null, + user: null, + hydrated: true, + }) + } + }, + login: async ({ token, user }) => { + await Promise.all([ + SecureStore.setItemAsync(TOKEN_KEY, token), + SecureStore.setItemAsync(USER_KEY, JSON.stringify(user)), + ]) + set({ + token, + user, + }) + }, + setToken: (token) => { + void (async () => { + if (token) { + await SecureStore.setItemAsync(TOKEN_KEY, token) + } else { + await SecureStore.deleteItemAsync(TOKEN_KEY) + } + })() + set({ token }) + }, + logout: async () => { + await Promise.all([SecureStore.deleteItemAsync(TOKEN_KEY), SecureStore.deleteItemAsync(USER_KEY)]) + set({ token: null, user: null }) + }, })) diff --git a/mobile/src/store/useCartStore.ts b/mobile/src/store/useCartStore.ts index fdf4d85..3be453e 100644 --- a/mobile/src/store/useCartStore.ts +++ b/mobile/src/store/useCartStore.ts @@ -4,6 +4,9 @@ export type CartItem = { id: string name: string price: number + restaurantName?: string + imageUrl?: string + quantity?: number } export type CartState = { @@ -15,7 +18,19 @@ export type CartState = { export const useCartStore = create((set) => ({ items: [], - addItem: (item) => set((state) => ({ items: [...state.items, item] })), + addItem: (item) => + set((state) => { + const existing = state.items.find((entry) => entry.id === item.id) + if (!existing) { + return { items: [...state.items, { ...item, quantity: item.quantity ?? 1 }] } + } + + return { + items: state.items.map((entry) => + entry.id === item.id ? { ...entry, quantity: (entry.quantity ?? 1) + 1 } : entry, + ), + } + }), removeItem: (id) => set((state) => ({ items: state.items.filter((item) => item.id !== id) })), clear: () => set({ items: [] }), }))