251 lines
9.9 KiB
TypeScript
251 lines
9.9 KiB
TypeScript
import { Alert, AppLayout, BreadcrumbGroup, Button, Container, Flashbar, FlashbarProps, Form, FormField, Header, Input, Link, SpaceBetween } from "@cloudscape-design/components";
|
|
import { useLoaderData, useSearchParams } from "react-router-dom";
|
|
import { DoorResponse } from "../types/DoorResponse";
|
|
import { useForm } from "react-hook-form";
|
|
import { InputTokenGroup } from "../components/InputTokenGroup";
|
|
import { ReactNode, useState } from "react";
|
|
import CInput from "react-hook-form-cloudscape/components/input";
|
|
import CTextArea from "react-hook-form-cloudscape/components/textarea";
|
|
import { fetchUrlEncoded } from "../helpers/FetchHelper";
|
|
|
|
export type DoorEditForm = DoorResponse & { pin: string, fallbackNumber: string, discordUser: string, isConfirmed: boolean };
|
|
|
|
const EDIT_ROUTE = '/api/door/edit';
|
|
const ONBOARD_ROUTE = '/api/door/onboard';
|
|
|
|
export interface EditPageProps {
|
|
isOnboarding?: boolean;
|
|
}
|
|
|
|
export const EditPage = ({ isOnboarding }: EditPageProps) => {
|
|
const doorResponse = useLoaderData() as DoorResponse;
|
|
const door = doorResponse.id;
|
|
const { setValue, setError, getValues, watch, formState, clearErrors, control, register } = useForm<DoorEditForm>({
|
|
defaultValues: {
|
|
...doorResponse,
|
|
isConfirmed: !isOnboarding,
|
|
},
|
|
mode: "all",
|
|
});
|
|
|
|
|
|
const [ alerts, setAlerts ] = useState<FlashbarProps.MessageDefinition[]>([]);
|
|
|
|
const dismissAlert = (id: string) => {
|
|
setAlerts(alerts => alerts.filter(alert => alert.id !== id));
|
|
}
|
|
|
|
const createAlert = (type: FlashbarProps.Type, content: ReactNode): FlashbarProps.MessageDefinition => {
|
|
const id = `${Math.random()}`;
|
|
return {
|
|
id,
|
|
type,
|
|
content,
|
|
dismissible: type !== 'in-progress',
|
|
onDismiss: () => dismissAlert(id),
|
|
}
|
|
}
|
|
|
|
const addAlert = (type: FlashbarProps.Type, content: ReactNode): string => {
|
|
const newAlert = createAlert(type, content);
|
|
setAlerts(alerts => [
|
|
newAlert,
|
|
...alerts,
|
|
]);
|
|
|
|
return newAlert.id as string;
|
|
}
|
|
|
|
const fallbackNumbers = watch("fallbackNumbers") || [];
|
|
const discordUsers = watch("discordUsers") || [];
|
|
|
|
const fallbackNumbersError = formState.errors.fallbackNumbers?.message;
|
|
const discordUsersError = formState.errors.discordUsers?.message;
|
|
|
|
const backUrl = isOnboarding? '/': `?door=${door}`;
|
|
const apiRoute = isOnboarding ? ONBOARD_ROUTE: EDIT_ROUTE;
|
|
|
|
return (
|
|
<AppLayout
|
|
contentType="form"
|
|
navigationHide
|
|
toolsHide
|
|
breadcrumbs={
|
|
<BreadcrumbGroup
|
|
items={[
|
|
{ text: 'Door', href: backUrl },
|
|
isOnboarding ? { text: "Onboard", href: "?onboard" }: { text: door, href: `?edit&door=${door}` },
|
|
]}
|
|
/>
|
|
}
|
|
notifications={
|
|
<Flashbar
|
|
stackItems={true}
|
|
items={alerts}
|
|
/>
|
|
}
|
|
content={
|
|
<Form
|
|
actions={
|
|
<SpaceBetween direction="horizontal" size="xs">
|
|
<Button formAction="none" variant="link" href={backUrl}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
variant="primary"
|
|
onClick={() => {
|
|
if (isOnboarding && !getValues("isConfirmed")) {
|
|
setError("isConfirmed", {
|
|
type: "manual",
|
|
message: "Must confirm that Doorman number is configured",
|
|
});
|
|
return;
|
|
}
|
|
const form = {
|
|
...getValues(),
|
|
id: undefined,
|
|
name: getValues("id"),
|
|
timeout: parseInt("" + getValues("timeout"))
|
|
};
|
|
|
|
const body = {
|
|
door,
|
|
newConfig: JSON.stringify(form),
|
|
};
|
|
|
|
fetchUrlEncoded(apiRoute, body)
|
|
.then(res => res.json())
|
|
.then(res => {
|
|
if (res.msg) {
|
|
addAlert("error", res.msg);
|
|
return;
|
|
}
|
|
if (!isOnboarding) {
|
|
addAlert("success", `Created approval, check Discord notifcation from Doorman to confirm and approve the changes`);
|
|
} else if (res.redirect) {
|
|
// redirect for discord login
|
|
addAlert("in-progress", `Created Door, you will now be redirected to Discord login to complete the onboarding`);
|
|
|
|
// redirect in 2 seconds
|
|
setTimeout(() => {
|
|
window.location = res.redirect;
|
|
}, 2_000);
|
|
}
|
|
});
|
|
}}
|
|
>
|
|
Submit
|
|
</Button>
|
|
</SpaceBetween>
|
|
}
|
|
header={
|
|
<Header variant="h1">
|
|
{isOnboarding? "Onboard Door": "Edit Door"}
|
|
</Header>
|
|
}
|
|
>
|
|
<Container>
|
|
<SpaceBetween direction="vertical" size="l">
|
|
<FormField label="Door Name" constraintText="Unique name for this Door, will be used in your Doorman URL">
|
|
<CInput readOnly={!isOnboarding} disabled={!isOnboarding} name="id" control={control} />
|
|
</FormField>
|
|
<FormField label="Buzzer Number" constraintText="Phone number that calls you when your buzzer is called">
|
|
<CInput readOnly={!isOnboarding} disabled={!isOnboarding} name="buzzer" control={control} />
|
|
</FormField>
|
|
<FormField label="PIN" constraintText={"Code to unlock this Door in Doorman"}>
|
|
<CInput type="password" name="pin" control={control} />
|
|
</FormField>
|
|
<FormField label="Buzzer Code" constraintText={"Extension that is used to reach your unit on your buzzer"}>
|
|
<CInput name="buzzerCode" control={control} />
|
|
</FormField>
|
|
<FormField label="Timeout" constraintText="Time in seconds for the door to remain unlocked from Doorman">
|
|
<CInput type="number" name="timeout" control={control} />
|
|
</FormField>
|
|
<FormField label="Unlock Key" constraintText="Phone digit pressed to grant access">
|
|
<CInput name="pressKey" control={control} />
|
|
</FormField>
|
|
<FormField
|
|
errorText={fallbackNumbersError}
|
|
label="Fallback numbers"
|
|
constraintText="Phone numbers to dial through in case Door is not unlocked by Doorman"
|
|
>
|
|
<InputTokenGroup
|
|
name={"fallbackNumber"}
|
|
control={control}
|
|
tokenGroupProps={{ items: fallbackNumbers.map(n => ({ label: n }))}}
|
|
onAdd={() => {
|
|
const newValue = getValues().fallbackNumber;
|
|
if (newValue.length === 0) {
|
|
return;
|
|
}
|
|
clearErrors("fallbackNumbers");
|
|
setValue("fallbackNumbers", [...fallbackNumbers, newValue]);
|
|
setValue("fallbackNumber", "");
|
|
}}
|
|
onDismiss={(i) => {
|
|
clearErrors("fallbackNumbers");
|
|
|
|
if (fallbackNumbers.length === 1) {
|
|
setError("fallbackNumbers", { message: "Can't remove last entry", type: "value" })
|
|
return;
|
|
}
|
|
fallbackNumbers.splice(i, 1);
|
|
setValue("fallbackNumbers", [...fallbackNumbers]);
|
|
}}
|
|
/>
|
|
</FormField>
|
|
{!isOnboarding &&
|
|
<FormField
|
|
label="Discord Users"
|
|
errorText={discordUsersError}
|
|
constraintText={
|
|
<>
|
|
<Link fontSize={"body-s"} href="https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID#h_01HRSTXPS5H5D7JBY2QKKPVKNA">Discord users</Link> to receive notifications
|
|
</>
|
|
}
|
|
>
|
|
<InputTokenGroup
|
|
name={"discordUser"}
|
|
control={control}
|
|
tokenGroupProps={{ items: discordUsers.map(n => ({ label: n }))}}
|
|
onAdd={() => {
|
|
const newValue = getValues().discordUser;
|
|
if (newValue.length === 0) {
|
|
return;
|
|
}
|
|
clearErrors("discordUsers");
|
|
setValue("discordUsers", [...discordUsers, newValue]);
|
|
setValue("discordUser", "");
|
|
}}
|
|
onDismiss={(i) => {
|
|
clearErrors("discordUsers");
|
|
|
|
if (discordUsers.length === 1) {
|
|
setError("discordUsers", { message: "Can't remove last entry" })
|
|
return;
|
|
}
|
|
discordUsers.splice(i, 1);
|
|
setValue("discordUsers", [...discordUsers]);
|
|
}}
|
|
/>
|
|
</FormField>
|
|
}
|
|
<FormField label="Welcome Message" constraintText="Message to display after a successful unlock">
|
|
<CTextArea name="greeting" control={control} />
|
|
</FormField>
|
|
{isOnboarding &&
|
|
<FormField label="Register Doorman in Building" constraintText="Confirm that you added our number to your buzzer system: 604-757-1824" errorText={formState.errors.isConfirmed?.message}>
|
|
<label>
|
|
I have added Doorman's number to my buzzer
|
|
<input type="checkbox" {...register("isConfirmed")} />
|
|
</label>
|
|
</FormField>
|
|
}
|
|
</SpaceBetween>
|
|
</Container>
|
|
</Form>
|
|
}
|
|
/>
|
|
);
|
|
};
|