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