Partiall upgrade packages/client
This commit is contained in:
parent
fea568b58a
commit
30d1f1fcd4
@ -29,6 +29,11 @@ rules:
|
|||||||
delimiter: none
|
delimiter: none
|
||||||
singleline:
|
singleline:
|
||||||
delimiter: comma
|
delimiter: comma
|
||||||
|
'@typescript-eslint/no-unused-vars':
|
||||||
|
- warn
|
||||||
|
- vars: all
|
||||||
|
args: none
|
||||||
|
ignoreRestSiblings: true
|
||||||
'@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
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
import { TStateSelector } from '@rondo.dev/redux'
|
|
||||||
import { Connector } from '../redux'
|
|
||||||
import { Crumb } from './Crumb'
|
|
||||||
import { CrumbsActions } from './CrumbsActions'
|
|
||||||
import { ICrumbsState } from './CrumbsReducer'
|
|
||||||
|
|
||||||
export class CrumbsConnector extends Connector<ICrumbsState> {
|
|
||||||
protected readonly breadcrumbsActions = new CrumbsActions()
|
|
||||||
|
|
||||||
connect<State>(getLocalState: TStateSelector<State, ICrumbsState>) {
|
|
||||||
return this.wrap(
|
|
||||||
getLocalState,
|
|
||||||
state => state,
|
|
||||||
dispatch => ({}),
|
|
||||||
Crumb,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -5,7 +5,7 @@ import {TestUtils} from '../test-utils'
|
|||||||
|
|
||||||
const t = new TestUtils()
|
const t = new TestUtils()
|
||||||
|
|
||||||
describe('CrumbsConnector', () => {
|
describe('configrueCrumbs', () => {
|
||||||
|
|
||||||
const createTestCase = () => t.withProvider({
|
const createTestCase = () => t.withProvider({
|
||||||
reducers: {Crumbs: Feature.Crumbs},
|
reducers: {Crumbs: Feature.Crumbs},
|
||||||
@ -23,7 +23,7 @@ describe('CrumbsConnector', () => {
|
|||||||
current: 'Three',
|
current: 'Three',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.withComponent(select => new Feature.CrumbsConnector().connect(select))
|
.withComponent(select => Feature.configureCrumbs(select))
|
||||||
.withJSX(Component => <MemoryRouter><Component /></MemoryRouter>)
|
.withJSX(Component => <MemoryRouter><Component /></MemoryRouter>)
|
||||||
|
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
14
packages/client/src/crumbs/configureCrumbs.tsx
Normal file
14
packages/client/src/crumbs/configureCrumbs.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { pack, TStateSelector } from '@rondo.dev/redux'
|
||||||
|
import { Crumb } from './Crumb'
|
||||||
|
import { ICrumbsState } from './CrumbsReducer'
|
||||||
|
|
||||||
|
export function configureCrumbs<State>(
|
||||||
|
getLocalState: TStateSelector<State, ICrumbsState>,
|
||||||
|
) {
|
||||||
|
return pack(
|
||||||
|
getLocalState,
|
||||||
|
state => state,
|
||||||
|
dispatch => ({}),
|
||||||
|
Crumb,
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
|
export * from './configureCrumbs'
|
||||||
export * from './Crumb'
|
export * from './Crumb'
|
||||||
export * from './CrumbsActions'
|
export * from './CrumbsActions'
|
||||||
export * from './CrumbsConnector'
|
|
||||||
export * from './CrumbsReducer'
|
export * from './CrumbsReducer'
|
||||||
export * from './HistoryCrumbs'
|
export * from './HistoryCrumbs'
|
||||||
export * from './ICrumbLink'
|
export * from './ICrumbLink'
|
||||||
|
|||||||
@ -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 { TestUtils, TestContainer } from '../test-utils'
|
||||||
import * as Feature from './'
|
import * as Feature from './'
|
||||||
import { configureRegister } from './configureRegister'
|
import { configureRegister } from './configureRegister'
|
||||||
|
|
||||||
@ -13,7 +12,7 @@ const test = new TestUtils()
|
|||||||
|
|
||||||
describe('configureRegister', () => {
|
describe('configureRegister', () => {
|
||||||
|
|
||||||
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({
|
||||||
@ -45,7 +44,7 @@ describe('configureRegister', () => {
|
|||||||
}
|
}
|
||||||
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',
|
||||||
@ -85,8 +84,7 @@ describe('configureRegister', () => {
|
|||||||
.value,
|
.value,
|
||||||
)
|
)
|
||||||
.toEqual('')
|
.toEqual('')
|
||||||
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,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {IPendingAction} from '@rondo.dev/redux'
|
import {PendingAction} from '@rondo.dev/redux'
|
||||||
|
|
||||||
export interface IComponentProps<Data> {
|
export interface ComponentProps<Data> {
|
||||||
onSubmit: () => void
|
onSubmit: () => void
|
||||||
onChange: (name: string, value: string) => void
|
onChange: (name: string, value: string) => void
|
||||||
// TODO clear data on successful submission. This is important to prevent
|
// TODO clear data on successful submission. This is important to prevent
|
||||||
@ -9,29 +9,29 @@ export interface IComponentProps<Data> {
|
|||||||
data: Data
|
data: Data
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFormHOCProps<Data> {
|
export interface FormHOCProps<Data> {
|
||||||
onSubmit: (props: Data) => IPendingAction<any, any>
|
onSubmit: (props: Data) => PendingAction<unknown, string>
|
||||||
// TODO figure out what would happen if the underlying child component
|
// TODO figure out what would happen if the underlying child component
|
||||||
// would have the same required property as the HOC, like onSuccess?
|
// would have the same required property as the HOC, like onSuccess?
|
||||||
onSuccess?: () => void
|
onSuccess?: () => void
|
||||||
clearOnSuccess?: boolean
|
clearOnSuccess?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFormHOCState<Data> {
|
export interface FormHOCState<Data> {
|
||||||
error: string
|
error: string
|
||||||
data: Data
|
data: Data
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withForm<Data, Props extends IComponentProps<Data>>(
|
export function withForm<Data, Props extends ComponentProps<Data>>(
|
||||||
Component: React.ComponentType<Props>,
|
Component: React.ComponentType<Props>,
|
||||||
initialState: Data,
|
initialState: Data,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
type OtherProps = Pick<Props,
|
type OtherProps = Pick<Props,
|
||||||
Exclude<keyof Props, keyof IComponentProps<Data>>>
|
Exclude<keyof Props, keyof ComponentProps<Data>>>
|
||||||
type T = IFormHOCProps<Data> & OtherProps
|
type T = FormHOCProps<Data> & OtherProps
|
||||||
|
|
||||||
return class FormHOC extends React.PureComponent<T, IFormHOCState<Data>> {
|
return class FormHOC extends React.PureComponent<T, FormHOCState<Data>> {
|
||||||
constructor(props: T) {
|
constructor(props: T) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
@ -78,7 +78,9 @@ export function withForm<Data, Props extends IComponentProps<Data>>(
|
|||||||
// https://github.com/Microsoft/TypeScript/issues/28938
|
// https://github.com/Microsoft/TypeScript/issues/28938
|
||||||
return (
|
return (
|
||||||
<Component
|
<Component
|
||||||
{...otherProps as any}
|
{
|
||||||
|
...otherProps as any // eslint-disable-line
|
||||||
|
}
|
||||||
data={this.state}
|
data={this.state}
|
||||||
onSubmit={this.handleSubmit}
|
onSubmit={this.handleSubmit}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
|
|||||||
@ -1,58 +0,0 @@
|
|||||||
import {TStateSelector} from '@rondo.dev/redux'
|
|
||||||
import {connect, Omit} from 'react-redux'
|
|
||||||
import {Dispatch} from 'redux'
|
|
||||||
import {ComponentType} from 'react'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helps isolate the component along with actions and reducer from the global
|
|
||||||
* application state.
|
|
||||||
*
|
|
||||||
* The connect() function requires a state selector, which can be used to
|
|
||||||
* select a slice of the current state to the component, which will be passed
|
|
||||||
* on to `mapStateToProps()`.
|
|
||||||
*
|
|
||||||
* Classes that extend Connector can provide dependencies in the constructor
|
|
||||||
* and then bind them to dispatch in mapDispatchToProps. This is useful to
|
|
||||||
* build components which do not depend on a "global" singletons. For example,
|
|
||||||
* the Actions class might depend on the HTTPClient class, and then it becomes
|
|
||||||
* easy to mock it during tests, or swap out different dependencies for
|
|
||||||
* different applications.
|
|
||||||
*/
|
|
||||||
export abstract class Connector<LocalState> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects a component using redux. The `selectState` method is used to
|
|
||||||
* select a subset of state to map to the component.
|
|
||||||
*
|
|
||||||
* It returns a component with `any` props. Ideally this could be changed to
|
|
||||||
* required props.
|
|
||||||
*
|
|
||||||
* https://stackoverflow.com/questions/54277411
|
|
||||||
*/
|
|
||||||
abstract connect<State>(
|
|
||||||
selectState: TStateSelector<State, LocalState>,
|
|
||||||
): ComponentType<any>
|
|
||||||
|
|
||||||
protected wrap<
|
|
||||||
State,
|
|
||||||
Props,
|
|
||||||
StateProps extends Partial<Props>,
|
|
||||||
DispatchProps extends Partial<Props>,
|
|
||||||
>(
|
|
||||||
getLocalState: TStateSelector<State, LocalState>,
|
|
||||||
mapStateToProps: (state: LocalState) => StateProps,
|
|
||||||
mapDispatchToProps: (dispatch: Dispatch) => DispatchProps,
|
|
||||||
Component: React.ComponentType<Props>,
|
|
||||||
): ComponentType<
|
|
||||||
Omit<Props, keyof Props & (keyof StateProps | keyof DispatchProps)>
|
|
||||||
> {
|
|
||||||
|
|
||||||
return connect(
|
|
||||||
(state: State) => {
|
|
||||||
const l = getLocalState(state)
|
|
||||||
return mapStateToProps(l)
|
|
||||||
},
|
|
||||||
mapDispatchToProps,
|
|
||||||
)(Component as any) as any
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from './Connector'
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
export interface IClientConfig {
|
export interface ClientConfig {
|
||||||
readonly appName: string
|
readonly appName: string
|
||||||
readonly baseUrl: string
|
readonly baseUrl: string
|
||||||
readonly csrfToken: string
|
readonly csrfToken: string
|
||||||
@ -1,30 +1,27 @@
|
|||||||
|
import { createBrowserHistory } from 'history'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import {Action} from 'redux'
|
import { Provider } from 'react-redux'
|
||||||
import {IAPIDef} from '@rondo.dev/common'
|
import { Router } from 'react-router-dom'
|
||||||
import {IClientConfig} from './IClientConfig'
|
import { Store } from 'redux'
|
||||||
import {IHTTPClient, HTTPClient} from '@rondo.dev/http-client'
|
import { ClientConfig } from './ClientConfig'
|
||||||
import {IRenderer} from './IRenderer'
|
import { Renderer } from './Renderer'
|
||||||
import {Provider} from 'react-redux'
|
|
||||||
import {Router} from 'react-router-dom'
|
|
||||||
import {Store} from 'redux'
|
|
||||||
import {createBrowserHistory} from 'history'
|
|
||||||
|
|
||||||
export interface IClientRendererParams<Props> {
|
export interface ClientRendererParams<Props> {
|
||||||
readonly RootComponent: React.ComponentType<Props>
|
readonly RootComponent: React.ComponentType<Props>
|
||||||
readonly target?: HTMLElement
|
readonly target?: HTMLElement
|
||||||
readonly hydrate: boolean // TODO make this better
|
readonly hydrate: boolean // TODO make this better
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ClientRenderer<Props>
|
export class ClientRenderer<Props>
|
||||||
implements IRenderer<Props> {
|
implements Renderer<Props> {
|
||||||
constructor(readonly params: IClientRendererParams<Props>) {}
|
constructor(readonly params: ClientRendererParams<Props>) {}
|
||||||
|
|
||||||
render<State>(
|
render<State>(
|
||||||
url: string,
|
url: string,
|
||||||
store: Store<State>,
|
store: Store<State>,
|
||||||
props: Props,
|
props: Props,
|
||||||
config: IClientConfig,
|
config: ClientConfig,
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
RootComponent,
|
RootComponent,
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
import {IAPIDef} from '@rondo.dev/common'
|
|
||||||
import {IClientConfig} from './IClientConfig'
|
|
||||||
import {Store} from 'redux'
|
|
||||||
|
|
||||||
export interface IRenderer<Props> {
|
|
||||||
render<State>(
|
|
||||||
url: string,
|
|
||||||
store: Store<State>,
|
|
||||||
props: Props,
|
|
||||||
config: IClientConfig,
|
|
||||||
): any
|
|
||||||
}
|
|
||||||
11
packages/client/src/renderer/Renderer.ts
Normal file
11
packages/client/src/renderer/Renderer.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Store } from 'redux'
|
||||||
|
import { ClientConfig } from './ClientConfig'
|
||||||
|
|
||||||
|
export interface Renderer<Props> {
|
||||||
|
render<State>(
|
||||||
|
url: string,
|
||||||
|
store: Store<State>,
|
||||||
|
props: Props,
|
||||||
|
config: ClientConfig,
|
||||||
|
): unknown
|
||||||
|
}
|
||||||
@ -8,6 +8,10 @@ import { Store } from 'redux'
|
|||||||
import { IClientConfig } from './IClientConfig'
|
import { IClientConfig } from './IClientConfig'
|
||||||
import { IRenderer } from './IRenderer'
|
import { IRenderer } from './IRenderer'
|
||||||
|
|
||||||
|
interface ComponentWithFetchData {
|
||||||
|
fetchData(): Promise<unknown>
|
||||||
|
}
|
||||||
|
|
||||||
export class ServerRenderer<Props> implements IRenderer<Props> {
|
export class ServerRenderer<Props> implements IRenderer<Props> {
|
||||||
constructor(
|
constructor(
|
||||||
readonly RootComponent: React.ComponentType<Props>,
|
readonly RootComponent: React.ComponentType<Props>,
|
||||||
@ -17,7 +21,7 @@ export class ServerRenderer<Props> implements IRenderer<Props> {
|
|||||||
store: Store<State>,
|
store: Store<State>,
|
||||||
props: Props,
|
props: Props,
|
||||||
config: IClientConfig,
|
config: IClientConfig,
|
||||||
host: string = '',
|
host = '',
|
||||||
headers: Record<string, string> = {},
|
headers: Record<string, string> = {},
|
||||||
) {
|
) {
|
||||||
const {RootComponent} = this
|
const {RootComponent} = this
|
||||||
@ -39,7 +43,7 @@ export class ServerRenderer<Props> implements IRenderer<Props> {
|
|||||||
|
|
||||||
await ssrPrepass(element, async (el, component) => {
|
await ssrPrepass(element, async (el, component) => {
|
||||||
if (component && 'fetchData' in component) {
|
if (component && 'fetchData' in component) {
|
||||||
await (component as any).fetchData()
|
await (component as ComponentWithFetchData).fetchData()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const stream = renderToNodeStream(element)
|
const stream = renderToNodeStream(element)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
export * from './ClientRenderer'
|
export * from './ClientRenderer'
|
||||||
export * from './IClientConfig'
|
export * from './ClientConfig'
|
||||||
export * from './IRenderer'
|
export * from './Renderer'
|
||||||
export * from './isClientSide'
|
export * from './isClientSide'
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
|
interface MockServerSide {
|
||||||
|
__MOCK_SERVER_SIDE__?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export function isClientSide() {
|
export function isClientSide() {
|
||||||
return typeof window !== 'undefined' &&
|
return typeof window !== 'undefined' &&
|
||||||
typeof window.document !== 'undefined' &&
|
typeof window.document !== 'undefined' &&
|
||||||
typeof window.document.createElement === 'function' &&
|
typeof window.document.createElement === 'function' &&
|
||||||
typeof (window as any).__MOCK_SERVER_SIDE__ === 'undefined'
|
typeof (window as MockServerSide).__MOCK_SERVER_SIDE__ === 'undefined'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,23 +3,27 @@ import React from 'react'
|
|||||||
import { FaCheck, FaEdit, FaPlusSquare } from 'react-icons/fa'
|
import { FaCheck, FaEdit, FaPlusSquare } from 'react-icons/fa'
|
||||||
import { TeamActions, Team } from '@rondo.dev/common'
|
import { TeamActions, Team } from '@rondo.dev/common'
|
||||||
|
|
||||||
export type TTeamEditorProps = {
|
interface AddTeamProps {
|
||||||
type: 'add'
|
type: 'add'
|
||||||
onAddTeam: TeamActions['create']
|
onAddTeam: TeamActions['create']
|
||||||
} | {
|
}
|
||||||
|
|
||||||
|
interface UpdateTeamProps {
|
||||||
type: 'update'
|
type: 'update'
|
||||||
onUpdateTeam: TeamActions['update']
|
onUpdateTeam: TeamActions['update']
|
||||||
team: Team
|
team: Team
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITeamEditorState {
|
export type TTeamEditorProps = AddTeamProps | UpdateTeamProps
|
||||||
|
|
||||||
|
export interface TeamEditorState {
|
||||||
// TODO use redux state for errors!
|
// TODO use redux state for errors!
|
||||||
error: string
|
error: string
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TeamEditor
|
export class TeamEditor
|
||||||
extends React.PureComponent<TTeamEditorProps, ITeamEditorState> {
|
extends React.PureComponent<TTeamEditorProps, TeamEditorState> {
|
||||||
constructor(props: TTeamEditorProps) {
|
constructor(props: TTeamEditorProps) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
@ -33,7 +37,7 @@ extends React.PureComponent<TTeamEditorProps, ITeamEditorState> {
|
|||||||
componentWillReceiveProps(nextProps: TTeamEditorProps) {
|
componentWillReceiveProps(nextProps: TTeamEditorProps) {
|
||||||
if (nextProps.type === 'update') {
|
if (nextProps.type === 'update') {
|
||||||
const {team} = nextProps
|
const {team} = nextProps
|
||||||
if (team !== (this.props as any).team) {
|
if (team !== (this.props as UpdateTeamProps).team) {
|
||||||
this.setState({
|
this.setState({
|
||||||
name: this.getName(team),
|
name: this.getName(team),
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,24 +1,24 @@
|
|||||||
import { TReadonlyRecord, Team, TeamActions } from '@rondo.dev/common'
|
import { ReadonlyRecord, Team, TeamActions } from '@rondo.dev/common'
|
||||||
import { Button, Panel, PanelBlock, PanelHeading } from 'bloomer'
|
import { Button, Panel, PanelBlock, PanelHeading } from 'bloomer'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { FaEdit, FaPlus, FaTimes } from 'react-icons/fa'
|
import { FaEdit, FaPlus, FaTimes } from 'react-icons/fa'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
export interface ITeamListProps {
|
export interface TeamListProps {
|
||||||
ListButtons?: React.ComponentType<{team: Team}>
|
ListButtons?: React.ComponentType<{team: Team}>
|
||||||
teamsById: TReadonlyRecord<number, Team>
|
teamsById: ReadonlyRecord<number, Team>
|
||||||
teamIds: ReadonlyArray<number>
|
teamIds: ReadonlyArray<number>
|
||||||
onAddTeam: TeamActions['create']
|
onAddTeam: TeamActions['create']
|
||||||
onRemoveTeam: TeamActions['remove']
|
onRemoveTeam: TeamActions['remove']
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITeamProps {
|
export interface TeamProps {
|
||||||
ListButtons?: React.ComponentType<{team: Team}>
|
ListButtons?: React.ComponentType<{team: Team}>
|
||||||
team: Team
|
team: Team
|
||||||
onRemoveTeam: TeamActions['remove']
|
onRemoveTeam: TeamActions['remove']
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TeamRow extends React.PureComponent<ITeamProps> {
|
export class TeamRow extends React.PureComponent<TeamProps> {
|
||||||
handleRemove = async () => {
|
handleRemove = async () => {
|
||||||
const {onRemoveTeam, team: {id}} = this.props
|
const {onRemoveTeam, team: {id}} = this.props
|
||||||
await onRemoveTeam({id}).payload
|
await onRemoveTeam({id}).payload
|
||||||
@ -53,7 +53,7 @@ export class TeamRow extends React.PureComponent<ITeamProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TeamList extends React.PureComponent<ITeamListProps> {
|
export class TeamList extends React.PureComponent<TeamListProps> {
|
||||||
render() {
|
render() {
|
||||||
const {teamIds, teamsById} = this.props
|
const {teamIds, teamsById} = this.props
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { IUserInTeam, TReadonlyRecord, TeamActions, UserActions, Team } from '@rondo.dev/common'
|
import { UserInTeam, ReadonlyRecord, TeamActions, UserActions, Team } from '@rondo.dev/common'
|
||||||
import { Panel, PanelBlock, PanelHeading } from 'bloomer'
|
import { Panel, PanelBlock, PanelHeading } from 'bloomer'
|
||||||
import { History, Location } from 'history'
|
import { History, Location } from 'history'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
@ -8,24 +8,24 @@ import { TeamEditor } from './TeamEditor'
|
|||||||
import { TeamList } from './TeamList'
|
import { TeamList } from './TeamList'
|
||||||
import { TeamUserList } from './TeamUserList'
|
import { TeamUserList } from './TeamUserList'
|
||||||
|
|
||||||
export interface ITeamManagerProps {
|
export interface TeamManagerProps {
|
||||||
history: History
|
history: History
|
||||||
location: Location
|
location: Location
|
||||||
match: Match<any>
|
match: Match<any> // eslint-disable-line
|
||||||
|
|
||||||
ListButtons?: React.ComponentType<{team: Team}>
|
ListButtons?: React.ComponentType<{team: Team}>
|
||||||
|
|
||||||
teamActions: TeamActions
|
teamActions: TeamActions
|
||||||
findUserByEmail: UserActions['findUserByEmail']
|
findUserByEmail: UserActions['findUserByEmail']
|
||||||
|
|
||||||
teamsById: TReadonlyRecord<number, Team>
|
teamsById: ReadonlyRecord<number, Team>
|
||||||
teamIds: ReadonlyArray<number>
|
teamIds: ReadonlyArray<number>
|
||||||
|
|
||||||
userKeysByTeamId: TReadonlyRecord<number, ReadonlyArray<string>>
|
userKeysByTeamId: ReadonlyRecord<number, ReadonlyArray<string>>
|
||||||
usersByKey: TReadonlyRecord<string, IUserInTeam>
|
usersByKey: ReadonlyRecord<string, UserInTeam>
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TeamManager extends React.PureComponent<ITeamManagerProps> {
|
export class TeamManager extends React.PureComponent<TeamManagerProps> {
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
await this.props.teamActions.find()
|
await this.props.teamActions.find()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
import { indexBy, Team as _Team, IUserInTeam, TReadonlyRecord, without, TeamActions } from '@rondo.dev/common'
|
import { indexBy, Team as _Team, UserInTeam, ReadonlyRecord, without, TeamActions } from '@rondo.dev/common'
|
||||||
import { createReducer } from '@rondo.dev/jsonrpc'
|
import { createReducer } from '@rondo.dev/jsonrpc'
|
||||||
|
|
||||||
export interface ITeamState {
|
export interface TeamState {
|
||||||
readonly loading: number
|
readonly loading: number
|
||||||
readonly error: string
|
readonly error: string
|
||||||
|
|
||||||
readonly teamIds: ReadonlyArray<number>
|
readonly teamIds: ReadonlyArray<number>
|
||||||
readonly teamsById: TReadonlyRecord<number, _Team>
|
readonly teamsById: ReadonlyRecord<number, _Team>
|
||||||
|
|
||||||
readonly userKeysByTeamId: TReadonlyRecord<number, ReadonlyArray<string>>
|
readonly userKeysByTeamId: ReadonlyRecord<number, ReadonlyArray<string>>
|
||||||
readonly usersByKey: TReadonlyRecord<string, IUserInTeam>
|
readonly usersByKey: ReadonlyRecord<string, UserInTeam>
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultState: ITeamState = {
|
const defaultState: TeamState = {
|
||||||
loading: 0,
|
loading: 0,
|
||||||
error: '',
|
error: '',
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ export const Team = createReducer('teamService', defaultState)
|
|||||||
.reduce((obj, userInTeam) => {
|
.reduce((obj, userInTeam) => {
|
||||||
obj[getUserKey(userInTeam)] = userInTeam
|
obj[getUserKey(userInTeam)] = userInTeam
|
||||||
return obj
|
return obj
|
||||||
}, {} as Record<string, IUserInTeam>)
|
}, {} as Record<string, UserInTeam>)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userKeysByTeamId: {
|
userKeysByTeamId: {
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { IUser, IUserInTeam, TReadonlyRecord, TeamActions, UserActions, Team } from '@rondo.dev/common'
|
import { User, UserInTeam, ReadonlyRecord, TeamActions, UserActions, Team } from '@rondo.dev/common'
|
||||||
import { Button, Control, Heading, Help, Input, Panel, PanelBlock, PanelHeading } from 'bloomer'
|
import { Button, Control, Heading, Help, Input, Panel, PanelBlock, PanelHeading } from 'bloomer'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { FaCheck, FaTimes, FaUser } from 'react-icons/fa'
|
import { FaCheck, FaTimes, FaUser } from 'react-icons/fa'
|
||||||
|
|
||||||
const EMPTY_ARRAY: ReadonlyArray<string> = []
|
const EMPTY_ARRAY: ReadonlyArray<string> = []
|
||||||
|
|
||||||
export interface ITeamUsersProps {
|
export interface TeamUsersProps {
|
||||||
// fetchMyTeams: () => void,
|
// fetchMyTeams: () => void,
|
||||||
fetchUsersInTeam: TeamActions['findUsers']
|
fetchUsersInTeam: TeamActions['findUsers']
|
||||||
findUserByEmail: UserActions['findUserByEmail']
|
findUserByEmail: UserActions['findUserByEmail']
|
||||||
@ -14,29 +14,29 @@ export interface ITeamUsersProps {
|
|||||||
onRemoveUser: TeamActions['removeUser']
|
onRemoveUser: TeamActions['removeUser']
|
||||||
|
|
||||||
team: Team
|
team: Team
|
||||||
userKeysByTeamId: TReadonlyRecord<number, ReadonlyArray<string>>
|
userKeysByTeamId: ReadonlyRecord<number, ReadonlyArray<string>>
|
||||||
usersByKey: TReadonlyRecord<string, IUserInTeam>
|
usersByKey: ReadonlyRecord<string, UserInTeam>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITeamUserProps {
|
export interface TeamUserProps {
|
||||||
onRemoveUser: (
|
onRemoveUser: (
|
||||||
params: {userId: number, teamId: number, roleId: number}) => void
|
params: {userId: number, teamId: number, roleId: number}) => void
|
||||||
user: IUserInTeam
|
user: UserInTeam
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAddUserProps {
|
export interface AddUserProps {
|
||||||
onAddUser: TeamActions['addUser']
|
onAddUser: TeamActions['addUser']
|
||||||
onSearchUser: UserActions['findUserByEmail']
|
onSearchUser: UserActions['findUserByEmail']
|
||||||
teamId: number
|
teamId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAddUserState {
|
export interface AddUserState {
|
||||||
error: string
|
error: string
|
||||||
email: string
|
email: string
|
||||||
user?: IUser
|
user?: User
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TeamUser extends React.PureComponent<ITeamUserProps> {
|
export class TeamUser extends React.PureComponent<TeamUserProps> {
|
||||||
handleRemoveUser = async () => {
|
handleRemoveUser = async () => {
|
||||||
const {onRemoveUser, user} = this.props
|
const {onRemoveUser, user} = this.props
|
||||||
await onRemoveUser({...user, roleId: 1})
|
await onRemoveUser({...user, roleId: 1})
|
||||||
@ -65,8 +65,8 @@ export class TeamUser extends React.PureComponent<ITeamUserProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AddUser extends React.PureComponent<IAddUserProps, IAddUserState> {
|
export class AddUser extends React.PureComponent<AddUserProps, AddUserState> {
|
||||||
constructor(props: IAddUserProps) {
|
constructor(props: AddUserProps) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
error: '',
|
error: '',
|
||||||
@ -131,10 +131,10 @@ export class AddUser extends React.PureComponent<IAddUserProps, IAddUserState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TeamUserList extends React.PureComponent<ITeamUsersProps> {
|
export class TeamUserList extends React.PureComponent<TeamUsersProps> {
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
await this.fetchUsersInTeam(this.props.team.id)
|
await this.fetchUsersInTeam(this.props.team.id)
|
||||||
} async componentWillReceiveProps(nextProps: ITeamUsersProps) {
|
} async componentWillReceiveProps(nextProps: TeamUsersProps) {
|
||||||
const {team} = nextProps
|
const {team} = nextProps
|
||||||
if (team.id !== this.props.team.id) {
|
if (team.id !== this.props.team.id) {
|
||||||
this.fetchUsersInTeam(team.id)
|
this.fetchUsersInTeam(team.id)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ITeamService, ITeamUsers, IUserService, Team, TeamActions, TeamServiceMethods, UserActions, UserServiceMethods } from '@rondo.dev/common'
|
import { TeamService, TeamUsers, UserService, Team, TeamActions, TeamServiceMethods, UserActions, UserServiceMethods } from '@rondo.dev/common'
|
||||||
import { createActions } from '@rondo.dev/jsonrpc'
|
import { createActions } from '@rondo.dev/jsonrpc'
|
||||||
import createClientMock from '@rondo.dev/jsonrpc/lib/createClientMock'
|
import createClientMock from '@rondo.dev/jsonrpc/lib/createClientMock'
|
||||||
import { getError } from '@rondo.dev/test-utils'
|
import { getError } from '@rondo.dev/test-utils'
|
||||||
@ -17,9 +17,9 @@ describe('TeamConnector', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [teamClient, teamClientMock] =
|
const [teamClient, teamClientMock] =
|
||||||
createClientMock<ITeamService>(TeamServiceMethods)
|
createClientMock<TeamService>(TeamServiceMethods)
|
||||||
const [userClient, userClientMock] =
|
const [userClient] =
|
||||||
createClientMock<IUserService>(UserServiceMethods)
|
createClientMock<UserService>(UserServiceMethods)
|
||||||
let teamActions!: TeamActions
|
let teamActions!: TeamActions
|
||||||
let userActions!: UserActions
|
let userActions!: UserActions
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -57,7 +57,7 @@ describe('TeamConnector', () => {
|
|||||||
userTeams: [],
|
userTeams: [],
|
||||||
}]
|
}]
|
||||||
|
|
||||||
const users: ITeamUsers = {
|
const users: TeamUsers = {
|
||||||
teamId: 123,
|
teamId: 123,
|
||||||
usersInTeam: [{
|
usersInTeam: [{
|
||||||
teamId: 123,
|
teamId: 123,
|
||||||
@ -111,7 +111,6 @@ describe('TeamConnector', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('displays an error', async () => {
|
it('displays an error', async () => {
|
||||||
const error = {error: 'An error'}
|
|
||||||
teamClientMock.create.mockRejectedValue(new Error('Test Error'))
|
teamClientMock.create.mockRejectedValue(new Error('Test Error'))
|
||||||
const {render} = createTestProvider()
|
const {render} = createTestProvider()
|
||||||
const {node, waitForActions} = render({
|
const {node, waitForActions} = render({
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
/* eslint @typescript-eslint/no-explicit-any: 0 */
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import T from 'react-dom/test-utils'
|
|
||||||
import {createStore, TStateSelector, WaitMiddleware} from '@rondo.dev/redux'
|
import {createStore, TStateSelector, WaitMiddleware} from '@rondo.dev/redux'
|
||||||
import {Provider} from 'react-redux'
|
import {Provider} from 'react-redux'
|
||||||
import {
|
import {
|
||||||
@ -10,20 +10,18 @@ import {
|
|||||||
Reducer,
|
Reducer,
|
||||||
ReducersMapObject,
|
ReducersMapObject,
|
||||||
combineReducers,
|
combineReducers,
|
||||||
Store as ReduxStore,
|
|
||||||
Unsubscribe,
|
|
||||||
} from 'redux'
|
} from 'redux'
|
||||||
import { format } from 'util'
|
|
||||||
|
|
||||||
interface IRenderParams<State, LocalState> {
|
interface RenderParams<State, LocalState> {
|
||||||
reducers: ReducersMapObject<State, any>
|
reducers: ReducersMapObject<State, any>
|
||||||
select: TStateSelector<State, LocalState>
|
select: TStateSelector<State, LocalState>
|
||||||
// getComponent: (
|
}
|
||||||
// select: TStateSelector<State, LocalState>) => React.ComponentType<Props>,
|
|
||||||
// customJSX?: (
|
export class TestContainer extends React.Component<{}> {
|
||||||
// Component: React.ComponentType<Props>,
|
ref = React.createRef<HTMLDivElement>()
|
||||||
// props: Props,
|
render() {
|
||||||
// ) => JSX.Element
|
return <div ref={this.ref}>{this.props.children}</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TestUtils {
|
export class TestUtils {
|
||||||
@ -34,9 +32,10 @@ export class TestUtils {
|
|||||||
|
|
||||||
render(jsx: JSX.Element) {
|
render(jsx: JSX.Element) {
|
||||||
const $div = document.createElement('div')
|
const $div = document.createElement('div')
|
||||||
|
ReactDOM.render(<TestContainer>{jsx}</TestContainer>, $div)
|
||||||
const component = ReactDOM.render(
|
const component = ReactDOM.render(
|
||||||
<div>{jsx}</div>, $div) as unknown as React.Component<any>
|
<div>{jsx}</div>, $div) as unknown as TestContainer
|
||||||
const node = (ReactDOM.findDOMNode(component) as Element).children[0]
|
const node = component.ref.current!
|
||||||
return {
|
return {
|
||||||
component,
|
component,
|
||||||
node,
|
node,
|
||||||
@ -55,7 +54,7 @@ export class TestUtils {
|
|||||||
* method to render the connected component with a `Provider`.
|
* method to render the connected component with a `Provider`.
|
||||||
*/
|
*/
|
||||||
withProvider<State, LocalState, A extends Action<any> = AnyAction>(
|
withProvider<State, LocalState, A extends Action<any> = AnyAction>(
|
||||||
params: IRenderParams<State, LocalState>,
|
params: RenderParams<State, LocalState>,
|
||||||
) {
|
) {
|
||||||
const {reducers, select} = params
|
const {reducers, select} = params
|
||||||
|
|
||||||
@ -102,7 +101,7 @@ export class TestUtils {
|
|||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
async waitForActions(timeout = 2000) {
|
async waitForActions(timeout = 2000) {
|
||||||
await waitMiddleware.waitForRecorded(recorder)
|
await waitMiddleware.waitForRecorded(recorder, timeout)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,7 +111,7 @@ export class TestUtils {
|
|||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
const self: ISelf<
|
const self: Self<
|
||||||
Props, typeof store, typeof Component, CreateJSX
|
Props, typeof store, typeof Component, CreateJSX
|
||||||
> = {
|
> = {
|
||||||
render,
|
render,
|
||||||
@ -128,14 +127,14 @@ export class TestUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISelf<Props, Store, Component, CreateJSX> {
|
interface Self<Props, Store, Component, CreateJSX> {
|
||||||
render: (props: Props) => {
|
render: (props: Props) => {
|
||||||
component: React.Component<any>
|
component: TestContainer
|
||||||
node: Element
|
node: Element
|
||||||
waitForActions(timeout?: number): Promise<void>
|
waitForActions(timeout?: number): Promise<void>
|
||||||
}
|
}
|
||||||
store: Store
|
store: Store
|
||||||
Component: Component
|
Component: Component
|
||||||
withJSX: (localCreateJSX: CreateJSX)
|
withJSX: (localCreateJSX: CreateJSX)
|
||||||
=> ISelf<Props, Store, Component, CreateJSX>
|
=> Self<Props, Store, Component, CreateJSX>
|
||||||
}
|
}
|
||||||
|
|||||||
2
packages/client/types/react-ssr-prepass.d.ts
vendored
2
packages/client/types/react-ssr-prepass.d.ts
vendored
@ -4,7 +4,7 @@ declare module 'react-ssr-prepass' {
|
|||||||
export type Visitor = (
|
export type Visitor = (
|
||||||
element: ReactElement,
|
element: ReactElement,
|
||||||
instance?: Component,
|
instance?: Component,
|
||||||
) => void | Promise<any>
|
) => void | Promise<unknown>
|
||||||
|
|
||||||
function ssrPrepass(node: ReactElement, visitor?: Visitor): Promise<void>
|
function ssrPrepass(node: ReactElement, visitor?: Visitor): Promise<void>
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
export * from './HTTPClient'
|
|
||||||
export * from './HTTPClientMock'
|
|
||||||
export * from './Headers'
|
export * from './Headers'
|
||||||
export * from './HTTPClient'
|
export * from './HTTPClient'
|
||||||
|
export * from './HTTPClientMock'
|
||||||
export * from './Request'
|
export * from './Request'
|
||||||
export * from './RequestParams'
|
export * from './RequestParams'
|
||||||
export * from './RequestQuery'
|
export * from './RequestQuery'
|
||||||
export * from './Response'
|
export * from './Response'
|
||||||
|
export * from './SimpleHTTPClient'
|
||||||
export * from './TypedRequestParams'
|
export * from './TypedRequestParams'
|
||||||
export * from './URLFormatter'
|
export * from './URLFormatter'
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { Logger } from '@rondo.dev/logger'
|
|||||||
import express, { ErrorRequestHandler, Request, Response, Router } from 'express'
|
import express, { ErrorRequestHandler, Request, Response, Router } from 'express'
|
||||||
import { createError, ErrorResponse, isRPCError } from './error'
|
import { createError, ErrorResponse, isRPCError } from './error'
|
||||||
import { IDEMPOTENT_METHOD_REGEX } from './idempotent'
|
import { IDEMPOTENT_METHOD_REGEX } from './idempotent'
|
||||||
import { createRpcService, ERROR_METHOD_NOT_FOUND, ERROR_SERVER, IRequest, SuccessResponse } from './jsonrpc'
|
import { createRpcService, ERROR_METHOD_NOT_FOUND, ERROR_SERVER, Request as RPCRequest, SuccessResponse } from './jsonrpc'
|
||||||
import { FunctionPropertyNames } from './types'
|
import { FunctionPropertyNames } from './types'
|
||||||
|
|
||||||
export type TGetContext<Context> = (req: Request) => Promise<Context> | Context
|
export type TGetContext<Context> = (req: Request) => Promise<Context> | Context
|
||||||
@ -16,13 +16,13 @@ export interface RPCReturnType {
|
|||||||
router(): Router
|
router(): Router
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InvocationDetails<A extends IRequest, Context> {
|
export interface InvocationDetails<A extends RPCRequest, Context> {
|
||||||
context: Context
|
context: Context
|
||||||
path: string
|
path: string
|
||||||
request: A
|
request: A
|
||||||
}
|
}
|
||||||
|
|
||||||
async function defaultHook<A extends IRequest, R, Context>(
|
async function defaultHook<A extends RPCRequest, R, Context>(
|
||||||
details: InvocationDetails<A, Context>,
|
details: InvocationDetails<A, Context>,
|
||||||
invoke: () => Promise<R>,
|
invoke: () => Promise<R>,
|
||||||
): Promise<R> {
|
): Promise<R> {
|
||||||
@ -33,7 +33,7 @@ async function defaultHook<A extends IRequest, R, Context>(
|
|||||||
export function jsonrpc<Context>(
|
export function jsonrpc<Context>(
|
||||||
getContext: TGetContext<Context>,
|
getContext: TGetContext<Context>,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
hook: <A extends IRequest, R>(
|
hook: <A extends RPCRequest, R>(
|
||||||
details: InvocationDetails<A, Context>,
|
details: InvocationDetails<A, Context>,
|
||||||
invoke: (request?: A) => Promise<R>) => Promise<R> = defaultHook,
|
invoke: (request?: A) => Promise<R>) => Promise<R> = defaultHook,
|
||||||
idempotentMethodRegex = IDEMPOTENT_METHOD_REGEX,
|
idempotentMethodRegex = IDEMPOTENT_METHOD_REGEX,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user