Merge pull request 'feature_validation' (#12) from feature_validation into master
This commit is contained in:
commit
b4a7e33fa4
@ -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
20
src/api/edition.ts
Normal 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
37
src/api/index.ts
Normal 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
9
src/api/user.ts
Normal 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;
|
||||
}
|
@ -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 }>
|
||||
|
29
src/pages/edition/register.tsx
Normal file
29
src/pages/edition/register.tsx
Normal 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>
|
||||
}
|
@ -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") }/>;
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
|
@ -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/> },
|
||||
|
@ -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;
|
||||
|
@ -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
16
src/state/actions/user.ts
Normal 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;
|
@ -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>;
|
||||
|
@ -37,3 +37,5 @@ export const insuranceReducer: Reducer<InsuranceState, InsuranceAction | Interns
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default insuranceReducer;
|
||||
|
@ -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
31
src/state/reducer/user.ts
Normal 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;
|
@ -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);
|
||||
|
@ -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
|
||||
|
17
yarn.lock
17
yarn.lock
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user