feat: 🎸 First release
Basically everything in react-native-embedded-api... but rewritten
This commit is contained in:
parent
d623705563
commit
d37f3db3e8
|
@ -1,3 +1,9 @@
|
|||
module.exports = {
|
||||
extends: ['airbnb-typescript'],
|
||||
parserOptions: {
|
||||
project: `./tsconfig.json`
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/semi': ['error', 'never']
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
name: Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable --silent --non-interactive 2> >(grep -v warning 1>&2)
|
||||
- name: Build
|
||||
run: yarn build
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npx semantic-release
|
|
@ -0,0 +1,20 @@
|
|||
# This workflow will do a clean install of node dependencies and run tests
|
||||
|
||||
name: Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
unit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup Node.js and run tests
|
||||
uses: actions/setup-node@v2.1.2
|
||||
with:
|
||||
node-version: 14.x
|
||||
- run: yarn install --immutable --silent --non-interactive 2> >(grep -v warning 1>&2)
|
||||
- run: yarn lint
|
||||
- run: yarn test
|
|
@ -1,9 +1,10 @@
|
|||
import { ApiCall, EntityAction, EntityName, ExtraActionProps } from './types'
|
||||
import {
|
||||
EntityAction, EntityName, ExtraActionProps,
|
||||
} from './types'
|
||||
|
||||
export const loadAction = <T>(entity: EntityName, extra: ExtraActionProps<T>): EntityAction<T> => {
|
||||
return {
|
||||
entity,
|
||||
extra,
|
||||
type: 'GET_FROM_API',
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const loadAction = <T>(entity: EntityName, extra: ExtraActionProps<T>): EntityAction<T> => ({
|
||||
entity,
|
||||
extra,
|
||||
type: 'GET_FROM_API',
|
||||
})
|
||||
|
|
38
src/hooks.ts
38
src/hooks.ts
|
@ -1,13 +1,23 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { Api, CalendarItem, Child, Classmate, MenuItem, NewsItem, Notification, ScheduleItem, User } from '@skolplattformen/embedded-api'
|
||||
import {
|
||||
Api,
|
||||
CalendarItem,
|
||||
Child,
|
||||
Classmate,
|
||||
MenuItem,
|
||||
NewsItem,
|
||||
Notification,
|
||||
ScheduleItem,
|
||||
User,
|
||||
} from '@skolplattformen/embedded-api'
|
||||
import {
|
||||
ApiCall,
|
||||
EntityHookResult,
|
||||
EntityMap,
|
||||
EntityName,
|
||||
EntityStoreRootState,
|
||||
ExtraActionProps
|
||||
ExtraActionProps,
|
||||
} from './types'
|
||||
import { useApi } from './context'
|
||||
import { loadAction } from './actions'
|
||||
|
@ -57,7 +67,9 @@ const hook = <T>(
|
|||
|
||||
const listener = () => {
|
||||
const newState = select(store.getState() as EntityStoreRootState)
|
||||
if (newState.status !== state.status || newState.data !== state.data || newState.error !== state.error) {
|
||||
if (newState.status !== state.status
|
||||
|| newState.data !== state.data
|
||||
|| newState.error !== state.error) {
|
||||
setState(newState)
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +77,7 @@ const hook = <T>(
|
|||
|
||||
return {
|
||||
...state,
|
||||
reload: () => load(true)
|
||||
reload: () => load(true),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +85,7 @@ export const useChildList = () => hook<Child[]>(
|
|||
'CHILDREN',
|
||||
'children',
|
||||
[],
|
||||
(store) => store.children,
|
||||
(s) => s.children,
|
||||
(api) => () => api.getChildren(),
|
||||
)
|
||||
|
||||
|
@ -81,7 +93,7 @@ export const useCalendar = (child: Child) => hook<CalendarItem[]>(
|
|||
'CALENDAR',
|
||||
`calendar_${child.id}`,
|
||||
[],
|
||||
(store) => store.calendar,
|
||||
(s) => s.calendar,
|
||||
(api) => () => api.getCalendar(child),
|
||||
)
|
||||
|
||||
|
@ -89,7 +101,7 @@ export const useClassmates = (child: Child) => hook<Classmate[]>(
|
|||
'CLASSMATES',
|
||||
`classmates_${child.id}`,
|
||||
[],
|
||||
(store) => store.classmates,
|
||||
(s) => s.classmates,
|
||||
(api) => () => api.getClassmates(child),
|
||||
)
|
||||
|
||||
|
@ -97,7 +109,7 @@ export const useMenu = (child: Child) => hook<MenuItem[]>(
|
|||
'MENU',
|
||||
`menu_${child.id}`,
|
||||
[],
|
||||
(store) => store.menu,
|
||||
(s) => s.menu,
|
||||
(api) => () => api.getMenu(child),
|
||||
)
|
||||
|
||||
|
@ -105,7 +117,7 @@ export const useNews = (child: Child) => hook<NewsItem[]>(
|
|||
'NEWS',
|
||||
`news_${child.id}`,
|
||||
[],
|
||||
(store) => store.news,
|
||||
(s) => s.news,
|
||||
(api) => () => api.getNews(child),
|
||||
)
|
||||
|
||||
|
@ -113,7 +125,7 @@ export const useNotifications = (child: Child) => hook<Notification[]>(
|
|||
'NOTIFICATIONS',
|
||||
`notifications_${child.id}`,
|
||||
[],
|
||||
(store) => store.notifications,
|
||||
(s) => s.notifications,
|
||||
(api) => () => api.getNotifications(child),
|
||||
)
|
||||
|
||||
|
@ -121,7 +133,7 @@ export const useSchedule = (child: Child, from: string, to: string) => hook<Sche
|
|||
'SCHEDULE',
|
||||
`schedule_${child.id}_${from}_${to}`,
|
||||
[],
|
||||
(store) => store.schedule,
|
||||
(s) => s.schedule,
|
||||
(api) => () => api.getSchedule(child, from, to),
|
||||
)
|
||||
|
||||
|
@ -129,6 +141,6 @@ export const useUser = () => hook<User>(
|
|||
'USER',
|
||||
'user',
|
||||
{},
|
||||
(store) => store.user,
|
||||
(s) => s.user,
|
||||
(api) => () => api.getUser(),
|
||||
)
|
||||
|
|
|
@ -1,81 +1,81 @@
|
|||
/* eslint-disable default-case */
|
||||
import { Middleware } from 'redux'
|
||||
import { EntityAction, EntityStoreRootState } from './types'
|
||||
|
||||
export const apiMiddleware: Middleware<{}, EntityStoreRootState> =
|
||||
(storeApi) =>
|
||||
(next) =>
|
||||
(action: EntityAction<any>) => {
|
||||
try {
|
||||
switch (action.type) {
|
||||
case 'GET_FROM_API': {
|
||||
// Call api
|
||||
const apiCall = action.extra?.apiCall
|
||||
if (apiCall) {
|
||||
apiCall()
|
||||
.then((res: any) => {
|
||||
const resultAction: EntityAction<any> = {
|
||||
...action,
|
||||
type: 'RESULT_FROM_API',
|
||||
data: res
|
||||
}
|
||||
storeApi.dispatch(resultAction)
|
||||
|
||||
if (action.extra?.saveToCache && res) {
|
||||
const cacheAction: EntityAction<any> = {
|
||||
...resultAction,
|
||||
type: 'STORE_IN_CACHE'
|
||||
}
|
||||
storeApi.dispatch(cacheAction)
|
||||
}
|
||||
})
|
||||
type IMiddleware = Middleware<{}, EntityStoreRootState>
|
||||
export const apiMiddleware: IMiddleware = (storeApi) => (next) => (action: EntityAction<any>) => {
|
||||
try {
|
||||
switch (action.type) {
|
||||
case 'GET_FROM_API': {
|
||||
// Call api
|
||||
const apiCall = action.extra?.apiCall
|
||||
if (apiCall) {
|
||||
apiCall()
|
||||
.then((res: any) => {
|
||||
const resultAction: EntityAction<any> = {
|
||||
...action,
|
||||
type: 'RESULT_FROM_API',
|
||||
data: res,
|
||||
}
|
||||
storeApi.dispatch(resultAction)
|
||||
|
||||
// Retrieve from cache
|
||||
if (action.extra?.getFromCache) {
|
||||
if (action.extra?.saveToCache && res) {
|
||||
const cacheAction: EntityAction<any> = {
|
||||
...action,
|
||||
type: 'GET_FROM_CACHE'
|
||||
...resultAction,
|
||||
type: 'STORE_IN_CACHE',
|
||||
}
|
||||
storeApi.dispatch(cacheAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
return next(action)
|
||||
}
|
||||
|
||||
export const cacheMiddleware: Middleware<{}, EntityStoreRootState> =
|
||||
(storeApi) =>
|
||||
(next) =>
|
||||
(action: EntityAction<any>) => {
|
||||
try {
|
||||
switch (action.type) {
|
||||
case 'GET_FROM_CACHE': {
|
||||
const getFromCache = action.extra?.getFromCache
|
||||
if (getFromCache) {
|
||||
getFromCache().then((res: string | null) => {
|
||||
if (res) {
|
||||
const cacheResultAction: EntityAction<any> = {
|
||||
...action,
|
||||
type: 'RESULT_FROM_CACHE',
|
||||
data: JSON.parse(res)
|
||||
}
|
||||
storeApi.dispatch(cacheResultAction)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
case 'STORE_IN_CACHE': {
|
||||
const saveToCache = action.extra?.saveToCache
|
||||
if (saveToCache && action.data) {
|
||||
saveToCache(JSON.stringify(action.data))
|
||||
}
|
||||
}
|
||||
// Retrieve from cache
|
||||
if (action.extra?.getFromCache) {
|
||||
const cacheAction: EntityAction<any> = {
|
||||
...action,
|
||||
type: 'GET_FROM_CACHE',
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
storeApi.dispatch(cacheAction)
|
||||
}
|
||||
return next(action)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err)
|
||||
}
|
||||
return next(action)
|
||||
}
|
||||
|
||||
export const cacheMiddleware: IMiddleware = (storeApi) => (next) => (action: EntityAction<any>) => {
|
||||
try {
|
||||
switch (action.type) {
|
||||
case 'GET_FROM_CACHE': {
|
||||
const getFromCache = action.extra?.getFromCache
|
||||
if (getFromCache) {
|
||||
getFromCache().then((res: string | null) => {
|
||||
if (res) {
|
||||
const cacheResultAction: EntityAction<any> = {
|
||||
...action,
|
||||
type: 'RESULT_FROM_CACHE',
|
||||
data: JSON.parse(res),
|
||||
}
|
||||
storeApi.dispatch(cacheResultAction)
|
||||
}
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'STORE_IN_CACHE': {
|
||||
const saveToCache = action.extra?.saveToCache
|
||||
if (saveToCache && action.data) {
|
||||
saveToCache(JSON.stringify(action.data))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err)
|
||||
}
|
||||
return next(action)
|
||||
}
|
||||
|
|
|
@ -12,8 +12,7 @@ import { EntityName, EntityReducer, EntityState } from './types'
|
|||
|
||||
const createReducer = <T>(entity: EntityName): EntityReducer<T> => {
|
||||
const reducer: EntityReducer<T> = (state = {}, action) => {
|
||||
if (action.entity !== entity || !action.extra)
|
||||
return state
|
||||
if (action.entity !== entity || !action.extra) return state
|
||||
const key = action.extra?.key
|
||||
|
||||
const node = state[key] || {
|
||||
|
@ -36,12 +35,14 @@ const createReducer = <T>(entity: EntityName): EntityReducer<T> => {
|
|||
data: action.data || node.data,
|
||||
status: 'loaded',
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'RESULT_FROM_CACHE': {
|
||||
newNode = {
|
||||
...node,
|
||||
data: action.data || node.data,
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
newNode = { ...node }
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
schedule,
|
||||
user,
|
||||
} from './reducers'
|
||||
import { EntityAction } from './types'
|
||||
|
||||
const appReducer = combineReducers({
|
||||
calendar,
|
||||
|
@ -22,8 +21,9 @@ const appReducer = combineReducers({
|
|||
schedule,
|
||||
user,
|
||||
})
|
||||
const rootReducer = (state: unknown, action: EntityAction<any>) => {
|
||||
const rootReducer: typeof appReducer = (state, action) => {
|
||||
if (action.type === 'CLEAR') {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
state = undefined
|
||||
}
|
||||
return appReducer(state, action)
|
||||
|
|
18
src/types.ts
18
src/types.ts
|
@ -7,7 +7,7 @@ import {
|
|||
MenuItem,
|
||||
NewsItem,
|
||||
Notification,
|
||||
ScheduleItem
|
||||
ScheduleItem,
|
||||
} from '@skolplattformen/embedded-api'
|
||||
import { Action, Reducer } from 'redux'
|
||||
|
||||
|
@ -37,14 +37,14 @@ export interface ExtraActionProps<T> {
|
|||
}
|
||||
export type EntityActionType = 'GET_FROM_API' | 'RESULT_FROM_API' | 'GET_FROM_CACHE' | 'RESULT_FROM_CACHE' | 'STORE_IN_CACHE' | 'CLEAR'
|
||||
export type EntityName = 'USER'
|
||||
| 'CHILDREN'
|
||||
| 'CALENDAR'
|
||||
| 'CLASSMATES'
|
||||
| 'MENU'
|
||||
| 'NEWS'
|
||||
| 'NOTIFICATIONS'
|
||||
| 'SCHEDULE'
|
||||
| 'ALL'
|
||||
| 'CHILDREN'
|
||||
| 'CALENDAR'
|
||||
| 'CLASSMATES'
|
||||
| 'MENU'
|
||||
| 'NEWS'
|
||||
| 'NOTIFICATIONS'
|
||||
| 'SCHEDULE'
|
||||
| 'ALL'
|
||||
export interface EntityAction<T> extends Action<EntityActionType> {
|
||||
entity: EntityName
|
||||
data?: T
|
||||
|
|
Loading…
Reference in New Issue