nx-react-native-cli 2.7.1 → 3.0.1
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/lib/index.cjs +27 -27
- package/package.json +1 -1
- package/templates/shared/apps/mobile/src/app/index.tsx +13 -8
- package/templates/shared/apps/mobile/src/components/atoms/AlertManager/alert-manager.component.tsx +134 -0
- package/templates/shared/apps/mobile/src/components/atoms/AlertManager/alert-manager.types.ts +18 -0
- package/templates/shared/apps/mobile/src/components/atoms/AlertManager/alert.service.ts +27 -0
- package/templates/shared/apps/mobile/src/components/atoms/AlertManager/index.ts +3 -0
- package/templates/shared/apps/mobile/src/components/atoms/BottomSheet/bottom-sheet.component.tsx +14 -8
- package/templates/shared/apps/mobile/src/components/atoms/Button/button.component.tsx +1 -1
- package/templates/shared/apps/mobile/src/components/atoms/DateModalInput/date-modal-input.component.tsx +69 -0
- package/templates/shared/apps/mobile/src/components/atoms/DateModalInput/index.ts +1 -0
- package/templates/shared/apps/mobile/src/components/atoms/DatePicker/date-picker.component.tsx +44 -0
- package/templates/shared/apps/mobile/src/components/atoms/DatePicker/index.ts +1 -0
- package/templates/shared/apps/mobile/src/components/atoms/DateTextInput/date-text-input.component.tsx +218 -0
- package/templates/shared/apps/mobile/src/components/atoms/DateTextInput/index.ts +1 -0
- package/templates/shared/apps/mobile/src/components/atoms/Divider/divider-component.tsx +1 -1
- package/templates/shared/apps/mobile/src/components/atoms/GradientBackground/gradient-background.component.tsx +45 -0
- package/templates/shared/apps/mobile/src/components/atoms/GradientBackground/index.ts +1 -0
- package/templates/shared/apps/mobile/src/components/atoms/InputLayout/input-layout.component.tsx +12 -4
- package/templates/shared/apps/mobile/src/components/atoms/KeyboardAccessory/keyboard-accessory.component.tsx +6 -3
- package/templates/shared/apps/mobile/src/components/atoms/Modal/modal.component.tsx +2 -0
- package/templates/shared/apps/mobile/src/components/atoms/ScreenLoader/screen-loader.component.tsx +6 -1
- package/templates/shared/apps/mobile/src/components/atoms/SelectDropdown/index.ts +1 -0
- package/templates/shared/apps/mobile/src/components/atoms/SelectDropdown/select-dropdown.component.tsx +223 -0
- package/templates/shared/apps/mobile/src/components/atoms/Skeleton/skeleton.component.tsx +1 -1
- package/templates/shared/apps/mobile/src/components/atoms/TextInput/bottom-sheet-text-input.component.tsx +4 -3
- package/templates/shared/apps/mobile/src/components/atoms/TextInput/text-input.component.tsx +8 -4
- package/templates/shared/apps/mobile/src/components/atoms/ThemeManager/index.ts +1 -0
- package/templates/shared/apps/mobile/src/components/atoms/ThemeManager/theme-manager.component.tsx +27 -0
- package/templates/shared/apps/mobile/src/components/atoms/ToastManager/index.ts +3 -0
- package/templates/shared/apps/mobile/src/components/atoms/ToastManager/toast-manager.component.tsx +109 -0
- package/templates/shared/apps/mobile/src/components/atoms/ToastManager/toast-manager.types.ts +10 -0
- package/templates/shared/apps/mobile/src/components/atoms/ToastManager/toast.service.ts +27 -0
- package/templates/shared/apps/mobile/src/components/atoms/Typography/typography.component.tsx +1 -1
- package/templates/shared/apps/mobile/src/components/atoms/index.ts +8 -0
- package/templates/shared/apps/mobile/src/components/molecules/BackButton/back-button.component.tsx +1 -1
- package/templates/shared/apps/mobile/src/components/molecules/ScreenContainer/screen-container.component.tsx +2 -22
- package/templates/shared/apps/mobile/src/components/molecules/ScreenHeader/screen-header.component.tsx +2 -2
- package/templates/shared/apps/mobile/src/hooks/index.ts +1 -0
- package/templates/shared/apps/mobile/src/hooks/usePushNotifications.hook.ts +104 -0
- package/templates/shared/apps/mobile/src/hooks/useToggleDarkMode.hook.tsx +24 -2
- package/templates/shared/apps/mobile/src/icons/alert-triangle.svg +5 -0
- package/templates/shared/apps/mobile/src/icons/check-circle.svg +4 -0
- package/templates/shared/apps/mobile/src/icons/chevron-down.svg +1 -0
- package/templates/shared/apps/mobile/src/icons/chevron-right.svg +1 -0
- package/templates/shared/apps/mobile/src/icons/index.ts +18 -1
- package/templates/shared/apps/mobile/src/icons/info.svg +5 -0
- package/templates/shared/apps/mobile/src/icons/x-circle.svg +5 -0
- package/templates/shared/apps/mobile/src/routes/index.tsx +19 -14
- package/templates/shared/apps/mobile/src/screens/LandingScreen/landing.screen.tsx +232 -8
- package/templates/shared/apps/mobile/src/stores/local-storage.store.ts +9 -5
- package/templates/shared/apps/mobile/src/stores/theme.slice.ts +15 -0
- package/templates/shared/apps/mobile/src/stores/user.slice.ts +5 -1
- package/templates/shared/apps/mobile/src/tailwind/index.ts +3 -3
- package/templates/shared/apps/mobile/tailwind.config.js +14 -0
- package/templates/shared/patches/react-native-animatable+1.4.0.patch +71 -0
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { useFocusEffect } from '@react-navigation/native';
|
|
2
1
|
import React, { ReactElement } from 'react';
|
|
3
2
|
import {
|
|
4
3
|
NativeScrollEvent,
|
|
5
4
|
NativeSyntheticEvent,
|
|
6
5
|
Platform,
|
|
7
6
|
RefreshControlProps,
|
|
8
|
-
StatusBar,
|
|
9
|
-
StatusBarStyle,
|
|
10
7
|
StyleProp,
|
|
11
8
|
View,
|
|
12
9
|
ViewStyle,
|
|
@@ -15,20 +12,15 @@ import { ScrollView as RNScrollView } from 'react-native-gesture-handler';
|
|
|
15
12
|
import { Edge, SafeAreaProviderProps, SafeAreaView } from 'react-native-safe-area-context';
|
|
16
13
|
|
|
17
14
|
import { KeyboardAwareScrollView } from '@/components';
|
|
18
|
-
import CONFIG from '@/config';
|
|
19
15
|
import { tw } from '@/tailwind';
|
|
20
16
|
|
|
21
17
|
type Props = SafeAreaProviderProps & {
|
|
22
|
-
barStyle?: StatusBarStyle;
|
|
23
18
|
scrollViewRef?: React.RefObject<RNScrollView | null>;
|
|
24
19
|
containerStyle?: StyleProp<ViewStyle>;
|
|
25
20
|
excludedEdges?: Edge[];
|
|
26
21
|
extraBottomPadding?: number;
|
|
27
22
|
hasScroll?: boolean;
|
|
28
23
|
refreshControl?: ReactElement;
|
|
29
|
-
shouldShowStatusBar?: boolean;
|
|
30
|
-
shouldBeTranslucent?: boolean;
|
|
31
|
-
statusBarColor?: string;
|
|
32
24
|
useSafeAreaView?: boolean;
|
|
33
25
|
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
|
34
26
|
};
|
|
@@ -43,7 +35,6 @@ const safeAreaViewEdges: Edge[] = Platform.select({
|
|
|
43
35
|
|
|
44
36
|
export function ScreenContainer(props: Props) {
|
|
45
37
|
const {
|
|
46
|
-
barStyle = 'dark-content',
|
|
47
38
|
children,
|
|
48
39
|
containerStyle,
|
|
49
40
|
excludedEdges = [],
|
|
@@ -52,20 +43,9 @@ export function ScreenContainer(props: Props) {
|
|
|
52
43
|
onScroll,
|
|
53
44
|
refreshControl,
|
|
54
45
|
scrollViewRef,
|
|
55
|
-
shouldBeTranslucent = false,
|
|
56
|
-
shouldShowStatusBar = true,
|
|
57
|
-
statusBarColor = 'transparent',
|
|
58
46
|
style,
|
|
59
47
|
useSafeAreaView = true,
|
|
60
48
|
} = props;
|
|
61
|
-
useFocusEffect(() => {
|
|
62
|
-
StatusBar.setHidden(!shouldShowStatusBar);
|
|
63
|
-
if (CONFIG.IS_ANDROID) {
|
|
64
|
-
StatusBar.setBackgroundColor(statusBarColor);
|
|
65
|
-
StatusBar.setTranslucent(!shouldBeTranslucent);
|
|
66
|
-
StatusBar.setBarStyle(barStyle);
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
49
|
|
|
70
50
|
const defaultContainerStyle = [
|
|
71
51
|
defaultStyle,
|
|
@@ -76,7 +56,7 @@ export function ScreenContainer(props: Props) {
|
|
|
76
56
|
|
|
77
57
|
if (!useSafeAreaView) {
|
|
78
58
|
return (
|
|
79
|
-
<View style={[tw`flex-1 bg-white`, style]}>
|
|
59
|
+
<View style={[tw`dark:bg-background flex-1 bg-white`, style]}>
|
|
80
60
|
{hasScroll ? (
|
|
81
61
|
<KeyboardAwareScrollView
|
|
82
62
|
containerStyle={defaultContainerStyle}
|
|
@@ -99,7 +79,7 @@ export function ScreenContainer(props: Props) {
|
|
|
99
79
|
: safeAreaViewEdges;
|
|
100
80
|
|
|
101
81
|
return (
|
|
102
|
-
<SafeAreaView edges={edges} style={[tw`flex-1 bg-white`, style]}>
|
|
82
|
+
<SafeAreaView edges={edges} style={[tw`dark:bg-background flex-1 bg-white`, style]}>
|
|
103
83
|
{hasScroll ? (
|
|
104
84
|
<KeyboardAwareScrollView
|
|
105
85
|
containerStyle={defaultContainerStyle}
|
|
@@ -39,7 +39,7 @@ export function ScreenHeader(props: Props) {
|
|
|
39
39
|
{extraActionComponent ? (
|
|
40
40
|
extraActionComponent
|
|
41
41
|
) : (
|
|
42
|
-
<GearIcon height={25} style={tw`text-
|
|
42
|
+
<GearIcon height={25} style={tw`dark:text-foreground text-gray-900`} width={25} />
|
|
43
43
|
)}
|
|
44
44
|
</View>
|
|
45
45
|
</TouchableOpacity>
|
|
@@ -51,7 +51,7 @@ export function ScreenHeader(props: Props) {
|
|
|
51
51
|
{hasBackButton && <BackButton style={tw`z-10`} onPress={onBackPress} />}
|
|
52
52
|
<Typography
|
|
53
53
|
style={[
|
|
54
|
-
tw`text-
|
|
54
|
+
tw`dark:text-foreground absolute inset-x-0 top-4 text-center text-xl font-medium text-gray-900`,
|
|
55
55
|
titleStyle,
|
|
56
56
|
]}
|
|
57
57
|
>
|
|
@@ -4,6 +4,7 @@ export * from './useDebounce.hook';
|
|
|
4
4
|
export * from './useGetLayoutHeight.hook';
|
|
5
5
|
export * from './useGetLayoutWidth.hook';
|
|
6
6
|
export * from './useNavigation.hook';
|
|
7
|
+
export * from './usePushNotifications.hook';
|
|
7
8
|
export * from './useShakeAnimation.hook';
|
|
8
9
|
export * from './useTextInputChangeFocus.hook';
|
|
9
10
|
export * from './useThrottle.hook';
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { AppState, AppStateStatus, Linking } from 'react-native';
|
|
3
|
+
import { checkNotifications, requestNotifications, RESULTS } from 'react-native-permissions';
|
|
4
|
+
|
|
5
|
+
import { Alert } from '@/components/atoms';
|
|
6
|
+
import { useLocalStorageStore } from '@/stores/local-storage.store';
|
|
7
|
+
import { devLog } from '@/utils/log.util';
|
|
8
|
+
|
|
9
|
+
function showEnableNotificationsAlert() {
|
|
10
|
+
Alert.info({
|
|
11
|
+
actions: [
|
|
12
|
+
{ label: 'Later', variant: 'cancel' },
|
|
13
|
+
{ label: 'Open Settings', onPress: () => Linking.openSettings() },
|
|
14
|
+
],
|
|
15
|
+
message: 'Enable notifications to stay updated with important alerts and messages.',
|
|
16
|
+
title: 'Notifications Disabled',
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isGranted(status: string): boolean {
|
|
21
|
+
return status === RESULTS.GRANTED || status === RESULTS.LIMITED;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// TODO: Implement once @react-native-firebase/messaging is installed
|
|
25
|
+
async function registerFcmToken() {
|
|
26
|
+
devLog('registerFcmToken: Firebase messaging not configured yet');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// TODO: Implement once @react-native-firebase/messaging is installed
|
|
30
|
+
async function removeFcmToken() {
|
|
31
|
+
devLog('removeFcmToken: Firebase messaging not configured yet');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function syncPushNotificationState(status: string) {
|
|
35
|
+
useLocalStorageStore.getState().setPushNotificationsEnabled(isGranted(status));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function requestPushNotification() {
|
|
39
|
+
const { status } = await checkNotifications();
|
|
40
|
+
syncPushNotificationState(status);
|
|
41
|
+
|
|
42
|
+
if (isGranted(status)) {
|
|
43
|
+
await registerFcmToken();
|
|
44
|
+
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { status: requestStatus } = await requestNotifications([]);
|
|
49
|
+
|
|
50
|
+
if (isGranted(requestStatus)) {
|
|
51
|
+
syncPushNotificationState(requestStatus);
|
|
52
|
+
await registerFcmToken();
|
|
53
|
+
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const isBlocked = status === RESULTS.BLOCKED || requestStatus === RESULTS.BLOCKED;
|
|
58
|
+
if (isBlocked) {
|
|
59
|
+
showEnableNotificationsAlert();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function handleAppForeground() {
|
|
64
|
+
const { status } = await checkNotifications();
|
|
65
|
+
syncPushNotificationState(status);
|
|
66
|
+
|
|
67
|
+
if (!isGranted(status)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await registerFcmToken();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function removeCurrentFcmToken(): Promise<void> {
|
|
75
|
+
try {
|
|
76
|
+
await removeFcmToken();
|
|
77
|
+
} catch (error) {
|
|
78
|
+
devLog('Failed to remove FCM token:', error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function usePushNotifications(isAuthenticated: boolean) {
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!isAuthenticated) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const handleAppStateChange = (nextState: AppStateStatus) => {
|
|
89
|
+
if (nextState !== 'active') {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
handleAppForeground().catch((error) => {
|
|
94
|
+
devLog('Failed to check notification permission on foreground:', error);
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const subscription = AppState.addEventListener('change', handleAppStateChange);
|
|
99
|
+
|
|
100
|
+
return () => {
|
|
101
|
+
subscription.remove();
|
|
102
|
+
};
|
|
103
|
+
}, [isAuthenticated]);
|
|
104
|
+
}
|
|
@@ -1,9 +1,31 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
1
2
|
import { useAppColorScheme } from 'twrnc';
|
|
2
3
|
|
|
4
|
+
import { useLocalStorageStore } from '@/stores';
|
|
5
|
+
import { ColorScheme } from '@/stores/theme.slice';
|
|
3
6
|
import { tw } from '@/tailwind';
|
|
4
7
|
|
|
5
8
|
export function useToggleDarkMode() {
|
|
6
|
-
const [,
|
|
9
|
+
const [colorScheme, , setTwColorScheme] = useAppColorScheme(tw);
|
|
10
|
+
const setStoredColorScheme = useLocalStorageStore((s) => s.setColorScheme);
|
|
7
11
|
|
|
8
|
-
|
|
12
|
+
const toggleColorScheme = useCallback(() => {
|
|
13
|
+
const next: ColorScheme = colorScheme === 'dark' ? 'light' : 'dark';
|
|
14
|
+
setTwColorScheme(next);
|
|
15
|
+
setStoredColorScheme(next);
|
|
16
|
+
}, [colorScheme, setTwColorScheme, setStoredColorScheme]);
|
|
17
|
+
|
|
18
|
+
const setColorScheme = useCallback(
|
|
19
|
+
(scheme: ColorScheme) => {
|
|
20
|
+
setTwColorScheme(scheme);
|
|
21
|
+
setStoredColorScheme(scheme);
|
|
22
|
+
},
|
|
23
|
+
[setTwColorScheme, setStoredColorScheme],
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
colorScheme: (colorScheme ?? 'light') as ColorScheme,
|
|
28
|
+
setColorScheme,
|
|
29
|
+
toggleColorScheme,
|
|
30
|
+
};
|
|
9
31
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
2
|
+
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/>
|
|
3
|
+
<path d="M12 9v4"/>
|
|
4
|
+
<path d="M12 17h.01"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
|
|
@@ -1,6 +1,23 @@
|
|
|
1
|
+
import AlertTriangleIcon from './alert-triangle.svg';
|
|
1
2
|
import ArrowLeftIcon from './arrow-left.svg';
|
|
3
|
+
import CheckCircleIcon from './check-circle.svg';
|
|
4
|
+
import ChevronDownIcon from './chevron-down.svg';
|
|
5
|
+
import ChevronRightIcon from './chevron-right.svg';
|
|
2
6
|
import CrossIcon from './cross.svg';
|
|
3
7
|
import GearIcon from './gear.svg';
|
|
8
|
+
import InfoIcon from './info.svg';
|
|
4
9
|
import KeyboardHideIcon from './keyboard-hide.svg';
|
|
10
|
+
import XCircleIcon from './x-circle.svg';
|
|
5
11
|
|
|
6
|
-
export {
|
|
12
|
+
export {
|
|
13
|
+
AlertTriangleIcon,
|
|
14
|
+
ArrowLeftIcon,
|
|
15
|
+
CheckCircleIcon,
|
|
16
|
+
ChevronDownIcon,
|
|
17
|
+
ChevronRightIcon,
|
|
18
|
+
CrossIcon,
|
|
19
|
+
GearIcon,
|
|
20
|
+
InfoIcon,
|
|
21
|
+
KeyboardHideIcon,
|
|
22
|
+
XCircleIcon,
|
|
23
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
2
|
+
<circle cx="12" cy="12" r="10"/>
|
|
3
|
+
<path d="M12 16v-4"/>
|
|
4
|
+
<path d="M12 8h.01"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
2
|
+
<circle cx="12" cy="12" r="10"/>
|
|
3
|
+
<path d="m15 9-6 6"/>
|
|
4
|
+
<path d="m9 9 6 6"/>
|
|
5
|
+
</svg>
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { DefaultTheme, NavigationContainer, Theme } from '@react-navigation/native';
|
|
1
|
+
import { DarkTheme, DefaultTheme, NavigationContainer, Theme } from '@react-navigation/native';
|
|
2
2
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
|
3
|
-
import React, {
|
|
4
|
-
import { StatusBar } from 'react-native';
|
|
3
|
+
import React, { useMemo } from 'react';
|
|
5
4
|
import BootSplash from 'react-native-bootsplash';
|
|
6
5
|
|
|
7
|
-
import CONFIG from '@/config';
|
|
8
6
|
import { Routes } from '@/routes';
|
|
9
7
|
import PrivateRoutes from '@/routes/privateRoutes';
|
|
10
8
|
import PublicRoutes from '@/routes/publicRoutes';
|
|
11
9
|
import { screenOptions } from '@/routes/screen-options';
|
|
10
|
+
import { useLocalStorageStore } from '@/stores';
|
|
12
11
|
import { colors } from '@/tailwind';
|
|
13
12
|
|
|
14
13
|
const RootStack = createNativeStackNavigator();
|
|
@@ -17,11 +16,19 @@ export const noAnimation = {
|
|
|
17
16
|
animationEnabled: false,
|
|
18
17
|
};
|
|
19
18
|
|
|
20
|
-
const
|
|
19
|
+
const lightNavigationTheme: Theme = {
|
|
21
20
|
...DefaultTheme,
|
|
22
21
|
colors: {
|
|
23
22
|
...DefaultTheme.colors,
|
|
24
|
-
background: colors.
|
|
23
|
+
background: colors.white,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const darkNavigationTheme: Theme = {
|
|
28
|
+
...DarkTheme,
|
|
29
|
+
colors: {
|
|
30
|
+
...DarkTheme.colors,
|
|
31
|
+
background: colors.background,
|
|
25
32
|
},
|
|
26
33
|
};
|
|
27
34
|
|
|
@@ -30,15 +37,13 @@ export default function ApplicationRoutes() {
|
|
|
30
37
|
// const isUserAuthenticated = useLocalStorageState((state) => !!state.accessToken);
|
|
31
38
|
const isUserAuthenticated = false;
|
|
32
39
|
const initialRouteName = isUserAuthenticated ? Routes.PRIVATE : Routes.PUBLIC;
|
|
40
|
+
const colorScheme = useLocalStorageStore((s) => s.colorScheme);
|
|
41
|
+
const isDark = colorScheme === 'dark';
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
StatusBar.setTranslucent(true);
|
|
39
|
-
StatusBar.setBarStyle('dark-content');
|
|
40
|
-
}
|
|
41
|
-
}, []);
|
|
43
|
+
const navigationTheme = useMemo(
|
|
44
|
+
() => (isDark ? darkNavigationTheme : lightNavigationTheme),
|
|
45
|
+
[isDark],
|
|
46
|
+
);
|
|
42
47
|
|
|
43
48
|
// if (isLoading) {
|
|
44
49
|
// return <ScreenLoader />;
|
|
@@ -1,17 +1,241 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Image, View } from 'react-native';
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
Alert,
|
|
6
|
+
BottomSheet,
|
|
7
|
+
Button,
|
|
8
|
+
DateModalInput,
|
|
9
|
+
DateTextInput,
|
|
10
|
+
Divider,
|
|
11
|
+
InputLayout,
|
|
12
|
+
Modal,
|
|
13
|
+
OutlinedButton,
|
|
14
|
+
ScreenContainer,
|
|
15
|
+
ScreenHeader,
|
|
16
|
+
SelectDropdown,
|
|
17
|
+
Skeleton,
|
|
18
|
+
TextInput,
|
|
19
|
+
Toast,
|
|
20
|
+
Typography,
|
|
21
|
+
useBottomSheet,
|
|
22
|
+
useModal,
|
|
23
|
+
} from '@/components';
|
|
24
|
+
import { requestPushNotification, useToggleDarkMode } from '@/hooks';
|
|
4
25
|
import { PublicScreenProps, Screens } from '@/routes';
|
|
5
|
-
import {
|
|
26
|
+
import { tw } from '@/tailwind';
|
|
6
27
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
28
|
+
const DROPDOWN_OPTIONS = [
|
|
29
|
+
{ id: '1', label: 'React Native' },
|
|
30
|
+
{ id: '2', label: 'Flutter' },
|
|
31
|
+
{ id: '3', label: 'SwiftUI' },
|
|
32
|
+
{ id: '4', label: 'Jetpack Compose' },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
function SectionTitle({ title }: { title: string }) {
|
|
36
|
+
return <Typography style={tw`mb-2 mt-6 text-lg font-semibold`}>{title}</Typography>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function LandingScreen(_props: PublicScreenProps<Screens.LANDING>) {
|
|
40
|
+
const [textValue, setTextValue] = useState('');
|
|
41
|
+
const [dateModalValue, setDateModalValue] = useState<Date | undefined>();
|
|
42
|
+
const [dateTextValue, setDateTextValue] = useState<Date | undefined>();
|
|
43
|
+
const [selectedDropdown, setSelectedDropdown] = useState<string | undefined>();
|
|
44
|
+
const { hideModal, isVisible: isModalVisible, showModal } = useModal();
|
|
45
|
+
const { closeSheet, expandSheet, sheetRef } = useBottomSheet();
|
|
46
|
+
const { colorScheme, toggleColorScheme } = useToggleDarkMode();
|
|
47
|
+
const isDark = colorScheme === 'dark';
|
|
11
48
|
|
|
12
49
|
return (
|
|
13
50
|
<ScreenContainer>
|
|
14
|
-
<ScreenHeader title="
|
|
51
|
+
<ScreenHeader hasBackButton={false} title="Component Showcase" />
|
|
52
|
+
<View style={tw`px-4 pb-8`}>
|
|
53
|
+
{/* Logo */}
|
|
54
|
+
<View style={tw`mt-4 items-center`}>
|
|
55
|
+
<Image source={require('@/assets/images/logo.png')} style={{ height: 80, width: 80 }} />
|
|
56
|
+
</View>
|
|
57
|
+
|
|
58
|
+
{/* Theme Toggle */}
|
|
59
|
+
<SectionTitle title="Theme" />
|
|
60
|
+
<Button
|
|
61
|
+
title={isDark ? 'Switch to Light Mode' : 'Switch to Dark Mode'}
|
|
62
|
+
onPress={toggleColorScheme}
|
|
63
|
+
/>
|
|
64
|
+
|
|
65
|
+
<Button
|
|
66
|
+
style={tw`mt-3`}
|
|
67
|
+
title="Request Push Notifications"
|
|
68
|
+
onPress={() => requestPushNotification()}
|
|
69
|
+
/>
|
|
70
|
+
|
|
71
|
+
<Divider style={tw`mt-4`} />
|
|
72
|
+
|
|
73
|
+
{/* Typography */}
|
|
74
|
+
<SectionTitle title="Typography" />
|
|
75
|
+
<Typography style={tw`text-2xl font-bold`}>Heading Bold</Typography>
|
|
76
|
+
<Typography style={tw`text-lg font-semibold`}>Subheading Semibold</Typography>
|
|
77
|
+
<Typography style={tw`text-base`}>Body text regular</Typography>
|
|
78
|
+
<Typography style={tw`text-sm text-gray-500`}>Caption text</Typography>
|
|
79
|
+
|
|
80
|
+
<Divider style={tw`mt-4`} />
|
|
81
|
+
|
|
82
|
+
{/* Buttons */}
|
|
83
|
+
<SectionTitle title="Buttons" />
|
|
84
|
+
<View style={tw`gap-3`}>
|
|
85
|
+
<Button title="Primary Button" onPress={() => {}} />
|
|
86
|
+
<Button isLoading title="Loading Button" onPress={() => {}} />
|
|
87
|
+
<Button isDisabled title="Disabled Button" onPress={() => {}} />
|
|
88
|
+
<OutlinedButton title="Outlined Button" onPress={() => {}} />
|
|
89
|
+
</View>
|
|
90
|
+
|
|
91
|
+
<Divider style={tw`mt-4`} />
|
|
92
|
+
|
|
93
|
+
{/* TextInput */}
|
|
94
|
+
<SectionTitle title="Text Input" />
|
|
95
|
+
<View style={tw`gap-3`}>
|
|
96
|
+
<InputLayout isRequired label="Name">
|
|
97
|
+
<TextInput
|
|
98
|
+
placeholder="Enter your name"
|
|
99
|
+
value={textValue}
|
|
100
|
+
onChangeText={setTextValue}
|
|
101
|
+
/>
|
|
102
|
+
</InputLayout>
|
|
103
|
+
<InputLayout error="This field is required" label="With Error">
|
|
104
|
+
<TextInput placeholder="Something went wrong" value="" onChangeText={() => {}} />
|
|
105
|
+
</InputLayout>
|
|
106
|
+
</View>
|
|
107
|
+
|
|
108
|
+
<Divider style={tw`mt-4`} />
|
|
109
|
+
|
|
110
|
+
{/* Select Dropdown */}
|
|
111
|
+
<SectionTitle title="Select Dropdown" />
|
|
112
|
+
<InputLayout label="Framework">
|
|
113
|
+
<SelectDropdown
|
|
114
|
+
options={DROPDOWN_OPTIONS}
|
|
115
|
+
placeholder="Choose a framework"
|
|
116
|
+
selectedId={selectedDropdown}
|
|
117
|
+
onSelect={setSelectedDropdown}
|
|
118
|
+
/>
|
|
119
|
+
</InputLayout>
|
|
120
|
+
|
|
121
|
+
<Divider style={tw`mt-4`} />
|
|
122
|
+
|
|
123
|
+
{/* Date Inputs */}
|
|
124
|
+
<SectionTitle title="Date Modal Input" />
|
|
125
|
+
<InputLayout label="Birthday">
|
|
126
|
+
<DateModalInput
|
|
127
|
+
maximumDate={new Date()}
|
|
128
|
+
placeholder="Pick a date"
|
|
129
|
+
value={dateModalValue}
|
|
130
|
+
onChange={setDateModalValue}
|
|
131
|
+
/>
|
|
132
|
+
</InputLayout>
|
|
133
|
+
|
|
134
|
+
<SectionTitle title="Date Text Input" />
|
|
135
|
+
<InputLayout label="Date of Birth">
|
|
136
|
+
<DateTextInput value={dateTextValue} onChange={setDateTextValue} />
|
|
137
|
+
</InputLayout>
|
|
138
|
+
|
|
139
|
+
<Divider style={tw`mt-4`} />
|
|
140
|
+
|
|
141
|
+
{/* Skeleton */}
|
|
142
|
+
<SectionTitle title="Skeleton" />
|
|
143
|
+
<View style={tw`gap-2`}>
|
|
144
|
+
<Skeleton isLoading>
|
|
145
|
+
<View style={tw`h-5 w-3/4 rounded`} />
|
|
146
|
+
</Skeleton>
|
|
147
|
+
<Skeleton isLoading>
|
|
148
|
+
<View style={tw`h-5 w-1/2 rounded`} />
|
|
149
|
+
</Skeleton>
|
|
150
|
+
<Skeleton isLoading>
|
|
151
|
+
<View style={tw`h-10 w-full rounded-xl`} />
|
|
152
|
+
</Skeleton>
|
|
153
|
+
</View>
|
|
154
|
+
|
|
155
|
+
<Divider style={tw`mt-4`} />
|
|
156
|
+
|
|
157
|
+
{/* Alert */}
|
|
158
|
+
<SectionTitle title="Alerts" />
|
|
159
|
+
<View style={tw`gap-3`}>
|
|
160
|
+
<Button
|
|
161
|
+
title="Show Error Alert"
|
|
162
|
+
onPress={() =>
|
|
163
|
+
Alert.error({
|
|
164
|
+
message: 'Something went wrong. Please try again.',
|
|
165
|
+
title: 'Request Failed',
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
/>
|
|
169
|
+
<Button
|
|
170
|
+
title="Show Success Alert"
|
|
171
|
+
onPress={() =>
|
|
172
|
+
Alert.success({
|
|
173
|
+
title: 'Profile Updated',
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
/>
|
|
177
|
+
<Button
|
|
178
|
+
title="Show Warning Alert"
|
|
179
|
+
onPress={() =>
|
|
180
|
+
Alert.warning({
|
|
181
|
+
actions: [
|
|
182
|
+
{ label: 'Cancel', variant: 'cancel' },
|
|
183
|
+
{ label: 'Delete', onPress: () => {} },
|
|
184
|
+
],
|
|
185
|
+
message: 'This action cannot be undone.',
|
|
186
|
+
title: 'Delete Account',
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
/>
|
|
190
|
+
</View>
|
|
191
|
+
|
|
192
|
+
<Divider style={tw`mt-4`} />
|
|
193
|
+
|
|
194
|
+
{/* Toast */}
|
|
195
|
+
<SectionTitle title="Toasts" />
|
|
196
|
+
<View style={tw`gap-3`}>
|
|
197
|
+
<Button title="Success Toast" onPress={() => Toast.success('Profile saved')} />
|
|
198
|
+
<Button title="Error Toast" onPress={() => Toast.error('Something went wrong')} />
|
|
199
|
+
<Button title="Info Toast" onPress={() => Toast.info('New version available')} />
|
|
200
|
+
<Button title="Warning Toast" onPress={() => Toast.warning('Check your connection')} />
|
|
201
|
+
</View>
|
|
202
|
+
|
|
203
|
+
<Divider style={tw`mt-4`} />
|
|
204
|
+
|
|
205
|
+
{/* Modal */}
|
|
206
|
+
<SectionTitle title="Modal" />
|
|
207
|
+
<Button title="Open Modal" onPress={showModal} />
|
|
208
|
+
<Modal isVisible={isModalVisible} onBackButtonPress={hideModal} onBackdropPress={hideModal}>
|
|
209
|
+
<View pointerEvents="box-none" style={tw`flex-1 items-center justify-center`}>
|
|
210
|
+
<View style={tw`dark:bg-sheet mx-8 w-[85%] rounded-2xl bg-white p-6`}>
|
|
211
|
+
<Typography style={tw`text-lg font-semibold`}>Modal Title</Typography>
|
|
212
|
+
<Typography style={tw`dark:text-subtitle mt-2 text-sm text-gray-500`}>
|
|
213
|
+
This is a custom modal with any content you want.
|
|
214
|
+
</Typography>
|
|
215
|
+
<Button style={tw`mt-4`} title="Close" onPress={hideModal} />
|
|
216
|
+
</View>
|
|
217
|
+
</View>
|
|
218
|
+
</Modal>
|
|
219
|
+
|
|
220
|
+
<Divider style={tw`mt-4`} />
|
|
221
|
+
|
|
222
|
+
{/* Bottom Sheet */}
|
|
223
|
+
<SectionTitle title="Bottom Sheet" />
|
|
224
|
+
<Button title="Open Bottom Sheet" onPress={expandSheet} />
|
|
225
|
+
<BottomSheet
|
|
226
|
+
backgroundStyle={tw`dark:bg-sheet bg-white`}
|
|
227
|
+
sheetRef={sheetRef}
|
|
228
|
+
snapPoints={['40%']}
|
|
229
|
+
>
|
|
230
|
+
<View style={tw`p-6`}>
|
|
231
|
+
<Typography style={tw`text-lg font-semibold`}>Bottom Sheet</Typography>
|
|
232
|
+
<Typography style={tw`dark:text-subtitle mt-2 text-sm text-gray-500`}>
|
|
233
|
+
Swipe down or tap the backdrop to dismiss.
|
|
234
|
+
</Typography>
|
|
235
|
+
<Button style={tw`mt-4`} title="Close" onPress={closeSheet} />
|
|
236
|
+
</View>
|
|
237
|
+
</BottomSheet>
|
|
238
|
+
</View>
|
|
15
239
|
</ScreenContainer>
|
|
16
240
|
);
|
|
17
241
|
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { StateCreator, create } from 'zustand';
|
|
2
2
|
import { PersistOptions, createJSONStorage, persist } from 'zustand/middleware';
|
|
3
3
|
|
|
4
|
+
import { createThemeSlice, ThemeSlice } from './theme.slice';
|
|
4
5
|
import { createUserSlice, UserSlice } from './user.slice';
|
|
5
6
|
|
|
6
7
|
import { MmkvStorage } from '@/stores/mmkvStorage';
|
|
7
8
|
|
|
8
|
-
export type LocalStorageStore = UserSlice &
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
export type LocalStorageStore = UserSlice &
|
|
10
|
+
ThemeSlice & {
|
|
11
|
+
_hasHydrated: boolean;
|
|
12
|
+
setHasHydrated: (hasHydrated: boolean) => void;
|
|
13
|
+
clear: () => void;
|
|
14
|
+
};
|
|
13
15
|
|
|
14
16
|
type MyPersist = (
|
|
15
17
|
config: StateCreator<LocalStorageStore>,
|
|
@@ -21,9 +23,11 @@ export const useLocalStorageStore = create<LocalStorageStore, []>(
|
|
|
21
23
|
(set, get, store) =>
|
|
22
24
|
(() => {
|
|
23
25
|
const userSlice = createUserSlice(set, get, store);
|
|
26
|
+
const themeSlice = createThemeSlice(set, get, store);
|
|
24
27
|
|
|
25
28
|
return {
|
|
26
29
|
...userSlice,
|
|
30
|
+
...themeSlice,
|
|
27
31
|
_hasHydrated: false,
|
|
28
32
|
clear: () => {
|
|
29
33
|
userSlice.signOut();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { StateCreator } from 'zustand';
|
|
2
|
+
|
|
3
|
+
export type ColorScheme = 'light' | 'dark';
|
|
4
|
+
|
|
5
|
+
export type ThemeSlice = {
|
|
6
|
+
colorScheme: ColorScheme;
|
|
7
|
+
setColorScheme: (scheme: ColorScheme) => void;
|
|
8
|
+
toggleColorScheme: () => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const createThemeSlice: StateCreator<ThemeSlice> = (set, get) => ({
|
|
12
|
+
colorScheme: 'light',
|
|
13
|
+
setColorScheme: (scheme: ColorScheme) => set({ colorScheme: scheme }),
|
|
14
|
+
toggleColorScheme: () => set({ colorScheme: get().colorScheme === 'light' ? 'dark' : 'light' }),
|
|
15
|
+
});
|