Skip to content

Commit 45da946

Browse files
feat: auth and some improvements (#37)
* chore: update to expo 52 from expo 51 * feat: make about page dynamic * feat: update tab layout to use BottomTabBarProps and adjust navigation dependencies * fix: add DimensionValue type for style width in EventMap component * fix: add type for notification trigger in schedulePushNotification function * chore: format * chore: remove test check from all checks in package.json * feat: implement sign-in functionality and redirect logic * feat: integrate toast notifications for sign-in error handling and add loading state * feat: store token in secure store * chore: remove unused imports * feat: enhance authentication logic with isAuthenticated check and token expiration handling * fix: import buffer * feat: refactor authentication state management by replacing GlobalStateProvider with AuthStateProvider and display username in profile tab * feat: add internationalization support for sign-in and profile views * ci: remove automatic test step * fix: disable auth for now --------- Co-authored-by: Samu Kupiainen <kupiainen@testausserveri.fi>
1 parent 869127c commit 45da946

File tree

21 files changed

+6163
-6301
lines changed

21 files changed

+6163
-6301
lines changed

.github/workflows/check-code.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,3 @@ jobs:
3232

3333
- name: Format
3434
run: npm run check:format
35-
36-
- name: Test
37-
run: npm run check:test

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ expo-env.d.ts
2828

2929
yarn.lock
3030
.vscode
31+
pnpm-lock.yaml

api/eventService.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ type ApiEventLocation = {
1616
priority: string; // A number encoded as a string or empty
1717
};
1818

19-
type ApiCalendarEventLocation = {
20-
calendar_event_location: number[];
21-
};
19+
type ApiCalendarEventLocation = { calendar_event_location: number[] };
2220

2321
type ApiAssemblyEvent = {
2422
ID: number;
@@ -84,7 +82,7 @@ const determineEvent = (): string => {
8482
process.env.EXPO_PUBLIC_ENVIRONMENT === 'development' ||
8583
process.env.EXPO_PUBLIC_ENVIRONMENT === 'preview'
8684
) {
87-
return 'summer23';
85+
return 'winter25';
8886
}
8987
const now = new Date();
9088
const julyFirst = new Date(now.getFullYear(), 6, 1);
@@ -156,4 +154,4 @@ const getEvents = async (): Promise<AssemblyEvent[]> => {
156154
}
157155
};
158156

159-
export { AssemblyEvent, getEvents };
157+
export { AssemblyEvent, getEvents, determineEvent };

api/userService.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,28 @@ interface ProfilePreferences {
3232
openedLootboxesCount: boolean;
3333
}
3434

35-
const opts = (token?: string, body?: unknown) => ({
36-
method: 'POST',
37-
headers: token
38-
? { Authorization: 'Bearer ' + token, 'content-type': 'application/json' }
39-
: ({ 'content-type': 'application/json' } as any),
40-
body: body ? JSON.stringify(body) : undefined,
41-
});
35+
const opts = (token?: string, body?: unknown) => {
36+
const headers: Record<string, string> = {};
37+
headers['Content-Type'] = 'application/json';
38+
39+
if (token) {
40+
headers['Authorization'] = 'Bearer ' + token;
41+
}
42+
43+
if (body) {
44+
const bodyString = JSON.stringify(body);
45+
headers['Content-Length'] = bodyString.length.toString();
46+
return { method: 'POST', headers, body: bodyString };
47+
}
48+
49+
return { method: 'POST', headers };
50+
};
4251

4352
export async function fetchProfile(token: string) {
4453
const url = `${process.env.EXPO_PUBLIC_API_URL}/auth/jwt`;
4554

4655
const response = await fetch(url, opts(token));
4756
const data = await response.json();
48-
console.log(data);
4957
return data as ProfileData;
5058
}
5159

@@ -57,23 +65,16 @@ export async function signupRequest(email: string, password: string) {
5765
throw new StatusError('signup-failed', response.status);
5866
}
5967
const data = await response.json();
60-
return {
61-
profile: data as ProfileData,
62-
token: response.headers.get('x-auth-token')!,
63-
};
68+
return { profile: data as ProfileData, token: response.headers.get('x-auth-token')! };
6469
}
6570

6671
export async function loginRequest(login: string, password: string) {
6772
const url = `${process.env.EXPO_PUBLIC_API_URL}/auth/local?region=asm`;
68-
6973
const response = await fetch(url, opts(undefined, { login, password }));
74+
7075
if (!response.ok) {
7176
throw new StatusError('login-failed', response.status);
7277
}
7378
const data = await response.json();
74-
console.log(data);
75-
return {
76-
profile: data as ProfileData,
77-
token: response.headers.get('x-auth-token')!,
78-
};
79+
return { profile: data as ProfileData, token: response.headers.get('x-auth-token')! };
7980
}

app.config.js

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export default () => {
88
icon: './assets/images/icon.png',
99
scheme: 'myapp',
1010
userInterfaceStyle: 'automatic',
11+
newArchEnabled: true,
1112
splash: {
1213
image: './assets/images/splash.png',
1314
resizeMode: 'contain',
@@ -31,21 +32,22 @@ export default () => {
3132
: 'fi.testausserveri.assemblyapp_dev',
3233
useNextNotificationApi: true,
3334
},
34-
web: {
35-
bundler: 'metro',
36-
output: 'static',
37-
favicon: './assets/images/favicon.png',
38-
},
35+
web: { bundler: 'metro', output: 'static', favicon: './assets/images/favicon.png' },
3936
plugins: [
37+
[
38+
'expo-secure-store',
39+
{
40+
configureAndroidBackup: true,
41+
faceIDPermission:
42+
'Allow $(PRODUCT_NAME) to access your Face ID biometric data.',
43+
},
44+
],
45+
'expo-font',
4046
'expo-router',
4147
'expo-build-properties',
4248
[
4349
'expo-notifications',
44-
{
45-
icon: './assets/images/icon.png',
46-
color: '#191919',
47-
sounds: [],
48-
},
50+
{ icon: './assets/images/icon.png', color: '#191919', sounds: [] },
4951
],
5052
[
5153
'expo-tracking-transparency',
@@ -55,24 +57,14 @@ export default () => {
5557
},
5658
],
5759
],
58-
experiments: {
59-
typedRoutes: true,
60-
},
60+
experiments: { typedRoutes: true },
6161
extra: {
62-
router: {
63-
origin: false,
64-
},
65-
eas: {
66-
projectId: '469c71b6-54c5-4111-bc4a-03e2cf92a23d',
67-
},
62+
router: { origin: false },
63+
eas: { projectId: '469c71b6-54c5-4111-bc4a-03e2cf92a23d' },
6864
},
6965
owner: 'testausserveri',
70-
updates: {
71-
url: 'https://u.expo.dev/469c71b6-54c5-4111-bc4a-03e2cf92a23d',
72-
},
73-
runtimeVersion: {
74-
policy: 'appVersion',
75-
},
66+
updates: { url: 'https://u.expo.dev/469c71b6-54c5-4111-bc4a-03e2cf92a23d' },
67+
runtimeVersion: { policy: 'appVersion' },
7668
},
7769
};
7870
};

app/(tabs)/_layout.tsx

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
11
import { TabBar, TabBarIcon } from '@/components';
22
import LootboxNavigationButton from '@/elements/lootbox/LootboxNavigationButton';
3-
import { Tabs } from 'expo-router';
4-
import React from 'react';
3+
import { useAuth } from '@/hooks/useAuth';
4+
import { BottomTabBarProps } from '@react-navigation/bottom-tabs';
5+
import { Redirect, SplashScreen, Tabs } from 'expo-router';
6+
import React, { useEffect } from 'react';
57

68
export default function TabLayout() {
9+
const { status } = useAuth();
10+
11+
useEffect(() => {
12+
if (status !== 'loading') {
13+
SplashScreen.hide();
14+
}
15+
}, [status]);
16+
17+
/*
18+
* TODO
19+
if (status === 'logged-out') {
20+
return <Redirect href={'/signin'} />;
21+
}
22+
*/
23+
724
return (
825
<Tabs
9-
tabBar={(props) => <TabBar {...props} />}
10-
screenOptions={{
11-
headerShown: false,
12-
}}
26+
tabBar={(props: BottomTabBarProps) => <TabBar {...props} />}
27+
screenOptions={{ headerShown: false }}
1328
>
1429
<Tabs.Screen
1530
name='index'
@@ -45,7 +60,7 @@ export default function TabLayout() {
4560
color={color}
4661
/>
4762
),
48-
unmountOnBlur: true,
63+
// unmountOnBlur: true,
4964
}}
5065
/>
5166
<Tabs.Screen

app/(tabs)/profile.tsx

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import AppBar from '@/elements/AppBar';
2-
import LanguageSelector from '@/elements/LanguageSelector';
3-
import { Link } from 'expo-router';
2+
import { ProfileView } from '@/elements/user/ProfileView';
3+
import { DarkTheme } from '@/styles';
44
import { useTranslation } from 'react-i18next';
55
import { View } from 'react-native';
6-
import { Surface, Text, useTheme } from 'react-native-paper';
6+
import { useTheme } from 'react-native-paper';
77
import { useSafeAreaInsets } from 'react-native-safe-area-context';
88

99
export default function HomeScreen() {
1010
const { t } = useTranslation();
11-
const theme = useTheme();
12-
11+
const theme = useTheme<DarkTheme>();
1312
const insets = useSafeAreaInsets();
1413

1514
return (
@@ -23,31 +22,7 @@ export default function HomeScreen() {
2322
}}
2423
>
2524
<AppBar title={t('profile')} />
26-
<View
27-
style={{ flex: 1, width: '100%', justifyContent: 'center', alignItems: 'center' }}
28-
>
29-
<LanguageSelector />
30-
<Surface
31-
elevation={0}
32-
style={{ padding: 16, width: '100%', justifyContent: 'center', gap: 16 }}
33-
>
34-
<Text style={{ textAlign: 'center' }} variant='bodyLarge'>
35-
{t('working-on-this')}
36-
</Text>
37-
<Link
38-
style={{
39-
color: theme.colors.primary,
40-
textDecorationLine: 'underline',
41-
textAlign: 'center',
42-
}}
43-
href='/credits'
44-
>
45-
{t('meanwhile')}
46-
</Link>
47-
</Surface>
48-
</View>
49-
50-
{/** <ProfileView /> */}
25+
<ProfileView />
5126
</View>
5227
);
5328
}

app/_layout.tsx

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { TabBarIcon } from '@/components';
2-
import { GlobalStateProvider } from '@/hooks/providers/GlobalStateProvider';
2+
import { AuthStateProvider } from '@/hooks/useAuth';
33
import Locales from '@/locales';
4-
import { Themes } from '@/styles';
4+
import { Colors, Themes } from '@/styles';
55
import { useFonts } from 'expo-font';
66
import * as Notifications from 'expo-notifications';
77
import { Stack, router } from 'expo-router';
88
import * as SplashScreen from 'expo-splash-screen';
99
import i18n from 'i18next';
10-
import { useEffect } from 'react';
1110
import { initReactI18next, useTranslation } from 'react-i18next';
1211
import { Platform } from 'react-native';
1312
import { PaperProvider } from 'react-native-paper';
1413
import 'react-native-reanimated';
1514
import { SafeAreaProvider } from 'react-native-safe-area-context';
15+
import Toast, { ErrorToast } from 'react-native-toast-message';
1616

1717
// Prevent the splash screen from auto-hiding before asset loading is complete.
1818
SplashScreen.preventAutoHideAsync();
@@ -21,9 +21,7 @@ i18n.use(initReactI18next).init({
2121
fallbackLng: 'en',
2222
compatibilityJSON: 'v3',
2323
debug: true,
24-
resources: {
25-
...Locales,
26-
},
24+
resources: { ...Locales },
2725
interpolation: {
2826
escapeValue: false, // not needed for react as it escapes by default
2927
},
@@ -37,32 +35,37 @@ Notifications.setNotificationHandler({
3735
}),
3836
});
3937

38+
const toastConfig = {
39+
error: ({ ...props }) => (
40+
<ErrorToast
41+
{...props}
42+
style={{ backgroundColor: Colors.dark.default.error, borderLeftWidth: 0 }}
43+
text1Style={{ color: Colors.dark.default.onError }}
44+
/>
45+
),
46+
};
47+
4048
export default function RootLayout() {
4149
const [loaded] = useFonts({
4250
Gaba: require('../assets/fonts/Gaba-Super.otf'),
4351
Roboto: require('../assets/fonts/Roboto-Regular.ttf'),
4452
});
45-
4653
const { t } = useTranslation();
4754

48-
useEffect(() => {
49-
if (loaded) {
50-
SplashScreen.hideAsync();
51-
}
52-
}, [loaded]);
53-
5455
if (!loaded) {
5556
return null;
5657
}
5758

5859
return (
59-
<GlobalStateProvider>
60+
<AuthStateProvider>
6061
<SafeAreaProvider>
6162
<PaperProvider theme={Themes['dark']['default']}>
6263
<Stack>
6364
<Stack.Screen name='(tabs)' options={{ headerShown: false }} />
6465
<Stack.Screen name='+not-found' />
6566

67+
<Stack.Screen name='signin' options={{ headerShown: false }} />
68+
6669
<Stack.Screen
6770
name='credits'
6871
options={{
@@ -84,8 +87,9 @@ export default function RootLayout() {
8487
}}
8588
/>
8689
</Stack>
90+
<Toast config={toastConfig} />
8791
</PaperProvider>
8892
</SafeAreaProvider>
89-
</GlobalStateProvider>
93+
</AuthStateProvider>
9094
);
9195
}

0 commit comments

Comments
 (0)