Skip to content
This repository has been archived by the owner on May 11, 2021. It is now read-only.

Commit

Permalink
feat(user): add forms for user maangment and forbidden page
Browse files Browse the repository at this point in the history
  • Loading branch information
igorkamyshev committed Feb 11, 2019
1 parent 5da4988 commit 5ca4b41
Show file tree
Hide file tree
Showing 33 changed files with 362 additions and 31 deletions.
7 changes: 7 additions & 0 deletions front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,21 @@
"dependencies": {
"@zeit/next-typescript": "^1.1.1",
"args-parser": "^1.1.0",
"axios": "^0.18.0",
"cookie-parser": "^1.4.3",
"express": "^4.16.4",
"final-form": "^4.11.1",
"http-status-codes": "^1.3.0",
"js-cookie": "^2.2.0",
"lodash": "^4.17.11",
"next": "^7.0.2",
"next-compose-plugins": "^2.1.1",
"next-css-unpluggable": "^2.1.0",
"next-redirect": "^1.0.1",
"next-routes": "^1.4.2",
"react": "^16.8.1",
"react-dom": "^16.8.1",
"react-final-form": "^4.0.2",
"react-redux": "^6.0.0",
"redux": "^4.0.1",
"redux-clear": "^1.0.3",
Expand All @@ -27,6 +33,7 @@
"redux-thunk": "^2.3.0"
},
"devDependencies": {
"@types/js-cookie": "^2.2.0",
"@types/lodash": "^4.14.120",
"@types/next": "^7.0.7",
"@types/react": "^16.8.2",
Expand Down
24 changes: 19 additions & 5 deletions front/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import redirect from 'next-redirect'
import App, { Container, NextAppContext } from 'next/app'
import Head from 'next/head'
import React from 'react'
Expand All @@ -9,20 +10,33 @@ import { AppContext } from '@front/domain/AppContext'
import { WithReduxProps } from '@front/domain/store/WithReduxProps'
import { withReduxStore } from '@front/domain/store/withReduxStore'
import { actions } from '@front/domain/user/reducer/data'
import { getToken } from '@front/domain/user/selectors/getToken'
import { pushRoute } from '@front/pushRoute'

class CheckmoneyWeb extends App<WithReduxProps> {
public static getInitialProps(appContext: NextAppContext) {
public static async getInitialProps(appContext: NextAppContext) {
const ctx: AppContext = appContext.ctx as any

// TODO: set cookie!
const token = Option.of(ctx)
.map(context => context.req)
.map(request => request.cookies)
.map(cookies => cookies.token)
.flatMap(context => Option.of(context.req))
.flatMap(request => Option.of(request.cookies))
.flatMap(cookies => Option.of(cookies.token))

if (token.nonEmpty()) {
ctx.reduxStore.dispatch(actions.setToken(token.get()))
}

const isSecure = !!(appContext.Component as any).isSecure
const loggedIn = getToken(ctx.reduxStore.getState()).nonEmpty()
if (isSecure && !loggedIn) {
if (!!ctx.res) {
await redirect(ctx, '/forbidden')
} else {
await pushRoute('/forbidden')
}
}

return App.getInitialProps(appContext)
}

Expand All @@ -44,4 +58,4 @@ class CheckmoneyWeb extends App<WithReduxProps> {
}
}

export default withReduxStore(CheckmoneyWeb)
export default withReduxStore(CheckmoneyWeb as any)
17 changes: 17 additions & 0 deletions front/pages/forbidden.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { FORBIDDEN } from 'http-status-codes'
import * as React from 'react'

import { AppContext } from '@front/domain/AppContext'

export default class ForbiddenPage extends React.Component {
public static getInitialProps({ res }: AppContext) {
// prettier-ignore
;(res as any).statusCode = FORBIDDEN

return {}
}

public render() {
return <p>Forbidden</p>
}
}
5 changes: 0 additions & 5 deletions front/pages/index.css

This file was deleted.

12 changes: 5 additions & 7 deletions front/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import * as React from 'react'

import log from '@front/log'
import con from '@shared/module'
import { Landing } from '@front/features/landing'

import s from './index.css'

export default () => {
con()
return <p className={s.title}>{log()}</p>
export default class IndexPage extends React.Component {
public render() {
return <Landing />
}
}
9 changes: 9 additions & 0 deletions front/pages/internal/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as React from 'react'

export default class AppPage extends React.Component {
public static isSecure = true

public render() {
return <p>app</p>
}
}
9 changes: 9 additions & 0 deletions front/pages/internal/hello.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as React from 'react'

export default class HelloPage extends React.Component {
public static isSecure = true

public render() {
return <p>hello</p>
}
}
6 changes: 5 additions & 1 deletion front/routes.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
const nextRoutes = require('next-routes')

module.exports = nextRoutes().add('index')
module.exports = nextRoutes()
.add({ pattern: '/', page: 'index' })
.add({ pattern: '/forbidden', page: 'forbidden' })
.add({ pattern: '/hello', page: 'internal/hello' })
.add({ pattern: '/app', page: 'internal/app' })
21 changes: 21 additions & 0 deletions front/src/domain/api/Api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import axios, { AxiosInstance } from 'axios'
import { Option } from 'tsoption'

export class Api {
public get client() {
return this.axios
}

private readonly axios: AxiosInstance

public constructor(token: Option<string>) {
const authHeaders = token.nonEmpty()
? { Authorization: `Bearer ${token.get()}` }
: {}

this.axios = axios.create({
baseURL: 'http://localhost:3000/',
headers: authHeaders,
})
}
}
5 changes: 5 additions & 0 deletions front/src/domain/api/createApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Option } from 'tsoption'

import { Api } from './Api'

export const createApi = (token: Option<string>) => new Api(token)
13 changes: 11 additions & 2 deletions front/src/domain/store/fetchingRedux/fetchOrFail.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { AnyAction, Dispatch } from 'redux'
import { Option } from 'tsoption'

import { Api } from '@front/domain/api/Api'
import { getToken } from '@front/domain/user/selectors/getToken'

import { State } from '../State'

Expand All @@ -10,19 +14,24 @@ interface FetchActions {

type Execute = (
dispatch: Dispatch,
getApi: () => Api,
getState: () => State,
) => Promise<void | any>

export const fetchOrFail = (
fetchActions: FetchActions,
execute: Execute,
) => async (dispatch: Dispatch<AnyAction>, getState: () => State) => {
) => async (
dispatch: Dispatch<AnyAction>,
getState: () => State,
createApi: (token: Option<string>) => Api,
) => {
const { request, success, failure } = fetchActions

try {
dispatch(request())

await execute(dispatch, getState)
await execute(dispatch, () => createApi(getToken(getState())), getState)

dispatch(success())
} catch (e) {
Expand Down
4 changes: 3 additions & 1 deletion front/src/domain/store/initializeStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { applyMiddleware, createStore } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunk from 'redux-thunk'

import { createApi } from '@front/domain/api/createApi'

import { reducer } from './reducer'
import { State } from './State'

export const initializeStore = (initialState?: State) =>
createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(thunk)),
composeWithDevTools(applyMiddleware(thunk.withExtraArgument(createApi))),
)
15 changes: 10 additions & 5 deletions front/src/domain/store/withReduxStore.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import App, { AppProps, DefaultAppIProps, NextAppContext } from 'next/app'
import React, { ComponentType } from 'react'
import {
AppComponentType,
AppProps,
DefaultAppIProps,
NextAppContext,
} from 'next/app'
import React from 'react'

import { AppContext } from '../AppContext'
import { getOrCreateStore } from './getOrCreateStore'
Expand All @@ -9,7 +14,7 @@ import { WithReduxProps } from './WithReduxProps'
type NextProps = AppProps & DefaultAppIProps

export const withReduxStore = (
Application: ComponentType<NextProps & WithReduxProps>,
Application: AppComponentType<NextProps & WithReduxProps>,
) => {
return class AppWithRedux extends React.Component<NextProps> {
public static async getInitialProps(appContext: NextAppContext) {
Expand All @@ -19,8 +24,8 @@ export const withReduxStore = (
context.reduxStore = reduxStore

let appProps = {}
if (typeof App.getInitialProps === 'function') {
appProps = await App.getInitialProps(appContext)
if (typeof Application.getInitialProps === 'function') {
appProps = await Application.getInitialProps(appContext)
}

return {
Expand Down
8 changes: 6 additions & 2 deletions front/src/domain/user/actions/signIn.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { fetchOrFail } from '@front/domain/store/fetchingRedux/fetchOrFail'

import { signIn as signInRequest } from '../api/signIn'
import { actions as dataActions } from '../reducer/data'
import { actions as signInActions } from '../reducer/signIn'
import { setCookie } from '../setCookie'

const { setToken } = dataActions

export const signIn = (login: string, password: string) =>
fetchOrFail(signInActions, async dispatch => {
const { token } = { token: `${login}+${password}` }
fetchOrFail(signInActions, async (dispatch, getApi) => {
const { token } = await signInRequest(getApi())(login, password)

setCookie(token)

dispatch(setToken(token))
})
17 changes: 17 additions & 0 deletions front/src/domain/user/actions/signUp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { fetchOrFail } from '@front/domain/store/fetchingRedux/fetchOrFail'

import { signUp as signUpRequest } from '../api/signUp'
import { actions as dataActions } from '../reducer/data'
import { actions as signUpActions } from '../reducer/signUp'
import { setCookie } from '../setCookie'

const { setToken } = dataActions

export const signUp = (login: string, password: string) =>
fetchOrFail(signUpActions, async (dispatch, getApi) => {
const { token } = await signUpRequest(getApi())(login, password)

setCookie(token)

dispatch(setToken(token))
})
14 changes: 14 additions & 0 deletions front/src/domain/user/api/signIn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { TokenModel } from '@shared/models/user/TokenModel'

import { Api } from '@front/domain/api/Api'

export const signIn = (api: Api) => (
email: string,
password: string,
): Promise<TokenModel> =>
api.client
.post('user/auth/sign-in', {
email,
password,
})
.then(response => response.data)
14 changes: 14 additions & 0 deletions front/src/domain/user/api/signUp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { TokenModel } from '@shared/models/user/TokenModel'

import { Api } from '@front/domain/api/Api'

export const signUp = (api: Api) => (
email: string,
password: string,
): Promise<TokenModel> =>
api.client
.post('user/auth/sign-up', {
email,
password,
})
.then(response => response.data)
14 changes: 14 additions & 0 deletions front/src/domain/user/hooks/useSignIn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useCallback } from 'react'
import { useDispatch } from 'redux-react-hook'

import { signIn } from '@front/domain/user/actions/signIn'

export const useSignIn = () => {
const dispatch = useDispatch()

return useCallback(
(login: string, password: string): Promise<any> =>
dispatch(signIn(login, password) as any),
[],
)
}
14 changes: 14 additions & 0 deletions front/src/domain/user/hooks/useSignUp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useCallback } from 'react'
import { useDispatch } from 'redux-react-hook'

import { signUp } from '@front/domain/user/actions/signUp'

export const useSignUp = () => {
const dispatch = useDispatch()

return useCallback(
(login: string, password: string): Promise<any> =>
dispatch(signUp(login, password) as any),
[],
)
}
3 changes: 3 additions & 0 deletions front/src/domain/user/reducer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ import { combineReducers } from 'redux'

import { reducer as dataReducer, State as DataState } from './data'
import { reducer as signInReducer, State as SignInState } from './signIn'
import { reducer as signUpReducer, State as SignUpState } from './signUp'

interface State {
signIn: SignInState
signUp: SignUpState
data: DataState
}

const reducer = combineReducers<State>({
signIn: signInReducer,
signUp: signUpReducer,
data: dataReducer,
})

Expand Down
8 changes: 8 additions & 0 deletions front/src/domain/user/reducer/signUp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createFetchingStore } from '@front/domain/store/fetchingRedux/createFetchingRedux'
import { FetchingState } from '@front/domain/store/fetchingRedux/FetchingState'

type State = FetchingState

const { reducer, actions } = createFetchingStore('user/sign-up')

export { reducer, actions, State }
3 changes: 3 additions & 0 deletions front/src/domain/user/selectors/getToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { State } from '@front/domain/store/State'

export const getToken = (state: State) => state.user.data.token
3 changes: 3 additions & 0 deletions front/src/domain/user/setCookie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Cookies from 'js-cookie'

export const setCookie = (token: string) => Cookies.set('token', token)
Loading

0 comments on commit 5ca4b41

Please sign in to comment.