Stabilize mobile auth session handling

This commit is contained in:
2026-06-29 17:58:50 -04:00
parent 477459edd4
commit 57e1aa658a
25 changed files with 1126 additions and 192 deletions
+94 -32
View File
@@ -1,6 +1,7 @@
import { router } from "expo-router";
import { useState } from "react";
import {
Alert,
Pressable,
RefreshControl,
ScrollView,
@@ -15,6 +16,7 @@ import { FloatingActionButton } from "@/components/FloatingActionButton";
import { GlassSurface } from "@/components/GlassSurface";
import { LoadingScreen } from "@/components/LoadingScreen";
import { PageHeader } from "@/components/PageHeader";
import { SwipeableRow } from "@/components/SwipeableRow";
import { TabPage } from "@/components/TabPage";
import { TabScrollView } from "@/components/TabScrollView";
import { fonts, spacing } from "@/constants/theme";
@@ -38,6 +40,12 @@ export default function EntitiesScreen() {
const clientsQuery = api.clients.getAll.useQuery();
const businessesQuery = api.businesses.getAll.useQuery();
const deleteClient = api.clients.delete.useMutation({
onSuccess: () => void clientsQuery.refetch(),
});
const deleteBusiness = api.businesses.delete.useMutation({
onSuccess: () => void businessesQuery.refetch(),
});
const activeQuery = tab === "clients" ? clientsQuery : businessesQuery;
const isLoading =
@@ -68,6 +76,20 @@ export default function EntitiesScreen() {
else void businessesQuery.refetch();
}
function confirmDelete(id: string, name: string) {
Alert.alert(`Delete ${tab === "clients" ? "client" : "business"}?`, `Remove ${name}?`, [
{ text: "Cancel", style: "cancel" },
{
text: "Delete",
style: "destructive",
onPress: () => {
if (tab === "clients") deleteClient.mutate({ id });
else deleteBusiness.mutate({ id });
},
},
]);
}
return (
<AppBackground>
<TabPage>
@@ -112,25 +134,45 @@ export default function EntitiesScreen() {
</View>
) : (
clients.map((client) => (
<Pressable
<SwipeableRow
key={client.id}
onPress={() => router.push(`/(app)/entities/clients/${client.id}`)}
backgroundColor={colors.cardGlass}
actions={[
{
key: "edit",
label: "Edit",
icon: "create-outline",
color: "#fff",
backgroundColor: colors.primary,
onPress: () => router.push(`/(app)/entities/clients/edit/${client.id}`),
},
{
key: "delete",
label: "Delete",
icon: "trash-outline",
color: "#fff",
backgroundColor: colors.destructive,
onPress: () => confirmDelete(client.id, client.name),
},
]}
>
<GlassSurface style={styles.card}>
<View style={styles.cardInner}>
<Text style={styles.name}>{client.name}</Text>
{client.email ? (
<Text style={styles.meta}>{client.email}</Text>
) : null}
{client.defaultHourlyRate != null ? (
<Text style={styles.meta}>
{formatCurrency(client.defaultHourlyRate, client.currency ?? "USD")}
/hr
</Text>
) : null}
</View>
</GlassSurface>
</Pressable>
<Pressable onPress={() => router.push(`/(app)/entities/clients/${client.id}`)}>
<GlassSurface style={styles.card}>
<View style={styles.cardInner}>
<Text style={styles.name}>{client.name}</Text>
{client.email ? (
<Text style={styles.meta}>{client.email}</Text>
) : null}
{client.defaultHourlyRate != null ? (
<Text style={styles.meta}>
{formatCurrency(client.defaultHourlyRate, client.currency ?? "USD")}
/hr
</Text>
) : null}
</View>
</GlassSurface>
</Pressable>
</SwipeableRow>
))
)
) : businesses.length === 0 ? (
@@ -142,25 +184,45 @@ export default function EntitiesScreen() {
</View>
) : (
businesses.map((business) => (
<Pressable
<SwipeableRow
key={business.id}
onPress={() => router.push(`/(app)/entities/businesses/${business.id}`)}
backgroundColor={colors.cardGlass}
actions={[
{
key: "edit",
label: "Edit",
icon: "create-outline",
color: "#fff",
backgroundColor: colors.primary,
onPress: () => router.push(`/(app)/entities/businesses/edit/${business.id}`),
},
{
key: "delete",
label: "Delete",
icon: "trash-outline",
color: "#fff",
backgroundColor: colors.destructive,
onPress: () => confirmDelete(business.id, business.name),
},
]}
>
<GlassSurface style={styles.card}>
<View style={styles.cardInner}>
<View style={styles.nameRow}>
<Text style={styles.name}>{business.name}</Text>
{business.isDefault ? (
<Text style={styles.badge}>Default</Text>
<Pressable onPress={() => router.push(`/(app)/entities/businesses/${business.id}`)}>
<GlassSurface style={styles.card}>
<View style={styles.cardInner}>
<View style={styles.nameRow}>
<Text style={styles.name}>{business.name}</Text>
{business.isDefault ? (
<Text style={styles.badge}>Default</Text>
) : null}
</View>
{business.nickname ? (
<Text style={styles.meta}>{business.nickname}</Text>
) : null}
{business.email ? <Text style={styles.meta}>{business.email}</Text> : null}
</View>
{business.nickname ? (
<Text style={styles.meta}>{business.nickname}</Text>
) : null}
{business.email ? <Text style={styles.meta}>{business.email}</Text> : null}
</View>
</GlassSurface>
</Pressable>
</GlassSurface>
</Pressable>
</SwipeableRow>
))
)}
</TabScrollView>