@teardown/cli 2.0.65 → 2.0.67
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.
- package/package.json +2 -2
- package/src/templates/generator.ts +66 -42
- package/templates/{src/index.tsx → index.tsx} +5 -2
- package/templates/metro.config.js +7 -35
- package/templates/src/components/ui/accordion.tsx +23 -21
- package/templates/src/components/ui/card.tsx +28 -26
- package/templates/src/components/ui/checkbox.tsx +12 -10
- package/templates/src/components/ui/dialog.tsx +30 -28
- package/templates/src/components/ui/divider.tsx +6 -6
- package/templates/src/components/ui/form-field.tsx +2 -2
- package/templates/src/components/ui/pressable-feedback.tsx +2 -2
- package/templates/src/components/ui/radio-group.tsx +37 -35
- package/templates/src/components/ui/scroll-shadow.tsx +4 -4
- package/templates/src/components/ui/select.tsx +31 -29
- package/templates/src/components/ui/skeleton-group.tsx +2 -2
- package/templates/src/components/ui/skeleton.tsx +2 -2
- package/templates/src/components/ui/spinner.tsx +2 -2
- package/templates/src/components/ui/tabs.tsx +25 -23
- package/templates/src/global.css +86 -70
- package/templates/src/navigation/navigation-provider.tsx +8 -1
- package/templates/src/navigation/router.tsx +12 -95
- package/templates/src/providers/app.provider.tsx +8 -12
- package/templates/src/routes/_layout.tsx +2 -4
- package/templates/src/routes/home.tsx +112 -0
- package/templates/tsconfig.json +10 -12
- package/templates/index.ts +0 -11
- package/templates/src/components/ui/index.ts +0 -100
- package/templates/src/routes/(tabs)/_layout.tsx +0 -42
- package/templates/src/routes/(tabs)/explore.tsx +0 -161
- package/templates/src/routes/(tabs)/home.tsx +0 -138
- package/templates/src/routes/(tabs)/profile.tsx +0 -114
- package/templates/src/routes/settings.tsx +0 -184
- package/templates/src/screens/auth/index.ts +0 -6
- package/templates/src/screens/auth/login.tsx +0 -165
- package/templates/src/screens/auth/register.tsx +0 -203
- package/templates/src/screens/home.tsx +0 -204
- package/templates/src/screens/index.ts +0 -17
- package/templates/src/screens/profile.tsx +0 -210
- package/templates/src/screens/settings.tsx +0 -216
- package/templates/src/screens/welcome.tsx +0 -101
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Home Screen
|
|
3
|
-
*
|
|
4
|
-
* The main landing screen of the app.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { defineScreen } from "@teardown/navigation/primitives";
|
|
8
|
-
import type React from "react";
|
|
9
|
-
import { Pressable, ScrollView, View } from "react-native";
|
|
10
|
-
import {
|
|
11
|
-
Badge,
|
|
12
|
-
Button,
|
|
13
|
-
Card,
|
|
14
|
-
CardContent,
|
|
15
|
-
CardDescription,
|
|
16
|
-
CardHeader,
|
|
17
|
-
CardTitle,
|
|
18
|
-
Text,
|
|
19
|
-
} from "@/components/ui";
|
|
20
|
-
import { useTypedNavigation } from "@/navigation";
|
|
21
|
-
|
|
22
|
-
function HomeScreen(): React.JSX.Element {
|
|
23
|
-
const navigation = useTypedNavigation();
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<ScrollView className="flex-1 bg-background" contentContainerClassName="p-6 gap-6">
|
|
27
|
-
{/* Hero Section */}
|
|
28
|
-
<View className="items-center gap-4 py-8">
|
|
29
|
-
<View className="h-20 w-20 items-center justify-center rounded-2xl bg-primary">
|
|
30
|
-
<Text className="text-3xl font-bold text-primary-foreground">T</Text>
|
|
31
|
-
</View>
|
|
32
|
-
<View className="items-center gap-2">
|
|
33
|
-
<Text variant="h1" className="text-center">
|
|
34
|
-
Welcome to <%= appName %>
|
|
35
|
-
</Text>
|
|
36
|
-
<Text variant="lead" className="text-center">
|
|
37
|
-
Your React Native app with type-safe navigation
|
|
38
|
-
</Text>
|
|
39
|
-
</View>
|
|
40
|
-
</View>
|
|
41
|
-
|
|
42
|
-
{/* Quick Actions */}
|
|
43
|
-
<Card>
|
|
44
|
-
<CardHeader>
|
|
45
|
-
<CardTitle>Quick Actions</CardTitle>
|
|
46
|
-
<CardDescription>Navigate through your app</CardDescription>
|
|
47
|
-
</CardHeader>
|
|
48
|
-
<CardContent className="gap-3">
|
|
49
|
-
<Button onPress={() => navigation.navigate("/settings")} fullWidth>
|
|
50
|
-
Open Settings
|
|
51
|
-
</Button>
|
|
52
|
-
<Button variant="secondary" onPress={() => navigation.navigate("/(tabs)/explore")} fullWidth>
|
|
53
|
-
Explore Content
|
|
54
|
-
</Button>
|
|
55
|
-
</CardContent>
|
|
56
|
-
</Card>
|
|
57
|
-
|
|
58
|
-
{/* Features */}
|
|
59
|
-
<View className="gap-4">
|
|
60
|
-
<Text variant="h3">Features</Text>
|
|
61
|
-
<FeatureCard
|
|
62
|
-
icon="🔒"
|
|
63
|
-
title="Type-Safe Navigation"
|
|
64
|
-
description="Routes are fully typed with TypeScript"
|
|
65
|
-
tags={["TypeScript", "Safe"]}
|
|
66
|
-
/>
|
|
67
|
-
<FeatureCard
|
|
68
|
-
icon="📁"
|
|
69
|
-
title="File-Based Routing"
|
|
70
|
-
description="Routes generated from file structure"
|
|
71
|
-
tags={["Auto", "Generated"]}
|
|
72
|
-
/>
|
|
73
|
-
<FeatureCard
|
|
74
|
-
icon="📱"
|
|
75
|
-
title="Tab Navigation"
|
|
76
|
-
description="Bottom tabs with Home, Explore, Profile"
|
|
77
|
-
tags={["Tabs", "Navigate"]}
|
|
78
|
-
/>
|
|
79
|
-
</View>
|
|
80
|
-
|
|
81
|
-
{/* Getting Started */}
|
|
82
|
-
<Card className="bg-primary/5 border-primary/20">
|
|
83
|
-
<CardContent className="py-4">
|
|
84
|
-
<Text variant="large">Getting Started</Text>
|
|
85
|
-
<Text variant="muted" className="mt-2">
|
|
86
|
-
1. Explore the tabs to see different screens{"\n"}
|
|
87
|
-
2. Open Settings to see stack navigation{"\n"}
|
|
88
|
-
3. Customize screens in src/routes/{"\n"}
|
|
89
|
-
4. Build your app!
|
|
90
|
-
</Text>
|
|
91
|
-
</CardContent>
|
|
92
|
-
</Card>
|
|
93
|
-
</ScrollView>
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function FeatureCard({
|
|
98
|
-
icon,
|
|
99
|
-
title,
|
|
100
|
-
description,
|
|
101
|
-
tags,
|
|
102
|
-
}: {
|
|
103
|
-
icon: string;
|
|
104
|
-
title: string;
|
|
105
|
-
description: string;
|
|
106
|
-
tags: string[];
|
|
107
|
-
}): React.JSX.Element {
|
|
108
|
-
return (
|
|
109
|
-
<Card>
|
|
110
|
-
<CardHeader className="flex-row items-start gap-3 pb-2">
|
|
111
|
-
<View className="h-10 w-10 items-center justify-center rounded-lg bg-muted">
|
|
112
|
-
<Text className="text-xl">{icon}</Text>
|
|
113
|
-
</View>
|
|
114
|
-
<View className="flex-1">
|
|
115
|
-
<CardTitle className="text-base">{title}</CardTitle>
|
|
116
|
-
<CardDescription>{description}</CardDescription>
|
|
117
|
-
</View>
|
|
118
|
-
</CardHeader>
|
|
119
|
-
<CardContent>
|
|
120
|
-
<View className="flex-row flex-wrap gap-2">
|
|
121
|
-
{tags.map((tag) => (
|
|
122
|
-
<Badge key={tag} variant="secondary">
|
|
123
|
-
{tag}
|
|
124
|
-
</Badge>
|
|
125
|
-
))}
|
|
126
|
-
</View>
|
|
127
|
-
</CardContent>
|
|
128
|
-
</Card>
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export default defineScreen({
|
|
133
|
-
component: HomeScreen,
|
|
134
|
-
options: {
|
|
135
|
-
title: "Home",
|
|
136
|
-
tabBarLabel: "Home",
|
|
137
|
-
},
|
|
138
|
-
});
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Profile Screen
|
|
3
|
-
*
|
|
4
|
-
* User profile and account management screen.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { defineScreen } from "@teardown/navigation/primitives";
|
|
8
|
-
import type React from "react";
|
|
9
|
-
import { Pressable, ScrollView, View } from "react-native";
|
|
10
|
-
import { Avatar, Badge, Button, Card, CardContent, Separator, Text } from "@/components/ui";
|
|
11
|
-
import { useTypedNavigation } from "@/navigation";
|
|
12
|
-
|
|
13
|
-
function ProfileScreen(): React.JSX.Element {
|
|
14
|
-
const navigation = useTypedNavigation();
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<ScrollView className="flex-1 bg-background" contentContainerClassName="p-6 gap-6">
|
|
18
|
-
{/* Profile Header */}
|
|
19
|
-
<View className="items-center gap-4 py-4">
|
|
20
|
-
<Avatar fallback="U" size="xl" />
|
|
21
|
-
<View className="items-center gap-1">
|
|
22
|
-
<Text variant="h2">User</Text>
|
|
23
|
-
<Text variant="muted">user@example.com</Text>
|
|
24
|
-
</View>
|
|
25
|
-
<View className="flex-row gap-2">
|
|
26
|
-
<Badge variant="secondary">Free Plan</Badge>
|
|
27
|
-
<Badge variant="outline">Member since 2024</Badge>
|
|
28
|
-
</View>
|
|
29
|
-
</View>
|
|
30
|
-
|
|
31
|
-
{/* Stats */}
|
|
32
|
-
<Card>
|
|
33
|
-
<CardContent className="flex-row justify-around py-4">
|
|
34
|
-
<StatItem label="Projects" value="12" />
|
|
35
|
-
<Separator orientation="vertical" />
|
|
36
|
-
<StatItem label="Tasks" value="48" />
|
|
37
|
-
<Separator orientation="vertical" />
|
|
38
|
-
<StatItem label="Completed" value="36" />
|
|
39
|
-
</CardContent>
|
|
40
|
-
</Card>
|
|
41
|
-
|
|
42
|
-
{/* Account Section */}
|
|
43
|
-
<View className="gap-2">
|
|
44
|
-
<Text variant="label" className="px-1">
|
|
45
|
-
ACCOUNT
|
|
46
|
-
</Text>
|
|
47
|
-
<Card>
|
|
48
|
-
<CardContent className="p-0">
|
|
49
|
-
<MenuItem icon="⚙️" title="Settings" onPress={() => navigation.navigate("/settings")} />
|
|
50
|
-
<Separator />
|
|
51
|
-
<MenuItem icon="🔒" title="Privacy" onPress={() => {}} />
|
|
52
|
-
<Separator />
|
|
53
|
-
<MenuItem icon="🔔" title="Notifications" onPress={() => {}} />
|
|
54
|
-
</CardContent>
|
|
55
|
-
</Card>
|
|
56
|
-
</View>
|
|
57
|
-
|
|
58
|
-
{/* Support Section */}
|
|
59
|
-
<View className="gap-2">
|
|
60
|
-
<Text variant="label" className="px-1">
|
|
61
|
-
SUPPORT
|
|
62
|
-
</Text>
|
|
63
|
-
<Card>
|
|
64
|
-
<CardContent className="p-0">
|
|
65
|
-
<MenuItem icon="❓" title="Help Center" onPress={() => {}} />
|
|
66
|
-
<Separator />
|
|
67
|
-
<MenuItem icon="💬" title="Contact Us" onPress={() => {}} />
|
|
68
|
-
<Separator />
|
|
69
|
-
<MenuItem icon="📝" title="Send Feedback" onPress={() => {}} />
|
|
70
|
-
</CardContent>
|
|
71
|
-
</Card>
|
|
72
|
-
</View>
|
|
73
|
-
|
|
74
|
-
{/* Sign Out */}
|
|
75
|
-
<Button variant="destructive" onPress={() => {}} fullWidth>
|
|
76
|
-
Sign Out
|
|
77
|
-
</Button>
|
|
78
|
-
|
|
79
|
-
{/* App Info */}
|
|
80
|
-
<View className="items-center pt-4">
|
|
81
|
-
<Text variant="muted">Version 1.0.0</Text>
|
|
82
|
-
</View>
|
|
83
|
-
</ScrollView>
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function StatItem({ label, value }: { label: string; value: string }): React.JSX.Element {
|
|
88
|
-
return (
|
|
89
|
-
<View className="items-center px-4">
|
|
90
|
-
<Text variant="h3">{value}</Text>
|
|
91
|
-
<Text variant="muted">{label}</Text>
|
|
92
|
-
</View>
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function MenuItem({ icon, title, onPress }: { icon: string; title: string; onPress: () => void }): React.JSX.Element {
|
|
97
|
-
return (
|
|
98
|
-
<Pressable className="flex-row items-center px-4 py-3 active:bg-muted/50" onPress={onPress}>
|
|
99
|
-
<Text className="text-xl mr-3">{icon}</Text>
|
|
100
|
-
<Text variant="large" className="flex-1">
|
|
101
|
-
{title}
|
|
102
|
-
</Text>
|
|
103
|
-
<Text className="text-muted-foreground">›</Text>
|
|
104
|
-
</Pressable>
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export default defineScreen({
|
|
109
|
-
component: ProfileScreen,
|
|
110
|
-
options: {
|
|
111
|
-
title: "Profile",
|
|
112
|
-
tabBarLabel: "Profile",
|
|
113
|
-
},
|
|
114
|
-
});
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Settings Screen
|
|
3
|
-
*
|
|
4
|
-
* App settings and preferences screen.
|
|
5
|
-
* This is a stack screen outside of the tab navigator.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { defineScreen } from "@teardown/navigation/primitives";
|
|
9
|
-
import React from "react";
|
|
10
|
-
import { Pressable, ScrollView, Switch, View } from "react-native";
|
|
11
|
-
import { Button, Card, CardContent, CardHeader, CardTitle, Separator, Text } from "@/components/ui";
|
|
12
|
-
import { useTypedNavigation } from "@/navigation";
|
|
13
|
-
|
|
14
|
-
function SettingsScreen(): React.JSX.Element {
|
|
15
|
-
const navigation = useTypedNavigation();
|
|
16
|
-
const [notifications, setNotifications] = React.useState(true);
|
|
17
|
-
const [darkMode, setDarkMode] = React.useState(false);
|
|
18
|
-
const [analytics, setAnalytics] = React.useState(true);
|
|
19
|
-
const [biometrics, setBiometrics] = React.useState(false);
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<ScrollView className="flex-1 bg-background" contentContainerClassName="p-6 gap-6">
|
|
23
|
-
{/* Preferences Section */}
|
|
24
|
-
<View className="gap-2">
|
|
25
|
-
<Text variant="label" className="px-1">
|
|
26
|
-
PREFERENCES
|
|
27
|
-
</Text>
|
|
28
|
-
<Card>
|
|
29
|
-
<CardContent className="p-0">
|
|
30
|
-
<SettingRow
|
|
31
|
-
icon="🔔"
|
|
32
|
-
title="Push Notifications"
|
|
33
|
-
description="Receive push notifications"
|
|
34
|
-
value={notifications}
|
|
35
|
-
onValueChange={setNotifications}
|
|
36
|
-
/>
|
|
37
|
-
<Separator />
|
|
38
|
-
<SettingRow
|
|
39
|
-
icon="🌙"
|
|
40
|
-
title="Dark Mode"
|
|
41
|
-
description="Use dark color theme"
|
|
42
|
-
value={darkMode}
|
|
43
|
-
onValueChange={setDarkMode}
|
|
44
|
-
/>
|
|
45
|
-
<Separator />
|
|
46
|
-
<SettingRow
|
|
47
|
-
icon="📊"
|
|
48
|
-
title="Analytics"
|
|
49
|
-
description="Help improve the app"
|
|
50
|
-
value={analytics}
|
|
51
|
-
onValueChange={setAnalytics}
|
|
52
|
-
/>
|
|
53
|
-
<Separator />
|
|
54
|
-
<SettingRow
|
|
55
|
-
icon="🔐"
|
|
56
|
-
title="Biometric Lock"
|
|
57
|
-
description="Require Face ID or Touch ID"
|
|
58
|
-
value={biometrics}
|
|
59
|
-
onValueChange={setBiometrics}
|
|
60
|
-
/>
|
|
61
|
-
</CardContent>
|
|
62
|
-
</Card>
|
|
63
|
-
</View>
|
|
64
|
-
|
|
65
|
-
{/* Storage Section */}
|
|
66
|
-
<View className="gap-2">
|
|
67
|
-
<Text variant="label" className="px-1">
|
|
68
|
-
STORAGE
|
|
69
|
-
</Text>
|
|
70
|
-
<Card>
|
|
71
|
-
<CardContent className="gap-4 py-4">
|
|
72
|
-
<View className="flex-row justify-between">
|
|
73
|
-
<Text>Cache Size</Text>
|
|
74
|
-
<Text variant="muted">24.5 MB</Text>
|
|
75
|
-
</View>
|
|
76
|
-
<Button variant="outline" size="sm">
|
|
77
|
-
Clear Cache
|
|
78
|
-
</Button>
|
|
79
|
-
</CardContent>
|
|
80
|
-
</Card>
|
|
81
|
-
</View>
|
|
82
|
-
|
|
83
|
-
{/* About Section */}
|
|
84
|
-
<View className="gap-2">
|
|
85
|
-
<Text variant="label" className="px-1">
|
|
86
|
-
ABOUT
|
|
87
|
-
</Text>
|
|
88
|
-
<Card>
|
|
89
|
-
<CardContent className="p-0">
|
|
90
|
-
<InfoRow icon="📱" title="Version" value="1.0.0" />
|
|
91
|
-
<Separator />
|
|
92
|
-
<InfoRow icon="🔨" title="Build" value="1" />
|
|
93
|
-
<Separator />
|
|
94
|
-
<InfoRow icon="⚛️" title="React Native" value="0.76.9" />
|
|
95
|
-
</CardContent>
|
|
96
|
-
</Card>
|
|
97
|
-
</View>
|
|
98
|
-
|
|
99
|
-
{/* Legal Section */}
|
|
100
|
-
<View className="gap-2">
|
|
101
|
-
<Text variant="label" className="px-1">
|
|
102
|
-
LEGAL
|
|
103
|
-
</Text>
|
|
104
|
-
<Card>
|
|
105
|
-
<CardContent className="p-0">
|
|
106
|
-
<LinkRow icon="📄" title="Terms of Service" />
|
|
107
|
-
<Separator />
|
|
108
|
-
<LinkRow icon="🔒" title="Privacy Policy" />
|
|
109
|
-
<Separator />
|
|
110
|
-
<LinkRow icon="📜" title="Licenses" />
|
|
111
|
-
</CardContent>
|
|
112
|
-
</Card>
|
|
113
|
-
</View>
|
|
114
|
-
|
|
115
|
-
{/* Back Button */}
|
|
116
|
-
<Button variant="secondary" onPress={() => navigation.goBack()} fullWidth>
|
|
117
|
-
Go Back
|
|
118
|
-
</Button>
|
|
119
|
-
</ScrollView>
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function SettingRow({
|
|
124
|
-
icon,
|
|
125
|
-
title,
|
|
126
|
-
description,
|
|
127
|
-
value,
|
|
128
|
-
onValueChange,
|
|
129
|
-
}: {
|
|
130
|
-
icon: string;
|
|
131
|
-
title: string;
|
|
132
|
-
description: string;
|
|
133
|
-
value: boolean;
|
|
134
|
-
onValueChange: (value: boolean) => void;
|
|
135
|
-
}): React.JSX.Element {
|
|
136
|
-
return (
|
|
137
|
-
<View className="flex-row items-center px-4 py-3">
|
|
138
|
-
<Text className="text-xl mr-3">{icon}</Text>
|
|
139
|
-
<View className="flex-1 mr-4">
|
|
140
|
-
<Text variant="large">{title}</Text>
|
|
141
|
-
<Text variant="muted">{description}</Text>
|
|
142
|
-
</View>
|
|
143
|
-
<Switch
|
|
144
|
-
value={value}
|
|
145
|
-
onValueChange={onValueChange}
|
|
146
|
-
trackColor={{ false: "#d1d5db", true: "#bfdbfe" }}
|
|
147
|
-
thumbColor={value ? "#3b82f6" : "#f3f4f6"}
|
|
148
|
-
/>
|
|
149
|
-
</View>
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function InfoRow({ icon, title, value }: { icon: string; title: string; value: string }): React.JSX.Element {
|
|
154
|
-
return (
|
|
155
|
-
<View className="flex-row items-center px-4 py-3">
|
|
156
|
-
<Text className="text-xl mr-3">{icon}</Text>
|
|
157
|
-
<Text variant="large" className="flex-1">
|
|
158
|
-
{title}
|
|
159
|
-
</Text>
|
|
160
|
-
<Text variant="muted">{value}</Text>
|
|
161
|
-
</View>
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function LinkRow({ icon, title }: { icon: string; title: string }): React.JSX.Element {
|
|
166
|
-
return (
|
|
167
|
-
<Pressable className="flex-row items-center px-4 py-3 active:bg-muted/50">
|
|
168
|
-
<Text className="text-xl mr-3">{icon}</Text>
|
|
169
|
-
<Text variant="large" className="flex-1">
|
|
170
|
-
{title}
|
|
171
|
-
</Text>
|
|
172
|
-
<Text className="text-muted-foreground">›</Text>
|
|
173
|
-
</Pressable>
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export default defineScreen({
|
|
178
|
-
component: SettingsScreen,
|
|
179
|
-
options: {
|
|
180
|
-
title: "Settings",
|
|
181
|
-
headerShown: true,
|
|
182
|
-
animation: "slide_from_right",
|
|
183
|
-
},
|
|
184
|
-
});
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Login Screen
|
|
3
|
-
*
|
|
4
|
-
* Email/password login with social auth options.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import React, { useState } from "react";
|
|
8
|
-
import { View, ScrollView, Pressable, KeyboardAvoidingView, Platform } from "react-native";
|
|
9
|
-
import {
|
|
10
|
-
Text,
|
|
11
|
-
Button,
|
|
12
|
-
Input,
|
|
13
|
-
Card,
|
|
14
|
-
CardHeader,
|
|
15
|
-
CardTitle,
|
|
16
|
-
CardDescription,
|
|
17
|
-
CardContent,
|
|
18
|
-
CardFooter,
|
|
19
|
-
Separator,
|
|
20
|
-
} from "@/components/ui";
|
|
21
|
-
|
|
22
|
-
interface LoginScreenProps {
|
|
23
|
-
onLogin?: (email: string, password: string) => void;
|
|
24
|
-
onForgotPassword?: () => void;
|
|
25
|
-
onRegister?: () => void;
|
|
26
|
-
onSocialLogin?: (provider: "google" | "apple" | "github") => void;
|
|
27
|
-
loading?: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function LoginScreen({
|
|
31
|
-
onLogin,
|
|
32
|
-
onForgotPassword,
|
|
33
|
-
onRegister,
|
|
34
|
-
onSocialLogin,
|
|
35
|
-
loading = false,
|
|
36
|
-
}: LoginScreenProps): React.JSX.Element {
|
|
37
|
-
const [email, setEmail] = useState("");
|
|
38
|
-
const [password, setPassword] = useState("");
|
|
39
|
-
const [errors, setErrors] = useState<{ email?: string; password?: string }>({});
|
|
40
|
-
|
|
41
|
-
const validate = (): boolean => {
|
|
42
|
-
const newErrors: { email?: string; password?: string } = {};
|
|
43
|
-
|
|
44
|
-
if (!email) {
|
|
45
|
-
newErrors.email = "Email is required";
|
|
46
|
-
} else if (!/\S+@\S+\.\S+/.test(email)) {
|
|
47
|
-
newErrors.email = "Please enter a valid email";
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (!password) {
|
|
51
|
-
newErrors.password = "Password is required";
|
|
52
|
-
} else if (password.length < 6) {
|
|
53
|
-
newErrors.password = "Password must be at least 6 characters";
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
setErrors(newErrors);
|
|
57
|
-
return Object.keys(newErrors).length === 0;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const handleLogin = () => {
|
|
61
|
-
if (validate()) {
|
|
62
|
-
onLogin?.(email, password);
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<KeyboardAvoidingView
|
|
68
|
-
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
69
|
-
className="flex-1 bg-background"
|
|
70
|
-
>
|
|
71
|
-
<ScrollView
|
|
72
|
-
contentContainerClassName="flex-grow justify-center p-6"
|
|
73
|
-
keyboardShouldPersistTaps="handled"
|
|
74
|
-
>
|
|
75
|
-
<Card className="w-full max-w-md self-center">
|
|
76
|
-
<CardHeader className="items-center">
|
|
77
|
-
<View className="mb-4 h-16 w-16 items-center justify-center rounded-2xl bg-primary">
|
|
78
|
-
<Text className="text-2xl font-bold text-primary-foreground">T</Text>
|
|
79
|
-
</View>
|
|
80
|
-
<CardTitle>Welcome back</CardTitle>
|
|
81
|
-
<CardDescription>Sign in to your account to continue</CardDescription>
|
|
82
|
-
</CardHeader>
|
|
83
|
-
|
|
84
|
-
<CardContent className="gap-4">
|
|
85
|
-
{/* Social Login Buttons */}
|
|
86
|
-
<View className="gap-3">
|
|
87
|
-
<Button
|
|
88
|
-
variant="outline"
|
|
89
|
-
fullWidth
|
|
90
|
-
onPress={() => onSocialLogin?.("google")}
|
|
91
|
-
>
|
|
92
|
-
<View className="flex-row items-center gap-2">
|
|
93
|
-
<Text>🔵</Text>
|
|
94
|
-
<Text className="font-semibold text-foreground">Continue with Google</Text>
|
|
95
|
-
</View>
|
|
96
|
-
</Button>
|
|
97
|
-
|
|
98
|
-
<Button
|
|
99
|
-
variant="outline"
|
|
100
|
-
fullWidth
|
|
101
|
-
onPress={() => onSocialLogin?.("apple")}
|
|
102
|
-
>
|
|
103
|
-
<View className="flex-row items-center gap-2">
|
|
104
|
-
<Text></Text>
|
|
105
|
-
<Text className="font-semibold text-foreground">Continue with Apple</Text>
|
|
106
|
-
</View>
|
|
107
|
-
</Button>
|
|
108
|
-
</View>
|
|
109
|
-
|
|
110
|
-
{/* Divider */}
|
|
111
|
-
<View className="flex-row items-center gap-4 my-2">
|
|
112
|
-
<Separator className="flex-1" />
|
|
113
|
-
<Text variant="muted">or</Text>
|
|
114
|
-
<Separator className="flex-1" />
|
|
115
|
-
</View>
|
|
116
|
-
|
|
117
|
-
{/* Email/Password Form */}
|
|
118
|
-
<View className="gap-4">
|
|
119
|
-
<Input
|
|
120
|
-
label="Email"
|
|
121
|
-
placeholder="you@example.com"
|
|
122
|
-
value={email}
|
|
123
|
-
onChangeText={setEmail}
|
|
124
|
-
error={errors.email}
|
|
125
|
-
keyboardType="email-address"
|
|
126
|
-
autoCapitalize="none"
|
|
127
|
-
autoComplete="email"
|
|
128
|
-
/>
|
|
129
|
-
|
|
130
|
-
<View>
|
|
131
|
-
<Input
|
|
132
|
-
label="Password"
|
|
133
|
-
placeholder="Enter your password"
|
|
134
|
-
value={password}
|
|
135
|
-
onChangeText={setPassword}
|
|
136
|
-
error={errors.password}
|
|
137
|
-
secureTextEntry
|
|
138
|
-
autoComplete="password"
|
|
139
|
-
/>
|
|
140
|
-
<Pressable onPress={onForgotPassword} className="mt-2 self-end">
|
|
141
|
-
<Text className="text-sm text-primary">Forgot password?</Text>
|
|
142
|
-
</Pressable>
|
|
143
|
-
</View>
|
|
144
|
-
</View>
|
|
145
|
-
</CardContent>
|
|
146
|
-
|
|
147
|
-
<CardFooter className="flex-col gap-4">
|
|
148
|
-
<Button fullWidth loading={loading} onPress={handleLogin}>
|
|
149
|
-
Sign In
|
|
150
|
-
</Button>
|
|
151
|
-
|
|
152
|
-
<View className="flex-row items-center justify-center gap-1">
|
|
153
|
-
<Text variant="muted">Don't have an account?</Text>
|
|
154
|
-
<Pressable onPress={onRegister}>
|
|
155
|
-
<Text className="font-semibold text-primary">Sign up</Text>
|
|
156
|
-
</Pressable>
|
|
157
|
-
</View>
|
|
158
|
-
</CardFooter>
|
|
159
|
-
</Card>
|
|
160
|
-
</ScrollView>
|
|
161
|
-
</KeyboardAvoidingView>
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export default LoginScreen;
|