2021-10-05 15:44:14 +00:00
|
|
|
# hooks
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
1. [Installing](#installing)
|
|
|
|
1. [Login / logout](#login--logout)
|
|
|
|
1. [Get data](#get-data)
|
|
|
|
1. [Fake mode](#fake-mode)
|
|
|
|
|
|
|
|
## Installing
|
|
|
|
|
2021-10-05 15:44:14 +00:00
|
|
|
`npm i -S hooks @skolplattformen/embedded-api`
|
2021-02-06 20:22:34 +00:00
|
|
|
|
2021-10-05 15:44:14 +00:00
|
|
|
`yarn add hooks @skolplattformen/embedded-api`
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
## ApiProvider
|
|
|
|
|
|
|
|
In order to use api hooks, you must wrap your app in an ApiProvider
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
import React from 'react'
|
2021-10-05 15:44:14 +00:00
|
|
|
import { ApiProvider } from '@skolplattformen/hooks'
|
2021-12-02 14:34:15 +00:00
|
|
|
import init from '@skolplattformen/api-skolplattformet'
|
2021-10-26 07:59:14 +00:00
|
|
|
import { CookieManager } from '@react-native-cookies/cookies'
|
2021-02-06 20:22:34 +00:00
|
|
|
import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
|
|
import { RootComponent } from './components/root'
|
2021-02-08 16:27:51 +00:00
|
|
|
import crashlytics from '@react-native-firebase/crashlytics'
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
const api = init(fetch, () => CookieManager.clearAll())
|
2021-02-08 16:27:51 +00:00
|
|
|
const reporter = {
|
|
|
|
log: (message) => crashlytics().log(message),
|
|
|
|
error: (error, label) => crashlytics().recordError(error, label),
|
|
|
|
}
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
export default () => (
|
2021-02-08 16:27:51 +00:00
|
|
|
<ApiProvider api={api} reporter={reporter} storage={AsyncStorage}>
|
2021-02-06 20:22:34 +00:00
|
|
|
<RootComponent />
|
|
|
|
</ApiProvider>
|
|
|
|
)
|
|
|
|
```
|
|
|
|
|
|
|
|
## Login / logout
|
|
|
|
|
|
|
|
```javascript
|
2021-10-05 15:44:14 +00:00
|
|
|
import { useApi } from '@skolplattformen/hooks'
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
export default function LoginController () {
|
|
|
|
const { api, isLoggedIn } = useApi()
|
|
|
|
|
|
|
|
api.on('login', () => { /* do login stuff */ })
|
|
|
|
api.on('logout', () => { /* do logout stuff */ })
|
|
|
|
|
|
|
|
const [personalNumber, setPersonalNumber] = useState()
|
|
|
|
const [bankIdStatus, setBankIdStatus] = useState('')
|
|
|
|
|
|
|
|
const doLogin = async () => {
|
|
|
|
const status = await api.login(personalNumber)
|
|
|
|
|
|
|
|
openBankID(status.token)
|
|
|
|
|
|
|
|
status.on('PENDING', () => { setBankIdStatus('BankID app not yet opened') })
|
|
|
|
status.on('USER_SIGN', () => { setBankIdStatus('BankID app is open') })
|
|
|
|
status.on('OK', () => { setBankIdStatus('BankID signed. NOTE! User is NOT yet logged in!') })
|
|
|
|
status.on('ERROR', (err) => { setBankIdStatus('BankID failed') })
|
|
|
|
})
|
|
|
|
|
|
|
|
return (
|
|
|
|
<View>
|
|
|
|
<Input value={personalNumber} onChange={(value) = setPersonalNumber(value)} />
|
|
|
|
<Button onClick={() => doLogin()}>
|
|
|
|
<Text>{bankIdStatus}</Text>
|
|
|
|
<Text>Logged in: {isLoggedIn}</Text>
|
|
|
|
</View>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
## Get data
|
|
|
|
|
|
|
|
1. [General](#general)
|
|
|
|
1. [useCalendar](#usecalendar)
|
|
|
|
1. [useChildList](#usechildList)
|
|
|
|
1. [useClassmates](#useclassmates)
|
|
|
|
1. [useMenu](#usemenu)
|
|
|
|
1. [useNews](#usenews)
|
|
|
|
1. [useNotifications](#usenotifications)
|
|
|
|
1. [useSchedule](#useschedule)
|
|
|
|
1. [useUser](#useuser)
|
|
|
|
|
|
|
|
### General
|
|
|
|
|
|
|
|
The data hooks return a `State<T>` object exposing the following properties:
|
|
|
|
|
2021-10-05 15:44:14 +00:00
|
|
|
| Property | Description |
|
|
|
|
| -------- | ------------------------------- |
|
|
|
|
| `status` | `pending` `loading` `loaded` |
|
|
|
|
| `data` | The requested data |
|
|
|
|
| `error` | Error from the API call if any |
|
|
|
|
| `reload` | Function that triggers a reload |
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
The hook will return a useable default for data at first (usually empty `[]`).
|
|
|
|
It then checks the cache (`AsyncStorage`) for any value and, if exists, updates data.
|
|
|
|
Simultaneously the API is called. This only automatically happens once during the
|
|
|
|
lifetime of the app. If several instances of the same hook are used, the data will be
|
|
|
|
shared and only one API call made.
|
|
|
|
When `reload` is called, a new API call will be made and all hook instances will have
|
|
|
|
their `status`, `data` and `error` updated.
|
|
|
|
|
|
|
|
### useCalendar
|
|
|
|
|
|
|
|
```javascript
|
2021-10-05 15:44:14 +00:00
|
|
|
import { useCalendar } from '@skolplattformen/hooks'
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
export default function CalendarComponent ({ selectedChild }) => {
|
|
|
|
const { status, data, error, reload } = useCalendar(selectedChild)
|
2021-10-05 15:44:14 +00:00
|
|
|
|
2021-02-06 20:22:34 +00:00
|
|
|
return (
|
|
|
|
<View>
|
|
|
|
{ status === 'loading' && <Spinner />}
|
|
|
|
{ error && <Text>{ error.message }</Text>}
|
|
|
|
{ data.map((item) => (
|
|
|
|
<CalendarItem item={item} />
|
|
|
|
))}
|
|
|
|
{ status !== 'loading' && status !== 'pending' && <Button onClick={() => reload()}> }
|
|
|
|
</View>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### useChildList
|
|
|
|
|
|
|
|
```javascript
|
2021-10-05 15:44:14 +00:00
|
|
|
import { useChildList } from '@skolplattformen/hooks'
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
export default function ChildListComponent () => {
|
|
|
|
const { status, data, error, reload } = useChildList()
|
2021-10-05 15:44:14 +00:00
|
|
|
|
2021-02-06 20:22:34 +00:00
|
|
|
return (
|
|
|
|
<View>
|
|
|
|
{ status === 'loading' && <Spinner />}
|
|
|
|
{ error && <Text>{ error.message }</Text>}
|
|
|
|
{ data.map((child) => (
|
|
|
|
<Text>{child.firstName} {child.lastName}</Text>
|
|
|
|
))}
|
|
|
|
{ status !== 'loading' && status !== 'pending' && <Button onClick={() => reload()}> }
|
|
|
|
</View>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### useClassmates
|
|
|
|
|
|
|
|
```javascript
|
2021-10-05 15:44:14 +00:00
|
|
|
import { useClassmates } from '@skolplattformen/hooks'
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
export default function ClassmatesComponent ({ selectedChild }) => {
|
|
|
|
const { status, data, error, reload } = useClassmates(selectedChild)
|
2021-10-05 15:44:14 +00:00
|
|
|
|
2021-02-06 20:22:34 +00:00
|
|
|
return (
|
|
|
|
<View>
|
|
|
|
{ status === 'loading' && <Spinner />}
|
|
|
|
{ error && <Text>{ error.message }</Text>}
|
|
|
|
{ data.map((classmate) => (
|
|
|
|
<Classmate item={classmate} />
|
|
|
|
))}
|
|
|
|
{ status !== 'loading' && status !== 'pending' && <Button onClick={() => reload()}> }
|
|
|
|
</View>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### useMenu
|
|
|
|
|
|
|
|
```javascript
|
2021-10-05 15:44:14 +00:00
|
|
|
import { useMenu } from '@skolplattformen/hooks'
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
export default function MenuComponent ({ selectedChild }) => {
|
|
|
|
const { status, data, error, reload } = useMenu(selectedChild)
|
2021-10-05 15:44:14 +00:00
|
|
|
|
2021-02-06 20:22:34 +00:00
|
|
|
return (
|
|
|
|
<View>
|
|
|
|
{ status === 'loading' && <Spinner />}
|
|
|
|
{ error && <Text>{ error.message }</Text>}
|
|
|
|
{ data.map((item) => (
|
|
|
|
<MenuItem item={item} />
|
|
|
|
))}
|
|
|
|
{ status !== 'loading' && status !== 'pending' && <Button onClick={() => reload()}> }
|
|
|
|
</View>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### useNews
|
|
|
|
|
|
|
|
```javascript
|
2021-10-05 15:44:14 +00:00
|
|
|
import { useNews } from '@skolplattformen/hooks'
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
export default function NewsComponent ({ selectedChild }) => {
|
|
|
|
const { status, data, error, reload } = useNews(selectedChild)
|
2021-10-05 15:44:14 +00:00
|
|
|
|
2021-02-06 20:22:34 +00:00
|
|
|
return (
|
|
|
|
<View>
|
|
|
|
{ status === 'loading' && <Spinner />}
|
|
|
|
{ error && <Text>{ error.message }</Text>}
|
|
|
|
{ data.map((item) => (
|
|
|
|
<NewsItem item={item} />
|
|
|
|
))}
|
|
|
|
{ status !== 'loading' && status !== 'pending' && <Button onClick={() => reload()}> }
|
|
|
|
</View>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
To display image from `NewsItem`:
|
|
|
|
|
|
|
|
```javascript
|
2021-10-05 15:44:14 +00:00
|
|
|
import { useApi } from '@skolplattformen/hooks'
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
export default function NewsItem ({ item }) => {
|
|
|
|
const { api } = useApi()
|
|
|
|
const cookie = api.getSessionCookie()
|
2021-10-05 15:44:14 +00:00
|
|
|
|
2021-02-06 20:22:34 +00:00
|
|
|
return (
|
|
|
|
<View>
|
|
|
|
{ cookie &&
|
|
|
|
<Image source={{ uri: item.fullImageUrl, headers: { cookie } }} /> }
|
|
|
|
</View>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### useNotifications
|
|
|
|
|
|
|
|
```javascript
|
2021-10-05 15:44:14 +00:00
|
|
|
import { useNotifications } from '@skolplattformen/hooks'
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
export default function NotificationsComponent ({ selectedChild }) => {
|
|
|
|
const { status, data, error, reload } = useNotifications(selectedChild)
|
2021-10-05 15:44:14 +00:00
|
|
|
|
2021-02-06 20:22:34 +00:00
|
|
|
return (
|
|
|
|
<View>
|
|
|
|
{ status === 'loading' && <Spinner />}
|
|
|
|
{ error && <Text>{ error.message }</Text>}
|
|
|
|
{ data.map((item) => (
|
|
|
|
<Notification item={item} />
|
|
|
|
))}
|
|
|
|
{ status !== 'loading' && status !== 'pending' && <Button onClick={() => reload()}> }
|
|
|
|
</View>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
To show content of `NotificationItem` url:
|
|
|
|
|
|
|
|
```javascript
|
2021-10-05 15:44:14 +00:00
|
|
|
import { useApi } from '@skolplattformen/hooks'
|
2021-02-06 20:22:34 +00:00
|
|
|
import { WebView } from 'react-native-webview'
|
|
|
|
|
|
|
|
export default function Notification ({ item }) => {
|
|
|
|
const { cookie } = useApi()
|
2021-10-05 15:44:14 +00:00
|
|
|
|
2021-02-06 20:22:34 +00:00
|
|
|
return (
|
|
|
|
<View>
|
|
|
|
<WebView source={{ uri: item.url, headers: { cookie }}} />
|
|
|
|
</View>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### useSchedule
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
import { DateTime } from 'luxon'
|
2021-10-05 15:44:14 +00:00
|
|
|
import { useSchedule } from '@skolplattformen/hooks'
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
export default function ScheduleComponent ({ selectedChild }) => {
|
|
|
|
const from = DateTime.local()
|
|
|
|
const to = DateTime.local.plus({ week: 1 })
|
|
|
|
const { status, data, error, reload } = useSchedule(selectedChild, from, to)
|
2021-10-05 15:44:14 +00:00
|
|
|
|
2021-02-06 20:22:34 +00:00
|
|
|
return (
|
|
|
|
<View>
|
|
|
|
{ status === 'loading' && <Spinner />}
|
|
|
|
{ error && <Text>{ error.message }</Text>}
|
|
|
|
{ data.map((item) => (
|
|
|
|
<ScheduleItem item={item} />
|
|
|
|
))}
|
|
|
|
{ status !== 'loading' && status !== 'pending' && <Button onClick={() => reload()}> }
|
|
|
|
</View>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### useUser
|
|
|
|
|
|
|
|
```javascript
|
2021-10-05 15:44:14 +00:00
|
|
|
import { useUser } from '@skolplattformen/hooks'
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
export default function UserComponent () => {
|
|
|
|
const { status, data, error, reload } = useUser()
|
2021-10-05 15:44:14 +00:00
|
|
|
|
2021-02-06 20:22:34 +00:00
|
|
|
return (
|
|
|
|
<View>
|
|
|
|
{ status === 'loading' && <Spinner />}
|
|
|
|
{ error && <Text>{ error.message }</Text>}
|
|
|
|
{ data &&
|
|
|
|
<>
|
|
|
|
<Text>{data.firstName} {data.lastName}</Text>
|
|
|
|
<Text>{data.email}</Text>
|
|
|
|
</>
|
|
|
|
}
|
|
|
|
{ status !== 'loading' && status !== 'pending' && <Button onClick={() => reload()}> }
|
|
|
|
</View>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
## Fake mode
|
|
|
|
|
|
|
|
To make testing easier, fake mode can be enabled at login. Just use any of the magic
|
|
|
|
personal numbers: `12121212121212`, `201212121212` or `1212121212`.
|
|
|
|
The returned login status will have `token` set to `'fake'`.
|
|
|
|
|
|
|
|
```javascript
|
2021-10-05 15:44:14 +00:00
|
|
|
import { useApi } from '@skolplattformen/hooks'
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
|
2021-10-05 15:44:14 +00:00
|
|
|
import { useApi } from '@skolplattformen/hooks'
|
2021-02-06 20:22:34 +00:00
|
|
|
|
|
|
|
export default function LoginController () {
|
|
|
|
const { api, isLoggedIn } = useApi()
|
|
|
|
|
|
|
|
const [personalNumber, setPersonalNumber] = useState()
|
|
|
|
const [bankIdStatus, setBankIdStatus] = useState('')
|
|
|
|
|
|
|
|
api.on('login', () => { /* do login stuff */ })
|
|
|
|
api.on('logout', () => { /* do logout stuff */ })
|
|
|
|
|
|
|
|
const doLogin = async () => {
|
|
|
|
const status = await api.login(personalNumber)
|
|
|
|
|
|
|
|
if (status.token !== 'fake') {
|
|
|
|
openBankID(status.token)
|
|
|
|
} else {
|
|
|
|
// Login will succeed
|
|
|
|
// All data will be faked
|
|
|
|
// No server calls will be made
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return (
|
|
|
|
<View>
|
|
|
|
<Input value={personalNumber} onChange={(value) = setPersonalNumber(value)} />
|
|
|
|
<Button onClick={() => doLogin()}>
|
|
|
|
<Text>{bankIdStatus}</Text>
|
|
|
|
<Text>Logged in: {isLoggedIn}</Text>
|
|
|
|
</View>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
```
|