@teardown/cli 2.0.66 → 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/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/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,203 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Register Screen
|
|
3
|
-
*
|
|
4
|
-
* Account registration with email/password.
|
|
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 RegisterScreenProps {
|
|
23
|
-
onRegister?: (name: string, email: string, password: string) => void;
|
|
24
|
-
onLogin?: () => void;
|
|
25
|
-
onSocialLogin?: (provider: "google" | "apple" | "github") => void;
|
|
26
|
-
loading?: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function RegisterScreen({
|
|
30
|
-
onRegister,
|
|
31
|
-
onLogin,
|
|
32
|
-
onSocialLogin,
|
|
33
|
-
loading = false,
|
|
34
|
-
}: RegisterScreenProps): React.JSX.Element {
|
|
35
|
-
const [name, setName] = useState("");
|
|
36
|
-
const [email, setEmail] = useState("");
|
|
37
|
-
const [password, setPassword] = useState("");
|
|
38
|
-
const [confirmPassword, setConfirmPassword] = useState("");
|
|
39
|
-
const [errors, setErrors] = useState<{
|
|
40
|
-
name?: string;
|
|
41
|
-
email?: string;
|
|
42
|
-
password?: string;
|
|
43
|
-
confirmPassword?: string;
|
|
44
|
-
}>({});
|
|
45
|
-
|
|
46
|
-
const validate = (): boolean => {
|
|
47
|
-
const newErrors: typeof errors = {};
|
|
48
|
-
|
|
49
|
-
if (!name.trim()) {
|
|
50
|
-
newErrors.name = "Name is required";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (!email) {
|
|
54
|
-
newErrors.email = "Email is required";
|
|
55
|
-
} else if (!/\S+@\S+\.\S+/.test(email)) {
|
|
56
|
-
newErrors.email = "Please enter a valid email";
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (!password) {
|
|
60
|
-
newErrors.password = "Password is required";
|
|
61
|
-
} else if (password.length < 8) {
|
|
62
|
-
newErrors.password = "Password must be at least 8 characters";
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (!confirmPassword) {
|
|
66
|
-
newErrors.confirmPassword = "Please confirm your password";
|
|
67
|
-
} else if (password !== confirmPassword) {
|
|
68
|
-
newErrors.confirmPassword = "Passwords do not match";
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
setErrors(newErrors);
|
|
72
|
-
return Object.keys(newErrors).length === 0;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const handleRegister = () => {
|
|
76
|
-
if (validate()) {
|
|
77
|
-
onRegister?.(name, email, password);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<KeyboardAvoidingView
|
|
83
|
-
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
84
|
-
className="flex-1 bg-background"
|
|
85
|
-
>
|
|
86
|
-
<ScrollView
|
|
87
|
-
contentContainerClassName="flex-grow justify-center p-6"
|
|
88
|
-
keyboardShouldPersistTaps="handled"
|
|
89
|
-
>
|
|
90
|
-
<Card className="w-full max-w-md self-center">
|
|
91
|
-
<CardHeader className="items-center">
|
|
92
|
-
<View className="mb-4 h-16 w-16 items-center justify-center rounded-2xl bg-primary">
|
|
93
|
-
<Text className="text-2xl font-bold text-primary-foreground">T</Text>
|
|
94
|
-
</View>
|
|
95
|
-
<CardTitle>Create an account</CardTitle>
|
|
96
|
-
<CardDescription>Enter your details to get started</CardDescription>
|
|
97
|
-
</CardHeader>
|
|
98
|
-
|
|
99
|
-
<CardContent className="gap-4">
|
|
100
|
-
{/* Social Login Buttons */}
|
|
101
|
-
<View className="gap-3">
|
|
102
|
-
<Button
|
|
103
|
-
variant="outline"
|
|
104
|
-
fullWidth
|
|
105
|
-
onPress={() => onSocialLogin?.("google")}
|
|
106
|
-
>
|
|
107
|
-
<View className="flex-row items-center gap-2">
|
|
108
|
-
<Text>🔵</Text>
|
|
109
|
-
<Text className="font-semibold text-foreground">Continue with Google</Text>
|
|
110
|
-
</View>
|
|
111
|
-
</Button>
|
|
112
|
-
|
|
113
|
-
<Button
|
|
114
|
-
variant="outline"
|
|
115
|
-
fullWidth
|
|
116
|
-
onPress={() => onSocialLogin?.("apple")}
|
|
117
|
-
>
|
|
118
|
-
<View className="flex-row items-center gap-2">
|
|
119
|
-
<Text></Text>
|
|
120
|
-
<Text className="font-semibold text-foreground">Continue with Apple</Text>
|
|
121
|
-
</View>
|
|
122
|
-
</Button>
|
|
123
|
-
</View>
|
|
124
|
-
|
|
125
|
-
{/* Divider */}
|
|
126
|
-
<View className="flex-row items-center gap-4 my-2">
|
|
127
|
-
<Separator className="flex-1" />
|
|
128
|
-
<Text variant="muted">or</Text>
|
|
129
|
-
<Separator className="flex-1" />
|
|
130
|
-
</View>
|
|
131
|
-
|
|
132
|
-
{/* Registration Form */}
|
|
133
|
-
<View className="gap-4">
|
|
134
|
-
<Input
|
|
135
|
-
label="Full Name"
|
|
136
|
-
placeholder="John Doe"
|
|
137
|
-
value={name}
|
|
138
|
-
onChangeText={setName}
|
|
139
|
-
error={errors.name}
|
|
140
|
-
autoCapitalize="words"
|
|
141
|
-
autoComplete="name"
|
|
142
|
-
/>
|
|
143
|
-
|
|
144
|
-
<Input
|
|
145
|
-
label="Email"
|
|
146
|
-
placeholder="you@example.com"
|
|
147
|
-
value={email}
|
|
148
|
-
onChangeText={setEmail}
|
|
149
|
-
error={errors.email}
|
|
150
|
-
keyboardType="email-address"
|
|
151
|
-
autoCapitalize="none"
|
|
152
|
-
autoComplete="email"
|
|
153
|
-
/>
|
|
154
|
-
|
|
155
|
-
<Input
|
|
156
|
-
label="Password"
|
|
157
|
-
placeholder="Create a password"
|
|
158
|
-
value={password}
|
|
159
|
-
onChangeText={setPassword}
|
|
160
|
-
error={errors.password}
|
|
161
|
-
hint="Must be at least 8 characters"
|
|
162
|
-
secureTextEntry
|
|
163
|
-
autoComplete="new-password"
|
|
164
|
-
/>
|
|
165
|
-
|
|
166
|
-
<Input
|
|
167
|
-
label="Confirm Password"
|
|
168
|
-
placeholder="Confirm your password"
|
|
169
|
-
value={confirmPassword}
|
|
170
|
-
onChangeText={setConfirmPassword}
|
|
171
|
-
error={errors.confirmPassword}
|
|
172
|
-
secureTextEntry
|
|
173
|
-
autoComplete="new-password"
|
|
174
|
-
/>
|
|
175
|
-
</View>
|
|
176
|
-
|
|
177
|
-
{/* Terms */}
|
|
178
|
-
<Text variant="muted" className="text-center">
|
|
179
|
-
By creating an account, you agree to our{" "}
|
|
180
|
-
<Text className="text-primary">Terms of Service</Text> and{" "}
|
|
181
|
-
<Text className="text-primary">Privacy Policy</Text>
|
|
182
|
-
</Text>
|
|
183
|
-
</CardContent>
|
|
184
|
-
|
|
185
|
-
<CardFooter className="flex-col gap-4">
|
|
186
|
-
<Button fullWidth loading={loading} onPress={handleRegister}>
|
|
187
|
-
Create Account
|
|
188
|
-
</Button>
|
|
189
|
-
|
|
190
|
-
<View className="flex-row items-center justify-center gap-1">
|
|
191
|
-
<Text variant="muted">Already have an account?</Text>
|
|
192
|
-
<Pressable onPress={onLogin}>
|
|
193
|
-
<Text className="font-semibold text-primary">Sign in</Text>
|
|
194
|
-
</Pressable>
|
|
195
|
-
</View>
|
|
196
|
-
</CardFooter>
|
|
197
|
-
</Card>
|
|
198
|
-
</ScrollView>
|
|
199
|
-
</KeyboardAvoidingView>
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export default RegisterScreen;
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Home Screen
|
|
3
|
-
*
|
|
4
|
-
* Main dashboard/home screen with content feed.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import React from "react";
|
|
8
|
-
import { View, ScrollView, Pressable, RefreshControl } from "react-native";
|
|
9
|
-
import {
|
|
10
|
-
Text,
|
|
11
|
-
Avatar,
|
|
12
|
-
Card,
|
|
13
|
-
CardHeader,
|
|
14
|
-
CardContent,
|
|
15
|
-
CardFooter,
|
|
16
|
-
Badge,
|
|
17
|
-
Skeleton,
|
|
18
|
-
SkeletonCircle,
|
|
19
|
-
} from "@/components/ui";
|
|
20
|
-
|
|
21
|
-
interface Post {
|
|
22
|
-
id: string;
|
|
23
|
-
author: {
|
|
24
|
-
name: string;
|
|
25
|
-
avatar?: string;
|
|
26
|
-
isVerified?: boolean;
|
|
27
|
-
};
|
|
28
|
-
content: string;
|
|
29
|
-
timestamp: string;
|
|
30
|
-
likes: number;
|
|
31
|
-
comments: number;
|
|
32
|
-
isLiked?: boolean;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface HomeScreenProps {
|
|
36
|
-
posts?: Post[];
|
|
37
|
-
loading?: boolean;
|
|
38
|
-
refreshing?: boolean;
|
|
39
|
-
onRefresh?: () => void;
|
|
40
|
-
onPostPress?: (post: Post) => void;
|
|
41
|
-
onProfilePress?: () => void;
|
|
42
|
-
onNotificationsPress?: () => void;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const mockPosts: Post[] = [
|
|
46
|
-
{
|
|
47
|
-
id: "1",
|
|
48
|
-
author: { name: "Jane Smith", isVerified: true },
|
|
49
|
-
content:
|
|
50
|
-
"Just shipped a new feature using Uniwind! The Tailwind CSS integration for React Native is a game-changer. Build UIs so much faster now!",
|
|
51
|
-
timestamp: "2h ago",
|
|
52
|
-
likes: 42,
|
|
53
|
-
comments: 8,
|
|
54
|
-
isLiked: true,
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: "2",
|
|
58
|
-
author: { name: "Alex Johnson" },
|
|
59
|
-
content:
|
|
60
|
-
"Working on something exciting with Teardown CLI. The developer experience is incredible - fast builds, great templates, and now with Uniwind support!",
|
|
61
|
-
timestamp: "4h ago",
|
|
62
|
-
likes: 28,
|
|
63
|
-
comments: 5,
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
id: "3",
|
|
67
|
-
author: { name: "Sam Wilson", isVerified: true },
|
|
68
|
-
content:
|
|
69
|
-
"Pro tip: Use the prebuilt components from the template as a starting point. They're fully customizable and follow best practices.",
|
|
70
|
-
timestamp: "6h ago",
|
|
71
|
-
likes: 156,
|
|
72
|
-
comments: 23,
|
|
73
|
-
},
|
|
74
|
-
];
|
|
75
|
-
|
|
76
|
-
export function HomeScreen({
|
|
77
|
-
posts = mockPosts,
|
|
78
|
-
loading = false,
|
|
79
|
-
refreshing = false,
|
|
80
|
-
onRefresh,
|
|
81
|
-
onPostPress,
|
|
82
|
-
onProfilePress,
|
|
83
|
-
onNotificationsPress,
|
|
84
|
-
}: HomeScreenProps): React.JSX.Element {
|
|
85
|
-
return (
|
|
86
|
-
<View className="flex-1 bg-background">
|
|
87
|
-
{/* Header */}
|
|
88
|
-
<View className="flex-row items-center justify-between px-6 py-4 border-b border-border">
|
|
89
|
-
<Pressable onPress={onProfilePress}>
|
|
90
|
-
<Avatar fallback="U" size="sm" />
|
|
91
|
-
</Pressable>
|
|
92
|
-
<Text variant="h4">Home</Text>
|
|
93
|
-
<Pressable onPress={onNotificationsPress} className="relative">
|
|
94
|
-
<Text className="text-xl">🔔</Text>
|
|
95
|
-
<View className="absolute -top-1 -right-1 h-3 w-3 rounded-full bg-destructive" />
|
|
96
|
-
</Pressable>
|
|
97
|
-
</View>
|
|
98
|
-
|
|
99
|
-
{/* Content */}
|
|
100
|
-
<ScrollView
|
|
101
|
-
className="flex-1"
|
|
102
|
-
contentContainerClassName="p-4 gap-4"
|
|
103
|
-
refreshControl={
|
|
104
|
-
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
|
105
|
-
}
|
|
106
|
-
>
|
|
107
|
-
{loading ? (
|
|
108
|
-
// Loading skeleton
|
|
109
|
-
<>
|
|
110
|
-
<PostSkeleton />
|
|
111
|
-
<PostSkeleton />
|
|
112
|
-
<PostSkeleton />
|
|
113
|
-
</>
|
|
114
|
-
) : (
|
|
115
|
-
// Posts
|
|
116
|
-
posts.map((post) => (
|
|
117
|
-
<PostCard
|
|
118
|
-
key={post.id}
|
|
119
|
-
post={post}
|
|
120
|
-
onPress={() => onPostPress?.(post)}
|
|
121
|
-
/>
|
|
122
|
-
))
|
|
123
|
-
)}
|
|
124
|
-
</ScrollView>
|
|
125
|
-
</View>
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function PostCard({
|
|
130
|
-
post,
|
|
131
|
-
onPress,
|
|
132
|
-
}: {
|
|
133
|
-
post: Post;
|
|
134
|
-
onPress?: () => void;
|
|
135
|
-
}): React.JSX.Element {
|
|
136
|
-
return (
|
|
137
|
-
<Pressable onPress={onPress}>
|
|
138
|
-
<Card>
|
|
139
|
-
<CardHeader className="flex-row items-center gap-3 pb-2">
|
|
140
|
-
<Avatar fallback={post.author.name} size="md" />
|
|
141
|
-
<View className="flex-1">
|
|
142
|
-
<View className="flex-row items-center gap-2">
|
|
143
|
-
<Text variant="large">{post.author.name}</Text>
|
|
144
|
-
{post.author.isVerified && (
|
|
145
|
-
<Badge variant="default" className="h-5">
|
|
146
|
-
<Text className="text-xs text-primary-foreground">✓</Text>
|
|
147
|
-
</Badge>
|
|
148
|
-
)}
|
|
149
|
-
</View>
|
|
150
|
-
<Text variant="muted">{post.timestamp}</Text>
|
|
151
|
-
</View>
|
|
152
|
-
</CardHeader>
|
|
153
|
-
|
|
154
|
-
<CardContent>
|
|
155
|
-
<Text>{post.content}</Text>
|
|
156
|
-
</CardContent>
|
|
157
|
-
|
|
158
|
-
<CardFooter className="gap-6 pt-2">
|
|
159
|
-
<Pressable className="flex-row items-center gap-2">
|
|
160
|
-
<Text className={post.isLiked ? "text-destructive" : ""}>
|
|
161
|
-
{post.isLiked ? "❤️" : "🤍"}
|
|
162
|
-
</Text>
|
|
163
|
-
<Text variant="muted">{post.likes}</Text>
|
|
164
|
-
</Pressable>
|
|
165
|
-
|
|
166
|
-
<Pressable className="flex-row items-center gap-2">
|
|
167
|
-
<Text>💬</Text>
|
|
168
|
-
<Text variant="muted">{post.comments}</Text>
|
|
169
|
-
</Pressable>
|
|
170
|
-
|
|
171
|
-
<Pressable className="flex-row items-center gap-2">
|
|
172
|
-
<Text>📤</Text>
|
|
173
|
-
</Pressable>
|
|
174
|
-
</CardFooter>
|
|
175
|
-
</Card>
|
|
176
|
-
</Pressable>
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function PostSkeleton(): React.JSX.Element {
|
|
181
|
-
return (
|
|
182
|
-
<Card>
|
|
183
|
-
<CardHeader className="flex-row items-center gap-3 pb-2">
|
|
184
|
-
<SkeletonCircle size={40} />
|
|
185
|
-
<View className="flex-1 gap-2">
|
|
186
|
-
<Skeleton className="h-4 w-32" />
|
|
187
|
-
<Skeleton className="h-3 w-20" />
|
|
188
|
-
</View>
|
|
189
|
-
</CardHeader>
|
|
190
|
-
<CardContent className="gap-2">
|
|
191
|
-
<Skeleton className="h-4 w-full" />
|
|
192
|
-
<Skeleton className="h-4 w-full" />
|
|
193
|
-
<Skeleton className="h-4 w-3/4" />
|
|
194
|
-
</CardContent>
|
|
195
|
-
<CardFooter className="gap-6 pt-2">
|
|
196
|
-
<Skeleton className="h-4 w-12" />
|
|
197
|
-
<Skeleton className="h-4 w-12" />
|
|
198
|
-
<Skeleton className="h-4 w-8" />
|
|
199
|
-
</CardFooter>
|
|
200
|
-
</Card>
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export default HomeScreen;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Screens Barrel Export
|
|
3
|
-
*
|
|
4
|
-
* Pre-built screens using Uniwind components.
|
|
5
|
-
* Use these as starting points for your app.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// Onboarding
|
|
9
|
-
export { WelcomeScreen } from "./welcome";
|
|
10
|
-
|
|
11
|
-
// Authentication
|
|
12
|
-
export { LoginScreen, RegisterScreen } from "./auth";
|
|
13
|
-
|
|
14
|
-
// Main App
|
|
15
|
-
export { HomeScreen } from "./home";
|
|
16
|
-
export { ProfileScreen } from "./profile";
|
|
17
|
-
export { SettingsScreen } from "./settings";
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Profile Screen
|
|
3
|
-
*
|
|
4
|
-
* User profile display with edit capabilities.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import React from "react";
|
|
8
|
-
import { View, ScrollView, Pressable } from "react-native";
|
|
9
|
-
import {
|
|
10
|
-
Text,
|
|
11
|
-
Button,
|
|
12
|
-
Avatar,
|
|
13
|
-
Card,
|
|
14
|
-
CardContent,
|
|
15
|
-
Badge,
|
|
16
|
-
Separator,
|
|
17
|
-
} from "@/components/ui";
|
|
18
|
-
|
|
19
|
-
interface ProfileScreenProps {
|
|
20
|
-
user?: {
|
|
21
|
-
name: string;
|
|
22
|
-
email: string;
|
|
23
|
-
avatar?: string;
|
|
24
|
-
bio?: string;
|
|
25
|
-
location?: string;
|
|
26
|
-
joinedDate?: string;
|
|
27
|
-
isPro?: boolean;
|
|
28
|
-
};
|
|
29
|
-
stats?: {
|
|
30
|
-
posts: number;
|
|
31
|
-
followers: number;
|
|
32
|
-
following: number;
|
|
33
|
-
};
|
|
34
|
-
onEditProfile?: () => void;
|
|
35
|
-
onSettings?: () => void;
|
|
36
|
-
onLogout?: () => void;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function ProfileScreen({
|
|
40
|
-
user = {
|
|
41
|
-
name: "John Doe",
|
|
42
|
-
email: "john@example.com",
|
|
43
|
-
bio: "Building amazing apps with React Native and Tailwind CSS",
|
|
44
|
-
location: "San Francisco, CA",
|
|
45
|
-
joinedDate: "January 2024",
|
|
46
|
-
isPro: true,
|
|
47
|
-
},
|
|
48
|
-
stats = {
|
|
49
|
-
posts: 42,
|
|
50
|
-
followers: 1234,
|
|
51
|
-
following: 567,
|
|
52
|
-
},
|
|
53
|
-
onEditProfile,
|
|
54
|
-
onSettings,
|
|
55
|
-
onLogout,
|
|
56
|
-
}: ProfileScreenProps): React.JSX.Element {
|
|
57
|
-
return (
|
|
58
|
-
<ScrollView className="flex-1 bg-background">
|
|
59
|
-
{/* Header */}
|
|
60
|
-
<View className="bg-primary/5 pb-6 pt-4">
|
|
61
|
-
<View className="flex-row items-center justify-between px-6">
|
|
62
|
-
<Text variant="h3">Profile</Text>
|
|
63
|
-
<Pressable onPress={onSettings} className="p-2">
|
|
64
|
-
<Text className="text-xl">⚙️</Text>
|
|
65
|
-
</Pressable>
|
|
66
|
-
</View>
|
|
67
|
-
|
|
68
|
-
{/* Profile Info */}
|
|
69
|
-
<View className="items-center mt-6 px-6">
|
|
70
|
-
<Avatar
|
|
71
|
-
fallback={user.name}
|
|
72
|
-
size="xl"
|
|
73
|
-
className="border-4 border-background"
|
|
74
|
-
/>
|
|
75
|
-
<View className="items-center mt-4 gap-1">
|
|
76
|
-
<View className="flex-row items-center gap-2">
|
|
77
|
-
<Text variant="h3">{user.name}</Text>
|
|
78
|
-
{user.isPro && <Badge variant="default">PRO</Badge>}
|
|
79
|
-
</View>
|
|
80
|
-
<Text variant="muted">{user.email}</Text>
|
|
81
|
-
</View>
|
|
82
|
-
</View>
|
|
83
|
-
|
|
84
|
-
{/* Stats */}
|
|
85
|
-
<View className="flex-row justify-center gap-8 mt-6">
|
|
86
|
-
<StatItem label="Posts" value={stats.posts} />
|
|
87
|
-
<StatItem label="Followers" value={stats.followers} />
|
|
88
|
-
<StatItem label="Following" value={stats.following} />
|
|
89
|
-
</View>
|
|
90
|
-
|
|
91
|
-
{/* Actions */}
|
|
92
|
-
<View className="flex-row gap-3 mt-6 px-6">
|
|
93
|
-
<Button variant="outline" className="flex-1" onPress={onEditProfile}>
|
|
94
|
-
Edit Profile
|
|
95
|
-
</Button>
|
|
96
|
-
<Button variant="outline" size="icon">
|
|
97
|
-
<Text>📤</Text>
|
|
98
|
-
</Button>
|
|
99
|
-
</View>
|
|
100
|
-
</View>
|
|
101
|
-
|
|
102
|
-
{/* Content */}
|
|
103
|
-
<View className="p-6 gap-4">
|
|
104
|
-
{/* Bio Card */}
|
|
105
|
-
<Card>
|
|
106
|
-
<CardContent className="gap-4 py-4">
|
|
107
|
-
<View className="flex-row items-center gap-2">
|
|
108
|
-
<Text>📝</Text>
|
|
109
|
-
<Text variant="label">About</Text>
|
|
110
|
-
</View>
|
|
111
|
-
<Text>{user.bio}</Text>
|
|
112
|
-
</CardContent>
|
|
113
|
-
</Card>
|
|
114
|
-
|
|
115
|
-
{/* Details Card */}
|
|
116
|
-
<Card>
|
|
117
|
-
<CardContent className="gap-4 py-4">
|
|
118
|
-
<View className="flex-row items-center gap-2">
|
|
119
|
-
<Text>ℹ️</Text>
|
|
120
|
-
<Text variant="label">Details</Text>
|
|
121
|
-
</View>
|
|
122
|
-
|
|
123
|
-
<View className="gap-3">
|
|
124
|
-
<DetailRow icon="📍" label="Location" value={user.location || "Not set"} />
|
|
125
|
-
<Separator />
|
|
126
|
-
<DetailRow icon="📅" label="Joined" value={user.joinedDate || "Unknown"} />
|
|
127
|
-
</View>
|
|
128
|
-
</CardContent>
|
|
129
|
-
</Card>
|
|
130
|
-
|
|
131
|
-
{/* Quick Actions */}
|
|
132
|
-
<Card>
|
|
133
|
-
<CardContent className="gap-1 py-2">
|
|
134
|
-
<ActionRow icon="🔔" label="Notifications" onPress={() => {}} />
|
|
135
|
-
<Separator />
|
|
136
|
-
<ActionRow icon="🔒" label="Privacy & Security" onPress={() => {}} />
|
|
137
|
-
<Separator />
|
|
138
|
-
<ActionRow icon="❓" label="Help & Support" onPress={() => {}} />
|
|
139
|
-
</CardContent>
|
|
140
|
-
</Card>
|
|
141
|
-
|
|
142
|
-
{/* Logout */}
|
|
143
|
-
<Button variant="destructive" fullWidth onPress={onLogout}>
|
|
144
|
-
Sign Out
|
|
145
|
-
</Button>
|
|
146
|
-
</View>
|
|
147
|
-
</ScrollView>
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function StatItem({ label, value }: { label: string; value: number }): React.JSX.Element {
|
|
152
|
-
const formatNumber = (num: number): string => {
|
|
153
|
-
if (num >= 1000) {
|
|
154
|
-
return `${(num / 1000).toFixed(1)}k`;
|
|
155
|
-
}
|
|
156
|
-
return num.toString();
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
return (
|
|
160
|
-
<View className="items-center">
|
|
161
|
-
<Text variant="h3">{formatNumber(value)}</Text>
|
|
162
|
-
<Text variant="muted">{label}</Text>
|
|
163
|
-
</View>
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function DetailRow({
|
|
168
|
-
icon,
|
|
169
|
-
label,
|
|
170
|
-
value,
|
|
171
|
-
}: {
|
|
172
|
-
icon: string;
|
|
173
|
-
label: string;
|
|
174
|
-
value: string;
|
|
175
|
-
}): React.JSX.Element {
|
|
176
|
-
return (
|
|
177
|
-
<View className="flex-row items-center justify-between">
|
|
178
|
-
<View className="flex-row items-center gap-2">
|
|
179
|
-
<Text>{icon}</Text>
|
|
180
|
-
<Text variant="muted">{label}</Text>
|
|
181
|
-
</View>
|
|
182
|
-
<Text>{value}</Text>
|
|
183
|
-
</View>
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function ActionRow({
|
|
188
|
-
icon,
|
|
189
|
-
label,
|
|
190
|
-
onPress,
|
|
191
|
-
}: {
|
|
192
|
-
icon: string;
|
|
193
|
-
label: string;
|
|
194
|
-
onPress: () => void;
|
|
195
|
-
}): React.JSX.Element {
|
|
196
|
-
return (
|
|
197
|
-
<Pressable
|
|
198
|
-
className="flex-row items-center justify-between py-3 active:bg-muted rounded-lg px-2 -mx-2"
|
|
199
|
-
onPress={onPress}
|
|
200
|
-
>
|
|
201
|
-
<View className="flex-row items-center gap-3">
|
|
202
|
-
<Text className="text-lg">{icon}</Text>
|
|
203
|
-
<Text>{label}</Text>
|
|
204
|
-
</View>
|
|
205
|
-
<Text className="text-muted-foreground">›</Text>
|
|
206
|
-
</Pressable>
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export default ProfileScreen;
|