From 7aaeb420cf40ac48ce6478cef5683500b484bc97 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 10:24:10 +0000 Subject: [PATCH 01/11] Initial plan From 16196c6e0940dd7a224f7ca06106c0b04ec32aa9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 11:03:52 +0000 Subject: [PATCH 02/11] feat: add edit profile photo and name functionality - Add avatarUrl column to users table with migration - Update API UserSchema and handlers to support avatarUrl - Add useUpdateProfile hook for saving profile changes - Fix name.tsx to use real user data and save via API - Make profile avatar tappable with image picker and upload - Add navigation from name row to name edit screen Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com> --- apps/expo/app/(app)/(tabs)/profile/index.tsx | 74 +++++++++++++++---- apps/expo/app/(app)/(tabs)/profile/name.tsx | 40 +++++++--- .../profile/hooks/useUpdateProfile.ts | 35 +++++++++ apps/expo/features/profile/types.ts | 1 + .../drizzle/0033_add_avatar_url_to_users.sql | 1 + packages/api/drizzle/meta/_journal.json | 9 ++- packages/api/src/db/schema.ts | 1 + packages/api/src/routes/user/index.ts | 5 +- packages/api/src/schemas/users.ts | 8 ++ 9 files changed, 146 insertions(+), 28 deletions(-) create mode 100644 apps/expo/features/profile/hooks/useUpdateProfile.ts create mode 100644 packages/api/drizzle/0033_add_avatar_url_to_users.sql diff --git a/apps/expo/app/(app)/(tabs)/profile/index.tsx b/apps/expo/app/(app)/(tabs)/profile/index.tsx index 3dbb0ca7d7..8bf642e13b 100644 --- a/apps/expo/app/(app)/(tabs)/profile/index.tsx +++ b/apps/expo/app/(app)/(tabs)/profile/index.tsx @@ -1,9 +1,10 @@ import type { AlertRef } from '@packrat/ui/nativewindui'; import { ActivityIndicator, - Alert, + Alert as AlertComponent, Avatar, AvatarFallback, + AvatarImage, Button, ESTIMATED_ITEM_HEIGHT, List, @@ -18,13 +19,17 @@ import { withAuthWall } from 'expo-app/features/auth/hocs'; import { useAuth } from 'expo-app/features/auth/hooks/useAuth'; import { useUser } from 'expo-app/features/auth/hooks/useUser'; import { ProfileAuthWall } from 'expo-app/features/profile/components'; +import { useUpdateProfile } from 'expo-app/features/profile/hooks/useUpdateProfile'; +import { useImagePicker } from 'expo-app/features/packs/hooks/useImagePicker'; +import { uploadImage } from 'expo-app/features/packs/utils/uploadImage'; import { cn } from 'expo-app/lib/cn'; import { hasUnsyncedChanges } from 'expo-app/lib/hasUnsyncedChanges'; import { useTranslation } from 'expo-app/lib/hooks/useTranslation'; -import { Stack } from 'expo-router'; +import { buildPackTemplateItemImageUrl } from 'expo-app/lib/utils/buildPackTemplateItemImageUrl'; +import { router, Stack } from 'expo-router'; import * as Updates from 'expo-updates'; import { useRef, useState } from 'react'; -import { Platform, SafeAreaView, View } from 'react-native'; +import { Alert, Platform, SafeAreaView, TouchableOpacity, View } from 'react-native'; const ESTIMATED_ITEM_SIZE = ESTIMATED_ITEM_HEIGHT[Platform.OS === 'ios' ? 'titleOnly' : 'withSubTitle']; @@ -52,6 +57,7 @@ function Profile() { { id: 'name', title: t('common.name'), + onPress: () => router.push('/(app)/(tabs)/profile/name'), ...(Platform.OS === 'ios' ? { value: displayName } : { subTitle: displayName }), }, { @@ -92,6 +98,7 @@ function Item({ info }: { info: ListRenderItemInfo }) { return ( {!!info.item.value && {info.item.value}} @@ -104,6 +111,11 @@ function Item({ info }: { info: ListRenderItemInfo }) { function ListHeaderComponent() { const user = useUser(); + const { updateProfile } = useUpdateProfile(); + const { pickImage } = useImagePicker(); + const [isUploading, setIsUploading] = useState(false); + const { t } = useTranslation(); + const initials = user?.firstName && user?.lastName ? `${user.firstName[0]}${user.lastName[0]}` @@ -116,21 +128,51 @@ function ListHeaderComponent() { const username = user?.email || ''; + // Build the full avatar URL from the stored R2 key or an absolute URL + const avatarUri = user?.avatarUrl ? buildPackTemplateItemImageUrl(user.avatarUrl) : null; + + async function handleAvatarPress() { + try { + const image = await pickImage(); + if (!image) return; + setIsUploading(true); + const remoteFileName = await uploadImage(image.fileName, image.uri); + if (remoteFileName) { + await updateProfile({ avatarUrl: remoteFileName }); + } + } catch (err) { + if (err instanceof Error && err.message !== 'Permission to access media library was denied') { + Alert.alert(t('errors.somethingWentWrong'), t('errors.tryAgain')); + } + } finally { + setIsUploading(false); + } + } + return ( - - - + + {avatarUri ? ( + + ) : null} + + {isUploading ? ( + + ) : ( + + {initials} + )} - > - {initials} - - - + + + {displayName} {username} @@ -216,7 +258,7 @@ function ListFooterComponent() { {t('auth.logOut')} )} - + ); } diff --git a/apps/expo/app/(app)/(tabs)/profile/name.tsx b/apps/expo/app/(app)/(tabs)/profile/name.tsx index d1fc92a19f..e9620e4a2a 100644 --- a/apps/expo/app/(app)/(tabs)/profile/name.tsx +++ b/apps/expo/app/(app)/(tabs)/profile/name.tsx @@ -1,19 +1,24 @@ import { Button, Form, FormItem, FormSection, Text, TextField } from '@packrat/ui/nativewindui'; import { cn } from 'expo-app/lib/cn'; import { useTranslation } from 'expo-app/lib/hooks/useTranslation'; +import { useUser } from 'expo-app/features/auth/hooks/useUser'; +import { useUpdateProfile } from 'expo-app/features/profile/hooks/useUpdateProfile'; import { router, Stack } from 'expo-router'; import * as React from 'react'; -import { Platform, View } from 'react-native'; +import { Alert, Platform, View } from 'react-native'; import { KeyboardAwareScrollView, KeyboardController } from 'react-native-keyboard-controller'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; export default function NameScreen() { const insets = useSafeAreaInsets(); const { t } = useTranslation(); + const user = useUser(); + const { updateProfile, isLoading } = useUpdateProfile(); + const [form, setForm] = React.useState({ - first: 'Zach', - middle: 'Danger', - last: 'Nugent', + first: user?.firstName || '', + middle: '', + last: user?.lastName || '', }); function onChangeText(type: 'first' | 'middle' | 'last') { @@ -26,11 +31,26 @@ export default function NameScreen() { KeyboardController.setFocusTo('next'); } + const originalFirst = user?.firstName || ''; + const originalLast = user?.lastName || ''; + const canSave = - (form.first !== 'Zach' || form.middle !== 'Danger' || form.last !== 'Nugent') && + (form.first !== originalFirst || form.last !== originalLast) && !!form.first && !!form.last; + async function handleSave() { + const success = await updateProfile({ + firstName: form.first, + lastName: form.last, + }); + if (success) { + router.back(); + } else { + Alert.alert(t('errors.somethingWentWrong'), t('errors.tryAgain')); + } + } + return ( <> ( @@ -106,7 +126,7 @@ export default function NameScreen() { placeholder={t('profile.requiredPlaceholder')} value={form.last} onChangeText={onChangeText('last')} - onSubmitEditing={router.back} + onSubmitEditing={handleSave} enterKeyHint="done" /> @@ -115,8 +135,8 @@ export default function NameScreen() { diff --git a/apps/expo/features/profile/hooks/useUpdateProfile.ts b/apps/expo/features/profile/hooks/useUpdateProfile.ts new file mode 100644 index 0000000000..e6bcf815aa --- /dev/null +++ b/apps/expo/features/profile/hooks/useUpdateProfile.ts @@ -0,0 +1,35 @@ +import { userStore } from 'expo-app/features/auth/store'; +import axiosInstance, { handleApiError } from 'expo-app/lib/api/client'; +import { useState } from 'react'; + +export type UpdateProfilePayload = { + firstName?: string; + lastName?: string; + email?: string; + avatarUrl?: string | null; +}; + +export function useUpdateProfile() { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const updateProfile = async (payload: UpdateProfilePayload): Promise => { + setIsLoading(true); + setError(null); + try { + const response = await axiosInstance.put('/api/user/profile', payload); + if (response.data?.user) { + userStore.set(response.data.user); + } + return true; + } catch (err) { + const { message } = handleApiError(err); + setError(message); + return false; + } finally { + setIsLoading(false); + } + }; + + return { updateProfile, isLoading, error }; +} \ No newline at end of file diff --git a/apps/expo/features/profile/types.ts b/apps/expo/features/profile/types.ts index 84cb690450..16645c2203 100644 --- a/apps/expo/features/profile/types.ts +++ b/apps/expo/features/profile/types.ts @@ -5,6 +5,7 @@ export interface User { email: string; firstName: string; lastName: string; + avatarUrl?: string | null; role: 'USER' | 'ADMIN'; preferredWeightUnit: WeightUnit; } diff --git a/packages/api/drizzle/0033_add_avatar_url_to_users.sql b/packages/api/drizzle/0033_add_avatar_url_to_users.sql new file mode 100644 index 0000000000..240b48c5ef --- /dev/null +++ b/packages/api/drizzle/0033_add_avatar_url_to_users.sql @@ -0,0 +1 @@ +ALTER TABLE "users" ADD COLUMN "avatar_url" text; \ No newline at end of file diff --git a/packages/api/drizzle/meta/_journal.json b/packages/api/drizzle/meta/_journal.json index 2766f37d07..934a0148bb 100644 --- a/packages/api/drizzle/meta/_journal.json +++ b/packages/api/drizzle/meta/_journal.json @@ -239,6 +239,13 @@ "when": 1760175950793, "tag": "0032_curvy_bromley", "breakpoints": true + }, + { + "idx": 33, + "version": "7", + "when": 1741516482000, + "tag": "0033_add_avatar_url_to_users", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/packages/api/src/db/schema.ts b/packages/api/src/db/schema.ts index 297af4e226..268871c591 100644 --- a/packages/api/src/db/schema.ts +++ b/packages/api/src/db/schema.ts @@ -25,6 +25,7 @@ export const users = pgTable('users', { passwordHash: text('password_hash'), firstName: text('first_name'), lastName: text('last_name'), + avatarUrl: text('avatar_url'), role: text('role').default('USER'), // 'USER', 'ADMIN' createdAt: timestamp('created_at').defaultNow(), updatedAt: timestamp('updated_at').defaultNow(), diff --git a/packages/api/src/routes/user/index.ts b/packages/api/src/routes/user/index.ts index acd0ff975d..7340b13a21 100644 --- a/packages/api/src/routes/user/index.ts +++ b/packages/api/src/routes/user/index.ts @@ -63,6 +63,7 @@ userRoutes.openapi(getUserProfileRoute, async (c) => { email: users.email, firstName: users.firstName, lastName: users.lastName, + avatarUrl: users.avatarUrl, role: users.role, emailVerified: users.emailVerified, createdAt: users.createdAt, @@ -165,7 +166,7 @@ userRoutes.openapi(updateUserProfileRoute, async (c) => { try { const auth = c.get('user'); - const { firstName, lastName, email } = c.req.valid('json'); + const { firstName, lastName, email, avatarUrl } = c.req.valid('json'); const db = createDb(c); // If email is being updated, check if it's already in use @@ -191,6 +192,7 @@ userRoutes.openapi(updateUserProfileRoute, async (c) => { if (firstName !== undefined) updateData.firstName = firstName; if (lastName !== undefined) updateData.lastName = lastName; + if (avatarUrl !== undefined) updateData.avatarUrl = avatarUrl; if (email !== undefined) { updateData.email = email.toLowerCase(); updateData.emailVerified = false; // Reset verification if email changes @@ -220,6 +222,7 @@ userRoutes.openapi(updateUserProfileRoute, async (c) => { email: updatedUser.email, firstName: updatedUser.firstName, lastName: updatedUser.lastName, + avatarUrl: updatedUser.avatarUrl, role: updatedUser.role, emailVerified: updatedUser.emailVerified, createdAt: updatedUser.createdAt?.toISOString() || null, diff --git a/packages/api/src/schemas/users.ts b/packages/api/src/schemas/users.ts index 92086577d4..c4f52f421b 100644 --- a/packages/api/src/schemas/users.ts +++ b/packages/api/src/schemas/users.ts @@ -40,6 +40,10 @@ export const UserSchema = z example: '2024-01-15T10:30:00Z', description: 'User account last update timestamp', }), + avatarUrl: z.string().nullable().optional().openapi({ + example: 'https://example.com/avatar.jpg', + description: 'User profile avatar URL', + }), }) .openapi('User'); @@ -66,6 +70,10 @@ export const UpdateUserRequestSchema = z example: 'newemail@example.com', description: 'Updated email address (requires re-verification)', }), + avatarUrl: z.string().nullable().optional().openapi({ + example: 'https://example.com/avatar.jpg', + description: 'Updated profile avatar URL', + }), }) .openapi('UpdateUserRequest'); From d5d8af0820daea82245e7910c896fb0f216d22fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 13:57:51 +0000 Subject: [PATCH 03/11] fix: address CodeRabbit review feedback on profile edit - Remove misleading middle name field (no DB column for it) - Fix silent failure: check updateProfile return value after R2 upload - Fix upload spinner: overlay on TouchableOpacity so it shows over avatar images - Fix canSave baseline: use useRef to capture initial name values at mount - Add 5 MB file size validation before avatar upload - Add profile.imageTooLarge translation key Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com> --- apps/expo/app/(app)/(tabs)/profile/index.tsx | 43 +++++++++++++------- apps/expo/app/(app)/(tabs)/profile/name.tsx | 38 ++++------------- apps/expo/lib/i18n/locales/en.json | 13 +++--- 3 files changed, 44 insertions(+), 50 deletions(-) diff --git a/apps/expo/app/(app)/(tabs)/profile/index.tsx b/apps/expo/app/(app)/(tabs)/profile/index.tsx index 8bf642e13b..e11a022b23 100644 --- a/apps/expo/app/(app)/(tabs)/profile/index.tsx +++ b/apps/expo/app/(app)/(tabs)/profile/index.tsx @@ -26,6 +26,7 @@ import { cn } from 'expo-app/lib/cn'; import { hasUnsyncedChanges } from 'expo-app/lib/hasUnsyncedChanges'; import { useTranslation } from 'expo-app/lib/hooks/useTranslation'; import { buildPackTemplateItemImageUrl } from 'expo-app/lib/utils/buildPackTemplateItemImageUrl'; +import * as FileSystem from 'expo-file-system'; import { router, Stack } from 'expo-router'; import * as Updates from 'expo-updates'; import { useRef, useState } from 'react'; @@ -34,6 +35,8 @@ import { Alert, Platform, SafeAreaView, TouchableOpacity, View } from 'react-nat const ESTIMATED_ITEM_SIZE = ESTIMATED_ITEM_HEIGHT[Platform.OS === 'ios' ? 'titleOnly' : 'withSubTitle']; +const AVATAR_MAX_BYTES = 5 * 1024 * 1024; // 5 MB + function Profile() { const user = useUser(); const { t } = useTranslation(); @@ -135,10 +138,21 @@ function ListHeaderComponent() { try { const image = await pickImage(); if (!image) return; + + // Validate file size before uploading (5 MB limit) + const info = await FileSystem.getInfoAsync(image.uri, { size: true }); + if (info.exists && info.size > AVATAR_MAX_BYTES) { + Alert.alert(t('errors.somethingWentWrong'), t('profile.imageTooLarge')); + return; + } + setIsUploading(true); const remoteFileName = await uploadImage(image.fileName, image.uri); if (remoteFileName) { - await updateProfile({ avatarUrl: remoteFileName }); + const success = await updateProfile({ avatarUrl: remoteFileName }); + if (!success) { + Alert.alert(t('errors.somethingWentWrong'), t('errors.tryAgain')); + } } } catch (err) { if (err instanceof Error && err.message !== 'Permission to access media library was denied') { @@ -157,21 +171,22 @@ function ListHeaderComponent() { ) : null} - {isUploading ? ( - - ) : ( - - {initials} - - )} + + {initials} + + {isUploading && ( + + + + )} {displayName} diff --git a/apps/expo/app/(app)/(tabs)/profile/name.tsx b/apps/expo/app/(app)/(tabs)/profile/name.tsx index e9620e4a2a..b50ef6118f 100644 --- a/apps/expo/app/(app)/(tabs)/profile/name.tsx +++ b/apps/expo/app/(app)/(tabs)/profile/name.tsx @@ -6,7 +6,7 @@ import { useUpdateProfile } from 'expo-app/features/profile/hooks/useUpdateProfi import { router, Stack } from 'expo-router'; import * as React from 'react'; import { Alert, Platform, View } from 'react-native'; -import { KeyboardAwareScrollView, KeyboardController } from 'react-native-keyboard-controller'; +import { KeyboardAwareScrollView } from 'react-native-keyboard-controller'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; export default function NameScreen() { @@ -15,27 +15,22 @@ export default function NameScreen() { const user = useUser(); const { updateProfile, isLoading } = useUpdateProfile(); + const initialFirst = React.useRef(user?.firstName || ''); + const initialLast = React.useRef(user?.lastName || ''); + const [form, setForm] = React.useState({ - first: user?.firstName || '', - middle: '', - last: user?.lastName || '', + first: initialFirst.current, + last: initialLast.current, }); - function onChangeText(type: 'first' | 'middle' | 'last') { + function onChangeText(type: 'first' | 'last') { return (text: string) => { setForm((prev) => ({ ...prev, [type]: text })); }; } - function focusNext() { - KeyboardController.setFocusTo('next'); - } - - const originalFirst = user?.firstName || ''; - const originalLast = user?.lastName || ''; - const canSave = - (form.first !== originalFirst || form.last !== originalLast) && + (form.first !== initialFirst.current || form.last !== initialLast.current) && !!form.first && !!form.last; @@ -94,23 +89,6 @@ export default function NameScreen() { placeholder={t('profile.requiredPlaceholder')} value={form.first} onChangeText={onChangeText('first')} - onSubmitEditing={focusNext} - submitBehavior="submit" - enterKeyHint="next" - /> - - - {t('profile.middleNameLabel')}, - })} - placeholder={t('profile.optionalPlaceholder')} - value={form.middle} - onChangeText={onChangeText('middle')} - onSubmitEditing={focusNext} submitBehavior="submit" enterKeyHint="next" /> diff --git a/apps/expo/lib/i18n/locales/en.json b/apps/expo/lib/i18n/locales/en.json index a8c448ea51..118f711c84 100644 --- a/apps/expo/lib/i18n/locales/en.json +++ b/apps/expo/lib/i18n/locales/en.json @@ -137,7 +137,7 @@ "loginFailed": "Login Failed", "invalidEmailOrPassword": "Invalid email or password", "resumeSync": "Resume Sync", - "syncPaused": "Sync paused — please sign in again." + "syncPaused": "Sync paused \u2014 please sign in again." }, "profile": { "profile": "Profile", @@ -174,7 +174,8 @@ "optionalPlaceholder": "optional", "usernameFootnote": "Choose a unique identifier for your account.", "notificationsFootnote": "Receive communication including announcements, marketing, recommendations, and updates about products, services, and software.", - "dangerZone": "Danger Zone" + "dangerZone": "Danger Zone", + "imageTooLarge": "Image must be smaller than 5 MB" }, "navigation": { "dashboard": "Dashboard", @@ -218,10 +219,10 @@ "lastUpdatedShort": "Last updated", "itemsCount": "{{count}} items", "sharingBenefits": "Sharing Benefits", - "distributeGroupGear": "• Distribute group gear among members to reduce individual pack weight", - "sharingBenefit1": "• Coordinate who brings shared items like tent, stove, and water filter", - "sharingBenefit2": "• See real-time updates when members modify the pack", - "sharingBenefit3": "• Plan together and ensure nothing essential is forgotten", + "distributeGroupGear": "\u2022 Distribute group gear among members to reduce individual pack weight", + "sharingBenefit1": "\u2022 Coordinate who brings shared items like tent, stove, and water filter", + "sharingBenefit2": "\u2022 See real-time updates when members modify the pack", + "sharingBenefit3": "\u2022 Plan together and ensure nothing essential is forgotten", "itemsInInventory": "{{count}} items in your inventory", "all": "All", "byCategory": "By Category", From 7ee88e95f2250043052780e4a7893e761cc4c60d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 17:05:16 +0000 Subject: [PATCH 04/11] fix: address CodeRabbit round-2 inline review comments - Show permission-specific alert with Open Settings button when photo library access is denied (instead of silently swallowing the error) - Trim whitespace in canSave and handleSave so whitespace-only names are rejected; use useMemo to avoid duplication and redundant trims - Guard handleSave with !canSave || isLoading to prevent double-submit via keyboard Return key bypassing the disabled button state - Add permissions and common.cancel i18n keys Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com> --- apps/expo/app/(app)/(tabs)/profile/index.tsx | 9 +++++++-- apps/expo/app/(app)/(tabs)/profile/name.tsx | 14 +++++++++----- apps/expo/lib/i18n/locales/en.json | 5 +++++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/apps/expo/app/(app)/(tabs)/profile/index.tsx b/apps/expo/app/(app)/(tabs)/profile/index.tsx index e11a022b23..05f2521041 100644 --- a/apps/expo/app/(app)/(tabs)/profile/index.tsx +++ b/apps/expo/app/(app)/(tabs)/profile/index.tsx @@ -30,7 +30,7 @@ import * as FileSystem from 'expo-file-system'; import { router, Stack } from 'expo-router'; import * as Updates from 'expo-updates'; import { useRef, useState } from 'react'; -import { Alert, Platform, SafeAreaView, TouchableOpacity, View } from 'react-native'; +import { Alert, Linking, Platform, SafeAreaView, TouchableOpacity, View } from 'react-native'; const ESTIMATED_ITEM_SIZE = ESTIMATED_ITEM_HEIGHT[Platform.OS === 'ios' ? 'titleOnly' : 'withSubTitle']; @@ -155,7 +155,12 @@ function ListHeaderComponent() { } } } catch (err) { - if (err instanceof Error && err.message !== 'Permission to access media library was denied') { + if (err instanceof Error && err.message === 'Permission to access media library was denied') { + Alert.alert(t('permissions.photoLibraryTitle'), t('permissions.photoLibraryMessage'), [ + { text: t('common.cancel'), style: 'cancel' }, + { text: t('permissions.openSettings'), onPress: () => Linking.openSettings() }, + ]); + } else { Alert.alert(t('errors.somethingWentWrong'), t('errors.tryAgain')); } } finally { diff --git a/apps/expo/app/(app)/(tabs)/profile/name.tsx b/apps/expo/app/(app)/(tabs)/profile/name.tsx index b50ef6118f..00b5b43007 100644 --- a/apps/expo/app/(app)/(tabs)/profile/name.tsx +++ b/apps/expo/app/(app)/(tabs)/profile/name.tsx @@ -29,15 +29,19 @@ export default function NameScreen() { }; } + const trimmedFirst = React.useMemo(() => form.first.trim(), [form.first]); + const trimmedLast = React.useMemo(() => form.last.trim(), [form.last]); + const canSave = - (form.first !== initialFirst.current || form.last !== initialLast.current) && - !!form.first && - !!form.last; + (trimmedFirst !== initialFirst.current.trim() || trimmedLast !== initialLast.current.trim()) && + !!trimmedFirst && + !!trimmedLast; async function handleSave() { + if (!canSave || isLoading) return; const success = await updateProfile({ - firstName: form.first, - lastName: form.last, + firstName: trimmedFirst, + lastName: trimmedLast, }); if (success) { router.back(); diff --git a/apps/expo/lib/i18n/locales/en.json b/apps/expo/lib/i18n/locales/en.json index 118f711c84..3a7b1bd83a 100644 --- a/apps/expo/lib/i18n/locales/en.json +++ b/apps/expo/lib/i18n/locales/en.json @@ -897,5 +897,10 @@ "by": "By", "updated": "Updated", "all": "All" + }, + "permissions": { + "photoLibraryTitle": "Photo Library Access Required", + "photoLibraryMessage": "PackRat needs access to your photo library to update your profile photo. Please enable it in Settings.", + "openSettings": "Open Settings" } } From 6d0292126b9e8317a17f4a9ce1e19d87722b0f8e Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Wed, 18 Mar 2026 20:23:02 +0100 Subject: [PATCH 05/11] fix: upgrade nativewind to 4.2.3 and @packrat-ai/nativewindui to 2.0.0 - fixes #1980, #1971, #1973, #1964, #1942, #1960 --- apps/expo/package.json | 2 +- bun.lock | 45 ++++++++++++++++++++++++++++++---------- packages/ui/package.json | 2 +- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/apps/expo/package.json b/apps/expo/package.json index 9c7a00470c..e7a95138ba 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -102,7 +102,7 @@ "i18n-js": "^4.4.3", "jotai": "^2.12.2", "lodash.debounce": "^4.0.8", - "nativewind": "^4.1.23", + "nativewind": "^4.2.3", "radash": "^12.1.1", "react": "19.1.0", "react-dom": "19.1.0", diff --git a/bun.lock b/bun.lock index 30866b6c50..57e4f439fb 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "name": "packrat-monorepo", @@ -17,7 +16,7 @@ }, "apps/expo": { "name": "packrat-expo-app", - "version": "2.0.14", + "version": "2.0.15", "dependencies": { "@ai-sdk/react": "^2.0.11", "@expo/react-native-action-sheet": "^4.1.1", @@ -82,7 +81,7 @@ "i18n-js": "^4.4.3", "jotai": "^2.12.2", "lodash.debounce": "^4.0.8", - "nativewind": "^4.1.23", + "nativewind": "^4.2.3", "radash": "^12.1.1", "react": "19.1.0", "react-dom": "19.1.0", @@ -124,7 +123,7 @@ }, "apps/guides": { "name": "packrat-guides-app", - "version": "2.0.14", + "version": "2.0.15", "dependencies": { "@ai-sdk/openai": "^2.0.11", "@hookform/resolvers": "^3.10.0", @@ -203,7 +202,7 @@ }, "apps/landing": { "name": "packrat-landing-app", - "version": "2.0.14", + "version": "2.0.15", "dependencies": { "@emotion/is-prop-valid": "^1.3.1", "@hookform/resolvers": "^3.10.0", @@ -316,9 +315,9 @@ }, "packages/ui": { "name": "@packrat/ui", - "version": "2.0.14", + "version": "2.0.15", "dependencies": { - "@packrat-ai/nativewindui": "^1.1.0", + "@packrat-ai/nativewindui": "^2.0.0-rc.3", }, }, }, @@ -946,7 +945,7 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@packrat-ai/nativewindui": ["@packrat-ai/nativewindui@1.1.0", "https://npm.pkg.github.com/download/@packrat-ai/nativewindui/1.1.0/5c57f431925d2054857b76bb898aa64633559b4e", { "peerDependencies": { "@gorhom/bottom-sheet": "^5.1.2", "@react-native-community/datetimepicker": "8.4.4", "@react-native-community/slider": "5.0.1", "@react-native-picker/picker": "2.11.1", "@react-native-segmented-control/segmented-control": "2.5.7", "@react-navigation/drawer": "^7.1.1", "@react-navigation/elements": "^2.3.1", "@react-navigation/native": "^7.0.14", "@rn-primitives/alert-dialog": "^1.1.0", "@rn-primitives/avatar": "^1.1.0", "@rn-primitives/checkbox": "^1.1.0", "@rn-primitives/context-menu": "^1.1.0", "@rn-primitives/dropdown-menu": "^1.1.0", "@rn-primitives/hooks": "^1.1.0", "@rn-primitives/portal": "^1.1.0", "@rn-primitives/slot": "^1.1.0", "@roninoss/icons": "^0.0.4", "@shopify/flash-list": "2.0.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "expo-blur": "~15.0.8", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", "expo-linear-gradient": "~15.0.8", "expo-navigation-bar": "~5.0.10", "expo-router": "~6.0.23", "expo-symbols": "~1.0.8", "nativewind": "^4.1.23", "react": "19.1.0", "react-native": "0.81.5", "react-native-keyboard-controller": "^1.16.7", "react-native-reanimated": "~4.1.1", "react-native-safe-area-context": "~5.6.0", "react-native-screens": "~4.16.0", "react-native-uitextview": "^1.1.4", "rn-icon-mapper": "^0.0.1", "tailwind-merge": "^2.2.1" } }, "sha512-iCPNH/SSqSGbs6p9je3UA4BSEezq+XnZ5x5xj3BGqxS1d4H89Jv/jeqIm/789/Q/xgBTSlozvfFXRemMKCqiUA=="], + "@packrat-ai/nativewindui": ["@packrat-ai/nativewindui@2.0.0-rc.3", "https://npm.pkg.github.com/download/@packrat-ai/nativewindui/2.0.0-rc.3/11ac155933fd39999bc39bdaa6c0f5e7d6492a69", { "peerDependencies": { "@expo/vector-icons": ">=15.0.0", "@gorhom/bottom-sheet": "^5.1.2", "@react-native-community/datetimepicker": "^8.4.0", "@react-native-community/slider": "^5.0.0", "@react-native-picker/picker": "^2.11.0", "@react-native-segmented-control/segmented-control": "^2.5.0", "@react-navigation/drawer": "^7.1.1", "@react-navigation/elements": "^2.3.1", "@react-navigation/native": "^7.0.14", "@rn-primitives/alert-dialog": "^1.1.0", "@rn-primitives/avatar": "^1.0.4", "@rn-primitives/checkbox": "^1.1.0", "@rn-primitives/context-menu": "^1.1.0", "@rn-primitives/dropdown-menu": "^1.1.0", "@rn-primitives/hooks": "^1.1.0", "@rn-primitives/portal": "^1.1.0", "@rn-primitives/slot": "^1.1.0", "@shopify/flash-list": "^2.0.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "expo-blur": "~15.0.8", "expo-device": "~8.0.0", "expo-glass-effect": "*", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", "expo-linear-gradient": "~15.0.8", "expo-navigation-bar": "~5.0.10", "expo-router": "~6.0.23", "expo-symbols": "~1.0.8", "nativewind": "^4.1.21", "react": ">=19.0.0", "react-native": ">=0.79.0", "react-native-keyboard-controller": "^1.16.7", "react-native-reanimated": ">=3.17.0", "react-native-safe-area-context": ">=5.4.0", "react-native-screens": ">=4.11.0", "react-native-uitextview": "^1.1.4", "rn-icon-mapper": "^0.0.1", "tailwind-merge": "^2.2.1" } }, "sha512-M5MecnXZgb2MKffccR/MBsot3t4t/siz3HNO2HvAZ4yBIrU/FrwwE/AgtWmGF47z4UoIngI6lVH+x0lAVf3F+g=="], "@packrat/api": ["@packrat/api@workspace:packages/api"], @@ -2056,12 +2055,16 @@ "expo-dev-menu-interface": ["expo-dev-menu-interface@2.0.0", "", { "peerDependencies": { "expo": "*" } }, "sha512-BvAMPt6x+vyXpThsyjjOYyjwfjREV4OOpQkZ0tNl+nGpsPfcY9mc6DRACoWnH9KpLzyIt3BOgh3cuy/h/OxQjw=="], + "expo-device": ["expo-device@8.0.10", "", { "dependencies": { "ua-parser-js": "^0.7.33" }, "peerDependencies": { "expo": "*" } }, "sha512-jd5BxjaF7382JkDMaC+P04aXXknB2UhWaVx5WiQKA05ugm/8GH5uaz9P9ckWdMKZGQVVEOC8MHaUADoT26KmFA=="], + "expo-eas-client": ["expo-eas-client@1.0.8", "", {}, "sha512-5or11NJhSeDoHHI6zyvQDW2cz/yFyE+1Cz8NTs5NK8JzC7J0JrkUgptWtxyfB6Xs/21YRNifd3qgbBN3hfKVgA=="], "expo-file-system": ["expo-file-system@19.0.21", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-s3DlrDdiscBHtab/6W1osrjGL+C2bvoInPJD7sOwmxfJ5Woynv2oc+Fz1/xVXaE/V7HE/+xrHC/H45tu6lZzzg=="], "expo-font": ["expo-font@14.0.11", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg=="], + "expo-glass-effect": ["expo-glass-effect@55.0.8", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-IvUjHb/4t6r2H/LXDjcQ4uDoHrmO2cLOvEb9leLavQ4HX5+P4LRtQrMDMlkWAn5Wo5DkLcG8+1CrQU2nqgogTA=="], + "expo-haptics": ["expo-haptics@15.0.8", "", { "peerDependencies": { "expo": "*" } }, "sha512-lftutojy8Qs8zaDzzjwM3gKHFZ8bOOEZDCkmh2Ddpe95Ra6kt2izeOfOfKuP/QEh0MZ1j9TfqippyHdRd1ZM9g=="], "expo-image": ["expo-image@3.0.11", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-4TudfUCLgYgENv+f48omnU8tjS2S0Pd9EaON5/s1ZUBRwZ7K8acEr4NfvLPSaeXvxW24iLAiyQ7sV7BXQH3RoA=="], @@ -2748,7 +2751,7 @@ "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], - "nativewind": ["nativewind@4.1.23", "", { "dependencies": { "comment-json": "^4.2.5", "debug": "^4.3.7", "react-native-css-interop": "0.1.22" }, "peerDependencies": { "tailwindcss": ">3.3.0" } }, "sha512-oLX3suGI6ojQqWxdQezOSM5GmJ4KvMnMtmaSMN9Ggb5j7ysFt4nHxb1xs8RDjZR7BWc+bsetNJU8IQdQMHqRpg=="], + "nativewind": ["nativewind@4.2.3", "", { "dependencies": { "comment-json": "^4.2.5", "debug": "^4.3.7", "react-native-css-interop": "0.2.3" }, "peerDependencies": { "tailwindcss": ">3.3.0" } }, "sha512-HglF1v6A8CqBFpXWs0d3yf4qQGurrreLuyE8FTRI/VDH8b0npZa2SDG5tviTkLiBg0s5j09mQALZOjxuocgMLA=="], "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], @@ -3008,7 +3011,7 @@ "react-native": ["react-native@0.81.5", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.5", "@react-native/codegen": "0.81.5", "@react-native/community-cli-plugin": "0.81.5", "@react-native/gradle-plugin": "0.81.5", "@react-native/js-polyfills": "0.81.5", "@react-native/normalize-colors": "0.81.5", "@react-native/virtualized-lists": "0.81.5", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.29.1", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.83.1", "metro-source-map": "^0.83.1", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.26.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^6.2.3", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.1.0", "react": "^19.1.0" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw=="], - "react-native-css-interop": ["react-native-css-interop@0.1.22", "", { "dependencies": { "@babel/helper-module-imports": "^7.22.15", "@babel/traverse": "^7.23.0", "@babel/types": "^7.23.0", "debug": "^4.3.7", "lightningcss": "^1.27.0", "semver": "^7.6.3" }, "peerDependencies": { "react": ">=18", "react-native": "*", "react-native-reanimated": ">=3.6.2", "tailwindcss": "~3" } }, "sha512-Mu01e+H9G+fxSWvwtgWlF5MJBJC4VszTCBXopIpeR171lbeBInHb8aHqoqRPxmJpi3xIHryzqKFOJYAdk7PBxg=="], + "react-native-css-interop": ["react-native-css-interop@0.2.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.22.15", "@babel/traverse": "^7.23.0", "@babel/types": "^7.23.0", "debug": "^4.3.7", "lightningcss": "~1.27.0", "semver": "^7.6.3" }, "peerDependencies": { "react": ">=18", "react-native": "*", "react-native-reanimated": ">=3.6.2", "tailwindcss": "~3" } }, "sha512-wc+JI7iUfdFBqnE18HhMTtD0q9vkhuMczToA87UdHGWwMyxdT5sCcNy+i4KInPCE855IY0Ic8kLQqecAIBWz7w=="], "react-native-drawer-layout": ["react-native-drawer-layout@4.1.12", "", { "dependencies": { "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*", "react-native-gesture-handler": ">= 2.0.0", "react-native-reanimated": ">= 2.0.0" } }, "sha512-oKolvp5seiUieG+RHGjpFe8rH8Ds24iW0QBl31TlCVOX7tdn42IQIBl5tuD1i7h3q+VqqnbcT+NB2dcJ5suZkw=="], @@ -3428,7 +3431,7 @@ "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - "ua-parser-js": ["ua-parser-js@1.0.40", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew=="], + "ua-parser-js": ["ua-parser-js@0.7.41", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg=="], "uc.micro": ["uc.micro@1.0.6", "", {}, "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="], @@ -3880,6 +3883,8 @@ "fbjs/promise": ["promise@7.3.1", "", { "dependencies": { "asap": "~2.0.3" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="], + "fbjs/ua-parser-js": ["ua-parser-js@1.0.40", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew=="], + "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "finalhandler/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="], @@ -3998,6 +4003,12 @@ "react-native/ws": ["ws@6.2.3", "", { "dependencies": { "async-limiter": "~1.0.0" } }, "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA=="], + "react-native-css-interop/@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], + + "react-native-css-interop/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "react-native-css-interop/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "react-native-web/@react-native/normalize-colors": ["@react-native/normalize-colors@0.74.89", "", {}, "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg=="], "react-native-web/memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="], @@ -4456,6 +4467,16 @@ "packrat-landing-app/react-dom/scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], + "react-native-css-interop/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], + + "react-native-css-interop/@babel/traverse/@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], + + "react-native-css-interop/@babel/traverse/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], + + "react-native-css-interop/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "react-native-css-interop/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + "react-native/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "read-pkg-up/find-up/path-exists": ["path-exists@2.1.0", "", { "dependencies": { "pinkie-promise": "^2.0.0" } }, "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ=="], @@ -4566,6 +4587,8 @@ "ora/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], + "react-native-css-interop/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + "react-native/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "serve-static/send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], diff --git a/packages/ui/package.json b/packages/ui/package.json index f70769adfd..b7bf712a09 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -3,6 +3,6 @@ "version": "2.0.15", "private": true, "dependencies": { - "@packrat-ai/nativewindui": "^1.1.0" + "@packrat-ai/nativewindui": "^2.0.0" } } From 8490999a1a8d6a5e89279a9cb9528c7f28cc2856 Mon Sep 17 00:00:00 2001 From: Anmol Verma Date: Fri, 20 Mar 2026 14:47:57 +0530 Subject: [PATCH 06/11] add missing imports --- apps/expo/features/trips/screens/TripDetailScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/expo/features/trips/screens/TripDetailScreen.tsx b/apps/expo/features/trips/screens/TripDetailScreen.tsx index 27691c449f..fddd285f42 100644 --- a/apps/expo/features/trips/screens/TripDetailScreen.tsx +++ b/apps/expo/features/trips/screens/TripDetailScreen.tsx @@ -12,7 +12,7 @@ import { useTranslation } from 'expo-app/lib/hooks/useTranslation'; import { assertDefined } from 'expo-app/utils/typeAssertions'; import { useLocalSearchParams, useRouter } from 'expo-router'; import { useRef } from 'react'; -import { ScrollView, View } from 'react-native'; +import { ScrollView, Share, View } from 'react-native'; import MapView, { Marker, PROVIDER_GOOGLE } from 'react-native-maps'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useDetailedPacks } from '../../packs/hooks/useDetailedPacks'; From 7dd1a787affa45487b9c6a9dc4483ab0c3361188 Mon Sep 17 00:00:00 2001 From: Anmol Verma Date: Fri, 20 Mar 2026 20:11:04 +0530 Subject: [PATCH 07/11] correct materialcommunity icon --- apps/expo/app/(app)/(tabs)/profile/index.tsx | 2 +- apps/expo/app/(app)/(tabs)/profile/name.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/expo/app/(app)/(tabs)/profile/index.tsx b/apps/expo/app/(app)/(tabs)/profile/index.tsx index e6c5ec722d..4289f368b0 100644 --- a/apps/expo/app/(app)/(tabs)/profile/index.tsx +++ b/apps/expo/app/(app)/(tabs)/profile/index.tsx @@ -30,7 +30,7 @@ import * as FileSystem from 'expo-file-system'; import { router, Stack } from 'expo-router'; import * as Updates from 'expo-updates'; import { useRef, useState } from 'react'; -import { Platform, View } from 'react-native'; +import { Alert, Platform, TouchableOpacity, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; const AVATAR_MAX_BYTES = 5 * 1024 * 1024; // 5 MB diff --git a/apps/expo/app/(app)/(tabs)/profile/name.tsx b/apps/expo/app/(app)/(tabs)/profile/name.tsx index 22c6c342b0..9da0faba40 100644 --- a/apps/expo/app/(app)/(tabs)/profile/name.tsx +++ b/apps/expo/app/(app)/(tabs)/profile/name.tsx @@ -80,7 +80,7 @@ export default function NameScreen() { contentContainerStyle={{ paddingBottom: insets.bottom }} >
- +