diff --git a/src/@types/recruitment-form.d.ts b/src/@types/recruitment-form.d.ts new file mode 100644 index 0000000000..5b12add0ab --- /dev/null +++ b/src/@types/recruitment-form.d.ts @@ -0,0 +1,16 @@ +declare module 'recruitment-form' { + export interface RecruitmentForm { + postingId: number; + firstName: string; + lastName: string; + emailAddress: string; + term: string; + termType: string; + program: string; + inPerson: boolean; + whyJoin: string; + technicalQ: string; + additionalInfo: string; + resumeLink: string; + } +} diff --git a/src/api/application.ts b/src/api/application.ts new file mode 100644 index 0000000000..96a408748c --- /dev/null +++ b/src/api/application.ts @@ -0,0 +1,21 @@ +import { Server } from "./server" +import { AxiosResponse } from 'axios'; + +const upload = (server:Server) => +(formData:FormData):Promise> => +server.post('/api/upload/resume', + formData, { + headers: { + 'Content-Type':'multipart/form-data' + } + } +) + +const apply = (server: Server) => +(body: Object):Promise> => + server.post('/api/applications', body) + +export default (server:Server) => ({ + upload: upload(server), + apply: apply(server) +}) \ No newline at end of file diff --git a/src/api/index.ts b/src/api/index.ts index 790626e82d..304cfb2858 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,12 +1,14 @@ import newsletter from './newsletter'; import postings from './postings'; -import server from './server'; +import server, { serverDashboard } from './server'; import sponsors from './sponsors'; import teams from './teams'; +import application from './application' export default { postings: postings(server), teams: teams(server), sponsors: sponsors(server), - newsletter: newsletter(server) + newsletter: newsletter(server), + application: application(serverDashboard) }; diff --git a/src/api/postings.ts b/src/api/postings.ts index 5731135a92..737b30440b 100644 --- a/src/api/postings.ts +++ b/src/api/postings.ts @@ -4,7 +4,6 @@ import { Server } from 'server'; const getPostings = (server: Server) => (joinTeamName: boolean = false): Promise> | Promise> => server.get(`/api/postings${joinTeamName ? '?joinTeamName=true' : ''}`); const getPostingById = (server: Server) => (id: number, joinTeamName: boolean = false): Promise> => server.get(`/api/postings/${id}${joinTeamName ? '?joinTeamName=true' : ''}`).then((res: AxiosResponse) => { - console.log(res); return res; }); diff --git a/src/api/recruitment-form.ts b/src/api/recruitment-form.ts new file mode 100644 index 0000000000..5383895299 --- /dev/null +++ b/src/api/recruitment-form.ts @@ -0,0 +1,10 @@ +import { Server } from 'server'; +import { AxiosResponse } from 'axios'; +import { RecruitmentForm } from 'recruitment-form'; + +const getRecruitmentForm = (server: Server) => (): Promise> => server.get(`/api/recruitment`); + + +export default (server: Server) => ({ + getRecruitmentForm: getRecruitmentForm(server), +}); diff --git a/src/api/server.ts b/src/api/server.ts index 0c3b4274cd..bff9a9cb77 100644 --- a/src/api/server.ts +++ b/src/api/server.ts @@ -6,6 +6,8 @@ const devHostName = window.location.hostname; // Retrieves the current device's const baseUrl = process.env.NODE_ENV === 'development' ? `http://${devHostName}:9000` : process.env.REACT_APP_BASE_URL +const baseUrlDashboard = process.env.NODE_ENV === 'production' ? '' : 'http://localhost:9001' + export const server = axios.create({ baseURL: baseUrl, timeout: 10000, @@ -15,5 +17,11 @@ export const server = axios.create({ }, }); -export default server; +export const serverDashboard = axios.create({ + baseURL: baseUrlDashboard, + timeout:10000, + withCredentials:false, +}) + +export default server export type Server = typeof server; diff --git a/src/components/DropDownList/DropDownList.tsx b/src/components/DropDownList/DropDownList.tsx new file mode 100644 index 0000000000..47aa2c8d9d --- /dev/null +++ b/src/components/DropDownList/DropDownList.tsx @@ -0,0 +1,137 @@ +import React, { useMemo, useState, useCallback, FC } from 'react'; +import styled, { css } from 'styled-components'; + +import ListOpenSvg from './assets/list-open.svg'; +import ListClosedSvg from './assets/list-closed.svg'; +import UnstyledListItem from './ListItem'; + +interface DropDownListProps { + className?: string; + items: string[]; + title: string; + handleClickItem: (value: string) => void; + value: string; + valid: boolean | null; + required: boolean; +} + +const ListItem = styled(UnstyledListItem)``; + +const ListOpenIcon = styled.img.attrs({ + src: ListOpenSvg, +})``; + +const ListClosedIcon = styled.img.attrs({ + src: ListClosedSvg, +})``; + +const Container = styled(({ valid, required, ...props }) =>
)` + overflow: visible; + z-index: 10; + ${'' /** Hides the top of the list under the List name container */} + background-color: #fff; + width: 100%; + border: 1px solid #c4c4c4; + border-radius: 5px; + border: 1px solid + ${({ valid, required }): string => + valid === false && required === true ? 'red' : '#c4c4c4'}; +`; + +const ListNameContainer = styled.div` + border-radius: 10px; + background-color: #fff; + + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + + cursor: pointer; +`; + +const TitleText = styled.div` + font: 14px IBM Plex Sans; + color: #00000080; +`; + +// Good practice to not pass props like "open" to a root element +const List = styled(({ open, ...props }) =>
    )` + border-radius: 5px; + + margin-top: -20px; + padding-top: 8px; + position: relative; + width: calc(100% - 10px); + margin-left: 6px; + padding-inline-start: 0; + + ${ + '' /* Cannot Use Transitions with "height: auto", so this calculates the height of the list based on the number of children */ + } + ${'' /* ${({ open, children }) => (open ? css` */} + ${({ open }) => + open + ? css` + ${ + '' /* height: calc(37px * ${Children.toArray(children).length} ); */ + } + ${ + '' /* This does not transition, but the above does not work well with text wrapping */ + } + ${'' /* TODO: This needs to get relooked at when I have more time */} + height: max-content; + ` + : css` + height: 0px; + `} + overflow-y: hidden; + + transition: height 0.15s; +`; + +const DropDownList: FC = ({ + className /* Allows for external styles to be applied to the component using + the styled components library + className prop needs to be passed to the parent JSX element */, + items /* an Array of items to be displayed in the selector dropdown */, + title /* Name of the drop down list */, + handleClickItem, + value, + valid, +}) => { + const [open, setOpen] = useState(false); + const [selected, setSelected] = useState('') + + const handleOpenToggle = useCallback(() => { + setOpen(!open); + }, [open, setOpen]); + + const handleClickItem2 = useCallback( + (value: string) => { + setSelected(value) + handleClickItem(value); + setOpen(!open); + }, + [handleClickItem, open, setOpen], + ); + + const ListStateIcon = useMemo(() => (open ? ListOpenIcon : ListClosedIcon), [ + open, + ]); + const listItems = items.map((item) => ( + + )); + + return ( + + + {selected!== '' ? selected : title} + + + {listItems} + + ); +}; + +export default DropDownList; diff --git a/src/components/DropDownList/ListItem.tsx b/src/components/DropDownList/ListItem.tsx new file mode 100644 index 0000000000..5c98e23824 --- /dev/null +++ b/src/components/DropDownList/ListItem.tsx @@ -0,0 +1,34 @@ +import React, { FC } from 'react'; +import styled from 'styled-components'; + +interface ListItemProps { + className?: string; + text: string; + onClick: (value: string) => void; +} + +const Container = styled.li` + list-style: none; + border-top: 0.75px solid rgba(214, 220, 227, 0.5); + padding-left: 16px; + padding-right: 16px; + display: flex; + justify-content: space-between; + padding-top: 8px; + padding-bottom: 8px; + + cursor: pointer; +`; + +const Text = styled.div` + font: 14px IBM Plex Sans; + color: #000; +`; + +const ListItem: FC = ({ className, text, onClick }) => ( + onClick(text)}> + {text} + +); + +export default ListItem; diff --git a/src/components/DropDownList/assets/list-closed.svg b/src/components/DropDownList/assets/list-closed.svg new file mode 100644 index 0000000000..6c1bbb2178 --- /dev/null +++ b/src/components/DropDownList/assets/list-closed.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/DropDownList/assets/list-open.svg b/src/components/DropDownList/assets/list-open.svg new file mode 100644 index 0000000000..b92d3c37a2 --- /dev/null +++ b/src/components/DropDownList/assets/list-open.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/DropDownList/assets/remove.svg b/src/components/DropDownList/assets/remove.svg new file mode 100644 index 0000000000..6a58f675c2 --- /dev/null +++ b/src/components/DropDownList/assets/remove.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/DropDownList/index.ts b/src/components/DropDownList/index.ts new file mode 100644 index 0000000000..eec7da7ed0 --- /dev/null +++ b/src/components/DropDownList/index.ts @@ -0,0 +1,3 @@ +import DropDownList from './DropDownList' + +export default DropDownList diff --git a/src/components/FileUpload/FileUpload.tsx b/src/components/FileUpload/FileUpload.tsx new file mode 100644 index 0000000000..0bc94a4175 --- /dev/null +++ b/src/components/FileUpload/FileUpload.tsx @@ -0,0 +1,95 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; + +const FileUpload = ({ + name, + multiple, + handleFileUpload, +}: { + name: string; + multiple: boolean; + handleFileUpload: (value:File) => void; +}) => { + const FileInput = styled.input` + opacity: 0; + position: absolute; + z-index: -1; + `; + + const Label = styled.label` + display: grid; + padding: 20px 10px; + cursor: pointer; + font-family: IBM Plex Sans; + font-style: normal; + font-size: 24px; + line-height: 140%; + text-align: center; + color: rgba(0, 0, 0, 0.5); + width: 100%; + border: 1.2px dashed #c4c4c4; + box-sizing: border-box; + border-radius: 6px; + background: #ffffff; + `; + let [fileName, setFileName] = useState("") + + return ( +
    + + { + if(e.target.files != null){ + // update file in state + handleFileUpload(e.target.files[0]) + setFileName(e.target.files[0].name) + } + } + } + + multiple={multiple} + /> + {fileName &&

    {fileName}

    } +
    + ); +}; + +export default FileUpload; diff --git a/src/components/FileUpload/index.tsx b/src/components/FileUpload/index.tsx new file mode 100644 index 0000000000..f11c02e058 --- /dev/null +++ b/src/components/FileUpload/index.tsx @@ -0,0 +1,3 @@ +import FileUpload from "./FileUpload"; + +export default FileUpload; diff --git a/src/components/Postings/Postings.tsx b/src/components/Postings/Postings.tsx index 1290692c2c..f9b761f75e 100644 --- a/src/components/Postings/Postings.tsx +++ b/src/components/Postings/Postings.tsx @@ -9,13 +9,23 @@ const Postings: React.FC = () => { const { postings } = usePostings(); const { teams } = useTeams(); // Move the "Executive" subteam to the front of the array - const executiveIdx = teams.findIndex((teams) => teams.teamName === 'Executive'); + const executiveIdx = teams.findIndex( + (teams) => teams.teamName === 'Executive', + ); teams.push(...teams.splice(0, executiveIdx)); return (
    {teams.map((team: Team, index: number) => { - return team.teamName === posting.team)} team={team.teamName} key={index} />; + return ( + team.teamName === posting.team, + )} + team={team.teamName} + key={index} + /> + ); })}
    ); diff --git a/src/components/RecruitmentForm/JobPosting.tsx b/src/components/RecruitmentForm/JobPosting.tsx index 582ce1445f..5b22c5d1de 100644 --- a/src/components/RecruitmentForm/JobPosting.tsx +++ b/src/components/RecruitmentForm/JobPosting.tsx @@ -4,6 +4,7 @@ import styled from 'styled-components'; import '../../theme/styles.scss'; type JobPostingProps = { + id: number; role: string; deadline: string; subteam: string; @@ -24,7 +25,6 @@ const LightHeader = styled.h6` color: #c4c4c4; margin-top: 0; `; -const applicationForm = process.env.REACT_APP_APPLICATION_FORM_URL; const FormattedP = styled.p` white-space: pre-line; // ensures spaces and line breaks get displayed @@ -48,7 +48,7 @@ const JobPosting: React.FC = (props) => ( backgroundColor="yellow" textColor="black" text="APPLY" - onClick={(): Window | null => window.open(`${applicationForm}`)} + onClick={(): Window | null => window.open(`/recruitment/${props.id}`, '_self')} />
@@ -118,7 +118,7 @@ const JobPosting: React.FC = (props) => ( backgroundColor="yellow" textColor="black" text="APPLY" - onClick={(): Window | null => window.open(`${applicationForm}`)} + onClick={(): Window | null => window.open(`/recruitment/${props.id}`, '_self')} /> diff --git a/src/components/RecruitmentForm/JobPostingPage.tsx b/src/components/RecruitmentForm/JobPostingPage.tsx index 94f9daab67..db0653933b 100644 --- a/src/components/RecruitmentForm/JobPostingPage.tsx +++ b/src/components/RecruitmentForm/JobPostingPage.tsx @@ -24,11 +24,11 @@ const JobPostingPage: React.FC = () => { if (posting && (posting.closed || deadline.toDate().getTime() <= Date.now())) { return (); } - return (
{posting && ( = ({ - role, - description, - skills, - technicalQ, - termList, -}) => { - const [isJoinClicked, setJoinClicked] = useState(false); - - return ( - - - - -

{role}

- -
- {description} - - Skills Required: - {skills} - -
- -