diff --git a/package.json b/package.json index 146977d6e..dfa7ce614 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "react-contenteditable": "^3.3.6", "react-css-super-themr": "^2.2.0", "react-date-range": "^1.1.3", + "react-datepicker": "^4.14.1", "react-dom": "^18.2.0", "react-dropzone": "^11.3.2", "react-elastic-carousel": "^0.11.5", @@ -81,6 +82,7 @@ "react-helmet": "^6.1.0", "react-html-parser": "^2.0.2", "react-markdown": "8.0.6", + "react-overlays": "^5.2.1", "react-redux": "^8.0.4", "react-redux-toastr": "^7.6.10", "react-responsive": "^9.0.0-beta.5", @@ -149,6 +151,7 @@ "@types/node": "^18.8.5", "@types/reach__router": "^1.3.11", "@types/react": "^18.0.21", + "@types/react-datepicker": "^4.11.2", "@types/react-dom": "^18.0.6", "@types/react-gtm-module": "^2.0.1", "@types/react-helmet": "^6.1.6", diff --git a/src/apps/onboarding/src/assets/images/edit.svg b/src/apps/onboarding/src/assets/images/edit.svg new file mode 100644 index 000000000..87e715e15 --- /dev/null +++ b/src/apps/onboarding/src/assets/images/edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/onboarding/src/assets/images/trash.svg b/src/apps/onboarding/src/assets/images/trash.svg new file mode 100644 index 000000000..4253c2a03 --- /dev/null +++ b/src/apps/onboarding/src/assets/images/trash.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/onboarding/src/components/DateInput/index.tsx b/src/apps/onboarding/src/components/DateInput/index.tsx new file mode 100644 index 000000000..f4ff0e3c4 --- /dev/null +++ b/src/apps/onboarding/src/components/DateInput/index.tsx @@ -0,0 +1,122 @@ +/* eslint-disable ordered-imports/ordered-imports */ +/* eslint-disable react/jsx-no-bind */ +/* eslint-disable unicorn/no-null */ +/* eslint-disable max-len */ +/* eslint-disable react/destructuring-assignment */ +/** + * DateInput + * + * Date Input control. + */ +import React, { FC, createRef, useState } from 'react' +import DatePicker from 'react-datepicker' +import { Portal } from 'react-overlays' +import cn from 'classnames' +import moment from 'moment' + +import 'react-datepicker/dist/react-datepicker.css' + +import styles from './styles.module.scss' + +const CalendarIcon: any = () => ( + + + +) +const ArrowIcon: any = () => ( + +) + +interface CalendarContainerProps { + children?: any +} +const CalendarContainer: FC = ({ children }: CalendarContainerProps) => { + const el: any = document.getElementById('calendar-portal') + + return {children} +} + +interface DateInputProps { + style2?: boolean + className?: string + placeholder?: string + value?: Date + onChange?: (date: Date | null) => void + onBlur?: () => void + onFocus?: () => void + allowFutureDate?: boolean + disabled?: boolean +} + +const DateInput: FC = (props: DateInputProps) => { + const [open, setOpen] = useState(false) + const calendarRef: any = createRef() + return ( +
+
calendarRef.current.setOpen(true)} + className={cn(styles.icon, styles['icon-calendar'])} + > + +
+ { + setOpen(false) + }} + onFocus={props.onFocus} + showYearDropdown + onCalendarOpen={() => setOpen(true)} + maxDate={ + props.allowFutureDate ? null : moment() + .subtract(1, 'days') + .toDate() + } + disabled={props.disabled} + popperContainer={CalendarContainer} + /> +
calendarRef.current.setOpen(true)} + > + +
+
+ ) +} + +export default DateInput diff --git a/src/apps/onboarding/src/components/DateInput/styles.module.scss b/src/apps/onboarding/src/components/DateInput/styles.module.scss new file mode 100644 index 000000000..7e82dc04f --- /dev/null +++ b/src/apps/onboarding/src/components/DateInput/styles.module.scss @@ -0,0 +1,75 @@ +.datepicker-wrapper { + position: relative; + padding: 0 10px; + height: 36px; + display: flex; + align-items: center; + + .icon { + position: absolute; + display: flex; + padding: 8px 0 8px 4px; + align-items: center; + + &>svg { + width: 20px; + height: 20px; + } + } + + .icon-calendar { + left: 8px; + cursor: pointer; + } + + .icon-arrow { + right: 8px; + top: 0; + + &>svg { + color: hsl(0, 0%, 80%); + } + + &:hover { + &>svg { + color: hsl(0, 0%, 60%); + } + } + + &.icon-arrow-open { + &>svg { + color: hsl(0, 0%, 40%); + } + } + } + + &.error { + input { + border-color: #fe665d; + } + } + + &>div:nth-child(2) { + margin-left: 24px; + } + + &.style2 input { + border: none !important; + box-shadow: none !important; + margin-bottom: 0 !important; + font-size: 14px; + font-weight: 400; + + &::placeholder { + color: #7F7F7F; + font-size: 14px; + font-weight: 400; + text-transform: none !important; + opacity: 1; + } + } +} + +.datepicker-wrapper>div:nth-child(2)>div:nth-child(2)>div:nth-child(2) { + z-index: 100; +} \ No newline at end of file diff --git a/src/apps/onboarding/src/components/FormField/index.tsx b/src/apps/onboarding/src/components/FormField/index.tsx new file mode 100644 index 000000000..41057793f --- /dev/null +++ b/src/apps/onboarding/src/components/FormField/index.tsx @@ -0,0 +1,55 @@ +/* eslint-disable ordered-imports/ordered-imports */ +/* eslint-disable react/jsx-no-bind */ +/* eslint-disable react/destructuring-assignment */ +/* eslint-disable unicorn/no-null */ +/** + * FormField + * + * A Form Field Is a wrapper for input to add the label to it + */ +import React, { FC } from 'react' +import classNames from 'classnames' + +import { IconSolid } from '~/libs/ui' + +import styles from './styles.module.scss' + +interface FormFieldProps { + children?: any + className?: string + label?: string + error?: string +} + +const FormField: FC = ({ + children, + className, + label, + ...props +}: FormFieldProps) => { + const handleClick: any = (e: any) => { + // focus on input label click + const inputElement: any = e.target.closest('.form-field') + .querySelector('input') + inputElement?.focus() + } + + return ( +
+
+
+ {label} +
+ {children} +
+
+ {props.error ? () : null} + {props.error} +
+
+ ) +} + +export default FormField diff --git a/src/apps/onboarding/src/components/FormField/styles.module.scss b/src/apps/onboarding/src/components/FormField/styles.module.scss new file mode 100644 index 000000000..b81310a4a --- /dev/null +++ b/src/apps/onboarding/src/components/FormField/styles.module.scss @@ -0,0 +1,32 @@ +$green1: #137d60; + +.form-field-wrapper { + position: relative; + display: flex; + flex-direction: column; + justify-content: flex-start; + margin-bottom: 18px; + + .form-field { + border: 1px solid #b7b7b7; + border-radius: 4px; + background: white; + padding-top: 24px; + margin-bottom: 10px; + + .label { + position: absolute; + top: 5px; + left: 15px; + color: $green1; + font-size: 11px; + font-weight: 500; + } + } + + .error { + color: #ec2710; + font-size: 11px; + gap: 4px; + } +} \ No newline at end of file diff --git a/src/apps/onboarding/src/components/connect-linked-in/index.tsx b/src/apps/onboarding/src/components/connect-linked-in/index.tsx new file mode 100644 index 000000000..ddff9dc93 --- /dev/null +++ b/src/apps/onboarding/src/components/connect-linked-in/index.tsx @@ -0,0 +1,21 @@ +/* eslint-disable ordered-imports/ordered-imports */ +/* eslint-disable react/jsx-no-bind */ +import React, { FC } from 'react' + +import { Button } from '~/libs/ui' + +const ConnectLinkedIn: FC<{}> = () => ( +
+ Wait! Get my skills from LinkedIn instead... + +
+) + +export default ConnectLinkedIn diff --git a/src/apps/onboarding/src/components/modal-add-education/index.tsx b/src/apps/onboarding/src/components/modal-add-education/index.tsx new file mode 100644 index 000000000..4afbd8a01 --- /dev/null +++ b/src/apps/onboarding/src/components/modal-add-education/index.tsx @@ -0,0 +1,200 @@ +/* eslint-disable ordered-imports/ordered-imports */ +/* eslint-disable react/jsx-no-bind */ +/* eslint-disable sort-keys */ +import { FC, useEffect, useState } from 'react' +import classNames from 'classnames' +import moment from 'moment' +import _ from 'lodash' + +import { BaseModal, Button, InputText } from '~/libs/ui' +import { FormInputCheckbox } from '~/apps/self-service/src/components/form-elements' + +import styles from './styles.module.scss' +import EducationInfo, { emptyEducationInfo } from '../../models/EducationInfo' +import FormField from '../FormField' +import DateInput from '../DateInput' + +const FormInputCheckboxMiddleware: any = FormInputCheckbox as any + +interface ModalAddEducationProps { + onClose?: () => void + editingEducation?: EducationInfo | null + onAdd?: (educationInfo: EducationInfo) => void + onEdit?: (educationInfo: EducationInfo) => void +} + +const ModalAddEducation: FC = (props: ModalAddEducationProps) => { + const [educationInfo, setEducationInfo] = useState(emptyEducationInfo()) + const [formErrors, setFormErrors] = useState({ + collegeName: undefined, + major: undefined, + startDate: undefined, + }) + + const validateDate: any = (startDate: Date | undefined, endDate: Date | undefined) => { + const isInValid: any = endDate + && startDate + && moment(endDate) + .isSameOrBefore(startDate) + return !isInValid + } + + const validateField: any = () => { + const errorTmp: any = {} + if (!educationInfo.collegeName) { + errorTmp.collegeName = 'Required' + } + + if (!educationInfo.major) { + errorTmp.major = 'Required' + } + + if (!validateDate(educationInfo.startDate, educationInfo.endDate)) { + errorTmp.startDate = 'Start Date should be before End Date' + } + + setFormErrors(errorTmp) + return _.isEmpty(errorTmp) + } + + useEffect(() => { + if (props.editingEducation) { + setEducationInfo(props.editingEducation) + } + }, [props.editingEducation]) + + return ( + { + if (validateField()) { + const endDate: Date | undefined = educationInfo.endDate + let endDateString: string = endDate ? moment(endDate) + .format('YYYY') : '' + if (!educationInfo.graduated) { + endDateString = 'current' + } + + let startDateString: string = educationInfo.startDate ? moment(educationInfo.startDate) + .format('YYYY') : '' + if (startDateString) { + startDateString += '-' + } + + (props.editingEducation ? props.onEdit : props.onAdd)?.({ + ...educationInfo, + dateDescription: ( + educationInfo.startDate || educationInfo.endDate + ) ? `${startDateString}${endDateString}` : '', + }) + props.onClose?.() + } + }} + /> + )} + onClose={props.onClose || _.noop} + open + size='body' + title={props.editingEducation ? 'Edit education:' : 'Add education:'} + classNames={{ modal: styles.infoModal }} + > +
+
+ { + setEducationInfo({ + ...educationInfo, + collegeName: event.target.value, + }) + }} + placeholder='Name of College or University' + tabIndex={0} + type='text' + dirty + error={formErrors.collegeName} + /> +
+
+ setEducationInfo({ + ...educationInfo, + major: event.target.value, + })} + placeholder='Major' + tabIndex={0} + type='text' + dirty + error={formErrors.major} + /> +
+
+
+ + { + setEducationInfo({ + ...educationInfo, + startDate: v || undefined, + }) + }} + style2 + placeholder='Start date' + /> + +
+
+ + { + setEducationInfo({ + ...educationInfo, + endDate: v || undefined, + }) + }} + style2 + placeholder='End date' + /> + +
+
+ { + setEducationInfo({ + ...educationInfo, + graduated: e.target.checked, + }) + }} + /> +
+
+ ) +} + +export default ModalAddEducation diff --git a/src/apps/onboarding/src/components/modal-add-education/styles.module.scss b/src/apps/onboarding/src/components/modal-add-education/styles.module.scss new file mode 100644 index 000000000..a8d052c57 --- /dev/null +++ b/src/apps/onboarding/src/components/modal-add-education/styles.module.scss @@ -0,0 +1,11 @@ +@import '@libs/ui/styles/includes'; + +.infoModal { + :global(.react-responsive-modal-closeButton) { + display: flex; + } + + .modalContent { + width: 100%; + } +} diff --git a/src/apps/onboarding/src/components/modal-add-work/index.tsx b/src/apps/onboarding/src/components/modal-add-work/index.tsx new file mode 100644 index 000000000..6941e4451 --- /dev/null +++ b/src/apps/onboarding/src/components/modal-add-work/index.tsx @@ -0,0 +1,250 @@ +/* eslint-disable ordered-imports/ordered-imports */ +/* eslint-disable react/jsx-no-bind */ +/* eslint-disable sort-keys */ +import { FC, useEffect, useState } from 'react' +import classNames from 'classnames' +import _ from 'lodash' +import moment from 'moment' + +import { BaseModal, Button, InputSelect, InputText, InputTextarea } from '~/libs/ui' +import { FormInputCheckbox } from '~/apps/self-service/src/components/form-elements' + +import styles from './styles.module.scss' +import WorkInfo, { emptyWorkInfo } from '../../models/WorkInfo' +import { INDUSTRIES_OPTIONS } from '../../config' +import FormField from '../FormField' +import DateInput from '../DateInput' + +const FormInputCheckboxMiddleware: any = FormInputCheckbox as any + +interface ModalAddWorkProps { + onClose?: () => void + editingWork?: WorkInfo | null + onAdd?: (workInfo: WorkInfo) => void + onEdit?: (workInfo: WorkInfo) => void +} + +const industryOptions: any = _.sortBy(INDUSTRIES_OPTIONS) + .map(v => ({ + value: v, + label: v, + })) + +const ModalAddWork: FC = (props: ModalAddWorkProps) => { + const [workInfo, setWorkInfo] = useState(emptyWorkInfo()) + const [formErrors, setFormErrors] = useState({ + company: undefined, + position: undefined, + startDate: undefined, + }) + + const validateDate: any = (startDate: Date | undefined, endDate: Date | undefined) => { + const isInValid: any = endDate + && startDate + && moment(endDate) + .isSameOrBefore(startDate) + return !isInValid + } + + const validateField: any = () => { + const errorTmp: any = {} + if (!workInfo.company) { + errorTmp.company = 'Required' + } + + if (!workInfo.position) { + errorTmp.position = 'Required' + } + + if (!validateDate(workInfo.startDate, workInfo.endDate)) { + errorTmp.startDate = 'Start Date should be before End Date' + } + + setFormErrors(errorTmp) + return _.isEmpty(errorTmp) + } + + useEffect(() => { + if (props.editingWork) { + setWorkInfo(props.editingWork) + } + }, [props.editingWork]) + + return ( + { + if (validateField()) { + const endDate: Date | undefined = workInfo.endDate + let endDateString: string = endDate ? moment(endDate) + .format('YYYY') : '' + if (workInfo.currentlyWorking) { + endDateString = 'current' + } + + let startDateString: string = workInfo.startDate ? moment(workInfo.startDate) + .format('YYYY') : '' + if (startDateString) { + startDateString += '-' + } + + (props.editingWork ? props.onEdit : props.onAdd)?.({ + ...workInfo, + dateDescription: ( + workInfo.startDate || workInfo.endDate + ) ? `${startDateString}${endDateString}` : '', + }) + props.onClose?.() + } + }} + /> + )} + onClose={props.onClose || _.noop} + open + size='body' + title={props.editingWork ? 'Edit work experience:' : 'Add work experience:'} + classNames={{ modal: styles.infoModal }} + > +
+
+ { + setWorkInfo({ + ...workInfo, + company: event.target.value, + }) + }} + placeholder='Company Name' + tabIndex={0} + type='text' + dirty + error={formErrors.company} + /> +
+
+
+ setWorkInfo({ + ...workInfo, + industry: event.target.value, + })} + name='industry' + label='Industry' + placeholder='Industry' + /> +
+
+ setWorkInfo({ + ...workInfo, + city: event.target.value, + })} + placeholder='Location' + tabIndex={0} + type='text' + /> +
+
+
+ setWorkInfo({ + ...workInfo, + position: event.target.value, + })} + placeholder='Position / Job Title' + tabIndex={0} + type='text' + dirty + error={formErrors.position} + /> +
+
+
+ + { + setWorkInfo({ + ...workInfo, + startDate: v || undefined, + }) + }} + style2 + placeholder='Start date' + /> + +
+
+ + { + setWorkInfo({ + ...workInfo, + endDate: v || undefined, + }) + }} + style2 + placeholder='End date' + /> + +
+
+ { + setWorkInfo({ + ...workInfo, + currentlyWorking: e.target.checked, + }) + }} + /> +
+ setWorkInfo({ + ...workInfo, + description: event.target.value, + })} + onBlur={_.noop} + placeholder='Description' + tabIndex={0} + /> +
+
+
+ ) +} + +export default ModalAddWork diff --git a/src/apps/onboarding/src/components/modal-add-work/styles.module.scss b/src/apps/onboarding/src/components/modal-add-work/styles.module.scss new file mode 100644 index 000000000..a8d052c57 --- /dev/null +++ b/src/apps/onboarding/src/components/modal-add-work/styles.module.scss @@ -0,0 +1,11 @@ +@import '@libs/ui/styles/includes'; + +.infoModal { + :global(.react-responsive-modal-closeButton) { + display: flex; + } + + .modalContent { + width: 100%; + } +} diff --git a/src/apps/onboarding/src/components/progress-bar/index.tsx b/src/apps/onboarding/src/components/progress-bar/index.tsx index e0672f03b..ed32ae048 100644 --- a/src/apps/onboarding/src/components/progress-bar/index.tsx +++ b/src/apps/onboarding/src/components/progress-bar/index.tsx @@ -1,3 +1,5 @@ +/* eslint-disable ordered-imports/ordered-imports */ +/* eslint-disable react/jsx-no-bind */ import React, { FC } from 'react' import styles from './styles.module.scss' diff --git a/src/apps/onboarding/src/components/skill-tag/index.tsx b/src/apps/onboarding/src/components/skill-tag/index.tsx index 271bc9314..b31b377f8 100644 --- a/src/apps/onboarding/src/components/skill-tag/index.tsx +++ b/src/apps/onboarding/src/components/skill-tag/index.tsx @@ -1,3 +1,5 @@ +/* eslint-disable ordered-imports/ordered-imports */ +/* eslint-disable react/jsx-no-bind */ import React, { FC } from 'react' import classNames from 'classnames' diff --git a/src/apps/onboarding/src/components/skill-tag/styles.module.scss b/src/apps/onboarding/src/components/skill-tag/styles.module.scss index 2af4e9a89..eabe14698 100644 --- a/src/apps/onboarding/src/components/skill-tag/styles.module.scss +++ b/src/apps/onboarding/src/components/skill-tag/styles.module.scss @@ -1,19 +1,19 @@ .container { - background-color: gray; - color: white; - border-radius: 3px; - padding: 0 10px; - font-size: 14px; - gap: 5px; + background-color: gray; + color: white; + border-radius: 3px; + padding: 0 10px; + font-size: 14px; + gap: 5px; } .btnDelete { - display: flex; - align-items: center; - justify-content: center; - background-color: white; - border-radius: 100%; - flex-shrink: 0; - width: 15px; - height: 15px; + display: flex; + align-items: center; + justify-content: center; + background-color: white; + border-radius: 100%; + flex-shrink: 0; + width: 15px; + height: 15px; } \ No newline at end of file diff --git a/src/apps/onboarding/src/config/index.ts b/src/apps/onboarding/src/config/index.ts index 100565d55..d9e7e1873 100644 --- a/src/apps/onboarding/src/config/index.ts +++ b/src/apps/onboarding/src/config/index.ts @@ -1,11 +1,29 @@ +/* eslint-disable sort-keys */ export const ACTIONS: { MEMBER: { GET_MEMBER: string; UPDATE_MEMBER_SKILLS: string; + SET_WORKS: string; + SET_EDUCATIONS: string; }; } = { MEMBER: { GET_MEMBER: 'GET_MEMBER', UPDATE_MEMBER_SKILLS: 'UPDATE_MEMBER_SKILLS', + SET_WORKS: 'SET_WORKS', + SET_EDUCATIONS: 'SET_EDUCATIONS', }, } + +export const INDUSTRIES_OPTIONS: string[] = [ + 'Banking', + 'Consumer goods', + 'Energy', + 'Entertainment', + 'Healthcare', + 'Pharma', + 'Tech & technology services', + 'Telecoms', + 'Public sector', + 'Travel & hospitality', +] diff --git a/src/apps/onboarding/src/models/EducationInfo.ts b/src/apps/onboarding/src/models/EducationInfo.ts new file mode 100644 index 000000000..c43ccecf0 --- /dev/null +++ b/src/apps/onboarding/src/models/EducationInfo.ts @@ -0,0 +1,20 @@ +/* eslint-disable sort-keys */ +export default interface EducationInfo { + collegeName: string + major: string + dateDescription: string + startDate?: Date + endDate?: Date + graduated?: boolean + id: number +} + +export const emptyEducationInfo: () => EducationInfo = () => ({ + collegeName: '', + major: '', + dateDescription: '', + startDate: undefined, + endDate: undefined, + graduated: false, + id: 0, +}) diff --git a/src/apps/onboarding/src/models/SkillInfo.ts b/src/apps/onboarding/src/models/SkillInfo.ts index ea886ae37..cbfb4a167 100644 --- a/src/apps/onboarding/src/models/SkillInfo.ts +++ b/src/apps/onboarding/src/models/SkillInfo.ts @@ -1,7 +1,7 @@ export default interface SkillInfo { - category: string; - emsiId: string; - name: string; - skillSources?: string[]; - subCategory: string; + category: string + emsiId: string + name: string + skillSources?: string[] + subCategory: string } diff --git a/src/apps/onboarding/src/models/WorkInfo.ts b/src/apps/onboarding/src/models/WorkInfo.ts new file mode 100644 index 000000000..6342a9681 --- /dev/null +++ b/src/apps/onboarding/src/models/WorkInfo.ts @@ -0,0 +1,26 @@ +/* eslint-disable sort-keys */ +export default interface WorkInfo { + company?: string + position?: string + industry?: string + city?: string + startDate?: Date + dateDescription?: string + description?: string + endDate?: Date + currentlyWorking?: boolean + id: number +} + +export const emptyWorkInfo: () => WorkInfo = () => ({ + company: '', + position: '', + industry: '', + city: '', + startDate: undefined, + dateDescription: '', + endDate: undefined, + description: '', + currentlyWorking: false, + id: 0, +}) diff --git a/src/apps/onboarding/src/onboarding.routes.tsx b/src/apps/onboarding/src/onboarding.routes.tsx index 26e5e29f8..0254db598 100644 --- a/src/apps/onboarding/src/onboarding.routes.tsx +++ b/src/apps/onboarding/src/onboarding.routes.tsx @@ -6,6 +6,8 @@ import { lazyLoad, LazyLoadedComponent, PlatformRoute, UserRole } from '~/libs/c const PageOnboarding: LazyLoadedComponent = lazyLoad(() => import('./pages/onboarding/index'), 'OnboardingWrapper') const PageStart: LazyLoadedComponent = lazyLoad(() => import('./pages/start/index'), 'PageStart') const PageSkills: LazyLoadedComponent = lazyLoad(() => import('./pages/skills/index'), 'PageSkills') +const PageWorks: LazyLoadedComponent = lazyLoad(() => import('./pages/works/index'), 'PageWorks') +const PageEducations: LazyLoadedComponent = lazyLoad(() => import('./pages/educations/index'), 'PageEducations') const toolTitle: string = ToolTitle.onboarding const onboardingRootRoute: string = '/onboarding' @@ -22,12 +24,18 @@ export const onboardingRoutes: ReadonlyArray = [ { element: , route: '/start', - title: toolTitle, }, { element: , route: '/skills', - title: toolTitle, + }, + { + element: , + route: '/works', + }, + { + element: , + route: '/educations', }, ], element: , diff --git a/src/apps/onboarding/src/pages/educations/index.tsx b/src/apps/onboarding/src/pages/educations/index.tsx new file mode 100644 index 000000000..871da4ae4 --- /dev/null +++ b/src/apps/onboarding/src/pages/educations/index.tsx @@ -0,0 +1,168 @@ +/* eslint-disable ordered-imports/ordered-imports */ +/* eslint-disable react/jsx-no-bind */ +/* eslint-disable unicorn/no-null */ +import { FC, useEffect, useState } from 'react' +import { useNavigate } from 'react-router-dom' +import classNames from 'classnames' +import _ from 'lodash' +import { connect } from 'react-redux' + +import { Button, PageDivider } from '~/libs/ui' + +import { ProgressBar } from '../../components/progress-bar' +import styles from './styles.module.scss' +import IconEdit from '../../assets/images/edit.svg' +import IconTrash from '../../assets/images/trash.svg' +import EducationInfo from '../../models/EducationInfo' +import ModalAddEducation from '../../components/modal-add-education' +import { updateMemberEducations, createMemberEducations } from '../../redux/actions/member' + +export const PageEducationsContent: FC<{ + reduxEducations: EducationInfo[] | null + updateMemberEducations: (educations: EducationInfo[]) => void + createMemberEducations: (educations: EducationInfo[]) => void +}> = props => { + const navigate: any = useNavigate() + const [editingEducation, setEditingEducation] = useState(null) + const [educations, setEducations] = useState(null) + const [educationId, setEducationId] = useState(10) + const [showAddEducationModal, setShowAddEducationModal] = useState(false) + const [loading, setLoading] = useState(false) + useEffect(() => { + if (!educations && props.reduxEducations) { + setEducations(props.reduxEducations) + setEducationId(props.reduxEducations[props.reduxEducations.length - 1].id + 1) + } + /* eslint-disable react-hooks/exhaustive-deps */ + }, [props.reduxEducations]) + + return ( +
+

Add your education information

+ + +
+
+ {(educations || []).map(education => ( +
+
+ {education.collegeName} + + +
+ {education.major} + {education.dateDescription ? ({education.dateDescription}) : null} + +
+ ))} +
+ + + {(!educations || educations.length === 0) ? ( + + Relevant education details will help make your + profile more valuable to potential employers, add it here! + + ) : null} +
+ + + +
+ + +
+ {showAddEducationModal ? ( + { + setShowAddEducationModal(false) + setEditingEducation(null) + }} + onAdd={newEducation => { + setEducations([...(educations || []), { + ...newEducation, + id: educationId + 1, + }]) + setEducationId(educationId + 1) + }} + onEdit={editEducation => { + setEducations((educations || []).map(w => (w.id !== editEducation.id ? w : editEducation))) + }} + /> + ) : null} +
+ ) +} + +const mapStateToProps: any = (state: any) => { + const { + educations, + }: any = state.member + + return { + reduxEducations: educations, + } +} + +const mapDispatchToProps: any = { + createMemberEducations, + updateMemberEducations, +} + +export const PageEducations: any = connect(mapStateToProps, mapDispatchToProps)(PageEducationsContent) + +export default PageEducations diff --git a/src/apps/onboarding/src/pages/educations/styles.module.scss b/src/apps/onboarding/src/pages/educations/styles.module.scss new file mode 100644 index 000000000..70463203a --- /dev/null +++ b/src/apps/onboarding/src/pages/educations/styles.module.scss @@ -0,0 +1,12 @@ +.container { + min-height: 100%; +} + +.blockContent { + margin-bottom: 20px; +} + +.ProgressBar { + margin-top: auto; + margin-bottom: 32px; +} diff --git a/src/apps/onboarding/src/pages/onboarding/index.tsx b/src/apps/onboarding/src/pages/onboarding/index.tsx index 96bd37517..5592b4025 100644 --- a/src/apps/onboarding/src/pages/onboarding/index.tsx +++ b/src/apps/onboarding/src/pages/onboarding/index.tsx @@ -7,16 +7,20 @@ import { ContentLayout } from '~/libs/ui' import { routerContext, RouterContextData } from '~/libs/core' import { onboardRouteId } from '../../onboarding.routes' -import { fetchMemberInfo } from '../../redux/actions/member' +import { fetchMemberInfo, fetchMemberTraits } from '../../redux/actions/member' import store from '../../redux/store' import '../../styles/global/_index.scss' import styles from './styles.module.scss' -const OnboardingContent: FC<{ fetchMemberInfo: () => void }> = props => { +const OnboardingContent: FC<{ + fetchMemberInfo: () => void; + fetchMemberTraits: () => void; +}> = props => { const { getChildRoutes }: RouterContextData = useContext(routerContext) useEffect(() => { props.fetchMemberInfo() + props.fetchMemberTraits() /* eslint-disable react-hooks/exhaustive-deps */ }, []) @@ -26,12 +30,14 @@ const OnboardingContent: FC<{ fetchMemberInfo: () => void }> = props => { {getChildRoutes(onboardRouteId)} +
) } const mapDispatchToProps: any = { fetchMemberInfo, + fetchMemberTraits, } const Onboarding: any = connect(undefined, mapDispatchToProps)(OnboardingContent) diff --git a/src/apps/onboarding/src/pages/skills/index.tsx b/src/apps/onboarding/src/pages/skills/index.tsx index 1a874eaa3..9eb871e38 100644 --- a/src/apps/onboarding/src/pages/skills/index.tsx +++ b/src/apps/onboarding/src/pages/skills/index.tsx @@ -18,19 +18,21 @@ import SkillInfo from '../../models/SkillInfo' import SkillTag from '../../components/skill-tag' import styles from './styles.module.scss' +import ConnectLinkedIn from '../../components/connect-linked-in' const PageSkillsContent: FC<{ memberInfo?: Member, updateMemberSkills: (skills: SkillInfo[]) => void }> = props => { const navigate: any = useNavigate() - const [skillsFilter, setSkillsFilter] = useState>([]) + const [skillsFilter, setSkillsFilter] = useState | null>(null) const [loading, setLoading] = useState(false) useEffect(() => { - if (!skillsFilter.length) { + if (!skillsFilter && props.memberInfo) { setSkillsFilter(props.memberInfo?.emsiSkills || []) } - }, [props.memberInfo, skillsFilter]) + /* eslint-disable react-hooks/exhaustive-deps */ + }, [props.memberInfo]) return (
@@ -39,13 +41,12 @@ const PageSkillsContent: FC<{

Select your skills

-
- + Add industry standard skills to your profile to let employers search and find you for opportunities that fit your capabilities.
- {(skillsFilter.length > 0) ? ( + {(skillsFilter && skillsFilter.length > 0) ? (
- {skillsFilter.map(skillItem => ( + {(skillsFilter || []).map(skillItem => ( skill.emsiId} onChange={(options: readonly SkillInfo[]) => { if (options.length > 0) { - const newSkillFilter: SkillInfo[] = _.uniqBy([...skillsFilter, ...options], 'name') + const newSkillFilter: SkillInfo[] = _.uniqBy( + [...(skillsFilter || []), ...options], + 'name', + ) _.forEach(options, option => { const matchSkill: SkillInfo | undefined = _.find( newSkillFilter, @@ -105,17 +109,8 @@ const PageSkillsContent: FC<{ />
-
- Wait! Get my skills from LinkedIn instead... - -
+ +
{ setLoading(true) - await props.updateMemberSkills([...skillsFilter]) + if (!_.isEqual(props.memberInfo?.emsiSkills, skillsFilter)) { + await props.updateMemberSkills([...(skillsFilter || [])]) + } + setLoading(false) + navigate('../works') }} > next diff --git a/src/apps/onboarding/src/pages/start/index.tsx b/src/apps/onboarding/src/pages/start/index.tsx index add10ccb9..bfe66debd 100644 --- a/src/apps/onboarding/src/pages/start/index.tsx +++ b/src/apps/onboarding/src/pages/start/index.tsx @@ -21,11 +21,9 @@ export const PageStart: FC<{}> = () => { help put your best foot forward. Let potential employers and others in our network see your exceptional talent! -
-

How would you like to share your skills and experience?

-
+

How would you like to share your skills and experience?

-
+
We can extract data from your LinkedIn profile or a digital version of your resume.
diff --git a/src/apps/onboarding/src/pages/works/index.tsx b/src/apps/onboarding/src/pages/works/index.tsx new file mode 100644 index 000000000..b13858074 --- /dev/null +++ b/src/apps/onboarding/src/pages/works/index.tsx @@ -0,0 +1,183 @@ +/* eslint-disable ordered-imports/ordered-imports */ +/* eslint-disable react/jsx-no-bind */ +/* eslint-disable unicorn/no-null */ +import { FC, useEffect, useState } from 'react' +import { connect } from 'react-redux' +import { useNavigate } from 'react-router-dom' +import classNames from 'classnames' +import _ from 'lodash' + +import { Button, PageDivider } from '~/libs/ui' + +import { ProgressBar } from '../../components/progress-bar' +import styles from './styles.module.scss' +import ConnectLinkedIn from '../../components/connect-linked-in' +import WorkInfo from '../../models/WorkInfo' +import ModalAddWork from '../../components/modal-add-work' +import IconEdit from '../../assets/images/edit.svg' +import IconTrash from '../../assets/images/trash.svg' +import { createMemberWorks, updateMemberWorks } from '../../redux/actions/member' + +export const PageWorksContent: FC<{ + reduxWorks: WorkInfo[] | null + updateMemberWorks: (works: WorkInfo[]) => void + createMemberWorks: (works: WorkInfo[]) => void +}> = props => { + const navigate: any = useNavigate() + const [editingWork, setEditingWork] = useState(null) + const [works, setWorks] = useState(null) + const [workId, setWorkId] = useState(10) + const [showAddWorkModal, setShowAddWorkModal] = useState(false) + const [loading, setLoading] = useState(false) + useEffect(() => { + if (!works && props.reduxWorks) { + setWorks(props.reduxWorks) + setWorkId(props.reduxWorks[props.reduxWorks.length - 1].id + 1) + } + /* eslint-disable react-hooks/exhaustive-deps */ + }, [props.reduxWorks]) + + return ( +
+

Add your work experience here

+ + +
+
+
+ {(works || []).map(work => ( +
+
+ {work.company} + + +
+ {work.city ? ({work.city}) : null} + {work.position} + {work.dateDescription ? ({work.dateDescription}) : null} + {work.description ? ( + + {work.description} + + ) : null} + +
+ ))} +
+ + + {(!works || works.length === 0) ? ( + + You will be able to add details for each of the work experiences + that you think will demonstrate your abilities. + + ) : null} +
+ +
+ + + +
+ + +
+ {showAddWorkModal ? ( + { + setShowAddWorkModal(false) + setEditingWork(null) + }} + onAdd={newWork => { + setWorks([...(works || []), { + ...newWork, + id: workId + 1, + }]) + setWorkId(workId + 1) + }} + onEdit={editWork => { + setWorks( + (works || []).map(w => (w.id !== editWork.id ? w : editWork)), + ) + }} + /> + ) : null} +
+ ) +} + +const mapStateToProps: any = (state: any) => { + const { + works, + }: any = state.member + + return { + reduxWorks: works, + } +} + +const mapDispatchToProps: any = { + createMemberWorks, + updateMemberWorks, +} + +export const PageWorks: any = connect(mapStateToProps, mapDispatchToProps)(PageWorksContent) + +export default PageWorks diff --git a/src/apps/onboarding/src/pages/works/styles.module.scss b/src/apps/onboarding/src/pages/works/styles.module.scss new file mode 100644 index 000000000..ecb8d6941 --- /dev/null +++ b/src/apps/onboarding/src/pages/works/styles.module.scss @@ -0,0 +1,20 @@ +.container { + min-height: 100%; +} + +.blockContent { + margin-bottom: 20px; +} + +.ProgressBar { + margin-top: auto; + margin-bottom: 32px; +} + +.textDescription { + white-space: pre; +} + +.textPosition { + font-weight: 700; +} \ No newline at end of file diff --git a/src/apps/onboarding/src/redux/actions/member.ts b/src/apps/onboarding/src/redux/actions/member.ts index 713ab79e3..c1eb93731 100644 --- a/src/apps/onboarding/src/redux/actions/member.ts +++ b/src/apps/onboarding/src/redux/actions/member.ts @@ -1,28 +1,232 @@ /* eslint-disable ordered-imports/ordered-imports */ /* eslint-disable react/jsx-no-bind */ /* eslint-disable sort-keys */ +import moment from 'moment' import { Member } from '~/apps/talent-search/src/lib/models' -import { getAsync, putAsync } from '~/libs/core/lib/xhr/xhr-functions/xhr.functions' import { getAsync as getAsyncToken } from '~/libs/core/lib/auth/token-functions/token.functions' -import { profile } from '~/libs/core/lib/profile/profile-functions/profile-store/profile-endpoint.config' import { TokenModel } from '~/libs/core' +import { + createMemberTraits, + updateMemberTraits, +} from '~/libs/core/lib/profile/profile-functions/profile-store/profile-xhr.store' + import { ACTIONS } from '../../config' import SkillInfo from '../../models/SkillInfo' +import { getMemberInfo, getMemberTraits, putMemberInfo } from '../../services/members' +import WorkInfo from '../../models/WorkInfo' +import EducationInfo from '../../models/EducationInfo' export const updateMemberInfo: any = (memberInfo: Member) => ({ type: ACTIONS.MEMBER.GET_MEMBER, payload: memberInfo, }) +export const updateWorks: any = (works: WorkInfo[]) => ({ + type: ACTIONS.MEMBER.SET_WORKS, + payload: works, +}) + +export const updateEducations: any = (educations: EducationInfo[]) => ({ + type: ACTIONS.MEMBER.SET_EDUCATIONS, + payload: educations, +}) + export const fetchMemberInfo: any = () => async (dispatch: any) => { try { const tokenInfo: TokenModel = await getAsyncToken() - const memberInfo: Member = await getAsync(profile(tokenInfo.handle || '')) + const memberInfo: Member = await getMemberInfo(tokenInfo.handle || '') dispatch(updateMemberInfo(memberInfo)) } catch (error) { } } +const dateTimeToDate: any = (s: string) => (s ? new Date(s) : undefined) +export const fetchMemberTraits: any = () => async (dispatch: any) => { + const tokenInfo: TokenModel = await getAsyncToken() + let memberTraits: any = [] + try { + memberTraits = await getMemberTraits(tokenInfo.handle || '') + } catch (error) { + } + + const workExp: any = memberTraits.find((t: any) => t.traitId === 'work') + const workExpValue: any = workExp?.traits?.data + if (workExpValue) { + // workExpValue is array of works. fill it to state + const works: WorkInfo[] = workExpValue.map((j: any, index: number) => { + const startDate: Date | undefined = dateTimeToDate(j.timePeriodFrom) + const endDate: Date | undefined = dateTimeToDate(j.timePeriodTo) + let endDateString: string = endDate ? moment(endDate) + .format('YYYY') : '' + if (j.working) { + endDateString = 'current' + } + + let startDateString: string = startDate ? moment(startDate) + .format('YYYY') : '' + if (startDateString) { + startDateString += '-' + } + + const dateDescription: string = ( + startDate || endDate + ) ? `${startDateString}${endDateString}` : '' + return ({ + company: j.company, + position: j.position, + industry: j.industry, + city: j.cityTown, + startDate, + endDate, + currentlyWorking: j.working, + description: j.description, + dateDescription, + id: index + 1, + }) + }) + dispatch(updateWorks(works)) + } + + const educationExp: any = memberTraits.find( + (t: any) => t.traitId === 'education', + ) + const educationExpValue: any = educationExp?.traits?.data + if (educationExpValue) { + // educationExpValue is array of educations. fill it to state + const educations: EducationInfo[] = educationExpValue.map((e: any, index: number) => { + const startDate: Date | undefined = dateTimeToDate(e.timePeriodFrom) + const endDate: Date | undefined = dateTimeToDate(e.timePeriodTo) + let endDateString: string = endDate ? moment(endDate) + .format('YYYY') : '' + if (!e.graduated) { + endDateString = 'current' + } + + let startDateString: string = startDate ? moment(startDate) + .format('YYYY') : '' + if (startDateString) { + startDateString += '-' + } + + const dateDescription: string = ( + startDate || endDate + ) ? `${startDateString}${endDateString}` : '' + return ({ + collegeName: e.schoolCollegeName, + major: e.major, + startDate, + endDate, + graduated: e.graduated, + dateDescription, + id: index + 1, + }) + }) + dispatch(updateEducations(educations)) + } +} + +const createWorksPayloadData: any = (works: WorkInfo[]) => { + const data: any = works.map(work => { + const { + company, + position, + industry, + city, + startDate, + endDate, + currentlyWorking, + description, + }: any = work + return { + company, + industry, + position, + cityTown: city, + description, + timePeriodFrom: startDate ? startDate.toISOString() : '', + timePeriodTo: endDate ? endDate.toISOString() : '', + working: currentlyWorking, + } + }) + + const payload: any = { + categoryName: 'Work', + traitId: 'work', + traits: { + data, + }, + } + return [payload] +} + +export const updateMemberWorks: any = (works: WorkInfo[]) => async (dispatch: any) => { + try { + const tokenInfo: TokenModel = await getAsyncToken() + + await updateMemberTraits(tokenInfo.handle || '', createWorksPayloadData(works)) + dispatch(updateWorks(works)) + } catch (error) { + } +} + +export const createMemberWorks: any = (works: WorkInfo[]) => async (dispatch: any) => { + try { + const tokenInfo: TokenModel = await getAsyncToken() + + await createMemberTraits(tokenInfo.handle || '', createWorksPayloadData(works)) + dispatch(updateWorks(works)) + } catch (error) { + } +} + +const createEducationsPayloadData: any = (educations: EducationInfo[]) => { + const data: any = educations.map(education => { + const { + collegeName, + major, + startDate, + endDate, + graduated, + }: any = education + return { + schoolCollegeName: collegeName, + major, + timePeriodFrom: startDate ? startDate.toISOString() : '', + timePeriodTo: endDate ? endDate.toISOString() : '', + graduated, + } + }) + + const payload: any = { + categoryName: 'Education', + traitId: 'education', + traits: { + data, + }, + } + return [payload] +} + +export const updateMemberEducations: any = (educations: EducationInfo[]) => async (dispatch: any) => { + try { + const tokenInfo: TokenModel = await getAsyncToken() + + await updateMemberTraits(tokenInfo.handle || '', createEducationsPayloadData(educations)) + dispatch(updateEducations(educations)) + } catch (error) { + } +} + +export const createMemberEducations: any = (educations: EducationInfo[]) => async (dispatch: any) => { + try { + const tokenInfo: TokenModel = await getAsyncToken() + + await createMemberTraits(tokenInfo.handle || '', createEducationsPayloadData(educations)) + dispatch(updateEducations(educations)) + } catch (error) { + } +} + export const setMemberSkills: any = (skills: SkillInfo[]) => ({ type: ACTIONS.MEMBER.UPDATE_MEMBER_SKILLS, payload: skills, @@ -31,7 +235,7 @@ export const setMemberSkills: any = (skills: SkillInfo[]) => ({ export const updateMemberSkills: any = (skills: SkillInfo[]) => async (dispatch: any) => { try { const tokenInfo: TokenModel = await getAsyncToken() - await putAsync(profile(tokenInfo.handle || ''), { + await putMemberInfo(tokenInfo.handle || '', { emsiSkills: skills.map(skill => ({ skillSources: skill.skillSources, subCategory: skill.subCategory, diff --git a/src/apps/onboarding/src/redux/reducers/member.ts b/src/apps/onboarding/src/redux/reducers/member.ts index 666b4ddb1..36a482604 100644 --- a/src/apps/onboarding/src/redux/reducers/member.ts +++ b/src/apps/onboarding/src/redux/reducers/member.ts @@ -3,9 +3,13 @@ /* eslint-disable default-param-last */ import { Member } from '~/apps/talent-search/src/lib/models' import { ACTIONS } from '../../config' +import WorkInfo from '../../models/WorkInfo' +import EducationInfo from '../../models/EducationInfo' const initialState: { memberInfo?: Member + works?: WorkInfo[] + educations?: EducationInfo[] } = { } @@ -16,6 +20,16 @@ const memberReducer: any = (state = initialState, action: { type: any; payload: ...state, memberInfo: action.payload, } + case ACTIONS.MEMBER.SET_WORKS: + return { + ...state, + works: action.payload, + } + case ACTIONS.MEMBER.SET_EDUCATIONS: + return { + ...state, + educations: action.payload, + } case ACTIONS.MEMBER.UPDATE_MEMBER_SKILLS: { if (!state.memberInfo) { return state diff --git a/src/apps/onboarding/src/services/members.ts b/src/apps/onboarding/src/services/members.ts new file mode 100644 index 000000000..c35aa9548 --- /dev/null +++ b/src/apps/onboarding/src/services/members.ts @@ -0,0 +1,15 @@ +import { getAsync, putAsync } from '~/libs/core/lib/xhr/xhr-functions/xhr.functions' +import { profile } from '~/libs/core/lib/profile/profile-functions/profile-store/profile-endpoint.config' +import { Member } from '~/apps/talent-search/src/lib/models' + +export async function getMemberInfo(handle: string): Promise { + return getAsync(profile(handle)) +} + +export async function getMemberTraits(handle: string): Promise { + return getAsync(`${profile(handle)}/traits`) +} + +export async function putMemberInfo(handle: string, data: any): Promise { + return putAsync(profile(handle || ''), data) +} diff --git a/src/apps/onboarding/src/styles/global/_flex.scss b/src/apps/onboarding/src/styles/global/_flex.scss index 14274549e..b4d3ec1a5 100644 --- a/src/apps/onboarding/src/styles/global/_flex.scss +++ b/src/apps/onboarding/src/styles/global/_flex.scss @@ -4,6 +4,12 @@ .flex-none { flex: none; } +.gap-20 { + gap: 20px; +} +.gap-50 { + gap: 50px; +} .d-flex { display: flex; diff --git a/src/apps/onboarding/src/styles/global/_index.scss b/src/apps/onboarding/src/styles/global/_index.scss index 31505310d..e89490574 100644 --- a/src/apps/onboarding/src/styles/global/_index.scss +++ b/src/apps/onboarding/src/styles/global/_index.scss @@ -1,2 +1,10 @@ @import "flex"; @import "layout"; + +.react-datepicker-popper { + z-index: 9999 !important; +} + +.full-width { + width: 100%; +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index cef4eff49..971508b90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1504,6 +1504,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.13.8", "@babel/runtime@^7.6.3": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec" + integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/runtime@^7.17.8", "@babel/runtime@^7.7.6": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" @@ -2721,11 +2728,23 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== +"@popperjs/core@^2.11.6", "@popperjs/core@^2.9.2": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + "@remix-run/router@1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.0.5.tgz#d5c65626add4c3c185a89aa5bd38b1e42daec075" integrity sha512-my0Mycd+jruq/1lQuO5LBB6WTlL/e8DTCYWp44DfMTDcXz8DcTlgF0ISaLsGewt+ctHN+yA8xMq3q/N7uWJPug== +"@restart/hooks@^0.4.7": + version "0.4.9" + resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.9.tgz#ad858fb39d99e252cccce19416adc18fc3f18fcb" + integrity sha512-3BekqcwB6Umeya+16XPooARn4qEPW6vNvwYnlofIYe6h9qG1/VeD7UvShCWx11eFz5ELYmwIEshz+MkPX3wjcQ== + dependencies: + dequal "^2.0.2" + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -4435,6 +4454,16 @@ dependencies: "@types/react" "*" +"@types/react-datepicker@^4.11.2": + version "4.11.2" + resolved "https://registry.yarnpkg.com/@types/react-datepicker/-/react-datepicker-4.11.2.tgz#44abfc6379faa58d28eb5730a058d6a389d40eac" + integrity sha512-ELYyX3lb3K1WltqdlF1hbnaDGgzlF6PIR5T4W38cSEcfrQDIrPE+Ioq5pwRe/KEJ+ihHMjvTVZQkwJx0pWMNHQ== + dependencies: + "@popperjs/core" "^2.9.2" + "@types/react" "*" + date-fns "^2.0.1" + react-popper "^2.2.5" + "@types/react-dom@^18.0.0", "@types/react-dom@^18.0.6": version "18.0.9" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.9.tgz#ffee5e4bfc2a2f8774b15496474f8e7fe8d0b504" @@ -4504,6 +4533,15 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@>=16.9.11": + version "18.2.14" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.14.tgz#fa7a6fecf1ce35ca94e74874f70c56ce88f7a127" + integrity sha512-A0zjq+QN/O0Kpe30hA1GidzyFjatVvrpIvWLxD+xv67Vt91TWWgco9IvrJBkeyHm1trGaFS/FSGqPlhyeZRm0g== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/redux-actions@2.6.2": version "2.6.2" resolved "https://registry.yarnpkg.com/@types/redux-actions/-/redux-actions-2.6.2.tgz#5956d9e7b9a644358e2c0610f47b1fa3060edc21" @@ -4648,6 +4686,11 @@ resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.10.tgz#f9763dc0933f8324920afa9c0790308eedf55ca7" integrity sha512-t1yxFAR2n0+VO6hd/FJ9F2uezAZVWHLmpmlJzm1eX03+H7+HsuTAp7L8QJs+2pQCfWkP1+EXsGK9Z9v7o/qPVQ== +"@types/warning@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52" + integrity sha512-t/Tvs5qR47OLOr+4E9ckN8AmP2Tf16gWq+/qA4iUGS/OOyHVO8wv2vjJuX8SNOUTJyWb+2t7wJm6cXILFnOROA== + "@types/ws@^8.5.1": version "8.5.3" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" @@ -7374,18 +7417,18 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date-fns@^2.29.1: - version "2.29.3" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" - integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== - -date-fns@^2.30.0: +date-fns@^2.0.1, date-fns@^2.24.0, date-fns@^2.30.0: version "2.30.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== dependencies: "@babel/runtime" "^7.21.0" +date-fns@^2.29.1: + version "2.29.3" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" + integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== + date-format@4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.3.tgz#f63de5dc08dc02efd8ef32bf2a6918e486f35873" @@ -7718,7 +7761,7 @@ dom-converter@^0.2.0: dependencies: utila "~0.4" -dom-helpers@^5.0.1: +dom-helpers@^5.0.1, dom-helpers@^5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== @@ -15113,6 +15156,18 @@ react-date-range@^1.1.3: react-list "^0.8.13" shallow-equal "^1.2.1" +react-datepicker@^4.14.1: + version "4.14.1" + resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.14.1.tgz#f9f7925d0a40e3195048bbd22a5faa635765163d" + integrity sha512-uiPfjD+25KI5WOfCAXlzQgSLyksTagk3wwKn1KGBdF19YtybFDregRmcoNNGveQHAbT10SJZdCvk/8pbc7zxJg== + dependencies: + "@popperjs/core" "^2.9.2" + classnames "^2.2.6" + date-fns "^2.24.0" + prop-types "^15.7.2" + react-onclickoutside "^6.12.2" + react-popper "^2.3.0" + react-dev-utils@^12.0.1: version "12.0.1" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73" @@ -15205,6 +15260,11 @@ react-error-overlay@^6.0.11: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== +react-fast-compare@^3.0.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" + integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== + react-fast-compare@^3.1.1: version "3.2.0" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" @@ -15304,11 +15364,38 @@ react-markdown@8.0.6: unist-util-visit "^4.0.0" vfile "^5.0.0" +react-onclickoutside@^6.12.2: + version "6.13.0" + resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz#e165ea4e5157f3da94f4376a3ab3e22a565f4ffc" + integrity sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A== + react-only-when@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/react-only-when/-/react-only-when-1.0.2.tgz#a8a79b48dd6cfbd91ddc710674a94153e88039d3" integrity sha512-agE6l3L6bqaVuwNtjihTQ36M+VBfPS63KOzcNL4ZTmlwSxQPvhzIqmBWfiol0/wLYmKxCcBqgXkEJpvj5Kob8Q== +react-overlays@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-5.2.1.tgz#49dc007321adb6784e1f212403f0fb37a74ab86b" + integrity sha512-GLLSOLWr21CqtJn8geSwQfoJufdt3mfdsnIiQswouuQ2MMPns+ihZklxvsTDKD3cR2tF8ELbi5xUsvqVhR6WvA== + dependencies: + "@babel/runtime" "^7.13.8" + "@popperjs/core" "^2.11.6" + "@restart/hooks" "^0.4.7" + "@types/warning" "^3.0.0" + dom-helpers "^5.2.0" + prop-types "^15.7.2" + uncontrollable "^7.2.1" + warning "^4.0.3" + +react-popper@^2.2.5, react-popper@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba" + integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q== + dependencies: + react-fast-compare "^3.0.1" + warning "^4.0.2" + react-redux-toastr@^7.6.10: version "7.6.10" resolved "https://registry.yarnpkg.com/react-redux-toastr/-/react-redux-toastr-7.6.10.tgz#e76c6a7a19162584494b878173fb97d0b557c45a" @@ -17952,6 +18039,16 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +uncontrollable@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738" + integrity sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ== + dependencies: + "@babel/runtime" "^7.6.3" + "@types/react" ">=16.9.11" + invariant "^2.2.4" + react-lifecycles-compat "^3.0.4" + unfetch@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" @@ -18387,6 +18484,13 @@ walker@^1.0.7, walker@^1.0.8: dependencies: makeerror "1.0.12" +warning@^4.0.2, warning@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + watchpack@^2.2.0, watchpack@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"