Create LoginForm
This commit is contained in:
parent
c1c7c829e7
commit
26f106fcae
@ -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,
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
export * from './ActionTypes'
|
||||
export * from './IAction'
|
||||
export * from './IErrorAction'
|
||||
export * from './UnionType'
|
||||
export * from './UserActions'
|
||||
|
||||
9
packages/client/src/components/Button.tsx
Normal file
9
packages/client/src/components/Button.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export class Button extends React.PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<button>{this.props.children}</button>
|
||||
)
|
||||
}
|
||||
}
|
||||
25
packages/client/src/components/Input.tsx
Normal file
25
packages/client/src/components/Input.tsx
Normal 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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
55
packages/client/src/components/LoginForm.tsx
Normal file
55
packages/client/src/components/LoginForm.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
4
packages/client/src/components/index.ts
Normal file
4
packages/client/src/components/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './Button'
|
||||
// export * from './Component'
|
||||
export * from './Input'
|
||||
export * from './LoginForm'
|
||||
31
packages/client/src/containers/LoginFormContainer.tsx
Normal file
31
packages/client/src/containers/LoginFormContainer.tsx
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
6
packages/client/src/index.ts
Normal file
6
packages/client/src/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export * from './actions'
|
||||
export * from './components'
|
||||
export * from './http'
|
||||
export * from './middleware'
|
||||
export * from './reducers'
|
||||
export * from './renderer'
|
||||
@ -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,
|
||||
// )
|
||||
// }
|
||||
1
packages/client/src/middleware/index.ts
Normal file
1
packages/client/src/middleware/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './PromiseMiddleware'
|
||||
@ -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>
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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,
|
||||
) {}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user