create-gufran-expo-app 2.0.3 → 2.0.5

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 (42) hide show
  1. package/README.md +1 -2
  2. package/package.json +3 -3
  3. package/template/src/navigation/AuthStack.tsx +6 -25
  4. package/template/src/navigation/MainStack.tsx +0 -148
  5. package/template/src/navigation/RootNavigator.tsx +4 -26
  6. package/template/src/navigation/index.ts +0 -1
  7. package/template/src/navigation/navigationRef.ts +1 -1
  8. package/template/src/screens/HomeScreen.tsx +3 -215
  9. package/template/src/screens/auth/LoginScreen.tsx +13 -13
  10. package/template/src/screens/auth/index.ts +1 -6
  11. package/template/src/screens/index.ts +0 -35
  12. package/template/src/services/api.ts +5 -5
  13. package/template/src/services/authService.ts +3 -299
  14. package/template/src/services/mainServices.ts +19 -1914
  15. package/template/src/types/navigation.ts +5 -155
  16. package/template/src/utils/index.ts +5 -8
  17. package/template/src/navigation/MiddleStack.tsx +0 -35
  18. package/template/src/screens/auth/AddMamber.tsx +0 -428
  19. package/template/src/screens/auth/ForgotPasswordScreen.tsx +0 -176
  20. package/template/src/screens/auth/OTPVerifyScreen.tsx +0 -359
  21. package/template/src/screens/auth/RegisterScreen.tsx +0 -430
  22. package/template/src/screens/auth/SuccessScreen.tsx +0 -201
  23. package/template/src/screens/chat/ChatScreen.tsx +0 -1819
  24. package/template/src/screens/chat/ChatThreadsScreen.tsx +0 -360
  25. package/template/src/screens/chat/ReportMessageScreen.tsx +0 -238
  26. package/template/src/screens/clubs/Announcements.tsx +0 -426
  27. package/template/src/screens/clubs/BuyRaffleTicketsScreen.tsx +0 -568
  28. package/template/src/screens/clubs/ClubDeteils.tsx +0 -497
  29. package/template/src/screens/clubs/JoinClub.tsx +0 -841
  30. package/template/src/screens/events/EventScreen.tsx +0 -460
  31. package/template/src/screens/raffles/MyReferralMembersScreen.tsx +0 -758
  32. package/template/src/screens/raffles/RaffleDetailsScreen.tsx +0 -762
  33. package/template/src/screens/raffles/RafflesScreen.tsx +0 -495
  34. package/template/src/screens/raffles/SetRaffleReminderScreen.tsx +0 -390
  35. package/template/src/screens/teams/JoinTeamScreen.tsx +0 -464
  36. package/template/src/screens/teams/MyTeamDetailsScreen.tsx +0 -979
  37. package/template/src/screens/teams/MyTeamScreen.tsx +0 -568
  38. package/template/src/screens/teams/PendingRequestsScreen.tsx +0 -426
  39. package/template/src/screens/volunteerOpportunities/SetReminderScreen.tsx +0 -631
  40. package/template/src/screens/volunteerOpportunities/VolunteerOpportunitiesDetailsScreen.tsx +0 -1049
  41. package/template/src/screens/volunteerOpportunities/VolunteerOpportunitiesScreen.tsx +0 -608
  42. package/template/src/utils/ClubSearchManager.ts +0 -222
@@ -1,428 +0,0 @@
1
- import React, { useState, useCallback, useRef } from 'react';
2
- import {
3
- View,
4
- Text,
5
- StyleSheet,
6
- StatusBar,
7
- TouchableOpacity,
8
- FlatList,
9
- Image,
10
- Platform,
11
- Alert,
12
- } from 'react-native';
13
- import { SafeAreaView } from 'react-native-safe-area-context';
14
- import { Button } from '../../components/common';
15
- import { theme } from '../../constants';
16
- import { Strings } from '../../constants/strings';
17
- import { moderateScale } from '../../utils/scaling';
18
- import { Fonts } from '../../constants/Fonts';
19
- import SVG from '../../assets/icons';
20
- import { AddMemberScreenProps } from '../../types/navigation';
21
- import { useAuthStore } from '../../stores/authStore';
22
- import { getApiErrorInfo, MembersRequest, useMembers, useGetImageUrl } from '../../services/authService';
23
- import { useUpdateMember, UpdateMemberRequest } from '../../services/authService';
24
- import ToastManager from '../../components/common/ToastManager';
25
- import { SuccessModal } from './SuccessScreen';
26
- import { AddMemberModal } from '../../components/common/index';
27
-
28
-
29
- interface Member {
30
- id: string;
31
- name: string;
32
- profileImage: string;
33
- isOwner: boolean;
34
- memberImageUrl?: string;
35
- }
36
-
37
- export const AddMemberScreen: React.FC<AddMemberScreenProps> = ({ navigation }) => {
38
- const updateMemberMutation = useUpdateMember();
39
- const getImageUrlMutation = useGetImageUrl();
40
-
41
- const [showModal, setShowModal] = useState(false);
42
- const [addMemberSuccessClose, setAddMemberSuccessClose] = useState(false);
43
- const [showSuccessModal, setShowSuccessModal] = useState(false);
44
- const [addMemberImageUrls, setAddMemberImageUrls] = useState<Record<number, string>>({});
45
- const { setUser } = useAuthStore();
46
-
47
-
48
-
49
- const membersParams: MembersRequest = {
50
- PageNumber: 0,
51
- PageSize: 20,
52
- };
53
-
54
- const handleUpdateMember = (memberId: number, memberData: UpdateMemberRequest) => {
55
- updateMemberMutation.mutate({
56
- memberId,
57
- memberData: memberData
58
- }, {
59
- onSuccess: (response) => {
60
- if(response?.status === 202){
61
- Alert.alert("Add Member", response.data.message);
62
- }else{
63
- setAddMemberSuccessClose(true);
64
- setTimeout(() => {
65
- setShowModal(false);
66
- ToastManager.success( response.data.message);
67
- refetch()
68
- setAddMemberSuccessClose(false);
69
- }, 1000);
70
- }
71
- },
72
- onError: (error) => {
73
- const errorInfo = getApiErrorInfo(error);
74
- ToastManager.error(errorInfo?.message);
75
- }
76
- });
77
- };
78
-
79
- const { data, isLoading, isError, refetch } = useMembers(membersParams);
80
- const rawMembers = (data?.data?.data || []).filter((member: Member) => !member.isOwner);
81
-
82
-
83
- // Add memberImageUrl from state
84
- const members = rawMembers.map((member: any) => ({
85
- ...member,
86
- memberImageUrl: addMemberImageUrls[member.id] || undefined
87
- }));
88
-
89
- const ownerName = data?.data?.data?.find(member => member.isOwner === true)?.name;
90
-
91
-
92
- const handleSkip = () => {
93
- setShowSuccessModal(true);
94
- };
95
-
96
- const handleAddMember = () => {
97
- setShowModal(!showModal);
98
- };
99
-
100
- const handleSaveMember = (memberData: any) => {
101
- const newMember: UpdateMemberRequest = {
102
- birthDate: memberData.dateOfBirth,
103
- relation: memberData.relationshipType,
104
- name: memberData.name,
105
- profileImage: memberData.profileImage?.fileName || '',
106
- };
107
- handleUpdateMember(0, newMember)
108
- };
109
-
110
- const handleNext = () => {
111
- // Update user to mark that member addition is complete
112
- const currentUser = useAuthStore.getState().user;
113
- if (currentUser) {
114
- setUser({
115
- ...currentUser,
116
- isAddMember: false
117
- });
118
- }
119
- };
120
-
121
- const getAddMemberImageUrls = async (memberItem: any) => {
122
- if (memberItem?.profileImage == null) return;
123
-
124
- // Skip if already fetched
125
- if (addMemberImageUrls[memberItem.id] !== undefined) return;
126
-
127
- const [folder, blobName] = memberItem?.profileImage.split('/');
128
-
129
- try {
130
- getImageUrlMutation.mutate({
131
- containerName: folder,
132
- blobName: blobName
133
- }, {
134
- onSuccess: (response) => {
135
- const imageUrl = response?.data?.data?.url;
136
- console.log('AddMember Image URL response', response.data);
137
-
138
- // Prefetch image into React Native cache to avoid blink when scrolling back
139
- if (imageUrl) {
140
- Image.prefetch(imageUrl).catch((prefetchError) => {
141
- console.warn('AddMember Image prefetch failed:', prefetchError);
142
- });
143
- }
144
-
145
- // Simple state update instead of recreating entire query cache
146
- setAddMemberImageUrls(prev => ({
147
- ...prev,
148
- [memberItem.id]: imageUrl
149
- }));
150
- },
151
- onError: (error) => {
152
- const errorInfo = getApiErrorInfo(error);
153
- ToastManager.error(errorInfo?.message);
154
- console.error('Failed to get AddMember image URL:', error);
155
-
156
- // Simple state update for failed image fetch
157
- setAddMemberImageUrls(prev => ({
158
- ...prev,
159
- [memberItem.id]: ""
160
- }));
161
- }
162
- });
163
- } catch (err) {
164
- console.error('Error in getAddMemberImageUrls:', err);
165
- setAddMemberImageUrls(prev => ({
166
- ...prev,
167
- [memberItem.id]: ""
168
- }));
169
- }
170
- };
171
-
172
- const onAddMemberViewableItemsChanged = useCallback(
173
- ({
174
- viewableItems,
175
- }: {
176
- viewableItems: Array<{ item: any }>;
177
- }) => {
178
- const items = viewableItems.map(({ item }) => item);
179
-
180
- // On-demand image fetching for viewable member items
181
- items.forEach((item) => {
182
- if (item && !item.memberImageUrl) {
183
- console.log('AddMember onViewableItemsChanged>>>>>', JSON.stringify(item));
184
- getAddMemberImageUrls(item);
185
- }
186
- });
187
- },
188
- [getAddMemberImageUrls]
189
- );
190
-
191
- const addMemberViewabilityConfig = useRef({
192
- viewAreaCoveragePercentThreshold: 50, // Increased threshold for better on-demand loading
193
- minimumViewTime: 100, // Minimum time item must be visible before triggering
194
- waitForInteraction: false, // Don't wait for user interaction
195
- }).current;
196
-
197
- const renderMemberItem = ({ item }: { item: Member }) => {
198
- return (
199
- <View style={styles.memberItem}>
200
- {item?.profileImage ? (
201
- <Image source={{ uri: item?.profileImage }} style={styles.profileImage} />
202
- ) : (
203
- <View style={[styles.profileImage, { backgroundColor: theme.colors.surface, justifyContent: 'center', alignItems: 'center' }]}>
204
- <SVG.emptyUser width={moderateScale(20)} height={moderateScale(20)} />
205
- </View>
206
- )}
207
- <Text style={styles.memberName}>{item.name}</Text>
208
- </View>
209
- );
210
- };
211
-
212
- const keyExtractor = (item: Member, index: number) => {
213
- // Create a more robust key that combines multiple attributes to ensure uniqueness
214
- const id = item?.id?.toString() || '';
215
- const name = item?.name || '';
216
- const email = item?.profileImage || '';
217
-
218
- // Create a unique composite key using multiple attributes
219
- const keyParts = [id, name, email].filter(part => part && part.toString().trim() !== '');
220
-
221
- if (keyParts.length > 0) {
222
- return `member-${keyParts.join('-')}-${index}`;
223
- }
224
-
225
- // Ultimate fallback with index to ensure uniqueness
226
- return `member-unknown-${index}`;
227
- };
228
-
229
- return (
230
- <View style={styles.container}>
231
- <StatusBar
232
- barStyle="light-content"
233
- backgroundColor={theme.colors.blue}
234
- translucent={Platform.OS === 'android' ? true : false}
235
- />
236
-
237
- {/* Status Bar Background for iOS */}
238
- {Platform.OS === 'ios' && <View style={styles.statusBarBackground} />}
239
-
240
- {/* Header Section */}
241
- <SafeAreaView style={styles.header} edges={['top']}>
242
- <TouchableOpacity onPress={handleSkip} style={styles.skipButton}>
243
- <Text style={styles.skipButtonText}>{Strings.AUTH.SKIP}</Text>
244
- </TouchableOpacity>
245
- <Text style={styles.headerTitle}>{Strings.AUTH.ADD_MEMBER_TITLE}</Text>
246
- </SafeAreaView>
247
-
248
- {/* Add Member Button */}
249
- <View style={styles.addMemberContainer}>
250
- <TouchableOpacity style={styles.addMemberButton} onPress={handleAddMember}>
251
- <SVG.icAdd width={moderateScale(20)} height={moderateScale(20)} />
252
- <Text style={styles.addMemberButtonText}>{Strings.AUTH.ADD_MEMBER_SUBTITLE}</Text>
253
- </TouchableOpacity>
254
- </View>
255
-
256
- {/* Content Section */}
257
- <View style={styles.content}>
258
- <Text style={styles.sectionTitle}>{Strings.AUTH.ADD_MEMBER_SECTION_TITLE}</Text>
259
-
260
- <FlatList
261
- data={members}
262
- renderItem={renderMemberItem}
263
- keyExtractor={keyExtractor}
264
- style={styles.membersList}
265
- showsVerticalScrollIndicator={false}
266
- contentContainerStyle={styles.flatListContent}
267
- removeClippedSubviews={true}
268
- maxToRenderPerBatch={10}
269
- windowSize={10}
270
- initialNumToRender={10}
271
- // onViewableItemsChanged={onAddMemberViewableItemsChanged}
272
- // viewabilityConfig={addMemberViewabilityConfig}
273
- getItemLayout={(data, index) => ({
274
- length: moderateScale(70), // Approximate item height
275
- offset: moderateScale(70) * index,
276
- index,
277
- })}
278
- refreshing={isLoading}
279
- onRefresh={refetch}
280
- ListEmptyComponent={() => (
281
- <Text style={styles.emptyListText}>No members found</Text>
282
- )}
283
-
284
- />
285
- </View>
286
-
287
- <View style={styles.bottomContainer}>
288
- <Button
289
- disabled={members?.length <= 0 ? true : false}
290
- title={Strings.AUTH.NEXT}
291
- onPress={handleSkip}
292
- variant="primary"
293
- size="medium"
294
- style={styles.nextButton}
295
- />
296
- </View>
297
- <AddMemberModal
298
- visible={showModal}
299
- onClose={() => setShowModal(false)}
300
- onSave={handleSaveMember}
301
- onSuccessClose={addMemberSuccessClose}
302
- />
303
- <SuccessModal
304
- visible={showSuccessModal}
305
- userName={ownerName}
306
- onClose={() => setShowSuccessModal(false)}
307
- onGoToHome={() => handleNext()}
308
- onSkipToAddMember={() => navigation.navigate('AddMember' as never)}
309
- />
310
- </View>
311
- );
312
- };
313
-
314
- const styles = StyleSheet.create({
315
- container: {
316
- flex: 1,
317
- backgroundColor: theme.colors.blue,
318
- },
319
- statusBarBackground: {
320
- position: 'absolute',
321
- top: 0,
322
- left: 0,
323
- right: 0,
324
- height: Platform.OS === 'ios' ? 44 : 0, // Status bar height for iOS
325
- backgroundColor: theme.colors.blue,
326
- zIndex: 1000,
327
- },
328
- header: {
329
- backgroundColor: theme.colors.blue,
330
- paddingHorizontal: theme.spacing.lg,
331
- paddingVertical: theme.spacing.md,
332
- justifyContent: 'space-between',
333
- alignItems: 'center',
334
- },
335
- headerTitle: {
336
- alignSelf: 'flex-start',
337
- fontFamily: Fonts.outfitSemiBold,
338
- fontSize: moderateScale(24),
339
- color: theme.colors.white,
340
- marginTop: moderateScale(20),
341
- },
342
- skipButton: {
343
- alignSelf: 'flex-end',
344
- paddingHorizontal: theme.spacing.sm,
345
- paddingVertical: theme.spacing.xs,
346
- },
347
- skipButtonText: {
348
- fontFamily: Fonts.outfitSemiBold,
349
- fontSize: theme.typography.fontSize.md,
350
- color: theme.colors.white,
351
- },
352
- addMemberContainer: {
353
- backgroundColor: "theme.colors.blue",
354
- paddingHorizontal: theme.spacing.lg,
355
- paddingBottom: theme.spacing.md,
356
- },
357
- addMemberButton: {
358
- flexDirection: 'row',
359
- alignItems: 'center',
360
- justifyContent: 'center',
361
- backgroundColor: 'transparent',
362
- borderWidth: 1.5,
363
- borderColor: theme.colors.appleGreen,
364
- borderRadius: theme.borderRadius.xxl,
365
- paddingVertical: theme.spacing.sm,
366
- paddingHorizontal: theme.spacing.lg,
367
- gap: theme.spacing.sm,
368
- },
369
- addMemberButtonText: {
370
- fontFamily: Fonts.outfitMedium,
371
- fontSize: theme.typography.fontSize.md,
372
- color: theme.colors.appleGreen,
373
- },
374
- content: {
375
- flex: 1,
376
- backgroundColor: theme.colors.background,
377
- paddingHorizontal: theme.spacing.lg,
378
- paddingTop: theme.spacing.md,
379
- borderTopLeftRadius: moderateScale(30),
380
- borderTopRightRadius: moderateScale(30),
381
- },
382
- sectionTitle: {
383
- fontFamily: Fonts.outfitMedium,
384
- fontSize: theme.typography.fontSize.xs,
385
- color: theme.colors.blue,
386
- marginBottom: theme.spacing.md,
387
- letterSpacing: 0.5,
388
- },
389
- membersList: {
390
- flex: 1,
391
- },
392
- flatListContent: {
393
- paddingBottom: theme.spacing.sm,
394
- },
395
- memberItem: {
396
- flexDirection: 'row',
397
- alignItems: 'center',
398
- paddingVertical: theme.spacing.sm,
399
- gap: theme.spacing.md,
400
- },
401
- profileImage: {
402
- width: moderateScale(50),
403
- height: moderateScale(50),
404
- borderRadius: moderateScale(25),
405
- backgroundColor: theme.colors.surface,
406
- },
407
- memberName: {
408
- fontFamily: Fonts.outfitBold,
409
- fontSize: theme.typography.fontSize.md,
410
- color: theme.colors.text,
411
- },
412
- bottomContainer: {
413
- height: moderateScale(120),
414
- paddingHorizontal: theme.spacing.lg,
415
- paddingVertical: theme.spacing.md,
416
- backgroundColor: theme.colors.white,
417
- },
418
- nextButton: {
419
- width: '100%',
420
- marginTop: moderateScale(-10),
421
- },
422
- emptyListText: {
423
- fontFamily: Fonts.outfitMedium,
424
- fontSize: theme.typography.fontSize.md,
425
- color: theme.colors.text,
426
- textAlign: 'center',
427
- },
428
- });
@@ -1,176 +0,0 @@
1
- import React, { useState } from 'react';
2
- import {
3
- View,
4
- Text,
5
- StyleSheet,
6
- TouchableOpacity,
7
- Keyboard,
8
- Platform,
9
- } from 'react-native';
10
- import { Button, hideLoader, showLoader, TextInput } from '../../components/common';
11
- import { theme } from '../../constants';
12
- import { Strings } from '../../constants/strings';
13
- import { Validation } from '../../utils';
14
- import { ImageBackground } from 'react-native';
15
- import Images from '../../assets/images';
16
- import SVG from '../../assets/icons';
17
- import { moderateScale } from '../../utils/scaling';
18
- import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
19
- import { SafeAreaView } from 'react-native-safe-area-context';
20
- import { StatusBar } from 'react-native';
21
- import { Fonts } from '../../constants/Fonts';
22
- import ToastManager from '../../components/common/ToastManager';
23
- import { useForgotPassword } from '../../services/authService';
24
-
25
- interface ForgotPasswordScreenProps {
26
- navigation: any;
27
- }
28
-
29
- export const ForgotPasswordScreen: React.FC<ForgotPasswordScreenProps> = ({ navigation }) => {
30
- const [email, setEmail] = useState('');
31
- const forgotPasswordMutation = useForgotPassword();
32
-
33
- const handleSubmit = async () => {
34
- Keyboard.dismiss();
35
- if (!Validation.validateForgotPasswordFormAndShowErrors(email)) {
36
- return;
37
- }
38
-
39
- showLoader();
40
- forgotPasswordMutation.mutate({ email: email }
41
- , {
42
- onSuccess: (response) => {
43
- navigation.reset({
44
- index: 0,
45
- routes: [{ name: 'Login' }],
46
- });
47
- ToastManager.success(response.data.message || 'Forgot password successful!');
48
- hideLoader();
49
- },
50
- onError: (error: any) => {
51
- console.error('Forgot password error:', error);
52
- if (error.response?.data?.message) {
53
- ToastManager.error( error.response.data.message);
54
- } else if (error.message) {
55
- ToastManager.error( error.message);
56
- } else {
57
- ToastManager.error('Something went wrong. Please try again.');
58
- }
59
- hideLoader();
60
- },
61
- onSettled: () => {
62
- hideLoader();
63
- }
64
- });
65
- };
66
-
67
-
68
-
69
- const onEmailChange = (text: string) => {
70
- const trimmedText = text.trim();
71
- setEmail(trimmedText);
72
- };
73
-
74
-
75
- return (
76
- <ImageBackground
77
- source={Images.backgroundImage}
78
- style={styles.backgroundImage}
79
- resizeMode="contain"
80
- >
81
- <SafeAreaView edges={['top']} style={{ flex: 1 }}>
82
- <StatusBar
83
- barStyle="dark-content"
84
- backgroundColor={theme.colors.background}
85
- translucent={Platform.OS === 'android' ? true : false}
86
- />
87
- <KeyboardAwareScrollView
88
- enableOnAndroid={true}
89
- contentContainerStyle={styles.container}
90
- keyboardShouldPersistTaps="handled"
91
- >
92
- <TouchableOpacity style={{ marginLeft: theme.spacing.md }} onPress={() => navigation.goBack()}>
93
- <SVG.arrowLeft height={moderateScale(30)} width={moderateScale(30)} style={styles.logoIcon} />
94
- </TouchableOpacity>
95
- <View style={styles.logoSection}>
96
- <SVG.login_logo height={moderateScale(150)} width={moderateScale(150)} style={styles.logoIcon} />
97
- </View>
98
-
99
- {/* Login Form */}
100
- <View style={styles.loginSection}>
101
- <Text style={styles.loginTitle}>{Strings.AUTH.FORGOT_PASSWORD_TITLE}</Text>
102
- <Text style={styles.loginSubTitle}>{Strings.AUTH.FORGOT_PASSWORD_SUBTITLE}</Text>
103
-
104
- <View style={styles.form}>
105
- <TextInput
106
- label={Strings.COMMON.EMAIL}
107
- value={email}
108
- onChangeText={onEmailChange}
109
- placeholder={Strings.COMMON.EMAIL_PLACEHOLDER}
110
- keyboardType="email-address"
111
- autoCapitalize="none"
112
- autoCorrect={false}
113
- variant="outlined"
114
- leftIcon={SVG.Email}
115
- maxLength={50}
116
- />
117
- <Button
118
- title={Strings.COMMON.SUBMIT}
119
- onPress={handleSubmit}
120
- variant="primary"
121
- size="medium"
122
- style={styles.button}
123
- />
124
- </View>
125
- </View>
126
- </KeyboardAwareScrollView>
127
- </SafeAreaView>
128
- </ImageBackground>
129
- );
130
- };
131
-
132
- const styles = StyleSheet.create({
133
- backgroundImage: {
134
- flex: 1,
135
- width: '100%',
136
- height: '100%',
137
- },
138
- container: {
139
- flexGrow: 1,
140
- },
141
- // Logo Section
142
- logoSection: {
143
- justifyContent: 'center',
144
- alignItems: 'center',
145
- },
146
- logoIcon: {
147
- marginBottom: theme.spacing.xs,
148
- },
149
- // Login Section
150
- loginSection: {
151
- flex: 1,
152
- backgroundColor: theme.colors.background,
153
- paddingHorizontal: theme.spacing.lg,
154
- borderTopLeftRadius: moderateScale(30),
155
- borderTopRightRadius: moderateScale(30),
156
- paddingTop: moderateScale(20),
157
- },
158
- loginTitle: {
159
- fontFamily: Fonts.outfitSemiBold,
160
- fontSize: moderateScale(24),
161
- color: theme.colors.black,
162
- marginBottom: theme.spacing.sm,
163
- },
164
- loginSubTitle: {
165
- fontFamily: Fonts.outfitRegular,
166
- fontSize: moderateScale(13),
167
- color: theme.colors.black,
168
- marginBottom: theme.spacing.md,
169
- },
170
- form: {
171
- gap: theme.spacing.sm,
172
- },
173
- button: {
174
- marginTop: theme.spacing.md,
175
- }
176
- });