From ee5a0b29e91c31df083822f3ee4cb3d561dfba10 Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Sun, 31 May 2026 10:07:55 +0100 Subject: [PATCH 1/3] fix(expo): improve pack selection UX and dynamic API base URL - Rename "Pack Name" header to "Choose a Pack" for clearer intent - Refactor PackSelectionScreen to use FlatList as root; sticky search bar - Move "ADDING" item card (with image) from PackSelectionScreen to AddCatalogItemDetailsScreen - Remove "Change pack" button from AddCatalogItemDetailsScreen pack card - Add edges={['bottom']} to CatalogItemDetailScreen SafeAreaView - Switch apiClient and authClient to getApiBaseUrl() helper --- .../screens/AddCatalogItemDetailsScreen.tsx | 76 ++++--- .../screens/CatalogItemDetailScreen.tsx | 2 +- .../catalog/screens/PackSelectionScreen.tsx | 195 ++++++++---------- apps/expo/lib/api/getBaseUrl.ts | 14 ++ apps/expo/lib/api/packrat.ts | 4 +- apps/expo/lib/auth-client.ts | 4 +- apps/expo/lib/i18n/locales/en.json | 2 +- 7 files changed, 139 insertions(+), 158 deletions(-) create mode 100644 apps/expo/lib/api/getBaseUrl.ts diff --git a/apps/expo/features/catalog/screens/AddCatalogItemDetailsScreen.tsx b/apps/expo/features/catalog/screens/AddCatalogItemDetailsScreen.tsx index cedaffedec..6d6ebf694a 100644 --- a/apps/expo/features/catalog/screens/AddCatalogItemDetailsScreen.tsx +++ b/apps/expo/features/catalog/screens/AddCatalogItemDetailsScreen.tsx @@ -22,6 +22,7 @@ import { View, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; +import { CatalogItemImage } from '../components/CatalogItemImage'; import { useCatalogItemDetails } from '../hooks'; import { cacheCatalogItemImage } from '../lib/cacheCatalogItemImage'; import type { CatalogItem } from '../types'; @@ -140,53 +141,48 @@ export function AddCatalogItemDetailsScreen() { > - - {catalogItem.name} - {catalogItem.description} - - - - - {catalogItem.weight} {catalogItem.weightUnit} + + + + + + {t('catalog.adding')} - - {catalogItem.brand && ( - - - {catalogItem.brand} + + {catalogItem.name} + + + + + {catalogItem.weight} {catalogItem.weightUnit} + + {catalogItem.brand && ( + <> + + {catalogItem.brand} + + )} - )} + {/* Selected Pack */} - - - {t('catalog.selectedPack')} - {pack.name} - - - - {pack.items.length}{' '} - {pack.items.length === 1 ? t('catalog.item') : t('catalog.items')} - - - {pack.category} - - - + {t('catalog.selectedPack')} + {pack.name} + + + + {pack.items.length}{' '} + {pack.items.length === 1 ? t('catalog.item') : t('catalog.items')} + + + {pack.category} diff --git a/apps/expo/features/catalog/screens/CatalogItemDetailScreen.tsx b/apps/expo/features/catalog/screens/CatalogItemDetailScreen.tsx index 2d164b6859..82c09368ff 100644 --- a/apps/expo/features/catalog/screens/CatalogItemDetailScreen.tsx +++ b/apps/expo/features/catalog/screens/CatalogItemDetailScreen.tsx @@ -62,7 +62,7 @@ export function CatalogItemDetailScreen() { } return ( - + + + + {t('catalog.noPacksAvailable')} + + + {t('catalog.createPackMessage')} + + + + ); + return ( - - {catalogItem && ( - - - - - {t('catalog.adding')} - - {catalogItem.name} + + {/* Fixed: search + count label always visible */} + + + {filteredPacks.length > 0 && ( + + {t('catalog.selectPack', { count: filteredPacks.length })} + + )} + + + item.id} + ListEmptyComponent={ + searchQuery.trim() !== '' ? ( + + + {t('catalog.noPacksFound')} - - - - {catalogItem.weight} {catalogItem.weightUnit} - - {catalogItem.brand && ( - <> - - {catalogItem.brand} - - )} - - - - )} - - - - - - - {filteredPacks && filteredPacks.length > 0 ? ( - <> - - {t('catalog.selectPack', { count: filteredPacks.length })} - - item.id} - renderItem={({ item }) => ( - handlePackSelect(item.id)} - activeOpacity={0.7} - > - - - - - {item.name} - - - - - - {item.items?.length}{' '} - {item.items?.length === 1 ? t('catalog.item') : t('catalog.items')} - - - - - - {item.baseWeight?.toFixed(2)} g - - - - - - {item.category} - - - - - + ) : ( + EmptyState + ) + } + renderItem={({ item }) => ( + handlePackSelect(item.id)} + activeOpacity={0.7} + > + + + + + {item.name} + + + + + + {item.items?.length}{' '} + {item.items?.length === 1 ? t('catalog.item') : t('catalog.items')} + + + + + + {item.baseWeight?.toFixed(2)} g + + + + + + {item.category} + - - )} - ListEmptyComponent={ - - - {t('catalog.noPacksFound')} - - } - /> - - ) : ( - - - - {t('catalog.noPacksAvailable')} - - - {t('catalog.createPackMessage')} - - - + + + + )} - + contentContainerStyle={{ paddingBottom: 24 }} + /> ); } diff --git a/apps/expo/lib/api/getBaseUrl.ts b/apps/expo/lib/api/getBaseUrl.ts new file mode 100644 index 0000000000..819e4eb27c --- /dev/null +++ b/apps/expo/lib/api/getBaseUrl.ts @@ -0,0 +1,14 @@ +import { Platform } from 'react-native'; + +/** + * Android emulators route `localhost` to the emulator itself, not the host. + * Rewrite localhost → 10.0.2.2 so dev API calls reach the host machine. + * No-op on iOS and in production (non-localhost URLs are returned unchanged). + */ +export function getApiBaseUrl(): string { + const url = process.env.EXPO_PUBLIC_API_URL ?? ''; + if (Platform.OS === 'android') { + return url.replace(/localhost/g, '10.0.2.2'); + } + return url; +} diff --git a/apps/expo/lib/api/packrat.ts b/apps/expo/lib/api/packrat.ts index 94ae708bc5..cf85382b93 100644 --- a/apps/expo/lib/api/packrat.ts +++ b/apps/expo/lib/api/packrat.ts @@ -1,8 +1,8 @@ import { createApiClient } from '@packrat/api-client'; -import { clientEnvs } from '@packrat/env/expo-client'; import { fromZod } from '@packrat/guards'; import { store } from 'expo-app/atoms/store'; import { needsReauthAtom } from 'expo-app/features/auth/atoms/authAtoms'; +import { getApiBaseUrl } from 'expo-app/lib/api/getBaseUrl'; import { authClient } from 'expo-app/lib/auth-client'; import * as SecureStore from 'expo-secure-store'; import { z } from 'zod'; @@ -27,7 +27,7 @@ function parseSessionToken(cookieJson: string | null): string | null { } export const apiClient = createApiClient({ - baseUrl: clientEnvs.EXPO_PUBLIC_API_URL, + baseUrl: getApiBaseUrl(), auth: { // Read the token from SecureStore — no network call on every API request. getAccessToken: async () => { diff --git a/apps/expo/lib/auth-client.ts b/apps/expo/lib/auth-client.ts index b685923235..f2556aee28 100644 --- a/apps/expo/lib/auth-client.ts +++ b/apps/expo/lib/auth-client.ts @@ -1,10 +1,10 @@ import { expoClient } from '@better-auth/expo/client'; -import { clientEnvs } from '@packrat/env/expo-client'; import { createAuthClient } from 'better-auth/react'; +import { getApiBaseUrl } from 'expo-app/lib/api/getBaseUrl'; import * as SecureStore from 'expo-secure-store'; export const authClient = createAuthClient({ - baseURL: clientEnvs.EXPO_PUBLIC_API_URL, + baseURL: getApiBaseUrl(), plugins: [ expoClient({ scheme: 'packrat', diff --git a/apps/expo/lib/i18n/locales/en.json b/apps/expo/lib/i18n/locales/en.json index dd81640f75..8f960aee78 100644 --- a/apps/expo/lib/i18n/locales/en.json +++ b/apps/expo/lib/i18n/locales/en.json @@ -233,7 +233,7 @@ "createPack": "Create Pack", "editPack": "Edit Pack", "noPacks": "No packs available", - "packName": "Pack Name", + "packName": "Choose a Pack", "packDescription": "Pack Description", "packWeight": "Pack Weight", "packItems": "Pack Items", From 06a506b01ae205f3b2527d46c3959e592d978338 Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Sun, 31 May 2026 10:08:37 +0100 Subject: [PATCH 2/3] fix(expo/api): replace raw regex with split/join in getApiBaseUrl --- apps/expo/lib/api/getBaseUrl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/expo/lib/api/getBaseUrl.ts b/apps/expo/lib/api/getBaseUrl.ts index 819e4eb27c..051cf54772 100644 --- a/apps/expo/lib/api/getBaseUrl.ts +++ b/apps/expo/lib/api/getBaseUrl.ts @@ -8,7 +8,7 @@ import { Platform } from 'react-native'; export function getApiBaseUrl(): string { const url = process.env.EXPO_PUBLIC_API_URL ?? ''; if (Platform.OS === 'android') { - return url.replace(/localhost/g, '10.0.2.2'); + return url.split('localhost').join('10.0.2.2'); } return url; } From 2048cc5ccc6ce9b20c598cd87a424c557cc01e33 Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Sun, 31 May 2026 10:09:12 +0100 Subject: [PATCH 3/3] fix(expo/api): use clientEnvs shim in getApiBaseUrl --- apps/expo/lib/api/getBaseUrl.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/expo/lib/api/getBaseUrl.ts b/apps/expo/lib/api/getBaseUrl.ts index 051cf54772..8196bb0435 100644 --- a/apps/expo/lib/api/getBaseUrl.ts +++ b/apps/expo/lib/api/getBaseUrl.ts @@ -1,12 +1,8 @@ +import { clientEnvs } from '@packrat/env/expo-client'; import { Platform } from 'react-native'; -/** - * Android emulators route `localhost` to the emulator itself, not the host. - * Rewrite localhost → 10.0.2.2 so dev API calls reach the host machine. - * No-op on iOS and in production (non-localhost URLs are returned unchanged). - */ export function getApiBaseUrl(): string { - const url = process.env.EXPO_PUBLIC_API_URL ?? ''; + const url = clientEnvs.EXPO_PUBLIC_API_URL; if (Platform.OS === 'android') { return url.split('localhost').join('10.0.2.2'); }