Mise en place structure frontend

This commit is contained in:
wpetit 2020-06-15 18:10:06 +02:00
parent af47331223
commit 7f7b187296
30 changed files with 11009 additions and 2 deletions

View File

@ -1,8 +1,11 @@
build: build:
docker-compose build docker-compose build
deps:
cd frontend && npm install
up: build up: build
USER_ID=$(shell id -u) docker-compose up ( cd frontend && npm run server ) & USER_ID=$(shell id -u) docker-compose up && wait
sg: sg:
docker-compose exec -u $(shell id -u) super-graph sh docker-compose exec -u $(shell id -u) super-graph sh

View File

@ -17,6 +17,7 @@ Application de gestion des Dossiers d'Aide à la Décision (D.A.D.) à Cadoles.
```bash ```bash
git clone https://forge.cadoles.com/Cadoles/daddy.git # Cloner le projet git clone https://forge.cadoles.com/Cadoles/daddy.git # Cloner le projet
cd daddy # Se placer dans le répertoire cd daddy # Se placer dans le répertoire
make deps # Installer les dépendances NPM
make up # Démarrer l'environnement de développement make up # Démarrer l'environnement de développement
``` ```
@ -25,8 +26,8 @@ Les services suivants devraient être disponibles après démarrage de l'environ
|Service|Type|Accès|Description| |Service|Type|Accès|Description|
|-------|----|-----|-----------| |-------|----|-----|-----------|
|Application React|HTTP (UI)|http://localhost:8081/|Page d'accueil de l'application React (serveur Webpack)| |Application React|HTTP (UI)|http://localhost:8081/|Page d'accueil de l'application React (serveur Webpack)|
|Serveur GraphQL|HTTP (GraphQL)|http://localhost:8080/api/v1/graphql|Point d'entrée de l'API GraphQL|
|Interface Web GraphQL|HTTP (UI)|http://localhost:8080/|Interface Web de développement de l'API GraphQL| |Interface Web GraphQL|HTTP (UI)|http://localhost:8080/|Interface Web de développement de l'API GraphQL|
|Serveur GraphQL|HTTP (GraphQL)|http://localhost:8080/api/v1/graphql|Point d'entrée de l'API GraphQL|
|Serveur PostgreSQL|TCP/IP (PostgreSQL)|`127.0.0.1:5432`|Port de connexion à la base de données PostgreSQL de développement| |Serveur PostgreSQL|TCP/IP (PostgreSQL)|`127.0.0.1:5432`|Port de connexion à la base de données PostgreSQL de développement|
#### Fichiers/répertoires notables #### Fichiers/répertoires notables

2
frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/node_modules
/dist

10304
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

65
frontend/package.json Normal file
View File

@ -0,0 +1,65 @@
{
"name": "dadd-",
"version": "0.0.0",
"description": "Daddy",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"watch": "webpack --watch",
"server": "webpack-dev-server"
},
"repository": {
"type": "git",
"url": "git+https://forge.cadoles.com/Cadoles/daddy.git"
},
"author": "William Petit <wpetit@cadoles.com>",
"license": "AGPL-3.0",
"bugs": {
"url": "https://forge.cadoles.com/Cadoles/daddy/issues"
},
"homepage": "https://forge.cadoles.com/Cadoles/daddy#readme",
"devDependencies": {
"@babel/core": "^7.7.2",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-transform-runtime": "^7.7.4",
"@babel/preset-env": "^7.7.1",
"@babel/preset-react": "^7.7.4",
"@fortawesome/fontawesome-free": "^5.11.2",
"@types/node": "^13.13.4",
"@types/react-dom": "^16.9.7",
"@types/react-redux": "^7.1.7",
"@types/react-router-dom": "^5.1.5",
"@types/uuid": "^7.0.3",
"babel-loader": "^8.0.6",
"css-loader": "^1.0.1",
"extract-loader": "^3.1.0",
"file-loader": "^2.0.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.4.4",
"node-sass": "^4.14.0",
"redux-logger": "^3.0.6",
"resolve-url-loader": "^3.0.0",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"ts-loader": "^7.0.2",
"webpack": "^4.25.0",
"webpack-cleanup-plugin": "^0.5.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.11.0"
},
"dependencies": {
"bulma": "^0.7.2",
"bulma-switch": "^2.0.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-redux": "^7.1.3",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"redux": "^4.0.4",
"redux-saga": "^1.1.3",
"styled-components": "^4.4.1",
"typescript": "^3.8.3"
}
}

View File

@ -0,0 +1,29 @@
import React from 'react';
import { HashRouter as Router, Route, Redirect, Switch } from "react-router-dom";
import { HomePage } from './HomePage/HomePage';
import { store } from '../store/store';
import { Provider } from 'react-redux';
import { logout } from '../store/actions/logout';
export class App extends React.Component {
render() {
return (
<Provider store={store}>
<Router>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/logout" exact component={() => {
this.logout();
return <Redirect to="/" />;
}} />
<Route component={() => <Redirect to="/" />} />
</Switch>
</Router>
</Provider>
);
}
logout() {
store.dispatch(logout());
}
}

View File

@ -0,0 +1,12 @@
import React from 'react';
import { Page } from '../Page';
export function HomePage() {
return (
<Page title="Daddy - Accueil">
<div className="container is-fluid">
</div>
</Page>
);
}

View File

@ -0,0 +1,14 @@
import React from 'react';
export class Loader extends React.Component {
render() {
return (
<div className="loader-container">
<div className="lds-ripple">
<div></div>
<div></div>
</div>
</div>
)
}
}

View File

@ -0,0 +1,26 @@
import React, { PropsWithChildren } from 'react';
export interface ModalProps {
active: boolean
showCloseButton?: boolean
onClose?: (evt: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
}
export class Modal extends React.PureComponent<PropsWithChildren<ModalProps>> {
render() {
const { children, active, showCloseButton, onClose } = this.props;
return (
<div className={`modal ${active ? 'is-active': ''}`}>
<div className="modal-background"></div>
<div className="modal-content">
{children}
</div>
{
showCloseButton ?
<button onClick={onClose} className="modal-close is-large" aria-label="close"></button> :
null
}
</div>
);
}
}

View File

@ -0,0 +1,36 @@
import React from 'react';
import logo from '../resources/logo.svg';
export class Navbar extends React.PureComponent {
render() {
return (
<nav className="navbar" role="navigation" aria-label="main navigation">
<div className="container is-fluid">
<div className="navbar-brand">
<a className="navbar-item" href="#/">
<img src={logo} style={{marginRight:'5px',width:'28px',height:'28px'}} />
<h1 className="is-size-4">Daddy</h1>
</a>
<a role="button" className="navbar-burger" aria-label="menu" aria-expanded="false">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div className="navbar-menu">
<div className="navbar-end">
<div className="navbar-item">
<a className="button is-small" href="#/logout">
<span className="icon">
<i className="fas fa-sign-out-alt"></i>
</span>
<span>Se déconnecter</span>
</a>
</div>
</div>
</div>
</div>
</nav>
);
}
}

View File

@ -0,0 +1,30 @@
import React from 'react';
import { Navbar } from './Navbar';
export interface PageProps {
title?: string
}
export class Page extends React.PureComponent<PageProps> {
render() {
return (
<React.Fragment>
<Navbar />
{this.props.children}
</React.Fragment>
);
}
componentDidMount() {
this.updateTitle();
}
componentDidUpdate() {
this.updateTitle();
}
updateTitle() {
const { title } = this.props;
if (title !== undefined) window.document.title = title;
}
}

4
frontend/src/custom.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module "*.svg" {
const content: any;
export default content;
}

20
frontend/src/index.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Daddy</title>
<% for (var css in htmlWebpackPlugin.files.css) { %>
<link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
<% } %>
<% if (htmlWebpackPlugin.files.favicon) { %>
<link rel="shortcut icon" href="<%= htmlWebpackPlugin.files.favicon%>">
<% } %>
</head>
<body>
<div id="app" class="is-fullheight"></div>
<% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
<script type="text/javascript" src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
<% } %>
</body>
</html>

15
frontend/src/index.tsx Normal file
View File

@ -0,0 +1,15 @@
import './sass/_all.scss';
import React from 'react';
import ReactDOM from 'react-dom';
import { App } from './components/App';
import '@fortawesome/fontawesome-free/js/fontawesome'
import '@fortawesome/fontawesome-free/js/solid'
import '@fortawesome/fontawesome-free/js/regular'
import '@fortawesome/fontawesome-free/js/brands'
import './resources/favicon.png';
ReactDOM.render(
<App />,
document.getElementById('app')
);

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="256px"
height="256px"
viewBox="0 0 256 256"
version="1.1"
id="SVGRoot"
sodipodi:docname="daddysvg.svg"
inkscape:export-filename="/home/benjamin/Images/daddysvg.svg.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
inkscape:version="0.92.4 5da689c313, 2019-01-14">
<defs
id="defs3785">
<inkscape:path-effect
effect="skeletal"
id="path-effect11307"
is_visible="true"
pattern="M 0,5 C 0,2.24 2.24,0 5,0 7.76,0 10,2.24 10,5 10,7.76 7.76,10 5,10 2.24,10 0,7.76 0,5 Z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="skeletal"
id="path-effect11303"
is_visible="true"
pattern="M 0,5 C 0,2.24 2.24,0 5,0 7.76,0 10,2.24 10,5 10,7.76 7.76,10 5,10 2.24,10 0,7.76 0,5 Z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="skeletal"
id="path-effect11299"
is_visible="true"
pattern="M 0,5 C 0,2.24 2.24,0 5,0 7.76,0 10,2.24 10,5 10,7.76 7.76,10 5,10 2.24,10 0,7.76 0,5 Z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="bspline"
id="path-effect11297"
is_visible="true"
weight="33.333333"
steps="2"
helper_size="0"
apply_no_weight="true"
apply_with_weight="true"
only_selected="false" />
<inkscape:path-effect
effect="skeletal"
id="path-effect4672"
is_visible="true"
pattern="M 0,5 C 0,2.24 2.24,0 5,0 7.76,0 10,2.24 10,5 10,7.76 7.76,10 5,10 2.24,10 0,7.76 0,5 Z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="bspline"
id="path-effect4670"
is_visible="true"
weight="33.333333"
steps="2"
helper_size="0"
apply_no_weight="true"
apply_with_weight="true"
only_selected="false" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8284271"
inkscape:cx="47.812546"
inkscape:cy="121.69356"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1865"
inkscape:window-height="1029"
inkscape:window-x="55"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:grid-bbox="true">
<inkscape:grid
type="xygrid"
id="grid11360" />
</sodipodi:namedview>
<metadata
id="metadata3788">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:#058eff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
d="m 32.583136,21.416152 c 0.432923,-0.183441 1.65138,1.815536 3.85459,5.558711 1.876009,3.187273 5.638052,9.780818 10.540554,14.926476 4.902145,5.186689 12.7877,10.329902 22.509118,11.171913 5.287649,0.501505 10.82326,0.01212 16.981963,-1.006244 6.160916,-1.036929 12.451394,-2.633697 19.096969,-4.720546 5.92081,-1.862873 11.96851,-4.083209 18.09205,-6.704773 4.70577,-2.01913 9.47684,-4.242646 13.98868,-6.84685 2.78157,-1.651835 5.40309,-3.206 7.1713,-5.122172 0.72442,-1.142891 1.45398,-1.672372 1.05724,-1.417617 0.32165,0.358747 -0.21409,0.06426 -0.74301,-0.731311 -1.10708,-1.096321 -2.77281,-2.009494 -4.71571,-3.103565 0,0 -2e-5,-8e-6 -2e-5,-8e-6 -5.02038,-2.724381 -10.68908,-5.025077 -16.52146,-7.931398 -2.71668,-1.344975 -5.54564,-2.85375 -8.31782,-4.709758 0,0 -2e-5,-10e-6 -2e-5,-10e-6 -2.15905,-1.3673133 -4.52019,-3.2751851 -6.52692,-6.0493324 -1.61911,-2.4211048 -2.44027,-5.21000004 -2.40705,-8.0392852 0,0 0,-2.59e-5 0,-2.59e-5 0.09,-2.7949565 0.69429,-5.2119617 1.41807,-7.2341845 0.42972,-1.302744 0.886,-2.553107 1.33877,-3.764434 1.25978,-3.370408 2.45493,-6.346665 2.94174,-9.192843 0.88436,-4.684509 0.5051,-9.593748 -1.02641,-13.97674 -1.81489,-5.355405 -5.58765,-10.060675 -10.09284,-13.565898 -6.185708,-4.82811 -13.850872,-7.899477 -21.424861,-10.19356 -7.846124,-2.284214 -15.31769,-3.712304 -22.49963,-3.291965 0,0 -10e-6,4e-6 -10e-6,4e-6 -4.941703,0.320675 -9.556379,1.724903 -12.759349,4.515646 -3.340828,2.680913 -5.806968,6.910841 -8.090315,11.031815 -6.254309,11.724587 -9.740983,22.044246 -10.398302,32.001908 -0.845296,10.957356 2.454185,22.2768522 4.090997,27.981816 1.895369,6.606141 3.015663,10.171026 2.441687,10.414234 -0.439344,0.186161 -2.566206,-2.934783 -5.446076,-9.308474 -2.557029,-5.6591822 -6.989301,-16.7224702 -7.020222,-29.295099 0.07855,-11.034421 3.227368,-22.756907 9.435014,-35.331317 2.142237,-4.508984 5.001492,-9.589461 9.535428,-13.760978 4.883182,-4.244584 11.147908,-6.571491 17.615613,-7.025057 10e-7,5e-6 1.6e-5,-10e-7 1.6e-5,-10e-7 8.592767,-0.650065 17.191925,0.693474 25.668062,3.084144 8.282922,2.247398 17.023837,5.688766 24.741208,11.430798 5.91486,4.416957 10.85903,10.581991 13.55164,18.017862 2.14173,6.040892 2.69157,12.662434 1.49516,19.106381 -0.80091,4.041083 -2.14135,7.582602 -3.37362,10.870104 -0.44272,1.1811067 -0.86613,2.3161062 -1.24092,3.4206913 -0.51854,1.6952301 -0.95487,3.091231 -0.91259,4.1877256 0,0 0,7.6e-6 0,7.6e-6 -0.0118,0.8349371 0.21925,1.7074536 0.6526,2.28958094 0.66465,1.13442943 2.14357,2.18347446 3.86618,3.44382626 0,0 2e-5,8.3e-6 2e-5,8.3e-6 2.17771,1.4948012 4.63292,2.8070937 7.18494,4.1111063 5.3624,2.7221697 11.13485,5.1455167 16.74875,8.2559417 0,0 2e-5,8e-6 2e-5,8e-6 2.13483,1.143298 4.46579,2.583477 6.67927,4.65126 1.80636,1.426501 3.29256,4.006836 3.65258,7.555011 -0.34386,3.964935 -2.0161,6.713128 -3.85914,8.18127 -3.01783,2.949299 -6.35207,4.9621 -9.36964,6.570362 -5.03606,2.76332 -10.17639,5.028534 -15.13018,6.999205 -6.47792,2.583408 -12.87124,4.723785 -19.12085,6.469439 -7.00088,1.959402 -13.740645,3.397556 -20.366513,4.235038 -6.575016,0.84831 -12.824726,1.051204 -18.888844,0.183956 C 56.987331,58.018531 48.354967,51.422103 43.313256,45.056121 38.316131,38.685001 35.474015,31.775565 34.132073,28.055142 32.633631,23.900835 32.212485,21.573206 32.583136,21.416152 Z"
id="path4668"
inkscape:connector-curvature="0"
inkscape:path-effect="#path-effect4670;#path-effect4672"
inkscape:original-d="m 30,18.5 c 8.834333,17.665667 17.667667,35.332333 26.5,53 C 90.50177,58.998717 124.501,46.499 158.5,34 155.33445,29.165838 152.16767,24.332333 149,19.5 133.33512,13.999275 117.66767,8.4989999 102,2.9999999 108.66791,-4.6679494 115.33433,-12.334333 122,-20 c -2.83194,-12.832552 -5.66567,-25.667667 -8.5,-38.5 -21.497755,-5.667339 -42.999,-11.334334 -64.5,-17 -5.499176,7.49924 -10.999,14.999 -16.5,22.5 -4.499676,12.000804 -8.999,23.999 -13.5,36 3.668035,11.8335217 7.334333,23.6656665 11,35.5 z"
transform="matrix(0,-1.3691421,1.8997251,0,140.89907,239.44713)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -0,0 +1,4 @@
@import 'bulma/bulma.sass';
@import 'bulma-switch/dist/css/bulma-switch.sass';
@import '_base.scss';
@import '_loader.scss';

View File

@ -0,0 +1,21 @@
html, body {
height: 100%;
background-color: #f7f7f7;
}
.is-fullheight {
height: 100%;
}
.has-margin-top-normal {
margin-top: $size-normal;
}
.has-padding-small {
padding: 1rem;
}
#app {
display: flex;
flex-direction: column;
}

View File

@ -0,0 +1,44 @@
.loader-container {
display: flex;
width: 100%;
justify-content: center;
height: 100%;
align-items: center;
}
.lds-ripple {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
transform: scale(2);
}
.lds-ripple div {
position: absolute;
border: 4px solid $grey;
opacity: 1;
border-radius: 50%;
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}
.lds-ripple div:nth-child(2) {
animation-delay: -0.5s;
}
@keyframes lds-ripple {
0% {
top: 36px;
left: 36px;
width: 0;
height: 0;
opacity: 1;
}
100% {
top: 0px;
left: 0px;
width: 72px;
height: 72px;
opacity: 0;
}
}

View File

@ -0,0 +1,7 @@
export const LOGOUT_REQUEST = "LOGOUT_REQUEST";
export const LOGOUT_SUCCESS = "LOGOUT_SUCCESS";
export const LOGOUT_FAILURE = "LOGOUT_FAILURE";
export function logout() {
return { type: LOGOUT_REQUEST };
};

View File

@ -0,0 +1,22 @@
const defaultState = {
actions: {}
};
export function flagsReducer(state = defaultState, action: any) {
const matches = (/^(.*)_((SUCCESS)|(FAILURE)|(REQUEST))$/).exec(action.type);
if(!matches) return state;
const actionPrefix = matches[1];
return {
...state,
actions: {
...state.actions,
[actionPrefix]: {
isLoading: matches[2] === 'REQUEST'
}
}
};
}

View File

@ -0,0 +1,6 @@
import { combineReducers } from 'redux';
import { flagsReducer } from './flags';
export const rootReducer = combineReducers({
flags: flagsReducer,
});

View File

@ -0,0 +1,9 @@
import { UnauthorizedError } from "../../util/daddy";
import { put } from 'redux-saga/effects';
import { logout } from '../actions/logout';
export function* failuresSaga(action) {
if (action.error instanceof UnauthorizedError) {
yield put(logout());
}
}

View File

@ -0,0 +1,17 @@
import { call, put } from 'redux-saga/effects';
import { LOGOUT_FAILURE, LOGOUT_SUCCESS } from '../actions/logout';
export function* logoutSaga() {
try {
yield call(fetch, '/logout', { mode: 'no-cors', credentials: 'include' });
} catch(err) {
yield put({ type: LOGOUT_FAILURE, error: err });
return;
}
yield put({ type: LOGOUT_SUCCESS });
}
export function* logoutSuccessSaga() {
window.location.reload();
}

View File

@ -0,0 +1,18 @@
import { all, takeEvery, takeLatest } from 'redux-saga/effects';
import { failuresSaga } from './failure';
import { LOGOUT_REQUEST, LOGOUT_SUCCESS } from '../actions/logout';
import { logoutSaga, logoutSuccessSaga } from './logout';
export function* rootSaga() {
yield all([
takeEvery(patternFromRegExp(/^.*_FAILURE/), failuresSaga),
takeLatest(LOGOUT_REQUEST, logoutSaga),
takeLatest(LOGOUT_SUCCESS, logoutSuccessSaga)
]);
}
export function patternFromRegExp(re: any) {
return (action: any) => {
return re.test(action.type);
};
}

View File

@ -0,0 +1,7 @@
export function selectFlagsIsLoading(state: any, ...actionPrefixes: any[]) {
const { actions } = state.flags;
return actionPrefixes.reduce((isLoading, prefix) => {
if (!(prefix in actions)) return isLoading;
return isLoading || actions[prefix].isLoading;
}, false);
};

View File

@ -0,0 +1,30 @@
import { createStore, applyMiddleware, compose } from 'redux'
import createSagaMiddleware from 'redux-saga'
import { rootReducer } from './reducers/root'
import { rootSaga } from './sagas/root'
let reduxMiddlewares = [];
if (process.env.NODE_ENV !== 'production') {
const createLogger = require('redux-logger').createLogger;
const loggerMiddleware = createLogger({
collapsed: true,
diff: true
});
reduxMiddlewares.push(loggerMiddleware);
}
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
reduxMiddlewares.push(sagaMiddleware);
// mount it on the Store
export const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(...reduxMiddlewares)),
)
// then run the saga
sagaMiddleware.run(rootSaga);

View File

@ -0,0 +1,22 @@
export class UnauthorizedError extends Error {
constructor(...args: any[]) {
super(...args)
Object.setPrototypeOf(this, UnauthorizedError.prototype);
}
}
export class DaddyClient {
assertOk(res: any) {
if (!res.ok) return Promise.reject(new Error('Request failed'));
return res;
}
assertAuthorization(res: any) {
if (res.status === 401 || res.status === 404) return Promise.reject(new UnauthorizedError());
return res;
}
}
export const daddy = new DaddyClient();

16
frontend/tsconfig.json Normal file
View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es5",
"module": "es6",
"lib": ["dom", "es6"],
"moduleResolution": "node",
"jsx": "react",
"strict": false,
"sourceMap": true,
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"files": [
"./src/custom.d.ts"
]
}

View File

@ -0,0 +1,75 @@
const path = require('path');
// Plugins
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WebpackCleanupPlugin = require('webpack-cleanup-plugin');
const env = process.env;
module.exports = {
mode: `${env.NODE_ENV ? env.NODE_ENV : 'production'}`,
entry: './src/index.tsx',
devtool: 'inline-source-map',
output: {
path: path.join(__dirname, 'dist')
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx"]
},
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 8081
},
module: {
rules: [{
test: /\.s(a|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {}
},
{
loader: "resolve-url-loader",
options: {}
},
{
loader: "sass-loader",
options: {
sourceMap: true,
sourceMapContents: false
}
}
]
},{
test: /\.(woff(2)?|ttf|eot|svg|png)(\?v=\d+\.\d+\.\d+)?$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: '/resources/'
}
}]
},{
test: /\.(t|j)sx?$/,
exclude: /node_modules/,
loaders: ['ts-loader']
}]
},
plugins: [
new MiniCssExtractPlugin({
filename: "css/[name].css",
chunkFilename: "css/[id].css"
}),
new HtmlWebpackPlugin({
template: './src/index.html',
inject: false,
favicon: "./src/resources/favicon.png"
}),
new WebpackCleanupPlugin({
exclude: ['resources/logo.svg']
})
]
}