create-ern-boilerplate 0.0.4 → 0.0.6
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 +1 -1
- package/templates/default/app/(admin)/user-management.tsx +13 -8
- package/templates/default/app/(protected)/_layout.tsx +17 -11
- package/templates/default/app/(protected)/home.tsx +27 -36
- package/templates/default/app/(protected)/profile.tsx +17 -3
- package/templates/default/app/(protected)/settings.tsx +20 -12
- package/templates/default/src/components/header/AppHeader.tsx +160 -0
package/package.json
CHANGED
|
@@ -29,11 +29,15 @@ import {
|
|
|
29
29
|
import { useTheme } from '@hooks/useTheme';
|
|
30
30
|
import { userService } from '@/services/userServices';
|
|
31
31
|
import { CreateUserInput, UpdateUserInput, UserData, UserFilters } from '@/types/user.types';
|
|
32
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
33
|
+
import { AppHeader } from '@/components/header/AppHeader';
|
|
34
|
+
import { useRouter } from 'expo-router';
|
|
32
35
|
|
|
33
36
|
const ITEMS_PER_PAGE = 10;
|
|
34
37
|
|
|
35
38
|
export default function UserManagementScreen() {
|
|
36
39
|
const { colors } = useTheme();
|
|
40
|
+
const router = useRouter();
|
|
37
41
|
const [searchQuery, setSearchQuery] = useState('');
|
|
38
42
|
const [filters, setFilters] = useState<UserFilters>({
|
|
39
43
|
page: 1,
|
|
@@ -571,14 +575,15 @@ export default function UserManagementScreen() {
|
|
|
571
575
|
}
|
|
572
576
|
|
|
573
577
|
return (
|
|
574
|
-
<
|
|
578
|
+
<SafeAreaView style={[styles.container, { backgroundColor: colors.background }]}>
|
|
575
579
|
{/* Header */}
|
|
576
|
-
<
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
580
|
+
<AppHeader
|
|
581
|
+
variant="back"
|
|
582
|
+
title="User Management"
|
|
583
|
+
subtitle={`${users.length} ${users.length === 1 ? 'user' : 'users'} • Manage users and permissions`}
|
|
584
|
+
onBackPress={() => router.back()}
|
|
585
|
+
colors={colors}
|
|
586
|
+
/>
|
|
582
587
|
|
|
583
588
|
{/* Search and Actions */}
|
|
584
589
|
<View style={styles.searchSection}>
|
|
@@ -892,7 +897,7 @@ export default function UserManagementScreen() {
|
|
|
892
897
|
</View>
|
|
893
898
|
</UserModal>
|
|
894
899
|
)}
|
|
895
|
-
</
|
|
900
|
+
</SafeAreaView>
|
|
896
901
|
);
|
|
897
902
|
}
|
|
898
903
|
|
|
@@ -2,12 +2,14 @@ import React from 'react';
|
|
|
2
2
|
import { View, Text, TouchableOpacity, StyleSheet, Platform } from 'react-native';
|
|
3
3
|
import { Tabs, Redirect } from 'expo-router';
|
|
4
4
|
import { Home, User, Settings } from 'lucide-react-native';
|
|
5
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
6
|
import { useTheme } from '@/hooks/useTheme';
|
|
6
7
|
import { useAuth } from '@/hooks/useAuth';
|
|
7
8
|
|
|
8
9
|
export default function MainLayout() {
|
|
9
10
|
const { colors } = useTheme();
|
|
10
11
|
const { user, isAuthenticated } = useAuth();
|
|
12
|
+
const insets = useSafeAreaInsets();
|
|
11
13
|
|
|
12
14
|
// Redirect to login if not authenticated
|
|
13
15
|
if (!isAuthenticated) {
|
|
@@ -25,18 +27,23 @@ export default function MainLayout() {
|
|
|
25
27
|
backgroundColor: colors.card || colors.background,
|
|
26
28
|
borderTopColor: colors.border,
|
|
27
29
|
borderTopWidth: 1,
|
|
28
|
-
height: Platform.OS === 'ios'
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
height: Platform.OS === 'ios'
|
|
31
|
+
? 50 + insets.bottom
|
|
32
|
+
: 50 + (insets.bottom > 0 ? insets.bottom : 8),
|
|
33
|
+
paddingBottom: insets.bottom > 0 ? insets.bottom : 8,
|
|
34
|
+
paddingTop: 0,
|
|
31
35
|
elevation: 0,
|
|
32
36
|
shadowOpacity: 0,
|
|
33
37
|
},
|
|
34
38
|
tabBarActiveTintColor: colors.primary,
|
|
35
39
|
tabBarInactiveTintColor: colors.textSecondary,
|
|
36
40
|
tabBarLabelStyle: {
|
|
37
|
-
fontSize:
|
|
41
|
+
fontSize: 10,
|
|
38
42
|
fontWeight: '600',
|
|
39
|
-
marginTop:
|
|
43
|
+
marginTop: 2,
|
|
44
|
+
},
|
|
45
|
+
tabBarItemStyle: {
|
|
46
|
+
paddingVertical: 2,
|
|
40
47
|
},
|
|
41
48
|
}}
|
|
42
49
|
>
|
|
@@ -70,7 +77,7 @@ export default function MainLayout() {
|
|
|
70
77
|
}}
|
|
71
78
|
/>
|
|
72
79
|
|
|
73
|
-
{
|
|
80
|
+
{isAdmin && (
|
|
74
81
|
<Tabs.Screen
|
|
75
82
|
name="settings"
|
|
76
83
|
options={{
|
|
@@ -83,19 +90,18 @@ export default function MainLayout() {
|
|
|
83
90
|
<Settings size={24} color={color} strokeWidth={focused ? 2.5 : 2} />
|
|
84
91
|
</View>
|
|
85
92
|
),
|
|
86
|
-
// Hide settings tab if not admin
|
|
87
|
-
// href: isAdmin ? '/(protected)/settings' : null,
|
|
88
93
|
}}
|
|
89
94
|
/>
|
|
95
|
+
)}
|
|
90
96
|
</Tabs>
|
|
91
97
|
);
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
const styles = StyleSheet.create({
|
|
95
101
|
iconContainer: {
|
|
96
|
-
width:
|
|
97
|
-
height:
|
|
98
|
-
borderRadius:
|
|
102
|
+
width: 40,
|
|
103
|
+
height: 26,
|
|
104
|
+
borderRadius: 8,
|
|
99
105
|
justifyContent: 'center',
|
|
100
106
|
alignItems: 'center',
|
|
101
107
|
},
|
|
@@ -14,6 +14,10 @@ import {
|
|
|
14
14
|
import { Search, Filter, Heart, Eye, Calendar, ChevronLeft, ChevronRight } from 'lucide-react-native';
|
|
15
15
|
import { useTheme } from '@/hooks/useTheme';
|
|
16
16
|
import { useAuth } from '@/hooks/useAuth';
|
|
17
|
+
// import { AppHeader } from '@/components/layout/AppHeader';
|
|
18
|
+
import { useRouter } from 'expo-router';
|
|
19
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
20
|
+
import { AppHeader } from '@/components/header/AppHeader';
|
|
17
21
|
|
|
18
22
|
const { width } = Dimensions.get('window');
|
|
19
23
|
const ITEMS_PER_PAGE = 6;
|
|
@@ -45,6 +49,7 @@ const categories = [
|
|
|
45
49
|
export default function HomeScreen() {
|
|
46
50
|
const { colors } = useTheme();
|
|
47
51
|
const { user } = useAuth();
|
|
52
|
+
const router = useRouter();
|
|
48
53
|
const [newsList, setNewsList] = useState<News[]>([]);
|
|
49
54
|
const [filteredNews, setFilteredNews] = useState<News[]>([]);
|
|
50
55
|
const [loading, setLoading] = useState(true);
|
|
@@ -219,10 +224,10 @@ export default function HomeScreen() {
|
|
|
219
224
|
|
|
220
225
|
const formatDate = (dateString: string) => {
|
|
221
226
|
const date = new Date(dateString);
|
|
222
|
-
return date.toLocaleDateString('en-US', {
|
|
223
|
-
month: 'short',
|
|
224
|
-
day: 'numeric',
|
|
225
|
-
year: 'numeric'
|
|
227
|
+
return date.toLocaleDateString('en-US', {
|
|
228
|
+
month: 'short',
|
|
229
|
+
day: 'numeric',
|
|
230
|
+
year: 'numeric'
|
|
226
231
|
});
|
|
227
232
|
};
|
|
228
233
|
|
|
@@ -245,6 +250,10 @@ export default function HomeScreen() {
|
|
|
245
250
|
}
|
|
246
251
|
};
|
|
247
252
|
|
|
253
|
+
const handleProfilePress = () => {
|
|
254
|
+
router.push('/(protected)/profile')
|
|
255
|
+
};
|
|
256
|
+
|
|
248
257
|
if (loading) {
|
|
249
258
|
return (
|
|
250
259
|
<View style={[styles.loadingContainer, { backgroundColor: colors.background }]}>
|
|
@@ -255,41 +264,23 @@ export default function HomeScreen() {
|
|
|
255
264
|
}
|
|
256
265
|
|
|
257
266
|
return (
|
|
258
|
-
<
|
|
267
|
+
<SafeAreaView style={[styles.container, { backgroundColor: colors.background }]}>
|
|
268
|
+
{/* Header */}
|
|
269
|
+
<AppHeader
|
|
270
|
+
variant="search"
|
|
271
|
+
searchQuery={searchQuery}
|
|
272
|
+
onSearchChange={setSearchQuery}
|
|
273
|
+
searchPlaceholder="Search..."
|
|
274
|
+
user={{ ...user }} // Pass user object langsung
|
|
275
|
+
onProfilePress={handleProfilePress}
|
|
276
|
+
colors={colors}
|
|
277
|
+
/>
|
|
259
278
|
<ScrollView
|
|
260
279
|
showsVerticalScrollIndicator={false}
|
|
261
280
|
refreshControl={
|
|
262
281
|
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
|
263
282
|
}
|
|
264
283
|
>
|
|
265
|
-
{/* Header */}
|
|
266
|
-
<View style={styles.header}>
|
|
267
|
-
<View>
|
|
268
|
-
<Text style={[styles.greeting, { color: colors.textSecondary }]}>
|
|
269
|
-
Welcome back,
|
|
270
|
-
</Text>
|
|
271
|
-
<Text style={[styles.username, { color: colors.text }]}>
|
|
272
|
-
{user?.name || 'User'}
|
|
273
|
-
</Text>
|
|
274
|
-
</View>
|
|
275
|
-
</View>
|
|
276
|
-
|
|
277
|
-
{/* Search Bar */}
|
|
278
|
-
<View style={[styles.searchContainer, { backgroundColor: `${colors.primary}08` }]}>
|
|
279
|
-
<Search size={20} color={colors.textSecondary} />
|
|
280
|
-
<TextInput
|
|
281
|
-
style={[styles.searchInput, { color: colors.text }]}
|
|
282
|
-
placeholder="Search news..."
|
|
283
|
-
placeholderTextColor={colors.textSecondary}
|
|
284
|
-
value={searchQuery}
|
|
285
|
-
onChangeText={setSearchQuery}
|
|
286
|
-
/>
|
|
287
|
-
{searchQuery.length > 0 && (
|
|
288
|
-
<TouchableOpacity onPress={() => setSearchQuery('')}>
|
|
289
|
-
<Text style={{ color: colors.primary, fontWeight: '600' }}>Clear</Text>
|
|
290
|
-
</TouchableOpacity>
|
|
291
|
-
)}
|
|
292
|
-
</View>
|
|
293
284
|
|
|
294
285
|
{/* Category Filter */}
|
|
295
286
|
<ScrollView
|
|
@@ -346,7 +337,7 @@ export default function HomeScreen() {
|
|
|
346
337
|
{currentNews.map((news) => (
|
|
347
338
|
<TouchableOpacity
|
|
348
339
|
key={news.id}
|
|
349
|
-
style={[styles.newsCard, {
|
|
340
|
+
style={[styles.newsCard, {
|
|
350
341
|
backgroundColor: colors.card || colors.background,
|
|
351
342
|
shadowColor: colors.text,
|
|
352
343
|
}]}
|
|
@@ -364,7 +355,7 @@ export default function HomeScreen() {
|
|
|
364
355
|
<Text style={[styles.newsExcerpt, { color: colors.textSecondary }]} numberOfLines={2}>
|
|
365
356
|
{news.excerpt}
|
|
366
357
|
</Text>
|
|
367
|
-
|
|
358
|
+
|
|
368
359
|
<View style={styles.newsFooter}>
|
|
369
360
|
<View style={styles.newsStats}>
|
|
370
361
|
<View style={styles.statItem}>
|
|
@@ -459,7 +450,7 @@ export default function HomeScreen() {
|
|
|
459
450
|
|
|
460
451
|
<View style={{ height: 20 }} />
|
|
461
452
|
</ScrollView>
|
|
462
|
-
</
|
|
453
|
+
</SafeAreaView>
|
|
463
454
|
);
|
|
464
455
|
}
|
|
465
456
|
|
|
@@ -13,10 +13,14 @@ import { User, Mail, Phone, MapPin, Calendar, Edit2, Save, X } from 'lucide-reac
|
|
|
13
13
|
import { useTheme } from '@/hooks/useTheme';
|
|
14
14
|
import { useAuth } from '@/hooks/useAuth';
|
|
15
15
|
import { Button } from '@/components/common/Button';
|
|
16
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
17
|
+
import { AppHeader } from '@/components/header/AppHeader';
|
|
18
|
+
import { useRouter } from 'expo-router';
|
|
16
19
|
|
|
17
20
|
export default function ProfileScreen() {
|
|
18
21
|
const { colors } = useTheme();
|
|
19
22
|
const { user } = useAuth();
|
|
23
|
+
const router = useRouter();
|
|
20
24
|
const [isEditing, setIsEditing] = useState(false);
|
|
21
25
|
const [loading, setLoading] = useState(false);
|
|
22
26
|
|
|
@@ -30,6 +34,7 @@ export default function ProfileScreen() {
|
|
|
30
34
|
joinDate: 'January 2024',
|
|
31
35
|
});
|
|
32
36
|
|
|
37
|
+
|
|
33
38
|
const handleSave = async () => {
|
|
34
39
|
setLoading(true);
|
|
35
40
|
try {
|
|
@@ -79,8 +84,17 @@ export default function ProfileScreen() {
|
|
|
79
84
|
);
|
|
80
85
|
|
|
81
86
|
return (
|
|
82
|
-
<
|
|
87
|
+
<SafeAreaView style={[styles.container, { backgroundColor: colors.background }]}>
|
|
88
|
+
<AppHeader
|
|
89
|
+
variant="back"
|
|
90
|
+
title="Profile"
|
|
91
|
+
subtitle="Manage your personal information"
|
|
92
|
+
onBackPress={() => router.back()}
|
|
93
|
+
colors={colors}
|
|
94
|
+
/>
|
|
83
95
|
<ScrollView showsVerticalScrollIndicator={false}>
|
|
96
|
+
|
|
97
|
+
|
|
84
98
|
{/* Header with Avatar */}
|
|
85
99
|
<View style={styles.header}>
|
|
86
100
|
<View style={[styles.avatarContainer, { backgroundColor: colors.primary }]}>
|
|
@@ -140,7 +154,7 @@ export default function ProfileScreen() {
|
|
|
140
154
|
{/* Profile Information */}
|
|
141
155
|
<View style={styles.section}>
|
|
142
156
|
<Text style={[styles.sectionTitle, { color: colors.text }]}>Profile Information</Text>
|
|
143
|
-
|
|
157
|
+
|
|
144
158
|
<InfoCard icon={User} label="Name" value={profileData.name} editable />
|
|
145
159
|
<InfoCard icon={Mail} label="Email" value={profileData.email} editable />
|
|
146
160
|
<InfoCard icon={Phone} label="Phone" value={profileData.phone} editable />
|
|
@@ -171,7 +185,7 @@ export default function ProfileScreen() {
|
|
|
171
185
|
|
|
172
186
|
<View style={{ height: 40 }} />
|
|
173
187
|
</ScrollView>
|
|
174
|
-
</
|
|
188
|
+
</SafeAreaView>
|
|
175
189
|
);
|
|
176
190
|
}
|
|
177
191
|
|
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
import { useTheme } from '@hooks/useTheme';
|
|
24
24
|
import { useAuth } from '@hooks/useAuth';
|
|
25
25
|
import { useRouter } from 'expo-router';
|
|
26
|
+
import { AppHeader } from '@/components/header/AppHeader';
|
|
27
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
26
28
|
|
|
27
29
|
export default function SettingsScreen() {
|
|
28
30
|
const { colors, theme, themeMode, setThemeMode } = useTheme();
|
|
@@ -74,7 +76,7 @@ export default function SettingsScreen() {
|
|
|
74
76
|
const SettingSection = ({ title, children }: any) => (
|
|
75
77
|
<View style={styles.section}>
|
|
76
78
|
<Text style={[styles.sectionTitle, { color: colors.textSecondary }]}>{title}</Text>
|
|
77
|
-
<View style={[styles.sectionContent, {
|
|
79
|
+
<View style={[styles.sectionContent, {
|
|
78
80
|
backgroundColor: colors.card || colors.background,
|
|
79
81
|
shadowColor: colors.text,
|
|
80
82
|
}]}>
|
|
@@ -112,15 +114,17 @@ export default function SettingsScreen() {
|
|
|
112
114
|
);
|
|
113
115
|
|
|
114
116
|
return (
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
117
|
+
<SafeAreaView style={[styles.container, { backgroundColor: colors.background }]}>
|
|
118
|
+
|
|
119
|
+
<AppHeader
|
|
120
|
+
variant="back"
|
|
121
|
+
title="Settings"
|
|
122
|
+
subtitle="Manage your account and preferences"
|
|
123
|
+
onBackPress={() => router.back()}
|
|
124
|
+
colors={colors}
|
|
125
|
+
/>
|
|
126
|
+
|
|
127
|
+
<ScrollView style={styles.scroll} showsVerticalScrollIndicator={false}>
|
|
124
128
|
|
|
125
129
|
{/* Appearance */}
|
|
126
130
|
<SettingSection title="APPEARANCE">
|
|
@@ -229,7 +233,7 @@ export default function SettingsScreen() {
|
|
|
229
233
|
|
|
230
234
|
<View style={{ height: 40 }} />
|
|
231
235
|
</ScrollView>
|
|
232
|
-
</
|
|
236
|
+
</SafeAreaView>
|
|
233
237
|
);
|
|
234
238
|
}
|
|
235
239
|
|
|
@@ -237,9 +241,13 @@ const styles = StyleSheet.create({
|
|
|
237
241
|
container: {
|
|
238
242
|
flex: 1,
|
|
239
243
|
},
|
|
244
|
+
scroll: {
|
|
245
|
+
paddingTop: 16,
|
|
246
|
+
paddingBottom: 24,
|
|
247
|
+
},
|
|
240
248
|
header: {
|
|
241
249
|
paddingHorizontal: 24,
|
|
242
|
-
paddingTop:
|
|
250
|
+
paddingTop: 16,
|
|
243
251
|
paddingBottom: 24,
|
|
244
252
|
},
|
|
245
253
|
headerTitle: {
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// components/AppHeader.tsx
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { View, TextInput, TouchableOpacity, Image, StyleSheet, Text } from 'react-native';
|
|
4
|
+
import { Search, X, ArrowLeft } from 'lucide-react-native';
|
|
5
|
+
|
|
6
|
+
interface AppHeaderProps {
|
|
7
|
+
variant?: 'search' | 'back';
|
|
8
|
+
searchQuery?: string;
|
|
9
|
+
onSearchChange?: (query: string) => void;
|
|
10
|
+
searchPlaceholder?: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
subtitle?: string; // Tambahkan subtitle
|
|
13
|
+
onBackPress?: () => void;
|
|
14
|
+
user?: {
|
|
15
|
+
name?: string;
|
|
16
|
+
avatar?: string;
|
|
17
|
+
avatarId?: string;
|
|
18
|
+
};
|
|
19
|
+
onProfilePress?: () => void;
|
|
20
|
+
colors: {
|
|
21
|
+
primary: string;
|
|
22
|
+
text: string;
|
|
23
|
+
textSecondary: string;
|
|
24
|
+
background: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const AppHeader: React.FC<AppHeaderProps> = ({
|
|
29
|
+
variant = 'search',
|
|
30
|
+
searchQuery = '',
|
|
31
|
+
onSearchChange,
|
|
32
|
+
searchPlaceholder = 'Search...',
|
|
33
|
+
title,
|
|
34
|
+
subtitle,
|
|
35
|
+
onBackPress,
|
|
36
|
+
user,
|
|
37
|
+
onProfilePress,
|
|
38
|
+
colors,
|
|
39
|
+
}) => {
|
|
40
|
+
const avatarUrl = user?.avatar || `https://i.pravatar.cc/150?img=${user?.avatarId || 1}`;
|
|
41
|
+
|
|
42
|
+
// Render header dengan search dan profile
|
|
43
|
+
if (variant === 'search') {
|
|
44
|
+
return (
|
|
45
|
+
<View style={styles.header}>
|
|
46
|
+
<View style={[styles.searchContainer, { backgroundColor: `${colors.primary}08` }]}>
|
|
47
|
+
<Search size={20} color={colors.textSecondary} />
|
|
48
|
+
<TextInput
|
|
49
|
+
style={[styles.searchInput, { color: colors.text }]}
|
|
50
|
+
placeholder={searchPlaceholder}
|
|
51
|
+
placeholderTextColor={colors.textSecondary}
|
|
52
|
+
value={searchQuery}
|
|
53
|
+
onChangeText={onSearchChange}
|
|
54
|
+
/>
|
|
55
|
+
{searchQuery.length > 0 && (
|
|
56
|
+
<TouchableOpacity onPress={() => onSearchChange?.('')}>
|
|
57
|
+
<X size={18} color={colors.textSecondary} />
|
|
58
|
+
</TouchableOpacity>
|
|
59
|
+
)}
|
|
60
|
+
</View>
|
|
61
|
+
|
|
62
|
+
<TouchableOpacity
|
|
63
|
+
style={styles.profileContainer}
|
|
64
|
+
onPress={onProfilePress}
|
|
65
|
+
activeOpacity={0.7}
|
|
66
|
+
>
|
|
67
|
+
<Image
|
|
68
|
+
source={{ uri: avatarUrl }}
|
|
69
|
+
style={styles.profileImage}
|
|
70
|
+
/>
|
|
71
|
+
</TouchableOpacity>
|
|
72
|
+
</View>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Render header dengan back button, title, dan subtitle
|
|
77
|
+
return (
|
|
78
|
+
<View style={styles.backHeader}>
|
|
79
|
+
<TouchableOpacity
|
|
80
|
+
onPress={onBackPress}
|
|
81
|
+
style={styles.backButton}
|
|
82
|
+
activeOpacity={0.7}
|
|
83
|
+
>
|
|
84
|
+
<ArrowLeft size={24} color={colors.text} />
|
|
85
|
+
</TouchableOpacity>
|
|
86
|
+
|
|
87
|
+
<View style={styles.headerTextContainer}>
|
|
88
|
+
<Text style={[styles.headerTitle, { color: colors.text }]}>
|
|
89
|
+
{title}
|
|
90
|
+
</Text>
|
|
91
|
+
{subtitle && (
|
|
92
|
+
<Text style={[styles.headerSubtitle, { color: colors.textSecondary }]}>
|
|
93
|
+
{subtitle}
|
|
94
|
+
</Text>
|
|
95
|
+
)}
|
|
96
|
+
</View>
|
|
97
|
+
</View>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const styles = StyleSheet.create({
|
|
102
|
+
header: {
|
|
103
|
+
flexDirection: 'row',
|
|
104
|
+
alignItems: 'center',
|
|
105
|
+
paddingHorizontal: 16,
|
|
106
|
+
paddingVertical: 12,
|
|
107
|
+
gap: 12,
|
|
108
|
+
},
|
|
109
|
+
searchContainer: {
|
|
110
|
+
flex: 1,
|
|
111
|
+
flexDirection: 'row',
|
|
112
|
+
alignItems: 'center',
|
|
113
|
+
paddingHorizontal: 12,
|
|
114
|
+
paddingVertical: 10,
|
|
115
|
+
borderRadius: 12,
|
|
116
|
+
gap: 8,
|
|
117
|
+
},
|
|
118
|
+
searchInput: {
|
|
119
|
+
flex: 1,
|
|
120
|
+
fontSize: 15,
|
|
121
|
+
padding: 0,
|
|
122
|
+
},
|
|
123
|
+
profileContainer: {
|
|
124
|
+
width: 40,
|
|
125
|
+
height: 40,
|
|
126
|
+
borderRadius: 20,
|
|
127
|
+
overflow: 'hidden',
|
|
128
|
+
borderWidth: 2,
|
|
129
|
+
borderColor: '#E5E7EB',
|
|
130
|
+
},
|
|
131
|
+
profileImage: {
|
|
132
|
+
width: '100%',
|
|
133
|
+
height: '100%',
|
|
134
|
+
},
|
|
135
|
+
backHeader: {
|
|
136
|
+
flexDirection: 'row',
|
|
137
|
+
alignItems: 'center',
|
|
138
|
+
paddingHorizontal: 16,
|
|
139
|
+
paddingVertical: 12,
|
|
140
|
+
gap: 12,
|
|
141
|
+
},
|
|
142
|
+
backButton: {
|
|
143
|
+
width: 40,
|
|
144
|
+
height: 40,
|
|
145
|
+
justifyContent: 'center',
|
|
146
|
+
alignItems: 'flex-start',
|
|
147
|
+
},
|
|
148
|
+
headerTextContainer: {
|
|
149
|
+
flex: 1,
|
|
150
|
+
},
|
|
151
|
+
headerTitle: {
|
|
152
|
+
fontSize: 24,
|
|
153
|
+
fontWeight: '700',
|
|
154
|
+
marginBottom: 4,
|
|
155
|
+
},
|
|
156
|
+
headerSubtitle: {
|
|
157
|
+
fontSize: 14,
|
|
158
|
+
lineHeight: 20,
|
|
159
|
+
},
|
|
160
|
+
});
|