From ee070bc62ca14f350a37791044404231773c4002 Mon Sep 17 00:00:00 2001
From: Kacper Donat <kadet1090@gmail.com>
Date: Tue, 11 Aug 2020 21:02:06 +0200
Subject: [PATCH 1/5] Add accept and discard actions for proposal

---
 src/components/actions.tsx         |  2 +-
 src/components/proposalPreview.tsx | 13 ++--------
 src/i18n.ts                        |  9 +++++--
 src/pages/internship/proposal.tsx  | 39 +++++++++++++++++++++++++++---
 translations/pl.yaml               |  4 ++-
 5 files changed, 49 insertions(+), 18 deletions(-)

diff --git a/src/components/actions.tsx b/src/components/actions.tsx
index 34357c3..b2e090e 100644
--- a/src/components/actions.tsx
+++ b/src/components/actions.tsx
@@ -2,7 +2,7 @@ import React, { HTMLProps } from "react";
 import { useHorizontalSpacing } from "@/styles";
 
 export const Actions = (props: HTMLProps<HTMLDivElement>) => {
-    const classes = useHorizontalSpacing(1);
+    const classes = useHorizontalSpacing(2);
 
     return <div className={ classes.root } { ...props }/>
 }
diff --git a/src/components/proposalPreview.tsx b/src/components/proposalPreview.tsx
index d0b8870..f57798f 100644
--- a/src/components/proposalPreview.tsx
+++ b/src/components/proposalPreview.tsx
@@ -1,12 +1,9 @@
 import { Internship, internshipTypeLabels } from "@/data";
 import React from "react";
-import { Button, Paper, PaperProps, Typography, TypographyProps } from "@material-ui/core";
+import { Paper, PaperProps, Typography, TypographyProps } from "@material-ui/core";
 import { useTranslation } from "react-i18next";
 import { createStyles, makeStyles } from "@material-ui/core/styles";
 import classNames from "classnames";
-import { Link as RouterLink } from "react-router-dom";
-import { route } from "@/routing";
-import { Actions } from "@/components/actions";
 import { useVerticalSpacing } from "@/styles";
 import moment from "moment";
 
@@ -74,7 +71,7 @@ export const ProposalPreview = ({ proposal }: ProposalPreviewProps) => {
                 { t('internship.date-range', { start: proposal.startDate, end: proposal.endDate }) }
             </Typography>
             <Typography className="proposal__secondary">
-                { t('internship.duration', { duration }) }
+                { t('internship.duration', { duration, count: Math.floor(duration.asWeeks()) }) }
                 { ", " }
                 { t('internship.hours', { hours: proposal.hours }) }
             </Typography>
@@ -85,11 +82,5 @@ export const ProposalPreview = ({ proposal }: ProposalPreviewProps) => {
             <Typography className="proposal__primary">{ proposal.mentor.name } { proposal.mentor.surname }</Typography>
             <Typography className="proposal__secondary">{ proposal.mentor.email }, { proposal.mentor.phone }</Typography>
         </Section>
-
-        <Actions>
-            <Button component={ RouterLink } to={ route("home") } variant="contained" color="primary">
-                { t('go-back') }
-            </Button>
-        </Actions>
     </div>
 }
diff --git a/src/i18n.ts b/src/i18n.ts
index c01bc57..84cc091 100644
--- a/src/i18n.ts
+++ b/src/i18n.ts
@@ -4,7 +4,7 @@ import I18nextBrowserLanguageDetector from "i18next-browser-languagedetector";
 
 import "moment/locale/pl"
 import "moment/locale/en-gb"
-import moment, { isDuration, isMoment } from "moment";
+import moment, { isDuration, isMoment, unitOfTime } from "moment";
 import { convertToRoman } from "@/utils/numbers";
 
 const resources = {
@@ -22,6 +22,7 @@ i18n
     .init({
         resources,
         fallbackLng: "pl",
+        compatibilityJSON: "v3",
         interpolation: {
             escapeValue: false,
             format: (value, format, lng) => {
@@ -34,7 +35,11 @@ i18n
                 }
 
                 if (isDuration(value)) {
-                    return value.locale(lng || "pl").humanize();
+                    if (format === "humanize") {
+                        return value.locale(lng || "pl").humanize();
+                    } else {
+                        return Math.floor(value.locale(lng || "pl").as(format as unitOfTime.Base));
+                    }
                 }
 
                 return value;
diff --git a/src/pages/internship/proposal.tsx b/src/pages/internship/proposal.tsx
index 09b8ca8..3e92aea 100644
--- a/src/pages/internship/proposal.tsx
+++ b/src/pages/internship/proposal.tsx
@@ -1,6 +1,6 @@
 import { Page } from "@/pages/base";
-import { Container, Link, Typography } from "@material-ui/core";
-import { Link as RouterLink } from "react-router-dom";
+import { Button, Container, Link, Typography } from "@material-ui/core";
+import { Link as RouterLink, useHistory } from "react-router-dom";
 import { route } from "@/routing";
 import { InternshipForm } from "@/forms/internship";
 import React from "react";
@@ -11,6 +11,10 @@ import { useSelector } from "react-redux";
 import { Internship } from "@/data";
 import { AppState } from "@/state/reducer";
 import { internshipSerializationTransformer } from "@/serialization";
+import { Actions } from "@/components";
+import { InternshipProposalActions, useDispatch } from "@/state/actions";
+import { StickerCheckOutline, StickerRemoveOutline } from "mdi-material-ui/index";
+import { useVerticalSpacing } from "@/styles";
 
 export const InternshipProposalFormPage = () => {
     return <Page title="Zgłoszenie praktyki">
@@ -32,6 +36,21 @@ export const InternshipProposalPreviewPage = () => {
     const { t } = useTranslation();
     const proposal = useSelector<AppState, Internship | null>(state => state.proposal.proposal && internshipSerializationTransformer.reverseTransform(state.proposal.proposal));
 
+    const dispatch = useDispatch();
+    const history = useHistory();
+
+    const handleAccept = () => {
+        dispatch({ type: InternshipProposalActions.Approve, comment: null });
+        history.push(route("home"));
+    }
+
+    const handleDiscard = () => {
+        dispatch({ type: InternshipProposalActions.Decline, comment: "Well..." });
+        history.push(route("home"));
+    }
+
+    const classes = useVerticalSpacing(3);
+
     return <Page title={ t("") }>
         <Page.Header maxWidth="md">
             <Page.Breadcrumbs>
@@ -40,9 +59,23 @@ export const InternshipProposalPreviewPage = () => {
             </Page.Breadcrumbs>
             <Page.Title>Moje zgłoszenie</Page.Title>
         </Page.Header>
-        <Container maxWidth={ "md" }>
+        <Container maxWidth={ "md" } className={ classes.root }>
             <ProposalComment />
             { proposal && <ProposalPreview proposal={ proposal } /> }
+
+            <Actions>
+                <Button component={ RouterLink } to={ route("home") } variant="contained" color="primary">
+                    { t('go-back') }
+                </Button>
+
+                <Button onClick={ handleAccept } color="primary" startIcon={ <StickerCheckOutline /> }>
+                    { t('accept') }
+                </Button>
+
+                <Button onClick={ handleDiscard } color="secondary" startIcon={ <StickerRemoveOutline /> }>
+                    { t('discard') }
+                </Button>
+            </Actions>
         </Container>
     </Page>
 }
diff --git a/translations/pl.yaml b/translations/pl.yaml
index c52c43d..1f35c9d 100644
--- a/translations/pl.yaml
+++ b/translations/pl.yaml
@@ -57,7 +57,9 @@ internship:
     semester: semestr {{ semester, roman }}
     album: "numer albumu {{ album }}"
   date-range: "{{ start, DD MMMM YYYY }} - {{ end, DD MMMM YYYY }}"
-  duration: "{{ duration, humanize }}"
+  duration_2: "{{ duration, weeks }} tygodni"
+  duration_0: "{{ duration, weeks }} tydzień"
+  duration_1: "{{ count }} tygodnie"
   hours: "{{ hours }} godzin"
   office: "Oddział / adres"
   address:
-- 
2.45.2


From a75e6957ea727731c187acef362f282b38c01fbf Mon Sep 17 00:00:00 2001
From: Kacper Donat <kadet1090@gmail.com>
Date: Wed, 12 Aug 2020 20:43:08 +0200
Subject: [PATCH 2/5] Add possibility to make comments to proposal

---
 package.json                       |   1 +
 src/components/actions.tsx         |   2 +-
 src/components/proposalPreview.tsx |   1 -
 src/pages/internship/proposal.tsx  | 107 ++++++++++++++++++++++++++---
 src/ui/theme.ts                    |   3 +
 translations/pl.yaml               |  11 ++-
 yarn.lock                          |  37 ++++++++++
 7 files changed, 150 insertions(+), 12 deletions(-)

diff --git a/package.json b/package.json
index 1379ef1..4b1d642 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
     "css-loader": "3.4.2",
     "date-holidays": "^1.5.3",
     "file-loader": "4.3.0",
+    "formik": "^2.1.5",
     "html-webpack-plugin": "4.0.0-beta.11",
     "i18next": "^19.6.0",
     "i18next-browser-languagedetector": "^5.0.0",
diff --git a/src/components/actions.tsx b/src/components/actions.tsx
index b2e090e..79ce5e2 100644
--- a/src/components/actions.tsx
+++ b/src/components/actions.tsx
@@ -4,5 +4,5 @@ import { useHorizontalSpacing } from "@/styles";
 export const Actions = (props: HTMLProps<HTMLDivElement>) => {
     const classes = useHorizontalSpacing(2);
 
-    return <div className={ classes.root } { ...props }/>
+    return <div className={ classes.root } { ...props } style={{ display: "flex", alignItems: "center" }}/>
 }
diff --git a/src/components/proposalPreview.tsx b/src/components/proposalPreview.tsx
index f57798f..1b8a46a 100644
--- a/src/components/proposalPreview.tsx
+++ b/src/components/proposalPreview.tsx
@@ -33,7 +33,6 @@ export const ProposalPreview = ({ proposal }: ProposalPreviewProps) => {
     const { t } = useTranslation();
 
     const classes = useVerticalSpacing(3);
-
     const duration = moment.duration(proposal.endDate.diff(proposal.startDate));
 
     return <div className={ classNames("proposal", classes.root) }>
diff --git a/src/pages/internship/proposal.tsx b/src/pages/internship/proposal.tsx
index 3e92aea..f9767bd 100644
--- a/src/pages/internship/proposal.tsx
+++ b/src/pages/internship/proposal.tsx
@@ -1,9 +1,22 @@
 import { Page } from "@/pages/base";
-import { Button, Container, Link, Typography } from "@material-ui/core";
+import {
+    Button,
+    ButtonGroup,
+    Container,
+    Dialog,
+    DialogActions,
+    DialogContent,
+    DialogTitle,
+    Link,
+    Menu,
+    MenuItem,
+    TextField,
+    Typography
+} from "@material-ui/core";
 import { Link as RouterLink, useHistory } from "react-router-dom";
 import { route } from "@/routing";
 import { InternshipForm } from "@/forms/internship";
-import React from "react";
+import React, { useState } from "react";
 import { ProposalComment } from "@/pages/steps/proposal";
 import { useTranslation } from "react-i18next";
 import { ProposalPreview } from "@/components/proposalPreview";
@@ -13,7 +26,7 @@ import { AppState } from "@/state/reducer";
 import { internshipSerializationTransformer } from "@/serialization";
 import { Actions } from "@/components";
 import { InternshipProposalActions, useDispatch } from "@/state/actions";
-import { StickerCheckOutline, StickerRemoveOutline } from "mdi-material-ui/index";
+import { MenuDown, StickerCheckOutline, StickerRemoveOutline } from "mdi-material-ui/index";
 import { useVerticalSpacing } from "@/styles";
 
 export const InternshipProposalFormPage = () => {
@@ -39,13 +52,49 @@ export const InternshipProposalPreviewPage = () => {
     const dispatch = useDispatch();
     const history = useHistory();
 
+    const [isDiscardModalOpen, setDiscardModelOpen] = useState<boolean>(false);
+    const [isAcceptModalOpen, setAcceptModelOpen] = useState<boolean>(false);
+
+    const [comment, setComment] = useState<string>("");
+    const [menuAnchor, setMenuAnchor] = useState<null | HTMLElement>(null);
+
     const handleAccept = () => {
-        dispatch({ type: InternshipProposalActions.Approve, comment: null });
+        dispatch({ type: InternshipProposalActions.Approve, comment });
         history.push(route("home"));
     }
 
     const handleDiscard = () => {
-        dispatch({ type: InternshipProposalActions.Decline, comment: "Well..." });
+        dispatch({ type: InternshipProposalActions.Decline, comment });
+        history.push(route("home"));
+    }
+
+    const handleAcceptModalClose = () => {
+        setAcceptModelOpen(false);
+    }
+
+    const handleDiscardModalClose = () => {
+        setDiscardModelOpen(false);
+    }
+
+    const handleDiscardAction = () => {
+        setDiscardModelOpen(true);
+    }
+
+    const handleAcceptMenuOpen = (ev: React.MouseEvent<HTMLElement>) => {
+        setMenuAnchor(ev.currentTarget);
+    }
+
+    const handleAcceptMenuClose = () => {
+        setMenuAnchor(null);
+    }
+
+    const handleAcceptWithComment = () => {
+        setAcceptModelOpen(true);
+        setMenuAnchor(null);
+    }
+
+    const handleAcceptWithoutComment = () => {
+        dispatch({ type: InternshipProposalActions.Approve, comment: null });
         history.push(route("home"));
     }
 
@@ -68,15 +117,55 @@ export const InternshipProposalPreviewPage = () => {
                     { t('go-back') }
                 </Button>
 
-                <Button onClick={ handleAccept } color="primary" startIcon={ <StickerCheckOutline /> }>
-                    { t('accept') }
-                </Button>
+                <ButtonGroup color="primary" variant="contained">
+                    <Button onClick={ handleAcceptWithoutComment } startIcon={ <StickerCheckOutline /> }>
+                        { t('accept-without-comments') }
+                    </Button>
+                    <Button size="small" onClick={ handleAcceptMenuOpen }><MenuDown /></Button>
+                </ButtonGroup>
 
-                <Button onClick={ handleDiscard } color="secondary" startIcon={ <StickerRemoveOutline /> }>
+                <Menu open={ !!menuAnchor } anchorEl={ menuAnchor } onClose={ handleAcceptMenuClose }>
+                    <MenuItem onClick={ handleAcceptWithoutComment }>{ t("accept-without-comments") }</MenuItem>
+                    <MenuItem onClick={ handleAcceptWithComment }>{ t("accept-with-comments") }</MenuItem>
+                </Menu>
+
+                <Button onClick={ handleDiscardAction } color="secondary" startIcon={ <StickerRemoveOutline /> }>
                     { t('discard') }
                 </Button>
             </Actions>
         </Container>
+        <Dialog open={ isDiscardModalOpen } onClose={ handleDiscardModalClose } maxWidth="md">
+            <DialogTitle>{ t("internship.discard.title") }</DialogTitle>
+            <DialogContent className={ classes.root }>
+                <Typography variant="body1">{ t("internship.discard.info") }</Typography>
+                <TextField multiline value={ comment } onChange={ ev => setComment(ev.target.value) } fullWidth label={ t("comments") } rows={3}/>
+
+                <DialogActions>
+                    <Button onClick={ handleDiscardModalClose }>
+                        { t('cancel') }
+                    </Button>
+                    <Button onClick={ handleDiscard } color="primary" variant="contained">
+                        { t('confirm') }
+                    </Button>
+                </DialogActions>
+            </DialogContent>
+        </Dialog>
+        <Dialog open={ isAcceptModalOpen } onClose={ handleAcceptModalClose } maxWidth="md">
+            <DialogTitle>{ t("internship.accept.title") }</DialogTitle>
+            <DialogContent className={ classes.root }>
+                <Typography variant="body1">{ t("internship.accept.info") }</Typography>
+                <TextField multiline value={ comment } onChange={ ev => setComment(ev.target.value) } fullWidth label={ t("comments") }/>
+
+                <DialogActions>
+                    <Button onClick={ handleAcceptModalClose }>
+                        { t('cancel') }
+                    </Button>
+                    <Button onClick={ handleAccept } color="primary" variant="contained">
+                        { t('confirm') }
+                    </Button>
+                </DialogActions>
+            </DialogContent>
+        </Dialog>
     </Page>
 }
 
diff --git a/src/ui/theme.ts b/src/ui/theme.ts
index 8f770f0..0cb8e31 100644
--- a/src/ui/theme.ts
+++ b/src/ui/theme.ts
@@ -8,6 +8,9 @@ export const studentTheme = responsiveFontSizes(createMuiTheme({
         },
         MuiContainer: {
             maxWidth: "md"
+        },
+        MuiTextField: {
+            variant: "outlined"
         }
     },
     palette: {
diff --git a/translations/pl.yaml b/translations/pl.yaml
index 1f35c9d..6547b33 100644
--- a/translations/pl.yaml
+++ b/translations/pl.yaml
@@ -20,6 +20,10 @@ comments: Zgłoszone uwagi
 send-again: wyślij ponownie
 cancel: anuluj
 
+accept: zaakceptuj
+accept-with-comments: zaakceptuj z uwagami
+accept-without-comments: zaakceptuj bez uwag
+discard: zgłoś uwagi
 
 dropzone: "Przeciągnij i upuść plik bądź kliknij, aby wybrać"
 
@@ -71,7 +75,12 @@ internship:
     place: "Miejsce odbywania praktyki"
     kind: "Rodzaj i program praktyki"
     mentor: "Zakładowy opiekun praktyki"
-
+  discard:
+    title: "Odrzuć zgłoszenie praktyki"
+    info: "Poniższa informacja zostanie przekazana praktykantowi w celu poprawy zgłoszenia."
+  accept:
+    title: "Zaakceptuj zgłoszenie praktyki"
+    info: "Poniższa informacja zostanie przekazana praktykantowi."
 
 steps:
   personal-data:
diff --git a/yarn.lock b/yarn.lock
index 697aea8..4ed4f49 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3170,6 +3170,11 @@ deep-equal@^1.0.1:
     object-keys "^1.1.1"
     regexp.prototype.flags "^1.2.0"
 
+deepmerge@^2.1.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
+  integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
+
 default-gateway@^4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
@@ -3954,6 +3959,20 @@ form-data@~2.3.2:
     combined-stream "^1.0.6"
     mime-types "^2.1.12"
 
+formik@^2.1.5:
+  version "2.1.5"
+  resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.5.tgz#de5bbbe35543fa6d049fe96b8ee329d6cd6892b8"
+  integrity sha512-bWpo3PiqVDYslvrRjTq0Isrm0mFXHiO33D8MS6t6dWcqSFGeYF52nlpCM2xwOJ6tRVRznDkL+zz/iHPL4LDuvQ==
+  dependencies:
+    deepmerge "^2.1.1"
+    hoist-non-react-statics "^3.3.0"
+    lodash "^4.17.14"
+    lodash-es "^4.17.14"
+    react-fast-compare "^2.0.1"
+    scheduler "^0.18.0"
+    tiny-warning "^1.0.2"
+    tslib "^1.10.0"
+
 forwarded@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@@ -5364,6 +5383,11 @@ locate-path@^5.0.0:
   dependencies:
     p-locate "^4.1.0"
 
+lodash-es@^4.17.14:
+  version "4.17.15"
+  resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
+  integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
+
 lodash._reinterpolate@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@@ -7400,6 +7424,11 @@ react-error-overlay@^6.0.7:
   resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
   integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==
 
+react-fast-compare@^2.0.1:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
+  integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
+
 react-i18next@^11.7.0:
   version "11.7.0"
   resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.7.0.tgz#f27c4c237a274e007a48ac1210db83e33719908b"
@@ -7888,6 +7917,14 @@ sax@~1.2.4:
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
   integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
 
+scheduler@^0.18.0:
+  version "0.18.0"
+  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4"
+  integrity sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==
+  dependencies:
+    loose-envify "^1.1.0"
+    object-assign "^4.1.1"
+
 scheduler@^0.19.1:
   version "0.19.1"
   resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196"
-- 
2.45.2


From c6c66492459db477f86c2da877c44c0598f0a5dc Mon Sep 17 00:00:00 2001
From: Kacper Donat <kadet1090@gmail.com>
Date: Fri, 14 Aug 2020 15:01:34 +0200
Subject: [PATCH 3/5] Add missing translations

---
 src/components/proposalPreview.tsx |  2 +-
 src/forms/company.tsx              | 30 ++++++-----
 src/forms/internship.tsx           | 64 ++++++++--------------
 src/forms/student.tsx              | 63 +++++++++++-----------
 src/pages/internship/plan.tsx      |  2 +-
 src/pages/internship/proposal.tsx  | 10 ++--
 src/pages/main.tsx                 |  2 +-
 src/ui/theme.ts                    |  2 +-
 translations/en.yaml               | 85 +++++++++++++++++++++++++++++-
 translations/pl.yaml               | 36 +++++++++++--
 10 files changed, 196 insertions(+), 100 deletions(-)

diff --git a/src/components/proposalPreview.tsx b/src/components/proposalPreview.tsx
index 1b8a46a..096779e 100644
--- a/src/components/proposalPreview.tsx
+++ b/src/components/proposalPreview.tsx
@@ -72,7 +72,7 @@ export const ProposalPreview = ({ proposal }: ProposalPreviewProps) => {
             <Typography className="proposal__secondary">
                 { t('internship.duration', { duration, count: Math.floor(duration.asWeeks()) }) }
                 { ", " }
-                { t('internship.hours', { hours: proposal.hours }) }
+                { t('internship.hours', { hours: proposal.hours, count: proposal.hours }) }
             </Typography>
         </Section>
 
diff --git a/src/forms/company.tsx b/src/forms/company.tsx
index a728935..d18ddcc 100644
--- a/src/forms/company.tsx
+++ b/src/forms/company.tsx
@@ -7,6 +7,7 @@ import { BoundProperty, formFieldProps } from "./helpers";
 import { InternshipFormSectionProps } from "@/forms/internship";
 import { emptyMentor } from "@/provider/dummy/internship";
 import { useProxyState } from "@/hooks";
+import { useTranslation } from "react-i18next";
 
 export type CompanyFormProps = {} & InternshipFormSectionProps;
 
@@ -32,6 +33,7 @@ export const OfficeItem = ({ office, ...props }: { office: BranchOffice } & HTML
 export const BranchForm: React.FC<BranchOfficeProps> = ({ value: office, onChange: setOffice, offices = [], disabled = false }) => {
     const canEdit = useMemo(() => !office.id && !disabled, [office.id, disabled]);
     const fieldProps = formFieldProps(office.address, address => setOffice({ ...office, address }))
+    const { t } = useTranslation();
 
     const handleCityChange = (event: any, value: BranchOffice | string | null) => {
         if (typeof value === "string") {
@@ -68,7 +70,7 @@ export const BranchForm: React.FC<BranchOfficeProps> = ({ value: office, onChang
                                   disabled={ disabled }
                                   getOptionLabel={ office => typeof office == "string" ? office : office.address.city }
                                   renderOption={ office => <OfficeItem office={ office }/> }
-                                  renderInput={ props => <TextField { ...props } label={ "Miasto" } fullWidth/> }
+                                  renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.city") } fullWidth/> }
                                   onChange={ handleCityChange }
                                   onInputChange={ handleCityInput }
                                   inputValue={ office.address.city }
@@ -77,16 +79,16 @@ export const BranchForm: React.FC<BranchOfficeProps> = ({ value: office, onChang
                     />
                 </Grid>
                 <Grid item md={ 2 }>
-                    <TextField label={ "Kod pocztowy" } fullWidth disabled={ !canEdit } { ...fieldProps("postalCode") }/>
+                    <TextField label={ t("forms.internship.fields.postal-code") } fullWidth disabled={ !canEdit } { ...fieldProps("postalCode") }/>
                 </Grid>
                 <Grid item md={ 3 }>
-                    <TextField label={ "Kraj" } fullWidth disabled={ !canEdit } { ...fieldProps("country") }/>
+                    <TextField label={ t("forms.internship.fields.country") } fullWidth disabled={ !canEdit } { ...fieldProps("country") }/>
                 </Grid>
                 <Grid item md={ 10 }>
-                    <TextField label={ "Ulica" } fullWidth disabled={ !canEdit } { ...fieldProps("street") }/>
+                    <TextField label={ t("forms.internship.fields.street") } fullWidth disabled={ !canEdit } { ...fieldProps("street") }/>
                 </Grid>
                 <Grid item md={ 2 }>
-                    <TextField label={ "Nr Budynku" } fullWidth disabled={ !canEdit } { ...fieldProps("building") }/>
+                    <TextField label={ t("forms.internship.fields.building") } fullWidth disabled={ !canEdit } { ...fieldProps("building") }/>
                 </Grid>
             </Grid>
         </div>
@@ -95,21 +97,22 @@ export const BranchForm: React.FC<BranchOfficeProps> = ({ value: office, onChang
 
 export const MentorForm = ({ mentor, onMentorChange }: BoundProperty<Mentor, 'onMentorChange', 'mentor'>) => {
     const fieldProps = formFieldProps(mentor, onMentorChange)
+    const { t } = useTranslation();
 
     return (
         <>
             <Grid container>
                 <Grid item md={6}>
-                    <TextField label="Imię" fullWidth { ...fieldProps("name") }/>
+                    <TextField label={ t("forms.internship.fields.first-name") } fullWidth { ...fieldProps("name") }/>
                 </Grid>
                 <Grid item md={6}>
-                    <TextField label="Nazwisko" value={ mentor.surname } fullWidth { ...fieldProps("surname") }/>
+                    <TextField label={ t("forms.internship.fields.last-name") } value={ mentor.surname } fullWidth { ...fieldProps("surname") }/>
                 </Grid>
                 <Grid item md={8}>
-                    <TextField label="E-mail" value={ mentor.email } fullWidth { ...fieldProps("email") }/>
+                    <TextField label={ t("forms.internship.fields.e-mail") } value={ mentor.email } fullWidth { ...fieldProps("email") }/>
                 </Grid>
                 <Grid item md={4}>
-                    <TextField label="Nr telefonu" value={ mentor.phone } fullWidth { ...fieldProps("phone") }/>
+                    <TextField label={ t("forms.internship.fields.phone" )} value={ mentor.phone } fullWidth { ...fieldProps("phone") }/>
                 </Grid>
             </Grid>
         </>
@@ -120,6 +123,7 @@ export const CompanyForm: React.FunctionComponent<CompanyFormProps> = ({ interns
     const [company, setCompany] = useProxyState<Company>(internship.company || emptyCompany, company => onChange({ ...internship, company }));
     const [mentor, setMentor] = useProxyState<Mentor>(internship.mentor || emptyMentor, mentor => onChange({ ...internship, mentor }));
     const [office, setOffice] = useProxyState<BranchOffice>(internship.office || emptyBranchOffice, office => onChange({ ...internship, office }));
+    const { t } = useTranslation();
 
     const canEdit = useMemo(() => !company.id, [company.id]);
 
@@ -145,21 +149,21 @@ export const CompanyForm: React.FunctionComponent<CompanyFormProps> = ({ interns
                     <Autocomplete options={ sampleCompanies }
                                   getOptionLabel={ option => option.name }
                                   renderOption={ company => <CompanyItem company={ company }/> }
-                                  renderInput={ props => <TextField { ...props } label={ "Nazwa firmy" } fullWidth/> }
+                                  renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.company-name") } fullWidth/> }
                                   onChange={ handleCompanyChange } value={ company }
                                   freeSolo
                     />
                 </Grid>
                 <Grid item md={ 4 }>
-                    <TextField label={ "NIP" } fullWidth { ...fieldProps("nip") } disabled={ !canEdit }/>
+                    <TextField label={ t("forms.internship.fields.nip") } fullWidth { ...fieldProps("nip") } disabled={ !canEdit }/>
                 </Grid>
                 {/*<Grid item md={ 8 }>*/}
                 {/*    <TextField label={ "Url" } fullWidth { ...fieldProps("url") } disabled={ !canEdit }/>*/}
                 {/*</Grid>*/}
             </Grid>
-            <Typography variant="subtitle1" className="subsection-header">Zakładowy opiekun praktyki</Typography>
+            <Typography variant="subtitle1" className="subsection-header">{ t("internship.mentor") }</Typography>
             <MentorForm mentor={ mentor } onMentorChange={ setMentor }/>
-            <Typography variant="subtitle1" className="subsection-header">Oddział</Typography>
+            <Typography variant="subtitle1" className="subsection-header">{ t("internship.office") }</Typography>
             <BranchForm value={ office } onChange={ setOffice } offices={ company.offices } />
         </>
     )
diff --git a/src/forms/internship.tsx b/src/forms/internship.tsx
index f4d2324..a6c5e3a 100644
--- a/src/forms/internship.tsx
+++ b/src/forms/internship.tsx
@@ -1,23 +1,10 @@
 import React, { HTMLProps, useEffect, useMemo, useState } from "react";
-import {
-    Button,
-    Dialog,
-    DialogActions,
-    DialogContent,
-    DialogContentText,
-    FormControl,
-    FormHelperText,
-    Grid,
-    Input,
-    InputLabel,
-    TextField,
-    Typography
-} from "@material-ui/core";
+import { Button, Dialog, DialogActions, DialogContent, DialogContentText, Grid, TextField, Typography } from "@material-ui/core";
 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 { Course, Internship, InternshipType, internshipTypeLabels } from "@/data";
+import { Internship, InternshipType, internshipTypeLabels } from "@/data";
 import { Nullable } from "@/helpers";
 import moment, { Moment } from "moment";
 import { computeWorkingHours } from "@/utils/date";
@@ -55,12 +42,12 @@ export const InternshipTypeItem = ({ type, ...props }: { type: InternshipType }
 const InternshipProgramForm = ({ internship, onChange }: InternshipFormSectionProps) => {
     const fieldProps = formFieldProps(internship, onChange);
 
-    const course = internship.intern?.course as Course;
+    const { t } = useTranslation();
 
     return (
         <Grid container>
             <Grid item md={ 4 }>
-                <Autocomplete renderInput={ props => <TextField { ...props } label="Rodzaj praktyki/umowy" fullWidth/> }
+                <Autocomplete renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.kind") } fullWidth/> }
                               getOptionLabel={ (option: InternshipType) => internshipTypeLabels[option].label }
                               renderOption={ (option: InternshipType) => <InternshipTypeItem type={ option }/> }
                               options={ Object.values(InternshipType) as InternshipType[] }
@@ -69,7 +56,7 @@ const InternshipProgramForm = ({ internship, onChange }: InternshipFormSectionPr
                 />
             </Grid>
             <Grid item md={ 8 }>
-                { internship.type === InternshipType.Other && <TextField label={ "Inny - Wprowadź" } fullWidth/> }
+                { internship.type === InternshipType.Other && <TextField label={ t("forms.internship.fields.kind") } fullWidth/> }
             </Grid>
             {/*<Grid item>*/ }
             {/*    <FormGroup>*/ }
@@ -88,6 +75,8 @@ const InternshipProgramForm = ({ internship, onChange }: InternshipFormSectionPr
 }
 
 const InternshipDurationForm = ({ internship, onChange }: InternshipFormSectionProps) => {
+    const { t } = useTranslation();
+
     const [startDate, setStartDate] = useProxyState<Moment | null>(internship.startDate, value => onChange({ ...internship, startDate: value }));
     const [endDate, setEndDate] = useProxyState<Moment | null>(internship.endDate, value => onChange({ ...internship, endDate: value }));
 
@@ -107,45 +96,34 @@ const InternshipDurationForm = ({ internship, onChange }: InternshipFormSectionP
                 <DatePicker value={ startDate } onChange={ setStartDate }
                             format="DD MMMM yyyy"
                             clearable disableToolbar fullWidth
-                            variant="inline" label={ "Data rozpoczęcia praktyki" }
+                            variant="inline" label={ t("forms.internship.fields.start-date") }
+                            minDate={ moment() }
                 />
             </Grid>
             <Grid item md={ 6 }>
                 <DatePicker value={ endDate } onChange={ setEndDate }
                             format="DD MMMM yyyy"
                             clearable disableToolbar fullWidth
-                            variant="inline" label={ "Data zakończenia praktyki" }
+                            variant="inline" label={ t("forms.internship.fields.end-date") }
                             minDate={ startDate || moment() }
                 />
             </Grid>
             <Grid item md={ 4 }>
-                <FormControl fullWidth>
-                    <InputLabel>Wymiar etatu</InputLabel>
-                    <Input value={ workingHours }
-                           onChange={ ev => setWorkingHours(parseInt(ev.target.value) || 0) }
-                           fullWidth
-                    />
-                    <FormHelperText>Liczba godzin w tygodniu roboczym</FormHelperText>
-                </FormControl>
+                <TextField fullWidth label={ t("forms.internship.fields.working-hours") }
+                           value={ workingHours } onChange={ ev => setWorkingHours(parseInt(ev.target.value) || 0) }
+                           helperText={ t("forms.internship.help.working-hours") }
+                />
             </Grid>
             <Grid item md={ 4 }>
-                <FormControl fullWidth>
-                    <InputLabel>Łączna liczba godzin</InputLabel>
-                    <Input value={ hours || "" }
-                           onChange={ ev => setHoursOverride(parseInt(ev.target.value) || 0) }
-                           fullWidth
-                    />
-                </FormControl>
+                <TextField fullWidth label={ t("forms.internship.fields.total-hours") }
+                           value={ hours } onChange={ ev => setHoursOverride(parseInt(ev.target.value) || 0) }
+                />
             </Grid>
             <Grid item md={ 4 }>
-                <FormControl fullWidth>
-                    <InputLabel>Liczba tygodni</InputLabel>
-                    <Input value={ weeks || "" }
-                           disabled
-                           fullWidth
-                    />
-                    <FormHelperText>Wyliczona automatycznie</FormHelperText>
-                </FormControl>
+                <TextField fullWidth label={ t("forms.internship.fields.weeks") }
+                           value={ weeks } disabled
+                           helperText={ t("forms.internship.help.weeks") }
+                />
             </Grid>
         </Grid>
     );
diff --git a/src/forms/student.tsx b/src/forms/student.tsx
index da722e8..db1ec6e 100644
--- a/src/forms/student.tsx
+++ b/src/forms/student.tsx
@@ -3,42 +3,43 @@ 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";
 
 type StudentFormProps = {
     student: Student
 }
 
 export const StudentForm = ({ student }: StudentFormProps) => {
-    return (
-        <>
-            <Grid container>
-                <Grid item md={4}>
-                    <TextField label="Imię" value={ student.name } disabled fullWidth/>
-                </Grid>
-                <Grid item md={4}>
-                    <TextField label="Nazwisko" value={ student.surname } disabled fullWidth/>
-                </Grid>
-                <Grid item md={4}>
-                    <TextField label="Nr Indeksu" value={ student.albumNumber } disabled fullWidth/>
-                </Grid>
-                <Grid item md={9}>
-                    <Autocomplete
-                        getOptionLabel={ (course: Course) => course.name }
-                        renderInput={ props => <TextField { ...props } label={ "Kierunek" } fullWidth/> }
-                        options={[ sampleCourse ]}
-                        value={ student.course }
-                        disabled
-                    />
-                </Grid>
-                <Grid item md={3}>
-                    <TextField label="Semestr" value={ student.semester } disabled fullWidth/>
-                </Grid>
-                <Grid item>
-                    <Alert severity="warning" action={ <Button color="inherit" size="small">skontaktuj się z opiekunem</Button> }>
-                        Powyższe dane nie są poprawne?
-                    </Alert>
-                </Grid>
+    const { t } = useTranslation();
+
+    return <>
+        <Grid container>
+            <Grid item md={4}>
+                <TextField label={ t("forms.internship.fields.first-name") } value={ student.name } disabled fullWidth/>
             </Grid>
-        </>
-    );
+            <Grid item md={4}>
+                <TextField label={ t("forms.internship.fields.last-name") } value={ student.surname } disabled fullWidth/>
+            </Grid>
+            <Grid item md={4}>
+                <TextField label={ t("forms.internship.fields.album") } value={ student.albumNumber } disabled fullWidth/>
+            </Grid>
+            <Grid item md={9}>
+                <Autocomplete
+                    getOptionLabel={ (course: Course) => course.name }
+                    renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.course") } fullWidth/> }
+                    options={[ sampleCourse ]}
+                    value={ student.course }
+                    disabled
+                />
+            </Grid>
+            <Grid item md={3}>
+                <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> }>
+                    Powyższe dane nie są poprawne?
+                </Alert>
+            </Grid>
+        </Grid>
+    </>;
 }
diff --git a/src/pages/internship/plan.tsx b/src/pages/internship/plan.tsx
index e09a214..5706817 100644
--- a/src/pages/internship/plan.tsx
+++ b/src/pages/internship/plan.tsx
@@ -12,7 +12,7 @@ export const SubmitPlanPage = () => {
     return <Page title={ t("steps.plan.submit") }>
         <Page.Header maxWidth="md">
             <Page.Breadcrumbs>
-                <Link component={ RouterLink } to={ route("home") }>{ t('sections.my-internship.header') }</Link>
+                <Link component={ RouterLink } to={ route("home") }>{ t('pages.my-internship.header') }</Link>
                 <Typography color="textPrimary">{ t("steps.plan.submit") }</Typography>
             </Page.Breadcrumbs>
             <Page.Title>{ t("steps.plan.submit") }</Page.Title>
diff --git a/src/pages/internship/proposal.tsx b/src/pages/internship/proposal.tsx
index f9767bd..939998b 100644
--- a/src/pages/internship/proposal.tsx
+++ b/src/pages/internship/proposal.tsx
@@ -30,13 +30,15 @@ import { MenuDown, StickerCheckOutline, StickerRemoveOutline } from "mdi-materia
 import { useVerticalSpacing } from "@/styles";
 
 export const InternshipProposalFormPage = () => {
-    return <Page title="Zgłoszenie praktyki">
+    const { t } = useTranslation();
+
+    return <Page title={ t("pages.proposal-form.header") }>
         <Page.Header maxWidth="md">
             <Page.Breadcrumbs>
-                <Link component={ RouterLink } to={ route("home") }>Moja praktyka</Link>
-                <Typography color="textPrimary">Zgłoszenie praktyki</Typography>
+                <Link component={ RouterLink } to={ route("home") }>{ t("pages.my-internship.header") }</Link>
+                <Typography color="textPrimary">{ t("pages.proposal-form.header") }</Typography>
             </Page.Breadcrumbs>
-            <Page.Title>Zgłoszenie praktyki</Page.Title>
+            <Page.Title>{ t("pages.proposal-form.header") }</Page.Title>
         </Page.Header>
         <Container maxWidth={ "md" }>
             <ProposalComment />
diff --git a/src/pages/main.tsx b/src/pages/main.tsx
index 5d2da88..f03e9b3 100644
--- a/src/pages/main.tsx
+++ b/src/pages/main.tsx
@@ -25,7 +25,7 @@ export const MainPage = () => {
 
     return <Page my={ 6 }>
         <Container>
-            <Typography variant="h2">{ t("sections.my-internship.header") }</Typography>
+            <Typography variant="h2">{ t("pages.my-internship.header") }</Typography>
             <Stepper orientation="vertical" nonLinear>
                 <Step label={ t('steps.personal-data.header') } completed={ missingStudentData.length === 0 } until={ deadlines.personalData }>
                     { missingStudentData.length > 0 && <>
diff --git a/src/ui/theme.ts b/src/ui/theme.ts
index 0cb8e31..cf65aad 100644
--- a/src/ui/theme.ts
+++ b/src/ui/theme.ts
@@ -10,7 +10,7 @@ export const studentTheme = responsiveFontSizes(createMuiTheme({
             maxWidth: "md"
         },
         MuiTextField: {
-            variant: "outlined"
+            variant: "outlined",
         }
     },
     palette: {
diff --git a/translations/en.yaml b/translations/en.yaml
index 902dbbd..1fc2920 100644
--- a/translations/en.yaml
+++ b/translations/en.yaml
@@ -11,6 +11,41 @@ left: '{{ left, humanize }} left'
 
 dropzone: "Drag and drop a file here or click to choose"
 
+forms:
+  internship:
+    fields:
+      start-date: Internship start date
+      end-date: Internship end date
+      working-hours: Working time
+      total-hours: Total hours
+      weeks: Total weeks
+      first-name: First name
+      last-name: Last name
+      album: Album number
+      course: Course
+      semester: Semester
+      kind: Contract type
+      kind-other: Other - please fill
+      company-name: Company name
+      nip: NIP
+      e-mail: e-mail address
+      phone: Phone number
+      city: City
+      postal-code: Postal code
+      country: Country
+      street: Street
+      building: Building
+    help:
+      weeks: Calculated automatically
+      working-hours: Total working hours in working week
+    send-confirmation: >
+      Po wysłaniu zgłoszenia nie będzie możliwości jego zmiany do czasu zweryfikowania go przez pełnomocnika ds. Twojego
+      kierunku. Czy na pewno chcesz wysłać zgłoszenie praktyki w tej formie?
+  plan:
+    instructions: >
+      Wypełnij i zeskanuj Indywidualny program Praktyk a następnie wyślij go z pomocą tego formularza. <więcej informacji>
+    dropzone-help: Skan dokumentu w formacie PDF
+
 student:
   name: first name
   surname: last name
@@ -19,7 +54,41 @@ student:
   email: e-mail
   albumNumber: album number
 
-sections:
+internship:
+  intern:
+    semester: semesetr {{ semester, roman }}
+    album: "album number {{ album }}"
+  date-range: "{{ start, DD MMMM YYYY }} - {{ end, DD MMMM YYYY }}"
+  duration: "{{ duration, weeks }} week"
+  duration_plural: "{{ duration, weeks }} weeks"
+  hours: "{{ hours }} hour"
+  hours_plural: "{{ hours }} hours"
+  office: "Office / Address"
+  mentor: "Internship mentor"
+  address:
+    city: "{{ city }}, {{ country }}"
+    street: "{{ postalCode }}, {{ street }} {{ building }}"
+  sections:
+    intern-info: "Intern personal data"
+    duration: "Internship duration"
+    place: "Internship place"
+    kind: "Contract and programme"
+    mentor: "Internship mentor"
+  discard:
+    title: "Discard internship proposal"
+    info: "This comments will be presented to student in order to fix errors."
+  accept:
+    title: "Accept internship proposal"
+    info: "This comments will be presented to student."
+
+submission:
+  status:
+    awaiting: "sent, awaiting verification"
+    accepted: "accepted"
+    declined: "needs correction"
+    draft: "draft"
+
+pages:
   my-internship:
     header: "My internship"
 
@@ -29,10 +98,22 @@ steps:
     info: >
       Your profile is incomplete. In order to continue your internship you have to supply information given below. In
       case of problem with providing those information - please contact with your internship coordinator of your course.
+    form: "Add missing data"
   internship-proposal:
     header: "Internship proposal"
     form: "Internship proposal form"
-    info: ""
+    info:
+      draft: >
+        Przed podjęciem praktyki należy ją zgłosić. (TODO)
+      awaiting: >
+        Twoje zgłoszenie musi zostać zweryfikowane i zatwierdzone. Po weryfikacji zostaniesz poinformowany o
+        akceptacji bądź konieczności wprowadzenia zmian.
+      accepted: >
+        Twoje zgłoszenie zostało zweryfikowane i zaakceptowane.
+      declined: >
+        Twoje zgłoszenie zostało zweryfikowane i odrzucone. Popraw zgłoszone uwagi i wyślij zgłoszenie ponownie. W razie
+        pytań możesz również skontaktować się z pełnomocnikiem ds. praktyk Twojego kierunku.
+    action: "Send internship proposal"
   plan:
     header: "Individual Internship Plan"
     info: ""
diff --git a/translations/pl.yaml b/translations/pl.yaml
index 6547b33..a82d788 100644
--- a/translations/pl.yaml
+++ b/translations/pl.yaml
@@ -27,12 +27,39 @@ discard: zgłoś uwagi
 
 dropzone: "Przeciągnij i upuść plik bądź kliknij, aby wybrać"
 
-sections:
+pages:
   my-internship:
     header: "Moja praktyka"
+  proposal-form:
+    header: "Propose internship"
 
 forms:
   internship:
+    fields:
+      start-date: Data rozpoczęcia praktyki
+      end-date: Data zakończenia praktyki
+      working-hours: Wymiar etatu
+      total-hours: Łączna liczba godzin
+      weeks: Liczba tygodni
+      first-name: Imię
+      last-name: Nazwisko
+      album: Numer albumu
+      course: Kierunek
+      semester: Semestr
+      kind: Rodzaj praktyki/umowy
+      kind-other: Inny - wprowadź
+      company-name: Nazwa firmy
+      nip: NIP
+      e-mail: Kontaktowy adres e-mail
+      phone: Numer telefonu
+      city: Miasto
+      postal-code: Kod pocztowy
+      country: Kraj
+      street: Ulica
+      building: Nr budynku
+    help:
+      weeks: Wartość wyliczana automatycznie
+      working-hours: Liczba godzin w tygodniu roboczym
     send-confirmation: >
       Po wysłaniu zgłoszenia nie będzie możliwości jego zmiany do czasu zweryfikowania go przez pełnomocnika ds. Twojego
       kierunku. Czy na pewno chcesz wysłać zgłoszenie praktyki w tej formie?
@@ -63,9 +90,12 @@ internship:
   date-range: "{{ start, DD MMMM YYYY }} - {{ end, DD MMMM YYYY }}"
   duration_2: "{{ duration, weeks }} tygodni"
   duration_0: "{{ duration, weeks }} tydzień"
-  duration_1: "{{ count }} tygodnie"
-  hours: "{{ hours }} godzin"
+  duration_1: "{{ duration, weeks }} tygodnie"
+  hours_2: "{{ hours }} godzin"
+  hours_0: "{{ hours }} godzina"
+  hours_1: "{{ hours }} godziny"
   office: "Oddział / adres"
+  mentor: "Zakładowy opiekun praktyki"
   address:
     city: "{{ city }}, {{ country }}"
     street: "{{ postalCode }}, {{ street }} {{ building }}"
-- 
2.45.2


From 99e12f76813fd7770afc395f17a18b78b4c711a6 Mon Sep 17 00:00:00 2001
From: Kacper Donat <kadet1090@gmail.com>
Date: Fri, 14 Aug 2020 15:51:27 +0200
Subject: [PATCH 4/5] Add mentor validation

---
 package.json             |   5 +-
 src/data/common.ts       |   4 +-
 src/forms/company.tsx    |  35 ++++++------
 src/forms/internship.tsx | 114 ++++++++++++++++++++++++++++++---------
 translations/pl.yaml     |   5 ++
 yarn.lock                |  52 +++++++++++++++++-
 6 files changed, 168 insertions(+), 47 deletions(-)

diff --git a/package.json b/package.json
index 4b1d642..c5eba09 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
     "@types/react-router-dom": "^5.1.5",
     "@types/redux": "^3.6.0",
     "@types/redux-persist": "^4.3.1",
+    "@types/yup": "^0.29.4",
     "@typescript-eslint/eslint-plugin": "^2.10.0",
     "@typescript-eslint/parser": "^2.10.0",
     "babel-core": "^6.26.3",
@@ -30,6 +31,7 @@
     "date-holidays": "^1.5.3",
     "file-loader": "4.3.0",
     "formik": "^2.1.5",
+    "formik-material-ui": "^3.0.0-alpha.0",
     "html-webpack-plugin": "4.0.0-beta.11",
     "i18next": "^19.6.0",
     "i18next-browser-languagedetector": "^5.0.0",
@@ -63,7 +65,8 @@
     "webpack-cli": "^3.3.11",
     "webpack-dev-server": "3.10.3",
     "workbox-webpack-plugin": "4.3.1",
-    "yaml-loader": "^0.6.0"
+    "yaml-loader": "^0.6.0",
+    "yup": "^0.29.3"
   },
   "scripts": {
     "serve": "webpack-dev-server --mode development",
diff --git a/src/data/common.ts b/src/data/common.ts
index 62671b8..5a735c4 100644
--- a/src/data/common.ts
+++ b/src/data/common.ts
@@ -1,3 +1,5 @@
+export type Identifier = string;
+
 export interface Identifiable {
-    id?: string
+    id?: Identifier
 }
diff --git a/src/forms/company.tsx b/src/forms/company.tsx
index d18ddcc..4cfd2b8 100644
--- a/src/forms/company.tsx
+++ b/src/forms/company.tsx
@@ -8,6 +8,8 @@ import { InternshipFormSectionProps } from "@/forms/internship";
 import { emptyMentor } from "@/provider/dummy/internship";
 import { useProxyState } from "@/hooks";
 import { useTranslation } from "react-i18next";
+import { Field } from "formik";
+import { TextField as TextFieldFormik } from "formik-material-ui"
 
 export type CompanyFormProps = {} & InternshipFormSectionProps;
 
@@ -95,27 +97,24 @@ export const BranchForm: React.FC<BranchOfficeProps> = ({ value: office, onChang
     )
 }
 
-export const MentorForm = ({ mentor, onMentorChange }: BoundProperty<Mentor, 'onMentorChange', 'mentor'>) => {
-    const fieldProps = formFieldProps(mentor, onMentorChange)
+export const MentorForm = () => {
     const { t } = useTranslation();
 
     return (
-        <>
-            <Grid container>
-                <Grid item md={6}>
-                    <TextField label={ t("forms.internship.fields.first-name") } fullWidth { ...fieldProps("name") }/>
-                </Grid>
-                <Grid item md={6}>
-                    <TextField label={ t("forms.internship.fields.last-name") } value={ mentor.surname } fullWidth { ...fieldProps("surname") }/>
-                </Grid>
-                <Grid item md={8}>
-                    <TextField label={ t("forms.internship.fields.e-mail") } value={ mentor.email } fullWidth { ...fieldProps("email") }/>
-                </Grid>
-                <Grid item md={4}>
-                    <TextField label={ t("forms.internship.fields.phone" )} value={ mentor.phone } fullWidth { ...fieldProps("phone") }/>
-                </Grid>
+        <Grid container>
+            <Grid item md={6}>
+                <Field name="mentorFirstName" label={ t("forms.internship.fields.first-name") } fullWidth component={ TextFieldFormik } />
             </Grid>
-        </>
+            <Grid item md={6}>
+                <Field name="mentorLastName" label={ t("forms.internship.fields.last-name") } fullWidth component={ TextFieldFormik } />
+            </Grid>
+            <Grid item md={8}>
+                <Field name="mentorEmail" label={ t("forms.internship.fields.e-mail") } fullWidth component={ TextFieldFormik } />
+            </Grid>
+            <Grid item md={4}>
+                <Field name="mentorPhone" label={ t("forms.internship.fields.phone") } fullWidth component={ TextFieldFormik } />
+            </Grid>
+        </Grid>
     );
 }
 
@@ -162,7 +161,7 @@ export const CompanyForm: React.FunctionComponent<CompanyFormProps> = ({ interns
                 {/*</Grid>*/}
             </Grid>
             <Typography variant="subtitle1" className="subsection-header">{ t("internship.mentor") }</Typography>
-            <MentorForm mentor={ mentor } onMentorChange={ setMentor }/>
+            <MentorForm />
             <Typography variant="subtitle1" className="subsection-header">{ t("internship.office") }</Typography>
             <BranchForm value={ office } onChange={ setOffice } offices={ company.offices } />
         </>
diff --git a/src/forms/internship.tsx b/src/forms/internship.tsx
index a6c5e3a..7b487eb 100644
--- a/src/forms/internship.tsx
+++ b/src/forms/internship.tsx
@@ -4,7 +4,7 @@ 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 { Internship, InternshipType, internshipTypeLabels } from "@/data";
+import { BranchOffice, Company, Internship, InternshipType, internshipTypeLabels, Student } from "@/data";
 import { Nullable } from "@/helpers";
 import moment, { Moment } from "moment";
 import { computeWorkingHours } from "@/utils/date";
@@ -20,6 +20,8 @@ import { route } from "@/routing";
 import { useProxyState } from "@/hooks";
 import { getInternshipProposal } from "@/state/reducer/proposal";
 import { Actions } from "@/components";
+import { Form, Formik } from "formik";
+import * as Yup from "yup";
 
 export type InternshipFormProps = {}
 
@@ -28,6 +30,50 @@ export type InternshipFormSectionProps = {
     onChange: (internship: Nullable<Internship>) => void,
 }
 
+export type InternshipFormState = {
+    startDate: Moment | null;
+    endDate: Moment | null;
+    hours: number | null;
+    companyName: string;
+    companyNip: string;
+    city: string;
+    postalCode: string;
+    country: string;
+    building: string;
+    mentorFirstName: string;
+    mentorLastName: string;
+    mentorEmail: string;
+    mentorPhone: string;
+    kindOther: string | null;
+
+    // relations
+    kind: InternshipType | null;
+    company: Company | null;
+    office: BranchOffice | null;
+    student: Student | null;
+}
+
+const emptyInternshipValues: InternshipFormState = {
+    building: "",
+    city: "",
+    company: null,
+    companyName: "",
+    companyNip: "",
+    country: "",
+    endDate: null,
+    hours: null,
+    kind: null,
+    kindOther: "",
+    mentorEmail: "",
+    mentorFirstName: "",
+    mentorLastName: "",
+    mentorPhone: "",
+    office: null,
+    postalCode: "",
+    startDate: null,
+    student: null
+}
+
 export const InternshipTypeItem = ({ type, ...props }: { type: InternshipType } & HTMLProps<any>) => {
     const info = internshipTypeLabels[type];
 
@@ -158,34 +204,50 @@ export const InternshipForm: React.FunctionComponent<InternshipFormProps> = prop
         setConfirmDialogOpen(false);
     }
 
+    const validationSchema = Yup.object<Partial<InternshipFormState>>({
+        mentorFirstName: Yup.string().required(t("validation.required")),
+        mentorLastName: Yup.string().required(t("validation.required")),
+        mentorEmail: Yup.string()
+            .required(t("validation.required"))
+            .email(t("validation.email")),
+        mentorPhone: Yup.string()
+            .required(t("validation.required"))
+            .matches(/^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*$/, t("validation.phone")),
+        hours: Yup.number()
+            .min(40, t("validation.internship.minimum-hours")) // todo: take it from edition
+    })
+
     return (
-        <div className="internship-form">
-            <Typography variant="h3" className="section-header">{ t('internship.sections.intern-info') }</Typography>
-            <StudentForm student={ sampleStudent }/>
-            <Typography variant="h3" className="section-header">{ t('internship.sections.kind' )}</Typography>
-            <InternshipProgramForm internship={ internship } onChange={ setInternship }/>
-            <Typography variant="h3" className="section-header">{ t('internship.sections.duration') }</Typography>
-            <InternshipDurationForm internship={ internship } onChange={ setInternship }/>
-            <Typography variant="h3" className="section-header">{ t('internship.sections.place') }</Typography>
-            <CompanyForm internship={ internship } onChange={ setInternship }/>
-            <Actions>
-                <Button variant="contained" color="primary" onClick={ handleSubmitConfirmation }>{ t("confirm") }</Button>
+        <Formik initialValues={ emptyInternshipValues } onSubmit={ values => console.log(values) }
+                validationSchema={ validationSchema } validateOnChange={ false } validateOnBlur={ true }>
+            { formik => <Form>
+                <Typography variant="h3" className="section-header">{ t('internship.sections.intern-info') }</Typography>
+                <StudentForm student={ sampleStudent }/>
+                <Typography variant="h3" className="section-header">{ t('internship.sections.kind' )}</Typography>
+                <InternshipProgramForm internship={ internship } onChange={ setInternship }/>
+                <Typography variant="h3" className="section-header">{ t('internship.sections.duration') }</Typography>
+                <InternshipDurationForm internship={ internship } onChange={ setInternship }/>
+                <Typography variant="h3" className="section-header">{ t('internship.sections.place') }</Typography>
+                <CompanyForm internship={ internship } onChange={ setInternship }/>
+                <Actions>
+                    <Button variant="contained" color="primary" onClick={ () => formik.validateForm(formik.values) }>{ t("confirm") }</Button>
 
-                <Button component={ RouterLink } to={ route("home") }>
-                    { t('go-back') }
-                </Button>
-            </Actions>
+                    <Button component={ RouterLink } to={ route("home") }>
+                        { t('go-back') }
+                    </Button>
+                </Actions>
 
-            <Dialog open={ confirmDialogOpen } onClose={ handleCancel }>
-                <DialogContent>
-                    <DialogContentText>{ t('forms.internship.send-confirmation') }</DialogContentText>
-                </DialogContent>
-                <DialogActions>
-                    <Button onClick={ handleCancel }>{ t('cancel') }</Button>
-                    <Button color="primary" autoFocus onClick={ handleSubmit }>{ t('confirm') }</Button>
-                </DialogActions>
-            </Dialog>
-        </div>
+                <Dialog open={ confirmDialogOpen } onClose={ handleCancel }>
+                    <DialogContent>
+                        <DialogContentText>{ t('forms.internship.send-confirmation') }</DialogContentText>
+                    </DialogContent>
+                    <DialogActions>
+                        <Button onClick={ handleCancel }>{ t('cancel') }</Button>
+                        <Button color="primary" autoFocus onClick={ formik.submitForm }>{ t('confirm') }</Button>
+                    </DialogActions>
+                </Dialog>
+            </Form> }
+        </Formik>
     )
 }
 
diff --git a/translations/pl.yaml b/translations/pl.yaml
index a82d788..8fc6115 100644
--- a/translations/pl.yaml
+++ b/translations/pl.yaml
@@ -159,4 +159,9 @@ steps:
     instructions: >
       papierki do podpisania...
 
+validation:
+  required: "To pole jest wymagane"
+  email: "Wprowadź poprawny adres e-mail"
+  phone: "Wprowadź poprawny numer telefonu"
+
 contact-coordinator: "Skontaktuj się z koordynatorem"
diff --git a/yarn.lock b/yarn.lock
index 4ed4f49..634fa14 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -933,6 +933,13 @@
   dependencies:
     regenerator-runtime "^0.13.4"
 
+"@babel/runtime@^7.10.5":
+  version "7.11.2"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
+  integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==
+  dependencies:
+    regenerator-runtime "^0.13.4"
+
 "@babel/template@^7.10.1", "@babel/template@^7.8.6":
   version "7.10.1"
   resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811"
@@ -1311,6 +1318,11 @@
     "@types/webpack-sources" "*"
     source-map "^0.6.0"
 
+"@types/yup@^0.29.4":
+  version "0.29.4"
+  resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.4.tgz#f7b1f9978180d5155663c1cd0ecdc41a72c23d81"
+  integrity sha512-OQ7gZRQb7eSbGu5h57tbK67sgX8UH5wbuqPORTFBG7qiBtOkEf1dXAr0QULyHIeRwaGLPYxPXiQru+40ClR6ng==
+
 "@typescript-eslint/eslint-plugin@^2.10.0":
   version "2.34.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9"
@@ -3919,6 +3931,11 @@ flush-write-stream@^1.0.0:
     inherits "^2.0.3"
     readable-stream "^2.3.6"
 
+fn-name@~3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c"
+  integrity sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA==
+
 follow-redirects@^1.0.0:
   version "1.11.0"
   resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb"
@@ -3959,6 +3976,11 @@ form-data@~2.3.2:
     combined-stream "^1.0.6"
     mime-types "^2.1.12"
 
+formik-material-ui@^3.0.0-alpha.0:
+  version "3.0.0-alpha.0"
+  resolved "https://registry.yarnpkg.com/formik-material-ui/-/formik-material-ui-3.0.0-alpha.0.tgz#4020b5cbd9e431406fb275a317cdce95ad398545"
+  integrity sha512-N9JcSngi4nWaKN67sN1M3ILXgz0fLCdoMhHHecrZC3NeR+C5lWWAUuAcjGZWNj+z87Qt7NW8VXlxSnGxGus8Uw==
+
 formik@^2.1.5:
   version "2.1.5"
   resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.5.tgz#de5bbbe35543fa6d049fe96b8ee329d6cd6892b8"
@@ -5383,7 +5405,7 @@ locate-path@^5.0.0:
   dependencies:
     p-locate "^4.1.0"
 
-lodash-es@^4.17.14:
+lodash-es@^4.17.11, lodash-es@^4.17.14:
   version "4.17.15"
   resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
   integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
@@ -7216,6 +7238,11 @@ prop-types@^15.6.2, prop-types@^15.7.2:
     object-assign "^4.1.1"
     react-is "^16.8.1"
 
+property-expr@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.2.tgz#fff2a43919135553a3bc2fdd94bdb841965b2330"
+  integrity sha512-bc/5ggaYZxNkFKj374aLbEDqVADdYaLcFo8XBkishUWbaAdjlphaBFns9TvRA2pUseVL/wMFmui9X3IdNDU37g==
+
 proxy-addr@~2.0.5:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
@@ -8591,6 +8618,11 @@ symbol-observable@^1.2.0:
   resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
   integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
 
+synchronous-promise@^2.0.13:
+  version "2.0.13"
+  resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.13.tgz#9d8c165ddee69c5a6542862b405bc50095926702"
+  integrity sha512-R9N6uDkVsghHePKh1TEqbnLddO2IY25OcsksyFp/qBe7XYd0PVbKEWxhcdMhpLzE1I6skj5l4aEZ3CRxcbArlA==
+
 tapable@^1.0.0, tapable@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
@@ -8733,6 +8765,11 @@ toidentifier@1.0.0:
   resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
   integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
 
+toposort@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
+  integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=
+
 tough-cookie@~2.5.0:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
@@ -9506,3 +9543,16 @@ yargs@^13.3.2:
     which-module "^2.0.0"
     y18n "^4.0.0"
     yargs-parser "^13.1.2"
+
+yup@^0.29.3:
+  version "0.29.3"
+  resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.3.tgz#69a30fd3f1c19f5d9e31b1cf1c2b851ce8045fea"
+  integrity sha512-RNUGiZ/sQ37CkhzKFoedkeMfJM0vNQyaz+wRZJzxdKE7VfDeVKH8bb4rr7XhRLbHJz5hSjoDNwMEIaKhuMZ8gQ==
+  dependencies:
+    "@babel/runtime" "^7.10.5"
+    fn-name "~3.0.0"
+    lodash "^4.17.15"
+    lodash-es "^4.17.11"
+    property-expr "^2.0.2"
+    synchronous-promise "^2.0.13"
+    toposort "^2.0.2"
-- 
2.45.2


From 1857785c84200d53aa0233f313867aab09143278 Mon Sep 17 00:00:00 2001
From: Kacper Donat <kadet1090@gmail.com>
Date: Mon, 17 Aug 2020 23:19:56 +0200
Subject: [PATCH 5/5] Add validation based on formik and Yup

---
 src/data/company.ts           |   6 +-
 src/data/edition.ts           |   2 +
 src/data/internship.ts        |   4 +-
 src/forms/company.tsx         | 173 +++++++++++--------
 src/forms/internship.tsx      | 315 +++++++++++++++++++++++-----------
 src/forms/student.tsx         |  11 +-
 src/hooks/index.ts            |   1 +
 src/hooks/useUpdateEffect.ts  |  15 ++
 src/provider/dummy/edition.ts |   3 +-
 src/serialization/types.ts    |   6 +-
 translations/pl.yaml          |   2 +
 11 files changed, 347 insertions(+), 191 deletions(-)
 create mode 100644 src/hooks/useUpdateEffect.ts

diff --git a/src/data/company.ts b/src/data/company.ts
index 5e6f2db..c799e1b 100644
--- a/src/data/company.ts
+++ b/src/data/company.ts
@@ -12,10 +12,10 @@ export interface Company extends Identifiable {
     name: string;
     url?: string;
     nip: string;
-    offices: BranchOffice[];
+    offices: Office[];
 }
 
-export interface BranchOffice extends Identifiable {
+export interface Office extends Identifiable {
     address: Address;
 }
 
@@ -34,7 +34,7 @@ export const emptyAddress: Address = {
     building: ""
 }
 
-export const emptyBranchOffice: BranchOffice = {
+export const emptyBranchOffice: Office = {
     address: emptyAddress,
 }
 
diff --git a/src/data/edition.ts b/src/data/edition.ts
index 7fc5438..0de2052 100644
--- a/src/data/edition.ts
+++ b/src/data/edition.ts
@@ -4,6 +4,8 @@ export type Edition = {
     startDate: Moment;
     endDate: Moment;
     proposalDeadline: Moment;
+    minimumInternshipHours: number;
+    maximumInternshipHours?: number;
 }
 
 export type Deadlines = {
diff --git a/src/data/internship.ts b/src/data/internship.ts
index ace8c57..4cd82ea 100644
--- a/src/data/internship.ts
+++ b/src/data/internship.ts
@@ -1,7 +1,7 @@
 import { Moment } from "moment";
 import { Identifiable } from "./common";
 import { Student } from "@/data/student";
-import { BranchOffice, Company } from "@/data/company";
+import { Company, Office } from "@/data/company";
 
 export enum InternshipType {
     FreeInternship = "FreeInternship",
@@ -64,7 +64,7 @@ export interface Internship extends Identifiable {
     hours: number;
     mentor: Mentor;
     company: Company;
-    office: BranchOffice;
+    office: Office;
 }
 
 export interface Plan extends Identifiable {
diff --git a/src/forms/company.tsx b/src/forms/company.tsx
index 4cfd2b8..74fbfdb 100644
--- a/src/forms/company.tsx
+++ b/src/forms/company.tsx
@@ -1,23 +1,13 @@
 import React, { HTMLProps, useMemo } from "react";
-import { BranchOffice, Company, emptyAddress, emptyBranchOffice, emptyCompany, formatAddress, Mentor } from "@/data";
+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 { BoundProperty, formFieldProps } from "./helpers";
-import { InternshipFormSectionProps } from "@/forms/internship";
-import { emptyMentor } from "@/provider/dummy/internship";
-import { useProxyState } from "@/hooks";
+import { InternshipFormValues } from "@/forms/internship";
 import { useTranslation } from "react-i18next";
-import { Field } from "formik";
+import { Field, useFormikContext } from "formik";
 import { TextField as TextFieldFormik } from "formik-material-ui"
 
-export type CompanyFormProps = {} & InternshipFormSectionProps;
-
-export type BranchOfficeProps = {
-    disabled?: boolean;
-    offices?: BranchOffice[];
-} & BoundProperty<BranchOffice, "onChange", "value">
-
 export const CompanyItem = ({ company, ...props }: { company: Company } & HTMLProps<any>) => (
     <div className="company-item" { ...props }>
         <div>{ company.name }</div>
@@ -25,43 +15,65 @@ export const CompanyItem = ({ company, ...props }: { company: Company } & HTMLPr
     </div>
 )
 
-export const OfficeItem = ({ office, ...props }: { office: BranchOffice } & HTMLProps<any>) => (
+export const OfficeItem = ({ office, ...props }: { office: Office } & HTMLProps<any>) => (
     <div className="office-item" { ...props }>
         <div>{ office.address.city }</div>
         <Typography variant="caption">{ formatAddress(office.address) }</Typography>
     </div>
 )
 
-export const BranchForm: React.FC<BranchOfficeProps> = ({ value: office, onChange: setOffice, offices = [], disabled = false }) => {
-    const canEdit = useMemo(() => !office.id && !disabled, [office.id, disabled]);
-    const fieldProps = formFieldProps(office.address, address => setOffice({ ...office, address }))
+export const BranchForm: React.FC = () => {
+    const { values, errors, setValues, touched, setFieldTouched } = useFormikContext<InternshipFormValues>();
     const { t } = useTranslation();
 
-    const handleCityChange = (event: any, value: BranchOffice | string | null) => {
+    const disabled = useMemo(() => !values.companyName, [values.companyName]);
+    const offices = useMemo(() => values.company?.offices || [], [values.company]);
+    const canEdit = useMemo(() => !values.office && !disabled, [values.office, disabled]);
+
+    const handleCityChange = (event: any, value: Office | string | null) => {
         if (typeof value === "string") {
-            setOffice({
-                ...emptyBranchOffice,
-                address: {
-                    ...emptyAddress,
-                    city: value,
-                }
-            });
+            setValues({
+                ...values,
+                office: null,
+                city: value,
+            }, true);
         } else if (typeof value === "object" && value !== null) {
-            setOffice(value);
-        } else {
-            setOffice(emptyBranchOffice);
+            const office = value as Office;
+
+            setValues({
+                ...values,
+                office,
+                city: office.address.city,
+                country: office.address.country,
+                street: office.address.street,
+                building: office.address.building,
+                postalCode: office.address.postalCode,
+            }, true)
+        } else { // null
+            setValues({
+                ...values,
+                office: null,
+                city: "",
+                country: "",
+                street: "",
+                building: "",
+                postalCode: "",
+            }, true)
         }
     }
 
     const handleCityInput = (event: any, value: string) => {
-        const base = office.id ? emptyBranchOffice : office;
-        setOffice({
-            ...base,
-            address: {
-                ...base.address,
-                city: value,
-            }
-        })
+        setValues( {
+            ...values,
+            office: null,
+            ...(values.office ? {
+                country: "",
+                street: "",
+                building: "",
+                postalCode: "",
+            } : { }),
+            city: value,
+        }, true);
     }
 
     return (
@@ -72,25 +84,34 @@ export const BranchForm: React.FC<BranchOfficeProps> = ({ value: office, onChang
                                   disabled={ disabled }
                                   getOptionLabel={ office => typeof office == "string" ? office : office.address.city }
                                   renderOption={ office => <OfficeItem office={ office }/> }
-                                  renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.city") } fullWidth/> }
+                                  renderInput={
+                                      props =>
+                                          <TextField { ...props }
+                                                     label={ t("forms.internship.fields.city") }
+                                                     fullWidth
+                                                     error={ touched.city && !!errors.city }
+                                                     helperText={ touched.city && errors.city }
+                                          />
+                                  }
                                   onChange={ handleCityChange }
                                   onInputChange={ handleCityInput }
-                                  inputValue={ office.address.city }
-                                  value={ office.id ? office : null }
+                                  onBlur={ ev => setFieldTouched("city", true) }
+                                  inputValue={ values.city }
+                                  value={ values.office ? values.office : null }
                                   freeSolo
                     />
                 </Grid>
                 <Grid item md={ 2 }>
-                    <TextField label={ t("forms.internship.fields.postal-code") } fullWidth disabled={ !canEdit } { ...fieldProps("postalCode") }/>
+                    <Field label={ t("forms.internship.fields.postal-code") } name="postalCode" fullWidth disabled={ !canEdit } component={ TextFieldFormik }/>
                 </Grid>
                 <Grid item md={ 3 }>
-                    <TextField label={ t("forms.internship.fields.country") } fullWidth disabled={ !canEdit } { ...fieldProps("country") }/>
+                    <Field label={ t("forms.internship.fields.country") } name="country" fullWidth disabled={ !canEdit } component={ TextFieldFormik }/>
                 </Grid>
                 <Grid item md={ 10 }>
-                    <TextField label={ t("forms.internship.fields.street") } fullWidth disabled={ !canEdit } { ...fieldProps("street") }/>
+                    <Field label={ t("forms.internship.fields.street") } name="street" fullWidth disabled={ !canEdit } component={ TextFieldFormik }/>
                 </Grid>
                 <Grid item md={ 2 }>
-                    <TextField label={ t("forms.internship.fields.building") } fullWidth disabled={ !canEdit } { ...fieldProps("building") }/>
+                    <Field label={ t("forms.internship.fields.building") } name="building" fullWidth disabled={ !canEdit } component={ TextFieldFormik }/>
                 </Grid>
             </Grid>
         </div>
@@ -102,42 +123,50 @@ export const MentorForm = () => {
 
     return (
         <Grid container>
-            <Grid item md={6}>
-                <Field name="mentorFirstName" label={ t("forms.internship.fields.first-name") } fullWidth component={ TextFieldFormik } />
+            <Grid item md={ 6 }>
+                <Field name="mentorFirstName" label={ t("forms.internship.fields.first-name") } fullWidth component={ TextFieldFormik }/>
             </Grid>
-            <Grid item md={6}>
-                <Field name="mentorLastName" label={ t("forms.internship.fields.last-name") } fullWidth component={ TextFieldFormik } />
+            <Grid item md={ 6 }>
+                <Field name="mentorLastName" label={ t("forms.internship.fields.last-name") } fullWidth component={ TextFieldFormik }/>
             </Grid>
-            <Grid item md={8}>
-                <Field name="mentorEmail" label={ t("forms.internship.fields.e-mail") } fullWidth component={ TextFieldFormik } />
+            <Grid item md={ 8 }>
+                <Field name="mentorEmail" label={ t("forms.internship.fields.e-mail") } fullWidth component={ TextFieldFormik }/>
             </Grid>
-            <Grid item md={4}>
-                <Field name="mentorPhone" label={ t("forms.internship.fields.phone") } fullWidth component={ TextFieldFormik } />
+            <Grid item md={ 4 }>
+                <Field name="mentorPhone" label={ t("forms.internship.fields.phone") } fullWidth component={ TextFieldFormik }/>
             </Grid>
         </Grid>
     );
 }
 
-export const CompanyForm: React.FunctionComponent<CompanyFormProps> = ({ internship, onChange }) => {
-    const [company, setCompany] = useProxyState<Company>(internship.company || emptyCompany, company => onChange({ ...internship, company }));
-    const [mentor, setMentor] = useProxyState<Mentor>(internship.mentor || emptyMentor, mentor => onChange({ ...internship, mentor }));
-    const [office, setOffice] = useProxyState<BranchOffice>(internship.office || emptyBranchOffice, office => onChange({ ...internship, office }));
+export const CompanyForm: React.FunctionComponent = () => {
+    const { values, setValues, errors, touched, setFieldTouched } = useFormikContext<InternshipFormValues>();
     const { t } = useTranslation();
 
-    const canEdit = useMemo(() => !company.id, [company.id]);
-
-    const fieldProps = formFieldProps(company, setCompany)
+    const canEdit = useMemo(() => !values.company, [values.company]);
 
     const handleCompanyChange = (event: any, value: Company | string | null) => {
+        setFieldTouched("companyName", true);
+
         if (typeof value === "string") {
-            setCompany({
-                ...emptyCompany,
-                name: value,
-            });
+            setValues({
+                ...values,
+                company: null,
+                companyName: value
+            }, true)
         } else if (typeof value === "object" && value !== null) {
-            setCompany(value);
+            setValues({
+                ...values,
+                company: value as Company,
+                companyName: value.name,
+                companyNip: value.nip,
+            }, true)
         } else {
-            setCompany(emptyCompany);
+            setValues({
+                ...values,
+                company: null,
+                companyName: "",
+            }, true);
         }
     }
 
@@ -146,24 +175,22 @@ export const CompanyForm: React.FunctionComponent<CompanyFormProps> = ({ interns
             <Grid container>
                 <Grid item>
                     <Autocomplete options={ sampleCompanies }
-                                  getOptionLabel={ option => option.name }
+                                  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/> }
-                                  onChange={ handleCompanyChange } value={ 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
                     />
                 </Grid>
                 <Grid item md={ 4 }>
-                    <TextField label={ t("forms.internship.fields.nip") } fullWidth { ...fieldProps("nip") } disabled={ !canEdit }/>
+                    <Field label={ t("forms.internship.fields.nip") } fullWidth name="companyNip" disabled={ !canEdit } component={ TextFieldFormik }/>
                 </Grid>
-                {/*<Grid item md={ 8 }>*/}
-                {/*    <TextField label={ "Url" } fullWidth { ...fieldProps("url") } disabled={ !canEdit }/>*/}
-                {/*</Grid>*/}
             </Grid>
             <Typography variant="subtitle1" className="subsection-header">{ t("internship.mentor") }</Typography>
-            <MentorForm />
+            <MentorForm/>
             <Typography variant="subtitle1" className="subsection-header">{ t("internship.office") }</Typography>
-            <BranchForm value={ office } onChange={ setOffice } offices={ company.offices } />
+            <BranchForm/>
         </>
     )
 }
diff --git a/src/forms/internship.tsx b/src/forms/internship.tsx
index 7b487eb..7f0b103 100644
--- a/src/forms/internship.tsx
+++ b/src/forms/internship.tsx
@@ -1,15 +1,14 @@
-import React, { HTMLProps, useEffect, useMemo, useState } from "react";
+import React, { HTMLProps, useMemo, useState } from "react";
 import { Button, Dialog, DialogActions, DialogContent, DialogContentText, Grid, TextField, Typography } from "@material-ui/core";
 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 { BranchOffice, Company, Internship, InternshipType, internshipTypeLabels, Student } from "@/data";
+import { Company, Internship, InternshipType, internshipTypeLabels, 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 { formFieldProps } from "@/forms/helpers";
 import { emptyInternship } from "@/provider/dummy/internship";
 import { InternshipProposalActions, useDispatch } from "@/state/actions";
 import { useTranslation } from "react-i18next";
@@ -17,28 +16,26 @@ import { useSelector } from "react-redux";
 import { AppState } from "@/state/reducer";
 import { Link as RouterLink, useHistory } from "react-router-dom";
 import { route } from "@/routing";
-import { useProxyState } from "@/hooks";
 import { getInternshipProposal } from "@/state/reducer/proposal";
 import { Actions } from "@/components";
-import { Form, Formik } from "formik";
+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";
 
-export type InternshipFormProps = {}
-
-export type InternshipFormSectionProps = {
-    internship: Nullable<Internship>,
-    onChange: (internship: Nullable<Internship>) => void,
-}
-
-export type InternshipFormState = {
+export type InternshipFormValues = {
     startDate: Moment | null;
     endDate: Moment | null;
-    hours: number | null;
+    hours: number | "";
+    workingHours: number;
     companyName: string;
     companyNip: string;
     city: string;
     postalCode: string;
     country: string;
+    street: string;
     building: string;
     mentorFirstName: string;
     mentorLastName: string;
@@ -49,19 +46,20 @@ export type InternshipFormState = {
     // relations
     kind: InternshipType | null;
     company: Company | null;
-    office: BranchOffice | null;
-    student: Student | null;
+    office: Office | null;
+    student: Student;
 }
 
-const emptyInternshipValues: InternshipFormState = {
+const emptyInternshipValues: InternshipFormValues = {
     building: "",
     city: "",
     company: null,
     companyName: "",
     companyNip: "",
     country: "",
+    street: "",
     endDate: null,
-    hours: null,
+    hours: "",
     kind: null,
     kindOther: "",
     mentorEmail: "",
@@ -71,7 +69,8 @@ const emptyInternshipValues: InternshipFormState = {
     office: null,
     postalCode: "",
     startDate: null,
-    student: null
+    student: sampleStudent,
+    workingHours: 40,
 }
 
 export const InternshipTypeItem = ({ type, ...props }: { type: InternshipType } & HTMLProps<any>) => {
@@ -85,89 +84,94 @@ export const InternshipTypeItem = ({ type, ...props }: { type: InternshipType }
     )
 }
 
-const InternshipProgramForm = ({ internship, onChange }: InternshipFormSectionProps) => {
-    const fieldProps = formFieldProps(internship, onChange);
-
+const InternshipProgramForm = () => {
     const { t } = useTranslation();
+    const { values, handleBlur, setFieldValue, errors } = useFormikContext<InternshipFormValues>();
 
     return (
         <Grid container>
             <Grid item md={ 4 }>
-                <Autocomplete renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.kind") } fullWidth/> }
+                <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[] }
                               disableClearable
-                              { ...fieldProps("type", (event, value) => value) as any }
+                              value={ values.kind || undefined }
+                              onChange={ (_, value) => setFieldValue("kind", value) }
+                              onBlur={ handleBlur }
                 />
             </Grid>
             <Grid item md={ 8 }>
-                { internship.type === InternshipType.Other && <TextField label={ t("forms.internship.fields.kind") } fullWidth/> }
+                {
+                    values.kind === InternshipType.Other &&
+                    <Field label={ t("forms.internship.fields.kind-other") } name="kindOther" fullWidth component={ TextFieldFormik } />
+                }
             </Grid>
-            {/*<Grid item>*/ }
-            {/*    <FormGroup>*/ }
-            {/*        <FormLabel component="legend" className="subsection-header">Realizowane punkty programu praktyk (minimum 3)</FormLabel>*/ }
-            {/*        { course.possibleProgramEntries.map(entry => {*/ }
-            {/*            return (*/ }
-            {/*                <FormControlLabel label={ entry.description } key={ entry.id }*/ }
-            {/*                                  control={ <Checkbox /> }*/ }
-            {/*                />*/ }
-            {/*            )*/ }
-            {/*        }) }*/ }
-            {/*    </FormGroup>*/ }
-            {/*</Grid>*/ }
         </Grid>
     )
 }
 
-const InternshipDurationForm = ({ internship, onChange }: InternshipFormSectionProps) => {
+const InternshipDurationForm = () => {
     const { t } = useTranslation();
-
-    const [startDate, setStartDate] = useProxyState<Moment | null>(internship.startDate, value => onChange({ ...internship, startDate: value }));
-    const [endDate, setEndDate] = useProxyState<Moment | null>(internship.endDate, value => onChange({ ...internship, endDate: value }));
+    const {
+        values: { startDate, endDate, workingHours },
+        errors,
+        touched,
+        setFieldTouched,
+        setFieldValue
+    } = useFormikContext<InternshipFormValues>();
 
     const [overrideHours, setHoursOverride] = useState<number | null>(null)
-    const [workingHours, setWorkingHours] = useState<number>(40)
 
     const computedHours = useMemo(() => startDate && endDate && computeWorkingHours(startDate, endDate, workingHours / 5), [startDate, endDate, workingHours]);
 
     const hours = useMemo(() => overrideHours !== null ? overrideHours : computedHours || null, [overrideHours, computedHours]);
-    const weeks = useMemo(() => hours !== null ? Math.floor(hours / 40) : null, [hours]);
+    const weeks = useMemo(() => hours !== null ? Math.floor(hours / workingHours) : null, [ hours ]);
 
-    useEffect(() => onChange({ ...internship, hours }), [hours])
+    useUpdateEffect(() => {
+        setFieldTouched("hours", true);
+        setFieldValue("hours", hours, true);
+    }, [ hours ]);
 
     return (
         <Grid container>
             <Grid item md={ 6 }>
-                <DatePicker value={ startDate } onChange={ setStartDate }
+                <DatePicker value={ startDate } onChange={ value => setFieldValue("startDate", value) }
                             format="DD MMMM yyyy"
-                            clearable disableToolbar fullWidth
+                            disableToolbar fullWidth
                             variant="inline" label={ t("forms.internship.fields.start-date") }
                             minDate={ moment() }
                 />
             </Grid>
             <Grid item md={ 6 }>
-                <DatePicker value={ endDate } onChange={ setEndDate }
+                <DatePicker value={ endDate } onChange={ value => setFieldValue("endDate", value) }
                             format="DD MMMM yyyy"
-                            clearable disableToolbar fullWidth
+                            disableToolbar fullWidth
                             variant="inline" label={ t("forms.internship.fields.end-date") }
                             minDate={ startDate || moment() }
                 />
             </Grid>
             <Grid item md={ 4 }>
-                <TextField fullWidth label={ t("forms.internship.fields.working-hours") }
-                           value={ workingHours } onChange={ ev => setWorkingHours(parseInt(ev.target.value) || 0) }
-                           helperText={ t("forms.internship.help.working-hours") }
+                <Field component={ TextFieldFormik }
+                       name="workingHours"
+                       label={ t("forms.internship.fields.working-hours") }
+                       helperText={ t("forms.internship.help.working-hours") }
+                       fullWidth
                 />
             </Grid>
             <Grid item md={ 4 }>
-                <TextField fullWidth label={ t("forms.internship.fields.total-hours") }
-                           value={ hours } onChange={ ev => setHoursOverride(parseInt(ev.target.value) || 0) }
+                <TextField fullWidth
+                           label={ t("forms.internship.fields.total-hours") }
+                           error={ !!errors.hours && touched.hours }
+                           helperText={ touched.hours && errors.hours }
+                           value={ hours || "" }
+                           onChange={ ev => setHoursOverride(parseInt(ev.target.value) || 0) }
                 />
             </Grid>
             <Grid item md={ 4 }>
                 <TextField fullWidth label={ t("forms.internship.fields.weeks") }
-                           value={ weeks } disabled
+                           value={ weeks || "" }
+                           disabled
                            helperText={ t("forms.internship.help.weeks") }
                 />
             </Grid>
@@ -175,36 +179,86 @@ const InternshipDurationForm = ({ internship, onChange }: InternshipFormSectionP
     );
 }
 
-export const InternshipForm: React.FunctionComponent<InternshipFormProps> = props => {
-    const initialInternshipState = useSelector<AppState, Nullable<Internship>>(state => getInternshipProposal(state.proposal) || {
+type InternshipConverterContext = {
+    internship: Internship,
+}
+
+const converter: Transformer<Nullable<Internship>, InternshipFormValues, InternshipConverterContext> = {
+    transform(internship: Nullable<Internship>): InternshipFormValues {
+        return {
+            student: internship.intern as Student,
+            kind: internship.type,
+            kindOther: "",
+            startDate: internship.startDate,
+            endDate: internship.endDate,
+            hours: internship.hours || "",
+            building: internship.office?.address?.building || "",
+            office: internship.office,
+            city: internship.office?.address?.city || "",
+            postalCode: internship.office?.address?.postalCode || "",
+            street: internship.office?.address?.street || "",
+            country: internship.office?.address?.country || "",
+            company: internship.company,
+            companyName: internship.company?.name || "",
+            companyNip: internship.company?.nip || "",
+            mentorEmail: internship.mentor?.email || "",
+            mentorFirstName: internship.mentor?.name || "",
+            mentorLastName: internship.mentor?.surname || "",
+            mentorPhone: internship.mentor?.phone || "",
+            workingHours: 40,
+        }
+    },
+    reverseTransform(form: InternshipFormValues, context: InternshipConverterContext): Nullable<Internship> {
+        return {
+            ...context.internship,
+            startDate: form.startDate as Moment,
+            endDate: form.endDate as Moment,
+            office: form.office || {
+                address: {
+                    street: form.street,
+                    postalCode: form.postalCode,
+                    country: form.country,
+                    city: form.city,
+                    building: form.building,
+                }
+            },
+            mentor: {
+                surname: form.mentorLastName,
+                name: form.mentorFirstName,
+                email: form.mentorEmail,
+                phone: form.mentorPhone,
+            },
+            company: form.company || {
+                name: form.companyName,
+                nip: form.companyNip,
+                offices: [],
+            },
+            hours: form.hours as number,
+            type: form.kind as InternshipType,
+        }
+    }
+}
+
+export const InternshipForm: React.FunctionComponent = () => {
+    const initialInternship = useSelector<AppState, Nullable<Internship>>(state => getInternshipProposal(state.proposal) || {
         ...emptyInternship,
+        office: null,
+        company: null,
+        mentor: null,
         intern: sampleStudent
     });
 
-    const [internship, setInternship] = useState<Nullable<Internship>>(initialInternshipState)
+    const edition = useSelector<AppState, Edition>(state => state.edition as Edition);
+
     const { t } = useTranslation();
 
+
     const dispatch = useDispatch();
     const history = useHistory();
 
     const [confirmDialogOpen, setConfirmDialogOpen] = useState<boolean>(false);
 
-    const handleSubmit = () => {
-        setConfirmDialogOpen(false);
-
-        dispatch({ type: InternshipProposalActions.Send, internship: internship as Internship });
-        history.push(route("home"))
-    }
-
-    const handleSubmitConfirmation = () => {
-        setConfirmDialogOpen(true);
-    }
-
-    const handleCancel = () => {
-        setConfirmDialogOpen(false);
-    }
-
-    const validationSchema = Yup.object<Partial<InternshipFormState>>({
+    const validationSchema = Yup.object<Partial<InternshipFormValues>>({
         mentorFirstName: Yup.string().required(t("validation.required")),
         mentorLastName: Yup.string().required(t("validation.required")),
         mentorEmail: Yup.string()
@@ -214,39 +268,94 @@ export const InternshipForm: React.FunctionComponent<InternshipFormProps> = prop
             .required(t("validation.required"))
             .matches(/^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*$/, t("validation.phone")),
         hours: Yup.number()
-            .min(40, t("validation.internship.minimum-hours")) // todo: take it from edition
+            .min(edition.minimumInternshipHours, t("validation.internship.minimum-hours", { hours: edition.minimumInternshipHours })),
+        companyName: Yup.string().when("company", {
+            is: null,
+            then: Yup.string().required(t("validation.required"))
+        }),
+        companyNip: Yup.string().when("company", {
+            is: null,
+            then: Yup.string()
+                .required(t("validation.required"))
+        }),
+        street: Yup.string().required(t("validation.required")),
+        country: Yup.string().required(t("validation.required")),
+        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"))
+        })
     })
 
+    const values = converter.transform(initialInternship);
+
+    const handleSubmit = (values: InternshipFormValues) => {
+        setConfirmDialogOpen(false);
+
+        dispatch({
+            type: InternshipProposalActions.Send,
+            internship: converter.reverseTransform(values, {
+                internship: initialInternship as Internship,
+            }) as Internship
+        });
+
+        history.push(route("home"))
+    }
+
+    const InnerForm = () => {
+        const { submitForm, validateForm } = useFormikContext();
+
+        const handleSubmitConfirmation = async () => {
+            const errors = await validateForm();
+
+            if (Object.keys(errors).length == 0) {
+                setConfirmDialogOpen(true);
+            }
+        }
+
+        const handleCancel = () => {
+            setConfirmDialogOpen(false);
+        }
+
+        return <Form>
+            <Typography variant="h3" className="section-header">{ t('internship.sections.intern-info') }</Typography>
+            <StudentForm />
+            <Typography variant="h3" className="section-header">{ t('internship.sections.kind' )}</Typography>
+            <InternshipProgramForm />
+            <Typography variant="h3" className="section-header">{ t('internship.sections.duration') }</Typography>
+            <InternshipDurationForm />
+            <Typography variant="h3" className="section-header">{ t('internship.sections.place') }</Typography>
+            <CompanyForm />
+            <Actions>
+                <Button variant="contained" color="primary" onClick={ handleSubmitConfirmation }>{ t("confirm") }</Button>
+
+                <Button component={ RouterLink } to={ route("home") }>
+                    { t('go-back') }
+                </Button>
+            </Actions>
+
+            <Dialog open={ confirmDialogOpen } onClose={ handleCancel }>
+                <DialogContent>
+                    <DialogContentText>{ t('forms.internship.send-confirmation') }</DialogContentText>
+                </DialogContent>
+                <DialogActions>
+                    <Button onClick={ handleCancel }>{ t('cancel') }</Button>
+                    <Button color="primary" autoFocus onClick={ submitForm }>{ t('confirm') }</Button>
+                </DialogActions>
+            </Dialog>
+        </Form>
+    }
+
     return (
-        <Formik initialValues={ emptyInternshipValues } onSubmit={ values => console.log(values) }
-                validationSchema={ validationSchema } validateOnChange={ false } validateOnBlur={ true }>
-            { formik => <Form>
-                <Typography variant="h3" className="section-header">{ t('internship.sections.intern-info') }</Typography>
-                <StudentForm student={ sampleStudent }/>
-                <Typography variant="h3" className="section-header">{ t('internship.sections.kind' )}</Typography>
-                <InternshipProgramForm internship={ internship } onChange={ setInternship }/>
-                <Typography variant="h3" className="section-header">{ t('internship.sections.duration') }</Typography>
-                <InternshipDurationForm internship={ internship } onChange={ setInternship }/>
-                <Typography variant="h3" className="section-header">{ t('internship.sections.place') }</Typography>
-                <CompanyForm internship={ internship } onChange={ setInternship }/>
-                <Actions>
-                    <Button variant="contained" color="primary" onClick={ () => formik.validateForm(formik.values) }>{ t("confirm") }</Button>
-
-                    <Button component={ RouterLink } to={ route("home") }>
-                        { t('go-back') }
-                    </Button>
-                </Actions>
-
-                <Dialog open={ confirmDialogOpen } onClose={ handleCancel }>
-                    <DialogContent>
-                        <DialogContentText>{ t('forms.internship.send-confirmation') }</DialogContentText>
-                    </DialogContent>
-                    <DialogActions>
-                        <Button onClick={ handleCancel }>{ t('cancel') }</Button>
-                        <Button color="primary" autoFocus onClick={ formik.submitForm }>{ t('confirm') }</Button>
-                    </DialogActions>
-                </Dialog>
-            </Form> }
+        <Formik initialValues={ values }
+                onSubmit={ handleSubmit }
+                validationSchema={ validationSchema }
+                validateOnChange={ false }
+                validateOnBlur={ true }
+        >
+            <InnerForm />
         </Formik>
     )
 }
diff --git a/src/forms/student.tsx b/src/forms/student.tsx
index db1ec6e..1c8ee26 100644
--- a/src/forms/student.tsx
+++ b/src/forms/student.tsx
@@ -1,16 +1,15 @@
-import { Course, Student } from "@/data";
+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";
 
-type StudentFormProps = {
-    student: Student
-}
-
-export const StudentForm = ({ student }: StudentFormProps) => {
+export const StudentForm = () => {
     const { t } = useTranslation();
+    const { values: { student } } = useFormikContext<InternshipFormValues>();
 
     return <>
         <Grid container>
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
index 283d0e1..9e7f847 100644
--- a/src/hooks/index.ts
+++ b/src/hooks/index.ts
@@ -1 +1,2 @@
 export * from "./useProxyState"
+export * from "./useUpdateEffect"
diff --git a/src/hooks/useUpdateEffect.ts b/src/hooks/useUpdateEffect.ts
new file mode 100644
index 0000000..939b1c0
--- /dev/null
+++ b/src/hooks/useUpdateEffect.ts
@@ -0,0 +1,15 @@
+import { DependencyList, EffectCallback, useEffect, useRef } from "react";
+
+export const useUpdateEffect = (effect: EffectCallback, dependencies: DependencyList) => {
+    const flag = useRef<boolean>(false);
+
+    useEffect(() => {
+        if (flag.current) {
+            effect();
+        } else {
+            flag.current = true;
+        }
+    }, dependencies)
+}
+
+export default useUpdateEffect;
diff --git a/src/provider/dummy/edition.ts b/src/provider/dummy/edition.ts
index 290a3ac..e53bd4e 100644
--- a/src/provider/dummy/edition.ts
+++ b/src/provider/dummy/edition.ts
@@ -4,5 +4,6 @@ import moment from "moment";
 export const sampleEdition: Edition = {
     startDate: moment("2020-07-01"),
     endDate: moment("2020-09-30"),
-    proposalDeadline: moment("2020-07-31")
+    proposalDeadline: moment("2020-07-31"),
+    minimumInternshipHours: 40,
 }
diff --git a/src/serialization/types.ts b/src/serialization/types.ts
index 94e443d..b7528c6 100644
--- a/src/serialization/types.ts
+++ b/src/serialization/types.ts
@@ -10,9 +10,9 @@ type Simplify<T> = string |
     T extends Object ? Serializable<T> : any;
 
 export type Serializable<T> = { [K in keyof T]: Simplify<T[K]> }
-export type Transformer<TFrom, TResult> = {
-    transform(subject: TFrom): TResult;
-    reverseTransform(subject: TResult): TFrom;
+export type Transformer<TFrom, TResult, TContext = never> = {
+    transform(subject: TFrom, context?: TContext): TResult;
+    reverseTransform(subject: TResult, context?: TContext): TFrom;
 }
 
 export type SerializationTransformer<T, TSerialized = Serializable<T>> = Transformer<T, TSerialized>
diff --git a/translations/pl.yaml b/translations/pl.yaml
index 8fc6115..9bc83eb 100644
--- a/translations/pl.yaml
+++ b/translations/pl.yaml
@@ -163,5 +163,7 @@ validation:
   required: "To pole jest wymagane"
   email: "Wprowadź poprawny adres e-mail"
   phone: "Wprowadź poprawny numer telefonu"
+  internship:
+    minimum-hours: "Minimalna liczba godzin wynosi {{ hours }}"
 
 contact-coordinator: "Skontaktuj się z koordynatorem"
-- 
2.45.2