Skip to content

Commit 0054e71

Browse files
authored
Merge pull request #28 from RoboJackets/settings_implementation
Settings implementation
2 parents 1303710 + fbd554c commit 0054e71

18 files changed

Lines changed: 2707 additions & 103 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ DerivedData
6464
*.ipa
6565
*.xcuserstate
6666
**/.xcode.env.local
67+
**/Settings.bundle/
6768

6869
# Android/IntelliJ
6970
#
@@ -77,6 +78,7 @@ local.properties
7778
*.keystore
7879
!debug.keystore
7980
.kotlin/
81+
**/config/
8082

8183
# node.js
8284
#

Api/ApiContextProvider.tsx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import axios, { AxiosInstance } from 'axios';
2+
import React, { createContext, ReactNode, useContext } from 'react';
3+
import { AppEnvironment, useAppEnvironment } from '../AppEnvironment';
4+
import { getAuthToken, refreshAuth } from '../Auth/Authentication';
5+
6+
/**
7+
* Context containing API configuration for the app.
8+
* Includes authentication and automatic refresh logic.
9+
*/
10+
export const ApiContext = createContext<AxiosInstance | undefined>(undefined);
11+
12+
/**
13+
* Creates API context in the form of a preconfigured Axios instance.
14+
* Sets base URL to the current app environment, adds interceptors for
15+
* requests to pass in authorization tokens and to retry with refresh tokens
16+
* if any 401 errors occur.
17+
* @param environment AppEnvironment containing the Base URL to use.
18+
* @returns AxiosInstance for the API context.
19+
*/
20+
function createApiContext(environment: AppEnvironment) {
21+
const instance = axios.create({
22+
baseURL: environment.baseUrl,
23+
});
24+
25+
instance.interceptors.request.use(async (config) => {
26+
const token = await getAuthToken(environment);
27+
console.log(config.baseURL);
28+
if (token) {
29+
config.headers.Authorization = `Bearer ${token}`;
30+
console.log(config.headers.Authorization);
31+
}
32+
33+
return config;
34+
});
35+
36+
instance.interceptors.response.use(
37+
(response) => response,
38+
async (error) => {
39+
if (axios.isAxiosError(error) && error.response?.status === 401) {
40+
if (!error.config) return Promise.reject(error);
41+
if (!(await refreshAuth(environment))) return Promise.reject(error);
42+
const token = await getAuthToken(environment);
43+
if (token) {
44+
error.config.headers.Authorization = `Bearer ${token.password}`;
45+
return axios.request(error.config);
46+
}
47+
}
48+
return Promise.reject(error);
49+
},
50+
);
51+
return instance;
52+
}
53+
54+
function ApiContextProvider({ children }: { children: ReactNode }) {
55+
const { environment } = useAppEnvironment();
56+
const apiContext = createApiContext(environment);
57+
58+
return <ApiContext.Provider value={apiContext}>{children}</ApiContext.Provider>;
59+
}
60+
61+
export function useApi() {
62+
const context = useContext(ApiContext);
63+
if (!context) {
64+
throw new Error('useApi must be used within an ApiContextProvider');
65+
}
66+
return context;
67+
}
68+
69+
export default ApiContextProvider;

Api/Models/Permission.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export enum Permission {
2+
CREATE_ATTENDANCE = 'create-attendance',
3+
READ_EVENTS = 'read-events',
4+
READ_TEAMS = 'read-teams',
5+
READ_TEAMS_HIDDEN = 'read-teams-hidden',
6+
READ_USERS = 'read-users',
7+
READ_MERCHANDISE = 'read-merchandise',
8+
DISTRIBUTE_SWAG = 'distribute-swag',
9+
}

Api/UserApi.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { AxiosInstance } from 'axios';
2+
import { Permission } from './Models/Permission';
3+
4+
export type UserInfo<T extends object = NonNullable<unknown>> = {
5+
id: number;
6+
uid: string;
7+
name: string;
8+
preferred_first_name: string;
9+
allPermissions: Permission[];
10+
} & T;
11+
12+
export async function getUserInfo(api: AxiosInstance): Promise<UserInfo | null> {
13+
try {
14+
const user = await api.get('/api/v1/user');
15+
console.log(user);
16+
//TODO: Incorporate Sentry
17+
return user.data.user;
18+
} catch (error) {
19+
//TODO: incorporate logging
20+
}
21+
return null;
22+
}

App.tsx

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
11
import { NavigationContainer } from '@react-navigation/native';
2-
import React, { createContext } from 'react';
2+
import React from 'react';
33
import { SafeAreaProvider } from 'react-native-safe-area-context';
4+
import ApiContextProvider from './Api/ApiContextProvider';
45
import { AppEnvironmentProvider } from './AppEnvironment';
56
import AuthContextProvider from './Auth/AuthContextProvider';
67
import RootStack from './Navigation/RootStack';
78
import ThemeProvider from './Themes/ThemeContextProvider';
89

9-
type AuthContextType = {
10-
authenticated: boolean | null;
11-
setAuthenticated: (u: boolean) => void;
12-
};
13-
14-
export const AuthContext = createContext<AuthContextType | undefined>(undefined);
15-
1610
function App() {
1711
return (
1812
<AppEnvironmentProvider>
1913
<SafeAreaProvider>
2014
<AuthContextProvider>
21-
<ThemeProvider>
22-
<NavigationContainer>
23-
<RootStack />
24-
</NavigationContainer>
25-
</ThemeProvider>
15+
<ApiContextProvider>
16+
<ThemeProvider>
17+
<NavigationContainer>
18+
<RootStack />
19+
</NavigationContainer>
20+
</ThemeProvider>
21+
</ApiContextProvider>
2622
</AuthContextProvider>
2723
</SafeAreaProvider>
2824
</AppEnvironmentProvider>

Auth/AuthContextProvider.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { createContext, ReactNode, useEffect, useState } from 'react';
1+
import React, { createContext, ReactNode, useContext, useEffect, useState } from 'react';
22
import { useAppEnvironment } from '../AppEnvironment';
33
import {
44
AuthenticationState,
@@ -51,4 +51,12 @@ function AuthContextProvider({ children }: AuthProviderProps) {
5151
);
5252
}
5353

54+
export function useAuth() {
55+
const context = useContext(AuthContext);
56+
if (!context) {
57+
throw new Error('useAuth must be used within an AuthContextProvider');
58+
}
59+
return context;
60+
}
61+
5462
export default AuthContextProvider;

Auth/Authentication.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,19 @@ async function storeCredentials(
9999
return store_success && refresh_store_success;
100100
}
101101

102+
/**
103+
* Gets the auth token from Keychain storage.
104+
* @param currentEnvironment Current AppEnvironment.
105+
* @returns Token if exists, null otherwise
106+
*/
107+
export async function getAuthToken(currentEnvironment: AppEnvironment) {
108+
const token = await Keychain.getInternetCredentials(currentEnvironment.baseUrl + ':accessToken');
109+
if (!token) {
110+
return null;
111+
}
112+
return token;
113+
}
114+
102115
/**
103116
* Checks for existence of auth token and whether it is expired.
104117
* @returns whether auth token is currently valid.
@@ -114,11 +127,8 @@ export async function authTokenIsValid(currentEnvironment: AppEnvironment) {
114127
return false;
115128
}
116129

117-
const currentTime = Date.now();
118-
if (currentTime > expiry) {
119-
return true;
120-
}
121-
return false;
130+
const currentTime = Math.floor(Date.now() / 1000);
131+
return currentTime < expiry;
122132
}
123133

124134
/**

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ npm run android
3030

3131
# OR using Yarn
3232
yarn android
33+
34+
# OR using npx
35+
npx react-native run-android
3336
```
3437

3538
### iOS
@@ -46,6 +49,11 @@ Then, and every time you update your native dependencies, run:
4649

4750
```sh
4851
bundle exec pod install
52+
53+
# OR manually
54+
cd ios
55+
pod install
56+
cd ..
4957
```
5058

5159
For more information, please visit [CocoaPods Getting Started guide](https://guides.cocoapods.org/using/getting-started.html).
@@ -56,8 +64,26 @@ npm run ios
5664

5765
# OR using Yarn
5866
yarn ios
67+
68+
# OR using npx
69+
npx react-native run-ios
5970
```
6071

6172
If everything is set up correctly, you should see your new app running in the Android Emulator, iOS Simulator, or your connected device.
6273

6374
This is one way to run your app — you can also build it directly from Android Studio or Xcode.
75+
76+
77+
# Legal
78+
79+
This project is open-source and is supported by many open-source libraries.
80+
The app includes a notice of these dependencies which must be updated when a
81+
library is added. Run the below command whenever adding a dependency to update
82+
the OSS notice:
83+
84+
```sh
85+
npx react-native legal-generate
86+
87+
# OR with yarn
88+
yarn react-native legal-generate
89+
```

0 commit comments

Comments
 (0)