feat: add business brand assets and health checks

This commit is contained in:
2026-08-17 20:33:04 -04:00
parent 5c9fbe6dc2
commit 7be4bb2abe
36 changed files with 1215 additions and 249 deletions
+79 -36
View File
@@ -1,12 +1,6 @@
import { router } from "expo-router";
import { useState } from "react";
import {
Alert,
ScrollView,
StyleSheet,
Text,
View,
} from "react-native";
import { Alert, ScrollView, StyleSheet, Text, View } from "react-native";
import { AppBackground } from "@/components/AppBackground";
import { FilterChip } from "@/components/FilterChip";
@@ -24,6 +18,10 @@ import { formatCurrency } from "@/lib/format";
import type { ThemeColors } from "@/lib/theme-palette";
import { useThemedStyles } from "@/lib/use-themed-styles";
import { api } from "@/lib/trpc";
import {
BusinessBrandImage,
hasMobileBusinessBrandAsset,
} from "@/components/businesses/BusinessBrandImage";
type EntityTab = "clients" | "businesses";
@@ -48,7 +46,8 @@ export default function EntitiesScreen() {
const activeQuery = tab === "clients" ? clientsQuery : businessesQuery;
const isLoading =
clientsQuery.isLoading || (tab === "businesses" && businessesQuery.isLoading);
clientsQuery.isLoading ||
(tab === "businesses" && businessesQuery.isLoading);
if (isLoading) {
return <LoadingScreen message="Loading…" />;
@@ -71,21 +70,27 @@ export default function EntitiesScreen() {
const businesses = businessesQuery.data ?? [];
function refresh() {
return tab === "clients" ? clientsQuery.refetch() : businessesQuery.refetch();
return tab === "clients"
? clientsQuery.refetch()
: 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 });
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 (
@@ -99,10 +104,7 @@ export default function EntitiesScreen() {
/>
}
refreshControl={
<PullToRefresh
onRefresh={refresh}
tintColor={colors.primary}
/>
<PullToRefresh onRefresh={refresh} tintColor={colors.primary} />
}
>
<ScrollView
@@ -141,7 +143,10 @@ export default function EntitiesScreen() {
icon: "create-outline",
color: "#fff",
backgroundColor: colors.primary,
onPress: () => router.push(`/(app)/entities/clients/edit/${client.id}`),
onPress: () =>
router.push(
`/(app)/entities/clients/edit/${client.id}`,
),
},
{
key: "delete",
@@ -152,7 +157,9 @@ export default function EntitiesScreen() {
onPress: () => confirmDelete(client.id, client.name),
},
]}
onPress={() => router.push(`/(app)/entities/clients/${client.id}`)}
onPress={() =>
router.push(`/(app)/entities/clients/${client.id}`)
}
>
<GlassSurface style={styles.card}>
<View style={styles.cardInner}>
@@ -162,7 +169,10 @@ export default function EntitiesScreen() {
) : null}
{client.defaultHourlyRate != null ? (
<Text style={styles.meta}>
{formatCurrency(client.defaultHourlyRate, client.currency ?? "USD")}
{formatCurrency(
client.defaultHourlyRate,
client.currency ?? "USD",
)}
/hr
</Text>
) : null}
@@ -190,7 +200,10 @@ export default function EntitiesScreen() {
icon: "create-outline",
color: "#fff",
backgroundColor: colors.primary,
onPress: () => router.push(`/(app)/entities/businesses/edit/${business.id}`),
onPress: () =>
router.push(
`/(app)/entities/businesses/edit/${business.id}`,
),
},
{
key: "delete",
@@ -201,20 +214,35 @@ export default function EntitiesScreen() {
onPress: () => confirmDelete(business.id, business.name),
},
]}
onPress={() => router.push(`/(app)/entities/businesses/${business.id}`)}
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>
<View style={styles.businessRow}>
{hasMobileBusinessBrandAsset(business) ? (
<BusinessBrandImage
business={business}
kind="icon"
style={styles.businessIcon}
/>
) : null}
<View style={styles.businessCopy}>
<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>
</View>
{business.nickname ? (
<Text style={styles.meta}>{business.nickname}</Text>
) : null}
{business.email ? <Text style={styles.meta}>{business.email}</Text> : null}
</View>
</GlassSurface>
</SwipeableRow>
@@ -257,6 +285,21 @@ const createEntitiesStyles = (colors: ThemeColors, isDark: boolean) =>
gap: spacing.sm,
flexWrap: "wrap",
},
businessRow: {
flexDirection: "row",
alignItems: "center",
gap: spacing.md,
},
businessCopy: {
flex: 1,
minWidth: 0,
gap: 4,
},
businessIcon: {
width: 44,
height: 44,
borderRadius: 10,
},
name: {
fontSize: 16,
fontFamily: fonts.bodySemiBold,