diff --git a/packages/client/src/actions/UserActions.ts b/packages/client/src/actions/UserActions.ts index 2780155..dfa5f4e 100644 --- a/packages/client/src/actions/UserActions.ts +++ b/packages/client/src/actions/UserActions.ts @@ -17,21 +17,23 @@ export enum UserActionKeys { export class UserActions { constructor(protected readonly http: IHTTPClient) {} - logIn(credentials: ICredentials): IAction { + logIn = + (credentials: ICredentials): IAction => { return { payload: this.http.post('/auth/login', credentials), type: UserActionKeys.USER_LOG_IN, } } - logInError(error: Error): IErrorAction { + logInError = + (error: Error): IErrorAction => { return { error, type: UserActionKeys.USER_LOG_IN_REJECTED, } } - logOut(): IAction { + logOut = (): IAction => { return { payload: this.http.get('/auth/logout'), type: UserActionKeys.USER_LOG_OUT, diff --git a/packages/client/src/actions/index.ts b/packages/client/src/actions/index.ts index 3fa9d76..a4fb173 100644 --- a/packages/client/src/actions/index.ts +++ b/packages/client/src/actions/index.ts @@ -1,3 +1,5 @@ +export * from './ActionTypes' export * from './IAction' +export * from './IErrorAction' export * from './UnionType' export * from './UserActions' diff --git a/packages/client/src/components/Button.tsx b/packages/client/src/components/Button.tsx new file mode 100644 index 0000000..bb9720e --- /dev/null +++ b/packages/client/src/components/Button.tsx @@ -0,0 +1,9 @@ +import React from 'react' + +export class Button extends React.PureComponent { + render() { + return ( + + ) + } +} diff --git a/packages/client/src/components/Input.tsx b/packages/client/src/components/Input.tsx new file mode 100644 index 0000000..703b7cd --- /dev/null +++ b/packages/client/src/components/Input.tsx @@ -0,0 +1,25 @@ +import React from 'react' + +export interface IInputProps { + name: string + type: 'text' | 'password' | 'hidden' + value?: string + onChange?: (name: string, value: string) => void +} + +export class Input extends React.PureComponent { + handleChange = (e: React.ChangeEvent) => { + if (this.props.onChange) { + this.props.onChange(e.target.name, e.target.value) + } + } + render() { + return ( + + ) + } +} diff --git a/packages/client/src/components/LoginForm.tsx b/packages/client/src/components/LoginForm.tsx new file mode 100644 index 0000000..72ba1ef --- /dev/null +++ b/packages/client/src/components/LoginForm.tsx @@ -0,0 +1,55 @@ +import React from 'react' +import {Input} from './Input' +import {ICredentials} from '@rondo/common' +import {IState} from '../reducers' + +export interface ILoginFormProps { + error?: string + csrfToken: string + onSubmit: (credentials: ICredentials) => void +} + +export interface ILoginFormState extends ICredentials {} + +// 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, ILoginFormState +> { + constructor(props: ILoginFormProps) { + super(props) + this.state = { + username: '', + password: '', + } + } + handleSubmit = () => { + this.props.onSubmit(this.state) + } + handleChange = (name: keyof ILoginFormState, value: string) => { + this.setState( + {[name]: value} as Pick, + ) + } + render() { + return ( +
+ + + +
+ ) + } +} diff --git a/packages/client/src/components/index.ts b/packages/client/src/components/index.ts new file mode 100644 index 0000000..c0ed20c --- /dev/null +++ b/packages/client/src/components/index.ts @@ -0,0 +1,4 @@ +export * from './Button' +// export * from './Component' +export * from './Input' +export * from './LoginForm' diff --git a/packages/client/src/containers/LoginFormContainer.tsx b/packages/client/src/containers/LoginFormContainer.tsx new file mode 100644 index 0000000..9c1395a --- /dev/null +++ b/packages/client/src/containers/LoginFormContainer.tsx @@ -0,0 +1,31 @@ +import {UserActions} from '../actions/UserActions' +import {connect} from 'react-redux' +import {bindActionCreators, Dispatch} from 'redux' +import {IState} from '../reducers' +// import {LoginForm} from '../components/LoginForm' +import React from 'react' + +export class LoginFormContainer { + constructor( + protected readonly Form: typeof React.PureComponent | typeof React.Component, + protected readonly userActions: UserActions, // TODO interface + ) {} + + connect() { + return connect(this.mapStateToProps, this.mapDispatchToProps)() + } + + mapStateToProps = (state: IState) => { + return { + csrfToken: '', // TODO this should be read from the state too + error: state.user.error, + user: state.user.user, + } + } + + mapDispatchToProps = (dispatch: Dispatch) => { + return { + logIn: bindActionCreators(this.userActions.logIn, dispatch), + } + } +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts new file mode 100644 index 0000000..28fbaa1 --- /dev/null +++ b/packages/client/src/index.ts @@ -0,0 +1,6 @@ +export * from './actions' +export * from './components' +export * from './http' +export * from './middleware' +export * from './reducers' +export * from './renderer' diff --git a/packages/client/src/index.tsx b/packages/client/src/index.tsx deleted file mode 100644 index d210aab..0000000 --- a/packages/client/src/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -export * from './renderer' - -// import ReactDOM from 'react-dom' -// import React from 'react' -// import {CComponent} from './components/Component' -// import {Provider} from 'react-redux' -// import {createStore} from './store' - -// const state = (window as any).__PRELOADED_STATE__ - -// if (state) { -// const store = createStore(state) -// ReactDOM.hydrate( -// -// -// , -// document.body, -// ) -// } else { -// const store = createStore() -// ReactDOM.render( -// -// -// , -// document.body, -// ) -// } diff --git a/packages/client/src/middleware/index.ts b/packages/client/src/middleware/index.ts new file mode 100644 index 0000000..b59310b --- /dev/null +++ b/packages/client/src/middleware/index.ts @@ -0,0 +1 @@ +export * from './PromiseMiddleware' diff --git a/packages/client/src/reducers/index.ts b/packages/client/src/reducers/index.ts index bfd7217..f27c65f 100644 --- a/packages/client/src/reducers/index.ts +++ b/packages/client/src/reducers/index.ts @@ -1,21 +1,7 @@ -export interface IState { - csrfToken: string - value: string -} +export * from './user' -const defaultState: IState = { - csrfToken: '', - value: '', -} +import {combineReducers} from 'redux' +import * as user from './user' -export function value(state: IState = defaultState, action: any): IState { - switch (action && action.type) { - case 'VALUE_SET': - return { - ...state, - value: action!.payload as string, - } - default: - return state || {value: ''} - } -} +export const reducers = combineReducers(user) +export type IState = ReturnType diff --git a/packages/client/src/reducers/user.ts b/packages/client/src/reducers/user.ts index 81554a5..3b6b497 100644 --- a/packages/client/src/reducers/user.ts +++ b/packages/client/src/reducers/user.ts @@ -1,17 +1,17 @@ import {IUser} from '@rondo/common' import {UserActionKeys, UserActionType} from '../actions/UserActions' -interface IState { +export interface IUserState { error?: string, user?: IUser } -const defaultState: IState = { +const defaultState: IUserState = { error: undefined, user: undefined, } -export function user(state = defaultState, action: UserActionType): IState { +export function user(state = defaultState, action: UserActionType): IUserState { switch (action.type) { case UserActionKeys.USER_LOG_IN: return {...state, user: action.payload} diff --git a/packages/client/src/renderer/ClientRenderer.tsx b/packages/client/src/renderer/ClientRenderer.tsx index 0ddacfe..0d3d33d 100644 --- a/packages/client/src/renderer/ClientRenderer.tsx +++ b/packages/client/src/renderer/ClientRenderer.tsx @@ -1,5 +1,5 @@ import ReactDOM from 'react-dom' -import React, {Component} from 'react' +import React from 'react' import {Provider} from 'react-redux' import {IStoreFactory} from './IStoreFactory' import {IRenderer} from './IRenderer' @@ -7,7 +7,7 @@ import {IRenderer} from './IRenderer' export class ClientRenderer implements IRenderer { constructor( readonly createStore: IStoreFactory, - readonly RootComponent: typeof Component, + readonly RootComponent: React.ComponentType, readonly target = document.body, ) {} diff --git a/packages/client/src/renderer/ServerRenderer.tsx b/packages/client/src/renderer/ServerRenderer.tsx index 7b8c568..0922804 100644 --- a/packages/client/src/renderer/ServerRenderer.tsx +++ b/packages/client/src/renderer/ServerRenderer.tsx @@ -1,5 +1,4 @@ import React from 'react' -import {Component} from 'react' import {IRenderer} from './IRenderer' import {IStoreFactory} from './IStoreFactory' import {Provider} from 'react-redux' @@ -8,7 +7,7 @@ import {renderToNodeStream} from 'react-dom/server' export class ServerRenderer implements IRenderer { constructor( readonly createStore: IStoreFactory, - readonly RootComponent: typeof Component, + readonly RootComponent: React.ComponentType, ) {} render(state?: any) { const {RootComponent} = this diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts index 249dd5d..8cd40d2 100644 --- a/packages/client/src/store.ts +++ b/packages/client/src/store.ts @@ -1,6 +1,13 @@ -import {createStore as create} from 'redux' -import {value, IState} from './reducers' +import {PromiseMiddleware} from './middleware' +import {applyMiddleware, createStore as create} from 'redux' +import {reducers, IState} from './reducers' export function createStore(state?: IState) { - return create(value, state) + return create( + reducers, + state, + applyMiddleware( + new PromiseMiddleware().handle, + ), + ) }