From ff2e9c8b82aac24543d017e5006c1de0614b4eba Mon Sep 17 00:00:00 2001
From: Kacper Donat <kadet1090@gmail.com>
Date: Sat, 3 Oct 2020 13:47:58 +0200
Subject: [PATCH 1/5] Add isLoggedIn middleware

---
 src/middleware.tsx        | 15 +++++++++++++--
 src/routing.tsx           | 10 +++++-----
 src/state/reducer/user.ts |  3 +--
 3 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/src/middleware.tsx b/src/middleware.tsx
index 7d16ca3..1bb00b0 100644
--- a/src/middleware.tsx
+++ b/src/middleware.tsx
@@ -1,10 +1,11 @@
 import { Middleware, route } from "@/routing";
 import { useSelector } from "react-redux";
-import { isReady } from "@/state/reducer";
+import { AppState, isReady } from "@/state/reducer";
 import { Redirect } from "react-router-dom";
 import React from "react";
+import { UserState } from "@/state/reducer/user";
 
-export const isReadyMiddleware: Middleware<any, any> = next => {
+export const isReadyMiddleware: Middleware<any, any> = next => isLoggedInMiddleware(() => {
     const ready = useSelector(isReady);
 
     if (ready) {
@@ -12,4 +13,14 @@ export const isReadyMiddleware: Middleware<any, any> = next => {
     }
 
     return <Redirect to={ route("edition_pick") } />;
+})
+
+export const isLoggedInMiddleware: Middleware<any, any> = next => {
+    const user = useSelector<AppState>(state => state.user) as UserState;
+
+    if (user.loggedIn) {
+        return next();
+    }
+
+    return <Redirect to={ route("login") } />;
 }
diff --git a/src/routing.tsx b/src/routing.tsx
index f9ef81f..2a5eddd 100644
--- a/src/routing.tsx
+++ b/src/routing.tsx
@@ -7,7 +7,7 @@ import SubmitPlanPage from "@/pages/internship/plan";
 import { UserLoginPage } from "@/pages/user/login";
 import { RegisterEditionPage } from "@/pages/edition/register";
 import PickEditionPage from "@/pages/edition/pick";
-import { isReadyMiddleware } from "@/middleware";
+import { isLoggedInMiddleware, isReadyMiddleware } from "@/middleware";
 
 type Route = {
     name?: string;
@@ -30,16 +30,16 @@ export function processMiddlewares<TArgs extends any[]>(middleware: Middleware<a
 }
 
 export const routes: Route[] = [
-    { name: "home", path: "/", exact: true, content: () => <MainPage/>, middlewares: [ isReadyMiddleware ] },
+    { name: "home", path: "/", exact: true, content: () => <MainPage/>, middlewares: [ isLoggedInMiddleware ] },
 
     // edition
-    { name: "edition_register", path: "/edition/register", exact: true, content: () => <RegisterEditionPage/> },
-    { name: "edition_pick", path: "/edition/pick", exact: true, content: () => <PickEditionPage/> },
+    { name: "edition_register", path: "/edition/register", exact: true, content: () => <RegisterEditionPage/>, middlewares: [ isLoggedInMiddleware ] },
+    { name: "edition_pick", path: "/edition/pick", exact: true, content: () => <PickEditionPage/>, middlewares: [ isLoggedInMiddleware ] },
 
     // internship
     { name: "internship_proposal", path: "/internship/proposal", exact: true, content: () => <InternshipProposalFormPage/>, middlewares: [ isReadyMiddleware ] },
     { name: "internship_proposal_preview", path: "/internship/preview/proposal", exact: true, content: () => <InternshipProposalPreviewPage/>, middlewares: [ isReadyMiddleware ] },
-    { name: "internship_plan", path: "/internship/plan", exact: true, content: () => <SubmitPlanPage/> },
+    { name: "internship_plan", path: "/internship/plan", exact: true, content: () => <SubmitPlanPage/>, middlewares: [ isReadyMiddleware ] },
 
     // user
     { name: "user_login", path: "/user/login", content: () => <UserLoginPage/> },
diff --git a/src/state/reducer/user.ts b/src/state/reducer/user.ts
index ae868a6..1757986 100644
--- a/src/state/reducer/user.ts
+++ b/src/state/reducer/user.ts
@@ -1,4 +1,3 @@
-import { Reducer } from "react";
 import { UserAction, UserActions } from "@/state/actions/user";
 
 export type UserState = {
@@ -10,7 +9,7 @@ const initialUserState: UserState = {
     loggedIn: false,
 }
 
-const userReducer: Reducer<UserState, UserAction> = (state = initialUserState, action) => {
+const userReducer = (state: UserState = initialUserState, action: UserAction): UserState => {
     switch (action.type) {
         case UserActions.Login:
             return {
-- 
2.45.2


From 411603e3a1859a5219f7c279e0c9bdc3aee9964e Mon Sep 17 00:00:00 2001
From: Kacper Donat <kadet1090@gmail.com>
Date: Sat, 3 Oct 2020 19:50:12 +0200
Subject: [PATCH 2/5] Add ability to fill student data

---
 src/api/{page.tsx => page.ts}      |   0
 src/api/student.ts                 |   6 ++
 src/api/user.ts                    |   9 +-
 src/components/proposalPreview.tsx |   8 +-
 src/data/student.ts                |   2 +-
 src/forms/user.tsx                 | 133 +++++++++++++++++++++++++++++
 src/hooks/index.ts                 |   1 +
 src/hooks/state.ts                 |  18 ++++
 src/middleware.tsx                 |   6 +-
 src/pages/base.tsx                 |   2 +-
 src/pages/main.tsx                 |  33 ++-----
 src/pages/steps/insurance.tsx      |  11 +--
 src/pages/steps/plan.tsx           |   4 +-
 src/pages/steps/proposal.tsx       |   4 +-
 src/pages/steps/student.tsx        |  39 +++++++++
 src/pages/user/fill.tsx            |  31 +++++++
 src/pages/user/login.tsx           |   4 +-
 src/pages/user/profile.tsx         |  58 +++++++++++++
 src/routing.tsx                    |   6 +-
 src/serialization/edition.ts       |  31 +++++++
 src/serialization/index.ts         |   1 +
 src/state/reducer/edition.ts       |   5 +-
 src/state/store.ts                 |   2 +-
 translations/pl.yaml               |  24 +++++-
 webpack.config.js                  |   2 +-
 25 files changed, 382 insertions(+), 58 deletions(-)
 rename src/api/{page.tsx => page.ts} (100%)
 create mode 100644 src/forms/user.tsx
 create mode 100644 src/hooks/state.ts
 create mode 100644 src/pages/steps/student.tsx
 create mode 100644 src/pages/user/fill.tsx
 create mode 100644 src/pages/user/profile.tsx
 create mode 100644 src/serialization/edition.ts

diff --git a/src/api/page.tsx b/src/api/page.ts
similarity index 100%
rename from src/api/page.tsx
rename to src/api/page.ts
diff --git a/src/api/student.ts b/src/api/student.ts
index e93a861..49ad468 100644
--- a/src/api/student.ts
+++ b/src/api/student.ts
@@ -11,3 +11,9 @@ export async function current(): Promise<Student> {
     return studentDtoTransfer.transform(dto);
 }
 
+export async function update(student: Student): Promise<Student> {
+    const dto = studentDtoTransfer.reverseTransform(student);
+    const response = await axios.put(CURRENT_STUDENT_ENDPOINT, dto);
+
+    return student;
+}
diff --git a/src/api/user.ts b/src/api/user.ts
index e930190..831c87b 100644
--- a/src/api/user.ts
+++ b/src/api/user.ts
@@ -1,13 +1,16 @@
 import { axios } from "@/api/index";
 import { query, route } from "@/routing";
 
-const LOGIN_ENDPOINT = "/access/login"
+const LOGIN_ENDPOINT = "/access/login";
+const DEV_LOGIN_ENDPOINT = "/dev/login";
 
 const CLIENT_ID = process.env.LOGIN_CLIENT_ID || "PraktykiClientId";
 const AUTHORIZE_URL = process.env.AUTHORIZE || "https://logowanie.pg.edu.pl/oauth2.0/authorize";
 
-export async function login(code: string): Promise<string> {
-    const response = await axios.get<string>(LOGIN_ENDPOINT, { params: { code }});
+export async function login(code?: string): Promise<string> {
+    const response = code
+        ? await axios.get<string>(LOGIN_ENDPOINT, { params: { code }})
+        : await axios.get<string>(DEV_LOGIN_ENDPOINT);
 
     return response.data;
 }
diff --git a/src/components/proposalPreview.tsx b/src/components/proposalPreview.tsx
index 3babfc7..8a17cd0 100644
--- a/src/components/proposalPreview.tsx
+++ b/src/components/proposalPreview.tsx
@@ -6,6 +6,7 @@ import classNames from "classnames";
 import { useVerticalSpacing } from "@/styles";
 import moment from "moment";
 import { Label, Section } from "@/components/section";
+import { StudentPreview } from "@/pages/user/profile";
 
 export type ProposalPreviewProps = {
     proposal: Internship;
@@ -19,12 +20,7 @@ export const ProposalPreview = ({ proposal }: ProposalPreviewProps) => {
 
     return <div className={ classNames("proposal", classes.root) }>
         <div>
-            <Typography className="proposal__primary">{ proposal.intern.name } { proposal.intern.surname }</Typography>
-            <Typography className="proposal__secondary">
-                { t('internship.intern.semester', { semester: proposal.intern.semester }) }
-                { ", " }
-                { t('internship.intern.album', { album: proposal.intern.albumNumber }) }
-            </Typography>
+            <StudentPreview student={ proposal.intern } />
         </div>
 
         <Section>
diff --git a/src/data/student.ts b/src/data/student.ts
index d649fbd..fe84901 100644
--- a/src/data/student.ts
+++ b/src/data/student.ts
@@ -23,6 +23,6 @@ export function getMissingStudentData(student: Student): (keyof Student)[] {
         !!student.email || "email",
         !!student.albumNumber || "albumNumber",
         !!student.semester || "semester",
-        !!student.course || "course",
+        // !!student.course || "course",
     ].filter(x => x !== true) as (keyof Student)[];
 }
diff --git a/src/forms/user.tsx b/src/forms/user.tsx
new file mode 100644
index 0000000..9b450bc
--- /dev/null
+++ b/src/forms/user.tsx
@@ -0,0 +1,133 @@
+import { Student } from "@/data";
+import { Transformer } from "@/serialization";
+import React, { useMemo } from "react";
+import { Field, Formik, useFormikContext } from "formik";
+import api from "@/api";
+import { Button, Grid, Typography } from "@material-ui/core";
+import { TextField as TextFieldFormik } from "formik-material-ui";
+import { useTranslation } from "react-i18next";
+import { Actions } from "@/components";
+import { Nullable } from "@/helpers";
+import * as Yup from "yup";
+import { StudentActions, useDispatch } from "@/state/actions";
+
+interface StudentFormValues {
+    firstName: string;
+    lastName: string;
+    email: string;
+    albumNumber: number | "";
+    semester: number | "";
+}
+
+type StudentFormProps = {
+    student: Student;
+}
+
+const studentToFormValuesTransformer: Transformer<Nullable<Student>, StudentFormValues, { current: Student }> = {
+    transform(subject: Nullable<Student>, context: { current: Student }): StudentFormValues {
+        return {
+            firstName: subject.name || "",
+            lastName: subject.surname || "",
+            albumNumber: subject.albumNumber || "",
+            semester: subject.semester || "",
+            email: subject.email || "",
+        };
+    },
+    reverseTransform(subject: StudentFormValues, { current }: { current: Student }): Nullable<Student> {
+        return {
+            ...current,
+            name: subject.firstName,
+            surname: subject.lastName,
+            albumNumber: subject.albumNumber ? subject.albumNumber : null,
+            semester: subject.semester ? subject.semester : null,
+            email: subject.email,
+        };
+    },
+}
+
+export const StudentForm = ({ student }: StudentFormProps) => {
+    const { t } = useTranslation();
+    const dispatch = useDispatch();
+
+    const validationSchema = useMemo(() => Yup.object<StudentFormValues>({
+        semester: Yup.number().required().min(1).max(10),
+        albumNumber: Yup.number().required(),
+        email: Yup.string().required(),
+        firstName: Yup.string().required(),
+        lastName: Yup.string().required(),
+    }), []);
+
+    const initialValues: StudentFormValues = useMemo(
+        () => studentToFormValuesTransformer.transform(student, { current: student }),
+        [ student ]
+    )
+
+
+    const handleFormSubmit = async (values: StudentFormValues) => {
+        const update = studentToFormValuesTransformer.reverseTransform(values, { current: student }) as Student;
+        const updated = await api.student.update(update);
+
+        dispatch({
+            type: StudentActions.Set,
+            student: updated,
+        })
+    }
+
+
+    const InnerForm = () => {
+        const { handleSubmit } = useFormikContext();
+
+        return <form onSubmit={ handleSubmit }>
+            <Typography variant="subtitle1">{ t("forms.student.sections.personal") }</Typography>
+            <Grid container>
+                <Grid item md={ 6 }>
+                    <Field component={ TextFieldFormik }
+                           name="firstName"
+                           label={ t("forms.student.fields.first-name") }
+                           fullWidth
+                    />
+                </Grid>
+                <Grid item md={ 6 }>
+                    <Field component={ TextFieldFormik }
+                           name="lastName"
+                           label={ t("forms.student.fields.last-name") }
+                           fullWidth
+                    />
+                </Grid>
+                <Grid item>
+                    <Field component={ TextFieldFormik }
+                           name="email"
+                           label={ t("forms.student.fields.email") }
+                           fullWidth
+                    />
+                </Grid>
+            </Grid>
+            <Typography variant="subtitle1">{ t("forms.student.sections.studies")}</Typography>
+            <Grid container>
+                <Grid item md={ 6 }>
+                    <Field component={ TextFieldFormik }
+                           name="albumNumber"
+                           label={ t("forms.student.fields.album-number") }
+                           fullWidth
+                    />
+                </Grid>
+                <Grid item md={ 6 }>
+                    <Field component={ TextFieldFormik }
+                           name="semester"
+                           label={ t("forms.student.fields.semester") }
+                           fullWidth
+                    />
+                </Grid>
+            </Grid>
+            <Actions>
+                <Button variant="contained" type="submit" color="primary">{ t("save") }</Button>
+            </Actions>
+        </form>
+    }
+
+    return <Formik initialValues={ initialValues } onSubmit={ handleFormSubmit } validationSchema={ validationSchema }>
+        <InnerForm />
+    </Formik>
+}
+
+export default StudentForm;
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
index 848cfd7..544e1f0 100644
--- a/src/hooks/index.ts
+++ b/src/hooks/index.ts
@@ -1,3 +1,4 @@
 export * from "./useProxyState"
 export * from "./useUpdateEffect"
 export * from "./useAsync"
+export * from "./state"
diff --git a/src/hooks/state.ts b/src/hooks/state.ts
new file mode 100644
index 0000000..d024dcd
--- /dev/null
+++ b/src/hooks/state.ts
@@ -0,0 +1,18 @@
+import { useSelector } from "react-redux";
+import { AppState } from "@/state/reducer";
+import { Edition, getEditionDeadlines } from "@/data/edition";
+import { editionSerializationTransformer } from "@/serialization";
+import { Student } from "@/data";
+
+export const useCurrentStudent = () => useSelector<AppState, Student | null>(
+    state => state.student
+)
+
+export const useCurrentEdition = () => useSelector<AppState, Edition | null>(
+    state => state.edition && editionSerializationTransformer.reverseTransform(state.edition)
+)
+
+export const useDeadlines = () => {
+    const edition = useCurrentEdition() as Edition;
+    return getEditionDeadlines(edition);
+}
diff --git a/src/middleware.tsx b/src/middleware.tsx
index 1bb00b0..166de95 100644
--- a/src/middleware.tsx
+++ b/src/middleware.tsx
@@ -9,7 +9,7 @@ export const isReadyMiddleware: Middleware<any, any> = next => isLoggedInMiddlew
     const ready = useSelector(isReady);
 
     if (ready) {
-        return next();
+        return <>{ next() }</>;
     }
 
     return <Redirect to={ route("edition_pick") } />;
@@ -19,8 +19,8 @@ export const isLoggedInMiddleware: Middleware<any, any> = next => {
     const user = useSelector<AppState>(state => state.user) as UserState;
 
     if (user.loggedIn) {
-        return next();
+        return <>{ next() }</>;
     }
 
-    return <Redirect to={ route("login") } />;
+    return <Redirect to={ route("user_login") } />;
 }
diff --git a/src/pages/base.tsx b/src/pages/base.tsx
index be27987..bc6e5cc 100644
--- a/src/pages/base.tsx
+++ b/src/pages/base.tsx
@@ -21,7 +21,7 @@ export const Page = ({ title, children, ...props }: PageProps) => {
     </Box>
 }
 
-Page.Header = ({ children, maxWidth = false, ...props }: PageHeaderProps) =>
+Page.Header = ({ children, maxWidth = undefined, ...props }: PageHeaderProps) =>
     <section {...props} className={classNames("page__header", props.className)}>
         <Container maxWidth={ maxWidth }>
             { children }
diff --git a/src/pages/main.tsx b/src/pages/main.tsx
index 28215eb..e6d9cf2 100644
--- a/src/pages/main.tsx
+++ b/src/pages/main.tsx
@@ -1,51 +1,34 @@
-import React, { useEffect, useMemo } from "react";
+import React 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";
+import { Container, Stepper, Typography } from "@material-ui/core";
+import { Redirect } from "react-router-dom";
 import { route } from "@/routing";
 import { useTranslation } from "react-i18next";
 import { useSelector } from "react-redux";
 import { AppState } from "@/state/reducer";
-import { getMissingStudentData, Student } from "@/data";
-import { Deadlines, Edition, getEditionDeadlines } from "@/data/edition";
+import { Student } from "@/data";
 import { Step } from "@/components";
 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";
+import { StudentStep } from "@/pages/steps/student";
+import { useDeadlines } from "@/hooks";
 
 export const MainPage = () => {
     const { t } = useTranslation();
 
     const student = useSelector<AppState, Student | null>(state => state.student);
 
-    const deadlines = useSelector<AppState, Deadlines>(state => getEditionDeadlines(state.edition as Edition)); // edition cannot be null at this point
+    const deadlines = useDeadlines();
     const insurance = useSelector<AppState, InsuranceState>(root => root.insurance);
 
-    const missingStudentData = useMemo(() => student ? getMissingStudentData(student) : [], [student]);
-
-    useEffect(() => void api.edition.available())
-
     if (!student) {
         return <Redirect to={ route("user_login") }/>;
     }
 
     function *getSteps() {
-        yield <Step label={ t('steps.personal-data.header') } completed={ missingStudentData.length === 0 } until={ deadlines.personalData } key="personal-data">
-            { missingStudentData.length > 0 && <>
-                <p>{ t('steps.personal-data.info') }</p>
-
-                <ul>
-                    { missingStudentData.map(field => <li key={ field }>{ t(`student.${ field }`) }</li>) }
-                </ul>
-
-                <Button to={ route("internship_proposal") } variant="contained" color="primary" component={ RouterLink }>
-                    { t('steps.personal-data.form') }
-                </Button>
-            </> }
-        </Step>;
-
+        yield <StudentStep key="student"/>;
         yield <ProposalStep key="proposal"/>;
         yield <PlanStep key="plan"/>;
 
diff --git a/src/pages/steps/insurance.tsx b/src/pages/steps/insurance.tsx
index 56e1a41..b005389 100644
--- a/src/pages/steps/insurance.tsx
+++ b/src/pages/steps/insurance.tsx
@@ -4,16 +4,17 @@ import { InsuranceState } from "@/state/reducer/insurance";
 import { Actions, Step } from "@/components";
 import { useTranslation } from "react-i18next";
 import React from "react";
-import { Edition, getEditionDeadlines } from "@/data/edition";
-import { Moment } from "moment";
 import { ContactAction } from "@/pages/steps/common";
+import { useDeadlines } from "@/hooks";
+import { StepProps } from "@material-ui/core";
 
-export const InsuranceStep = () => {
+export const InsuranceStep = (props: StepProps) => {
     const insurance = useSelector<AppState, InsuranceState>(root => root.insurance);
-    const deadline  = useSelector<AppState, Moment | undefined>(state => getEditionDeadlines(state.edition as Edition).insurance); // edition cannot be null at this point
+    const deadline  = useDeadlines().insurance;
+
     const { t } = useTranslation();
 
-    return <Step label={ t("steps.insurance.header") } until={ deadline } completed={ insurance.signed } active={ !insurance.signed }>
+    return <Step { ...props } label={ t("steps.insurance.header") } until={ deadline } completed={ insurance.signed } active={ !insurance.signed }>
         <p>{ t(`steps.insurance.instructions`) }</p>
         <Actions>
             <ContactAction />
diff --git a/src/pages/steps/plan.tsx b/src/pages/steps/plan.tsx
index 7a94c7f..e6f6c4c 100644
--- a/src/pages/steps/plan.tsx
+++ b/src/pages/steps/plan.tsx
@@ -9,9 +9,9 @@ import { Link as RouterLink } from "react-router-dom";
 import { Actions, Step } from "@/components";
 import React, { HTMLProps } from "react";
 import { Alert, AlertTitle } from "@material-ui/lab";
-import { Deadlines, Edition, getEditionDeadlines } from "@/data/edition";
 import { ContactAction, Status } from "@/pages/steps/common";
 import { Description as DescriptionIcon } from "@material-ui/icons";
+import { useDeadlines } from "@/hooks";
 
 const PlanActions = () => {
     const status = useSelector<AppState, SubmissionStatus>(state => getSubmissionStatus(state.plan));
@@ -74,7 +74,7 @@ export const PlanStep = (props: StepProps) => {
     const submission = useSelector<AppState, SubmissionState>(state => state.plan);
 
     const status = getSubmissionStatus(submission);
-    const deadlines = useSelector<AppState, Deadlines>(state => getEditionDeadlines(state.edition as Edition)); // edition cannot be null at this point
+    const deadlines = useDeadlines();
 
     const { sent, declined, comment } = submission;
 
diff --git a/src/pages/steps/proposal.tsx b/src/pages/steps/proposal.tsx
index f7646d9..9996ba4 100644
--- a/src/pages/steps/proposal.tsx
+++ b/src/pages/steps/proposal.tsx
@@ -6,12 +6,12 @@ import React, { HTMLProps } from "react";
 import { InternshipProposalState } from "@/state/reducer/proposal";
 import { Alert, AlertTitle } from "@material-ui/lab";
 import { Box, Button, ButtonProps, StepProps } from "@material-ui/core";
-import { Deadlines, Edition, getEditionDeadlines } from "@/data/edition";
 import { Actions, Step } from "@/components";
 import { route } from "@/routing";
 import { Link as RouterLink } from "react-router-dom";
 import { ClipboardEditOutline, FileFind } from "mdi-material-ui/index";
 import { ContactAction, Status } from "@/pages/steps/common";
+import { useDeadlines } from "@/hooks";
 
 const ProposalActions = () => {
     const status = useSelector<AppState, SubmissionStatus>(state => getSubmissionStatus(state.proposal));
@@ -69,7 +69,7 @@ export const ProposalStep = (props: StepProps) => {
 
     const submission = useSelector<AppState, SubmissionState>(state => state.proposal);
     const status = useSelector<AppState, SubmissionStatus>(state => getSubmissionStatus(state.proposal));
-    const deadlines = useSelector<AppState, Deadlines>(state => getEditionDeadlines(state.edition as Edition)); // edition cannot be null at this point
+    const deadlines = useDeadlines();
 
     const { sent, declined, comment } = submission;
 
diff --git a/src/pages/steps/student.tsx b/src/pages/steps/student.tsx
new file mode 100644
index 0000000..24eab0b
--- /dev/null
+++ b/src/pages/steps/student.tsx
@@ -0,0 +1,39 @@
+import { Button, StepProps } from "@material-ui/core";
+import { route } from "@/routing";
+import { Link as RouterLink } from "react-router-dom";
+import { Actions, Step } from "@/components";
+import React, { useMemo } from "react";
+import { useTranslation } from "react-i18next";
+import { getMissingStudentData, Student } from "@/data";
+import { useSelector } from "react-redux";
+import { AppState } from "@/state/reducer";
+import { useDeadlines } from "@/hooks";
+import { AccountDetails } from "mdi-material-ui";
+
+export const StudentStep = (props: StepProps) => {
+    const { t } = useTranslation();
+    const student = useSelector<AppState, Student | null>(state => state.student);
+    const missingStudentData = useMemo(() => student ? getMissingStudentData(student) : [], [student]);
+    const deadlines = useDeadlines();
+
+    return <Step {...props} label={ t('steps.personal-data.header') } completed={ missingStudentData.length === 0 } until={ deadlines.personalData }>
+        { missingStudentData.length > 0 ? <>
+            <p>{ t('steps.personal-data.info') }</p>
+
+            <ul>
+                { missingStudentData.map(field => <li key={ field }>{ t(`student.${ field }`) }</li>) }
+            </ul>
+
+            <Button to={ route("user_fill") } variant="contained" color="primary" component={ RouterLink }>
+                { t('steps.personal-data.actions.form') }
+            </Button>
+        </> : <>
+            <p>{ t('steps.personal-data.all-filled') }</p>
+            <Actions>
+                <Button to={ route("user_profile") } variant="outlined" color="primary" component={ RouterLink } startIcon={ <AccountDetails /> }>
+                    { t('steps.personal-data.actions.info') }
+                </Button>
+            </Actions>
+        </> }
+    </Step>
+}
diff --git a/src/pages/user/fill.tsx b/src/pages/user/fill.tsx
new file mode 100644
index 0000000..de3cc1c
--- /dev/null
+++ b/src/pages/user/fill.tsx
@@ -0,0 +1,31 @@
+import { useSelector } from "react-redux";
+import { AppState } from "@/state/reducer";
+import React from "react";
+import { Page } from "@/pages/base";
+import { useTranslation } from "react-i18next";
+import { Container, Link, Typography } from "@material-ui/core";
+import StudentForm from "@/forms/user";
+import { Student } from "@/data";
+import { Link as RouterLink } from "react-router-dom";
+import { route } from "@/routing";
+
+export const UserFillPage = () => {
+    const student = useSelector<AppState>(state => state.student) as Student;
+
+    const { t } = useTranslation();
+
+    return <Page>
+        <Page.Header maxWidth="md">
+            <Page.Breadcrumbs>
+                <Link component={ RouterLink } to={ route("home") }>{ t("pages.my-internship.header") }</Link>
+                <Typography color="textPrimary">{ t("pages.user-fill.title") }</Typography>
+            </Page.Breadcrumbs>
+            <Page.Title>{ t("pages.user-fill.title") }</Page.Title>
+        </Page.Header>
+        <Container>
+            <StudentForm student={ student } />
+        </Container>
+    </Page>
+}
+
+export default UserFillPage;
diff --git a/src/pages/user/login.tsx b/src/pages/user/login.tsx
index 46a5a00..c7dd466 100644
--- a/src/pages/user/login.tsx
+++ b/src/pages/user/login.tsx
@@ -11,7 +11,7 @@ import api from "@/api";
 import { UserActions } from "@/state/actions/user";
 import { getAuthorizeUrl } from "@/api/user";
 
-const authorizeUser = (code: string) => async (dispatch: Dispatch<Action>, getState: () => AppState): Promise<void> => {
+const authorizeUser = (code?: string) => async (dispatch: Dispatch<Action>, getState: () => AppState): Promise<void> => {
     const token = await api.user.login(code);
 
     dispatch({
@@ -34,7 +34,7 @@ export const UserLoginPage = () => {
     const query    = new URLSearchParams(useLocation().search);
 
     const handleSampleLogin = async () => {
-        await dispatch(authorizeUser("test"));
+        await dispatch(authorizeUser());
 
         history.push(route("home"));
     }
diff --git a/src/pages/user/profile.tsx b/src/pages/user/profile.tsx
new file mode 100644
index 0000000..5ebc8ba
--- /dev/null
+++ b/src/pages/user/profile.tsx
@@ -0,0 +1,58 @@
+import React from "react";
+import { Page } from "@/pages/base";
+import { useTranslation } from "react-i18next";
+import { useCurrentStudent } from "@/hooks";
+import { Box, Button, Container, Link, Paper, Typography } from "@material-ui/core";
+import { Student } from "@/data";
+import { Link as RouterLink } from "react-router-dom";
+import { route } from "@/routing";
+import { Actions } from "@/components";
+import { useVerticalSpacing } from "@/styles";
+
+type StudentPreviewProps = {
+    student: Student;
+}
+
+export const StudentPreview = ({ student }: StudentPreviewProps) => {
+    const { t } = useTranslation();
+
+    return <>
+        <Typography className="proposal__primary">{ student.name } { student.surname }</Typography>
+        <Typography className="proposal__secondary">
+            { t('internship.intern.semester', { semester: student.semester }) }
+            { ", " }
+            { t('internship.intern.album', { album: student.albumNumber }) }
+        </Typography>
+    </>;
+}
+
+export const UserProfilePage = () => {
+    const { t } = useTranslation();
+
+    const student = useCurrentStudent() as Student;
+    const spacing = useVerticalSpacing(3);
+
+    return <Page>
+        <Page.Header>
+            <Page.Breadcrumbs>
+                <Link component={ RouterLink } to={ route("home") }>{ t("pages.my-internship.header") }</Link>
+                <Typography color="textPrimary">{ t("pages.user-profile.title") }</Typography>
+            </Page.Breadcrumbs>
+            <Page.Title>{ t('pages.user-profile.title') }</Page.Title>
+        </Page.Header>
+        <Container className={ spacing.root }>
+            <Paper>
+                <Box p={2}>
+                    <StudentPreview student={ student } />
+                </Box>
+            </Paper>
+            <Actions>
+                <Button variant="contained" component={ RouterLink } to={ route("home") }>
+                    { t('go-back') }
+                </Button>
+            </Actions>
+        </Container>
+    </Page>
+}
+
+export default UserProfilePage;
diff --git a/src/routing.tsx b/src/routing.tsx
index 2a5eddd..f7d37e5 100644
--- a/src/routing.tsx
+++ b/src/routing.tsx
@@ -8,6 +8,8 @@ import { UserLoginPage } from "@/pages/user/login";
 import { RegisterEditionPage } from "@/pages/edition/register";
 import PickEditionPage from "@/pages/edition/pick";
 import { isLoggedInMiddleware, isReadyMiddleware } from "@/middleware";
+import UserFillPage from "@/pages/user/fill";
+import UserProfilePage from "@/pages/user/profile";
 
 type Route = {
     name?: string;
@@ -30,7 +32,7 @@ export function processMiddlewares<TArgs extends any[]>(middleware: Middleware<a
 }
 
 export const routes: Route[] = [
-    { name: "home", path: "/", exact: true, content: () => <MainPage/>, middlewares: [ isLoggedInMiddleware ] },
+    { name: "home", path: "/", exact: true, content: () => <MainPage/>, middlewares: [ isReadyMiddleware ] },
 
     // edition
     { name: "edition_register", path: "/edition/register", exact: true, content: () => <RegisterEditionPage/>, middlewares: [ isLoggedInMiddleware ] },
@@ -43,6 +45,8 @@ export const routes: Route[] = [
 
     // user
     { name: "user_login", path: "/user/login", content: () => <UserLoginPage/> },
+    { name: "user_fill", path: "/user/data", content: () => <UserFillPage/>, middlewares: [ isLoggedInMiddleware ] },
+    { name: "user_profile", path: "/user/profile", content: () => <UserProfilePage/>, middlewares: [ isLoggedInMiddleware ] },
 
     // fallback route for 404 pages
     { name: "fallback", path: "*", content: () => <FallbackPage/> }
diff --git a/src/serialization/edition.ts b/src/serialization/edition.ts
new file mode 100644
index 0000000..ae4555c
--- /dev/null
+++ b/src/serialization/edition.ts
@@ -0,0 +1,31 @@
+import { Serializable, SerializationTransformer } from "@/serialization/types";
+import { Edition } from "@/data/edition";
+import { momentSerializationTransformer } from "@/serialization/moment";
+import { Moment } from "moment";
+
+export const editionSerializationTransformer: SerializationTransformer<Edition> = {
+    transform(subject: Edition, context?: unknown): Serializable<Edition> {
+        return {
+            course: subject.course,
+            minimumInternshipHours: subject.minimumInternshipHours,
+            maximumInternshipHours: subject.maximumInternshipHours,
+            proposalDeadline: momentSerializationTransformer.transform(subject.proposalDeadline),
+            reportingEnd: momentSerializationTransformer.transform(subject.reportingEnd),
+            reportingStart: momentSerializationTransformer.transform(subject.reportingStart),
+            startDate: momentSerializationTransformer.transform(subject.startDate),
+            endDate: momentSerializationTransformer.transform(subject.endDate),
+        }
+    },
+    reverseTransform(subject: Serializable<Edition>, context?: unknown): Edition {
+        return {
+            course: subject.course,
+            minimumInternshipHours: subject.minimumInternshipHours,
+            maximumInternshipHours: subject.maximumInternshipHours,
+            proposalDeadline: momentSerializationTransformer.reverseTransform(subject.proposalDeadline) as Moment,
+            reportingEnd: momentSerializationTransformer.reverseTransform(subject.reportingEnd) as Moment,
+            reportingStart: momentSerializationTransformer.reverseTransform(subject.reportingStart) as Moment,
+            startDate: momentSerializationTransformer.reverseTransform(subject.startDate) as Moment,
+            endDate: momentSerializationTransformer.reverseTransform(subject.endDate) as Moment,
+        }
+    },
+}
diff --git a/src/serialization/index.ts b/src/serialization/index.ts
index e6efcb2..1f6c52f 100644
--- a/src/serialization/index.ts
+++ b/src/serialization/index.ts
@@ -1,3 +1,4 @@
 export * from "./internship"
 export * from "./moment"
 export * from "./types"
+export * from "./edition"
diff --git a/src/state/reducer/edition.ts b/src/state/reducer/edition.ts
index ef696f9..93847c2 100644
--- a/src/state/reducer/edition.ts
+++ b/src/state/reducer/edition.ts
@@ -1,14 +1,15 @@
 import { Edition } from "@/data/edition";
 import { EditionAction, EditionActions } from "@/state/actions/edition";
+import { editionSerializationTransformer, Serializable } from "@/serialization";
 
-export type EditionState = Edition | null;
+export type EditionState = Serializable<Edition> | null;
 
 const initialEditionState: EditionState = null;
 
 const editionReducer = (state: EditionState = initialEditionState, action: EditionAction): EditionState => {
     switch (action.type) {
         case EditionActions.Set:
-            return action.edition;
+            return editionSerializationTransformer.transform(action.edition);
     }
 
     return state;
diff --git a/src/state/store.ts b/src/state/store.ts
index 935b3b2..7168bf7 100644
--- a/src/state/store.ts
+++ b/src/state/store.ts
@@ -10,7 +10,7 @@ const store = createStore(
         {
             key: 'state',
             storage: sessionStorage,
-            blacklist: ['edition']
+            blacklist: []
         },
         rootReducer
     ),
diff --git a/translations/pl.yaml b/translations/pl.yaml
index 2d7554f..d077211 100644
--- a/translations/pl.yaml
+++ b/translations/pl.yaml
@@ -11,7 +11,7 @@ left: jeszcze {{ left, humanize }}
 
 confirm: zatwierdź
 go-back: wstecz
-
+save: zapisz
 make-changes: wprowadź zmiany
 review: podgląd
 fix-errors: popraw uwagi
@@ -39,8 +39,22 @@ pages:
     my-editions: "Moje praktyki"
     pick: "wybierz"
     register: "Zapisz się do edycji praktyk"
+  user-fill:
+    title: "Uzupełnij swoje dane"
+  user-profile:
+    title: "Moje dane"
 
 forms:
+  student:
+    fields:
+      first-name: Imię
+      last-name: Nazwisko
+      email: Kontaktowy adres e-mail
+      album-number: Numer albumu
+      semester: Aktualny semestr studiów
+    sections:
+      personal: "Dane osobowe"
+      studies: "Dane kierunkowe"
   internship:
     fields:
       start-date: Data rozpoczęcia praktyki
@@ -127,11 +141,15 @@ internship:
 
 steps:
   personal-data:
-    header: "Uzupełnienie informacji"
+    header: "Uzupełnienie danych"
     info: >
       Twój profil jest niekompletny. W celu kontynuacji praktyki musisz uzupełnić informacje podane poniżej. Jeżeli masz
       problem z uzupełnieniem tych informacji - skontaktuj się z pełnomocnikiem ds. praktyk dla Twojego kierunku.
-    form: "Uzupełnij dane"
+    all-filled: >
+      Wypełniłeś wszystkie wymagane informacje o sobie.
+    actions:
+      form: "Uzupełnij dane"
+      info: $t(pages.user-profile.title)
   internship-proposal:
     header: "Zgłoszenie praktyki"
     info:
diff --git a/webpack.config.js b/webpack.config.js
index 6585967..201baa4 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -59,7 +59,7 @@ const config = {
         port: parseInt(process.env.APP_PORT || "3000"),
         proxy: {
             "/api": {
-                target: "http://system-praktyk-front.localhost:8080/",
+                target: "https://system-praktyk.stg.kadet.net/api/",
                 changeOrigin: true,
                 pathRewrite: {
                     "^/api": ''
-- 
2.45.2


From 8b2523572d6e515647ac1752fa771a4c7a64495e Mon Sep 17 00:00:00 2001
From: Kacper Donat <kadet1090@gmail.com>
Date: Sun, 4 Oct 2020 14:18:25 +0200
Subject: [PATCH 3/5] Add internship registration submission

---
 package.json                           |  1 +
 src/api/companies.ts                   | 18 ++++++
 src/api/dto/internship-registration.ts | 33 +++++++++++
 src/api/dto/type.ts                    | 34 +++++++++++
 src/api/edition.ts                     | 19 +++++-
 src/api/index.ts                       | 12 +++-
 src/api/internship.ts                  | 11 ++++
 src/api/type.ts                        | 12 ++++
 src/api/user.ts                        |  2 +-
 src/components/proposalPreview.tsx     |  4 +-
 src/data/internship.ts                 | 49 ++--------------
 src/forms/company.tsx                  | 24 ++++++--
 src/forms/internship.tsx               | 66 ++++++++++-----------
 src/forms/student.tsx                  |  7 ++-
 src/hooks/index.ts                     |  1 +
 src/hooks/providers.ts                 | 15 +++++
 src/pages/edition/pick.tsx             | 13 ++++-
 src/routing.tsx                        |  5 +-
 src/state/reducer/insurance.ts         |  7 ---
 yarn.lock                              | 80 ++++++++++++++++++++++++++
 20 files changed, 309 insertions(+), 104 deletions(-)
 create mode 100644 src/api/companies.ts
 create mode 100644 src/api/dto/internship-registration.ts
 create mode 100644 src/api/dto/type.ts
 create mode 100644 src/api/internship.ts
 create mode 100644 src/api/type.ts
 create mode 100644 src/hooks/providers.ts

diff --git a/package.json b/package.json
index f2da2fd..df24a9f 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
     "html-webpack-plugin": "4.0.0-beta.11",
     "i18next": "^19.6.0",
     "i18next-browser-languagedetector": "^5.0.0",
+    "jsonwebtoken": "^8.5.1",
     "material-ui-dropzone": "^3.3.0",
     "mdi-material-ui": "^6.17.0",
     "moment": "^2.26.0",
diff --git a/src/api/companies.ts b/src/api/companies.ts
new file mode 100644
index 0000000..838348f
--- /dev/null
+++ b/src/api/companies.ts
@@ -0,0 +1,18 @@
+import { Company, Office } from "@/data";
+import { axios } from "@/api/index";
+import { prepare, query } from "@/routing";
+
+export const COMPANY_SEARCH_ENDPOINT = '/companies';
+export const COMPANY_OFFICES_ENDPOINT = '/companies/:id'
+
+export async function search(name: string): Promise<Company[]> {
+    const companies = await axios.get<Company[]>(query(COMPANY_SEARCH_ENDPOINT, { Name: name }));
+
+    return companies.data;
+}
+
+export async function offices(id: string): Promise<Office[]> {
+    const response = await axios.get<Office[]>(prepare(COMPANY_OFFICES_ENDPOINT, { id }));
+
+    return response.data;
+}
diff --git a/src/api/dto/internship-registration.ts b/src/api/dto/internship-registration.ts
new file mode 100644
index 0000000..b7a94a7
--- /dev/null
+++ b/src/api/dto/internship-registration.ts
@@ -0,0 +1,33 @@
+import { Identifiable, Internship, Mentor } from "@/data";
+import { OneWayTransformer } from "@/serialization";
+import { Nullable } from "@/helpers";
+
+export interface InternshipRegistrationUpdateCompany {
+    id: string,
+    branchOffice: Identifiable,
+}
+
+export interface InternshipRegistrationUpdate {
+    company: InternshipRegistrationUpdateCompany,
+    start: string,
+    end: string,
+    type: number,
+    mentor: Mentor,
+}
+
+export const internshipRegistrationUpdateTransformer: OneWayTransformer<Nullable<Internship>, Nullable<InternshipRegistrationUpdate>> = {
+    transform(subject: Nullable<Internship>, context?: unknown): Nullable<InternshipRegistrationUpdate> {
+        return {
+            start: subject?.startDate?.toISOString() || null,
+            end: subject?.endDate?.toISOString() || null,
+            type: parseInt(subject?.type?.id || "0"),
+            mentor: subject.mentor,
+            company: {
+                id: subject?.company?.id as string,
+                branchOffice: {
+                    id: subject?.office?.id
+                },
+            }
+        }
+    }
+}
diff --git a/src/api/dto/type.ts b/src/api/dto/type.ts
new file mode 100644
index 0000000..213dcb1
--- /dev/null
+++ b/src/api/dto/type.ts
@@ -0,0 +1,34 @@
+import { Identifiable, InternshipType } from "@/data";
+import { Transformer } from "@/serialization";
+
+export interface InternshipTypeDTO extends Identifiable {
+    label: string;
+    labelEng: string;
+    description?: string;
+    descriptionEng?: string;
+}
+
+export const internshipTypeDtoTransformer: Transformer<InternshipTypeDTO, InternshipType> = {
+    transform(subject: InternshipTypeDTO, context?: unknown): InternshipType {
+        return {
+            id: subject.id,
+            label: {
+                pl: subject.label,
+                en: subject.labelEng
+            },
+            description: subject.description ? {
+                pl: subject.description,
+                en: subject.descriptionEng || ""
+            } : undefined
+        }
+    },
+    reverseTransform(subject: InternshipType, context?: unknown): InternshipTypeDTO {
+        return {
+            id: subject.id,
+            label: subject.label.pl,
+            labelEng: subject.label.en,
+            description: subject.description?.pl || undefined,
+            descriptionEng: subject.description?.en || undefined,
+        }
+    },
+}
diff --git a/src/api/edition.ts b/src/api/edition.ts
index 6164634..1a04c9b 100644
--- a/src/api/edition.ts
+++ b/src/api/edition.ts
@@ -5,7 +5,9 @@ import { EditionDTO, editionDtoTransformer, editionTeaserDtoTransformer } from "
 
 const EDITIONS_ENDPOINT = "/editions";
 const EDITION_INFO_ENDPOINT = "/editions/:key";
-const REGISTER_ENDPOINT = "/register";
+const EDITION_CURRENT_ENDPOINT = "/editions/current";
+const EDITION_REGISTER_ENDPOINT = "/register";
+const EDITION_LOGIN_ENDPOINT = "/access/loginEdition";
 
 export async function available() {
     const response = await axios.get(EDITIONS_ENDPOINT);
@@ -15,7 +17,7 @@ export async function available() {
 
 export async function join(key: string): Promise<boolean> {
     try {
-        await axios.post(REGISTER_ENDPOINT, JSON.stringify(key), { headers: { "Content-Type": "application/json" } });
+        await axios.post(EDITION_REGISTER_ENDPOINT, JSON.stringify(key), { headers: { "Content-Type": "application/json" } });
         return true;
     } catch (error) {
         console.error(error);
@@ -29,3 +31,16 @@ export async function get(key: string): Promise<Edition | null> {
 
     return editionDtoTransformer.transform(dto);
 }
+
+export async function current(): Promise<Edition> {
+    const response = await axios.get<EditionDTO>(EDITION_CURRENT_ENDPOINT);
+    const dto = response.data;
+
+    return editionDtoTransformer.transform(dto);
+}
+
+export async function login(key: string): Promise<string> {
+    const response = await axios.post<string>(EDITION_LOGIN_ENDPOINT, JSON.stringify(key), { headers: { "Content-Type": "application/json" } })
+
+    return response.data;
+}
diff --git a/src/api/index.ts b/src/api/index.ts
index 3432330..cb20750 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -5,8 +5,11 @@ import { UserState } from "@/state/reducer/user";
 
 import * as user from "./user";
 import * as edition from "./edition";
-import * as page from "./page"
-import * as student from "./student"
+import * as page from "./page";
+import * as student from "./student";
+import * as type from "./type";
+import * as companies from "./companies";
+import * as internship from "./internship";
 
 export const axios = Axios.create({
     baseURL: process.env.API_BASE_URL || "https://system-praktyk.stg.kadet.net/api/",
@@ -33,7 +36,10 @@ const api = {
     user,
     edition,
     page,
-    student
+    student,
+    type,
+    companies,
+    internship,
 }
 
 export default api;
diff --git a/src/api/internship.ts b/src/api/internship.ts
new file mode 100644
index 0000000..3096b15
--- /dev/null
+++ b/src/api/internship.ts
@@ -0,0 +1,11 @@
+import { InternshipRegistrationUpdate } from "@/api/dto/internship-registration";
+import { axios } from "@/api/index";
+import { Nullable } from "@/helpers";
+
+const INTERNSHIP_ENDPOINT = '/internshipRegistration';
+
+export async function update(internship: Nullable<InternshipRegistrationUpdate>): Promise<boolean> {
+    const response = await axios.put(INTERNSHIP_ENDPOINT, internship);
+
+    return true;
+}
diff --git a/src/api/type.ts b/src/api/type.ts
new file mode 100644
index 0000000..aa6c577
--- /dev/null
+++ b/src/api/type.ts
@@ -0,0 +1,12 @@
+import { InternshipType } from "@/data";
+import { axios } from "@/api/index";
+import { InternshipTypeDTO, internshipTypeDtoTransformer } from "@/api/dto/type";
+
+const AVAILABLE_INTERNSHIP_TYPES = '/internshipTypes';
+
+export async function available(): Promise<InternshipType[]> {
+    const response = await axios.get<InternshipTypeDTO[]>(AVAILABLE_INTERNSHIP_TYPES);
+    const dtos = response.data;
+
+    return dtos.map(dto => internshipTypeDtoTransformer.transform(dto));
+}
diff --git a/src/api/user.ts b/src/api/user.ts
index 831c87b..cd2a0de 100644
--- a/src/api/user.ts
+++ b/src/api/user.ts
@@ -9,7 +9,7 @@ const AUTHORIZE_URL = process.env.AUTHORIZE || "https://logowanie.pg.edu.pl/oaut
 
 export async function login(code?: string): Promise<string> {
     const response = code
-        ? await axios.get<string>(LOGIN_ENDPOINT, { params: { code }})
+        ? await axios.post<string>(LOGIN_ENDPOINT, JSON.stringify(code), { headers: { 'Content-Type': 'application/json' } })
         : await axios.get<string>(DEV_LOGIN_ENDPOINT);
 
     return response.data;
diff --git a/src/components/proposalPreview.tsx b/src/components/proposalPreview.tsx
index 8a17cd0..b452875 100644
--- a/src/components/proposalPreview.tsx
+++ b/src/components/proposalPreview.tsx
@@ -1,4 +1,4 @@
-import { Internship, internshipTypeLabels } from "@/data";
+import { Internship } from "@/data";
 import React from "react";
 import { Typography } from "@material-ui/core";
 import { useTranslation } from "react-i18next";
@@ -39,7 +39,7 @@ export const ProposalPreview = ({ proposal }: ProposalPreviewProps) => {
 
         <Section>
             <Label>{ t('internship.sections.kind') }</Label>
-            <Typography className="proposal__primary">{ internshipTypeLabels[proposal.type].label }</Typography>
+            <Typography className="proposal__primary">{ proposal.type.label.pl }</Typography>
         </Section>
 
         <Section>
diff --git a/src/data/internship.ts b/src/data/internship.ts
index 4cd82ea..29733f4 100644
--- a/src/data/internship.ts
+++ b/src/data/internship.ts
@@ -1,52 +1,11 @@
 import { Moment } from "moment";
-import { Identifiable } from "./common";
+import { Identifiable, Multilingual } from "./common";
 import { Student } from "@/data/student";
 import { Company, Office } from "@/data/company";
 
-export enum InternshipType {
-    FreeInternship = "FreeInternship",
-    GraduateInternship = "GraduateInternship",
-    FreeApprenticeship = "FreeApprenticeship",
-    PaidApprenticeship = "PaidApprenticeship",
-    ForeignInternship = "ForeignInternship",
-    UOP = "UOP",
-    UD = "UD",
-    UZ = "UZ",
-    Other = "Other",
-}
-
-export const internshipTypeLabels: { [type in InternshipType]: { label: string, description?: string } } = {
-    [InternshipType.FreeInternship]: {
-        label: "Umowa o organizację praktyki",
-        description: "Praktyka bezpłatna"
-    },
-    [InternshipType.GraduateInternship]: {
-        label: "Umowa o praktykę absolwencką"
-    },
-    [InternshipType.FreeApprenticeship]: {
-        label: "Umowa o staż bezpłatny"
-    },
-    [InternshipType.PaidApprenticeship]: {
-        label: "Umowa o staż płatny",
-        description: "np. przemysłowy"
-    },
-    [InternshipType.ForeignInternship]: {
-        label: "Praktyka zagraniczna",
-        description: "np. IAESTE, ERASMUS"
-    },
-    [InternshipType.UOP]: {
-        label: "Umowa o pracę"
-    },
-    [InternshipType.UD]: {
-        label: "Umowa o dzieło (w tym B2B)"
-    },
-    [InternshipType.UZ]: {
-        label: "Umowa o zlecenie (w tym B2B)"
-    },
-    [InternshipType.Other]: {
-        label: "Inna",
-        description: "Należy wprowadzić samodzielnie"
-    },
+export interface InternshipType extends Identifiable {
+    label: Multilingual<string>,
+    description?: Multilingual<string>,
 }
 
 export interface InternshipProgramEntry extends Identifiable {
diff --git a/src/forms/company.tsx b/src/forms/company.tsx
index 74fbfdb..21a5300 100644
--- a/src/forms/company.tsx
+++ b/src/forms/company.tsx
@@ -1,12 +1,12 @@
-import React, { HTMLProps, useMemo } from "react";
+import React, { HTMLProps, useEffect, useMemo, useState } from "react";
 import { Company, formatAddress, Office } from "@/data";
-import { sampleCompanies } from "@/provider/dummy";
 import { Autocomplete } from "@material-ui/lab";
 import { Grid, TextField, Typography } from "@material-ui/core";
 import { InternshipFormValues } from "@/forms/internship";
 import { useTranslation } from "react-i18next";
 import { Field, useFormikContext } from "formik";
 import { TextField as TextFieldFormik } from "formik-material-ui"
+import api from "@/api";
 
 export const CompanyItem = ({ company, ...props }: { company: Company } & HTMLProps<any>) => (
     <div className="company-item" { ...props }>
@@ -27,9 +27,15 @@ export const BranchForm: React.FC = () => {
     const { t } = useTranslation();
 
     const disabled = useMemo(() => !values.companyName, [values.companyName]);
-    const offices = useMemo(() => values.company?.offices || [], [values.company]);
+    const [offices, setOffices] = useState<Office[]>([]);
     const canEdit = useMemo(() => !values.office && !disabled, [values.office, disabled]);
 
+    useEffect(() => {
+        (async () => {
+            setOffices(values.company?.id ? (await api.companies.offices(values.company?.id)) : []);
+        })()
+    }, [ values.company?.id ])
+
     const handleCityChange = (event: any, value: Office | string | null) => {
         if (typeof value === "string") {
             setValues({
@@ -143,8 +149,17 @@ export const CompanyForm: React.FunctionComponent = () => {
     const { values, setValues, errors, touched, setFieldTouched } = useFormikContext<InternshipFormValues>();
     const { t } = useTranslation();
 
+    const [input, setInput] = useState<string>("");
+    const [companies, setCompanies] = useState<Company[]>([]);
+
     const canEdit = useMemo(() => !values.company, [values.company]);
 
+    useEffect(() => {
+        (async () => {
+            setCompanies(await api.companies.search(input));
+        })()
+    }, [ input ]);
+
     const handleCompanyChange = (event: any, value: Company | string | null) => {
         setFieldTouched("companyName", true);
 
@@ -174,13 +189,14 @@ export const CompanyForm: React.FunctionComponent = () => {
         <>
             <Grid container>
                 <Grid item>
-                    <Autocomplete options={ sampleCompanies }
+                    <Autocomplete options={ companies }
                                   getOptionLabel={ option => typeof option === "string" ? option : option.name }
                                   renderOption={ company => <CompanyItem company={ company }/> }
                                   renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.company-name") } fullWidth
                                                                     error={ touched.companyName && !!errors.companyName } helperText={ touched.companyName && errors.companyName }/> }
                                   onChange={ handleCompanyChange } value={ values.company || values.companyName }
                                   freeSolo
+                                  onInputChange={ (_, value) => setInput(value) }
                     />
                 </Grid>
                 <Grid item md={ 4 }>
diff --git a/src/forms/internship.tsx b/src/forms/internship.tsx
index 10fb302..3ce409a 100644
--- a/src/forms/internship.tsx
+++ b/src/forms/internship.tsx
@@ -4,13 +4,13 @@ import { KeyboardDatePicker as DatePicker } from "@material-ui/pickers";
 import { CompanyForm } from "@/forms/company";
 import { StudentForm } from "@/forms/student";
 import { sampleStudent } from "@/provider/dummy/student";
-import { Company, Internship, InternshipType, internshipTypeLabels, Office, Student } from "@/data";
+import { Company, Internship, InternshipType, Office, Student } from "@/data";
 import { Nullable } from "@/helpers";
 import moment, { Moment } from "moment";
 import { computeWorkingHours } from "@/utils/date";
 import { Autocomplete } from "@material-ui/lab";
 import { emptyInternship } from "@/provider/dummy/internship";
-import { InternshipProposalActions, useDispatch } from "@/state/actions";
+import { useDispatch } from "@/state/actions";
 import { useTranslation } from "react-i18next";
 import { useSelector } from "react-redux";
 import { AppState } from "@/state/reducer";
@@ -22,8 +22,9 @@ import { Field, Form, Formik, useFormikContext } from "formik";
 import * as Yup from "yup";
 import { Transformer } from "@/serialization";
 import { TextField as TextFieldFormik } from "formik-material-ui"
-import { Edition } from "@/data/edition";
-import { useUpdateEffect } from "@/hooks";
+import { useCurrentEdition, useCurrentStudent, useInternshipTypes, useUpdateEffect } from "@/hooks";
+import { internshipRegistrationUpdateTransformer } from "@/api/dto/internship-registration";
+import api from "@/api";
 
 export type InternshipFormValues = {
     startDate: Moment | null;
@@ -73,13 +74,11 @@ const emptyInternshipValues: InternshipFormValues = {
     workingHours: 40,
 }
 
-export const InternshipTypeItem = ({ type, ...props }: { type: InternshipType } & HTMLProps<any>) => {
-    const info = internshipTypeLabels[type];
-
+export const InternshipTypeItem = ({ internshipType: type, ...props }: { internshipType: InternshipType } & HTMLProps<any>) => {
     return (
         <div className="internship=type-item" { ...props }>
-            <div>{ info.label }</div>
-            { info.description && <Typography variant="caption">{ info.description }</Typography> }
+            <div>{ type.label.pl }</div>
+            { type.description && <Typography variant="caption">{ type.description.pl }</Typography> }
         </div>
     )
 }
@@ -88,25 +87,27 @@ const InternshipProgramForm = () => {
     const { t } = useTranslation();
     const { values, handleBlur, setFieldValue, errors } = useFormikContext<InternshipFormValues>();
 
+    const types = useInternshipTypes();
+
     return (
         <Grid container>
             <Grid item md={ 4 }>
                 <Autocomplete renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.kind") } fullWidth error={ !!errors.kind } helperText={ errors.kind }/> }
-                              getOptionLabel={ (option: InternshipType) => internshipTypeLabels[option].label }
-                              renderOption={ (option: InternshipType) => <InternshipTypeItem type={ option }/> }
-                              options={ Object.values(InternshipType) as InternshipType[] }
+                              getOptionLabel={ (option: InternshipType) => option.label.pl }
+                              renderOption={ (option: InternshipType) => <InternshipTypeItem internshipType={ option }/> }
+                              options={ types }
                               disableClearable
                               value={ values.kind || undefined }
                               onChange={ (_, value) => setFieldValue("kind", value) }
                               onBlur={ handleBlur }
                 />
             </Grid>
-            <Grid item md={ 8 }>
-                {
-                    values.kind === InternshipType.Other &&
-                    <Field label={ t("forms.internship.fields.kind-other") } name="kindOther" fullWidth component={ TextFieldFormik } />
-                }
-            </Grid>
+            {/*<Grid item md={ 8 }>*/}
+            {/*    {*/}
+            {/*        values.kind === InternshipType.Other &&*/}
+            {/*        <Field label={ t("forms.internship.fields.kind-other") } name="kindOther" fullWidth component={ TextFieldFormik } />*/}
+            {/*    }*/}
+            {/*</Grid>*/}
         </Grid>
     )
 }
@@ -240,19 +241,20 @@ const converter: Transformer<Nullable<Internship>, InternshipFormValues, Interns
 }
 
 export const InternshipForm: React.FunctionComponent = () => {
+    const student = useCurrentStudent();
+
     const initialInternship = useSelector<AppState, Nullable<Internship>>(state => getInternshipProposal(state.proposal) || {
         ...emptyInternship,
         office: null,
         company: null,
         mentor: null,
-        intern: sampleStudent
+        intern: student
     });
 
-    const edition = useSelector<AppState, Edition>(state => state.edition as Edition);
+    const edition = useCurrentEdition();
 
     const { t } = useTranslation();
 
-
     const dispatch = useDispatch();
     const history = useHistory();
 
@@ -268,7 +270,7 @@ export const InternshipForm: React.FunctionComponent = () => {
             .required(t("validation.required"))
             .matches(/^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*$/, t("validation.phone")),
         hours: Yup.number()
-            .min(edition.minimumInternshipHours, t("validation.internship.minimum-hours", { hours: edition.minimumInternshipHours })),
+            .min(edition?.minimumInternshipHours || 0, t("validation.internship.minimum-hours", { hours: edition?.minimumInternshipHours || 0 })),
         companyName: Yup.string().when("company", {
             is: null,
             then: Yup.string().required(t("validation.required"))
@@ -283,10 +285,10 @@ export const InternshipForm: React.FunctionComponent = () => {
         city: Yup.string().required(t("validation.required")),
         postalCode: Yup.string().required(t("validation.required")),
         building: Yup.string().required(t("validation.required")),
-        kindOther: Yup.string().when("kind", {
-            is: (values: InternshipFormValues) => values?.kind === InternshipType.Other,
-            then: Yup.string().required(t("validation.required"))
-        })
+        // kindOther: Yup.string().when("kind", {
+        //     is: (values: InternshipFormValues) => values?.kind === InternshipType.Other,
+        //     then: Yup.string().required(t("validation.required"))
+        // })
     })
 
     const values = converter.transform(initialInternship);
@@ -294,14 +296,12 @@ export const InternshipForm: React.FunctionComponent = () => {
     const handleSubmit = (values: InternshipFormValues) => {
         setConfirmDialogOpen(false);
 
-        dispatch({
-            type: InternshipProposalActions.Send,
-            internship: converter.reverseTransform(values, {
-                internship: initialInternship as Internship,
-            }) as Internship
-        });
+        const internship = converter.reverseTransform(values, { internship: initialInternship as Internship });
+        const update = internshipRegistrationUpdateTransformer.transform(internship);
 
-        history.push(route("home"))
+        api.internship.update(update);
+
+        // history.push(route("home"))
     }
 
     const InnerForm = () => {
diff --git a/src/forms/student.tsx b/src/forms/student.tsx
index 1c8ee26..189c3c4 100644
--- a/src/forms/student.tsx
+++ b/src/forms/student.tsx
@@ -2,14 +2,15 @@ import { Course } from "@/data";
 import { Button, Grid, TextField } from "@material-ui/core";
 import { Alert, Autocomplete } from "@material-ui/lab";
 import React from "react";
-import { sampleCourse } from "@/provider/dummy/student";
 import { useTranslation } from "react-i18next";
 import { useFormikContext } from "formik";
 import { InternshipFormValues } from "@/forms/internship";
+import { useCurrentEdition } from "@/hooks";
 
 export const StudentForm = () => {
     const { t } = useTranslation();
     const { values: { student } } = useFormikContext<InternshipFormValues>();
+    const course = useCurrentEdition()?.course as Course;
 
     return <>
         <Grid container>
@@ -26,8 +27,8 @@ export const StudentForm = () => {
                 <Autocomplete
                     getOptionLabel={ (course: Course) => course.name }
                     renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.course") } fullWidth/> }
-                    options={[ sampleCourse ]}
-                    value={ student.course }
+                    options={[ course ]}
+                    value={ course }
                     disabled
                 />
             </Grid>
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
index 544e1f0..3c9050a 100644
--- a/src/hooks/index.ts
+++ b/src/hooks/index.ts
@@ -2,3 +2,4 @@ export * from "./useProxyState"
 export * from "./useUpdateEffect"
 export * from "./useAsync"
 export * from "./state"
+export * from "./providers"
diff --git a/src/hooks/providers.ts b/src/hooks/providers.ts
new file mode 100644
index 0000000..2f0d8f6
--- /dev/null
+++ b/src/hooks/providers.ts
@@ -0,0 +1,15 @@
+import { useEffect, useState } from "react";
+import api from "@/api";
+import { InternshipType } from "@/data";
+
+export const useInternshipTypes = () => {
+    const [types, setTypes] = useState<InternshipType[]>([]);
+
+    useEffect(() => {
+        (async () => {
+            setTypes(await api.type.available());
+        })()
+    }, [])
+
+    return types;
+}
diff --git a/src/pages/edition/pick.tsx b/src/pages/edition/pick.tsx
index 5275408..a1261c0 100644
--- a/src/pages/edition/pick.tsx
+++ b/src/pages/edition/pick.tsx
@@ -11,7 +11,7 @@ import api from "@/api";
 import { Section } from "@/components/section";
 import { useVerticalSpacing } from "@/styles";
 import { Alert } from "@material-ui/lab";
-import { EditionActions, useDispatch } from "@/state/actions";
+import { EditionActions, useDispatch, UserActions } from "@/state/actions";
 
 export const PickEditionPage = () => {
     const { t } = useTranslation();
@@ -23,12 +23,19 @@ export const PickEditionPage = () => {
     const classes = useVerticalSpacing(3);
 
     const pickEditionHandler = (id: string) => async () => {
-        const edition = await api.edition.get(id);
+        const token   = await api.edition.login(id);
 
-        if (!edition) {
+        if (!token) {
             return;
         }
 
+        await dispatch({
+            type: UserActions.Login,
+            token,
+        })
+
+        const edition = await api.edition.current();
+
         dispatch({
             type: EditionActions.Set,
             edition
diff --git a/src/routing.tsx b/src/routing.tsx
index f7d37e5..8b88172 100644
--- a/src/routing.tsx
+++ b/src/routing.tsx
@@ -67,7 +67,10 @@ export function route(name: string, params: URLParams = {}) {
 }
 
 export const query = (url: string, params: URLParams) => {
-    const query = Object.entries(params).map(([name, value]) => `${ name }=${ encodeURIComponent(value) }`).join("&");
+    const query = Object.entries(params)
+        .filter(([_, value]) => !!value)
+        .map(([name, value]) => `${ name }=${ encodeURIComponent(value) }`)
+        .join("&");
 
     return url + (query.length > 0 ? `?${ query }` : '');
 }
diff --git a/src/state/reducer/insurance.ts b/src/state/reducer/insurance.ts
index 61cabc9..9e99ae2 100644
--- a/src/state/reducer/insurance.ts
+++ b/src/state/reducer/insurance.ts
@@ -1,7 +1,6 @@
 import { Reducer } from "react";
 import { InsuranceAction, InsuranceActions } from "@/state/actions/insurance";
 import { InternshipProposalAction, InternshipProposalActions } from "@/state/actions";
-import { InternshipType } from "@/data";
 
 export type InsuranceState = {
     required: boolean;
@@ -21,12 +20,6 @@ export const insuranceReducer: Reducer<InsuranceState, InsuranceAction | Interns
         case InternshipProposalActions.Send:
             return {
                 ...state,
-                required: [
-                    InternshipType.FreeApprenticeship,
-                    InternshipType.FreeInternship,
-                    InternshipType.PaidApprenticeship,
-                    InternshipType.GraduateInternship,
-                ].includes(action.internship.type)
             }
 
         case InsuranceActions.Signed:
diff --git a/yarn.lock b/yarn.lock
index f385c39..e5383ca 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2258,6 +2258,11 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.6.2, browserslist@^4.
     node-releases "^1.1.53"
     pkg-up "^2.0.0"
 
+buffer-equal-constant-time@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
+  integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
+
 buffer-from@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
@@ -3433,6 +3438,13 @@ ecc-jsbn@~0.1.1:
     jsbn "~0.1.0"
     safer-buffer "^2.1.0"
 
+ecdsa-sig-formatter@1.0.11:
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
+  integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
+  dependencies:
+    safe-buffer "^5.0.1"
+
 ee-first@1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -5219,6 +5231,22 @@ jsonify@~0.0.0:
   resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
   integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
 
+jsonwebtoken@^8.5.1:
+  version "8.5.1"
+  resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
+  integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
+  dependencies:
+    jws "^3.2.2"
+    lodash.includes "^4.3.0"
+    lodash.isboolean "^3.0.3"
+    lodash.isinteger "^4.0.4"
+    lodash.isnumber "^3.0.3"
+    lodash.isplainobject "^4.0.6"
+    lodash.isstring "^4.0.1"
+    lodash.once "^4.0.0"
+    ms "^2.1.1"
+    semver "^5.6.0"
+
 jsprim@^1.2.2:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -5299,6 +5327,23 @@ jss@^10.0.3, jss@^10.3.0:
     is-in-browser "^1.1.3"
     tiny-warning "^1.0.2"
 
+jwa@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
+  integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
+  dependencies:
+    buffer-equal-constant-time "1.0.1"
+    ecdsa-sig-formatter "1.0.11"
+    safe-buffer "^5.0.1"
+
+jws@^3.2.2:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
+  integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
+  dependencies:
+    jwa "^1.4.1"
+    safe-buffer "^5.0.1"
+
 killable@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@@ -5432,6 +5477,36 @@ lodash.get@^4.4.2:
   resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
   integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
 
+lodash.includes@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
+  integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
+
+lodash.isboolean@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
+  integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
+
+lodash.isinteger@^4.0.4:
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
+  integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
+
+lodash.isnumber@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
+  integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
+
+lodash.isplainobject@^4.0.6:
+  version "4.0.6"
+  resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
+  integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
+
+lodash.isstring@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
+  integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
+
 lodash.memoize@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@@ -5447,6 +5522,11 @@ lodash.omit@^4.5.0:
   resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60"
   integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=
 
+lodash.once@^4.0.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
+  integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
+
 lodash.pick@^4.4.0:
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
-- 
2.45.2


From 263be22901cd81d3591d909a1d5e2c19c6ce6a3c Mon Sep 17 00:00:00 2001
From: Kacper Donat <kadet1090@gmail.com>
Date: Wed, 7 Oct 2020 22:25:55 +0200
Subject: [PATCH 4/5] Add obtaining info about current internship status

---
 src/api/dto/internship-registration.ts | 53 ++++++++++++++++++++++++--
 src/api/dto/mentor.ts                  | 28 ++++++++++++++
 src/api/internship.ts                  | 10 ++++-
 src/pages/main.tsx                     | 18 ++++++++-
 src/state/actions/proposal.ts          |  3 ++
 src/state/reducer/proposal.ts          | 12 ++++++
 6 files changed, 118 insertions(+), 6 deletions(-)
 create mode 100644 src/api/dto/mentor.ts

diff --git a/src/api/dto/internship-registration.ts b/src/api/dto/internship-registration.ts
index b7a94a7..09659c6 100644
--- a/src/api/dto/internship-registration.ts
+++ b/src/api/dto/internship-registration.ts
@@ -1,6 +1,18 @@
-import { Identifiable, Internship, Mentor } from "@/data";
-import { OneWayTransformer } from "@/serialization";
+import { Company, Identifiable, Internship, Mentor, Office } from "@/data";
+import { momentSerializationTransformer, OneWayTransformer } from "@/serialization";
 import { Nullable } from "@/helpers";
+import { MentorDTO, mentorDtoTransformer } from "@/api/dto/mentor";
+import { InternshipTypeDTO, internshipTypeDtoTransformer } from "@/api/dto/type";
+import { Moment } from "moment";
+import { sampleStudent } from "@/provider/dummy";
+
+export enum SubmissionState {
+    Draft,
+    Submitted,
+    Accepted,
+    Rejected,
+    Archival,
+}
 
 export interface InternshipRegistrationUpdateCompany {
     id: string,
@@ -12,7 +24,21 @@ export interface InternshipRegistrationUpdate {
     start: string,
     end: string,
     type: number,
-    mentor: Mentor,
+    mentor: MentorDTO,
+}
+
+export interface InternshipRegistrationDTO extends Identifiable {
+    start: string;
+    end: string;
+    type: InternshipTypeDTO,
+    state: SubmissionState,
+    mentor: MentorDTO,
+    company: Company,
+    branchAddress: Office,
+}
+
+export interface InternshipInfoDTO {
+    internshipRegistration: InternshipRegistrationDTO;
 }
 
 export const internshipRegistrationUpdateTransformer: OneWayTransformer<Nullable<Internship>, Nullable<InternshipRegistrationUpdate>> = {
@@ -21,7 +47,7 @@ export const internshipRegistrationUpdateTransformer: OneWayTransformer<Nullable
             start: subject?.startDate?.toISOString() || null,
             end: subject?.endDate?.toISOString() || null,
             type: parseInt(subject?.type?.id || "0"),
-            mentor: subject.mentor,
+            mentor: mentorDtoTransformer.reverseTransform(subject.mentor as Mentor),
             company: {
                 id: subject?.company?.id as string,
                 branchOffice: {
@@ -31,3 +57,22 @@ export const internshipRegistrationUpdateTransformer: OneWayTransformer<Nullable
         }
     }
 }
+
+export const internshipRegistrationDtoTransformer: OneWayTransformer<InternshipRegistrationDTO, Internship> = {
+    transform(dto: InternshipRegistrationDTO, context?: unknown): Internship {
+        return {
+            id: dto.id,
+            office: dto.branchAddress,
+            company: dto.company,
+            mentor: mentorDtoTransformer.transform(dto.mentor),
+            startDate: momentSerializationTransformer.reverseTransform(dto.start) as Moment,
+            endDate: momentSerializationTransformer.reverseTransform(dto.end) as Moment,
+            type: internshipTypeDtoTransformer.transform(dto.type),
+            hours: 0,
+            isAccepted: dto.state === SubmissionState.Accepted,
+            lengthInWeeks: 0,
+            program: [],
+            intern: sampleStudent, // fixme
+        };
+    }
+}
diff --git a/src/api/dto/mentor.ts b/src/api/dto/mentor.ts
new file mode 100644
index 0000000..2d5f03a
--- /dev/null
+++ b/src/api/dto/mentor.ts
@@ -0,0 +1,28 @@
+import { Transformer } from "@/serialization";
+import { Mentor } from "@/data";
+
+export interface MentorDTO {
+    firstName: string;
+    lastName: string;
+    email: string;
+    phoneNumber: string;
+}
+
+export const mentorDtoTransformer: Transformer<MentorDTO, Mentor> = {
+    reverseTransform(subject: Mentor, context?: unknown): MentorDTO {
+        return {
+            firstName: subject.name,
+            lastName: subject.surname,
+            email: subject.email,
+            phoneNumber: subject.phone || "",
+        }
+    },
+    transform(subject: MentorDTO, context?: unknown): Mentor {
+        return {
+            name: subject.firstName,
+            surname: subject.lastName,
+            email: subject.email,
+            phone: subject.phoneNumber,
+        }
+    }
+}
diff --git a/src/api/internship.ts b/src/api/internship.ts
index 3096b15..cc4dda0 100644
--- a/src/api/internship.ts
+++ b/src/api/internship.ts
@@ -1,4 +1,4 @@
-import { InternshipRegistrationUpdate } from "@/api/dto/internship-registration";
+import { InternshipInfoDTO, InternshipRegistrationUpdate } from "@/api/dto/internship-registration";
 import { axios } from "@/api/index";
 import { Nullable } from "@/helpers";
 
@@ -9,3 +9,11 @@ export async function update(internship: Nullable<InternshipRegistrationUpdate>)
 
     return true;
 }
+
+export async function get(): Promise<InternshipInfoDTO> {
+    const response = await axios.get<InternshipInfoDTO>(INTERNSHIP_ENDPOINT);
+
+    console.log(response);
+
+    return response.data;
+}
diff --git a/src/pages/main.tsx b/src/pages/main.tsx
index e6d9cf2..e204644 100644
--- a/src/pages/main.tsx
+++ b/src/pages/main.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useEffect } from "react";
 import { Page } from "@/pages/base";
 import { Container, Stepper, Typography } from "@material-ui/core";
 import { Redirect } from "react-router-dom";
@@ -14,6 +14,9 @@ import { InsuranceState } from "@/state/reducer/insurance";
 import { InsuranceStep } from "@/pages/steps/insurance";
 import { StudentStep } from "@/pages/steps/student";
 import { useDeadlines } from "@/hooks";
+import api from "@/api";
+import { InternshipProposalActions, useDispatch } from "@/state/actions";
+import { internshipRegistrationDtoTransformer } from "@/api/dto/internship-registration";
 
 export const MainPage = () => {
     const { t } = useTranslation();
@@ -22,6 +25,19 @@ export const MainPage = () => {
 
     const deadlines = useDeadlines();
     const insurance = useSelector<AppState, InsuranceState>(root => root.insurance);
+    const dispatch = useDispatch();
+
+    useEffect(() => {
+        (async () => {
+            const internship = await api.internship.get();
+
+            dispatch({
+                type: InternshipProposalActions.Receive,
+                state: internship.internshipRegistration.state,
+                internship: internshipRegistrationDtoTransformer.transform(internship.internshipRegistration),
+            })
+        })()
+    }, [])
 
     if (!student) {
         return <Redirect to={ route("user_login") }/>;
diff --git a/src/state/actions/proposal.ts b/src/state/actions/proposal.ts
index ad8102a..2db7692 100644
--- a/src/state/actions/proposal.ts
+++ b/src/state/actions/proposal.ts
@@ -6,6 +6,7 @@ import {
     SaveSubmissionAction,
     SendSubmissionAction
 } from "@/state/actions/submission";
+import { SubmissionState } from "@/api/dto/internship-registration";
 
 export enum InternshipProposalActions {
     Send = "SEND_PROPOSAL",
@@ -26,6 +27,8 @@ export interface ReceiveProposalDeclineAction extends ReceiveSubmissionDeclineAc
 }
 
 export interface ReceiveProposalUpdateAction extends ReceiveSubmissionUpdateAction<InternshipProposalActions.Receive> {
+    internship: Internship;
+    state: SubmissionState,
 }
 
 export interface SaveProposalAction extends SaveSubmissionAction<InternshipProposalActions.Save> {
diff --git a/src/state/reducer/proposal.ts b/src/state/reducer/proposal.ts
index 783983c..d20eeb4 100644
--- a/src/state/reducer/proposal.ts
+++ b/src/state/reducer/proposal.ts
@@ -11,6 +11,7 @@ import {
 } from "@/state/reducer/submission";
 import { Reducer } from "react";
 import { SubmissionAction } from "@/state/actions/submission";
+import { SubmissionState as ApiSubmissionState } from "@/api/dto/internship-registration";
 
 export type InternshipProposalState = SubmissionState & MayRequireDeanApproval & {
     proposal: Serializable<Internship> | null;
@@ -43,6 +44,17 @@ const internshipProposalReducer = (state: InternshipProposalState = defaultInter
                 ...state,
                 proposal: internshipSerializationTransformer.transform(action.internship),
             }
+        case InternshipProposalActions.Receive:
+            return {
+                ...state,
+                accepted: action.state === ApiSubmissionState.Accepted,
+                sent: [
+                    ApiSubmissionState.Accepted,
+                    ApiSubmissionState.Rejected,
+                    ApiSubmissionState.Submitted
+                ].includes(action.state),
+                proposal: internshipSerializationTransformer.transform(action.internship),
+            }
         default:
             return state;
     }
-- 
2.45.2


From 5879efc978a73ce5e010025ccf0aaeb0e9c07295 Mon Sep 17 00:00:00 2001
From: Kacper Donat <kadet1090@gmail.com>
Date: Sun, 18 Oct 2020 13:48:05 +0200
Subject: [PATCH 5/5] Fix multiple issues with forms

---
 src/api/dto/internship-registration.ts | 46 ++++++++++++++++++--------
 src/api/index.ts                       |  2 ++
 src/api/internship.ts                  |  5 +--
 src/api/upload.ts                      | 23 +++++++++++++
 src/forms/company.tsx                  | 12 +++++--
 src/forms/internship.tsx               | 10 +++---
 src/forms/plan.tsx                     |  7 ++--
 src/forms/student.tsx                  |  2 +-
 src/middleware.tsx                     |  8 ++---
 src/pages/internship/proposal.tsx      |  8 ++---
 src/pages/user/profile.tsx             |  2 +-
 src/state/reducer/edition.ts           |  6 +++-
 12 files changed, 96 insertions(+), 35 deletions(-)
 create mode 100644 src/api/upload.ts

diff --git a/src/api/dto/internship-registration.ts b/src/api/dto/internship-registration.ts
index 09659c6..b415db9 100644
--- a/src/api/dto/internship-registration.ts
+++ b/src/api/dto/internship-registration.ts
@@ -1,4 +1,4 @@
-import { Company, Identifiable, Internship, Mentor, Office } from "@/data";
+import { Address, Company, Identifiable, Internship, Mentor, Office } from "@/data";
 import { momentSerializationTransformer, OneWayTransformer } from "@/serialization";
 import { Nullable } from "@/helpers";
 import { MentorDTO, mentorDtoTransformer } from "@/api/dto/mentor";
@@ -7,24 +7,34 @@ import { Moment } from "moment";
 import { sampleStudent } from "@/provider/dummy";
 
 export enum SubmissionState {
-    Draft,
-    Submitted,
-    Accepted,
-    Rejected,
-    Archival,
+    Draft = "Draft",
+    Submitted = "Submitted",
+    Accepted = "Accepted",
+    Rejected = "Rejected",
+    Archival = "Archival",
+}
+
+export interface NewBranchOffice extends Address {
 }
 
 export interface InternshipRegistrationUpdateCompany {
     id: string,
-    branchOffice: Identifiable,
+    branchOffice: Identifiable | NewBranchOffice,
+}
+
+export interface NewCompany {
+    nip: string;
+    name: string;
+    branchOffice: NewBranchOffice | null;
 }
 
 export interface InternshipRegistrationUpdate {
-    company: InternshipRegistrationUpdateCompany,
+    company: InternshipRegistrationUpdateCompany | NewCompany,
     start: string,
     end: string,
     type: number,
     mentor: MentorDTO,
+    hours: number,
 }
 
 export interface InternshipRegistrationDTO extends Identifiable {
@@ -35,8 +45,11 @@ export interface InternshipRegistrationDTO extends Identifiable {
     mentor: MentorDTO,
     company: Company,
     branchAddress: Office,
+    declaredHours: number,
 }
 
+const reference = (subject: Identifiable | null): Identifiable | null => subject && { id: subject.id };
+
 export interface InternshipInfoDTO {
     internshipRegistration: InternshipRegistrationDTO;
 }
@@ -48,12 +61,17 @@ export const internshipRegistrationUpdateTransformer: OneWayTransformer<Nullable
             end: subject?.endDate?.toISOString() || null,
             type: parseInt(subject?.type?.id || "0"),
             mentor: mentorDtoTransformer.reverseTransform(subject.mentor as Mentor),
-            company: {
+            company: subject?.company?.id ? {
                 id: subject?.company?.id as string,
-                branchOffice: {
-                    id: subject?.office?.id
-                },
-            }
+                branchOffice: subject?.office?.id
+                    ? reference(subject?.office) as Identifiable
+                    : subject?.office?.address as NewBranchOffice,
+            } : {
+                name: subject?.company?.name as string,
+                nip: subject?.company?.nip as string,
+                branchOffice: subject?.office?.address as NewBranchOffice
+            },
+            hours: subject?.hours,
         }
     }
 }
@@ -68,7 +86,7 @@ export const internshipRegistrationDtoTransformer: OneWayTransformer<InternshipR
             startDate: momentSerializationTransformer.reverseTransform(dto.start) as Moment,
             endDate: momentSerializationTransformer.reverseTransform(dto.end) as Moment,
             type: internshipTypeDtoTransformer.transform(dto.type),
-            hours: 0,
+            hours: dto.declaredHours,
             isAccepted: dto.state === SubmissionState.Accepted,
             lengthInWeeks: 0,
             program: [],
diff --git a/src/api/index.ts b/src/api/index.ts
index cb20750..5f76ba3 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -10,6 +10,7 @@ import * as student from "./student";
 import * as type from "./type";
 import * as companies from "./companies";
 import * as internship from "./internship";
+import * as upload from "./upload";
 
 export const axios = Axios.create({
     baseURL: process.env.API_BASE_URL || "https://system-praktyk.stg.kadet.net/api/",
@@ -40,6 +41,7 @@ const api = {
     type,
     companies,
     internship,
+    upload
 }
 
 export default api;
diff --git a/src/api/internship.ts b/src/api/internship.ts
index cc4dda0..5741950 100644
--- a/src/api/internship.ts
+++ b/src/api/internship.ts
@@ -2,10 +2,11 @@ import { InternshipInfoDTO, InternshipRegistrationUpdate } from "@/api/dto/inter
 import { axios } from "@/api/index";
 import { Nullable } from "@/helpers";
 
-const INTERNSHIP_ENDPOINT = '/internshipRegistration';
+const INTERNSHIP_REGISTRATION_ENDPOINT = '/internshipRegistration';
+const INTERNSHIP_ENDPOINT = '/internship';
 
 export async function update(internship: Nullable<InternshipRegistrationUpdate>): Promise<boolean> {
-    const response = await axios.put(INTERNSHIP_ENDPOINT, internship);
+    const response = await axios.put(INTERNSHIP_REGISTRATION_ENDPOINT, internship);
 
     return true;
 }
diff --git a/src/api/upload.ts b/src/api/upload.ts
new file mode 100644
index 0000000..e27735a
--- /dev/null
+++ b/src/api/upload.ts
@@ -0,0 +1,23 @@
+import { Identifiable } from "@/data";
+import { axios } from "@/api/index";
+
+export enum UploadType {
+    Ipp = "IppScan",
+    DeanConsent = "DeanConsent",
+    Insurance = "NnwInsurance",
+}
+
+const CREATE_DOCUMENT_ENDPOINT = '/document';
+const DOCUMENT_UPLOAD_ENDPOINT = '/document/:id/scan';
+
+interface Document extends Identifiable {
+    description?: string;
+    type: UploadType;
+}
+
+export async function create(type: UploadType, content: File)
+{
+    const response = await axios.post<Document>(CREATE_DOCUMENT_ENDPOINT, { type });
+
+    console.log(response.data);
+}
diff --git a/src/forms/company.tsx b/src/forms/company.tsx
index 21a5300..246b1c2 100644
--- a/src/forms/company.tsx
+++ b/src/forms/company.tsx
@@ -103,7 +103,7 @@ export const BranchForm: React.FC = () => {
                                   onInputChange={ handleCityInput }
                                   onBlur={ ev => setFieldTouched("city", true) }
                                   inputValue={ values.city }
-                                  value={ values.office ? values.office : null }
+                                  value={ values.office ? values.office : values.city }
                                   freeSolo
                     />
                 </Grid>
@@ -155,9 +155,15 @@ export const CompanyForm: React.FunctionComponent = () => {
     const canEdit = useMemo(() => !values.company, [values.company]);
 
     useEffect(() => {
+        if (!input || values.companyName == input) {
+            return;
+        }
+
         (async () => {
             setCompanies(await api.companies.search(input));
         })()
+
+        setValues({ ...values, company: null, companyName: input }, true)
     }, [ input ]);
 
     const handleCompanyChange = (event: any, value: Company | string | null) => {
@@ -194,7 +200,9 @@ export const CompanyForm: React.FunctionComponent = () => {
                                   renderOption={ company => <CompanyItem company={ company }/> }
                                   renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.company-name") } fullWidth
                                                                     error={ touched.companyName && !!errors.companyName } helperText={ touched.companyName && errors.companyName }/> }
-                                  onChange={ handleCompanyChange } value={ values.company || values.companyName }
+                                  onChange={ handleCompanyChange }
+                                  value={ values.company || values.companyName }
+                                  inputValue={ input }
                                   freeSolo
                                   onInputChange={ (_, value) => setInput(value) }
                     />
diff --git a/src/forms/internship.tsx b/src/forms/internship.tsx
index 3ce409a..c36b3a3 100644
--- a/src/forms/internship.tsx
+++ b/src/forms/internship.tsx
@@ -97,7 +97,7 @@ const InternshipProgramForm = () => {
                               renderOption={ (option: InternshipType) => <InternshipTypeItem internshipType={ option }/> }
                               options={ types }
                               disableClearable
-                              value={ values.kind || undefined }
+                              value={ values.kind || null as any }
                               onChange={ (_, value) => setFieldValue("kind", value) }
                               onBlur={ handleBlur }
                 />
@@ -234,7 +234,7 @@ const converter: Transformer<Nullable<Internship>, InternshipFormValues, Interns
                 nip: form.companyNip,
                 offices: [],
             },
-            hours: form.hours as number,
+            hours: form.hours ? form.hours : 0,
             type: form.kind as InternshipType,
         }
     }
@@ -299,6 +299,8 @@ export const InternshipForm: React.FunctionComponent = () => {
         const internship = converter.reverseTransform(values, { internship: initialInternship as Internship });
         const update = internshipRegistrationUpdateTransformer.transform(internship);
 
+        console.log(update);
+
         api.internship.update(update);
 
         // history.push(route("home"))
@@ -308,8 +310,8 @@ export const InternshipForm: React.FunctionComponent = () => {
         const { submitForm, validateForm } = useFormikContext();
 
         const handleSubmitConfirmation = async () => {
-            const errors = await validateForm();
-
+            // const errors = await validateForm();
+            const errors = {};
             if (Object.keys(errors).length == 0) {
                 setConfirmDialogOpen(true);
             } else {
diff --git a/src/forms/plan.tsx b/src/forms/plan.tsx
index 0c96203..1626d03 100644
--- a/src/forms/plan.tsx
+++ b/src/forms/plan.tsx
@@ -7,7 +7,9 @@ import { route } from "@/routing";
 import React, { useState } from "react";
 import { Plan } from "@/data";
 import { useTranslation } from "react-i18next";
-import { InternshipPlanActions, useDispatch } from "@/state/actions";
+import { useDispatch } from "@/state/actions";
+import { UploadType } from "@/api/upload";
+import api from "@/api";
 
 export const PlanForm = () => {
     const { t } = useTranslation();
@@ -18,7 +20,8 @@ export const PlanForm = () => {
     const history = useHistory();
 
     const handleSubmit = () => {
-        dispatch({ type: InternshipPlanActions.Send, plan });
+        api.upload.create(UploadType.Ipp, null as any);
+        // dispatch({ type: InternshipPlanActions.Send, plan });
         history.push(route("home"))
     }
 
diff --git a/src/forms/student.tsx b/src/forms/student.tsx
index 189c3c4..efc4842 100644
--- a/src/forms/student.tsx
+++ b/src/forms/student.tsx
@@ -33,7 +33,7 @@ export const StudentForm = () => {
                 />
             </Grid>
             <Grid item md={3}>
-                <TextField label={ t("forms.internship.fields.semester") } value={ student.semester } disabled fullWidth/>
+                <TextField label={ t("forms.internship.fields.semester") } value={ student.semester || "" } disabled fullWidth/>
             </Grid>
             <Grid item>
                 <Alert severity="warning" action={ <Button color="inherit" size="small">skontaktuj się z opiekunem</Button> }>
diff --git a/src/middleware.tsx b/src/middleware.tsx
index 166de95..621d234 100644
--- a/src/middleware.tsx
+++ b/src/middleware.tsx
@@ -5,21 +5,21 @@ import { Redirect } from "react-router-dom";
 import React from "react";
 import { UserState } from "@/state/reducer/user";
 
-export const isReadyMiddleware: Middleware<any, any> = next => isLoggedInMiddleware(() => {
+export const isReadyMiddleware: Middleware<any, any> = Next => isLoggedInMiddleware(() => {
     const ready = useSelector(isReady);
 
     if (ready) {
-        return <>{ next() }</>;
+        return <Next />;
     }
 
     return <Redirect to={ route("edition_pick") } />;
 })
 
-export const isLoggedInMiddleware: Middleware<any, any> = next => {
+export const isLoggedInMiddleware: Middleware<any, any> = Next => {
     const user = useSelector<AppState>(state => state.user) as UserState;
 
     if (user.loggedIn) {
-        return <>{ next() }</>;
+        return <Next />;
     }
 
     return <Redirect to={ route("user_login") } />;
diff --git a/src/pages/internship/proposal.tsx b/src/pages/internship/proposal.tsx
index 939998b..1b3a814 100644
--- a/src/pages/internship/proposal.tsx
+++ b/src/pages/internship/proposal.tsx
@@ -115,10 +115,6 @@ export const InternshipProposalPreviewPage = () => {
             { proposal && <ProposalPreview proposal={ proposal } /> }
 
             <Actions>
-                <Button component={ RouterLink } to={ route("home") } variant="contained" color="primary">
-                    { t('go-back') }
-                </Button>
-
                 <ButtonGroup color="primary" variant="contained">
                     <Button onClick={ handleAcceptWithoutComment } startIcon={ <StickerCheckOutline /> }>
                         { t('accept-without-comments') }
@@ -134,6 +130,10 @@ export const InternshipProposalPreviewPage = () => {
                 <Button onClick={ handleDiscardAction } color="secondary" startIcon={ <StickerRemoveOutline /> }>
                     { t('discard') }
                 </Button>
+
+                <Button component={ RouterLink } to={ route("home") }>
+                    { t('go-back') }
+                </Button>
             </Actions>
         </Container>
         <Dialog open={ isDiscardModalOpen } onClose={ handleDiscardModalClose } maxWidth="md">
diff --git a/src/pages/user/profile.tsx b/src/pages/user/profile.tsx
index 5ebc8ba..54bca0e 100644
--- a/src/pages/user/profile.tsx
+++ b/src/pages/user/profile.tsx
@@ -47,7 +47,7 @@ export const UserProfilePage = () => {
                 </Box>
             </Paper>
             <Actions>
-                <Button variant="contained" component={ RouterLink } to={ route("home") }>
+                <Button component={ RouterLink } to={ route("home") }>
                     { t('go-back') }
                 </Button>
             </Actions>
diff --git a/src/state/reducer/edition.ts b/src/state/reducer/edition.ts
index 93847c2..43084d4 100644
--- a/src/state/reducer/edition.ts
+++ b/src/state/reducer/edition.ts
@@ -1,15 +1,19 @@
 import { Edition } from "@/data/edition";
 import { EditionAction, EditionActions } from "@/state/actions/edition";
 import { editionSerializationTransformer, Serializable } from "@/serialization";
+import { LoginAction, LogoutAction, UserActions } from "@/state/actions";
 
 export type EditionState = Serializable<Edition> | null;
 
 const initialEditionState: EditionState = null;
 
-const editionReducer = (state: EditionState = initialEditionState, action: EditionAction): EditionState => {
+const editionReducer = (state: EditionState = initialEditionState, action: EditionAction | LogoutAction | LoginAction): EditionState => {
     switch (action.type) {
         case EditionActions.Set:
             return editionSerializationTransformer.transform(action.edition);
+        case UserActions.Login:
+        case UserActions.Logout:
+            return initialEditionState;
     }
 
     return state;
-- 
2.45.2