Create LoginForm

This commit is contained in:
Jerko Steiner 2019-01-19 19:21:56 +01:00
parent c1c7c829e7
commit 26f106fcae
15 changed files with 159 additions and 59 deletions

View File

@ -17,21 +17,23 @@ export enum UserActionKeys {
export class UserActions {
constructor(protected readonly http: IHTTPClient<IAPIDef>) {}
logIn(credentials: ICredentials): IAction<IUser, UserActionKeys.USER_LOG_IN> {
logIn =
(credentials: ICredentials): IAction<IUser, UserActionKeys.USER_LOG_IN> => {
return {
payload: this.http.post('/auth/login', credentials),
type: UserActionKeys.USER_LOG_IN,
}
}
logInError(error: Error): IErrorAction<UserActionKeys.USER_LOG_IN_REJECTED> {
logInError =
(error: Error): IErrorAction<UserActionKeys.USER_LOG_IN_REJECTED> => {
return {
error,
type: UserActionKeys.USER_LOG_IN_REJECTED,
}
}
logOut(): IAction<unknown, UserActionKeys.USER_LOG_OUT> {
logOut = (): IAction<unknown, UserActionKeys.USER_LOG_OUT> => {
return {
payload: this.http.get('/auth/logout'),
type: UserActionKeys.USER_LOG_OUT,

View File

@ -1,3 +1,5 @@
export * from './ActionTypes'
export * from './IAction'
export * from './IErrorAction'
export * from './UnionType'
export * from './UserActions'

View File

@ -0,0 +1,9 @@
import React from 'react'
export class Button extends React.PureComponent {
render() {
return (
<button>{this.props.children}</button>
)
}
}

View File

@ -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<IInputProps> {
handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (this.props.onChange) {
this.props.onChange(e.target.name, e.target.value)
}
}
render() {
return (
<input
name={this.props.name}
type={this.props.type}
value={this.props.value}
/>
)
}
}

View File

@ -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<ILoginFormState, keyof ILoginFormState>,
)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<Input
type='hidden'
name='_csrf'
value={this.props.csrfToken}
/>
<Input
name='username'
type='text'
value={this.state.username}
/>
<Input
name='password'
type='password'
value={this.state.password}
/>
</form>
)
}
}

View File

@ -0,0 +1,4 @@
export * from './Button'
// export * from './Component'
export * from './Input'
export * from './LoginForm'

View File

@ -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),
}
}
}

View File

@ -0,0 +1,6 @@
export * from './actions'
export * from './components'
export * from './http'
export * from './middleware'
export * from './reducers'
export * from './renderer'

View File

@ -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(
// <Provider store={store}>
// <CComponent />
// </Provider>,
// document.body,
// )
// } else {
// const store = createStore()
// ReactDOM.render(
// <Provider store={store}>
// <Component />
// </Provider>,
// document.body,
// )
// }

View File

@ -0,0 +1 @@
export * from './PromiseMiddleware'

View File

@ -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<typeof reducers>

View File

@ -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}

View File

@ -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,
) {}

View File

@ -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

View File

@ -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,
),
)
}