diff --git a/frontends/react-native-ios/app.json b/frontends/react-native-ios/app.json index ed4a0b9..054e63a 100644 --- a/frontends/react-native-ios/app.json +++ b/frontends/react-native-ios/app.json @@ -10,7 +10,7 @@ "newArchEnabled": true, "splash": { "image": "./assets/splash-icon.png", - "resizeMode": "contain", + "resizeMode": "cover", "backgroundColor": "#0a0a0a" }, "ios": { @@ -29,7 +29,7 @@ { "backgroundColor": "#0a0a0a", "image": "./assets/splash-icon.png", - "imageWidth": 200 + "resizeMode": "cover" } ] ], diff --git a/frontends/react-native-ios/app/(app)/(tabs)/home.tsx b/frontends/react-native-ios/app/(app)/(tabs)/home.tsx index ca9de4f..17eb9fc 100644 --- a/frontends/react-native-ios/app/(app)/(tabs)/home.tsx +++ b/frontends/react-native-ios/app/(app)/(tabs)/home.tsx @@ -3,21 +3,19 @@ * home.tsx */ +import { useCurrentUser } from '@/api/hooks' +import { DottedBackground } from '@/shared/components' import type React from 'react' import { SafeAreaView } from 'react-native-safe-area-context' import { Stack, Text, YStack } from 'tamagui' -import { useCurrentUser } from '@/api/hooks' -import { colors } from '@/theme/tokens' export default function HomeScreen(): React.ReactElement { const { data: user } = useCurrentUser() return ( - - + + + - - + + + ) } diff --git a/frontends/react-native-ios/app/(app)/(tabs)/settings.tsx b/frontends/react-native-ios/app/(app)/(tabs)/settings.tsx index d8f19dc..f8784c3 100644 --- a/frontends/react-native-ios/app/(app)/(tabs)/settings.tsx +++ b/frontends/react-native-ios/app/(app)/(tabs)/settings.tsx @@ -3,15 +3,17 @@ * settings.tsx */ -import { ChevronRight, LogOut, Shield, User } from 'lucide-react-native' -import type React from 'react' -import { SafeAreaView } from 'react-native-safe-area-context' -import { Stack, Switch, Text, YStack } from 'tamagui' import { useCurrentUser, useLogout } from '@/api/hooks' import { useBiometricsEnabled, useUIStore } from '@/core/lib' +import { DottedBackground } from '@/shared/components' import { getBiometricLabel, useBiometrics } from '@/shared/hooks' import { haptics } from '@/shared/utils' import { colors } from '@/theme/tokens' +import { router } from 'expo-router' +import { ChevronRight, LogOut, Shield, User } from 'lucide-react-native' +import type React from 'react' +import { SafeAreaView } from 'react-native-safe-area-context' +import { Stack, Switch, Text, YStack } from 'tamagui' function SettingsRow({ icon, @@ -67,11 +69,9 @@ export default function SettingsScreen(): React.ReactElement { const canUseBiometrics = isAvailable && isEnrolled return ( - - + + + } label="Profile" - onPress={() => {}} + onPress={() => router.push('/(app)/profile')} /> {canUseBiometrics && ( } - label={getBiometricLabel(biometryType)} + label={`${getBiometricLabel(biometryType)} Lock`} rightElement={ - + } /> @@ -133,7 +140,8 @@ export default function SettingsScreen(): React.ReactElement { onPress={handleLogout} /> - - + + + ) } diff --git a/frontends/react-native-ios/app/(app)/profile.tsx b/frontends/react-native-ios/app/(app)/profile.tsx new file mode 100644 index 0000000..a78cc5e --- /dev/null +++ b/frontends/react-native-ios/app/(app)/profile.tsx @@ -0,0 +1,224 @@ +/** + * @AngelaMos | 2026 + * profile.tsx + */ + +import { useChangePassword, useCurrentUser } from '@/api/hooks' +import { useUpdateProfile } from '@/api/hooks/useUsers' +import { Button, DottedBackground, Input, PasswordInput } from '@/shared/components' +import { haptics } from '@/shared/utils' +import { router } from 'expo-router' +import { ArrowLeft } from 'lucide-react-native' +import type React from 'react' +import { useEffect, useState } from 'react' +import { Alert, KeyboardAvoidingView, Platform, ScrollView } from 'react-native' +import { SafeAreaView } from 'react-native-safe-area-context' +import { Stack, Text, YStack } from 'tamagui' +import { colors } from '@/theme/tokens' + +export default function ProfileScreen(): React.ReactElement { + const { data: user } = useCurrentUser() + const updateProfile = useUpdateProfile() + const changePassword = useChangePassword() + + const [fullName, setFullName] = useState('') + const [currentPassword, setCurrentPassword] = useState('') + const [newPassword, setNewPassword] = useState('') + const [confirmPassword, setConfirmPassword] = useState('') + + useEffect(() => { + if (user) { + setFullName(user.full_name ?? '') + } + }, [user]) + + const handleSaveProfile = (): void => { + if (!fullName.trim()) { + haptics.error() + Alert.alert('Error', 'Please enter your name') + return + } + + updateProfile.mutate( + { full_name: fullName.trim() }, + { + onSuccess: () => { + haptics.success() + Alert.alert('Success', 'Profile updated') + }, + onError: (err) => { + haptics.error() + Alert.alert('Error', err.message) + }, + } + ) + } + + const handleChangePassword = (): void => { + if (!currentPassword || !newPassword || !confirmPassword) { + haptics.error() + Alert.alert('Error', 'Please fill in all password fields') + return + } + + if (newPassword !== confirmPassword) { + haptics.error() + Alert.alert('Error', 'New passwords do not match') + return + } + + if (newPassword.length < 8) { + haptics.error() + Alert.alert('Error', 'Password must be at least 8 characters') + return + } + + changePassword.mutate( + { current_password: currentPassword, new_password: newPassword }, + { + onSuccess: () => { + haptics.success() + Alert.alert('Success', 'Password changed') + setCurrentPassword('') + setNewPassword('') + setConfirmPassword('') + }, + onError: (err) => { + haptics.error() + Alert.alert('Error', err.message) + }, + } + ) + } + + return ( + + + + + router.back()} + > + + + Back + + + + + Profile + + + + + + Account Info + + + + + + + + + + + + + + + + Change Password + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/frontends/react-native-ios/app/(auth)/login.tsx b/frontends/react-native-ios/app/(auth)/login.tsx index bd1290b..af3689e 100644 --- a/frontends/react-native-ios/app/(auth)/login.tsx +++ b/frontends/react-native-ios/app/(auth)/login.tsx @@ -3,12 +3,6 @@ * login.tsx */ -import { Link, router } from 'expo-router' -import { ArrowLeft } from 'lucide-react-native' -import type React from 'react' -import { useState } from 'react' -import { KeyboardAvoidingView, Platform, ScrollView } from 'react-native' -import { Stack, Text, YStack } from 'tamagui' import { useLogin } from '@/api/hooks' import { loginRequestSchema } from '@/api/types' import { @@ -21,11 +15,16 @@ import { CardHeader, CardSubtitle, CardTitle, + DottedBackground, Input, PasswordInput, } from '@/shared/components' import { haptics } from '@/shared/utils' -import { colors } from '@/theme/tokens' +import { Link, router } from 'expo-router' +import type React from 'react' +import { useState } from 'react' +import { KeyboardAvoidingView, Platform, ScrollView } from 'react-native' +import { Stack, Text, YStack } from 'tamagui' export default function LoginScreen(): React.ReactElement { const login = useLogin() @@ -62,36 +61,21 @@ export default function LoginScreen(): React.ReactElement { } return ( - - + - router.back()} + - - - Home - - - - + Login @@ -143,8 +127,9 @@ export default function LoginScreen(): React.ReactElement { - - - + + + + ) } diff --git a/frontends/react-native-ios/app/(auth)/register.tsx b/frontends/react-native-ios/app/(auth)/register.tsx index caa78d3..7c79965 100644 --- a/frontends/react-native-ios/app/(auth)/register.tsx +++ b/frontends/react-native-ios/app/(auth)/register.tsx @@ -3,13 +3,6 @@ * register.tsx */ -import { Link, router } from 'expo-router' -import { ArrowLeft } from 'lucide-react-native' -import type React from 'react' -import { useState } from 'react' -import { KeyboardAvoidingView, Platform, ScrollView } from 'react-native' -import { Stack, Text, YStack } from 'tamagui' -import { z } from 'zod' import { useRegister } from '@/api/hooks' import { userCreateRequestSchema } from '@/api/types' import { PASSWORD_CONSTRAINTS } from '@/core/config' @@ -23,11 +16,17 @@ import { CardHeader, CardSubtitle, CardTitle, + DottedBackground, Input, PasswordInput, } from '@/shared/components' import { haptics } from '@/shared/utils' -import { colors } from '@/theme/tokens' +import { Link, router } from 'expo-router' +import type React from 'react' +import { useState } from 'react' +import { KeyboardAvoidingView, Platform, ScrollView } from 'react-native' +import { Stack, Text, YStack } from 'tamagui' +import { z } from 'zod' const registerFormSchema = userCreateRequestSchema .extend({ @@ -84,37 +83,22 @@ export default function RegisterScreen(): React.ReactElement { } return ( - - + - router.back()} + - - - Home - - - - - + + Sign up Create a new account @@ -171,9 +155,10 @@ export default function RegisterScreen(): React.ReactElement { - - - - + + + + + ) } diff --git a/frontends/react-native-ios/eas.json b/frontends/react-native-ios/eas.json index 78fb43d..0d60262 100644 --- a/frontends/react-native-ios/eas.json +++ b/frontends/react-native-ios/eas.json @@ -1,6 +1,7 @@ { "cli": { - "version": ">= 5.0.0" + "version": ">= 5.0.0", + "appVersionSource": "remote" }, "build": { "development": { diff --git a/frontends/react-native-ios/package.json b/frontends/react-native-ios/package.json index eba160b..a9bebf5 100644 --- a/frontends/react-native-ios/package.json +++ b/frontends/react-native-ios/package.json @@ -38,12 +38,11 @@ "react": "19.1.0", "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", - "react-native-mmkv": "^4.1.1", "react-native-reanimated": "~4.1.1", "react-native-safe-area-context": "~5.6.2", - "react-native-worklets": "^0.7.1", "react-native-screens": "~4.16.0", "react-native-svg": "15.12.1", + "react-native-worklets": "^0.7.1", "tamagui": "^1.144.1", "zod": "^4.3.5", "zustand": "^5.0.9" diff --git a/frontends/react-native-ios/pnpm-lock.yaml b/frontends/react-native-ios/pnpm-lock.yaml index adcec29..3c799b0 100644 --- a/frontends/react-native-ios/pnpm-lock.yaml +++ b/frontends/react-native-ios/pnpm-lock.yaml @@ -84,9 +84,6 @@ importers: react-native-gesture-handler: specifier: ~2.28.0 version: 2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-mmkv: - specifier: ^4.1.1 - version: 4.1.1(react-native-nitro-modules@0.32.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-reanimated: specifier: ~4.1.1 version: 4.1.6(@babel/core@7.28.5)(react-native-worklets@0.7.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -3776,19 +3773,6 @@ packages: react: 19.1.0 react-native: '*' - react-native-mmkv@4.1.1: - resolution: {integrity: sha512-nYFjM27l7zVhIiyAqWEFRagGASecb13JMIlzAuOeakRRz9GMJ49hCQntUBE2aeuZRE4u4ehSqTOomB0mTF56Ew==} - peerDependencies: - react: 19.1.0 - react-native: '*' - react-native-nitro-modules: '*' - - react-native-nitro-modules@0.32.1: - resolution: {integrity: sha512-V+Vy76e4fxRxgVGu5Uh3cBPvuFQW8fM1OUKk1mqEA/JawjhX+hxHtBhpfuvNjV0BnV/uXCIg8/eK+rTpB6tqFg==} - peerDependencies: - react: 19.1.0 - react-native: '*' - react-native-reanimated@4.1.6: resolution: {integrity: sha512-F+ZJBYiok/6Jzp1re75F/9aLzkgoQCOh4yxrnwATa8392RvM3kx+fiXXFvwcgE59v48lMwd9q0nzF1oJLXpfxQ==} peerDependencies: @@ -9251,17 +9235,6 @@ snapshots: react: 19.1.0 react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0) - react-native-mmkv@4.1.1(react-native-nitro-modules@0.32.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): - dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0) - react-native-nitro-modules: 0.32.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - - react-native-nitro-modules@0.32.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): - dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0) - react-native-reanimated@4.1.6(@babel/core@7.28.5)(react-native-worklets@0.7.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: '@babel/core': 7.28.5 diff --git a/frontends/react-native-ios/src/api/types/auth.types.ts b/frontends/react-native-ios/src/api/types/auth.types.ts index ae046d6..1957ac3 100644 --- a/frontends/react-native-ios/src/api/types/auth.types.ts +++ b/frontends/react-native-ios/src/api/types/auth.types.ts @@ -42,7 +42,7 @@ export const tokenWithUserResponseSchema = tokenResponseSchema.extend({ export const mobileLoginResponseSchema = z.object({ access_token: z.string(), - refresh_token: z.string(), + refresh_token: z.string().optional(), token_type: z.string(), user: userResponseSchema, }) diff --git a/frontends/react-native-ios/src/core/api/api.config.ts b/frontends/react-native-ios/src/core/api/api.config.ts index 7412ff9..9234a65 100644 --- a/frontends/react-native-ios/src/core/api/api.config.ts +++ b/frontends/react-native-ios/src/core/api/api.config.ts @@ -148,9 +148,12 @@ apiClient.interceptors.response.use( const isUnauthorized = error.response?.status === HTTP_STATUS.UNAUTHORIZED const isNotRetried = originalRequest._retry !== true - const isNotRefreshEndpoint = originalRequest.url?.includes('refresh') !== true + const isNotAuthEndpoint = + !originalRequest.url?.includes('refresh') && + !originalRequest.url?.includes('login') && + !originalRequest.url?.includes('register') - if (isUnauthorized && isNotRetried && isNotRefreshEndpoint) { + if (isUnauthorized && isNotRetried && isNotAuthEndpoint) { if (isRefreshing) { return new Promise((resolve, reject) => { addRefreshSubscriber( diff --git a/frontends/react-native-ios/src/core/api/query.config.ts b/frontends/react-native-ios/src/core/api/query.config.ts index e0a839f..66519c1 100644 --- a/frontends/react-native-ios/src/core/api/query.config.ts +++ b/frontends/react-native-ios/src/core/api/query.config.ts @@ -6,7 +6,7 @@ import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister' import { MutationCache, QueryCache, QueryClient } from '@tanstack/react-query' import { QUERY_CONFIG } from '@/core/config' -import { queryClientMMKVStorage } from '@/core/storage' +import { queryClientStorage } from '@/core/storage' import { ApiError, ApiErrorCode } from './errors' const NO_RETRY_ERROR_CODES: readonly ApiErrorCode[] = [ @@ -111,6 +111,6 @@ export const queryClient = new QueryClient({ }) export const queryClientPersister = createAsyncStoragePersister({ - storage: queryClientMMKVStorage, + storage: queryClientStorage, key: 'query-cache', }) diff --git a/frontends/react-native-ios/src/core/config.ts b/frontends/react-native-ios/src/core/config.ts index 0b2eb90..f58d0c9 100644 --- a/frontends/react-native-ios/src/core/config.ts +++ b/frontends/react-native-ios/src/core/config.ts @@ -10,7 +10,6 @@ export const APP_CONFIG = { KEYCHAIN_SERVICE: 'com.yourcompany.app.auth', AUTH_STORAGE_KEY: 'app-auth', - MMKV_STORAGE_ID: 'app-storage', ACCENT_COLOR: '#C15F3C', } as const diff --git a/frontends/react-native-ios/src/core/lib/auth.store.ts b/frontends/react-native-ios/src/core/lib/auth.store.ts index ee6c12b..4635937 100644 --- a/frontends/react-native-ios/src/core/lib/auth.store.ts +++ b/frontends/react-native-ios/src/core/lib/auth.store.ts @@ -7,7 +7,7 @@ import { create } from 'zustand' import { createJSONStorage, persist } from 'zustand/middleware' import { type UserResponse, UserRole } from '@/api/types' import { APP_CONFIG } from '@/core/config' -import { zustandMMKVStorage } from '@/core/storage' +import { zustandStorage } from '@/core/storage' interface AuthState { user: UserResponse | null @@ -59,7 +59,7 @@ export const useAuthStore = create()( }), { name: APP_CONFIG.AUTH_STORAGE_KEY, - storage: createJSONStorage(() => zustandMMKVStorage), + storage: createJSONStorage(() => zustandStorage), partialize: (state) => ({ user: state.user, @@ -67,6 +67,7 @@ export const useAuthStore = create()( }) as AuthStore, onRehydrateStorage: () => (state) => { state?.setHasHydrated(true) + state?.setLoading(false) }, } ) diff --git a/frontends/react-native-ios/src/core/lib/ui.store.ts b/frontends/react-native-ios/src/core/lib/ui.store.ts index d7f227e..9456c29 100644 --- a/frontends/react-native-ios/src/core/lib/ui.store.ts +++ b/frontends/react-native-ios/src/core/lib/ui.store.ts @@ -5,7 +5,7 @@ import { create } from 'zustand' import { createJSONStorage, persist } from 'zustand/middleware' -import { zustandMMKVStorage } from '@/core/storage' +import { zustandStorage } from '@/core/storage' interface UIState { biometricsEnabled: boolean @@ -26,7 +26,7 @@ export const useUIStore = create()( }), { name: 'ui-storage', - storage: createJSONStorage(() => zustandMMKVStorage), + storage: createJSONStorage(() => zustandStorage), } ) ) diff --git a/frontends/react-native-ios/src/core/storage/app.storage.ts b/frontends/react-native-ios/src/core/storage/app.storage.ts new file mode 100644 index 0000000..f74eba4 --- /dev/null +++ b/frontends/react-native-ios/src/core/storage/app.storage.ts @@ -0,0 +1,35 @@ +// =================== +// © AngelaMos | 2026 +// app.storage.ts +// =================== + +import * as SecureStore from 'expo-secure-store' +import type { StateStorage } from 'zustand/middleware' + +export const zustandStorage: StateStorage = { + getItem: (name: string): string | null => { + return SecureStore.getItem(name) + }, + + setItem: (name: string, value: string): void => { + SecureStore.setItem(name, value) + }, + + removeItem: (name: string): void => { + SecureStore.deleteItemAsync(name) + }, +} + +export const queryClientStorage = { + getItem: (key: string): string | null => { + return SecureStore.getItem(key) + }, + + setItem: (key: string, value: string): void => { + SecureStore.setItem(key, value) + }, + + removeItem: (key: string): void => { + SecureStore.deleteItemAsync(key) + }, +} diff --git a/frontends/react-native-ios/src/core/storage/index.ts b/frontends/react-native-ios/src/core/storage/index.ts index 77fcded..c494275 100644 --- a/frontends/react-native-ios/src/core/storage/index.ts +++ b/frontends/react-native-ios/src/core/storage/index.ts @@ -1,7 +1,7 @@ -/** - * @AngelaMos | 2026 - * index.ts - */ +// =================== +// © AngelaMos | 2026 +// index.ts +// =================== -export * from './mmkv.storage' export * from './secure.storage' +export * from './app.storage' diff --git a/frontends/react-native-ios/src/core/storage/mmkv.storage.ts b/frontends/react-native-ios/src/core/storage/mmkv.storage.ts deleted file mode 100644 index 0251a8b..0000000 --- a/frontends/react-native-ios/src/core/storage/mmkv.storage.ts +++ /dev/null @@ -1,42 +0,0 @@ -// =================== -// © AngelaMos | 2026 -// mmkv.storage.ts -// =================== - -import { MMKV } from 'react-native-mmkv' -import type { StateStorage } from 'zustand/middleware' -import { APP_CONFIG } from '@/core/config' - -export const mmkv = new MMKV({ - id: APP_CONFIG.MMKV_STORAGE_ID, -}) - -export const zustandMMKVStorage: StateStorage = { - getItem: (name: string): string | null => { - const value = mmkv.getString(name) - return value ?? null - }, - - setItem: (name: string, value: string): void => { - mmkv.set(name, value) - }, - - removeItem: (name: string): void => { - mmkv.delete(name) - }, -} - -export const queryClientMMKVStorage = { - getItem: (key: string): string | null => { - const value = mmkv.getString(key) - return value ?? null - }, - - setItem: (key: string, value: string): void => { - mmkv.set(key, value) - }, - - removeItem: (key: string): void => { - mmkv.delete(key) - }, -} diff --git a/frontends/react-native-ios/src/shared/components/ui/Button.tsx b/frontends/react-native-ios/src/shared/components/ui/Button.tsx index fee9716..79508e6 100644 --- a/frontends/react-native-ios/src/shared/components/ui/Button.tsx +++ b/frontends/react-native-ios/src/shared/components/ui/Button.tsx @@ -3,9 +3,10 @@ * Button.tsx */ -import type React from 'react' -import { type GetProps, Stack, styled, Text } from 'tamagui' import { haptics } from '@/shared/utils' +import type React from 'react' +import { type GetProps, styled } from 'tamagui' +import { Stack, Text } from 'tamagui' const ButtonFrame = styled(Stack, { name: 'Button', @@ -14,9 +15,9 @@ const ButtonFrame = styled(Stack, { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', - height: 48, + height: 44, paddingHorizontal: '$4', - borderRadius: '$3', + borderRadius: '$2', cursor: 'pointer', pressStyle: { opacity: 0.9, @@ -64,7 +65,7 @@ const ButtonFrame = styled(Stack, { const ButtonText = styled(Text, { name: 'ButtonText', - fontSize: 16, + fontSize: 14, fontWeight: '500', textAlign: 'center', diff --git a/frontends/react-native-ios/src/shared/components/ui/Card.tsx b/frontends/react-native-ios/src/shared/components/ui/Card.tsx index ea1b8b1..dc50af6 100644 --- a/frontends/react-native-ios/src/shared/components/ui/Card.tsx +++ b/frontends/react-native-ios/src/shared/components/ui/Card.tsx @@ -3,17 +3,18 @@ * Card.tsx */ -import { type GetProps, Stack, styled, Text } from 'tamagui' +import { type GetProps, styled } from 'tamagui' +import { Stack, Text } from 'tamagui' export const Card = styled(Stack, { name: 'Card', backgroundColor: '$black', borderWidth: 1, borderColor: '$borderDefault', - borderRadius: '$4', - padding: '$8', + borderRadius: '$3', + padding: '$6', width: '100%', - maxWidth: 400, + maxWidth: 340, variants: { variant: { @@ -32,27 +33,27 @@ export const Card = styled(Stack, { export const CardHeader = styled(Stack, { name: 'CardHeader', flexDirection: 'column', - marginBottom: '$6', + marginBottom: '$4', }) export const CardTitle = styled(Text, { name: 'CardTitle', - fontSize: 24, + fontSize: 22, fontWeight: '600', color: '$white', - marginBottom: '$2', + marginBottom: '$1', }) export const CardSubtitle = styled(Text, { name: 'CardSubtitle', - fontSize: 14, + fontSize: 12, color: '$textLight', }) export const CardContent = styled(Stack, { name: 'CardContent', flexDirection: 'column', - gap: '$5', + gap: '$4', }) export const CardFooter = styled(Stack, { @@ -60,19 +61,19 @@ export const CardFooter = styled(Stack, { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', - marginTop: '$6', + marginTop: '$4', }) export const CardFooterText = styled(Text, { name: 'CardFooterText', - fontSize: 14, + fontSize: 12, color: '$textLight', textAlign: 'center', }) export const CardFooterLink = styled(Text, { name: 'CardFooterLink', - fontSize: 14, + fontSize: 12, color: '$accent', textDecorationLine: 'underline', }) diff --git a/frontends/react-native-ios/src/shared/components/ui/DottedBackground.tsx b/frontends/react-native-ios/src/shared/components/ui/DottedBackground.tsx new file mode 100644 index 0000000..c410bdd --- /dev/null +++ b/frontends/react-native-ios/src/shared/components/ui/DottedBackground.tsx @@ -0,0 +1,65 @@ +// =================== +// © AngelaMos | 2026 +// DottedBackground.tsx +// =================== + +import { colors } from '@/theme/tokens' +import type React from 'react' +import { Dimensions, StyleSheet, View } from 'react-native' +import Svg, { Circle, Defs, Pattern, Rect } from 'react-native-svg' + +interface DottedBackgroundProps { + children: React.ReactNode + dotColor?: string + dotSize?: number + dotSpacing?: number +} + +export function DottedBackground({ + children, + dotColor = colors.bgShellDot.val, + dotSize = 1, + dotSpacing = 16, +}: DottedBackgroundProps): React.ReactElement { + const { width, height } = Dimensions.get('window') + + return ( + + + + + + + + + + + {children} + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + content: { + flex: 1, + }, +}) diff --git a/frontends/react-native-ios/src/shared/components/ui/Input.tsx b/frontends/react-native-ios/src/shared/components/ui/Input.tsx index 888b9c4..7911186 100644 --- a/frontends/react-native-ios/src/shared/components/ui/Input.tsx +++ b/frontends/react-native-ios/src/shared/components/ui/Input.tsx @@ -3,25 +3,20 @@ * Input.tsx */ -import { forwardRef, type Ref } from 'react' +import { type Ref, forwardRef } from 'react' import type { TextInput as RNTextInput } from 'react-native' -import { - type GetProps, - Stack, - styled, - Input as TamaguiInput, - Text, -} from 'tamagui' +import { type GetProps, styled } from 'tamagui' +import { Stack, Input as TamaguiInput, Text } from 'tamagui' const InputFrame = styled(TamaguiInput, { name: 'Input', - height: 48, - paddingHorizontal: '$4', + height: 44, + paddingHorizontal: '$3', backgroundColor: 'transparent', borderWidth: 1, borderColor: '$borderDefault', - borderRadius: '$3', - fontSize: 16, + borderRadius: '$2', + fontSize: 14, color: '$white', placeholderTextColor: '$textMuted', outlineWidth: 0, @@ -50,7 +45,7 @@ const InputContainer = styled(Stack, { const Label = styled(Text, { name: 'Label', - fontSize: 14, + fontSize: 12, fontWeight: '500', color: '$white', }) diff --git a/frontends/react-native-ios/src/shared/components/ui/index.ts b/frontends/react-native-ios/src/shared/components/ui/index.ts index a5d296f..afdfda7 100644 --- a/frontends/react-native-ios/src/shared/components/ui/index.ts +++ b/frontends/react-native-ios/src/shared/components/ui/index.ts @@ -4,6 +4,7 @@ */ export * from './Button' -export * from './Card' export * from './Input' +export * from './Card' export * from './PasswordInput' +export * from './DottedBackground'