Fix packages/client
This commit is contained in:
parent
30d1f1fcd4
commit
92912af839
@ -37,6 +37,10 @@ rules:
|
|||||||
'@typescript-eslint/explicit-function-return-type': off
|
'@typescript-eslint/explicit-function-return-type': off
|
||||||
'@typescript-eslint/no-non-null-assertion': off
|
'@typescript-eslint/no-non-null-assertion': off
|
||||||
'@typescript-eslint/no-use-before-define': off
|
'@typescript-eslint/no-use-before-define': off
|
||||||
|
'@typescript-eslint/no-empty-interface': off
|
||||||
|
'@typescript-eslint/no-explicit-any':
|
||||||
|
- warn
|
||||||
|
- ignoreRestArgs: true
|
||||||
overrides:
|
overrides:
|
||||||
- files:
|
- files:
|
||||||
- '*.test.ts'
|
- '*.test.ts'
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import {Control, Field, Input as I, Heading} from 'bloomer'
|
import {Control, Field, Input as I, Heading} from 'bloomer'
|
||||||
import {IconType} from 'react-icons'
|
import {IconType} from 'react-icons'
|
||||||
|
|
||||||
export interface IInputProps {
|
export interface InputProps {
|
||||||
name: string
|
name: string
|
||||||
type: 'text' | 'password' | 'hidden' | 'submit' | 'email'
|
type: 'text' | 'password' | 'hidden' | 'submit' | 'email'
|
||||||
value?: string
|
value?: string
|
||||||
@ -14,7 +14,7 @@ export interface IInputProps {
|
|||||||
required?: boolean
|
required?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Input extends React.PureComponent<IInputProps> {
|
export class Input extends React.PureComponent<InputProps> {
|
||||||
handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (this.props.onChange) {
|
if (this.props.onChange) {
|
||||||
this.props.onChange(this.props.name, e.target.value)
|
this.props.onChange(this.props.name, e.target.value)
|
||||||
|
|||||||
@ -1,24 +1,21 @@
|
|||||||
import React from 'react'
|
|
||||||
import {History, Location} from 'history'
|
|
||||||
import {IWithRouterProps} from './IWithRouterProps'
|
|
||||||
import {Link as RouterLink, LinkProps} from 'react-router-dom'
|
|
||||||
import { URLFormatter } from '@rondo.dev/http-client'
|
import { URLFormatter } from '@rondo.dev/http-client'
|
||||||
|
import React from 'react'
|
||||||
import { withRouter } from 'react-router'
|
import { withRouter } from 'react-router'
|
||||||
|
import { Link as RouterLink } from 'react-router-dom'
|
||||||
|
import { WithRouterProps } from './WithRouterProps'
|
||||||
|
|
||||||
export interface ILinkProps
|
export interface LinkProps
|
||||||
extends IWithRouterProps<Record<string, string>> {
|
extends WithRouterProps<Record<string, string>> {
|
||||||
readonly className?: string
|
readonly className?: string
|
||||||
readonly to: string
|
readonly to: string
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContextLink extends React.PureComponent<ILinkProps> {
|
class ContextLink extends React.PureComponent<LinkProps> {
|
||||||
protected readonly urlFormatter = new URLFormatter()
|
protected readonly urlFormatter = new URLFormatter()
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
history,
|
|
||||||
location,
|
|
||||||
match,
|
match,
|
||||||
to,
|
to,
|
||||||
children,
|
children,
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export class Redirect extends React.PureComponent<RedirectProps> {
|
|||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
You are being redirected.
|
You are being redirected.
|
||||||
Click <a href={href}>here</a> to 'continue'
|
Click <a href={href}>here</a> to {'continue'}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,18 @@
|
|||||||
import React, {useEffect} from 'react'
|
import {useEffect} from 'react'
|
||||||
import {Dispatch, bindActionCreators} from 'redux'
|
import {Dispatch, bindActionCreators} from 'redux'
|
||||||
import {IWithRouterProps} from './IWithRouterProps'
|
import {WithRouterProps} from './WithRouterProps'
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
import {setRedirectTo} from '../login/LoginActions'
|
import {setRedirectTo} from '../login/LoginActions'
|
||||||
import {withRouter} from 'react-router'
|
import {withRouter} from 'react-router'
|
||||||
|
|
||||||
export interface IReturnToProps extends IWithRouterProps {
|
export interface ReturnToProps extends WithRouterProps {
|
||||||
setRedirectTo: typeof setRedirectTo
|
setRedirectTo: typeof setRedirectTo
|
||||||
}
|
}
|
||||||
|
|
||||||
function FReturnHere(props: IReturnToProps) {
|
function FReturnHere(props: ReturnToProps) {
|
||||||
const {
|
const {
|
||||||
// tslint:disable-next-line
|
|
||||||
setRedirectTo,
|
setRedirectTo,
|
||||||
history,
|
|
||||||
location,
|
|
||||||
match,
|
match,
|
||||||
...otherProps
|
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import {format} from 'timeago.js'
|
import {format} from 'timeago.js'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export interface ITimeAgoProps {
|
export interface TimeAgoProps {
|
||||||
className?: string
|
className?: string
|
||||||
date: Date | string
|
date: Date | string
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TimeAgo extends React.PureComponent<ITimeAgoProps> {
|
export class TimeAgo extends React.PureComponent<TimeAgoProps> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<time className={this.props.className}>
|
<time className={this.props.className}>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {match as Match} from 'react-router'
|
import {match as Match} from 'react-router'
|
||||||
import {History, Location} from 'history'
|
import {History, Location} from 'history'
|
||||||
|
|
||||||
export interface IWithRouterProps<MatchProps = unknown> {
|
export interface WithRouterProps<MatchProps = unknown> {
|
||||||
history: History
|
history: History
|
||||||
location: Location
|
location: Location
|
||||||
match: Match<MatchProps>
|
match: Match<MatchProps>
|
||||||
@ -2,7 +2,7 @@ import {History, Location} from 'history'
|
|||||||
import {withRouter, match as Match} from 'react-router'
|
import {withRouter, match as Match} from 'react-router'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export interface IWithHistoryProps {
|
export interface WithHistoryProps {
|
||||||
history: History
|
history: History
|
||||||
location: Location
|
location: Location
|
||||||
match: Match
|
match: Match
|
||||||
@ -11,9 +11,9 @@ export interface IWithHistoryProps {
|
|||||||
export function withHistory<T extends {history: History}>(
|
export function withHistory<T extends {history: History}>(
|
||||||
Component: React.ComponentType<T>,
|
Component: React.ComponentType<T>,
|
||||||
) {
|
) {
|
||||||
class HistoryProvider extends React.PureComponent<IWithHistoryProps & T> {
|
class HistoryProvider extends React.PureComponent<WithHistoryProps & T> {
|
||||||
render() {
|
render() {
|
||||||
const {history, location, match, children, ...props} = this.props
|
const {history, children} = this.props
|
||||||
return (
|
return (
|
||||||
<Component history={history} {...this.props}>
|
<Component history={history} {...this.props}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -1,69 +1,69 @@
|
|||||||
import { HTTPClientMock } from '@rondo.dev/http-client'
|
import { HTTPClientMock } from '@rondo.dev/http-client'
|
||||||
import { TMethod } from '@rondo.dev/http-types'
|
import { Method } from '@rondo.dev/http-types'
|
||||||
import { IPendingAction } from '@rondo.dev/redux'
|
import { PendingAction } from '@rondo.dev/redux'
|
||||||
import { getError } from '@rondo.dev/test-utils'
|
import { getError } from '@rondo.dev/test-utils'
|
||||||
import { AnyAction } from 'redux'
|
import { AnyAction } from 'redux'
|
||||||
import { TestUtils } from '../test-utils'
|
import { TestUtils } from '../test-utils'
|
||||||
import { CRUDReducer, TCRUDAsyncMethod } from './'
|
import { CRUDReducer, CRUDAsyncMethod } from './'
|
||||||
import { createCRUDActions } from './CRUDActions'
|
import { createCRUDActions } from './CRUDActions'
|
||||||
|
|
||||||
describe('CRUD', () => {
|
describe('CRUD', () => {
|
||||||
|
|
||||||
interface ITwo {
|
interface Two {
|
||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ITwoCreateBody {
|
interface TwoCreateBody {
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ITwoListParams {
|
interface TwoListParams {
|
||||||
oneId: number
|
oneId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ITwoSpecificParams {
|
interface TwoSpecificParams {
|
||||||
oneId: number
|
oneId: number
|
||||||
twoId: number
|
twoId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ITestAPI {
|
interface TestAPI {
|
||||||
'/one/:oneId/two/:twoId': {
|
'/one/:oneId/two/:twoId': {
|
||||||
get: {
|
get: {
|
||||||
params: ITwoSpecificParams
|
params: TwoSpecificParams
|
||||||
response: ITwo
|
response: Two
|
||||||
}
|
}
|
||||||
put: {
|
put: {
|
||||||
params: ITwoSpecificParams
|
params: TwoSpecificParams
|
||||||
body: ITwoCreateBody
|
body: TwoCreateBody
|
||||||
response: ITwo
|
response: Two
|
||||||
}
|
}
|
||||||
delete: {
|
delete: {
|
||||||
params: ITwoSpecificParams
|
params: TwoSpecificParams
|
||||||
response: {id: number} // TODO return ITwoSpecificParams
|
response: {id: number} // TODO return ITwoSpecificParams
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'/one/:oneId/two': {
|
'/one/:oneId/two': {
|
||||||
get: {
|
get: {
|
||||||
params: ITwoListParams
|
params: TwoListParams
|
||||||
response: ITwo[]
|
response: Two[]
|
||||||
}
|
}
|
||||||
post: {
|
post: {
|
||||||
params: ITwoListParams
|
params: TwoListParams
|
||||||
body: ITwoCreateBody
|
body: TwoCreateBody
|
||||||
response: ITwo
|
response: Two
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const http = new HTTPClientMock<ITestAPI>()
|
const http = new HTTPClientMock<TestAPI>()
|
||||||
const actions = createCRUDActions(
|
const actions = createCRUDActions(
|
||||||
http,
|
http,
|
||||||
'/one/:oneId/two/:twoId',
|
'/one/:oneId/two/:twoId',
|
||||||
'/one/:oneId/two',
|
'/one/:oneId/two',
|
||||||
'TEST',
|
'TEST',
|
||||||
)
|
)
|
||||||
const crudReducer = new CRUDReducer<ITwo, 'TEST'>('TEST', {name: ''})
|
const crudReducer = new CRUDReducer<Two, 'TEST'>('TEST', {name: ''})
|
||||||
const Crud = crudReducer.reduce
|
const Crud = crudReducer.reduce
|
||||||
|
|
||||||
const test = new TestUtils()
|
const test = new TestUtils()
|
||||||
@ -101,8 +101,8 @@ describe('CRUD', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function dispatch(
|
function dispatch(
|
||||||
method: TCRUDAsyncMethod,
|
method: CRUDAsyncMethod,
|
||||||
action: IPendingAction<unknown, string>,
|
action: PendingAction<unknown, string>,
|
||||||
) {
|
) {
|
||||||
store.dispatch(action)
|
store.dispatch(action)
|
||||||
expect(store.getState().Crud.status[method].isLoading).toBe(true)
|
expect(store.getState().Crud.status[method].isLoading).toBe(true)
|
||||||
@ -110,13 +110,13 @@ describe('CRUD', () => {
|
|||||||
return action
|
return action
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUrl(method: TCRUDAsyncMethod) {
|
function getUrl(method: CRUDAsyncMethod) {
|
||||||
return method === 'save' || method === 'findMany'
|
return method === 'save' || method === 'findMany'
|
||||||
? '/one/1/two'
|
? '/one/1/two'
|
||||||
: '/one/1/two/2'
|
: '/one/1/two/2'
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHTTPMethod(method: TCRUDAsyncMethod): TMethod {
|
function getHTTPMethod(method: CRUDAsyncMethod): Method {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'save':
|
case 'save':
|
||||||
return 'post'
|
return 'post'
|
||||||
@ -132,7 +132,7 @@ describe('CRUD', () => {
|
|||||||
|
|
||||||
describe('Promise rejections', () => {
|
describe('Promise rejections', () => {
|
||||||
const testCases: Array<{
|
const testCases: Array<{
|
||||||
method: TCRUDAsyncMethod
|
method: CRUDAsyncMethod
|
||||||
params: any
|
params: any
|
||||||
}> = [{
|
}> = [{
|
||||||
method: 'findOne',
|
method: 'findOne',
|
||||||
@ -204,7 +204,7 @@ describe('CRUD', () => {
|
|||||||
const entity = {id: 100, name: 'test'}
|
const entity = {id: 100, name: 'test'}
|
||||||
|
|
||||||
const testCases: Array<{
|
const testCases: Array<{
|
||||||
method: TCRUDAsyncMethod,
|
method: CRUDAsyncMethod
|
||||||
params: any
|
params: any
|
||||||
body?: any
|
body?: any
|
||||||
response: any
|
response: any
|
||||||
|
|||||||
41
packages/client/src/crud/CRUDAction.ts
Normal file
41
packages/client/src/crud/CRUDAction.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import {Action, AsyncAction} from '@rondo.dev/redux'
|
||||||
|
import {CRUDMethod} from './CRUDMethod'
|
||||||
|
|
||||||
|
// Async actions
|
||||||
|
|
||||||
|
export type CRUDSaveAction<T, ActionType extends string> =
|
||||||
|
AsyncAction<T, ActionType> & {method: Extract<CRUDMethod, 'save'>}
|
||||||
|
|
||||||
|
export type CRUDUpdateAction<T, ActionType extends string> =
|
||||||
|
AsyncAction<T, ActionType> & {method: Extract<CRUDMethod, 'update'>}
|
||||||
|
|
||||||
|
export type CRUDRemoveAction<T, ActionType extends string> =
|
||||||
|
AsyncAction<T, ActionType> & {method: Extract<CRUDMethod, 'remove'>}
|
||||||
|
|
||||||
|
export type CRUDFindOneAction<T, ActionType extends string> =
|
||||||
|
AsyncAction<T, ActionType> & {method: Extract<CRUDMethod, 'findOne'>}
|
||||||
|
|
||||||
|
export type CRUDFindManyAction<T, ActionType extends string> =
|
||||||
|
AsyncAction<T[], ActionType> & {method: Extract<CRUDMethod, 'findMany'>}
|
||||||
|
|
||||||
|
// Synchronous actions
|
||||||
|
|
||||||
|
export type CRUDCreateAction<T, ActionType extends string> =
|
||||||
|
Action<Partial<T>, ActionType> & {method: Extract<CRUDMethod, 'create'>}
|
||||||
|
|
||||||
|
export type CRUDEditAction<ActionType extends string> =
|
||||||
|
Action<{id: number}, ActionType> & {method: Extract<CRUDMethod, 'edit'>}
|
||||||
|
|
||||||
|
export type CRUDChangeAction<T, ActionType extends string> =
|
||||||
|
Action<{id?: number, key: keyof T, value: string}, ActionType>
|
||||||
|
& {method: Extract<CRUDMethod, 'change'>}
|
||||||
|
|
||||||
|
export type CRUDAction<T, ActionType extends string> =
|
||||||
|
CRUDSaveAction<T, ActionType>
|
||||||
|
| CRUDUpdateAction<T, ActionType>
|
||||||
|
| CRUDRemoveAction<T, ActionType>
|
||||||
|
| CRUDFindOneAction<T, ActionType>
|
||||||
|
| CRUDFindManyAction<T, ActionType>
|
||||||
|
| CRUDCreateAction<T, ActionType>
|
||||||
|
| CRUDEditAction<ActionType>
|
||||||
|
| CRUDChangeAction<T, ActionType>
|
||||||
@ -1,35 +1,45 @@
|
|||||||
import { TFilter, TOnlyDefined } from '@rondo.dev/common'
|
import { Filter, OnlyDefined } from '@rondo.dev/common'
|
||||||
import { IHTTPClient } from '@rondo.dev/http-client'
|
import { HTTPClient } from '@rondo.dev/http-client'
|
||||||
import { IRoutes } from '@rondo.dev/http-types'
|
import { Routes } from '@rondo.dev/http-types'
|
||||||
import { TCRUDAction, TCRUDChangeAction, TCRUDCreateAction, TCRUDEditAction } from './TCRUDAction'
|
import { CRUDAction, CRUDChangeAction, CRUDCreateAction, CRUDEditAction } from './CRUDAction'
|
||||||
import { TCRUDMethod } from './TCRUDMethod'
|
import { CRUDMethod } from './CRUDMethod'
|
||||||
|
|
||||||
type TAction <T, ActionType extends string, Method extends TCRUDMethod> =
|
type Action <T, ActionType extends string, Method extends CRUDMethod> =
|
||||||
TFilter<TCRUDAction<T, ActionType> , {method: Method, status: 'pending'}>
|
Filter<CRUDAction<T, ActionType> , {method: Method, status: 'pending'}>
|
||||||
|
|
||||||
export interface ICRUDChangeParams<T> {
|
interface PostParams<Body = unknown, Params = unknown> {
|
||||||
|
body: Body
|
||||||
|
params: Params
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetParams<Query = unknown, Params = unknown> {
|
||||||
|
query: Query
|
||||||
|
params: Params
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CRUDChangeParams<T> {
|
||||||
id?: number
|
id?: number
|
||||||
key: keyof T & string
|
key: keyof T & string
|
||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SaveActionCreator<
|
export class SaveActionCreator<
|
||||||
T extends IRoutes,
|
T extends Routes,
|
||||||
Route extends keyof T & string,
|
Route extends keyof T & string,
|
||||||
ActionType extends string,
|
ActionType extends string,
|
||||||
> {
|
> {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly http: IHTTPClient<T>,
|
readonly http: HTTPClient<T>,
|
||||||
readonly route: Route,
|
readonly route: Route,
|
||||||
readonly type: ActionType,
|
readonly type: ActionType,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
save = (params: TOnlyDefined<{
|
save = (params: OnlyDefined<{
|
||||||
body: T[Route]['post']['body'],
|
body: T[Route]['post']['body']
|
||||||
params: T[Route]['post']['params'],
|
params: T[Route]['post']['params']
|
||||||
}>): TAction<T[Route]['post']['response'], ActionType, 'save'> => {
|
}>): Action<T[Route]['post']['response'], ActionType, 'save'> => {
|
||||||
const p = params as any
|
const p = params as PostParams
|
||||||
return {
|
return {
|
||||||
payload: this.http.post(this.route, p.body, p.params),
|
payload: this.http.post(this.route, p.body, p.params),
|
||||||
type: this.type,
|
type: this.type,
|
||||||
@ -40,22 +50,22 @@ export class SaveActionCreator<
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class FindOneActionCreator<
|
export class FindOneActionCreator<
|
||||||
T extends IRoutes,
|
T extends Routes,
|
||||||
Route extends keyof T & string,
|
Route extends keyof T & string,
|
||||||
ActionType extends string,
|
ActionType extends string,
|
||||||
> {
|
> {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly http: IHTTPClient<T>,
|
readonly http: HTTPClient<T>,
|
||||||
readonly route: Route,
|
readonly route: Route,
|
||||||
readonly type: ActionType,
|
readonly type: ActionType,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
findOne = (params: TOnlyDefined<{
|
findOne = (params: OnlyDefined<{
|
||||||
query: T[Route]['get']['query'],
|
query: T[Route]['get']['query']
|
||||||
params: T[Route]['get']['params'],
|
params: T[Route]['get']['params']
|
||||||
}>): TAction<T[Route]['get']['response'], ActionType, 'findOne'> => {
|
}>): Action<T[Route]['get']['response'], ActionType, 'findOne'> => {
|
||||||
const p = params as any
|
const p = params as {query: unknown, params: unknown}
|
||||||
return {
|
return {
|
||||||
payload: this.http.get(this.route, p.query, p.params),
|
payload: this.http.get(this.route, p.query, p.params),
|
||||||
type: this.type,
|
type: this.type,
|
||||||
@ -67,22 +77,22 @@ export class FindOneActionCreator<
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateActionCreator<
|
export class UpdateActionCreator<
|
||||||
T extends IRoutes,
|
T extends Routes,
|
||||||
Route extends keyof T & string,
|
Route extends keyof T & string,
|
||||||
ActionType extends string
|
ActionType extends string
|
||||||
> {
|
> {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly http: IHTTPClient<T>,
|
readonly http: HTTPClient<T>,
|
||||||
readonly route: Route,
|
readonly route: Route,
|
||||||
readonly type: ActionType,
|
readonly type: ActionType,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
update = (params: TOnlyDefined<{
|
update = (params: OnlyDefined<{
|
||||||
body: T[Route]['put']['body'],
|
body: T[Route]['put']['body']
|
||||||
params: T[Route]['put']['params'],
|
params: T[Route]['put']['params']
|
||||||
}>): TAction<T[Route]['put']['response'], ActionType, 'update'> => {
|
}>): Action<T[Route]['put']['response'], ActionType, 'update'> => {
|
||||||
const p = params as any
|
const p = params as PostParams
|
||||||
return {
|
return {
|
||||||
payload: this.http.put(this.route, p.body, p.params),
|
payload: this.http.put(this.route, p.body, p.params),
|
||||||
type: this.type,
|
type: this.type,
|
||||||
@ -94,22 +104,22 @@ export class UpdateActionCreator<
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class RemoveActionCreator<
|
export class RemoveActionCreator<
|
||||||
T extends IRoutes,
|
T extends Routes,
|
||||||
Route extends keyof T & string,
|
Route extends keyof T & string,
|
||||||
ActionType extends string,
|
ActionType extends string,
|
||||||
> {
|
> {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly http: IHTTPClient<T>,
|
readonly http: HTTPClient<T>,
|
||||||
readonly route: Route,
|
readonly route: Route,
|
||||||
readonly type: ActionType,
|
readonly type: ActionType,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
remove = (params: TOnlyDefined<{
|
remove = (params: OnlyDefined<{
|
||||||
body: T[Route]['delete']['body'],
|
body: T[Route]['delete']['body']
|
||||||
params: T[Route]['delete']['params'],
|
params: T[Route]['delete']['params']
|
||||||
}>): TAction<T[Route]['delete']['response'], ActionType, 'remove'> => {
|
}>): Action<T[Route]['delete']['response'], ActionType, 'remove'> => {
|
||||||
const p = params as any
|
const p = params as PostParams
|
||||||
return {
|
return {
|
||||||
payload: this.http.delete(this.route, p.body, p.params),
|
payload: this.http.delete(this.route, p.body, p.params),
|
||||||
type: this.type,
|
type: this.type,
|
||||||
@ -120,27 +130,27 @@ export class RemoveActionCreator<
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class FindManyActionCreator<
|
export class FindManyActionCreator<
|
||||||
T extends IRoutes,
|
T extends Routes,
|
||||||
Route extends keyof T & string,
|
Route extends keyof T & string,
|
||||||
ActionType extends string,
|
ActionType extends string,
|
||||||
> {
|
> {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly http: IHTTPClient<T>,
|
readonly http: HTTPClient<T>,
|
||||||
readonly route: Route,
|
readonly route: Route,
|
||||||
readonly type: ActionType,
|
readonly type: ActionType,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
findMany = (params: TOnlyDefined<{
|
findMany = (params: OnlyDefined<{
|
||||||
query: T[Route]['get']['query'],
|
query: T[Route]['get']['query']
|
||||||
params: T[Route]['get']['params'],
|
params: T[Route]['get']['params']
|
||||||
}>): {
|
}>): {
|
||||||
payload: Promise<T[Route]['get']['response']>
|
payload: Promise<T[Route]['get']['response']>
|
||||||
type: ActionType
|
type: ActionType
|
||||||
status: 'pending',
|
status: 'pending'
|
||||||
method: 'findMany',
|
method: 'findMany'
|
||||||
} => {
|
} => {
|
||||||
const p = params as any
|
const p = params as GetParams
|
||||||
return {
|
return {
|
||||||
payload: this.http.get(this.route, p.query, p.params),
|
payload: this.http.get(this.route, p.query, p.params),
|
||||||
type: this.type,
|
type: this.type,
|
||||||
@ -154,7 +164,7 @@ export class FindManyActionCreator<
|
|||||||
export class FormActionCreator<T, ActionType extends string> {
|
export class FormActionCreator<T, ActionType extends string> {
|
||||||
constructor(readonly actionType: ActionType) {}
|
constructor(readonly actionType: ActionType) {}
|
||||||
|
|
||||||
create = (item: Partial<T>): TCRUDCreateAction<T, ActionType> => {
|
create = (item: Partial<T>): CRUDCreateAction<T, ActionType> => {
|
||||||
return {
|
return {
|
||||||
payload: item,
|
payload: item,
|
||||||
type: this.actionType,
|
type: this.actionType,
|
||||||
@ -162,7 +172,7 @@ export class FormActionCreator<T, ActionType extends string> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
edit = (params: {id: number}): TCRUDEditAction<ActionType> => {
|
edit = (params: {id: number}): CRUDEditAction<ActionType> => {
|
||||||
return {
|
return {
|
||||||
payload: {id: params.id},
|
payload: {id: params.id},
|
||||||
type: this.actionType,
|
type: this.actionType,
|
||||||
@ -170,8 +180,9 @@ export class FormActionCreator<T, ActionType extends string> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
change = (params: ICRUDChangeParams<T>)
|
change = (
|
||||||
: TCRUDChangeAction<T, ActionType> => {
|
params: CRUDChangeParams<T>,
|
||||||
|
): CRUDChangeAction<T, ActionType> => {
|
||||||
return {
|
return {
|
||||||
payload: params,
|
payload: params,
|
||||||
type: this.actionType,
|
type: this.actionType,
|
||||||
@ -181,12 +192,12 @@ export class FormActionCreator<T, ActionType extends string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createCRUDActions<
|
export function createCRUDActions<
|
||||||
T extends IRoutes,
|
T extends Routes,
|
||||||
EntityRoute extends keyof T & string,
|
EntityRoute extends keyof T & string,
|
||||||
ListRoute extends keyof T & string,
|
ListRoute extends keyof T & string,
|
||||||
ActionType extends string,
|
ActionType extends string,
|
||||||
>(
|
>(
|
||||||
http: IHTTPClient<T>,
|
http: HTTPClient<T>,
|
||||||
entityRoute: EntityRoute,
|
entityRoute: EntityRoute,
|
||||||
listRoute: ListRoute,
|
listRoute: ListRoute,
|
||||||
actionType: ActionType,
|
actionType: ActionType,
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Control, Field, Heading, Icon, Input} from 'bloomer'
|
import {Control, Field, Heading, Icon, Input} from 'bloomer'
|
||||||
import {ICRUDChangeParams} from './CRUDActions'
|
import {CRUDChangeParams} from './CRUDActions'
|
||||||
|
|
||||||
export type TCRUDFieldType = 'text' | 'password' | 'number' | 'email' | 'tel'
|
export type TCRUDFieldType = 'text' | 'password' | 'number' | 'email' | 'tel'
|
||||||
|
|
||||||
export interface ICRUDFieldProps<T> {
|
export interface CRUDFieldProps<T> {
|
||||||
id?: number
|
id?: number
|
||||||
onChange<K extends keyof T>(params: ICRUDChangeParams<T>): void
|
onChange<K extends keyof T>(params: CRUDChangeParams<T>): void
|
||||||
Icon?: React.ComponentType
|
Icon?: React.ComponentType
|
||||||
error?: string
|
error?: string
|
||||||
label: string
|
label: string
|
||||||
@ -18,7 +18,7 @@ export interface ICRUDFieldProps<T> {
|
|||||||
|
|
||||||
export type TCRUDErrors<T> = Partial<Record<keyof T & string, string>>
|
export type TCRUDErrors<T> = Partial<Record<keyof T & string, string>>
|
||||||
|
|
||||||
export interface ICRUDField<T> {
|
export interface CRUDField<T> {
|
||||||
Icon?: React.ComponentType
|
Icon?: React.ComponentType
|
||||||
label: string
|
label: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
@ -26,19 +26,19 @@ export interface ICRUDField<T> {
|
|||||||
type: TCRUDFieldType
|
type: TCRUDFieldType
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICRUDFormProps<T> {
|
export interface CRUDFormProps<T> {
|
||||||
errors: TCRUDErrors<T>
|
errors: TCRUDErrors<T>
|
||||||
id?: number
|
id?: number
|
||||||
item?: T
|
item?: T
|
||||||
error: string
|
error: string
|
||||||
submitText: string
|
submitText: string
|
||||||
fields: Array<ICRUDField<T>>
|
fields: Array<CRUDField<T>>
|
||||||
|
|
||||||
onSubmit: (t: T) => void
|
onSubmit: (t: T) => void
|
||||||
onChange(params: ICRUDChangeParams<T>): void
|
onChange(params: CRUDChangeParams<T>): void
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CRUDField<T> extends React.PureComponent<ICRUDFieldProps<T>> {
|
export class CRUDField<T> extends React.PureComponent<CRUDFieldProps<T>> {
|
||||||
handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const {onChange} = this.props
|
const {onChange} = this.props
|
||||||
const {value} = e.target
|
const {value} = e.target
|
||||||
@ -71,7 +71,7 @@ export class CRUDField<T> extends React.PureComponent<ICRUDFieldProps<T>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CRUDForm<T> extends React.PureComponent<ICRUDFormProps<T>> {
|
export class CRUDForm<T> extends React.PureComponent<CRUDFormProps<T>> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
errors: {},
|
errors: {},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import {Button, Panel, PanelHeading, PanelBlock} from 'bloomer'
|
|||||||
import {FaPlus, FaEdit, FaTimes} from 'react-icons/fa'
|
import {FaPlus, FaEdit, FaTimes} from 'react-icons/fa'
|
||||||
import {Link} from '../components'
|
import {Link} from '../components'
|
||||||
|
|
||||||
export interface ICRUDListProps<T> {
|
export interface CRUDListProps<T> {
|
||||||
nameKey: keyof T
|
nameKey: keyof T
|
||||||
editLink?: (item: T) => string
|
editLink?: (item: T) => string
|
||||||
itemIds: ReadonlyArray<number>
|
itemIds: ReadonlyArray<number>
|
||||||
@ -11,37 +11,37 @@ export interface ICRUDListProps<T> {
|
|||||||
newLink?: string
|
newLink?: string
|
||||||
onRemove?: (t: T) => void
|
onRemove?: (t: T) => void
|
||||||
title: string
|
title: string
|
||||||
Info?: React.ComponentType<ICRUDItemInfoProps<T>>
|
Info?: React.ComponentType<CRUDItemInfoProps<T>>
|
||||||
RowButtons?: React.ComponentType<ICRUDRowButtons<T>>
|
RowButtons?: React.ComponentType<CRUDRowButtons<T>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICRUDRowButtons<T> {
|
export interface CRUDRowButtons<T> {
|
||||||
item: T
|
item: T
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICRUDItemRowProps<T> {
|
export interface CRUDItemRowProps<T> {
|
||||||
Info?: React.ComponentType<ICRUDItemInfoProps<T>>
|
Info?: React.ComponentType<CRUDItemInfoProps<T>>
|
||||||
RowButtons?: React.ComponentType<ICRUDRowButtons<T>>
|
RowButtons?: React.ComponentType<CRUDRowButtons<T>>
|
||||||
nameKey: keyof T
|
nameKey: keyof T
|
||||||
editLink?: string
|
editLink?: string
|
||||||
item: T
|
item: T
|
||||||
onRemove?: (t: T) => void
|
onRemove?: (t: T) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICRUDItemInfoProps<T> {
|
export interface CRUDItemInfoProps<T> {
|
||||||
item: T
|
item: T
|
||||||
nameKey: keyof T
|
nameKey: keyof T
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CRUDItemInfo<T>
|
export class CRUDItemInfo<T>
|
||||||
extends React.PureComponent<ICRUDItemInfoProps<T>> {
|
extends React.PureComponent<CRUDItemInfoProps<T>> {
|
||||||
render() {
|
render() {
|
||||||
const {item, nameKey} = this.props
|
const {item, nameKey} = this.props
|
||||||
return <span>{item[nameKey]}</span>
|
return <span>{item[nameKey]}</span>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CRUDItemRow<T> extends React.PureComponent<ICRUDItemRowProps<T>> {
|
export class CRUDItemRow<T> extends React.PureComponent<CRUDItemRowProps<T>> {
|
||||||
handleRemove = () => {
|
handleRemove = () => {
|
||||||
const {onRemove, item} = this.props
|
const {onRemove, item} = this.props
|
||||||
if (onRemove) {
|
if (onRemove) {
|
||||||
@ -88,7 +88,7 @@ export class CRUDItemRow<T> extends React.PureComponent<ICRUDItemRowProps<T>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CRUDList<T> extends React.PureComponent<ICRUDListProps<T>> {
|
export class CRUDList<T> extends React.PureComponent<CRUDListProps<T>> {
|
||||||
render() {
|
render() {
|
||||||
const {nameKey, editLink, itemIds, itemsById, newLink, title} = this.props
|
const {nameKey, editLink, itemIds, itemsById, newLink, title} = this.props
|
||||||
|
|
||||||
|
|||||||
15
packages/client/src/crud/CRUDMethod.ts
Normal file
15
packages/client/src/crud/CRUDMethod.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export type CRUDAsyncMethod =
|
||||||
|
'save'
|
||||||
|
| 'update'
|
||||||
|
| 'findOne'
|
||||||
|
| 'findMany'
|
||||||
|
| 'remove'
|
||||||
|
|
||||||
|
export type CRUDSyncMethod =
|
||||||
|
| 'create'
|
||||||
|
| 'edit'
|
||||||
|
| 'change'
|
||||||
|
|
||||||
|
export type CRUDMethod =
|
||||||
|
CRUDAsyncMethod
|
||||||
|
| CRUDSyncMethod
|
||||||
@ -1,45 +1,44 @@
|
|||||||
import {IAction, IResolvedAction} from '@rondo.dev/redux'
|
import { indexBy, without } from '@rondo.dev/common'
|
||||||
import {TCRUDAction} from './TCRUDAction'
|
import { CRUDAction } from './CRUDAction'
|
||||||
import {TCRUDMethod} from './TCRUDMethod'
|
import { CRUDMethod } from './CRUDMethod'
|
||||||
import {indexBy, without, TFilter} from '@rondo.dev/common'
|
|
||||||
|
|
||||||
export interface ICRUDEntity {
|
export interface CRUDEntity {
|
||||||
readonly id: number
|
readonly id: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICRUDMethodStatus {
|
export interface CRUDMethodStatus {
|
||||||
readonly isLoading: boolean
|
readonly isLoading: boolean
|
||||||
readonly error: string
|
readonly error: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICRUDState<T extends ICRUDEntity> {
|
export interface CRUDState<T extends CRUDEntity> {
|
||||||
readonly ids: ReadonlyArray<number>
|
readonly ids: ReadonlyArray<number>
|
||||||
readonly byId: Record<number, T>
|
readonly byId: Record<number, T>
|
||||||
readonly status: ICRUDStatus
|
readonly status: CRUDStatus
|
||||||
readonly form: ICRUDForm<T>
|
readonly form: CRUDForm<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICRUDForm<T extends ICRUDEntity> {
|
export interface CRUDForm<T extends CRUDEntity> {
|
||||||
readonly createItem: Pick<T, Exclude<keyof T, 'id'>>,
|
readonly createItem: Pick<T, Exclude<keyof T, 'id'>>
|
||||||
readonly createErrors: Partial<Record<keyof T, string>>
|
readonly createErrors: Partial<Record<keyof T, string>>
|
||||||
|
|
||||||
readonly itemsById: Record<number, T>
|
readonly itemsById: Record<number, T>
|
||||||
readonly errorsById: Record<number, Partial<Record<keyof T, string>>>
|
readonly errorsById: Record<number, Partial<Record<keyof T, string>>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICRUDStatus {
|
export interface CRUDStatus {
|
||||||
readonly save: ICRUDMethodStatus
|
readonly save: CRUDMethodStatus
|
||||||
readonly update: ICRUDMethodStatus
|
readonly update: CRUDMethodStatus
|
||||||
readonly remove: ICRUDMethodStatus
|
readonly remove: CRUDMethodStatus
|
||||||
readonly findOne: ICRUDMethodStatus
|
readonly findOne: CRUDMethodStatus
|
||||||
readonly findMany: ICRUDMethodStatus
|
readonly findMany: CRUDMethodStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CRUDReducer<
|
export class CRUDReducer<
|
||||||
T extends ICRUDEntity,
|
T extends CRUDEntity,
|
||||||
ActionType extends string,
|
ActionType extends string,
|
||||||
> {
|
> {
|
||||||
readonly defaultState: ICRUDState<T>
|
readonly defaultState: CRUDState<T>
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly actionName: ActionType,
|
readonly actionName: ActionType,
|
||||||
@ -67,14 +66,14 @@ export class CRUDReducer<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultMethodStatus(): ICRUDMethodStatus {
|
getDefaultMethodStatus(): CRUDMethodStatus {
|
||||||
return {
|
return {
|
||||||
error: '',
|
error: '',
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getSuccessStatus(): ICRUDMethodStatus {
|
protected getSuccessStatus(): CRUDMethodStatus {
|
||||||
return {
|
return {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: '',
|
error: '',
|
||||||
@ -82,10 +81,10 @@ export class CRUDReducer<
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleRejected = (
|
handleRejected = (
|
||||||
state: ICRUDState<T>,
|
state: CRUDState<T>,
|
||||||
method: TCRUDMethod,
|
method: CRUDMethod,
|
||||||
error: Error,
|
error: Error,
|
||||||
): ICRUDState<T> => {
|
): CRUDState<T> => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
status: {
|
status: {
|
||||||
@ -99,9 +98,9 @@ export class CRUDReducer<
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleLoading = (
|
handleLoading = (
|
||||||
state: ICRUDState<T>,
|
state: CRUDState<T>,
|
||||||
method: TCRUDMethod,
|
method: CRUDMethod,
|
||||||
): ICRUDState<T> => {
|
): CRUDState<T> => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
status: {
|
status: {
|
||||||
@ -114,7 +113,7 @@ export class CRUDReducer<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFindOne = (state: ICRUDState<T>, payload: T): ICRUDState<T> => {
|
handleFindOne = (state: CRUDState<T>, payload: T): CRUDState<T> => {
|
||||||
const ids = !state.byId[payload.id]
|
const ids = !state.byId[payload.id]
|
||||||
? [...state.ids, payload.id]
|
? [...state.ids, payload.id]
|
||||||
: state.ids
|
: state.ids
|
||||||
@ -132,7 +131,7 @@ export class CRUDReducer<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSave = (state: ICRUDState<T>, payload: T): ICRUDState<T> => {
|
handleSave = (state: CRUDState<T>, payload: T): CRUDState<T> => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
ids: [...state.ids, payload.id],
|
ids: [...state.ids, payload.id],
|
||||||
@ -147,7 +146,7 @@ export class CRUDReducer<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpdate = (state: ICRUDState<T>, payload: T): ICRUDState<T> => {
|
handleUpdate = (state: CRUDState<T>, payload: T): CRUDState<T> => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
byId: {
|
byId: {
|
||||||
@ -160,7 +159,7 @@ export class CRUDReducer<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRemove = (state: ICRUDState<T>, payload: T): ICRUDState<T> => {
|
handleRemove = (state: CRUDState<T>, payload: T): CRUDState<T> => {
|
||||||
// FIXME site does not get removed because payload looks different!
|
// FIXME site does not get removed because payload looks different!
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -173,11 +172,11 @@ export class CRUDReducer<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFindMany = (state: ICRUDState<T>, payload: T[]): ICRUDState<T> => {
|
handleFindMany = (state: CRUDState<T>, payload: T[]): CRUDState<T> => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
ids: payload.map(item => item.id),
|
ids: payload.map(item => item.id),
|
||||||
byId: indexBy(payload, 'id' as any),
|
byId: indexBy(payload, 'id' as any), // eslint-disable-line
|
||||||
status: {
|
status: {
|
||||||
...state.status,
|
...state.status,
|
||||||
findMany: this.getSuccessStatus(),
|
findMany: this.getSuccessStatus(),
|
||||||
@ -185,7 +184,7 @@ export class CRUDReducer<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCreate = (state: ICRUDState<T>, payload: Partial<T>): ICRUDState<T> => {
|
handleCreate = (state: CRUDState<T>, payload: Partial<T>): CRUDState<T> => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
form: {
|
form: {
|
||||||
@ -199,7 +198,7 @@ export class CRUDReducer<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEdit = (state: ICRUDState<T>, id: number): ICRUDState<T> => {
|
handleEdit = (state: CRUDState<T>, id: number): CRUDState<T> => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
form: {
|
form: {
|
||||||
@ -216,11 +215,11 @@ export class CRUDReducer<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange = (state: ICRUDState<T>, payload: {
|
handleChange = (state: CRUDState<T>, payload: {
|
||||||
id?: number,
|
id?: number
|
||||||
key: keyof T,
|
key: keyof T
|
||||||
value: string,
|
value: string
|
||||||
}): ICRUDState<T> => {
|
}): CRUDState<T> => {
|
||||||
const {id, key, value} = payload
|
const {id, key, value} = payload
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
@ -252,9 +251,9 @@ export class CRUDReducer<
|
|||||||
}
|
}
|
||||||
|
|
||||||
reduce = (
|
reduce = (
|
||||||
state: ICRUDState<T> | undefined,
|
state: CRUDState<T> | undefined,
|
||||||
action: TCRUDAction<T, ActionType>,
|
action: CRUDAction<T, ActionType>,
|
||||||
): ICRUDState<T> => {
|
): CRUDState<T> => {
|
||||||
const {defaultState} = this
|
const {defaultState} = this
|
||||||
state = state || defaultState
|
state = state || defaultState
|
||||||
|
|
||||||
@ -293,6 +292,7 @@ export class CRUDReducer<
|
|||||||
case 'findMany':
|
case 'findMany':
|
||||||
return this.handleFindMany(state, action.payload)
|
return this.handleFindMany(state, action.payload)
|
||||||
}
|
}
|
||||||
|
return state
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,41 +0,0 @@
|
|||||||
import {IAction, TAsyncAction} from '@rondo.dev/redux'
|
|
||||||
import {TCRUDMethod} from './TCRUDMethod'
|
|
||||||
|
|
||||||
// Async actions
|
|
||||||
|
|
||||||
export type TCRUDSaveAction<T, ActionType extends string> =
|
|
||||||
TAsyncAction<T, ActionType> & {method: Extract<TCRUDMethod, 'save'>}
|
|
||||||
|
|
||||||
export type TCRUDUpdateAction<T, ActionType extends string> =
|
|
||||||
TAsyncAction<T, ActionType> & {method: Extract<TCRUDMethod, 'update'>}
|
|
||||||
|
|
||||||
export type TCRUDRemoveAction<T, ActionType extends string> =
|
|
||||||
TAsyncAction<T, ActionType> & {method: Extract<TCRUDMethod, 'remove'>}
|
|
||||||
|
|
||||||
export type TCRUDFindOneAction<T, ActionType extends string> =
|
|
||||||
TAsyncAction<T, ActionType> & {method: Extract<TCRUDMethod, 'findOne'>}
|
|
||||||
|
|
||||||
export type TCRUDFindManyAction<T, ActionType extends string> =
|
|
||||||
TAsyncAction<T[], ActionType> & {method: Extract<TCRUDMethod, 'findMany'>}
|
|
||||||
|
|
||||||
// Synchronous actions
|
|
||||||
|
|
||||||
export type TCRUDCreateAction<T, ActionType extends string> =
|
|
||||||
IAction<Partial<T>, ActionType> & {method: Extract<TCRUDMethod, 'create'>}
|
|
||||||
|
|
||||||
export type TCRUDEditAction<ActionType extends string> =
|
|
||||||
IAction<{id: number}, ActionType> & {method: Extract<TCRUDMethod, 'edit'>}
|
|
||||||
|
|
||||||
export type TCRUDChangeAction<T, ActionType extends string> =
|
|
||||||
IAction<{id?: number, key: keyof T, value: string}, ActionType>
|
|
||||||
& {method: Extract<TCRUDMethod, 'change'>}
|
|
||||||
|
|
||||||
export type TCRUDAction<T, ActionType extends string> =
|
|
||||||
TCRUDSaveAction<T, ActionType>
|
|
||||||
| TCRUDUpdateAction<T, ActionType>
|
|
||||||
| TCRUDRemoveAction<T, ActionType>
|
|
||||||
| TCRUDFindOneAction<T, ActionType>
|
|
||||||
| TCRUDFindManyAction<T, ActionType>
|
|
||||||
| TCRUDCreateAction<T, ActionType>
|
|
||||||
| TCRUDEditAction<ActionType>
|
|
||||||
| TCRUDChangeAction<T, ActionType>
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
export type TCRUDAsyncMethod =
|
|
||||||
'save'
|
|
||||||
| 'update'
|
|
||||||
| 'findOne'
|
|
||||||
| 'findMany'
|
|
||||||
| 'remove'
|
|
||||||
|
|
||||||
export type TCRUDSyncMethod =
|
|
||||||
| 'create'
|
|
||||||
| 'edit'
|
|
||||||
| 'change'
|
|
||||||
|
|
||||||
export type TCRUDMethod =
|
|
||||||
TCRUDAsyncMethod
|
|
||||||
| TCRUDSyncMethod
|
|
||||||
@ -2,5 +2,5 @@ export * from './CRUDActions'
|
|||||||
export * from './CRUDForm'
|
export * from './CRUDForm'
|
||||||
export * from './CRUDList'
|
export * from './CRUDList'
|
||||||
export * from './CRUDReducer'
|
export * from './CRUDReducer'
|
||||||
export * from './TCRUDAction'
|
export * from './CRUDAction'
|
||||||
export * from './TCRUDMethod'
|
export * from './CRUDMethod'
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Breadcrumb, BreadcrumbItem} from 'bloomer'
|
import {Breadcrumb, BreadcrumbItem} from 'bloomer'
|
||||||
import {Link} from 'react-router-dom'
|
import {Link} from 'react-router-dom'
|
||||||
import {ICrumbLink} from './ICrumbLink'
|
import {CrumbLink} from './CrumbLink'
|
||||||
|
|
||||||
export interface ICrumbProps {
|
export interface CrumbProps {
|
||||||
links: ICrumbLink[]
|
links: CrumbLink[]
|
||||||
current: string
|
current: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Crumb extends React.PureComponent<ICrumbProps> {
|
export class Crumb extends React.PureComponent<CrumbProps> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
export interface ICrumbLink {
|
export interface CrumbLink {
|
||||||
name: string
|
name: string
|
||||||
to: string
|
to: string
|
||||||
}
|
}
|
||||||
@ -1,18 +1,15 @@
|
|||||||
import {TGetAction, IAction} from '@rondo.dev/redux'
|
import {GetAllActions, Action} from '@rondo.dev/redux'
|
||||||
import {ICrumbLink} from './ICrumbLink'
|
import {CrumbLink} from './CrumbLink'
|
||||||
|
|
||||||
export interface ICrumbs {
|
export interface Crumbs {
|
||||||
links: ICrumbLink[]
|
links: CrumbLink[]
|
||||||
current: string
|
current: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TCrumbsAction =
|
export type CrumbsAction = GetAllActions<CrumbsActions>
|
||||||
IAction<ICrumbs, 'BREADCRUMBS_SET'>
|
|
||||||
|
|
||||||
type Action<T extends string> = TGetAction<TCrumbsAction, T>
|
|
||||||
|
|
||||||
export class CrumbsActions {
|
export class CrumbsActions {
|
||||||
setCrumbs(breadcrumbs: ICrumbs): Action<'BREADCRUMBS_SET'> {
|
setCrumbs(breadcrumbs: Crumbs): Action<Crumbs, 'BREADCRUMBS_SET'> {
|
||||||
return {
|
return {
|
||||||
payload: breadcrumbs,
|
payload: breadcrumbs,
|
||||||
type: 'BREADCRUMBS_SET',
|
type: 'BREADCRUMBS_SET',
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
import {ICrumbs, TCrumbsAction} from './CrumbsActions'
|
import {Crumbs, CrumbsAction} from './CrumbsActions'
|
||||||
|
|
||||||
export interface ICrumbsState extends ICrumbs {
|
export interface CrumbsState extends Crumbs {
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultState: ICrumbsState = {
|
const defaultState: CrumbsState = {
|
||||||
links: [],
|
links: [],
|
||||||
current: 'Home',
|
current: 'Home',
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Crumbs(state = defaultState, action: TCrumbsAction)
|
export function Crumbs(
|
||||||
: ICrumbsState {
|
state = defaultState,
|
||||||
|
action: CrumbsAction,
|
||||||
|
): CrumbsState {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'BREADCRUMBS_SET':
|
case 'BREADCRUMBS_SET':
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,32 +1,32 @@
|
|||||||
|
import { History } from 'history'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Crumb} from './Crumb'
|
import { match as Match, matchPath } from 'react-router'
|
||||||
import {History, Location} from 'history'
|
|
||||||
import {ICrumbLink} from './ICrumbLink'
|
|
||||||
import {match as Match, matchPath, withRouter} from 'react-router'
|
|
||||||
import { withHistory } from '../components'
|
import { withHistory } from '../components'
|
||||||
|
import { Crumb } from './Crumb'
|
||||||
|
import { CrumbLink } from './CrumbLink'
|
||||||
|
|
||||||
export interface ICrumbsRoute {
|
export interface CrumbsRoute {
|
||||||
exact?: boolean
|
exact?: boolean
|
||||||
links: ICrumbLink[]
|
links: CrumbLink[]
|
||||||
current: string
|
current: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IHistoryCrumbsProps {
|
export interface HistoryCrumbsProps {
|
||||||
history: History
|
history: History
|
||||||
routes: Record<string, ICrumbsRoute>
|
routes: Record<string, CrumbsRoute>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IHistoryCrumbsState {
|
export interface HistoryCrumbsState {
|
||||||
links: ICrumbLink[]
|
links: CrumbLink[]
|
||||||
current: string
|
current: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HistoryCrumbs = withHistory(
|
export const HistoryCrumbs = withHistory(
|
||||||
class InnerHistoryCrumbs
|
class InnerHistoryCrumbs
|
||||||
extends React.PureComponent<IHistoryCrumbsProps, IHistoryCrumbsState> {
|
extends React.PureComponent<HistoryCrumbsProps, HistoryCrumbsState> {
|
||||||
unlisten!: () => void
|
unlisten!: () => void
|
||||||
|
|
||||||
constructor(props: IHistoryCrumbsProps) {
|
constructor(props: HistoryCrumbsProps) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
links: [],
|
links: [],
|
||||||
@ -48,7 +48,7 @@ export const HistoryCrumbs = withHistory(
|
|||||||
handleChange(path: string) {
|
handleChange(path: string) {
|
||||||
const {routes} = this.props
|
const {routes} = this.props
|
||||||
|
|
||||||
let found: null | {match: Match<{}>, route: ICrumbsRoute} = null
|
let found: null | {match: Match<{}>, route: CrumbsRoute} = null
|
||||||
|
|
||||||
Object.keys(routes).some(route => {
|
Object.keys(routes).some(route => {
|
||||||
const match = matchPath(path, {
|
const match = matchPath(path, {
|
||||||
|
|||||||
@ -3,4 +3,4 @@ export * from './Crumb'
|
|||||||
export * from './CrumbsActions'
|
export * from './CrumbsActions'
|
||||||
export * from './CrumbsReducer'
|
export * from './CrumbsReducer'
|
||||||
export * from './HistoryCrumbs'
|
export * from './HistoryCrumbs'
|
||||||
export * from './ICrumbLink'
|
export * from './CrumbLink'
|
||||||
|
|||||||
@ -1,17 +1,18 @@
|
|||||||
import {TGetAction, TAsyncAction, IAction, PendingAction} from '@rondo.dev/redux'
|
import { APIDef, Credentials, NewUser, UserProfile } from '@rondo.dev/common'
|
||||||
import {IAPIDef, ICredentials, INewUser, IUser} from '@rondo.dev/common'
|
import { HTTPClient } from '@rondo.dev/http-client'
|
||||||
import {IHTTPClient} from '@rondo.dev/http-client'
|
import { Action, AsyncAction, createPendingAction, TGetAction } from '@rondo.dev/redux'
|
||||||
|
|
||||||
export type TLoginAction =
|
export type TLoginAction =
|
||||||
TAsyncAction<IUser, 'LOGIN'>
|
AsyncAction<UserProfile, 'LOGIN'>
|
||||||
| TAsyncAction<unknown, 'LOGIN_LOGOUT'>
|
| AsyncAction<unknown, 'LOGIN_LOGOUT'>
|
||||||
| TAsyncAction<IUser, 'LOGIN_REGISTER'>
|
| AsyncAction<UserProfile, 'LOGIN_REGISTER'>
|
||||||
| IAction<{redirectTo: string}, 'LOGIN_REDIRECT_SET'>
|
| Action<{redirectTo: string}, 'LOGIN_REDIRECT_SET'>
|
||||||
|
|
||||||
type TAction<T extends string> = TGetAction<TLoginAction, T>
|
type TAction<T extends string> = TGetAction<TLoginAction, T>
|
||||||
|
|
||||||
export const setRedirectTo = (redirectTo: string)
|
export const setRedirectTo = (
|
||||||
: TAction<'LOGIN_REDIRECT_SET'> => {
|
redirectTo: string,
|
||||||
|
): Action<{redirectTo: string}, 'LOGIN_REDIRECT_SET'> => {
|
||||||
return {
|
return {
|
||||||
payload: {redirectTo},
|
payload: {redirectTo},
|
||||||
type: 'LOGIN_REDIRECT_SET',
|
type: 'LOGIN_REDIRECT_SET',
|
||||||
@ -19,24 +20,24 @@ export const setRedirectTo = (redirectTo: string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class LoginActions {
|
export class LoginActions {
|
||||||
constructor(protected readonly http: IHTTPClient<IAPIDef>) {}
|
constructor(protected readonly http: HTTPClient<APIDef>) {}
|
||||||
|
|
||||||
logIn = (credentials: ICredentials) => {
|
logIn = (credentials: Credentials) => {
|
||||||
return new PendingAction(
|
return createPendingAction(
|
||||||
this.http.post('/auth/login', credentials),
|
this.http.post('/auth/login', credentials),
|
||||||
'LOGIN',
|
'LOGIN',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
logOut = () => {
|
logOut = () => {
|
||||||
return new PendingAction(
|
return createPendingAction(
|
||||||
this.http.get('/auth/logout'),
|
this.http.get('/auth/logout'),
|
||||||
'LOGIN_LOGOUT',
|
'LOGIN_LOGOUT',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
register = (profile: INewUser) => {
|
register = (profile: NewUser) => {
|
||||||
return new PendingAction(
|
return createPendingAction(
|
||||||
this.http.post('/auth/register', profile),
|
this.http.post('/auth/register', profile),
|
||||||
'LOGIN_REGISTER',
|
'LOGIN_REGISTER',
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,22 +1,22 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {FaUser, FaLock} from 'react-icons/fa'
|
import {FaUser, FaLock} from 'react-icons/fa'
|
||||||
import {ICredentials, IUser} from '@rondo.dev/common'
|
import {Credentials, UserProfile} from '@rondo.dev/common'
|
||||||
import {Input} from '../components/Input'
|
import {Input} from '../components/Input'
|
||||||
import {Link} from 'react-router-dom'
|
import {Link} from 'react-router-dom'
|
||||||
import {Redirect} from '../components/Redirect'
|
import {Redirect} from '../components/Redirect'
|
||||||
|
|
||||||
export interface ILoginFormProps {
|
export interface LoginFormProps {
|
||||||
error?: string
|
error?: string
|
||||||
onSubmit: () => void
|
onSubmit: () => void
|
||||||
onChange: (name: string, value: string) => void
|
onChange: (name: string, value: string) => void
|
||||||
data: ICredentials
|
data: Credentials
|
||||||
user?: IUser
|
user?: UserProfile
|
||||||
redirectTo: string
|
redirectTo: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO maybe replace this with Formik, which is recommended in React docs
|
// TODO maybe replace this with Formik, which is recommended in React docs
|
||||||
// https://jaredpalmer.com/formik/docs/overview
|
// https://jaredpalmer.com/formik/docs/overview
|
||||||
export class LoginForm extends React.PureComponent<ILoginFormProps> {
|
export class LoginForm extends React.PureComponent<LoginFormProps> {
|
||||||
render() {
|
render() {
|
||||||
if (this.props.user) {
|
if (this.props.user) {
|
||||||
return <Redirect to={this.props.redirectTo} />
|
return <Redirect to={this.props.redirectTo} />
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import {IUser} from '@rondo.dev/common'
|
import {UserProfile} from '@rondo.dev/common'
|
||||||
import {TLoginAction} from './LoginActions'
|
import {TLoginAction} from './LoginActions'
|
||||||
|
|
||||||
export interface ILoginState {
|
export interface LoginState {
|
||||||
readonly error: string
|
readonly error: string
|
||||||
readonly isLoading: boolean
|
readonly isLoading: boolean
|
||||||
readonly user?: IUser
|
readonly user?: UserProfile
|
||||||
readonly redirectTo: string
|
readonly redirectTo: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultState: ILoginState = {
|
const defaultState: LoginState = {
|
||||||
error: '',
|
error: '',
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
user: undefined,
|
user: undefined,
|
||||||
@ -18,7 +18,7 @@ const defaultState: ILoginState = {
|
|||||||
export function Login(
|
export function Login(
|
||||||
state = defaultState,
|
state = defaultState,
|
||||||
action: TLoginAction,
|
action: TLoginAction,
|
||||||
): ILoginState {
|
): LoginState {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
// sync actions
|
// sync actions
|
||||||
case 'LOGIN_REDIRECT_SET':
|
case 'LOGIN_REDIRECT_SET':
|
||||||
|
|||||||
@ -1,20 +1,20 @@
|
|||||||
|
import { NewUser, UserProfile } from '@rondo.dev/common'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {FaEnvelope, FaUser, FaLock} from 'react-icons/fa'
|
import { FaEnvelope, FaLock, FaUser } from 'react-icons/fa'
|
||||||
import {INewUser, IUser} from '@rondo.dev/common'
|
|
||||||
import {Input} from '../components/Input'
|
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Input } from '../components/Input'
|
||||||
import { Redirect } from '../components/Redirect'
|
import { Redirect } from '../components/Redirect'
|
||||||
|
|
||||||
export interface IRegisterFormProps {
|
export interface RegisterFormProps {
|
||||||
error?: string
|
error?: string
|
||||||
onSubmit: () => void
|
onSubmit: () => void
|
||||||
onChange: (name: string, value: string) => void
|
onChange: (name: string, value: string) => void
|
||||||
data: INewUser
|
data: NewUser
|
||||||
user?: IUser
|
user?: UserProfile
|
||||||
redirectTo: string
|
redirectTo: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RegisterForm extends React.PureComponent<IRegisterFormProps> {
|
export class RegisterForm extends React.PureComponent<RegisterFormProps> {
|
||||||
render() {
|
render() {
|
||||||
if (this.props.user) {
|
if (this.props.user) {
|
||||||
return <Redirect to={this.props.redirectTo} />
|
return <Redirect to={this.props.redirectTo} />
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { IAPIDef } from '@rondo.dev/common'
|
import { APIDef } from '@rondo.dev/common'
|
||||||
import { HTTPClientMock } from '@rondo.dev/http-client'
|
import { HTTPClientMock } from '@rondo.dev/http-client'
|
||||||
import { getError } from '@rondo.dev/test-utils'
|
import { getError } from '@rondo.dev/test-utils'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import T from 'react-dom/test-utils'
|
import T from 'react-dom/test-utils'
|
||||||
import { MemoryRouter } from 'react-router-dom'
|
import { MemoryRouter } from 'react-router-dom'
|
||||||
import { TestUtils } from '../test-utils'
|
import { TestContainer, TestUtils } from '../test-utils'
|
||||||
import * as Feature from './'
|
import * as Feature from './'
|
||||||
import { configureLogin } from './configureLogin'
|
import { configureLogin } from './configureLogin'
|
||||||
|
|
||||||
@ -13,7 +12,7 @@ const test = new TestUtils()
|
|||||||
|
|
||||||
describe('configureLogin', () => {
|
describe('configureLogin', () => {
|
||||||
|
|
||||||
const http = new HTTPClientMock<IAPIDef>()
|
const http = new HTTPClientMock<APIDef>()
|
||||||
const loginActions = new Feature.LoginActions(http)
|
const loginActions = new Feature.LoginActions(http)
|
||||||
|
|
||||||
const createTestProvider = () => test.withProvider({
|
const createTestProvider = () => test.withProvider({
|
||||||
@ -40,7 +39,7 @@ describe('configureLogin', () => {
|
|||||||
const data = {username: 'user', password: 'pass'}
|
const data = {username: 'user', password: 'pass'}
|
||||||
const onSuccess = jest.fn()
|
const onSuccess = jest.fn()
|
||||||
let node: Element
|
let node: Element
|
||||||
let component: React.Component
|
let component: TestContainer
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
http.mockAdd({
|
http.mockAdd({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@ -72,8 +71,7 @@ describe('configureLogin', () => {
|
|||||||
})
|
})
|
||||||
expect(onSuccess.mock.calls.length).toBe(1)
|
expect(onSuccess.mock.calls.length).toBe(1)
|
||||||
// TODO test clear username/password
|
// TODO test clear username/password
|
||||||
node = ReactDOM.findDOMNode(component) as Element
|
expect(component.ref.current!.innerHTML).toMatch(/<a href="\/">/)
|
||||||
expect(node.innerHTML).toMatch(/<a href="\/">/)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sets the error message on failure', async () => {
|
it('sets the error message on failure', async () => {
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import { ICredentials } from '@rondo.dev/common'
|
import { Credentials } from '@rondo.dev/common'
|
||||||
import { TStateSelector, pack } from '@rondo.dev/redux'
|
import { pack, TStateSelector } from '@rondo.dev/redux'
|
||||||
import { bindActionCreators } from 'redux'
|
import { bindActionCreators } from 'redux'
|
||||||
import { Connector } from '../redux/Connector'
|
|
||||||
import { LoginActions } from './LoginActions'
|
import { LoginActions } from './LoginActions'
|
||||||
import { LoginForm } from './LoginForm'
|
import { LoginForm } from './LoginForm'
|
||||||
import { ILoginState } from './LoginReducer'
|
import { ILoginState } from './LoginReducer'
|
||||||
import { withForm } from './withForm'
|
import { withForm } from './withForm'
|
||||||
|
|
||||||
const defaultCredentials: ICredentials = {
|
const defaultCredentials: Credentials = {
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import {Action} from './Action'
|
import {Action} from './Action'
|
||||||
|
|
||||||
export type TGetAction<ActionTypes, T extends string> =
|
export type GetAction<ActionTypes, T extends string> =
|
||||||
ActionTypes extends Action<infer U, T>
|
ActionTypes extends Action<infer U, T>
|
||||||
? ActionTypes
|
? ActionTypes
|
||||||
: never
|
: never
|
||||||
|
|||||||
5
packages/redux/src/actions/GetAllActions.ts
Normal file
5
packages/redux/src/actions/GetAllActions.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export type GetAllActions<T> = {
|
||||||
|
[K in keyof T]: T[K] extends (...args: any[]) => infer R
|
||||||
|
? R
|
||||||
|
: never
|
||||||
|
}[keyof T]
|
||||||
@ -1,9 +1,10 @@
|
|||||||
export * from './Action'
|
export * from './Action'
|
||||||
export * from './createPendingAction'
|
|
||||||
export * from './RejectedAction'
|
|
||||||
export * from './ResolvedAction'
|
|
||||||
export * from './PendingAction'
|
|
||||||
export * from './AsyncAction'
|
export * from './AsyncAction'
|
||||||
|
export * from './createPendingAction'
|
||||||
export * from './GetAction'
|
export * from './GetAction'
|
||||||
|
export * from './GetAllActions'
|
||||||
export * from './GetPendingAction'
|
export * from './GetPendingAction'
|
||||||
export * from './GetResolvedAction'
|
export * from './GetResolvedAction'
|
||||||
|
export * from './PendingAction'
|
||||||
|
export * from './RejectedAction'
|
||||||
|
export * from './ResolvedAction'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user