feature_validation #12

Manually merged
kadet merged 2 commits from feature_validation into master 2020-09-06 22:55:02 +02:00
19 changed files with 224 additions and 43 deletions

View File

@ -21,6 +21,7 @@
"@types/yup": "^0.29.4",
"@typescript-eslint/eslint-plugin": "^2.10.0",
"@typescript-eslint/parser": "^2.10.0",
"axios": "^0.20.0",
"babel-core": "^6.26.3",
"babel-loader": "^8.1.0",
"babel-plugin-import": "^1.13.0",
@ -56,6 +57,7 @@
"redux": "^4.0.5",
"redux-devtools-extension": "^2.13.8",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0",
"sass-loader": "8.0.2",
"style-loader": "0.23.1",
"ts-loader": "^7.0.5",

20
src/api/edition.ts Normal file
View File

@ -0,0 +1,20 @@
import { axios } from "@/api/index";
const EDITIONS_ENDPOINT = "/editions";
const REGISTER_ENDPOINT = "/register";
export async function editions() {
const response = await axios.get(EDITIONS_ENDPOINT);
return response.data;
}
export async function join(key: string): Promise<boolean> {
try {
await axios.post(REGISTER_ENDPOINT, JSON.stringify(key), { headers: { "Content-Type": "application/json" } });
return true;
} catch (error) {
console.error(error);
return false;
}
}

37
src/api/index.ts Normal file
View File

@ -0,0 +1,37 @@
import Axios from "axios";
import store from "@/state/store"
import { AppState } from "@/state/reducer";
import { UserState } from "@/state/reducer/user";
import * as user from "./user";
import * as edition from "./edition";
export const axios = Axios.create({
baseURL: "http://system-praktyk-front.localhost:3000/api/",
})
axios.interceptors.request.use(config => {
const state = store.getState() as AppState;
const user = state.user as UserState;
if (!user.loggedIn) {
return config;
}
return {
...config,
headers: {
...config.headers,
Authorization: `Bearer ${ user.token }`
}
}
})
const api = {
user,
edition
}
export default api;

9
src/api/user.ts Normal file
View File

@ -0,0 +1,9 @@
import { axios } from "@/api/index";
const AUTHORIZE_ENDPOINT = "/access/login"
export async function authorize(code: string): Promise<string> {
const response = await axios.get<string>(AUTHORIZE_ENDPOINT, { params: { code }});
return response.data;
}

View File

@ -3,7 +3,6 @@ import { Link, Route, Switch } from "react-router-dom"
import { route, routes } from "@/routing";
import { useSelector } from "react-redux";
import { AppState, isReady } from "@/state/reducer";
import { StudentActions } from "@/state/actions/student";
import { Trans, useTranslation } from "react-i18next";
import { Student } from "@/data";
import '@/styles/overrides.scss'
@ -14,7 +13,7 @@ import { EditionActions } from "@/state/actions/edition";
import { sampleEdition } from "@/provider/dummy/edition";
import { Edition } from "@/data/edition";
import { SettingActions } from "@/state/actions/settings";
import { useDispatch } from "@/state/actions";
import { useDispatch, UserActions } from "@/state/actions";
import { getLocale, Locale } from "@/state/reducer/settings";
import i18n from "@/i18n";
import moment from "moment";
@ -26,7 +25,7 @@ const UserMenu = (props: HTMLProps<HTMLUListElement>) => {
const { t } = useTranslation();
const handleUserLogout = () => {
dispatch({ type: StudentActions.Logout })
dispatch({ type: UserActions.Logout })
}
return <ul { ...props }>

View File

@ -0,0 +1,29 @@
import React, { useState } from "react";
import { Page } from "@/pages/base";
import { useTranslation } from "react-i18next";
import { Button, Container, TextField } from "@material-ui/core";
import api from "@/api";
export const RegisterEditionPage = () => {
const { t } = useTranslation();
const [key, setKey] = useState<string>("");
const handleRegister = () => {
api.edition.join(key);
}
return <Page>
<Page.Header maxWidth="md">
<Page.Title>{ t("edition.register") }</Page.Title>
</Page.Header>
<Container maxWidth="md">
<TextField label={ t("edition.key") } fullWidth
onChange={ (ev: React.ChangeEvent<HTMLInputElement>) => setKey(ev.currentTarget.value) }
value={ key } />
<Button onClick={ handleRegister }>{ t("edition.register") }</Button>
</Container>
</Page>
}

View File

@ -1,4 +1,4 @@
import React, { useMemo } from "react";
import React, { useEffect, useMemo } from "react";
import { Page } from "@/pages/base";
import { Button, Container, Stepper, Typography } from "@material-ui/core";
import { Link as RouterLink, Redirect } from "react-router-dom";
@ -13,6 +13,7 @@ import { ProposalStep } from "@/pages/steps/proposal";
import { PlanStep } from "@/pages/steps/plan";
import { InsuranceState } from "@/state/reducer/insurance";
import { InsuranceStep } from "@/pages/steps/insurance";
import api from "@/api";
export const MainPage = () => {
const { t } = useTranslation();
@ -24,6 +25,8 @@ export const MainPage = () => {
const missingStudentData = useMemo(() => student ? getMissingStudentData(student) : [], [student]);
useEffect(() => void api.edition.editions())
if (!student) {
return <Redirect to={ route("user_login") }/>;
}

View File

@ -1,21 +1,32 @@
import React from "react";
import React, { Dispatch } from "react";
import { Page } from "@/pages/base";
import { Button, Container, Typography } from "@material-ui/core";
import { StudentActions, useDispatch } from "@/state/actions";
import { sampleStudent } from "@/provider/dummy";
import { Action, useDispatch } from "@/state/actions";
import { useHistory } from "react-router-dom";
import { route } from "@/routing";
import { useVerticalSpacing } from "@/styles";
import { AppState } from "@/state/reducer";
import api from "@/api";
import { UserActions } from "@/state/actions/user";
import { sampleStudent } from "@/provider/dummy";
const authorizeUser = async (dispatch: Dispatch<Action>, getState: () => AppState): Promise<void> => {
const token = await api.user.authorize("test");
dispatch({
type: UserActions.Login,
token,
student: sampleStudent,
})
}
export const UserLoginPage = () => {
const dispatch = useDispatch();
const history = useHistory();
const handleSampleLogin = () => {
dispatch({
type: StudentActions.Login,
student: sampleStudent,
})
const handleSampleLogin = async () => {
await dispatch(authorizeUser);
history.push(route("home"));
}

View File

@ -5,6 +5,7 @@ import { InternshipProposalFormPage, InternshipProposalPreviewPage } from "@/pag
import { NotFoundPage } from "@/pages/errors/not-found";
import SubmitPlanPage from "@/pages/internship/plan";
import { UserLoginPage } from "@/pages/user/login";
import { RegisterEditionPage } from "@/pages/edition/register";
type Route = {
name?: string;
@ -15,6 +16,10 @@ type Route = {
export const routes: Route[] = [
{ name: "home", path: "/", exact: true, content: () => <MainPage/> },
// edition
{ name: "edition_register", path: "/edition/register", exact: true, content: () => <RegisterEditionPage/> },
// internship
{ name: "internship_proposal", path: "/internship/proposal", exact: true, content: () => <InternshipProposalFormPage/> },
{ name: "internship_proposal_preview", path: "/internship/preview/proposal", exact: true, content: () => <InternshipProposalPreviewPage/> },
{ name: "internship_plan", path: "/internship/plan", exact: true, content: () => <SubmitPlanPage/> },

View File

@ -1,25 +1,26 @@
import { StudentAction, StudentActions } from "@/state/actions/student";
import { useDispatch as useReduxDispatch } from "react-redux";
import { EditionAction, EditionActions } from "@/state/actions/edition";
import { SettingActions, SettingsAction } from "@/state/actions/settings";
import { InternshipProposalAction, InternshipProposalActions } from "@/state/actions/proposal";
import { Dispatch } from "react";
import { useDispatch as useReduxDispatch } from "react-redux";
import { InternshipPlanAction, InternshipPlanActions } from "@/state/actions/plan";
import { InsuranceAction, InsuranceActions } from "@/state/actions/insurance";
import { UserAction, UserActions } from "@/state/actions/user";
import { ThunkDispatch } from "redux-thunk";
import { AppState } from "@/state/reducer";
export * from "./base"
export * from "./edition"
export * from "./settings"
export * from "./student"
export * from "./proposal"
export * from "./plan"
export * from "./user"
export type Action = StudentAction | EditionAction | SettingsAction | InternshipProposalAction | InternshipPlanAction | InsuranceAction;
export type Action = UserAction | EditionAction | SettingsAction | InternshipProposalAction | InternshipPlanAction | InsuranceAction;
export const Actions = { ...StudentActions, ...EditionActions, ...SettingActions, ...InternshipProposalActions, ...InternshipPlanActions, ...InsuranceActions }
export const Actions = { ...UserActions, ...EditionActions, ...SettingActions, ...InternshipProposalActions, ...InternshipPlanActions, ...InsuranceActions }
export type Actions = typeof Actions;
export const useDispatch = () => useReduxDispatch<Dispatch<Action>>()
export const useDispatch = () => useReduxDispatch<ThunkDispatch<AppState, any, Action>>()
export default Actions;

View File

@ -1,16 +0,0 @@
import { Action } from "@/state/actions/base";
import { Student } from "@/data";
export enum StudentActions {
Login = 'LOGIN',
Logout = 'LOGOUT'
}
export interface LoginAction extends Action<StudentActions.Login> {
student: Student
}
export type LogoutAction = Action<StudentActions.Logout>;
export type StudentAction = LoginAction | LogoutAction;

16
src/state/actions/user.ts Normal file
View File

@ -0,0 +1,16 @@
import { Action } from "@/state/actions/base";
import { Student } from "@/data";
export enum UserActions {
Login = 'LOGIN',
Logout = 'LOGOUT'
}
export interface LoginAction extends Action<UserActions.Login> {
token: string;
student: Student;
}
export type LogoutAction = Action<UserActions.Logout>;
export type UserAction = LoginAction | LogoutAction;

View File

@ -5,7 +5,8 @@ import editionReducer from "@/state/reducer/edition";
import settingsReducer from "@/state/reducer/settings";
import internshipProposalReducer from "@/state/reducer/proposal";
import internshipPlanReducer from "@/state/reducer/plan";
import { insuranceReducer } from "@/state/reducer/insurance";
import insuranceReducer from "@/state/reducer/insurance";
import userReducer from "@/state/reducer/user";
const rootReducer = combineReducers({
student: studentReducer,
@ -14,6 +15,7 @@ const rootReducer = combineReducers({
proposal: internshipProposalReducer,
plan: internshipPlanReducer,
insurance: insuranceReducer,
user: userReducer,
})
export type AppState = ReturnType<typeof rootReducer>;

View File

@ -37,3 +37,5 @@ export const insuranceReducer: Reducer<InsuranceState, InsuranceAction | Interns
return state;
}
}
export default insuranceReducer;

View File

@ -1,16 +1,16 @@
import { Student } from "@/data";
import { StudentAction, StudentActions } from "@/state/actions/student";
import { UserAction, UserActions } from "@/state/actions/user";
export type StudentState = Student | null;
const initialStudentState: StudentState = null;
const studentReducer = (state: StudentState = initialStudentState, action: StudentAction): StudentState => {
const studentReducer = (state: StudentState = initialStudentState, action: UserAction): StudentState => {
switch (action.type) {
case StudentActions.Login:
case UserActions.Login:
return action.student;
case StudentActions.Logout:
case UserActions.Logout:
return null;
}

31
src/state/reducer/user.ts Normal file
View File

@ -0,0 +1,31 @@
import { Reducer } from "react";
import { UserAction, UserActions } from "@/state/actions/user";
export type UserState = {
loggedIn: boolean;
token?: string;
}
const initialUserState: UserState = {
loggedIn: false,
}
const userReducer: Reducer<UserState, UserAction> = (state = initialUserState, action) => {
switch (action.type) {
case UserActions.Login:
return {
...state,
loggedIn: true,
token: action.token,
}
case UserActions.Logout:
return {
loggedIn: false
};
}
return state;
}
export default userReducer;

View File

@ -1,8 +1,9 @@
import { createStore } from "redux";
import { applyMiddleware, compose, createStore } from "redux";
import rootReducer from "@/state/reducer";
import { devToolsEnhancer } from "redux-devtools-extension";
import { persistReducer, persistStore } from "redux-persist"
import sessionStorage from "redux-persist/lib/storage/session"
import thunk from "redux-thunk";
const store = createStore(
persistReducer(
@ -13,7 +14,10 @@ const store = createStore(
},
rootReducer
),
devToolsEnhancer({})
compose(
applyMiddleware(thunk),
devToolsEnhancer({})
)
);
export const persistor = persistStore(store);

View File

@ -54,6 +54,15 @@ const config = {
disableHostCheck: true,
historyApiFallback: true,
overlay: true,
proxy: {
"/api": {
target: "http://system-praktyk-front.localhost:8080/",
changeOrigin: true,
pathRewrite: {
"^/api": ''
}
}
}
},
optimization: {
usedExports: true

View File

@ -1816,6 +1816,13 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2"
integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==
axios@^0.20.0:
version "0.20.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.20.0.tgz#057ba30f04884694993a8cd07fa394cff11c50bd"
integrity sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==
dependencies:
follow-redirects "^1.10.0"
babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
@ -3943,6 +3950,11 @@ follow-redirects@^1.0.0:
dependencies:
debug "^3.0.0"
follow-redirects@^1.10.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@ -7613,6 +7625,11 @@ redux-persist@*, redux-persist@^6.0.0:
resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8"
integrity sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==
redux-thunk@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
redux@*, redux@^4.0.0, redux@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"