Skip to content

Commit 01680a5

Browse files
authored
ft(#67): setup in-app notifications (#88)
1 parent f0f9a3c commit 01680a5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+5027
-7884
lines changed

Diff for: %ProgramData%/Microsoft/Windows/UUS/State/_active.uusver

-1
This file was deleted.

Diff for: .gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ expo-env.d.ts
2828
coverage/**/*
2929
.idea/
3030
android/
31-
ios/
31+
ios/
32+
# google-services.json

Diff for: app.json

+38-21
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
"slug": "devpulse",
55
"version": "1.0.1",
66
"orientation": "portrait",
7-
"icon": "./assets/images/icon.png",
8-
"scheme": "devpulse",
7+
"scheme": "pulseapp",
98
"userInterfaceStyle": "automatic",
9+
"newArchEnabled": false,
10+
"icon": "./assets/images/icon.png",
1011
"ios": {
1112
"supportsTablet": true,
1213
"bundleIdentifier": "com.atlp.pulseapp",
@@ -15,11 +16,12 @@
1516
}
1617
},
1718
"android": {
19+
"package": "com.atlp.pulseapp",
20+
"googleServicesFile": "./google-services.json",
1821
"adaptiveIcon": {
1922
"foregroundImage": "./assets/images/adaptive-icon.png",
2023
"backgroundColor": "#ffffff"
2124
},
22-
"package": "com.atlp.pulseapp",
2325
"intentFilters": [
2426
{
2527
"action": "VIEW",
@@ -35,25 +37,40 @@
3537
}
3638
]
3739
},
38-
"plugins": ["expo-router", "expo-font", "expo-localization", [
39-
"expo-image-picker",
40-
{
41-
"photosPermission": "The app needs access to your photos to let you upload a profile picture."
42-
}
43-
],
44-
[
45-
"expo-splash-screen",
46-
{
47-
"backgroundColor": "#E0E7FF",
48-
"image": "./assets/images/icon.png",
49-
"dark": {
40+
"web": {
41+
"bundler": "metro"
42+
},
43+
"plugins": [
44+
"expo-router",
45+
"expo-font",
46+
"expo-localization",
47+
[
48+
"expo-notifications",
49+
{
50+
"icon": "./assets/images/icon.png",
51+
"color": "#E0E7FF",
52+
"mode": "production"
53+
}
54+
],
55+
[
56+
"expo-splash-screen",
57+
{
58+
"backgroundColor": "#E0E7FF",
5059
"image": "./assets/images/icon.png",
51-
"backgroundColor": "#020917"
52-
},
53-
"imageWidth": 128
54-
}
55-
]
56-
],
60+
"dark": {
61+
"image": "./assets/images/icon.png",
62+
"backgroundColor": "#020917"
63+
},
64+
"imageWidth": 128
65+
}
66+
],
67+
[
68+
"expo-image-picker",
69+
{
70+
"photosPermission": "The app needs access to your photos to let you upload a profile picture."
71+
}
72+
]
73+
],
5774
"experiments": {
5875
"typedRoutes": true
5976
},

Diff for: app/(onboarding)/_layout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export default function AppOnboardingLayout() {
3030
const currentTime = Math.floor(Date.now() / 1000);
3131

3232
if (expiryTime > currentTime) {
33-
return router.push('/dashboard');
33+
return router.replace('/dashboard');
3434
}
3535

3636
await AsyncStorage.removeItem('authToken');

Diff for: app/(onboarding)/index.tsx

+35-32
Original file line numberDiff line numberDiff line change
@@ -53,38 +53,41 @@ export default function AppOnboarding() {
5353

5454
return (
5555
<ScrollView
56-
className="flex-1"
56+
bounces={false}
5757
showsVerticalScrollIndicator={false}
5858
showsHorizontalScrollIndicator={false}
59-
bounces={false}
59+
contentContainerClassName="flex-1"
6060
>
6161
{/* Pager View for Onboarding Screens */}
62-
<PagerView
63-
initialPage={page}
64-
style={{ minHeight: 580 }}
65-
onPageSelected={(p) => setPage(p.nativeEvent.position)}
66-
ref={pagerViewRef}
67-
>
68-
{pages.map((page, index) => (
69-
<View key={index} className={`flex-1 px-8 py-12 ${bgColor}`}>
70-
<Image
71-
source={page.image}
72-
contentFit="contain"
73-
className="items-end justify-center mb-6"
74-
style={{ width: '100%', flex: 1 }}
75-
/>
76-
<Text
77-
style={{ fontSize: 24 }}
78-
className={`font-Inter-SemiBold text-center leading-9 ${textColor}`}
79-
>
80-
{page.content}
81-
</Text>
82-
</View>
83-
))}
84-
</PagerView>
62+
<View className="flex-1 flex-row justify-center items-center">
63+
<PagerView
64+
className="bg-red-500 justify-center items-center"
65+
initialPage={page}
66+
style={{ height: '100%', width: '100%' }}
67+
onPageSelected={(p) => setPage(p.nativeEvent.position)}
68+
ref={pagerViewRef}
69+
>
70+
{pages.map((page, index) => (
71+
<View key={index} className={`flex-1 px-8 py-12 ${bgColor}`}>
72+
<Image
73+
source={page.image}
74+
contentFit="contain"
75+
className="items-end justify-center mb-6"
76+
style={{ width: '100%', flex: 1 }}
77+
/>
78+
<Text
79+
style={{ fontSize: 24 }}
80+
className={`font-Inter-SemiBold text-center leading-9 ${textColor}`}
81+
>
82+
{page.content}
83+
</Text>
84+
</View>
85+
))}
86+
</PagerView>
87+
</View>
8588

8689
{/* Pagination Dots */}
87-
<View className={`flex-1 flex-row justify-center items-center gap-3 ${bgColor}`}>
90+
<View className={`flex-row w-full justify-center items-center gap-3 ${bgColor}`}>
8891
<View className={`rounded-full w-4 h-4 ${getDotColor(0)}`}></View>
8992
<View className={`rounded-full w-4 h-4 ${getDotColor(1)}`}></View>
9093
<View className={`rounded-full w-4 h-4 ${getDotColor(2)}`}></View>
@@ -96,12 +99,12 @@ export default function AppOnboarding() {
9699
</View>
97100

98101
{/* Get Started Button */}
99-
<View className={`flex-1 flex-row justify-center items-center ${bgColor}`}>
100-
<TouchableOpacity>
101-
<Text
102-
className="text-lg font-Inter-Medium dark:text-white"
103-
onPress={() => router.push('/auth/login')}
104-
>
102+
<View className={`flex-row justify-center items-center my-8`}>
103+
<TouchableOpacity
104+
onPress={() => router.push('/auth/login')}
105+
className="bg-action-500 py-4 px-16 rounded-lg"
106+
>
107+
<Text className="text-lg font-Inter-Medium dark:text-white">
105108
{t('onboarding.getStarted')}
106109
</Text>
107110
</TouchableOpacity>

Diff for: app/+not-found.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default function NotFoundScreen() {
2020
{t('notFound.title')}
2121
</Text>
2222

23-
<Link href="/" className="mt-12">
23+
<Link href="/(onboarding)" className="mt-12">
2424
<View className="px-6 py-4 rounded-full bg-action-500">
2525
<Text className="text-lg text-white">{t('notFound.goHome')}</Text>
2626
</View>

Diff for: app/_layout.tsx

+18-14
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { useColorScheme } from '@/components/useColorScheme';
22
import '@/global.css';
33
import { client } from '@/graphql/client';
4+
import NotificationsProvider from '@/providers/notifications';
45
import { ApolloProvider } from '@apollo/client';
5-
import { useApolloClientDevTools } from '@dev-plugins/apollo-client';
66
import {
77
Inter_400Regular,
88
Inter_500Medium,
@@ -11,22 +11,27 @@ import {
1111
} from '@expo-google-fonts/inter';
1212
import FontAwesome from '@expo/vector-icons/FontAwesome';
1313
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
14+
import { isDevice } from 'expo-device';
1415
import { useFonts } from 'expo-font';
1516
import { Stack } from 'expo-router';
1617
import * as SplashScreen from 'expo-splash-screen';
1718
import { useEffect } from 'react';
18-
import 'react-native-reanimated';
1919
import { ToastProvider } from 'react-native-toast-notifications';
20-
export { ErrorBoundary } from 'expo-router';
2120
import '../internationalization/index';
22-
import { NotificationProvider } from '@/hooks/useNotification';
2321

2422
export const unstable_settings = {
2523
initialRouteName: '(onboarding)',
2624
};
2725

2826
SplashScreen.preventAutoHideAsync();
2927

28+
if (isDevice) {
29+
SplashScreen.setOptions({
30+
duration: 1000,
31+
fade: true,
32+
});
33+
}
34+
3035
export default function RootLayout() {
3136
const [loaded, error] = useFonts({
3237
Inter_400Regular,
@@ -55,21 +60,20 @@ export default function RootLayout() {
5560

5661
function RootLayoutNav() {
5762
const colorScheme = useColorScheme();
58-
useApolloClientDevTools(client);
5963

6064
return (
61-
6265
<ApolloProvider client={client}>
6366
<ToastProvider placement="top" duration={5000}>
64-
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
65-
<Stack screenOptions={{ headerShown: false }}>
66-
<Stack.Screen name="(onboarding)" options={{ headerShown: false }} />
67-
<Stack.Screen name="auth" options={{ headerShown: false }} />
68-
<Stack.Screen name="dashboard" options={{ headerShown: false }} />
69-
</Stack>
70-
</ThemeProvider>
67+
<NotificationsProvider>
68+
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
69+
<Stack screenOptions={{ headerShown: false }}>
70+
<Stack.Screen name="(onboarding)" />
71+
<Stack.Screen name="auth" />
72+
<Stack.Screen name="dashboard" />
73+
</Stack>
74+
</ThemeProvider>
75+
</NotificationsProvider>
7176
</ToastProvider>
7277
</ApolloProvider>
73-
7478
);
7579
}

Diff for: app/auth/forgot-password.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ export default function ResetPassword() {
9898
keyboardType="email-address"
9999
/>
100100
</View>
101-
{formik.touched.email && formik.errors.email && <Text className="mb-4 text-error-500">{t('forgotPassword.errorEmail')}</Text>}
101+
{formik.touched.email && formik.errors.email && (
102+
<Text className="mb-4 text-error-500">{t('forgotPassword.errorEmail')}</Text>
103+
)}
102104
<TouchableOpacity
103105
testID="submit-button"
104106
onPress={() => formik.handleSubmit()}

Diff for: app/auth/login/index.tsx renamed to app/auth/login.tsx

+16-50
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,21 @@
11
import OrgLogin from '@/components/Login/OrgLogin';
22
import UserLogin from '@/components/Login/UserLogin';
3-
import TwoFactorScreen from '../two-factor';
43
import { LOGIN_MUTATION, ORG_LOGIN_MUTATION } from '@/graphql/mutations/login.mutation';
5-
import { ApolloError, useApolloClient, useMutation } from '@apollo/client';
4+
import { ApolloError, useMutation } from '@apollo/client';
65
import AsyncStorage from '@react-native-async-storage/async-storage';
7-
import { Href, useLocalSearchParams, useRouter } from 'expo-router';
8-
import { useEffect,useState } from 'react';
9-
import { useToast } from 'react-native-toast-notifications';
10-
import {ToastAndroid } from 'react-native';
6+
import { RelativePathString, useLocalSearchParams, useRouter } from 'expo-router';
7+
import { useState } from 'react';
118
import { useTranslation } from 'react-i18next';
12-
13-
class ErrorHandler {
14-
static handleNetworkError() {
15-
ToastAndroid.show('There was a problem contacting the server', ToastAndroid.LONG);
16-
}
17-
18-
static handleInvalidCredentials() {
19-
ToastAndroid.show('Error Invalid credentials',ToastAndroid.LONG);
20-
}
21-
22-
static handleCustomError(message: string | undefined) {
23-
ToastAndroid.show('Error:' + message,ToastAndroid.LONG);
24-
}
25-
26-
static handleGeneralError() {
27-
ToastAndroid.show('Error An unexpected error occurred.',ToastAndroid.LONG);
28-
}
29-
}
9+
import { useToast } from 'react-native-toast-notifications';
3010

3111
export default function SignInOrganization() {
3212
const toast = useToast();
3313
const router = useRouter();
34-
const {t} = useTranslation();
14+
const { t } = useTranslation();
3515
const [orgLoginSuccess, setOrgLoginSuccess] = useState(false);
3616
const [orgLoginMutation] = useMutation(ORG_LOGIN_MUTATION);
3717
const [LoginUser] = useMutation(LOGIN_MUTATION);
38-
const params = useLocalSearchParams<{ redirect?: string; logout: string }>();
39-
const client = useApolloClient();
40-
41-
useEffect(() => {
42-
if (params.logout == '1') {
43-
while (router.canGoBack()) {
44-
router.back();
45-
}
46-
client.clearStore();
47-
router.replace('/auth/login');
48-
}
49-
}, []);
18+
const params = useLocalSearchParams<{ redirect?: string }>();
5019

5120
const onOrgSubmit = async (values: any) => {
5221
try {
@@ -69,13 +38,13 @@ export default function SignInOrganization() {
6938
setOrgLoginSuccess(true);
7039
},
7140
onError(err: any) {
72-
toast.show(err.message,{
41+
toast.show(err.message, {
7342
type: 'fail',
74-
placement : 'top',
75-
duration : 5000,
76-
animationType : 'slide-in',
43+
placement: 'top',
44+
duration: 5000,
45+
animationType: 'slide-in',
7746
style: { backgroundColor: 'red' },
78-
})
47+
});
7948
},
8049
});
8150
} catch (err: any) {
@@ -105,13 +74,10 @@ export default function SignInOrganization() {
10574
if (data.loginUser.user.role === 'trainee') {
10675
await AsyncStorage.setItem('authToken', token);
10776

108-
while (router.canGoBack()) {
109-
router.back();
110-
}
111-
77+
router.canDismiss() && router.dismissAll();
11278
params.redirect
113-
? router.push(`${params.redirect}` as Href<string | object>)
114-
: router.push('/dashboard');
79+
? router.replace(`${params.redirect}` as RelativePathString)
80+
: router.replace('/dashboard');
11581
return;
11682
} else {
11783
toast.show(t('toasts.auth.loginErr'), {
@@ -129,7 +95,7 @@ export default function SignInOrganization() {
12995
}
13096
else {
13197
await AsyncStorage.setItem('authToken', data.loginUser.token);
132-
router.push('/dashboard');
98+
router.replace('/dashboard');
13399
}
134100
},
135101
onError: (err) => {
@@ -143,7 +109,7 @@ export default function SignInOrganization() {
143109
},
144110
});
145111
} catch (error: any) {
146-
toast.show(t('toasts.auth.generalError') , { type: 'danger' });
112+
toast.show(t('toasts.auth.generalError'), { type: 'danger' });
147113
}
148114
};
149115

0 commit comments

Comments
 (0)