Skip to content

Commit

Permalink
feat: 🎸 First release
Browse files Browse the repository at this point in the history
Basically everything in react-native-embedded-api... but rewritten
  • Loading branch information
JohanObrink committed Feb 6, 2021
1 parent d623705 commit d37f3db
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 98 deletions.
6 changes: 6 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
module.exports = {
extends: ['airbnb-typescript'],
parserOptions: {
project: `./tsconfig.json`
},
rules: {
'@typescript-eslint/semi': ['error', 'never']
}
}
27 changes: 27 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -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
17 changes: 9 additions & 8 deletions src/actions.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 25 additions & 13 deletions src/hooks.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -57,78 +67,80 @@ 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)
}
}
useEffect(() => store.subscribe(listener), [])

return {
...state,
reload: () => load(true)
reload: () => load(true),
}
}

export const useChildList = () => hook<Child[]>(
'CHILDREN',
'children',
[],
(store) => store.children,
(s) => s.children,
(api) => () => api.getChildren(),
)

export const useCalendar = (child: Child) => hook<CalendarItem[]>(
'CALENDAR',
`calendar_${child.id}`,
[],
(store) => store.calendar,
(s) => s.calendar,
(api) => () => api.getCalendar(child),
)

export const useClassmates = (child: Child) => hook<Classmate[]>(
'CLASSMATES',
`classmates_${child.id}`,
[],
(store) => store.classmates,
(s) => s.classmates,
(api) => () => api.getClassmates(child),
)

export const useMenu = (child: Child) => hook<MenuItem[]>(
'MENU',
`menu_${child.id}`,
[],
(store) => store.menu,
(s) => s.menu,
(api) => () => api.getMenu(child),
)

export const useNews = (child: Child) => hook<NewsItem[]>(
'NEWS',
`news_${child.id}`,
[],
(store) => store.news,
(s) => s.news,
(api) => () => api.getNews(child),
)

export const useNotifications = (child: Child) => hook<Notification[]>(
'NOTIFICATIONS',
`notifications_${child.id}`,
[],
(store) => store.notifications,
(s) => s.notifications,
(api) => () => api.getNotifications(child),
)

export const useSchedule = (child: Child, from: string, to: string) => hook<ScheduleItem[]>(
'SCHEDULE',
`schedule_${child.id}_${from}_${to}`,
[],
(store) => store.schedule,
(s) => s.schedule,
(api) => () => api.getSchedule(child, from, to),
)

export const useUser = () => hook<User>(
'USER',
'user',
{},
(store) => store.user,
(s) => s.user,
(api) => () => api.getUser(),
)
128 changes: 64 additions & 64 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -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)
}
}
})
}

// 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: 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)
}
})
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)
}
case 'STORE_IN_CACHE': {
const saveToCache = action.extra?.saveToCache
if (saveToCache && action.data) {
saveToCache(JSON.stringify(action.data))
}
}
}
} catch (err) {
console.error(err)
})
}
break
}
case 'STORE_IN_CACHE': {
const saveToCache = action.extra?.saveToCache
if (saveToCache && action.data) {
saveToCache(JSON.stringify(action.data))
}
return next(action)
break
}
}
} catch (err) {
// eslint-disable-next-line no-console
console.error(err)
}
return next(action)
}
5 changes: 3 additions & 2 deletions src/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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] || {
Expand All @@ -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 }
Expand Down
4 changes: 2 additions & 2 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
schedule,
user,
} from './reducers'
import { EntityAction } from './types'

const appReducer = combineReducers({
calendar,
Expand All @@ -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)
Expand Down
Loading

0 comments on commit d37f3db

Please sign in to comment.