diff --git a/client/package-lock.json b/client/package-lock.json index c1ee6f386..e537097f6 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14,6 +14,7 @@ "@material-ui/lab": "4.0.0-alpha.49", "@material-ui/pickers": "3.2.10", "@react-keycloak/web": "2.1.4", + "axios": "1.3.6", "classnames": "2.2.6", "cross-fetch": "3.1.5", "dayjs": "1.11.1", @@ -4392,8 +4393,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -4460,6 +4460,29 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz", + "integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -5889,7 +5912,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -7100,7 +7122,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -9265,7 +9286,6 @@ "version": "1.15.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", - "dev": true, "funding": [ { "type": "individual", @@ -12324,9 +12344,9 @@ "dev": true }, "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -12921,7 +12941,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -12930,7 +12949,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -15811,6 +15829,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -25583,8 +25606,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "at-least-node": { "version": "1.0.0", @@ -25631,6 +25653,28 @@ "integrity": "sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==", "dev": true }, + "axios": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz", + "integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -26800,7 +26844,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -27768,8 +27811,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "depd": { "version": "2.0.0", @@ -29474,8 +29516,7 @@ "follow-redirects": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", - "dev": true + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" }, "for-in": { "version": "1.0.2", @@ -31848,9 +31889,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "jsonfile": { @@ -32354,14 +32395,12 @@ "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "requires": { "mime-db": "1.52.0" } @@ -34705,6 +34744,11 @@ "ipaddr.js": "1.9.1" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", diff --git a/client/package.json b/client/package.json index d882db340..e6ce9af7d 100644 --- a/client/package.json +++ b/client/package.json @@ -18,6 +18,7 @@ "@material-ui/lab": "4.0.0-alpha.49", "@material-ui/pickers": "3.2.10", "@react-keycloak/web": "2.1.4", + "axios": "1.3.6", "classnames": "2.2.6", "cross-fetch": "3.1.5", "dayjs": "1.11.1", diff --git a/client/src/components/employer-form/index.js b/client/src/components/employer-form/index.js index d6a6f99e8..032819049 100644 --- a/client/src/components/employer-form/index.js +++ b/client/src/components/employer-form/index.js @@ -14,9 +14,9 @@ import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft'; import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight'; import Typography from '@material-ui/core/Typography'; -import { API_URL, EmployerFormSchema, Routes, ToastStatus } from '../../constants'; +import { EmployerFormSchema, Routes } from '../../constants'; import { useToast } from '../../hooks'; -import { scrollUp, mapObjectProps } from '../../utils'; +import { scrollUp, mapObjectProps, getErrorMessage } from '../../utils'; import { Card, Button } from '../generic'; import { BeforeYouBegin } from './BeforeYouBegin'; import { OperatorInfo } from './OperatorInfo'; @@ -24,6 +24,7 @@ import { SiteInfo } from './SiteInfo'; import { ExpressionOfInt } from './ExpressionOfInt'; import { WorkforceBaseline } from './WorkforceBaseline'; import { Review } from './Review'; +import { axiosInstance } from '../../services/api'; const steps = [ 'Before You Begin', @@ -150,19 +151,11 @@ export const Form = ({ hideCollectionNotice, initialValues, isDisabled }) => { const handleSubmit = async (values) => { setSubmitLoading(true); - const response = await fetch(`${API_URL}/api/v1/employer-form`, { - method: 'POST', - headers: { Accept: 'application/json', 'Content-type': 'application/json' }, - body: JSON.stringify(mapBaselineList(values)), - }); - - if (response.ok) { + try { + await axiosInstance.post('/employer-form', mapBaselineList(values)); history.push(Routes.EmployerConfirmation, { formValues: values }); - } else { - openToast({ - status: ToastStatus.Error, - message: response.error || response.statusText || 'Server error', - }); + } catch (e) { + openToast(getErrorMessage(e)); } setSubmitLoading(false); diff --git a/client/src/components/modal-forms/AllocationForm.js b/client/src/components/modal-forms/AllocationForm.js index 25971cee5..76cac578b 100644 --- a/client/src/components/modal-forms/AllocationForm.js +++ b/client/src/components/modal-forms/AllocationForm.js @@ -5,8 +5,9 @@ import { makeStyles } from '@material-ui/core/styles'; import { RenderTextField, RenderDateField } from '../fields'; import { Field, Formik, Form as FormikForm } from 'formik'; import { CreateAllocationSchema, ToastStatus } from '../../constants'; -import { createAllocation, updateAllocation } from '../../services/allocations'; +import { createAllocation, updateAllocation } from '../../services'; import { useToast } from '../../hooks'; +import { getErrorMessage } from '../../utils'; const useStyles = makeStyles(() => ({ formButton: { @@ -40,10 +41,12 @@ export const AllocationForm = ({ onSubmit, onClose, open, content, isNew, siteId site_id: parseInt(siteId, 10), } : { allocation: allocation.allocation }; - const response = await (isNew - ? createAllocation(allocationJson) - : updateAllocation(content.allocationId, allocationJson)); - if (response.ok) { + + try { + await (isNew + ? createAllocation(allocationJson) + : updateAllocation(content.allocationId, allocationJson)); + openToast({ status: ToastStatus.Success, message: isNew @@ -51,11 +54,8 @@ export const AllocationForm = ({ onSubmit, onClose, open, content, isNew, siteId : `Phase allocation has been successfully updated`, }); await onSubmit(); - } else { - openToast({ - status: ToastStatus.Error, - message: response.error || response.statusText || 'Server error', - }); + } catch (e) { + openToast(getErrorMessage(e)); } }; return ( diff --git a/client/src/components/modal-forms/BulkAllocationForm.js b/client/src/components/modal-forms/BulkAllocationForm.js index a1b5f4ee9..7e946761d 100644 --- a/client/src/components/modal-forms/BulkAllocationForm.js +++ b/client/src/components/modal-forms/BulkAllocationForm.js @@ -1,14 +1,13 @@ import React, { useState } from 'react'; -import { Button, Dialog } from '../generic'; +import { Field, Formik, Form as FormikForm } from 'formik'; import { Box, Typography } from '@material-ui/core'; import Alert from '@material-ui/lab/Alert'; import { makeStyles } from '@material-ui/core/styles'; +import { Button, Dialog } from '../generic'; import { BulkAllocationSchema, ToastStatus } from '../../constants'; import { RenderTextField, RenderSelectField, RenderCheckbox } from '../fields'; -import { Field, Formik, Form as FormikForm } from 'formik'; -import { formatLongDate } from '../../utils/date'; -import { addEllipsisMask } from '../../utils'; -import { bulkAllocation } from '../../services/allocations'; +import { addEllipsisMask, formatLongDate, getErrorMessage } from '../../utils'; +import { bulkAllocation } from '../../services'; import { useToast } from '../../hooks'; const useStyles = makeStyles(() => ({ @@ -69,18 +68,16 @@ export const BulkAllocationForm = ({ onClose, afterSubmit, open, sites, phases = allocation: values.allocation, phase_id: values.phase_id, }; - const response = await bulkAllocation(payload); - if (response.ok) { + try { + await bulkAllocation(payload); + await afterSubmit(); openToast({ status: ToastStatus.Success, message: `${sites.length} sites have been assigned allocations`, }); - } else { - openToast({ - status: ToastStatus.Error, - message: response.error || response.statusText || 'Server error', - }); + } catch (e) { + openToast(getErrorMessage(e)); } }; diff --git a/client/src/components/modal-forms/EditParticipantForm.js b/client/src/components/modal-forms/EditParticipantForm.js index c6f44848f..cfe6f4583 100644 --- a/client/src/components/modal-forms/EditParticipantForm.js +++ b/client/src/components/modal-forms/EditParticipantForm.js @@ -1,13 +1,17 @@ import React from 'react'; -import store from 'store'; import Grid from '@material-ui/core/Grid'; import { Button } from '../generic'; import { Box } from '@material-ui/core'; import { RenderTextField, RenderSelectField } from '../fields'; import { Field, Formik, Form as FormikForm } from 'formik'; -import { API_URL, EditParticipantFormSchema, participantStatus } from '../../constants'; +import { EditParticipantFormSchema, participantStatus } from '../../constants'; +import { axiosInstance } from '../../services/api'; +import { useToast } from '../../hooks'; +import { getErrorMessage } from '../../utils'; export const EditParticipantForm = ({ initialValues, onClose, submissionCallback }) => { + const { openToast } = useToast(); + const participantIsHired = initialValues.latestStatuses.some( (status) => status.status === participantStatus.HIRED ); @@ -36,21 +40,15 @@ export const EditParticipantForm = ({ initialValues, onClose, submissionCallback } }); values.history = initialValues.history ? [history, ...initialValues.history] : [history]; - const response = await fetch(`${API_URL}/api/v1/participant`, { - method: 'PATCH', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(values), - }); - if (response.ok) { + try { + await axiosInstance.patch('/participant', values); if (submissionCallback) { submissionCallback(); } onClose(); + } catch (e) { + openToast(getErrorMessage(e)); } }} > diff --git a/client/src/components/modal-forms/NewParticipantForm.js b/client/src/components/modal-forms/NewParticipantForm.js index e3d6fa04e..0ccfe8e34 100644 --- a/client/src/components/modal-forms/NewParticipantForm.js +++ b/client/src/components/modal-forms/NewParticipantForm.js @@ -1,14 +1,19 @@ import React from 'react'; -import store from 'store'; import Grid from '@material-ui/core/Grid'; import { Button } from '../generic'; import { Box } from '@material-ui/core'; -import { RenderDateField, RenderCheckbox, RenderTextField, RenderSelectField } from '../fields'; +import { + RenderAutocomplete, + RenderDateField, + RenderCheckbox, + RenderTextField, + RenderSelectField, +} from '../fields'; import { Field, Formik, Form as FormikForm } from 'formik'; -import { getTodayDate } from '../../utils'; -import { RenderAutocomplete } from '../fields/RenderAutocomplete'; -import { API_URL, ExternalHiredParticipantSchema, ToastStatus } from '../../constants'; +import { getErrorMessage, getTodayDate } from '../../utils'; +import { ExternalHiredParticipantSchema } from '../../constants'; import { useToast } from '../../hooks'; +import { axiosInstance } from '../../services/api'; const newParticipantInitialValues = { firstName: '', @@ -29,26 +34,15 @@ export const NewParticipantForm = ({ submissionCallback, onClose, sites }) => { const { openToast } = useToast(); const handleExternalHire = async (participantInfo) => { - const response = await fetch(`${API_URL}/api/v1/new-hired-participant`, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(participantInfo), - }); - - if (response.ok) { + try { + await axiosInstance.post('/new-hired-participant', participantInfo); onClose(); submissionCallback(); - } else { - openToast({ - status: ToastStatus.Error, - message: response.error || response.statusText || 'Server error', - }); + } catch (e) { + openToast(getErrorMessage(e)); } }; + return ( ({ formButton: { @@ -44,25 +45,21 @@ export const NewSiteDialog = ({ onSubmit, onClose, open }) => { ...site, siteId: parseInt(site.siteId), }; - const response = await createSite(siteJson); - if (response.ok) { + try { + await createSite(siteJson); openToast({ status: ToastStatus.Success, message: `Site '${site.siteName}' added successfully`, }); await onSubmit(); - } else { - const error = await response.json(); - if (error.status && error.status === 'Duplicate') { + } catch (e) { + if (e.response.data.status === 'Duplicate') { openToast({ status: ToastStatus.Error, message: 'Duplicate site ID', }); } else { - openToast({ - status: ToastStatus.Error, - message: response.error || response.statusText || 'Server error', - }); + openToast(getErrorMessage(e)); } } }; diff --git a/client/src/components/modal-forms/PhaseDialog.js b/client/src/components/modal-forms/PhaseDialog.js index ffda112fe..6c97cfb93 100644 --- a/client/src/components/modal-forms/PhaseDialog.js +++ b/client/src/components/modal-forms/PhaseDialog.js @@ -9,7 +9,7 @@ import { Field, Formik, Form as FormikForm } from 'formik'; import { CreatePhaseSchema, ToastStatus } from '../../constants'; import { createPhase, updatePhase, fetchPhases } from '../../services/phases'; import { useToast } from '../../hooks'; -import { formatLongDate } from '../../utils/date'; +import { formatLongDate, getErrorMessage } from '../../utils'; const useStyles = makeStyles(() => ({ formButton: { @@ -72,18 +72,15 @@ export const PhaseDialog = ({ onSubmit, onClose, open, content, isNew = false }) start_date: phase.startDate, end_date: phase.endDate, }; - const response = await (isNew ? createPhase(phaseJson) : updatePhase(content.id, phaseJson)); - if (response.ok) { + try { + await (isNew ? createPhase(phaseJson) : updatePhase(content.id, phaseJson)); openToast({ status: ToastStatus.Success, message: `Phase '${phase.phaseName}' ${isNew ? 'created' : 'updated'} successfully`, }); await onSubmit(); - } else { - openToast({ - status: ToastStatus.Error, - message: response.error || response.statusText || 'Server error', - }); + } catch (e) { + openToast(getErrorMessage(e)); } }; diff --git a/client/src/components/modal-forms/UserManagementDialog.js b/client/src/components/modal-forms/UserManagementDialog.js index 60406ff1c..52b60be2f 100644 --- a/client/src/components/modal-forms/UserManagementDialog.js +++ b/client/src/components/modal-forms/UserManagementDialog.js @@ -9,7 +9,7 @@ import { healthAuthorities, regionLabelsMap, } from '../../constants'; -import { RenderMultiSelectField, RenderSelectField, RenderCheckbox } from '../../components/fields'; +import { RenderMultiSelectField, RenderSelectField, RenderCheckbox } from '../fields'; import { Dialog } from '../generic'; import { UserManagementViewForm } from './UserManagementViewForm'; diff --git a/client/src/components/participant-details/track-graduation.js b/client/src/components/participant-details/track-graduation.js index 23f8f6c7d..625d7fabb 100644 --- a/client/src/components/participant-details/track-graduation.js +++ b/client/src/components/participant-details/track-graduation.js @@ -1,24 +1,22 @@ +import React, { useEffect, useState } from 'react'; import { Grid, Typography, Dialog, Box } from '@material-ui/core'; -import { Button } from '../../components/generic/Button'; +import dayjs from 'dayjs'; import { ArchiveHiredParticipantForm } from '../modal-forms'; -import store from 'store'; import { ManageGraduationForm } from '../modal-forms/ManageGraduationForm'; import { AssignCohortForm } from '../modal-forms/AssignCohort'; -import React, { useEffect, useState } from 'react'; -import dayjs from 'dayjs'; import { createPostHireStatus, fetchUserNotifications } from '../../services'; import { - postHireStatuses, - participantStatus, + Role, ToastStatus, - API_URL, ArchiveHiredParticipantSchema, - Role, + postHireStatuses, + participantStatus, } from '../../constants'; import { AuthContext } from '../../providers'; import { useToast } from '../../hooks'; import { formatCohortDate } from '../../utils'; -import { CheckPermissions } from '../generic'; +import { Button, CheckPermissions } from '../generic'; +import { axiosInstance } from '../../services/api'; // Helper function to call archive participant service const handleArchive = async ( participantId, @@ -28,29 +26,23 @@ const handleArchive = async ( siteId = null, currentStatusId = null ) => { - const response = await fetch(`${API_URL}/api/v1/employer-actions`, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify({ - participantId, - status: 'archived', - data: additional, - site: siteId, - currentStatusId, - }), - }); - if (response.ok) { - fetchUserNotifications(dispatchFunction); + const data = { + participantId, + status: 'archived', + data: additional, + site: siteId, + currentStatusId, + }; + + try { + await axiosInstance.post('/employer-actions', data); + fetchUserNotifications(dispatchFunction); openToast({ status: ToastStatus.Info, message: 'Participant Archived', }); - } else { + } catch (e) { openToast({ status: ToastStatus.Error, message: 'Unable to archive participant', diff --git a/client/src/components/participant-form/EmailSubmissionForm.js b/client/src/components/participant-form/EmailSubmissionForm.js index b6f7ebb91..ff2a9b6a1 100644 --- a/client/src/components/participant-form/EmailSubmissionForm.js +++ b/client/src/components/participant-form/EmailSubmissionForm.js @@ -7,8 +7,9 @@ import NotificationsActiveIcon from '@material-ui/icons/NotificationsActive'; import { RenderTextField } from '../fields'; import { Button } from '../generic'; -import { EmailSubmissionSchema, API_URL, ToastStatus } from '../../constants'; +import { EmailSubmissionSchema, ToastStatus } from '../../constants'; import { useToast } from '../../hooks'; +import { axiosInstance } from '../../services/api'; const useStyles = makeStyles((theme) => ({ submissionInputContainer: { @@ -53,24 +54,23 @@ const useStyles = makeStyles((theme) => ({ })); const addEmailToWaitlist = async (email, openToast) => { - const resp = await fetch(`${API_URL}/api/v1/participants/waitlist`, { - headers: { Accept: 'application/json', 'Content-type': 'application/json' }, - body: JSON.stringify({ email }), - method: 'POST', - }); - if (resp.ok) { + try { + await axiosInstance.post('/participants/waitlist', { email }); + openToast({ status: ToastStatus.Success, message: 'Your email has been added to the list. You will be notified when submissions are open.', }); - } else if (resp.status === 409) { - openToast({ - status: ToastStatus.Info, - message: 'Your email address was already on the list.', - }); - } else { - throw new Error(); + } catch (e) { + if (e.status === 409) { + openToast({ + status: ToastStatus.Info, + message: 'Your email address was already on the list.', + }); + } else { + throw new Error(); + } } }; diff --git a/client/src/components/participant-form/index.js b/client/src/components/participant-form/index.js index e8c394a26..ba3125b35 100644 --- a/client/src/components/participant-form/index.js +++ b/client/src/components/participant-form/index.js @@ -4,20 +4,15 @@ import Grid from '@material-ui/core/Grid'; import { Formik, Form as FormikForm } from 'formik'; import { useHistory } from 'react-router-dom'; -import { - API_URL, - ParticipantFormSchema, - ParticipantEditFormSchema, - Routes, - ToastStatus, -} from '../../constants'; +import { ParticipantFormSchema, ParticipantEditFormSchema, Routes } from '../../constants'; import { useToast } from '../../hooks'; -import { scrollUp } from '../../utils'; +import { getErrorMessage, scrollUp } from '../../utils'; import { Button } from '../generic'; import { Summary } from './Summary'; import { Fields } from './Fields'; import { isNonPortalHire } from '../../utils/isNonPortalHire'; +import { axiosInstance } from '../../services/api'; export const Form = ({ initialValues, @@ -52,22 +47,14 @@ export const Form = ({ return; } setSubmitLoading(true); - const response = await fetch(`${API_URL}/api/v1/participants`, { - method: 'POST', - headers: { Accept: 'application/json', 'Content-type': 'application/json' }, - body: JSON.stringify(values), - }); - if (response.ok) { - const { id } = await response.json(); + try { + const { id } = await axiosInstance.post('/participants', values); + history.push(Routes.ParticipantConfirmation, { formValues: values, id }); - } else { - openToast({ - status: ToastStatus.Error, - message: response.error || response.statusText || 'Server error', - }); + } catch (e) { + openToast(getErrorMessage(e)); } - setSubmitLoading(false); }; diff --git a/client/src/pages/private/EOIView.js b/client/src/pages/private/EOIView.js index 31a866b28..c6755ff2f 100644 --- a/client/src/pages/private/EOIView.js +++ b/client/src/pages/private/EOIView.js @@ -2,14 +2,17 @@ import React, { useEffect, useMemo, useState } from 'react'; import { useHistory } from 'react-router-dom'; import Grid from '@material-ui/core/Grid'; import { Box, Typography } from '@material-ui/core'; -import store from 'store'; import { Button, Page, Table, CheckPermissions } from '../../components/generic'; -import { Routes, regionLabelsMap, API_URL, healthAuthoritiesFilter, Role } from '../../constants'; +import { Routes, regionLabelsMap, healthAuthoritiesFilter, Role } from '../../constants'; import { TableFilter } from '../../components/generic/TableFilter'; import { AuthContext } from '../../providers'; -import { sortObjects } from '../../utils'; +import { getErrorMessage, sortObjects } from '../../utils'; +import { axiosInstance } from '../../services/api'; +import { useToast } from '../../hooks'; export default () => { + const { openToast } = useToast(); + const [order, setOrder] = useState('asc'); const [isLoadingData, setLoadingData] = useState(false); const [fetchedRows, setFetchedRows] = useState([]); @@ -82,19 +85,14 @@ export default () => { const getEOIs = async () => { setLoadingData(true); - const response = await fetch(`${API_URL}/api/v1/employer-form`, { - headers: { - Accept: 'application/json', - 'Content-type': 'application/json', - Authorization: `Bearer ${store.get('TOKEN')}`, - }, - method: 'GET', - }); let rows = []; - if (response.ok) { - const { data } = await response.json(); - rows = filterData(data); + try { + const { data } = await axiosInstance.get('/employer-form'); + + rows = filterData(data?.data); + } catch (e) { + openToast(getErrorMessage(e)); } setFetchedRows(rows); diff --git a/client/src/pages/private/EOIViewDetails.js b/client/src/pages/private/EOIViewDetails.js index 144b39d63..7f08b78d1 100644 --- a/client/src/pages/private/EOIViewDetails.js +++ b/client/src/pages/private/EOIViewDetails.js @@ -2,27 +2,27 @@ import React, { useEffect, useState } from 'react'; import Grid from '@material-ui/core/Grid'; import { Page, CheckPermissions } from '../../components/generic'; import { Form } from '../../components/employer-form'; -import { scrollUp } from '../../utils'; -import store from 'store'; -import { API_URL, Role } from '../../constants'; +import { Role } from '../../constants'; +import { getErrorMessage, scrollUp } from '../../utils'; +import { axiosInstance } from '../../services/api'; +import { useToast } from '../../hooks'; export default ({ match }) => { + const { openToast } = useToast(); + const [user, setUser] = useState(undefined); const expressionID = match.params.id; useEffect(() => { const fetchDetails = async () => { - const response = await fetch(`${API_URL}/api/v1/employer-form/${expressionID}`, { - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - }, - method: 'GET', - }); - - if (response.ok) { - setUser(await response.json()); + try { + const { data } = await axiosInstance.get(`/employer-form/${expressionID}`); + setUser(data); + } catch (e) { + openToast(getErrorMessage(e)); } }; + fetchDetails(); }, [expressionID]); diff --git a/client/src/pages/private/PSIView.js b/client/src/pages/private/PSIView.js index f99c29d42..4e9468023 100644 --- a/client/src/pages/private/PSIView.js +++ b/client/src/pages/private/PSIView.js @@ -1,14 +1,14 @@ import React, { useState, useEffect, useMemo, lazy } from 'react'; -import store from 'store'; - import { Box, Typography } from '@material-ui/core'; import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline'; import { Page, CheckPermissions, Button, Dialog } from '../../components/generic'; import { PSIForm, CohortForm } from '../../components/modal-forms'; -import { ToastStatus, API_URL, NewCohortSchema, Role } from '../../constants'; +import { ToastStatus, NewCohortSchema, Role } from '../../constants'; import { AuthContext } from '../../providers'; import { useToast } from '../../hooks'; +import { getErrorMessage } from '../../utils'; +import { axiosInstance } from '../../services/api'; const PSITable = lazy(() => import('./PSITable')); @@ -50,37 +50,21 @@ export default () => { }; const handleAddCohort = async (cohort) => { - const response = await fetch(`${API_URL}/api/v1/psi/${selectedPSI}/cohorts/`, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(cohort), - }); - - if (response.ok) { + try { + await axiosInstance.post(`/psi/${selectedPSI}/cohorts`, cohort); setActiveModalForm(null); - } else { - openToast({ - status: ToastStatus.Error, - message: response.error || response.statusText || 'Server error', - }); + } catch (e) { + openToast(getErrorMessage(e)); } }; // Hooks useEffect(() => { const fetchCohorts = async () => { - const response = await fetch(`${API_URL}/api/v1/cohorts`, { - headers: { Authorization: `Bearer ${store.get('TOKEN')}` }, - method: 'GET', - }); - if (response.ok) { - const data = await response.json(); + try { + const { data } = await axiosInstance.get('/cohorts'); setCohorts(data); - } else { + } catch (e) { setCohorts([]); } }; @@ -90,12 +74,9 @@ export default () => { useEffect(() => { const fetchPSIs = async () => { - const response = await fetch(`${API_URL}/api/v1/psi`, { - headers: { Authorization: `Bearer ${store.get('TOKEN')}` }, - method: 'GET', - }); - if (response.ok) { - const data = await response.json(); + try { + const { data } = await axiosInstance.get('/psi'); + const mappedData = data.map((row) => { const rowCohorts = cohorts.filter((cohort) => cohort.psi_id === row.id); // To calculate available_seats, we filter out the expired cohorts and @@ -111,7 +92,7 @@ export default () => { }; }); setPSIs(mappedData); - } else { + } catch { setPSIs([]); } }; diff --git a/client/src/pages/private/ParticipantDetailsView.js b/client/src/pages/private/ParticipantDetailsView.js index db7b0ee03..42bde4346 100644 --- a/client/src/pages/private/ParticipantDetailsView.js +++ b/client/src/pages/private/ParticipantDetailsView.js @@ -31,7 +31,7 @@ import { EditRosStartDateDialog, EditRosSiteDialog, } from '../../components/participant-details'; -import { keyedString } from '../../utils'; +import { getErrorMessage, keyedString } from '../../utils'; const useStyles = makeStyles((theme) => ({ root: { @@ -197,32 +197,24 @@ export default () => { const handleEditRosField = async (values) => { const EDIT_ERROR_MESSAGE = 'Unable to update the field'; try { - const response = await updateRosStatus(actualParticipant?.id, values); - if (response.ok) { - openToast({ - status: ToastStatus.Success, - message: `${rosKeyMap[editFormField]?.label} is successfully updated`, - }); - fetchData({ - setParticipant, - setPSIList, - setActualParticipant, - setDisableAssign, - setError, - id, - }); - } else { - throw new Error( - response.message || response.error || response.statusText || EDIT_ERROR_MESSAGE - ); - } - } catch (err) { + await updateRosStatus(actualParticipant?.id, values); + openToast({ - status: ToastStatus.Error, - message: err?.message || EDIT_ERROR_MESSAGE, + status: ToastStatus.Success, + message: `${rosKeyMap[editFormField]?.label} is successfully updated`, }); - } + fetchData({ + setParticipant, + setPSIList, + setActualParticipant, + setDisableAssign, + setError, + id, + }); + } catch (e) { + openToast(getErrorMessage(e, EDIT_ERROR_MESSAGE)); + } handleEditRosFieldClose(); }; diff --git a/client/src/pages/private/ParticipantEOI.js b/client/src/pages/private/ParticipantEOI.js index 24e5d2bc8..59f285962 100644 --- a/client/src/pages/private/ParticipantEOI.js +++ b/client/src/pages/private/ParticipantEOI.js @@ -6,16 +6,13 @@ import { red } from '@material-ui/core/colors'; import LinearProgress from '@material-ui/core/LinearProgress'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; -import store from 'store'; import { Routes, ToastStatus } from '../../constants'; import { Page } from '../../components/generic'; import { Form } from '../../components/participant-form'; -import { API_URL } from '../../constants'; import { useToast } from '../../hooks'; import { Dialog } from '../../components/generic'; - -const rootUrl = `${API_URL}/api/v1/participant-user/participant`; +import { axiosInstance } from '../../services/api'; // Custom UI const DeleteButton = withStyles((theme) => ({ @@ -40,15 +37,8 @@ const useStyles = makeStyles((theme) => ({ // Helper methods const fetchParticipant = async (id) => { try { - const response = await fetch(`${rootUrl}/${id}`, { - method: 'GET', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - }); - const [participant] = await response.json(); + const { data } = await axiosInstance.get(`/participant-user/participant/${id}`); + const [participant] = data; return participant || null; } catch { return null; @@ -66,43 +56,31 @@ const updateParticipant = async (values, id) => { postalCode, postalCodeFsa, }; - const resp = await fetch(`${rootUrl}/${id}`, { - method: 'PATCH', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(body), - }); - return resp.ok; + try { + await axiosInstance.patch(`/participant-user/participant/${id}`, body); + return true; + } catch { + return false; + } }; const withdrawParticipant = async (id) => { - const resp = await fetch(`${rootUrl}/${id}/withdraw`, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify({}), - }); - return resp.ok; + try { + await axiosInstance.post(`/participant-user/participant/${id}/withdraw`, {}); + return true; + } catch { + return false; + } }; const submitConfirmInterestRequest = async (id) => { - const resp = await fetch(`${rootUrl}/${id}/reconfirm_interest`, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify({}), - }); - return resp.ok; + try { + await axiosInstance.post(`/participant-user/participant/${id}/reconfirm_interest`, {}); + return true; + } catch { + return false; + } }; const isHiredParticipant = (participant) => diff --git a/client/src/pages/private/ParticipantLanding.js b/client/src/pages/private/ParticipantLanding.js index e8ef9f32f..7efbc9344 100644 --- a/client/src/pages/private/ParticipantLanding.js +++ b/client/src/pages/private/ParticipantLanding.js @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; +import dayjs from 'dayjs'; import { Grid, Card, @@ -11,18 +12,15 @@ import { CircularProgress, } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import store from 'store'; +import isNil from 'lodash/isNil'; import { Page } from '../../components/generic'; -import { API_URL, Routes, ToastStatus } from '../../constants'; import { PEOIWithdrawalDialogForm } from '../../components/modal-forms/PEOIWithdrawalDialogForm'; -import { genericConfirm } from '../../constants/validation'; +import { genericConfirm, Routes } from '../../constants'; import { IndigenousDeclarationForm } from '../../components/modal-forms/IndigenousDeclarationForm'; -import isNil from 'lodash/isNil'; import { useToast } from '../../hooks'; import ParticipantLandingEmpty from './ParticipantLandingEmpty'; -import dayjs from 'dayjs'; - -const rootUrl = `${API_URL}/api/v1/participant-user/participant`; +import { axiosInstance } from '../../services/api'; +import { getErrorMessage } from '../../utils'; const useStyles = makeStyles(() => ({ rootContainer: { @@ -73,15 +71,8 @@ const useStyles = makeStyles(() => ({ const getParticipants = async () => { try { - const response = await fetch(`${API_URL}/api/v1/participant-user/participants`, { - method: 'GET', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - }); - return response.json(); + const { data } = await axiosInstance.get(`/participant-user/participants`); + return data; } catch { return []; } @@ -103,14 +94,7 @@ export default () => { }; const submitWithdrawal = async (values) => { if (values.confirmed) { - await fetch(`${API_URL}/api/v1/participant-user/withdraw`, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - }); + await axiosInstance.post(`/participant-user/withdraw`); } await getParticipants().then((items) => afterInterestFetch(items)); setShowWithdrawDialog(false); @@ -158,28 +142,20 @@ export default () => { .map(([identity, selected]) => (selected ? identity : null)) .filter(Boolean); - const response = await fetch(`${rootUrl}/batch`, { - method: 'PATCH', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify({ + try { + const data = { isIndigenous: values.isIndigenous, indigenousIdentities: identities, - }), - }); - if (response.ok) { + }; + + await axiosInstance.patch('/participant-user/participant/batch', data); + await getParticipants().then((items) => { afterInterestFetch(items); }); setHideIndigenousIdentityForm(true); - } else { - openToast({ - status: ToastStatus.Error, - message: response.error || response.statusText || 'Server error', - }); + } catch (e) { + openToast(getErrorMessage(e)); } }; diff --git a/client/src/pages/private/SiteViewDetails.js b/client/src/pages/private/SiteViewDetails.js index 097917858..fca909771 100644 --- a/client/src/pages/private/SiteViewDetails.js +++ b/client/src/pages/private/SiteViewDetails.js @@ -3,11 +3,11 @@ import { makeStyles } from '@material-ui/core/styles'; import { Box, Chip, Grid, Link, Typography } from '@material-ui/core'; import { Button, Card, Dialog, Page, CheckPermissions } from '../../components/generic'; -import { scrollUp, addEllipsisMask } from '../../utils'; import { Routes, MAX_LABEL_LENGTH, Role } from '../../constants'; +import { addEllipsisMask, getErrorMessage, scrollUp } from '../../utils'; import { EditSiteForm } from '../../components/modal-forms'; import { useToast } from '../../hooks'; -import { ToastStatus, EditSiteSchema } from '../../constants'; +import { EditSiteSchema } from '../../constants'; import { SiteDetailTabContext } from '../../providers'; import { fetchSitePhases } from '../../services/phases'; import { fetchSiteParticipants, updateSite, fetchSite } from '../../services/site'; @@ -74,46 +74,38 @@ export default ({ match }) => { const id = match.params.id; const handleSiteEdit = async (site) => { - const response = await updateSite(site, id); - setIsLoading(false); - if (response.ok) { + try { + await updateSite(site, id); + setIsLoading(false); setActiveModalForm(null); fetchDetails(); - } else { - openToast({ - status: ToastStatus.Error, - message: response.error || response.statusText || 'Server error', - }); + } catch (e) { + openToast(getErrorMessage(e)); } }; const fetchDetails = useCallback(async () => { setHasFetched(true); setIsLoading(true); - const response = await fetchSite(id); - if (response.ok) { - const site = await response.json(); + try { + const site = await fetchSite(id); const phases = await fetchSitePhases(site.id); - const participants = await fetchSiteParticipants(columnIDs, site.siteId); - const { hired, withdrawn } = await participants.json(); + const { hired, withdrawn } = await fetchSiteParticipants(columnIDs, site.siteId); const hiredParticipants = mapSiteParticipantsDataToRow(hired, columnIDs); const withdrawnParticipants = mapSiteParticipantsDataToRow(withdrawn, columnIDs); setIsLoading(false); - return setSite({ + setSite({ ...site, hiredParticipants, withdrawnParticipants, phases, }); - } else { - openToast({ - status: ToastStatus.Error, - message: response.error || response.statusText || 'Server error', - }); - setIsLoading(false); - return setSite({}); + } catch (e) { + openToast(getErrorMessage(e)); + setSite({}); } - }, [id, setSite, openToast, setIsLoading]); + setIsLoading(false); + }, [id, openToast]); useEffect(() => { if (!hasFetched) fetchDetails(); diff --git a/client/src/pages/private/UserMigrationTable.js b/client/src/pages/private/UserMigrationTable.js index 3eadad8ab..2018018c0 100644 --- a/client/src/pages/private/UserMigrationTable.js +++ b/client/src/pages/private/UserMigrationTable.js @@ -1,12 +1,12 @@ import React, { useEffect, useState } from 'react'; -import store from 'store'; import { sortObjects } from '../../utils'; import { Button, Table } from '../../components/generic'; -import { API_URL, ToastStatus } from '../../constants'; +import { ToastStatus } from '../../constants'; import { useToast } from '../../hooks'; import { mapTableRows } from '../../utils/user-management-table-util'; import { UserMigrationDialog } from '../../components/modal-forms/UserMigrationDialog'; +import { axiosInstance } from '../../services/api'; const columns = [ { id: 'username', name: 'Username' }, @@ -29,24 +29,16 @@ export const UserMigrationTable = () => { const handleSubmit = async (values) => { setLoading(true); - const response = await fetch(`${API_URL}/api/v1/user-migrations/${selectedUser.id}`, { - method: 'PATCH', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(values), - }); + try { + await axiosInstance.patch(`/user-migrations/${selectedUser.id}`, values); - if (response.ok) { setUserMigrationModalOpen(false); await fetchUserMigrations(); openToast({ status: ToastStatus.Success, message: 'User to be migrated has been updated', }); - } else { + } catch { openToast({ status: ToastStatus.Error, message: 'User to be migrated has failed to update', @@ -71,18 +63,15 @@ export const UserMigrationTable = () => { const fetchUserMigrations = async () => { setLoading(true); - const response = await fetch(`${API_URL}/api/v1/user-migrations`, { - headers: { Authorization: `Bearer ${store.get('TOKEN')}` }, - method: 'GET', - }); - - if (response.ok) { - const { data } = await response.json(); + try { + const { + data: { data }, + } = await axiosInstance.get('/user-migrations'); const rows = data.map((row) => { return mapTableRows(columns, row, userMigrationOptionsButton(row)); }); setUsers(rows); - } else { + } catch { setUsers([]); } setLoading(false); diff --git a/client/src/pages/private/UserView.js b/client/src/pages/private/UserView.js index 8eb8dc17a..23105cec2 100644 --- a/client/src/pages/private/UserView.js +++ b/client/src/pages/private/UserView.js @@ -2,17 +2,17 @@ import React, { useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import Grid from '@material-ui/core/Grid'; import { Box, Typography } from '@material-ui/core'; -import store from 'store'; import { useToast } from '../../hooks'; import { Button, Page, Table, CheckPermissions } from '../../components/generic'; -import { Routes, ToastStatus, API_URL, Role } from '../../constants'; +import { Routes, ToastStatus, Role } from '../../constants'; import { useLocation } from 'react-router-dom'; import { sortObjects } from '../../utils'; import { UserMigrationTable } from './UserMigrationTable'; import { mapTableRows } from '../../utils/user-management-table-util'; import { UserManagementDialog } from '../../components/modal-forms/UserManagementDialog'; import { FeatureFlaggedComponent, flagKeys } from '../../services'; +import { axiosInstance } from '../../services/api'; const columns = [ { id: 'firstName', name: 'First Name' }, @@ -48,27 +48,24 @@ export default () => { const handleSubmit = async (values) => { setLoadingData(true); + const isUserAccessRequest = location.pathname === Routes.UserPending; - const response = await fetch( - `${API_URL}/api/v1/${isUserAccessRequest ? 'approve-user' : 'user-details'}`, - { - headers: { - 'Content-type': 'application/json', - Authorization: `Bearer ${store.get('TOKEN')}`, - }, - method: isUserAccessRequest ? 'POST' : 'PATCH', - body: JSON.stringify({ ...values, userId: selectedUserId, username: selectedUserName }), - } - ); - setLoadingData(false); - if (response.ok) { + + try { + const method = isUserAccessRequest ? 'post' : 'patch'; + const uri = isUserAccessRequest ? 'approve-user' : 'user-details'; + const payload = { ...values, userId: selectedUserId, username: selectedUserName }; + + await axiosInstance[method](uri, payload); + + setLoadingData(false); setModalOpen(false); fetchUsers({ pending: isUserAccessRequest }); openToast({ status: ToastStatus.Success, message: isUserAccessRequest ? 'Access request approved' : 'User updated', }); - } else { + } catch { openToast({ status: ToastStatus.Error, message: isUserAccessRequest ? 'Access request approval failed' : 'User update failed', @@ -84,17 +81,14 @@ export default () => { setSelectedUserName(row.username); if (!pending) { setLoadingData(true); - const response = await fetch(`${API_URL}/api/v1/user-details?id=${row.id}`, { - headers: { Authorization: `Bearer ${store.get('TOKEN')}` }, - method: 'GET', - }); - setLoadingData(false); - if (response.ok) { - const details = await response.json(); + try { + const { data: details } = await axiosInstance.get(`/user-details?id=${row.id}`); + setLoadingData(false); setSelectedUserDetails(details); setModalOpen(true); return; - } + } catch {} + setLoadingData(false); } setModalOpen(true); }} @@ -107,19 +101,20 @@ export default () => { const fetchUsers = async ({ pending }) => { setLoadingData(true); - const response = await fetch(`${API_URL}/api/v1/${pending ? 'pending-users' : 'users'}`, { - headers: { Authorization: `Bearer ${store.get('TOKEN')}` }, - method: 'GET', - }); - if (response.ok) { - const { data } = await response.json(); + try { + const uri = pending ? 'pending-users' : 'users'; + + const { + data: { data = [] }, + } = await axiosInstance.get(uri); + const rows = data.map((row) => { return mapTableRows(columns, row, userManagementOptionsButton(row, pending)); }); setRows(rows); setIsPendingRequests(rows.length > 0); - } else { + } catch { setRows([]); setIsPendingRequests(false); } @@ -128,15 +123,10 @@ export default () => { const fetchSites = async () => { setLoadingData(true); - const response = await fetch(`${API_URL}/api/v1/employer-sites/user`, { - headers: { Authorization: `Bearer ${store.get('TOKEN')}` }, - method: 'GET', - }); - - if (response.ok) { - const { data } = await response.json(); - setSites(data); - } + try { + const { data } = await axiosInstance.get('/employer-sites/user'); + setSites(data.data); + } catch {} setLoadingData(false); }; diff --git a/client/src/pages/public/ConfirmInterest.js b/client/src/pages/public/ConfirmInterest.js index 7f662593d..3b1fa0af5 100644 --- a/client/src/pages/public/ConfirmInterest.js +++ b/client/src/pages/public/ConfirmInterest.js @@ -1,21 +1,17 @@ -import { Page } from '../../components/generic'; import React, { useEffect, useState } from 'react'; import { makeStyles } from '@material-ui/core/styles'; -import { API_URL } from '../../constants'; - import * as qs from 'querystring'; - import { Typography, Icon } from '@material-ui/core'; -import { Button } from '../../components/generic/Button'; -import { Card } from '../../components/generic/Card'; import Box from '@material-ui/core/Box'; +import { Button, Card, Page } from '../../components/generic'; import { confirmInterestDefault, confirmInterestError, confirmInterestSuccess, confirmInterestLoading, } from '../../constants/confirmInterestConstants'; +import { axiosInstance } from '../../services/api'; const useStyles = makeStyles((theme) => { return { @@ -56,16 +52,13 @@ export default (props) => { useEffect(() => { const checkId = async () => { - const res = await fetch(`${API_URL}/api/v1/participants/confirm-interest?id=${query.id}`, { - method: 'GET', - }); - setTimeout(() => { - if (res.ok) { - setState(confirmInterestDefault); - } else { - setState(confirmInterestError); - } - }, 500); + let confirm = confirmInterestDefault; + try { + await axiosInstance.get(`/participants/confirm-interest?id=${query.id}`); + } catch { + confirm = confirmInterestError; + } + setTimeout(() => setState(confirm), 500); }; if (!query.id) { props.history.push('/'); @@ -76,16 +69,13 @@ export default (props) => { const handleCheckToken = async () => { setState(confirmInterestLoading); - const res = await fetch(`${API_URL}/api/v1/participants/confirm-interest?id=${query.id}`, { - method: 'POST', - }); - setTimeout(() => { - if (res.ok) { - setState(confirmInterestSuccess); - } else { - setState(confirmInterestError); - } - }, 500); + let confirm = confirmInterestSuccess; + try { + await axiosInstance.post(`/participants/confirm-interest?id=${query.id}`); + } catch { + confirm = confirmInterestError; + } + setTimeout(() => setState(confirm), 500); }; if (!state) return null; diff --git a/client/src/routes/index.js b/client/src/routes/index.js index e5675c24e..5c6a6d0ac 100644 --- a/client/src/routes/index.js +++ b/client/src/routes/index.js @@ -5,9 +5,10 @@ import { useKeycloak, KeycloakProvider } from '@react-keycloak/web'; import store from 'store'; import Keycloak from 'keycloak-js'; -import { API_URL, Routes } from '../constants'; +import { Routes } from '../constants'; import { AuthContext } from '../providers'; import PhaseView from '../pages/private/PhaseView'; +import { axiosInstance } from '../services/api'; const ParticipantLogin = lazy(() => import('../pages/public/ParticipantLogin')); const Admin = lazy(() => import('../pages/private/Admin')); const UserView = lazy(() => import('../pages/private/UserView')); @@ -79,45 +80,34 @@ export default () => { const getUserInfo = async (token) => { dispatch({ action: AuthContext.USER_LOADING }); - const response = await fetch(`${API_URL}/api/v1/user`, { - headers: { - Authorization: `Bearer ${token}`, - }, - method: 'GET', - }); - if (response.ok) { - const payload = await response.json(); + try { + const { data: payload } = await axiosInstance.get('/user'); + await checkRolesAndRefreshToken(payload.roles); dispatch({ type: AuthContext.USER_LOADED, payload }); - } else { + } catch { // logout, remove token if it's invalid dispatch({ type: AuthContext.USER_LOADED, payload: null }); store.remove('TOKEN'); await keycloakInfo.logout({ redirectUri: window.location.origin }); } }; + const getKeycloakInfo = async () => { - const response = await fetch(`${API_URL}/api/v1/keycloak-realm-client-info`, { - headers: { - Accept: 'application/json', - 'Content-type': 'application/json', - }, - method: 'GET', - }); + const { data } = await axiosInstance.get('/keycloak-realm-client-info'); - const result = await response.json(); setKeycloakInfo( new Keycloak({ - realm: result.realm, - url: result.url, - clientId: result.clientId, + realm: data.realm, + url: data.url, + clientId: data.clientId, }) ); // Saving all received env variables to store - if (result.envVariables && Object.keys(result.envVariables).length > 0) { - for (const key of Object.keys(result.envVariables)) { - store.set(key, result.envVariables[key]); + if (data.envVariables && Object.keys(data.envVariables).length > 0) { + for (const key of Object.keys(data.envVariables)) { + store.set(key, data.envVariables[key]); } } }; diff --git a/client/src/services/allocations.js b/client/src/services/allocations.js index 4997baba1..8d930531f 100644 --- a/client/src/services/allocations.js +++ b/client/src/services/allocations.js @@ -1,41 +1,16 @@ -import store from 'store'; -import { API_URL } from '../constants'; +import { axiosInstance } from './api'; export const createAllocation = async (payload) => { - const response = await fetch(`${API_URL}/api/v1/allocation`, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(payload), - }); - return response; + const { data } = await axiosInstance.post('/allocation', payload); + return data; }; export const updateAllocation = async (allocationId, payload) => { - const response = await fetch(`${API_URL}/api/v1/allocation/${allocationId}`, { - method: 'PATCH', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(payload), - }); - return response; + const { data } = await axiosInstance.patch(`/allocation/${allocationId}`, payload); + return data; }; export const bulkAllocation = async (payload) => { - const response = await fetch(`${API_URL}/api/v1/allocation/bulk-allocation`, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(payload), - }); - return response; + const { data } = await axiosInstance.post('/allocation/bulk-allocation', payload); + return data; }; diff --git a/client/src/services/api.js b/client/src/services/api.js new file mode 100644 index 000000000..1cdad8fa1 --- /dev/null +++ b/client/src/services/api.js @@ -0,0 +1,20 @@ +import axios from 'axios'; +import store from 'store'; + +export const axiosInstance = axios.create({ + baseURL: `${process.env.REACT_APP_API_URL ?? ''}/api/v1`, +}); + +axiosInstance.interceptors.request.use((config) => { + const headers = { + Accept: 'application/json', + 'Content-Type': 'application/json', + }; + + const token = store.get('TOKEN'); + + if (token) { + headers.Authorization = `Bearer ${store.get('TOKEN')}`; + } + return { ...config, headers }; +}); diff --git a/client/src/services/notifications.js b/client/src/services/notifications.js index 12f84042a..73ab13f4f 100644 --- a/client/src/services/notifications.js +++ b/client/src/services/notifications.js @@ -1,17 +1,7 @@ -import store from 'store'; -import { API_URL } from '../constants'; +import { axiosInstance } from './api'; export const fetchUserNotifications = async (dispatchFunction) => { - const response = await fetch(`${API_URL}/api/v1/user-notifications`, { - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - }, - method: 'GET', - }); + const { data } = await axiosInstance.get('/user-notifications'); - if (response.ok) { - const notifications = await response.json(); - dispatchFunction(notifications); - } - return response; + dispatchFunction(data); }; diff --git a/client/src/services/participant.js b/client/src/services/participant.js index 506baac35..c04475253 100644 --- a/client/src/services/participant.js +++ b/client/src/services/participant.js @@ -1,5 +1,5 @@ -import store from 'store'; -import { API_URL, postHireStatuses } from '../constants'; +import { postHireStatuses } from '../constants'; +import { axiosInstance } from './api'; export const getCohortPsiName = (cohort = {}) => cohort?.cohort_name && cohort.psi?.institute_name @@ -40,17 +40,10 @@ const getDefaultStatusAndSite = (statusArr) => { // Fetch Participant export const fetchParticipant = async ({ id }) => { - const url = `${API_URL}/api/v1/participant/details/${id}`; - const resp = await fetch(url, { - method: 'GET', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - }); - if (resp.ok) { - const { participant } = await resp.json(); + try { + const { data } = await axiosInstance.get(`/participant/details/${id}`); + + const { participant } = data; const cohort = await fetchParticipantCohort({ id }); const postHireStatus = await fetchParticipantPostHireStatus({ id }); @@ -62,60 +55,37 @@ export const fetchParticipant = async ({ id }) => { postHireStatusLabel: getPostHireStatusLabel(postHireStatus), ...getDefaultStatusAndSite(participant.latestStatuses), }; - } else { + } catch { throw new Error('Unable to load participant'); } }; export const fetchParticipantById = async (participantId) => { - const url = `${API_URL}/api/v1/participant?id=${participantId}`; - const response = await fetch(url, { - headers: { - Accept: 'application/json', - 'Content-type': 'application/json', - Authorization: `Bearer ${store.get('TOKEN')}`, - }, - method: 'GET', - }); - if (response.ok) { - return response.json(); - } else { + try { + const { data } = await axiosInstance.get(`/participant?id=${participantId}`); + return data; + } catch { throw new Error('Unable to load participant'); } }; export const fetchParticipantPostHireStatus = async ({ id }) => { - const url = `${API_URL}/api/v1/post-hire-status/participant/${id}`; - const resp = await fetch(url, { - method: 'GET', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - }); - if (resp.ok) { - const statuses = await resp.json(); + try { + const { data } = await axiosInstance.get(`/post-hire-status/participant/${id}`); + // Return latest status which is the first element in the array - return statuses[0]; - } else { + return data[0]; + } catch { throw new Error(`Unable to fetch participant's post hire status`); } }; export const fetchParticipantCohort = async ({ id }) => { - const url = `${API_URL}/api/v1/cohorts/assigned-participant/${id}`; - const resp = await fetch(url, { - method: 'GET', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - }); - if (resp.ok) { - return await resp.json(); - } else { + try { + const { data } = await axiosInstance.get(`/cohorts/assigned-participant/${id}`); + + return data; + } catch { throw new Error(`Unable to fetch participant's cohorts details`); } }; @@ -141,23 +111,15 @@ export const updateParticipant = async (values, participant) => { } }); values.history = participant.history ? [history, ...participant.history] : [history]; - const response = await fetch(`${API_URL}/api/v1/participant`, { - method: 'PATCH', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(values), - }); - if (response.ok) { - return await response.json(); - } else { + try { + const { data } = await axiosInstance.patch(`/participant`, values); + return data; + } catch (e) { throw new Error('Unable to update participant', { - status: response.status, - statusText: response.statusText, - cause: response.statusText, + status: e.response.status, + statusText: e.response.statusText, + cause: e.response.statusText, }); } }; @@ -184,129 +146,94 @@ export const getParticipants = async ({ selectedTabStatuses.forEach((status) => { params.append('statusFilters[]', status); }); - const response = await fetch(`${API_URL}/api/v1/participants?${params.toString()}`, { - headers: { - Accept: 'application/json', - 'Content-type': 'application/json', - Authorization: `Bearer ${store.get('TOKEN')}`, - }, - method: 'GET', - }); - if (response.ok) { - return response.json(); - } + try { + const { data } = await axiosInstance.get(`/participants?${params.toString()}`); - throw new Error('Failed to fetch participants'); + return data; + } catch { + throw new Error('Failed to fetch participants'); + } }; export const addParticipantStatus = async ({ participantId, status, additional }) => { const { sites = [], currentStatusId, ...rest } = additional; const [siteObj] = sites; const site = siteObj; - const response = await fetch(`${API_URL}/api/v1/employer-actions`, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify({ participantId, status, data: rest, site, currentStatusId }), - }); - - if (response.ok) { - return response.json(); - } - if (response.status === 400) { - // Try - try { - let errorMessage = ''; - if (response.headers.get('content-type').includes('application/json')) { - const error = (await response.json()) || { message: 'Unknown error' }; - errorMessage = error.message; - } else { - errorMessage = `Failed to add participant status due to server error: ${await response.text()}`; + try { + const payload = { participantId, status, data: rest, site, currentStatusId }; + const { data } = await axiosInstance.post('/employer-actions', payload); + + return data; + } catch (e) { + const { response } = e; + if (e.status === 400) { + // Try + try { + let errorMessage = ''; + if (response.headers.get('content-type').includes('application/json')) { + const error = response.data || { message: 'Unknown error' }; + errorMessage = error.message; + } else { + errorMessage = `Failed to add participant status due to server error: ${await response.text()}`; + } + throw new Error(errorMessage); + } catch (error) { + // Non json response from server + throw new Error(`Failed to add participant status: ${error.message}`); } - throw new Error(errorMessage); - } catch (error) { - // Non json response from server - throw new Error(`Failed to add participant status: ${error.message}`); } + throw new Error('Failed to add participant status', response.error || response.statusText); } - - throw new Error('Failed to add participant status', response.error || response.statusText); }; export const acknowledgeParticipant = async ({ participantId, multiOrgHire, currentStatusId }) => { - const response = await fetch(`${API_URL}/api/v1/employer-actions/acknowledgment`, { - method: 'DELETE', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify({ participantId, multiOrgHire, currentStatusId }), - }); + try { + const payload = { participantId, multiOrgHire, currentStatusId }; + const { data } = await axiosInstance.delete('/employer-actions/acknowledgment', payload); - if (response.ok) { return { - ...(await response.json()), + ...data, success: true, }; + } catch (e) { + const { response } = e; + if (response.status === 400) { + return { + ...response.data, + success: false, + }; + } + throw new Error('Failed to acknowledge participant', response.error || response.statusText); } - if (response.status === 400) { - return { - ...(await response.json()), - success: false, - }; - } - - throw new Error('Failed to acknowledge participant', response.error || response.statusText); }; export const createPostHireStatus = async ({ participantIds, status, data }) => { - const url = `${API_URL}/api/v1/post-hire-status`; - const response = await fetch(url, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify({ - participantIds, - status, - data, - }), - }); - if (response.ok) { - return await response.json(); - } + try { + const payload = { participantIds, status, data }; - throw new Error('Failed to create post-hire status', response.error || response.statusText); + const res = await axiosInstance.post('/post-hire-status', payload); + return res.data; + } catch (e) { + const { response } = e; + throw new Error('Failed to create post-hire status', response.error || response.statusText); + } }; export const archiveParticipant = async (participantId, siteId, additional) => { - const url = `${API_URL}/api/v1/employer-actions/archive`; - const response = await fetch(url, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify({ + try { + const payload = { participantId, site: siteId, data: additional, status: 'archived', - }), - }); + }; - if (response.ok) { - return response; + const { data } = await axiosInstance.post('/employer-actions/archive', payload); + return data; + } catch (e) { + const { response } = e; + throw new Error('Failed to archive participant', response.error || response.statusText); } - - throw new Error('Failed to archive participant', response.error || response.statusText); }; diff --git a/client/src/services/phases.js b/client/src/services/phases.js index b6d812b5b..8c43eb1d0 100644 --- a/client/src/services/phases.js +++ b/client/src/services/phases.js @@ -1,5 +1,5 @@ -import store from 'store'; import { API_URL } from '../constants'; +import { axiosInstance } from './api'; /** * @@ -28,16 +28,10 @@ const mapPhasesResponse = async (data, columns) => { export const fetchPhases = async (queryString = null) => { const url = queryString ? `${API_URL}/api/v1/phase${queryString}` : `${API_URL}/api/v1/phase/`; - const response = await fetch(url, { - headers: { Authorization: `Bearer ${store.get('TOKEN')}` }, - method: 'GET', - }); - - if (response.ok) { - const { data } = await response.json(); - return data; - } - throw response; + const { + data: { data }, + } = await axiosInstance.get(url); + return data; }; export const FetchMappedPhases = async (columns) => { @@ -51,40 +45,21 @@ export const FetchMappedPhases = async (columns) => { }; export const fetchSitePhases = async (siteId) => { - const response = await fetch(`${API_URL}/api/v1/phase/${siteId}`, { - headers: { Authorization: `Bearer ${store.get('TOKEN')}` }, - method: 'GET', - }); - if (response.ok) { - const phases = await response.json(); - return phases.data; + try { + const { + data: { data }, + } = await axiosInstance.get(`/phase/${siteId}`); + return data; + } catch { + return []; } - return []; }; // Note: This should be converted to ISO, ideally export const createPhase = async (phaseJson) => { - const response = await fetch(`${API_URL}/api/v1/phase`, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(phaseJson), - }); - return response; + await axiosInstance.post('/phase', phaseJson); }; export const updatePhase = async (phaseId, phaseJson) => { - const response = await fetch(`${API_URL}/api/v1/phase/${phaseId}`, { - method: 'PATCH', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(phaseJson), - }); - return response; + await axiosInstance.patch(`/phase/${phaseId}`, phaseJson); }; diff --git a/client/src/services/psi-cohort.js b/client/src/services/psi-cohort.js index 06f390563..a05a360fc 100644 --- a/client/src/services/psi-cohort.js +++ b/client/src/services/psi-cohort.js @@ -1,21 +1,14 @@ -import store from 'store'; -import { API_URL } from '../constants'; import { formatCohortDate } from '../utils'; +import { axiosInstance } from './api'; const getCohortAvailAbleSize = (cohort) => cohort.cohort_size - (cohort.participantsCohorts?.length || 0); export const getPsi = async () => { - const response = await fetch(`${API_URL}/api/v1/psi/with-cohorts`, { - method: 'GET', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - }); - if (response.ok) { - const psiList = (await response.json()) || []; + try { + const { data } = await axiosInstance.get('/psi/with-cohorts'); + + const psiList = data || []; return psiList.map((psi) => ({ ...psi, size: @@ -27,23 +20,17 @@ export const getPsi = async () => { availableSize: getCohortAvailAbleSize(cohort), })) || [], })); - } else { + } catch { throw new Error('Unable to load post secondary institutes'); } }; export const assignParticipantWithCohort = async ({ participantId, cohortId }) => { - const response = await fetch(`${API_URL}/api/v1/cohorts/${cohortId}/assign/${participantId}`, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - }); - if (response.ok) { - return await response.json(); - } else { + try { + const { data } = await axiosInstance.post(`/cohorts/${cohortId}/assign/${participantId}`); + + return data; + } catch { throw new Error('Unable to assign cohort'); } }; @@ -59,15 +46,9 @@ export const sortPSI = ({ psiList = [], cohort = {} }) => }); export const fetchPSI = async ({ psiId }) => { - const response = await fetch(`${API_URL}/api/v1/psi/${psiId}`, { - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - }, - method: 'GET', - }); + try { + const { data: psi } = await axiosInstance.get(`/psi/${psiId}`); - if (response.ok) { - const psi = await response.json(); return { id: psi.id, instituteName: psi.institute_name, @@ -76,96 +57,68 @@ export const fetchPSI = async ({ psiId }) => { postalCode: psi.postal_code, city: psi.city, }; + } catch (e) { + const { response } = e; + throw new Error( + response.error || response.statusText || 'Unable to load post secondary institutes' + ); } - - throw new Error( - response.error || response.statusText || 'Unable to load post secondary institutes' - ); }; export const fetchCohorts = async ({ psiId }) => { - const response = await fetch(`${API_URL}/api/v1/psi/${psiId}/cohorts/`, { - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - }, - method: 'GET', - }); + try { + const { data } = await axiosInstance.get(`/psi/${psiId}/cohorts`); - if (response.ok) { - const cohortList = await response.json(); - return cohortList; + return data; + } catch (e) { + const { response } = e; + throw new Error(response.error || response.statusText || 'Unable to load cohorts details'); } - - throw new Error(response.error || response.statusText || 'Unable to load cohorts details'); }; export const fetchCohort = async ({ cohortId }) => { - const response = await fetch(`${API_URL}/api/v1/cohorts/${cohortId}`, { - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - }, - method: 'GET', - }); + try { + const { data } = await axiosInstance.get(`/cohorts/${cohortId}`); - if (response.ok) { - const cohort = await response.json(); - return cohort; + return data; + } catch (e) { + const { response } = e; + throw new Error(response.error || response.statusText || 'Unable to load cohorts details'); } - - throw new Error(response.error || response.statusText || 'Unable to load cohorts details'); }; export const fetchCohortParticipants = async ({ cohortId }) => { - const res = await fetch(`${API_URL}/api/v1/cohorts/${cohortId}/participants`, { - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - }, - method: 'GET', - }); + try { + const { data } = await axiosInstance.get(`/cohorts/${cohortId}/participants`); - if (res.ok) { - const participants = await res.json(); - return participants; + return data; + } catch (e) { + const { response } = e; + throw new Error(response.error || response.statusText || 'Unable to load cohort participants'); } - - throw new Error(res.error || res.statusText || 'Unable to load cohort participants'); }; export const addCohort = async ({ psiId, cohort }) => { - const response = await fetch(`${API_URL}/api/v1/psi/${psiId}/cohorts/`, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(cohort), - }); + try { + const { data } = await axiosInstance.post(`/psi/${psiId}/cohorts`, cohort); - if (response.ok) { - return await response.json(); + return data; + } catch (e) { + const { response } = e; + throw new Error(`Unable to add cohort for error ${response.error || response.statusText}`); } - - throw new Error(`Unable to add cohort for error ${response.error || response.statusText}`); }; export const editCohort = async ({ cohort, cohortId }) => { // Remove id from cohort body - const response = await fetch(`${API_URL}/api/v1/cohorts/${cohortId}`, { - method: 'PATCH', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(cohort), - }); + try { + const { data } = await axiosInstance.patch(`/cohorts/${cohortId}`, cohort); - if (response.ok) { - return await response.json(); + return data; + } catch (e) { + const { response } = e; + throw new Error(`Unable to edit cohort for error: ${response.error || response.statusText}`); } - - throw new Error(`Unable to edit cohort for error: ${response.error || response.statusText}`); }; export const mapCohortToFormData = (cohort) => @@ -185,54 +138,32 @@ export const mapCohortToFormData = (cohort) => */ export const createPSI = async ({ psi }) => { try { - const response = await fetch(`${API_URL}/api/v1/psi`, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(psi), - }); - - if (response.ok) { - return [true, null]; - } else { - if (response.status === 409) { - try { - const errorDetails = await response.json(); - return [ - false, - errorDetails.error || - errorDetails.message || - 'Unable to create psi due to server error', - ]; - } catch { - return [false, 'Unable to create psi due to server error']; - } + await axiosInstance.post('/psi', psi); + + return [true, null]; + } catch (e) { + if (e.response.status === 409) { + try { + const errorDetails = e.response.data; + return [ + false, + errorDetails.error || errorDetails.message || 'Unable to create psi due to server error', + ]; + } catch { + return [false, 'Unable to create psi due to server error']; } - return [false, (await response.text()) || 'Unable to create post secondary institute']; } - } catch (error) { - return [false, `Unable to create PSI due to error: ${error}`]; + return [false, e.response.statusText || 'Unable to create post secondary institute']; } }; export const updatePSI = async ({ id, psi }) => { - const resp = await fetch(`${API_URL}/api/v1/psi/${id}`, { - method: 'PUT', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(psi), - }); - // Decode response - const responseMessage = await resp.text(); - if (resp.ok) { + try { + await axiosInstance.put(`/psi/${id}`, psi); + return [true, null]; + } catch (e) { + return [false, e.response.statusText]; } - return [false, responseMessage]; }; diff --git a/client/src/services/reports.js b/client/src/services/reports.js index cd8a46dbc..fba426f3d 100644 --- a/client/src/services/reports.js +++ b/client/src/services/reports.js @@ -1,6 +1,7 @@ import store from 'store'; import { API_URL } from '../constants'; import { handleReportDownloadResult } from '../utils'; +import { axiosInstance } from './api'; const reportTitleHash = { ros: 'return-of-service-milestones-', @@ -20,16 +21,11 @@ const csvTitle = (reportType, region = null) => { return `${reportTitleHash[reportType]}${new Date().toJSON()}.csv`; }; export const fetchMilestoneReports = async () => { - const response = await fetch(`${API_URL}/api/v1/milestone-report`, { - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - }, - method: 'GET', - }); + try { + const { data } = await axiosInstance.get('/milestone-report'); - if (response.ok) { - return response.json(); - } else { + return data; + } catch { throw new Error('Failed to fetch milestone reports'); } }; diff --git a/client/src/services/return-of-service.js b/client/src/services/return-of-service.js index f9c43a411..eadafc7bf 100644 --- a/client/src/services/return-of-service.js +++ b/client/src/services/return-of-service.js @@ -1,6 +1,5 @@ -import store from 'store'; import dayjs from 'dayjs'; -import { API_URL } from '../constants'; +import { axiosInstance } from './api'; export const createReturnOfServiceStatus = async ({ participantId, data, siteId }) => { const finalBody = { @@ -9,48 +8,33 @@ export const createReturnOfServiceStatus = async ({ participantId, data, siteId date: dayjs(data.date, 'YYYY/MM/DD').toDate(), }; - const url = `${API_URL}/api/v1/ros/participant/${participantId}`; - const response = await fetch(url, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify({ - data: finalBody, - status: 'assigned-same-site', - ...(siteId && { siteId }), - }), - }); - if (response.ok) { - return response.json(); + const payload = { + data: finalBody, + status: 'assigned-same-site', + ...(siteId && { siteId }), + }; + try { + const { data } = await axiosInstance.post(`/ros/participant/${participantId}`, payload); + + return data; + } catch (e) { + const { response } = e; + const message = (await response.text()) || 'Failed to create post-hire status'; + throw new Error(message, response.error || response.statusText); } - - const message = (await response.text()) || 'Failed to create post-hire status'; - throw new Error(message, response.error || response.statusText); }; export const getAllSites = async () => { - const url = `${API_URL}/api/v1/employer-sites`; - const response = await fetch(url, { - method: 'GET', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - }); - - if (response.ok) { - return response.json(); - } else { + try { + const { data } = await axiosInstance.get('/employer-sites'); + return data; + } catch { throw new Error('Failed to fetch all sites'); } }; export const updateRosStatus = async (participantId, newValues, status) => { - let url = `${API_URL}/api/v1/ros/participant/${participantId}`; + let url = `/ros/participant/${participantId}`; if (status) { url += '/change-site'; } @@ -65,22 +49,14 @@ export const updateRosStatus = async (participantId, newValues, status) => { const dateTimestamp = date ? dayjs(date, 'YYYY/MM/DD').toDate() : undefined; const startDateTimestamp = startDate ? dayjs(startDate, 'YYYY/MM/DD').toDate() : undefined; - return fetch(url, { - method: 'PATCH', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify({ - data: { - site, - date: dateTimestamp, - startDate: startDateTimestamp, - employmentType, - positionType, - status, - }, - }), - }); + const data = { + site, + date: dateTimestamp, + startDate: startDateTimestamp, + employmentType, + positionType, + status, + }; + + return axiosInstance.patch(url, { data }); }; diff --git a/client/src/services/site.js b/client/src/services/site.js index 84dd56246..53c8a5a47 100644 --- a/client/src/services/site.js +++ b/client/src/services/site.js @@ -1,90 +1,56 @@ -import store from 'store'; -import { API_URL } from '../constants'; +import { axiosInstance } from './api'; export const fetchSite = async (siteId) => { - const response = await fetch(`${API_URL}/api/v1/employer-sites/${siteId}`, { - headers: { Authorization: `Bearer ${store.get('TOKEN')}` }, - method: 'GET', - }); - return response; + const { data } = await axiosInstance.get(`/employer-sites/${siteId}`); + return data; }; export const fetchRegionSiteRows = async (columns) => { - const response = await fetch(`${API_URL}/api/v1/employer-sites/region`, { - headers: { Authorization: `Bearer ${store.get('TOKEN')}` }, - method: 'GET', - }); - return mapSiteRowsResponse(response, columns); + const { + data: { data }, + } = await axiosInstance.get('/employer-sites/region'); + return mapSiteRowsResponse(data, columns); }; export const fetchSiteRows = async (columns) => { - const response = await fetch(`${API_URL}/api/v1/employer-sites/user`, { - headers: { Authorization: `Bearer ${store.get('TOKEN')}` }, - method: 'GET', - }); - return mapSiteRowsResponse(response, columns); + const { + data: { data }, + } = await axiosInstance.get('/employer-sites/user'); + return mapSiteRowsResponse(data, columns); }; /** * - * @param {*} response API Request response + * @param {*} data list of sites * @param {*} columns Which columns we're expected to map to * @returns */ -const mapSiteRowsResponse = async (response, columns) => { - if (response.ok) { - const { data } = await response.json(); - const rowsData = data.map((row) => { - // Pull all relevant props from row based on columns constant - const mappedRow = columns.reduce( - (accumulator, column) => ({ - ...accumulator, - [column.id]: row[column.id], - }), - {} - ); - // Add additional props (user ID, button) to row - return { - ...mappedRow, - id: row.id, - }; - }); - return rowsData; - } else { - return []; - } +const mapSiteRowsResponse = async (data, columns) => { + return data.map((row) => { + // Pull all relevant props from row based on columns constant + const mappedRow = columns.reduce( + (accumulator, column) => ({ + ...accumulator, + [column.id]: row[column.id], + }), + {} + ); + // Add additional props (user ID, button) to row + return { + ...mappedRow, + id: row.id, + }; + }); }; export const createSite = async (siteJson) => { - const response = await fetch(`${API_URL}/api/v1/employer-sites`, { - method: 'POST', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(siteJson), - }); - return response; + return axiosInstance.post('/employer-sites', siteJson); }; export const updateSite = async (payload, siteId) => { - const response = await fetch(`${API_URL}/api/v1/employer-sites/${siteId}`, { - method: 'PATCH', - headers: { - Authorization: `Bearer ${store.get('TOKEN')}`, - Accept: 'application/json', - 'Content-type': 'application/json', - }, - body: JSON.stringify(payload), - }); - return response; + return axiosInstance.patch(`/employer-sites/${siteId}`, payload); }; export const fetchSiteParticipants = async (columnIDs, siteId) => { - const response = await fetch(`${API_URL}/api/v1/employer-sites/${siteId}/participants`, { - headers: { Authorization: `Bearer ${store.get('TOKEN')}` }, - method: 'GET', - }); - - return response; + const { data } = await axiosInstance.get(`/employer-sites/${siteId}/participants`); + return data; }; diff --git a/client/src/utils/get-toast-message.js b/client/src/utils/get-toast-message.js new file mode 100644 index 000000000..4343f797e --- /dev/null +++ b/client/src/utils/get-toast-message.js @@ -0,0 +1,6 @@ +import { ToastStatus } from '../constants'; + +export const getErrorMessage = (e, msg) => { + let message = e.response.error || e.response?.statusText || msg || 'Server error'; + return { status: ToastStatus.Error, message }; +}; diff --git a/client/src/utils/index.js b/client/src/utils/index.js index b4b7afce8..7d553ced0 100644 --- a/client/src/utils/index.js +++ b/client/src/utils/index.js @@ -8,3 +8,4 @@ export * from './prettifyStatus'; export * from './gen-util'; export * from './csvReports'; export * from './keycloak-util'; +export * from './get-toast-message';