From 358ea24514748d8b6241505ca4da960b1d3f5d11 Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Sat, 5 Sep 2020 22:00:13 +0200 Subject: [PATCH 1/2] Communicate with API to authorize user --- package.json | 2 ++ src/api/index.ts | 13 +++++++++++++ src/api/user.ts | 9 +++++++++ src/app.tsx | 5 ++--- src/pages/user/login.tsx | 27 +++++++++++++++++++-------- src/state/actions/index.ts | 17 +++++++++-------- src/state/actions/student.ts | 16 ---------------- src/state/actions/user.ts | 16 ++++++++++++++++ src/state/reducer/index.ts | 4 +++- src/state/reducer/insurance.ts | 2 ++ src/state/reducer/student.ts | 8 ++++---- src/state/reducer/user.ts | 31 +++++++++++++++++++++++++++++++ src/state/store.ts | 8 ++++++-- yarn.lock | 17 +++++++++++++++++ 14 files changed, 133 insertions(+), 42 deletions(-) create mode 100644 src/api/index.ts create mode 100644 src/api/user.ts delete mode 100644 src/state/actions/student.ts create mode 100644 src/state/actions/user.ts create mode 100644 src/state/reducer/user.ts diff --git a/package.json b/package.json index c5eba09..f2da2fd 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..2d1e182 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,13 @@ +import Axios from "axios"; +import * as user from "./user"; + +export const axios = Axios.create({ + baseURL: "https://system-praktyk.stg.kadet.net/api", +}) + +const api = { + user, +} + +export default api; + diff --git a/src/api/user.ts b/src/api/user.ts new file mode 100644 index 0000000..ba70d5f --- /dev/null +++ b/src/api/user.ts @@ -0,0 +1,9 @@ +import { axios } from "@/api/index"; + +const AUTHORIZE_ENDPOINT = "/access/login" + +export async function authorize(code: string): Promise { + const response = await axios.get(AUTHORIZE_ENDPOINT, { params: { code }}); + + return response.data; +} diff --git a/src/app.tsx b/src/app.tsx index 1758fdb..d879206 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -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) => { const { t } = useTranslation(); const handleUserLogout = () => { - dispatch({ type: StudentActions.Logout }) + dispatch({ type: UserActions.Logout }) } return
    diff --git a/src/pages/user/login.tsx b/src/pages/user/login.tsx index d9cfe8c..89fa966 100644 --- a/src/pages/user/login.tsx +++ b/src/pages/user/login.tsx @@ -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, getState: () => AppState): Promise => { + 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")); } diff --git a/src/state/actions/index.ts b/src/state/actions/index.ts index 54ac996..a629b04 100644 --- a/src/state/actions/index.ts +++ b/src/state/actions/index.ts @@ -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>() +export const useDispatch = () => useReduxDispatch>() export default Actions; diff --git a/src/state/actions/student.ts b/src/state/actions/student.ts deleted file mode 100644 index 29c6ae0..0000000 --- a/src/state/actions/student.ts +++ /dev/null @@ -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 { - student: Student -} - -export type LogoutAction = Action; - -export type StudentAction = LoginAction | LogoutAction; - diff --git a/src/state/actions/user.ts b/src/state/actions/user.ts new file mode 100644 index 0000000..6ee23f1 --- /dev/null +++ b/src/state/actions/user.ts @@ -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 { + token: string; + student: Student; +} + +export type LogoutAction = Action; + +export type UserAction = LoginAction | LogoutAction; diff --git a/src/state/reducer/index.ts b/src/state/reducer/index.ts index ca5d238..6374056 100644 --- a/src/state/reducer/index.ts +++ b/src/state/reducer/index.ts @@ -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; diff --git a/src/state/reducer/insurance.ts b/src/state/reducer/insurance.ts index fded761..61cabc9 100644 --- a/src/state/reducer/insurance.ts +++ b/src/state/reducer/insurance.ts @@ -37,3 +37,5 @@ export const insuranceReducer: Reducer { +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; } diff --git a/src/state/reducer/user.ts b/src/state/reducer/user.ts new file mode 100644 index 0000000..ae868a6 --- /dev/null +++ b/src/state/reducer/user.ts @@ -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 = (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; diff --git a/src/state/store.ts b/src/state/store.ts index d3f3095..935b3b2 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -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); diff --git a/yarn.lock b/yarn.lock index 634fa14..f385c39 100644 --- a/yarn.lock +++ b/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" -- 2.45.2 From eb33e19992593006bc9d89424ed66339e505fe0a Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Sun, 6 Sep 2020 22:51:16 +0200 Subject: [PATCH 2/2] Add possibilty to register to editions --- src/api/edition.ts | 20 ++++++++++++++++++++ src/api/index.ts | 26 +++++++++++++++++++++++++- src/pages/edition/register.tsx | 29 +++++++++++++++++++++++++++++ src/pages/main.tsx | 5 ++++- src/routing.tsx | 5 +++++ webpack.config.js | 9 +++++++++ 6 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 src/api/edition.ts create mode 100644 src/pages/edition/register.tsx diff --git a/src/api/edition.ts b/src/api/edition.ts new file mode 100644 index 0000000..0fac55c --- /dev/null +++ b/src/api/edition.ts @@ -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 { + try { + await axios.post(REGISTER_ENDPOINT, JSON.stringify(key), { headers: { "Content-Type": "application/json" } }); + return true; + } catch (error) { + console.error(error); + return false; + } +} diff --git a/src/api/index.ts b/src/api/index.ts index 2d1e182..b118f65 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,12 +1,36 @@ 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: "https://system-praktyk.stg.kadet.net/api", + 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; diff --git a/src/pages/edition/register.tsx b/src/pages/edition/register.tsx new file mode 100644 index 0000000..bb53874 --- /dev/null +++ b/src/pages/edition/register.tsx @@ -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(""); + + const handleRegister = () => { + api.edition.join(key); + } + + return + + { t("edition.register") } + + + + ) => setKey(ev.currentTarget.value) } + value={ key } /> + + + +} diff --git a/src/pages/main.tsx b/src/pages/main.tsx index 70ee9b4..73d7cab 100644 --- a/src/pages/main.tsx +++ b/src/pages/main.tsx @@ -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 ; } diff --git a/src/routing.tsx b/src/routing.tsx index 6520bb8..3726afb 100644 --- a/src/routing.tsx +++ b/src/routing.tsx @@ -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: () => }, + // edition + { name: "edition_register", path: "/edition/register", exact: true, content: () => }, + + // internship { name: "internship_proposal", path: "/internship/proposal", exact: true, content: () => }, { name: "internship_proposal_preview", path: "/internship/preview/proposal", exact: true, content: () => }, { name: "internship_plan", path: "/internship/plan", exact: true, content: () => }, diff --git a/webpack.config.js b/webpack.config.js index dae0296..9e0e0c6 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -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 -- 2.45.2