Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import { useState } from "react";
import { useRouter } from "next/navigation";
import { Alert } from "@mui/material";
import { UUID } from "crypto";
import SimpleModal from "@bciers/components/modal/SimpleModal";
import cancelAccessRequest from "@/administration/app/components/userOperators/cancelAccessRequest";
import {
useValidationErrors,
handleApiResponse,
} from "@bciers/components/validationErrors";

interface CancelAccessRequestProps {
userOperatorId: UUID;
Expand All @@ -15,30 +18,27 @@ export default function CancelAccessRequest({
userOperatorId,
}: Readonly<CancelAccessRequestProps>) {
const router = useRouter();
const [error, setError] = useState(undefined);
const [modalOpen, setModalOpen] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);

const { setErrors, renderedErrors } = useValidationErrors({
config: {},
});

const handleCancelAccessRequest = async () => {
setIsSubmitting(true);
try {
const response = await cancelAccessRequest(userOperatorId);
setErrors(undefined);

const response = await cancelAccessRequest(userOperatorId);
setIsSubmitting(false);

if (typeof response !== "boolean" && response?.error) {
setError(response.error as any);
setModalOpen(false);
setIsSubmitting(false);
return;
}
router.push("/select-operator");
} catch (err: any) {
setError(
err?.message || "An unexpected error occurred. Please try again.",
);
const isSuccess = handleApiResponse(response, setErrors);
if (!isSuccess) {
setModalOpen(false);
} finally {
setIsSubmitting(false);
return;
}

router.push("/select-operator");
};

return (
Expand All @@ -54,16 +54,16 @@ export default function CancelAccessRequest({
>
Are you sure you want to cancel this request?
</SimpleModal>

<button
className="button-link mt-8 text-[#D8292F]"
aria-label="Cancel Access Request"
onClick={() => setModalOpen(true)}
>
Cancel Request
</button>
<div className="min-h-6 mt-4">
{error && <Alert severity="error">{error}</Alert>}
</div>

<div className="min-h-6 mt-4">{renderedErrors}</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
"use client";

import { useState } from "react";
import type { ReactNode } from "react";
import Button from "@mui/material/Button";
import { actionHandler } from "@bciers/actions";
import { useRouter } from "next/navigation";
import { Alert } from "@mui/material";
import { ghgRegulatorEmail } from "@bciers/utils/src/urls";
import {
useValidationErrors,
handleApiResponse,
} from "@bciers/components/validationErrors";
import { validationUIConfig } from "@/administration/app/components/validationErrors/config";
import type { ValidationKey } from "@/administration/app/components/validationErrors/types";

interface RequestAccessButtonProps {
operatorId: number;
Expand All @@ -20,9 +23,12 @@ export default function RequestAccessButton({
isAdminRequest = false,
}: Readonly<RequestAccessButtonProps>) {
const router = useRouter();
const [error, setError] = useState<string | undefined>(undefined);
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);

const { setErrors, renderedErrors } = useValidationErrors<ValidationKey>({
config: validationUIConfig,
});

const label = isAdminRequest
? "Request administrator access"
: "Request access";
Expand All @@ -31,49 +37,33 @@ export default function RequestAccessButton({
isAdminRequest ? "request-admin-access" : "request-access"
}`;

const errorMessageJSX: ReactNode = (
<>
{error}
{error?.includes(
"Your business BCeID does not have access to this operator.",
) && (
<a className="ps-1" href={ghgRegulatorEmail}>
ghgregulator@gov.bc.ca
</a>
)}
</>
);

const handleRequestAccess = async () => {
setIsSubmitting(true);
setErrors(undefined);

const response = await actionHandler(endpointUrl, "POST", "");
if (response?.error) {
setError(response.error);
setIsSubmitting(false);
return;
}
// admin vs. subsequent access request conditionality handled in component: select-operator/(request-access)/received/[step]/[id]
setIsSubmitting(false);

const isSuccess = handleApiResponse(response, setErrors);
if (!isSuccess) return;

router.push(
`/select-operator/received/request-access/${operatorId}?title=${operatorName}`,
`/select-operator/received/request-access/${operatorId}?title=${encodeURIComponent(
operatorName,
)}`,
);
};

return (
<>
<div className="min-h-6 flex justify-center">
{error && (
<Alert severity="error" className="w-2/3">
{errorMessageJSX}
</Alert>
)}
</div>
<div className="min-h-6 flex justify-center w-full">{renderedErrors}</div>
<Button
className="my-10"
sx={{ textTransform: "none" }} //to remove uppercase text
sx={{ textTransform: "none" }}
aria-label={label ?? "Request Access"}
color="primary"
variant="contained"
onClick={async () => handleRequestAccess()}
onClick={handleRequestAccess}
disabled={isSubmitting}
>
{label}
Expand Down
125 changes: 65 additions & 60 deletions bciers/apps/administration/app/components/contacts/ContactForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import { useParams, useRouter } from "next/navigation";
import { useSessionRole } from "@bciers/utils/src/sessionUtils";
import { actionHandler } from "@bciers/actions";
import useKey from "@bciers/utils/src/useKey";
import {
useValidationErrors,
handleApiResponse,
} from "@bciers/components/validationErrors";

interface Props {
schema: any;
Expand All @@ -35,34 +39,77 @@ export default function ContactForm({
allowEdit,
}: Readonly<Props>) {
const router = useRouter();
const [error, setError] = useState(undefined);
const [formState, setFormState] = useState(formData ?? {});
const [isCreatingState, setIsCreatingState] = useState(isCreating);
const [key, resetKey] = useKey();
const params = useParams();
const role = useSessionRole();
const [key, resetKey] = useKey();

const [formState, setFormState] = useState<ContactFormData>(
formData ?? ({} as ContactFormData),
);
const [isCreatingState, setIsCreatingState] = useState(Boolean(isCreating));
const [modalOpen, setModalOpen] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const params = useParams();

const handleClickDelete = () => {
setModalOpen(true);
};
const { setErrors, renderedErrors } = useValidationErrors({
config: {},
});

const hasPlacesAssigned = Boolean(
formData.places_assigned && formData.places_assigned.length > 0,
);

const handleArchiveContact = async () => {
setIsSubmitting(true);
setErrors(undefined);

const response = await archiveContact(params.contactId as string);
if (response?.error) {
setError(response.error as any);
setIsSubmitting(false);

const isSuccess = handleApiResponse(response, setErrors);
if (!isSuccess) {
setModalOpen(false);
setIsSubmitting(false);
return;
}

router.push("/contacts?from_deletion=true");
return;
};

const hasPlacesAssigned =
formData.places_assigned && formData.places_assigned.length > 0;
const handleSubmit = async (data: { formData?: any }) => {
setErrors(undefined);
const updatedFormData = { ...formState, ...data.formData };
setFormState(updatedFormData);

const method = isCreatingState ? "POST" : "PUT";
const contactId = formState.id ?? params.contactId;
const endpoint = isCreatingState
? "registration/contacts"
: `registration/contacts/${contactId}`;
const pathToRevalidate = isCreatingState
? "/contacts"
: `/contacts/${contactId}`;

const response = await actionHandler(endpoint, method, pathToRevalidate, {
body: JSON.stringify(data.formData),
});

const isSuccess = handleApiResponse(response, setErrors);
if (!isSuccess) {
return { error: response.error };
}

const activeId = response.id ?? contactId;

if (isCreatingState) {
setIsCreatingState(false);
setFormState((prev) => ({ ...prev, id: activeId }));
} else {
resetKey();
}

const titleQuery =
`${response.first_name ?? ""} ${response.last_name ?? ""}`.trim();
router.replace(`/contacts/${activeId}?contacts_title=${titleQuery}`);
};

return (
<>
Expand All @@ -80,9 +127,10 @@ export default function ContactForm({
? "Before you can delete this contact, please remove them from the places they are assigned. If they are the only one assigned, you must replace them with another contact in the assigned place."
: "Please confirm that you would like to delete this contact."}
</SimpleModal>

<SingleStepTaskListForm
key={key}
error={error}
errors={renderedErrors}
schema={schema}
uiSchema={contactsUiSchema}
formData={formState}
Expand All @@ -95,52 +143,9 @@ export default function ContactForm({
showDeleteButton={
!isCreatingState && role === FrontEndRoles.INDUSTRY_USER_ADMIN
}
handleDelete={handleClickDelete}
handleDelete={() => setModalOpen(true)}
deleteButtonText="Delete Contact"
onSubmit={async (data: { formData?: any }) => {
setError(undefined);
const updatedFormData = { ...formState, ...data.formData };
setFormState(updatedFormData);

const method = isCreatingState ? "POST" : "PUT";
const endpoint = isCreatingState
? "registration/contacts"
: `registration/contacts/${formState.id}`;
const pathToRevalidate = isCreatingState
? "/contacts"
: `/contacts/${formState.id}`;
const body = {
...data.formData,
};

const response = await actionHandler(
endpoint,
method,
pathToRevalidate,
{
body: JSON.stringify(body),
},
);

if (response.error) {
setError(response.error);
return { error: response.error };
}

if (isCreatingState) {
setIsCreatingState(false);
setFormState((prevState) => ({
...prevState,
id: response.id,
}));
} else {
resetKey();
}
const replaceUrl = `/contacts/${
method === "POST" ? response.id : formState.id
}?contacts_title=${response.first_name} ${response.last_name}`;
router.replace(replaceUrl);
}}
onSubmit={handleSubmit}
onCancel={() => router.replace("/contacts")}
/>
</>
Expand Down
Loading
Loading