Add internship registration submission

This commit is contained in:
Kacper Donat 2020-10-04 14:18:25 +02:00
parent 411603e3a1
commit 8b2523572d
20 changed files with 309 additions and 104 deletions

View File

@ -36,6 +36,7 @@
"html-webpack-plugin": "4.0.0-beta.11", "html-webpack-plugin": "4.0.0-beta.11",
"i18next": "^19.6.0", "i18next": "^19.6.0",
"i18next-browser-languagedetector": "^5.0.0", "i18next-browser-languagedetector": "^5.0.0",
"jsonwebtoken": "^8.5.1",
"material-ui-dropzone": "^3.3.0", "material-ui-dropzone": "^3.3.0",
"mdi-material-ui": "^6.17.0", "mdi-material-ui": "^6.17.0",
"moment": "^2.26.0", "moment": "^2.26.0",

18
src/api/companies.ts Normal file
View File

@ -0,0 +1,18 @@
import { Company, Office } from "@/data";
import { axios } from "@/api/index";
import { prepare, query } from "@/routing";
export const COMPANY_SEARCH_ENDPOINT = '/companies';
export const COMPANY_OFFICES_ENDPOINT = '/companies/:id'
export async function search(name: string): Promise<Company[]> {
const companies = await axios.get<Company[]>(query(COMPANY_SEARCH_ENDPOINT, { Name: name }));
return companies.data;
}
export async function offices(id: string): Promise<Office[]> {
const response = await axios.get<Office[]>(prepare(COMPANY_OFFICES_ENDPOINT, { id }));
return response.data;
}

View File

@ -0,0 +1,33 @@
import { Identifiable, Internship, Mentor } from "@/data";
import { OneWayTransformer } from "@/serialization";
import { Nullable } from "@/helpers";
export interface InternshipRegistrationUpdateCompany {
id: string,
branchOffice: Identifiable,
}
export interface InternshipRegistrationUpdate {
company: InternshipRegistrationUpdateCompany,
start: string,
end: string,
type: number,
mentor: Mentor,
}
export const internshipRegistrationUpdateTransformer: OneWayTransformer<Nullable<Internship>, Nullable<InternshipRegistrationUpdate>> = {
transform(subject: Nullable<Internship>, context?: unknown): Nullable<InternshipRegistrationUpdate> {
return {
start: subject?.startDate?.toISOString() || null,
end: subject?.endDate?.toISOString() || null,
type: parseInt(subject?.type?.id || "0"),
mentor: subject.mentor,
company: {
id: subject?.company?.id as string,
branchOffice: {
id: subject?.office?.id
},
}
}
}
}

34
src/api/dto/type.ts Normal file
View File

@ -0,0 +1,34 @@
import { Identifiable, InternshipType } from "@/data";
import { Transformer } from "@/serialization";
export interface InternshipTypeDTO extends Identifiable {
label: string;
labelEng: string;
description?: string;
descriptionEng?: string;
}
export const internshipTypeDtoTransformer: Transformer<InternshipTypeDTO, InternshipType> = {
transform(subject: InternshipTypeDTO, context?: unknown): InternshipType {
return {
id: subject.id,
label: {
pl: subject.label,
en: subject.labelEng
},
description: subject.description ? {
pl: subject.description,
en: subject.descriptionEng || ""
} : undefined
}
},
reverseTransform(subject: InternshipType, context?: unknown): InternshipTypeDTO {
return {
id: subject.id,
label: subject.label.pl,
labelEng: subject.label.en,
description: subject.description?.pl || undefined,
descriptionEng: subject.description?.en || undefined,
}
},
}

View File

@ -5,7 +5,9 @@ import { EditionDTO, editionDtoTransformer, editionTeaserDtoTransformer } from "
const EDITIONS_ENDPOINT = "/editions"; const EDITIONS_ENDPOINT = "/editions";
const EDITION_INFO_ENDPOINT = "/editions/:key"; const EDITION_INFO_ENDPOINT = "/editions/:key";
const REGISTER_ENDPOINT = "/register"; const EDITION_CURRENT_ENDPOINT = "/editions/current";
const EDITION_REGISTER_ENDPOINT = "/register";
const EDITION_LOGIN_ENDPOINT = "/access/loginEdition";
export async function available() { export async function available() {
const response = await axios.get(EDITIONS_ENDPOINT); const response = await axios.get(EDITIONS_ENDPOINT);
@ -15,7 +17,7 @@ export async function available() {
export async function join(key: string): Promise<boolean> { export async function join(key: string): Promise<boolean> {
try { try {
await axios.post(REGISTER_ENDPOINT, JSON.stringify(key), { headers: { "Content-Type": "application/json" } }); await axios.post(EDITION_REGISTER_ENDPOINT, JSON.stringify(key), { headers: { "Content-Type": "application/json" } });
return true; return true;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -29,3 +31,16 @@ export async function get(key: string): Promise<Edition | null> {
return editionDtoTransformer.transform(dto); return editionDtoTransformer.transform(dto);
} }
export async function current(): Promise<Edition> {
const response = await axios.get<EditionDTO>(EDITION_CURRENT_ENDPOINT);
const dto = response.data;
return editionDtoTransformer.transform(dto);
}
export async function login(key: string): Promise<string> {
const response = await axios.post<string>(EDITION_LOGIN_ENDPOINT, JSON.stringify(key), { headers: { "Content-Type": "application/json" } })
return response.data;
}

View File

@ -5,8 +5,11 @@ import { UserState } from "@/state/reducer/user";
import * as user from "./user"; import * as user from "./user";
import * as edition from "./edition"; import * as edition from "./edition";
import * as page from "./page" import * as page from "./page";
import * as student from "./student" import * as student from "./student";
import * as type from "./type";
import * as companies from "./companies";
import * as internship from "./internship";
export const axios = Axios.create({ export const axios = Axios.create({
baseURL: process.env.API_BASE_URL || "https://system-praktyk.stg.kadet.net/api/", baseURL: process.env.API_BASE_URL || "https://system-praktyk.stg.kadet.net/api/",
@ -33,7 +36,10 @@ const api = {
user, user,
edition, edition,
page, page,
student student,
type,
companies,
internship,
} }
export default api; export default api;

11
src/api/internship.ts Normal file
View File

@ -0,0 +1,11 @@
import { InternshipRegistrationUpdate } from "@/api/dto/internship-registration";
import { axios } from "@/api/index";
import { Nullable } from "@/helpers";
const INTERNSHIP_ENDPOINT = '/internshipRegistration';
export async function update(internship: Nullable<InternshipRegistrationUpdate>): Promise<boolean> {
const response = await axios.put(INTERNSHIP_ENDPOINT, internship);
return true;
}

12
src/api/type.ts Normal file
View File

@ -0,0 +1,12 @@
import { InternshipType } from "@/data";
import { axios } from "@/api/index";
import { InternshipTypeDTO, internshipTypeDtoTransformer } from "@/api/dto/type";
const AVAILABLE_INTERNSHIP_TYPES = '/internshipTypes';
export async function available(): Promise<InternshipType[]> {
const response = await axios.get<InternshipTypeDTO[]>(AVAILABLE_INTERNSHIP_TYPES);
const dtos = response.data;
return dtos.map(dto => internshipTypeDtoTransformer.transform(dto));
}

View File

@ -9,7 +9,7 @@ const AUTHORIZE_URL = process.env.AUTHORIZE || "https://logowanie.pg.edu.pl/oaut
export async function login(code?: string): Promise<string> { export async function login(code?: string): Promise<string> {
const response = code const response = code
? await axios.get<string>(LOGIN_ENDPOINT, { params: { code }}) ? await axios.post<string>(LOGIN_ENDPOINT, JSON.stringify(code), { headers: { 'Content-Type': 'application/json' } })
: await axios.get<string>(DEV_LOGIN_ENDPOINT); : await axios.get<string>(DEV_LOGIN_ENDPOINT);
return response.data; return response.data;

View File

@ -1,4 +1,4 @@
import { Internship, internshipTypeLabels } from "@/data"; import { Internship } from "@/data";
import React from "react"; import React from "react";
import { Typography } from "@material-ui/core"; import { Typography } from "@material-ui/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -39,7 +39,7 @@ export const ProposalPreview = ({ proposal }: ProposalPreviewProps) => {
<Section> <Section>
<Label>{ t('internship.sections.kind') }</Label> <Label>{ t('internship.sections.kind') }</Label>
<Typography className="proposal__primary">{ internshipTypeLabels[proposal.type].label }</Typography> <Typography className="proposal__primary">{ proposal.type.label.pl }</Typography>
</Section> </Section>
<Section> <Section>

View File

@ -1,52 +1,11 @@
import { Moment } from "moment"; import { Moment } from "moment";
import { Identifiable } from "./common"; import { Identifiable, Multilingual } from "./common";
import { Student } from "@/data/student"; import { Student } from "@/data/student";
import { Company, Office } from "@/data/company"; import { Company, Office } from "@/data/company";
export enum InternshipType { export interface InternshipType extends Identifiable {
FreeInternship = "FreeInternship", label: Multilingual<string>,
GraduateInternship = "GraduateInternship", description?: Multilingual<string>,
FreeApprenticeship = "FreeApprenticeship",
PaidApprenticeship = "PaidApprenticeship",
ForeignInternship = "ForeignInternship",
UOP = "UOP",
UD = "UD",
UZ = "UZ",
Other = "Other",
}
export const internshipTypeLabels: { [type in InternshipType]: { label: string, description?: string } } = {
[InternshipType.FreeInternship]: {
label: "Umowa o organizację praktyki",
description: "Praktyka bezpłatna"
},
[InternshipType.GraduateInternship]: {
label: "Umowa o praktykę absolwencką"
},
[InternshipType.FreeApprenticeship]: {
label: "Umowa o staż bezpłatny"
},
[InternshipType.PaidApprenticeship]: {
label: "Umowa o staż płatny",
description: "np. przemysłowy"
},
[InternshipType.ForeignInternship]: {
label: "Praktyka zagraniczna",
description: "np. IAESTE, ERASMUS"
},
[InternshipType.UOP]: {
label: "Umowa o pracę"
},
[InternshipType.UD]: {
label: "Umowa o dzieło (w tym B2B)"
},
[InternshipType.UZ]: {
label: "Umowa o zlecenie (w tym B2B)"
},
[InternshipType.Other]: {
label: "Inna",
description: "Należy wprowadzić samodzielnie"
},
} }
export interface InternshipProgramEntry extends Identifiable { export interface InternshipProgramEntry extends Identifiable {

View File

@ -1,12 +1,12 @@
import React, { HTMLProps, useMemo } from "react"; import React, { HTMLProps, useEffect, useMemo, useState } from "react";
import { Company, formatAddress, Office } from "@/data"; import { Company, formatAddress, Office } from "@/data";
import { sampleCompanies } from "@/provider/dummy";
import { Autocomplete } from "@material-ui/lab"; import { Autocomplete } from "@material-ui/lab";
import { Grid, TextField, Typography } from "@material-ui/core"; import { Grid, TextField, Typography } from "@material-ui/core";
import { InternshipFormValues } from "@/forms/internship"; import { InternshipFormValues } from "@/forms/internship";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Field, useFormikContext } from "formik"; import { Field, useFormikContext } from "formik";
import { TextField as TextFieldFormik } from "formik-material-ui" import { TextField as TextFieldFormik } from "formik-material-ui"
import api from "@/api";
export const CompanyItem = ({ company, ...props }: { company: Company } & HTMLProps<any>) => ( export const CompanyItem = ({ company, ...props }: { company: Company } & HTMLProps<any>) => (
<div className="company-item" { ...props }> <div className="company-item" { ...props }>
@ -27,9 +27,15 @@ export const BranchForm: React.FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const disabled = useMemo(() => !values.companyName, [values.companyName]); const disabled = useMemo(() => !values.companyName, [values.companyName]);
const offices = useMemo(() => values.company?.offices || [], [values.company]); const [offices, setOffices] = useState<Office[]>([]);
const canEdit = useMemo(() => !values.office && !disabled, [values.office, disabled]); const canEdit = useMemo(() => !values.office && !disabled, [values.office, disabled]);
useEffect(() => {
(async () => {
setOffices(values.company?.id ? (await api.companies.offices(values.company?.id)) : []);
})()
}, [ values.company?.id ])
const handleCityChange = (event: any, value: Office | string | null) => { const handleCityChange = (event: any, value: Office | string | null) => {
if (typeof value === "string") { if (typeof value === "string") {
setValues({ setValues({
@ -143,8 +149,17 @@ export const CompanyForm: React.FunctionComponent = () => {
const { values, setValues, errors, touched, setFieldTouched } = useFormikContext<InternshipFormValues>(); const { values, setValues, errors, touched, setFieldTouched } = useFormikContext<InternshipFormValues>();
const { t } = useTranslation(); const { t } = useTranslation();
const [input, setInput] = useState<string>("");
const [companies, setCompanies] = useState<Company[]>([]);
const canEdit = useMemo(() => !values.company, [values.company]); const canEdit = useMemo(() => !values.company, [values.company]);
useEffect(() => {
(async () => {
setCompanies(await api.companies.search(input));
})()
}, [ input ]);
const handleCompanyChange = (event: any, value: Company | string | null) => { const handleCompanyChange = (event: any, value: Company | string | null) => {
setFieldTouched("companyName", true); setFieldTouched("companyName", true);
@ -174,13 +189,14 @@ export const CompanyForm: React.FunctionComponent = () => {
<> <>
<Grid container> <Grid container>
<Grid item> <Grid item>
<Autocomplete options={ sampleCompanies } <Autocomplete options={ companies }
getOptionLabel={ option => typeof option === "string" ? option : option.name } getOptionLabel={ option => typeof option === "string" ? option : option.name }
renderOption={ company => <CompanyItem company={ company }/> } renderOption={ company => <CompanyItem company={ company }/> }
renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.company-name") } fullWidth renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.company-name") } fullWidth
error={ touched.companyName && !!errors.companyName } helperText={ touched.companyName && errors.companyName }/> } error={ touched.companyName && !!errors.companyName } helperText={ touched.companyName && errors.companyName }/> }
onChange={ handleCompanyChange } value={ values.company || values.companyName } onChange={ handleCompanyChange } value={ values.company || values.companyName }
freeSolo freeSolo
onInputChange={ (_, value) => setInput(value) }
/> />
</Grid> </Grid>
<Grid item md={ 4 }> <Grid item md={ 4 }>

View File

@ -4,13 +4,13 @@ import { KeyboardDatePicker as DatePicker } from "@material-ui/pickers";
import { CompanyForm } from "@/forms/company"; import { CompanyForm } from "@/forms/company";
import { StudentForm } from "@/forms/student"; import { StudentForm } from "@/forms/student";
import { sampleStudent } from "@/provider/dummy/student"; import { sampleStudent } from "@/provider/dummy/student";
import { Company, Internship, InternshipType, internshipTypeLabels, Office, Student } from "@/data"; import { Company, Internship, InternshipType, Office, Student } from "@/data";
import { Nullable } from "@/helpers"; import { Nullable } from "@/helpers";
import moment, { Moment } from "moment"; import moment, { Moment } from "moment";
import { computeWorkingHours } from "@/utils/date"; import { computeWorkingHours } from "@/utils/date";
import { Autocomplete } from "@material-ui/lab"; import { Autocomplete } from "@material-ui/lab";
import { emptyInternship } from "@/provider/dummy/internship"; import { emptyInternship } from "@/provider/dummy/internship";
import { InternshipProposalActions, useDispatch } from "@/state/actions"; import { useDispatch } from "@/state/actions";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { AppState } from "@/state/reducer"; import { AppState } from "@/state/reducer";
@ -22,8 +22,9 @@ import { Field, Form, Formik, useFormikContext } from "formik";
import * as Yup from "yup"; import * as Yup from "yup";
import { Transformer } from "@/serialization"; import { Transformer } from "@/serialization";
import { TextField as TextFieldFormik } from "formik-material-ui" import { TextField as TextFieldFormik } from "formik-material-ui"
import { Edition } from "@/data/edition"; import { useCurrentEdition, useCurrentStudent, useInternshipTypes, useUpdateEffect } from "@/hooks";
import { useUpdateEffect } from "@/hooks"; import { internshipRegistrationUpdateTransformer } from "@/api/dto/internship-registration";
import api from "@/api";
export type InternshipFormValues = { export type InternshipFormValues = {
startDate: Moment | null; startDate: Moment | null;
@ -73,13 +74,11 @@ const emptyInternshipValues: InternshipFormValues = {
workingHours: 40, workingHours: 40,
} }
export const InternshipTypeItem = ({ type, ...props }: { type: InternshipType } & HTMLProps<any>) => { export const InternshipTypeItem = ({ internshipType: type, ...props }: { internshipType: InternshipType } & HTMLProps<any>) => {
const info = internshipTypeLabels[type];
return ( return (
<div className="internship=type-item" { ...props }> <div className="internship=type-item" { ...props }>
<div>{ info.label }</div> <div>{ type.label.pl }</div>
{ info.description && <Typography variant="caption">{ info.description }</Typography> } { type.description && <Typography variant="caption">{ type.description.pl }</Typography> }
</div> </div>
) )
} }
@ -88,25 +87,27 @@ const InternshipProgramForm = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { values, handleBlur, setFieldValue, errors } = useFormikContext<InternshipFormValues>(); const { values, handleBlur, setFieldValue, errors } = useFormikContext<InternshipFormValues>();
const types = useInternshipTypes();
return ( return (
<Grid container> <Grid container>
<Grid item md={ 4 }> <Grid item md={ 4 }>
<Autocomplete renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.kind") } fullWidth error={ !!errors.kind } helperText={ errors.kind }/> } <Autocomplete renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.kind") } fullWidth error={ !!errors.kind } helperText={ errors.kind }/> }
getOptionLabel={ (option: InternshipType) => internshipTypeLabels[option].label } getOptionLabel={ (option: InternshipType) => option.label.pl }
renderOption={ (option: InternshipType) => <InternshipTypeItem type={ option }/> } renderOption={ (option: InternshipType) => <InternshipTypeItem internshipType={ option }/> }
options={ Object.values(InternshipType) as InternshipType[] } options={ types }
disableClearable disableClearable
value={ values.kind || undefined } value={ values.kind || undefined }
onChange={ (_, value) => setFieldValue("kind", value) } onChange={ (_, value) => setFieldValue("kind", value) }
onBlur={ handleBlur } onBlur={ handleBlur }
/> />
</Grid> </Grid>
<Grid item md={ 8 }> {/*<Grid item md={ 8 }>*/}
{ {/* {*/}
values.kind === InternshipType.Other && {/* values.kind === InternshipType.Other &&*/}
<Field label={ t("forms.internship.fields.kind-other") } name="kindOther" fullWidth component={ TextFieldFormik } /> {/* <Field label={ t("forms.internship.fields.kind-other") } name="kindOther" fullWidth component={ TextFieldFormik } />*/}
} {/* }*/}
</Grid> {/*</Grid>*/}
</Grid> </Grid>
) )
} }
@ -240,19 +241,20 @@ const converter: Transformer<Nullable<Internship>, InternshipFormValues, Interns
} }
export const InternshipForm: React.FunctionComponent = () => { export const InternshipForm: React.FunctionComponent = () => {
const student = useCurrentStudent();
const initialInternship = useSelector<AppState, Nullable<Internship>>(state => getInternshipProposal(state.proposal) || { const initialInternship = useSelector<AppState, Nullable<Internship>>(state => getInternshipProposal(state.proposal) || {
...emptyInternship, ...emptyInternship,
office: null, office: null,
company: null, company: null,
mentor: null, mentor: null,
intern: sampleStudent intern: student
}); });
const edition = useSelector<AppState, Edition>(state => state.edition as Edition); const edition = useCurrentEdition();
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useDispatch(); const dispatch = useDispatch();
const history = useHistory(); const history = useHistory();
@ -268,7 +270,7 @@ export const InternshipForm: React.FunctionComponent = () => {
.required(t("validation.required")) .required(t("validation.required"))
.matches(/^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*$/, t("validation.phone")), .matches(/^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*$/, t("validation.phone")),
hours: Yup.number() hours: Yup.number()
.min(edition.minimumInternshipHours, t("validation.internship.minimum-hours", { hours: edition.minimumInternshipHours })), .min(edition?.minimumInternshipHours || 0, t("validation.internship.minimum-hours", { hours: edition?.minimumInternshipHours || 0 })),
companyName: Yup.string().when("company", { companyName: Yup.string().when("company", {
is: null, is: null,
then: Yup.string().required(t("validation.required")) then: Yup.string().required(t("validation.required"))
@ -283,10 +285,10 @@ export const InternshipForm: React.FunctionComponent = () => {
city: Yup.string().required(t("validation.required")), city: Yup.string().required(t("validation.required")),
postalCode: Yup.string().required(t("validation.required")), postalCode: Yup.string().required(t("validation.required")),
building: Yup.string().required(t("validation.required")), building: Yup.string().required(t("validation.required")),
kindOther: Yup.string().when("kind", { // kindOther: Yup.string().when("kind", {
is: (values: InternshipFormValues) => values?.kind === InternshipType.Other, // is: (values: InternshipFormValues) => values?.kind === InternshipType.Other,
then: Yup.string().required(t("validation.required")) // then: Yup.string().required(t("validation.required"))
}) // })
}) })
const values = converter.transform(initialInternship); const values = converter.transform(initialInternship);
@ -294,14 +296,12 @@ export const InternshipForm: React.FunctionComponent = () => {
const handleSubmit = (values: InternshipFormValues) => { const handleSubmit = (values: InternshipFormValues) => {
setConfirmDialogOpen(false); setConfirmDialogOpen(false);
dispatch({ const internship = converter.reverseTransform(values, { internship: initialInternship as Internship });
type: InternshipProposalActions.Send, const update = internshipRegistrationUpdateTransformer.transform(internship);
internship: converter.reverseTransform(values, {
internship: initialInternship as Internship,
}) as Internship
});
history.push(route("home")) api.internship.update(update);
// history.push(route("home"))
} }
const InnerForm = () => { const InnerForm = () => {

View File

@ -2,14 +2,15 @@ import { Course } from "@/data";
import { Button, Grid, TextField } from "@material-ui/core"; import { Button, Grid, TextField } from "@material-ui/core";
import { Alert, Autocomplete } from "@material-ui/lab"; import { Alert, Autocomplete } from "@material-ui/lab";
import React from "react"; import React from "react";
import { sampleCourse } from "@/provider/dummy/student";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useFormikContext } from "formik"; import { useFormikContext } from "formik";
import { InternshipFormValues } from "@/forms/internship"; import { InternshipFormValues } from "@/forms/internship";
import { useCurrentEdition } from "@/hooks";
export const StudentForm = () => { export const StudentForm = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { values: { student } } = useFormikContext<InternshipFormValues>(); const { values: { student } } = useFormikContext<InternshipFormValues>();
const course = useCurrentEdition()?.course as Course;
return <> return <>
<Grid container> <Grid container>
@ -26,8 +27,8 @@ export const StudentForm = () => {
<Autocomplete <Autocomplete
getOptionLabel={ (course: Course) => course.name } getOptionLabel={ (course: Course) => course.name }
renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.course") } fullWidth/> } renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.course") } fullWidth/> }
options={[ sampleCourse ]} options={[ course ]}
value={ student.course } value={ course }
disabled disabled
/> />
</Grid> </Grid>

View File

@ -2,3 +2,4 @@ export * from "./useProxyState"
export * from "./useUpdateEffect" export * from "./useUpdateEffect"
export * from "./useAsync" export * from "./useAsync"
export * from "./state" export * from "./state"
export * from "./providers"

15
src/hooks/providers.ts Normal file
View File

@ -0,0 +1,15 @@
import { useEffect, useState } from "react";
import api from "@/api";
import { InternshipType } from "@/data";
export const useInternshipTypes = () => {
const [types, setTypes] = useState<InternshipType[]>([]);
useEffect(() => {
(async () => {
setTypes(await api.type.available());
})()
}, [])
return types;
}

View File

@ -11,7 +11,7 @@ import api from "@/api";
import { Section } from "@/components/section"; import { Section } from "@/components/section";
import { useVerticalSpacing } from "@/styles"; import { useVerticalSpacing } from "@/styles";
import { Alert } from "@material-ui/lab"; import { Alert } from "@material-ui/lab";
import { EditionActions, useDispatch } from "@/state/actions"; import { EditionActions, useDispatch, UserActions } from "@/state/actions";
export const PickEditionPage = () => { export const PickEditionPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -23,12 +23,19 @@ export const PickEditionPage = () => {
const classes = useVerticalSpacing(3); const classes = useVerticalSpacing(3);
const pickEditionHandler = (id: string) => async () => { const pickEditionHandler = (id: string) => async () => {
const edition = await api.edition.get(id); const token = await api.edition.login(id);
if (!edition) { if (!token) {
return; return;
} }
await dispatch({
type: UserActions.Login,
token,
})
const edition = await api.edition.current();
dispatch({ dispatch({
type: EditionActions.Set, type: EditionActions.Set,
edition edition

View File

@ -67,7 +67,10 @@ export function route(name: string, params: URLParams = {}) {
} }
export const query = (url: string, params: URLParams) => { export const query = (url: string, params: URLParams) => {
const query = Object.entries(params).map(([name, value]) => `${ name }=${ encodeURIComponent(value) }`).join("&"); const query = Object.entries(params)
.filter(([_, value]) => !!value)
.map(([name, value]) => `${ name }=${ encodeURIComponent(value) }`)
.join("&");
return url + (query.length > 0 ? `?${ query }` : ''); return url + (query.length > 0 ? `?${ query }` : '');
} }

View File

@ -1,7 +1,6 @@
import { Reducer } from "react"; import { Reducer } from "react";
import { InsuranceAction, InsuranceActions } from "@/state/actions/insurance"; import { InsuranceAction, InsuranceActions } from "@/state/actions/insurance";
import { InternshipProposalAction, InternshipProposalActions } from "@/state/actions"; import { InternshipProposalAction, InternshipProposalActions } from "@/state/actions";
import { InternshipType } from "@/data";
export type InsuranceState = { export type InsuranceState = {
required: boolean; required: boolean;
@ -21,12 +20,6 @@ export const insuranceReducer: Reducer<InsuranceState, InsuranceAction | Interns
case InternshipProposalActions.Send: case InternshipProposalActions.Send:
return { return {
...state, ...state,
required: [
InternshipType.FreeApprenticeship,
InternshipType.FreeInternship,
InternshipType.PaidApprenticeship,
InternshipType.GraduateInternship,
].includes(action.internship.type)
} }
case InsuranceActions.Signed: case InsuranceActions.Signed:

View File

@ -2258,6 +2258,11 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.6.2, browserslist@^4.
node-releases "^1.1.53" node-releases "^1.1.53"
pkg-up "^2.0.0" pkg-up "^2.0.0"
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
buffer-from@^1.0.0: buffer-from@^1.0.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
@ -3433,6 +3438,13 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0" jsbn "~0.1.0"
safer-buffer "^2.1.0" safer-buffer "^2.1.0"
ecdsa-sig-formatter@1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
ee-first@1.1.1: ee-first@1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@ -5219,6 +5231,22 @@ jsonify@~0.0.0:
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
jsonwebtoken@^8.5.1:
version "8.5.1"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
dependencies:
jws "^3.2.2"
lodash.includes "^4.3.0"
lodash.isboolean "^3.0.3"
lodash.isinteger "^4.0.4"
lodash.isnumber "^3.0.3"
lodash.isplainobject "^4.0.6"
lodash.isstring "^4.0.1"
lodash.once "^4.0.0"
ms "^2.1.1"
semver "^5.6.0"
jsprim@^1.2.2: jsprim@^1.2.2:
version "1.4.1" version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@ -5299,6 +5327,23 @@ jss@^10.0.3, jss@^10.3.0:
is-in-browser "^1.1.3" is-in-browser "^1.1.3"
tiny-warning "^1.0.2" tiny-warning "^1.0.2"
jwa@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
dependencies:
jwa "^1.4.1"
safe-buffer "^5.0.1"
killable@^1.0.1: killable@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@ -5432,6 +5477,36 @@ lodash.get@^4.4.2:
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
lodash.isboolean@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
lodash.isnumber@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
lodash.isstring@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
lodash.memoize@^4.1.2: lodash.memoize@^4.1.2:
version "4.1.2" version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@ -5447,6 +5522,11 @@ lodash.omit@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60"
integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA= integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=
lodash.once@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
lodash.pick@^4.4.0: lodash.pick@^4.4.0:
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"