Skip to content

Commit

Permalink
UIT-54: Add API hook to connect to Publiq API
Browse files Browse the repository at this point in the history
As seen in axios/axios#4998 (comment), axios causes an error in the latest RN version, therefor we need to upgrade to the most recent beta version for it to work.
  • Loading branch information
hstandaert committed Nov 15, 2022
1 parent e4b35de commit 7bcfce5
Show file tree
Hide file tree
Showing 14 changed files with 200 additions and 29 deletions.
1 change: 1 addition & 0 deletions .env.example
Expand Up @@ -2,6 +2,7 @@ NODE_ENV=
API_HOST=
REACT_NATIVE_APP_VERSION_NR=
REACT_NATIVE_APP_AUTH0_DOMAIN=
REACT_NATIVE_APP_AUTH0_AUDIENCE=
REACT_NATIVE_APP_AUTH0_SCHEME=
REACT_NATIVE_APP_AUTH0_CLIENT_ID=
REACT_NATIVE_APP_ENCRYPTION_KEY=
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -23,7 +23,7 @@
"@tanstack/query-sync-storage-persister": "^4.14.5",
"@tanstack/react-query": "^4.14.1",
"@tanstack/react-query-persist-client": "^4.14.5",
"axios": "^1.1.3",
"axios": "1.2.0-alpha.1",
"color": "^4.2.3",
"date-fns": "^2.29.3",
"i18next": "^22.0.4",
Expand Down
19 changes: 9 additions & 10 deletions src/App.tsx
Expand Up @@ -7,7 +7,7 @@ import { ThemeProvider } from 'styled-components/native';

import { AuthenticationProvider } from './_context';
import { StorageKey } from './_models';
// import { QueryClientProvider } from './_providers';
import { QueryClientProvider } from './_providers';
import RootStackNavigator from './_routing';
import { theme } from './_styles/theme';
import { storage } from './storage';
Expand All @@ -24,15 +24,14 @@ const App = () => {
return (
<ThemeProvider theme={theme}>
<AuthenticationProvider>
{/* @TODO: Uncomment when react-query is used in the source code */}
{/* <QueryClientProvider> */}
<SafeAreaProvider>
<NavigationContainer>
<StatusBar barStyle="light-content" />
<RootStackNavigator />
</NavigationContainer>
</SafeAreaProvider>
{/* </QueryClientProvider> */}
<QueryClientProvider>
<SafeAreaProvider>
<NavigationContainer>
<StatusBar barStyle="light-content" />
<RootStackNavigator />
</NavigationContainer>
</SafeAreaProvider>
</QueryClientProvider>
</AuthenticationProvider>
</ThemeProvider>
);
Expand Down
38 changes: 38 additions & 0 deletions src/_hooks/usePubliqApi.ts
@@ -0,0 +1,38 @@
import { useCallback } from 'react';
import { Config } from 'react-native-config';
import { QueryObserverOptions, useQuery } from '@tanstack/react-query';

import { useAuthentication } from '../_context';
import { HttpClient, TApiError } from '../_http';
import { Headers, Params } from '../_http/HttpClient';

type TGetOptions = {
enabled?: boolean;
headers?: Headers;
onError?: QueryObserverOptions['onError'];
onSuccess?: QueryObserverOptions['onSuccess'];
params?: Params;
};

export function usePubliqApi() {
const { accessToken } = useAuthentication();

const defaultHeaders: Headers = {
Authorization: `Bearer ${accessToken}`,
};

const get = useCallback(
<T>(queryKey: unknown[], path: string, { headers = {}, params = {}, ...options }: TGetOptions = {}) => {
return useQuery<T, TApiError>({
enabled: !!accessToken && (options.enabled === undefined || options.enabled),
onError: options.onError,
onSuccess: options.onSuccess,
queryFn: async () => HttpClient.get<T>(`${Config.API_HOST}${path}`, params, { ...defaultHeaders, ...headers }),
queryKey,
});
},
[Config.API_HOST, accessToken],
);

return { get };
}
4 changes: 2 additions & 2 deletions src/_http/HttpClient.ts
Expand Up @@ -4,8 +4,8 @@ import axios, { AxiosError, AxiosResponse, ResponseType } from 'axios';
import { TApiError, TValidationError } from './HttpError';
import { HttpStatus } from './HttpStatus';

type Params = Record<string, string | number | boolean | null | undefined>;
type Headers = Record<string, string>;
export type Params = Record<string, string | number | boolean | null | undefined>;
export type Headers = Record<string, string>;

class HttpClient {
static getUrl(route: string): string {
Expand Down
6 changes: 3 additions & 3 deletions src/_routing/_components/RootStackNavigator.tsx
Expand Up @@ -4,12 +4,12 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack';

import { useAuthentication } from '../../_context';
import { StorageKey } from '../../_models';
import Home from '../../login/Login';
import Login from '../../login/Login';
import Onboarding from '../../onboarding/Onboarding';
import { storage } from '../../storage';
import { MainNavigator } from './MainNavigator';

export type TRootRoutes = 'MainNavigator' | 'Onboarding' | 'Home';
export type TRootRoutes = 'MainNavigator' | 'Onboarding' | 'Login';
export type TRootParams = Record<TRootRoutes, undefined>;

const RootStack = createNativeStackNavigator<TRootParams>();
Expand All @@ -35,7 +35,7 @@ export const RootStackNavigator = () => {
name="Onboarding"
/>
)}
{!isAuthenticated && <RootStack.Screen component={Home} name="Home" />}
{!isAuthenticated && <RootStack.Screen component={Login} name="Login" />}
{isAuthenticated && <RootStack.Screen component={MainNavigator} name="MainNavigator" />}
</RootStack.Navigator>
);
Expand Down
1 change: 1 addition & 0 deletions src/env.d.ts
Expand Up @@ -7,6 +7,7 @@ declare module 'react-native-config' {
| 'NODE_ENV'
| 'REACT_NATIVE_APP_AUTH0_CLIENT_ID'
| 'REACT_NATIVE_APP_AUTH0_DOMAIN'
| 'REACT_NATIVE_APP_AUTH0_AUDIENCE'
| 'REACT_NATIVE_APP_ENCRYPTION_KEY'
| 'REACT_NATIVE_APP_VERSION_NR';

Expand Down
3 changes: 2 additions & 1 deletion src/login/Login.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Config } from 'react-native-config';

import { BrandLogo, DiagonalSplitView } from '../_components';
import { useAuthentication } from '../_context';
Expand All @@ -11,7 +12,7 @@ const Login = () => {

const handleLogin = async () => {
try {
await authorize({ scope: 'openid profile email offline_access' });
await authorize({ audience: Config.REACT_NATIVE_APP_AUTH0_AUDIENCE, scope: 'openid profile email offline_access' });
} catch (e) {
// @TODO: general error handling?
console.error(e);
Expand Down
2 changes: 1 addition & 1 deletion src/onboarding/Onboarding.tsx
Expand Up @@ -12,7 +12,7 @@ const Onboarding = () => {

const onPress = () => {
storage.set(StorageKey.IsPolicyApproved, true);
navigation.navigate('Home');
navigation.navigate('Login');
};

return (
Expand Down
17 changes: 10 additions & 7 deletions src/profile/Profile.tsx
@@ -1,22 +1,25 @@
import React from 'react';
import { SafeAreaView, ScrollView, Text } from 'react-native';
import { Text } from 'react-native';

import { Button } from '../_components';
import { Button, SafeAreaView } from '../_components';
import { useToggle } from '../_hooks';
import { useGetMe } from './_queries/useGetMe';
import LogoutModal from './LogOutModal';

const Profile = () => {
const [logOutModalVisible, toggleLogOutModalVisible] = useToggle(false);
const { data, isLoading } = useGetMe();
console.log({ data, isLoading }); // @TODO: remove this console.log

return (
<SafeAreaView>
<LogoutModal isVisible={logOutModalVisible} toggleIsVisible={toggleLogOutModalVisible} />
<ScrollView>
<>
<SafeAreaView isScrollable>
{/* @TODO: This is placeholder content */}
<Text>This is the profile page</Text>
<Button label="Logout" onPress={toggleLogOutModalVisible} />
</ScrollView>
</SafeAreaView>
</SafeAreaView>
<LogoutModal isVisible={logOutModalVisible} toggleIsVisible={toggleLogOutModalVisible} />
</>
);
};

Expand Down
1 change: 1 addition & 0 deletions src/profile/_models/index.ts
@@ -0,0 +1 @@
export * from './passholder';
120 changes: 120 additions & 0 deletions src/profile/_models/passholder.ts
@@ -0,0 +1,120 @@
export type TCity = {
/** Name of the city */
city: string;
/** Postalcode of the city */
postalCode: string;
};

export type TAddress = TCity & {
/** Postal box number. */
box: string;
/** ISO 3166-1 alpha-2 country code. */
country: string;
/** House number. */
number: string;
/** Street name of the address. */
street: string;
};

export type TCardSystemMembership = {
/** A region, usually one or multiple municipalities in Belgium, that uses UiTPAS and provides discounts and/or rewards. For example "Paspartoe" (Brussels), UiTPAS Leuven, UiTPAS Hasselt, UiTPAS Gent, and so on. */
cardSystem: {
/** Indicates whether this cardsystem allows online cardless registrations. */
allowsCardlessRegistration?: boolean;
/** Branding information of the card system */
branding?: {
/** URL to the logo of the card system */
logo: string;
/** Color code of the primary branding color. */
primaryColor: string;
/** Color code of the secondary branding color. */
secondaryColor: string;
};
/** List of cities that are part of this card system */
cities: TCity[];
/** ID of the card system */
id: number;
/** Links of the card system */
links: {
/** URL of the website of the card system */
website: string;
};
/** Name of the card system. */
name: string;
/** Indicates whether this is a permanent card system */
permanent?: boolean;
};
/** If the passholder has right to a social tariff, this object contains details like the end date. */
socialTariff?: {
/** Exact moment that the passholder's right to a social tariff expires. */
endDate: string;
/** If true, the passholder's right to a social tariff has completely expired (the end date has passed and the passholder is no longer in a grace period). */
expired: boolean;
/** When the end date of the right to a social tariff has passed, the passholder may still be in a grace period that they can buy tickets at a social tariff until their right to a social tariff has been renewed. */
inGracePeriod: boolean;
};
status?: 'ACTIVE' | 'BLOCKED';
/** The UiTPAS number of the card that is linked to this card system membership. It is possible to have a CardSystemMembership without a card. However, a passholder always has at least one CardSystemMembership with a card. */
uitpasNumber: string;
};

export type TPassHolder = {
/** Address that the passholder lives at. Always present in responses. Passholders living outside of Belgium (usually near the border) will only have a postalCode and city in their address. */
address: TCity | TAddress;
/** This field is always available in responses. */
cardSystemMemberships: TCardSystemMembership[];
/**
* Name of the municipality that the passholder lives in. Deprecated in favor of address.city.
* @deprecated
*/
city?: string;
/** This field is always available in responses. */
creationDate: string;
/** Date that the passholder was born. */
dateOfBirth?: string;
/** Contact email address of the passholder. Not present for every passholder. Multiple passholders can have the same email address. */
email?: string;
/** First name of the passholder. */
firstName: string;
/** Gender of the passholder. */
gender?: 'MALE' | 'FEMALE' | 'X';
/** This field is always available in responses. */
id: string;
/** Unique national (Belgian) INSZ number of an individual passholder to look up. */
inszNumber?: string;
/** Last name of the passholder. */
name: string;
/** Human-readable name of the passholder's nationality. */
nationality?: string;
/** Permissions that the passholder has given to be contacted. */
optInPreferences?: {
/** Rewards, actions and events selected specifically for the passholder based on their UiTPAS history. */
infoMails: boolean;
/** Notification when you reach an important UiTPAS milestone, for example a specific amount of points or an exclusive reward becomes available to you. */
milestoneMails: boolean;
/** Sporadic post mail with information about UiTPAS. Will be sent to the passholder's postal address. */
post: boolean;
/** Important information about the functionality of UiTPAS. */
serviceMails: boolean;
/** Free (sporadic) SMS messages with rewards, actions and events selected specifically for the passholder based on their UiTPAS history. */
sms: boolean;
};
/** Phone number that the passholder has registered, for example for SMS alerts. */
phoneNumber?: string;
/** Amount of points the passholder has currently saved (and not used). This field is always available in responses. */
points: number;
/**
* Postal code of the municipality that the passholder lives in. Deprecated in favor of address.postalCode
* @deprecated
*/
postalCode?: string;
/** An organisation that partners with UiTPAS to provide discounts and/or rewards, and/or allows points to be collected at their events. */
registrationOrganizer: {
/** Unique ID of an UiTPAS organizer. (Same as its ID in UiTdatabank) */
id: string;
/** Human-readable name of an UiTPAS organizer. */
name: string;
};
/** Whether or not the passholder has a an UiTID registered. This field is always available in responses. */
uitidStatus: 'REGISTERED' | 'UNREGISTERED';
};
7 changes: 7 additions & 0 deletions src/profile/_queries/useGetMe.ts
@@ -0,0 +1,7 @@
import { usePubliqApi } from '../../_hooks/usePubliqApi';
import { TPassHolder } from '../_models';

export function useGetMe() {
const api = usePubliqApi();
return api.get<TPassHolder>(['me'], '/passholders/me');
}
8 changes: 4 additions & 4 deletions yarn.lock
Expand Up @@ -2178,10 +2178,10 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==

axios@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.1.3.tgz#8274250dada2edf53814ed7db644b9c2866c1e35"
integrity sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==
axios@1.2.0-alpha.1:
version "1.2.0-alpha.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.0-alpha.1.tgz#67ade13f4d84c26ca555e552f15e3f6fba849e0d"
integrity sha512-qt/7xkSQNBRKP26mt28cmSI1Y3jVtrQzu7oLjIyUHEdjpVeg100luMJrRpBlKlCmMd233Peu00mOkNC1OwXOrw==
dependencies:
follow-redirects "^1.15.0"
form-data "^4.0.0"
Expand Down

0 comments on commit 7bcfce5

Please sign in to comment.