@croacroa/react-native-template 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/.env.example +18 -0
  2. package/.eslintrc.js +55 -0
  3. package/.github/workflows/ci.yml +184 -0
  4. package/.github/workflows/eas-build.yml +55 -0
  5. package/.github/workflows/eas-update.yml +50 -0
  6. package/.gitignore +62 -0
  7. package/.prettierrc +11 -0
  8. package/.storybook/main.ts +28 -0
  9. package/.storybook/preview.tsx +30 -0
  10. package/CHANGELOG.md +106 -0
  11. package/CONTRIBUTING.md +377 -0
  12. package/README.md +399 -0
  13. package/__tests__/components/Button.test.tsx +74 -0
  14. package/__tests__/hooks/useAuth.test.tsx +499 -0
  15. package/__tests__/services/api.test.ts +535 -0
  16. package/__tests__/utils/cn.test.ts +39 -0
  17. package/app/(auth)/_layout.tsx +36 -0
  18. package/app/(auth)/home.tsx +117 -0
  19. package/app/(auth)/profile.tsx +152 -0
  20. package/app/(auth)/settings.tsx +147 -0
  21. package/app/(public)/_layout.tsx +21 -0
  22. package/app/(public)/forgot-password.tsx +127 -0
  23. package/app/(public)/login.tsx +120 -0
  24. package/app/(public)/onboarding.tsx +5 -0
  25. package/app/(public)/register.tsx +139 -0
  26. package/app/_layout.tsx +97 -0
  27. package/app/index.tsx +21 -0
  28. package/app.config.ts +72 -0
  29. package/assets/images/.gitkeep +7 -0
  30. package/assets/images/adaptive-icon.png +0 -0
  31. package/assets/images/favicon.png +0 -0
  32. package/assets/images/icon.png +0 -0
  33. package/assets/images/notification-icon.png +0 -0
  34. package/assets/images/splash.png +0 -0
  35. package/babel.config.js +10 -0
  36. package/components/ErrorBoundary.tsx +169 -0
  37. package/components/forms/FormInput.tsx +78 -0
  38. package/components/forms/index.ts +1 -0
  39. package/components/onboarding/OnboardingScreen.tsx +370 -0
  40. package/components/onboarding/index.ts +2 -0
  41. package/components/ui/AnimatedButton.tsx +156 -0
  42. package/components/ui/AnimatedCard.tsx +108 -0
  43. package/components/ui/Avatar.tsx +316 -0
  44. package/components/ui/Badge.tsx +416 -0
  45. package/components/ui/BottomSheet.tsx +307 -0
  46. package/components/ui/Button.stories.tsx +115 -0
  47. package/components/ui/Button.tsx +104 -0
  48. package/components/ui/Card.stories.tsx +84 -0
  49. package/components/ui/Card.tsx +32 -0
  50. package/components/ui/Checkbox.tsx +261 -0
  51. package/components/ui/Input.stories.tsx +106 -0
  52. package/components/ui/Input.tsx +117 -0
  53. package/components/ui/Modal.tsx +98 -0
  54. package/components/ui/OptimizedImage.tsx +369 -0
  55. package/components/ui/Select.tsx +240 -0
  56. package/components/ui/Skeleton.tsx +180 -0
  57. package/components/ui/index.ts +18 -0
  58. package/constants/config.ts +54 -0
  59. package/docs/adr/001-state-management.md +79 -0
  60. package/docs/adr/002-styling-approach.md +130 -0
  61. package/docs/adr/003-data-fetching.md +155 -0
  62. package/docs/adr/004-auth-adapter-pattern.md +144 -0
  63. package/docs/adr/README.md +78 -0
  64. package/eas.json +47 -0
  65. package/global.css +10 -0
  66. package/hooks/index.ts +25 -0
  67. package/hooks/useApi.ts +236 -0
  68. package/hooks/useAuth.tsx +290 -0
  69. package/hooks/useBiometrics.ts +295 -0
  70. package/hooks/useDeepLinking.ts +256 -0
  71. package/hooks/useNotifications.ts +138 -0
  72. package/hooks/useOffline.ts +69 -0
  73. package/hooks/usePerformance.ts +434 -0
  74. package/hooks/useTheme.tsx +85 -0
  75. package/hooks/useUpdates.ts +358 -0
  76. package/i18n/index.ts +77 -0
  77. package/i18n/locales/en.json +101 -0
  78. package/i18n/locales/fr.json +101 -0
  79. package/jest.config.js +32 -0
  80. package/maestro/README.md +113 -0
  81. package/maestro/config.yaml +35 -0
  82. package/maestro/flows/login.yaml +62 -0
  83. package/maestro/flows/navigation.yaml +68 -0
  84. package/maestro/flows/offline.yaml +60 -0
  85. package/maestro/flows/register.yaml +94 -0
  86. package/metro.config.js +6 -0
  87. package/nativewind-env.d.ts +1 -0
  88. package/package.json +170 -0
  89. package/scripts/init.ps1 +162 -0
  90. package/scripts/init.sh +174 -0
  91. package/services/analytics.ts +428 -0
  92. package/services/api.ts +340 -0
  93. package/services/authAdapter.ts +333 -0
  94. package/services/index.ts +22 -0
  95. package/services/queryClient.ts +97 -0
  96. package/services/sentry.ts +131 -0
  97. package/services/storage.ts +82 -0
  98. package/stores/appStore.ts +54 -0
  99. package/stores/index.ts +2 -0
  100. package/stores/notificationStore.ts +40 -0
  101. package/tailwind.config.js +47 -0
  102. package/tsconfig.json +26 -0
  103. package/types/index.ts +42 -0
  104. package/types/user.ts +63 -0
  105. package/utils/accessibility.ts +446 -0
  106. package/utils/cn.ts +14 -0
  107. package/utils/index.ts +43 -0
  108. package/utils/toast.ts +113 -0
  109. package/utils/validation.ts +67 -0
@@ -0,0 +1,117 @@
1
+ import { View, Text, ScrollView } from "react-native";
2
+ import { Link } from "expo-router";
3
+ import { SafeAreaView } from "react-native-safe-area-context";
4
+ import { Ionicons } from "@expo/vector-icons";
5
+
6
+ import { Card } from "@/components/ui/Card";
7
+ import { Button } from "@/components/ui/Button";
8
+ import { useAuth } from "@/hooks/useAuth";
9
+ import { useTheme } from "@/hooks/useTheme";
10
+
11
+ export default function HomeScreen() {
12
+ const { user } = useAuth();
13
+ const { isDark } = useTheme();
14
+
15
+ return (
16
+ <SafeAreaView className="flex-1 bg-background-light dark:bg-background-dark">
17
+ <ScrollView className="flex-1 px-4 pt-4">
18
+ {/* Header */}
19
+ <View className="mb-6 flex-row items-center justify-between">
20
+ <View>
21
+ <Text className="text-muted-light dark:text-muted-dark">
22
+ Welcome back,
23
+ </Text>
24
+ <Text className="text-2xl font-bold text-text-light dark:text-text-dark">
25
+ {user?.name || "User"}
26
+ </Text>
27
+ </View>
28
+ <Link href="/(auth)/profile" asChild>
29
+ <View className="h-12 w-12 items-center justify-center rounded-full bg-primary-100 dark:bg-primary-900">
30
+ <Ionicons
31
+ name="person"
32
+ size={24}
33
+ color={isDark ? "#93c5fd" : "#2563eb"}
34
+ />
35
+ </View>
36
+ </Link>
37
+ </View>
38
+
39
+ {/* Quick Actions */}
40
+ <Text className="mb-3 text-lg font-semibold text-text-light dark:text-text-dark">
41
+ Quick Actions
42
+ </Text>
43
+ <View className="mb-6 flex-row gap-3">
44
+ <Card className="flex-1 items-center p-4">
45
+ <View className="mb-2 h-12 w-12 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900/30">
46
+ <Ionicons
47
+ name="add"
48
+ size={24}
49
+ color={isDark ? "#93c5fd" : "#2563eb"}
50
+ />
51
+ </View>
52
+ <Text className="text-sm text-text-light dark:text-text-dark">
53
+ New Item
54
+ </Text>
55
+ </Card>
56
+ <Card className="flex-1 items-center p-4">
57
+ <View className="mb-2 h-12 w-12 items-center justify-center rounded-full bg-green-100 dark:bg-green-900/30">
58
+ <Ionicons
59
+ name="search"
60
+ size={24}
61
+ color={isDark ? "#86efac" : "#16a34a"}
62
+ />
63
+ </View>
64
+ <Text className="text-sm text-text-light dark:text-text-dark">
65
+ Search
66
+ </Text>
67
+ </Card>
68
+ <Card className="flex-1 items-center p-4">
69
+ <View className="mb-2 h-12 w-12 items-center justify-center rounded-full bg-purple-100 dark:bg-purple-900/30">
70
+ <Ionicons
71
+ name="stats-chart"
72
+ size={24}
73
+ color={isDark ? "#c4b5fd" : "#7c3aed"}
74
+ />
75
+ </View>
76
+ <Text className="text-sm text-text-light dark:text-text-dark">
77
+ Stats
78
+ </Text>
79
+ </Card>
80
+ </View>
81
+
82
+ {/* Recent Activity */}
83
+ <Text className="mb-3 text-lg font-semibold text-text-light dark:text-text-dark">
84
+ Recent Activity
85
+ </Text>
86
+ <Card className="mb-4 p-4">
87
+ <View className="items-center py-8">
88
+ <Ionicons
89
+ name="time-outline"
90
+ size={48}
91
+ color={isDark ? "#64748b" : "#94a3b8"}
92
+ />
93
+ <Text className="mt-2 text-muted-light dark:text-muted-dark">
94
+ No recent activity
95
+ </Text>
96
+ <Text className="mt-1 text-sm text-muted-light dark:text-muted-dark">
97
+ Your activity will appear here
98
+ </Text>
99
+ </View>
100
+ </Card>
101
+
102
+ {/* Settings Link */}
103
+ <Link href="/(auth)/settings" asChild>
104
+ <Button variant="outline" className="mb-8">
105
+ <Ionicons
106
+ name="settings-outline"
107
+ size={20}
108
+ color={isDark ? "#f8fafc" : "#0f172a"}
109
+ style={{ marginRight: 8 }}
110
+ />
111
+ Settings
112
+ </Button>
113
+ </Link>
114
+ </ScrollView>
115
+ </SafeAreaView>
116
+ );
117
+ }
@@ -0,0 +1,152 @@
1
+ import { View, Text, ScrollView, Pressable } from "react-native";
2
+ import { router } from "expo-router";
3
+ import { SafeAreaView } from "react-native-safe-area-context";
4
+ import { Ionicons } from "@expo/vector-icons";
5
+
6
+ import { Card } from "@/components/ui/Card";
7
+ import { Button } from "@/components/ui/Button";
8
+ import { useAuth } from "@/hooks/useAuth";
9
+ import { useTheme } from "@/hooks/useTheme";
10
+
11
+ export default function ProfileScreen() {
12
+ const { user, signOut } = useAuth();
13
+ const { isDark } = useTheme();
14
+
15
+ const handleSignOut = async () => {
16
+ await signOut();
17
+ router.replace("/(public)/login");
18
+ };
19
+
20
+ return (
21
+ <SafeAreaView className="flex-1 bg-background-light dark:bg-background-dark">
22
+ <ScrollView className="flex-1">
23
+ {/* Header */}
24
+ <View className="flex-row items-center px-4 py-2">
25
+ <Pressable onPress={() => router.back()} className="mr-4">
26
+ <Ionicons
27
+ name="arrow-back"
28
+ size={24}
29
+ color={isDark ? "#f8fafc" : "#0f172a"}
30
+ />
31
+ </Pressable>
32
+ <Text className="text-xl font-semibold text-text-light dark:text-text-dark">
33
+ Profile
34
+ </Text>
35
+ </View>
36
+
37
+ {/* Avatar */}
38
+ <View className="items-center py-8">
39
+ <View className="mb-4 h-24 w-24 items-center justify-center rounded-full bg-primary-100 dark:bg-primary-900">
40
+ <Text className="text-4xl font-bold text-primary-600 dark:text-primary-400">
41
+ {user?.name?.charAt(0).toUpperCase() || "U"}
42
+ </Text>
43
+ </View>
44
+ <Text className="text-2xl font-bold text-text-light dark:text-text-dark">
45
+ {user?.name || "User"}
46
+ </Text>
47
+ <Text className="text-muted-light dark:text-muted-dark">
48
+ {user?.email || "user@example.com"}
49
+ </Text>
50
+ </View>
51
+
52
+ {/* Info Cards */}
53
+ <View className="px-4">
54
+ <Card className="mb-4">
55
+ <Pressable className="flex-row items-center justify-between p-4">
56
+ <View className="flex-row items-center">
57
+ <Ionicons
58
+ name="person-outline"
59
+ size={24}
60
+ color={isDark ? "#94a3b8" : "#64748b"}
61
+ />
62
+ <Text className="ml-3 text-text-light dark:text-text-dark">
63
+ Edit Profile
64
+ </Text>
65
+ </View>
66
+ <Ionicons
67
+ name="chevron-forward"
68
+ size={20}
69
+ color={isDark ? "#64748b" : "#94a3b8"}
70
+ />
71
+ </Pressable>
72
+ </Card>
73
+
74
+ <Card className="mb-4">
75
+ <Pressable className="flex-row items-center justify-between p-4">
76
+ <View className="flex-row items-center">
77
+ <Ionicons
78
+ name="notifications-outline"
79
+ size={24}
80
+ color={isDark ? "#94a3b8" : "#64748b"}
81
+ />
82
+ <Text className="ml-3 text-text-light dark:text-text-dark">
83
+ Notifications
84
+ </Text>
85
+ </View>
86
+ <Ionicons
87
+ name="chevron-forward"
88
+ size={20}
89
+ color={isDark ? "#64748b" : "#94a3b8"}
90
+ />
91
+ </Pressable>
92
+ </Card>
93
+
94
+ <Card className="mb-4">
95
+ <Pressable className="flex-row items-center justify-between p-4">
96
+ <View className="flex-row items-center">
97
+ <Ionicons
98
+ name="shield-outline"
99
+ size={24}
100
+ color={isDark ? "#94a3b8" : "#64748b"}
101
+ />
102
+ <Text className="ml-3 text-text-light dark:text-text-dark">
103
+ Privacy & Security
104
+ </Text>
105
+ </View>
106
+ <Ionicons
107
+ name="chevron-forward"
108
+ size={20}
109
+ color={isDark ? "#64748b" : "#94a3b8"}
110
+ />
111
+ </Pressable>
112
+ </Card>
113
+
114
+ <Card className="mb-4">
115
+ <Pressable className="flex-row items-center justify-between p-4">
116
+ <View className="flex-row items-center">
117
+ <Ionicons
118
+ name="help-circle-outline"
119
+ size={24}
120
+ color={isDark ? "#94a3b8" : "#64748b"}
121
+ />
122
+ <Text className="ml-3 text-text-light dark:text-text-dark">
123
+ Help & Support
124
+ </Text>
125
+ </View>
126
+ <Ionicons
127
+ name="chevron-forward"
128
+ size={20}
129
+ color={isDark ? "#64748b" : "#94a3b8"}
130
+ />
131
+ </Pressable>
132
+ </Card>
133
+
134
+ {/* Sign Out */}
135
+ <Button
136
+ variant="outline"
137
+ onPress={handleSignOut}
138
+ className="mt-4 mb-8 border-red-500"
139
+ >
140
+ <Ionicons
141
+ name="log-out-outline"
142
+ size={20}
143
+ color="#ef4444"
144
+ style={{ marginRight: 8 }}
145
+ />
146
+ <Text className="text-red-500">Sign Out</Text>
147
+ </Button>
148
+ </View>
149
+ </ScrollView>
150
+ </SafeAreaView>
151
+ );
152
+ }
@@ -0,0 +1,147 @@
1
+ import { View, Text, ScrollView, Pressable, Switch } from "react-native";
2
+ import { router } from "expo-router";
3
+ import { SafeAreaView } from "react-native-safe-area-context";
4
+ import { Ionicons } from "@expo/vector-icons";
5
+
6
+ import { Card } from "@/components/ui/Card";
7
+ import { useTheme } from "@/hooks/useTheme";
8
+ import { useNotificationStore } from "@/stores/notificationStore";
9
+
10
+ export default function SettingsScreen() {
11
+ const { isDark, toggleTheme } = useTheme();
12
+ const { isEnabled, toggleNotifications } = useNotificationStore();
13
+
14
+ return (
15
+ <SafeAreaView className="flex-1 bg-background-light dark:bg-background-dark">
16
+ <ScrollView className="flex-1">
17
+ {/* Header */}
18
+ <View className="flex-row items-center px-4 py-2">
19
+ <Pressable onPress={() => router.back()} className="mr-4">
20
+ <Ionicons
21
+ name="arrow-back"
22
+ size={24}
23
+ color={isDark ? "#f8fafc" : "#0f172a"}
24
+ />
25
+ </Pressable>
26
+ <Text className="text-xl font-semibold text-text-light dark:text-text-dark">
27
+ Settings
28
+ </Text>
29
+ </View>
30
+
31
+ <View className="px-4 pt-4">
32
+ {/* Appearance */}
33
+ <Text className="mb-3 text-sm font-medium uppercase text-muted-light dark:text-muted-dark">
34
+ Appearance
35
+ </Text>
36
+ <Card className="mb-6">
37
+ <View className="flex-row items-center justify-between p-4">
38
+ <View className="flex-row items-center">
39
+ <Ionicons
40
+ name={isDark ? "moon" : "sunny"}
41
+ size={24}
42
+ color={isDark ? "#94a3b8" : "#64748b"}
43
+ />
44
+ <Text className="ml-3 text-text-light dark:text-text-dark">
45
+ Dark Mode
46
+ </Text>
47
+ </View>
48
+ <Switch
49
+ value={isDark}
50
+ onValueChange={toggleTheme}
51
+ trackColor={{ false: "#cbd5e1", true: "#3b82f6" }}
52
+ thumbColor="#ffffff"
53
+ />
54
+ </View>
55
+ </Card>
56
+
57
+ {/* Notifications */}
58
+ <Text className="mb-3 text-sm font-medium uppercase text-muted-light dark:text-muted-dark">
59
+ Notifications
60
+ </Text>
61
+ <Card className="mb-6">
62
+ <View className="flex-row items-center justify-between p-4">
63
+ <View className="flex-row items-center">
64
+ <Ionicons
65
+ name="notifications-outline"
66
+ size={24}
67
+ color={isDark ? "#94a3b8" : "#64748b"}
68
+ />
69
+ <Text className="ml-3 text-text-light dark:text-text-dark">
70
+ Push Notifications
71
+ </Text>
72
+ </View>
73
+ <Switch
74
+ value={isEnabled}
75
+ onValueChange={toggleNotifications}
76
+ trackColor={{ false: "#cbd5e1", true: "#3b82f6" }}
77
+ thumbColor="#ffffff"
78
+ />
79
+ </View>
80
+ </Card>
81
+
82
+ {/* About */}
83
+ <Text className="mb-3 text-sm font-medium uppercase text-muted-light dark:text-muted-dark">
84
+ About
85
+ </Text>
86
+ <Card className="mb-6">
87
+ <Pressable className="flex-row items-center justify-between p-4">
88
+ <View className="flex-row items-center">
89
+ <Ionicons
90
+ name="information-circle-outline"
91
+ size={24}
92
+ color={isDark ? "#94a3b8" : "#64748b"}
93
+ />
94
+ <Text className="ml-3 text-text-light dark:text-text-dark">
95
+ App Version
96
+ </Text>
97
+ </View>
98
+ <Text className="text-muted-light dark:text-muted-dark">
99
+ 1.0.0
100
+ </Text>
101
+ </Pressable>
102
+
103
+ <View className="mx-4 h-px bg-gray-200 dark:bg-gray-700" />
104
+
105
+ <Pressable className="flex-row items-center justify-between p-4">
106
+ <View className="flex-row items-center">
107
+ <Ionicons
108
+ name="document-text-outline"
109
+ size={24}
110
+ color={isDark ? "#94a3b8" : "#64748b"}
111
+ />
112
+ <Text className="ml-3 text-text-light dark:text-text-dark">
113
+ Terms of Service
114
+ </Text>
115
+ </View>
116
+ <Ionicons
117
+ name="chevron-forward"
118
+ size={20}
119
+ color={isDark ? "#64748b" : "#94a3b8"}
120
+ />
121
+ </Pressable>
122
+
123
+ <View className="mx-4 h-px bg-gray-200 dark:bg-gray-700" />
124
+
125
+ <Pressable className="flex-row items-center justify-between p-4">
126
+ <View className="flex-row items-center">
127
+ <Ionicons
128
+ name="lock-closed-outline"
129
+ size={24}
130
+ color={isDark ? "#94a3b8" : "#64748b"}
131
+ />
132
+ <Text className="ml-3 text-text-light dark:text-text-dark">
133
+ Privacy Policy
134
+ </Text>
135
+ </View>
136
+ <Ionicons
137
+ name="chevron-forward"
138
+ size={20}
139
+ color={isDark ? "#64748b" : "#94a3b8"}
140
+ />
141
+ </Pressable>
142
+ </Card>
143
+ </View>
144
+ </ScrollView>
145
+ </SafeAreaView>
146
+ );
147
+ }
@@ -0,0 +1,21 @@
1
+ import { Stack } from "expo-router";
2
+ import { useTheme } from "@/hooks/useTheme";
3
+
4
+ export default function PublicLayout() {
5
+ const { isDark } = useTheme();
6
+
7
+ return (
8
+ <Stack
9
+ screenOptions={{
10
+ headerShown: false,
11
+ contentStyle: {
12
+ backgroundColor: isDark ? "#0f172a" : "#ffffff",
13
+ },
14
+ }}
15
+ >
16
+ <Stack.Screen name="login" />
17
+ <Stack.Screen name="register" />
18
+ <Stack.Screen name="forgot-password" />
19
+ </Stack>
20
+ );
21
+ }
@@ -0,0 +1,127 @@
1
+ import { useState } from "react";
2
+ import { View, Text, Pressable, KeyboardAvoidingView, Platform } from "react-native";
3
+ import { Link, router } from "expo-router";
4
+ import { SafeAreaView } from "react-native-safe-area-context";
5
+ import { Ionicons } from "@expo/vector-icons";
6
+
7
+ import { Input } from "@/components/ui/Input";
8
+ import { Button } from "@/components/ui/Button";
9
+
10
+ export default function ForgotPasswordScreen() {
11
+ const [email, setEmail] = useState("");
12
+ const [error, setError] = useState("");
13
+ const [success, setSuccess] = useState(false);
14
+ const [isLoading, setIsLoading] = useState(false);
15
+
16
+ const handleResetPassword = async () => {
17
+ if (!email) {
18
+ setError("Please enter your email");
19
+ return;
20
+ }
21
+
22
+ setIsLoading(true);
23
+ setError("");
24
+
25
+ try {
26
+ // TODO: Implement password reset API call
27
+ // await api.resetPassword(email);
28
+ await new Promise((resolve) => setTimeout(resolve, 1000));
29
+ setSuccess(true);
30
+ } catch (err) {
31
+ setError("Failed to send reset email. Please try again.");
32
+ } finally {
33
+ setIsLoading(false);
34
+ }
35
+ };
36
+
37
+ if (success) {
38
+ return (
39
+ <SafeAreaView className="flex-1 bg-background-light dark:bg-background-dark">
40
+ <View className="flex-1 items-center justify-center px-6">
41
+ <View className="mb-6 h-20 w-20 items-center justify-center rounded-full bg-green-100 dark:bg-green-900/30">
42
+ <Ionicons name="mail-outline" size={40} color="#22c55e" />
43
+ </View>
44
+ <Text className="text-center text-2xl font-bold text-text-light dark:text-text-dark">
45
+ Check your email
46
+ </Text>
47
+ <Text className="mt-2 text-center text-muted-light dark:text-muted-dark">
48
+ We've sent a password reset link to {email}
49
+ </Text>
50
+ <Button
51
+ onPress={() => router.replace("/(public)/login")}
52
+ className="mt-8"
53
+ >
54
+ Back to Sign In
55
+ </Button>
56
+ </View>
57
+ </SafeAreaView>
58
+ );
59
+ }
60
+
61
+ return (
62
+ <SafeAreaView className="flex-1 bg-background-light dark:bg-background-dark">
63
+ <KeyboardAvoidingView
64
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
65
+ className="flex-1"
66
+ >
67
+ <View className="flex-1 justify-center px-6">
68
+ <Pressable
69
+ onPress={() => router.back()}
70
+ className="mb-8 flex-row items-center"
71
+ >
72
+ <Ionicons name="arrow-back" size={24} color="#64748b" />
73
+ <Text className="ml-2 text-muted-light dark:text-muted-dark">Back</Text>
74
+ </Pressable>
75
+
76
+ <View className="mb-8">
77
+ <Text className="text-3xl font-bold text-text-light dark:text-text-dark">
78
+ Reset password
79
+ </Text>
80
+ <Text className="mt-2 text-muted-light dark:text-muted-dark">
81
+ Enter your email and we'll send you a reset link
82
+ </Text>
83
+ </View>
84
+
85
+ {error ? (
86
+ <View className="mb-4 rounded-lg bg-red-100 p-3 dark:bg-red-900/30">
87
+ <Text className="text-red-600 dark:text-red-400">{error}</Text>
88
+ </View>
89
+ ) : null}
90
+
91
+ <View className="gap-4">
92
+ <Input
93
+ label="Email"
94
+ placeholder="Enter your email"
95
+ value={email}
96
+ onChangeText={setEmail}
97
+ keyboardType="email-address"
98
+ autoCapitalize="none"
99
+ autoComplete="email"
100
+ />
101
+
102
+ <Button
103
+ onPress={handleResetPassword}
104
+ isLoading={isLoading}
105
+ className="mt-4"
106
+ >
107
+ Send Reset Link
108
+ </Button>
109
+ </View>
110
+
111
+ <View className="mt-8 flex-row justify-center">
112
+ <Text className="text-muted-light dark:text-muted-dark">
113
+ Remember your password?{" "}
114
+ </Text>
115
+ <Link href="/(public)/login" asChild>
116
+ <Pressable>
117
+ <Text className="font-semibold text-primary-600 dark:text-primary-400">
118
+ Sign In
119
+ </Text>
120
+ </Pressable>
121
+ </Link>
122
+ </View>
123
+ </View>
124
+ </KeyboardAvoidingView>
125
+ </SafeAreaView>
126
+ );
127
+ }
@@ -0,0 +1,120 @@
1
+ import { View, Text, Pressable, KeyboardAvoidingView, Platform } from "react-native";
2
+ import { Link, router } from "expo-router";
3
+ import { SafeAreaView } from "react-native-safe-area-context";
4
+ import { useForm } from "react-hook-form";
5
+ import { zodResolver } from "@hookform/resolvers/zod";
6
+ import Animated, { FadeInDown } from "react-native-reanimated";
7
+
8
+ import { FormInput } from "@/components/forms/FormInput";
9
+ import { AnimatedButton } from "@/components/ui/AnimatedButton";
10
+ import { useAuth } from "@/hooks/useAuth";
11
+ import { loginSchema, LoginFormData } from "@/utils/validation";
12
+
13
+ export default function LoginScreen() {
14
+ const { signIn } = useAuth();
15
+
16
+ const {
17
+ control,
18
+ handleSubmit,
19
+ formState: { isSubmitting },
20
+ } = useForm<LoginFormData>({
21
+ resolver: zodResolver(loginSchema),
22
+ defaultValues: {
23
+ email: "",
24
+ password: "",
25
+ },
26
+ });
27
+
28
+ const onSubmit = async (data: LoginFormData) => {
29
+ try {
30
+ await signIn(data.email, data.password);
31
+ router.replace("/(auth)/home");
32
+ } catch {
33
+ // Error is handled by useAuth with toast
34
+ }
35
+ };
36
+
37
+ return (
38
+ <SafeAreaView className="flex-1 bg-background-light dark:bg-background-dark">
39
+ <KeyboardAvoidingView
40
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
41
+ className="flex-1"
42
+ >
43
+ <View className="flex-1 justify-center px-6">
44
+ {/* Header */}
45
+ <Animated.View
46
+ entering={FadeInDown.delay(100).springify()}
47
+ className="mb-8"
48
+ >
49
+ <Text className="text-3xl font-bold text-text-light dark:text-text-dark">
50
+ Welcome back
51
+ </Text>
52
+ <Text className="mt-2 text-muted-light dark:text-muted-dark">
53
+ Sign in to your account
54
+ </Text>
55
+ </Animated.View>
56
+
57
+ {/* Form */}
58
+ <Animated.View
59
+ entering={FadeInDown.delay(200).springify()}
60
+ className="gap-4"
61
+ >
62
+ <FormInput
63
+ name="email"
64
+ control={control}
65
+ label="Email"
66
+ placeholder="Enter your email"
67
+ keyboardType="email-address"
68
+ autoCapitalize="none"
69
+ autoComplete="email"
70
+ leftIcon="mail-outline"
71
+ />
72
+
73
+ <FormInput
74
+ name="password"
75
+ control={control}
76
+ label="Password"
77
+ placeholder="Enter your password"
78
+ secureTextEntry
79
+ autoComplete="password"
80
+ leftIcon="lock-closed-outline"
81
+ />
82
+
83
+ <Link href="/(public)/forgot-password" asChild>
84
+ <Pressable className="self-end">
85
+ <Text className="text-primary-600 dark:text-primary-400">
86
+ Forgot password?
87
+ </Text>
88
+ </Pressable>
89
+ </Link>
90
+
91
+ <AnimatedButton
92
+ onPress={handleSubmit(onSubmit)}
93
+ isLoading={isSubmitting}
94
+ className="mt-4"
95
+ >
96
+ Sign In
97
+ </AnimatedButton>
98
+ </Animated.View>
99
+
100
+ {/* Footer */}
101
+ <Animated.View
102
+ entering={FadeInDown.delay(300).springify()}
103
+ className="mt-8 flex-row justify-center"
104
+ >
105
+ <Text className="text-muted-light dark:text-muted-dark">
106
+ Don't have an account?{" "}
107
+ </Text>
108
+ <Link href="/(public)/register" asChild>
109
+ <Pressable>
110
+ <Text className="font-semibold text-primary-600 dark:text-primary-400">
111
+ Sign Up
112
+ </Text>
113
+ </Pressable>
114
+ </Link>
115
+ </Animated.View>
116
+ </View>
117
+ </KeyboardAvoidingView>
118
+ </SafeAreaView>
119
+ );
120
+ }