diff --git a/apps/mobile/build-1787188470851.ipa b/apps/mobile/build-1787188470851.ipa new file mode 100644 index 0000000..ddcfc96 Binary files /dev/null and b/apps/mobile/build-1787188470851.ipa differ diff --git a/apps/mobile/src/app/index.tsx b/apps/mobile/src/app/index.tsx index 7c01cf8..cc5e1a1 100644 --- a/apps/mobile/src/app/index.tsx +++ b/apps/mobile/src/app/index.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; import { router } from "expo-router"; import { ActivityIndicator, View } from "react-native"; -import { api } from "@/lib/api"; -import { getActiveRestaurant, setActiveRestaurant } from "@/lib/active-restaurant"; +import { api, ApiError } from "@/lib/api"; +import { clearActiveRestaurant, getActiveRestaurant, setActiveRestaurant } from "@/lib/active-restaurant"; import { supabase } from "@/lib/supabase"; interface MeRestaurantResponse { @@ -55,6 +55,19 @@ export default function Index() { } } catch (e) { console.warn("Index resolve error:", e); + + // A 401 here means the cached session's token is no longer valid + // server-side (expired refresh token, deleted user, etc.) — that is + // not the same as "no restaurant yet", so send them to login instead + // of silently starting onboarding with a broken session. + if (e instanceof ApiError && e.status === 401) { + await clearActiveRestaurant(); + await supabase.auth.signOut(); + if (!cancelled) { + router.replace("/(auth)/login"); + } + return; + } } if (!cancelled) { diff --git a/apps/mobile/src/lib/api.ts b/apps/mobile/src/lib/api.ts index 2a3e522..eec6c15 100644 --- a/apps/mobile/src/lib/api.ts +++ b/apps/mobile/src/lib/api.ts @@ -2,6 +2,15 @@ import { supabase } from "./supabase"; const API_URL = process.env.EXPO_PUBLIC_API_URL ?? "http://localhost:3001"; +export class ApiError extends Error { + status: number; + + constructor(status: number, message: string) { + super(message); + this.status = status; + } +} + async function authHeaders(): Promise> { const { data } = await supabase.auth.getSession(); const token = data.session?.access_token; @@ -17,7 +26,7 @@ async function request(path: string, options: RequestInit = {}): Promise { if (!res.ok) { const body = await res.json().catch(() => ({ message: res.statusText })); - throw new Error(body.message ?? `Request failed: ${res.status}`); + throw new ApiError(res.status, body.message ?? `Request failed: ${res.status}`); } if (res.status === 204) return undefined as T; diff --git a/apps/mobile/src/lib/urls.ts b/apps/mobile/src/lib/urls.ts index 3205f22..6ad38c5 100644 --- a/apps/mobile/src/lib/urls.ts +++ b/apps/mobile/src/lib/urls.ts @@ -1,15 +1,11 @@ -const WEB_BASE_URL = process.env.EXPO_PUBLIC_WEB_URL || "http://localhost:3000"; +const WEB_BASE_URL = (process.env.EXPO_PUBLIC_WEB_URL || "https://menul.io").replace(/\/+$/, ""); export function getPublicMenuUrl(slug: string): string { - if (WEB_BASE_URL.includes("menul.io")) { - return `https://${slug}.menul.io`; - } return `${WEB_BASE_URL}/menu/${slug}`; } export function getPublicMenuDisplayUrl(slug: string): string { - if (WEB_BASE_URL.includes("menul.io")) { - return `${slug}.menul.io`; - } - return `${WEB_BASE_URL.replace(/^https?:\/\//, "")}/menu/${slug}`; + const displayBase = WEB_BASE_URL.replace(/^https?:\/\//, ""); + return `${displayBase}/menu/${slug}`; } +