import { ReactNode, useEffect, useState } from "react"; import { useLoaderData, useNavigate, useSearchParams } from "react-router-dom"; import { AppLayout, BreadcrumbGroup, Container, Flashbar, FlashbarProps, Header, SpaceBetween, TextContent, Wizard } from "@cloudscape-design/components"; import { AuthComponent, IAuthMode } from "../components/AuthComponent"; import OtpInput from 'react-otp-input'; import { CountdownBar } from "../components/CountdownBar"; import { DoorResponse } from "../types/DoorResponse"; import { fetchUrlEncoded } from "../helpers/FetchHelper"; export async function loader({ params, request }: any) { const door = new URL(request.url).searchParams.get('door'); if (!door) { return {}; } const res = await fetchUrlEncoded('/api/door/info', { door, }).then(res => res.json()); console.log(res); if (res.msg) { throw new Error("Not a valid door"); } return res as DoorResponse; } export function DoorPage() { const doorResponse = useLoaderData() as DoorResponse; const navigate = useNavigate(); const door = doorResponse.id; const [ step, setStep ] = useState(0); const [searchParams, setSearchParams] = useSearchParams(); const secret = searchParams.get('key') || searchParams.get('rotatingKey'); const [ alerts, setAlerts ] = useState([]); const [ polling, setPolling ] = useState(false); const [ submitPin, setSubmitPin ] = useState(0); const dismissAlert = (id: string) => { setAlerts(alerts => alerts.filter(alert => alert.id !== id)); } const dismissInProgressAlerts = () => { setAlerts(alerts => alerts.filter(alert => alert.type !== 'in-progress')); } 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; } useEffect(() => { if (!polling) { return; } const timer = setInterval(async () => { const response = await fetchUrlEncoded('/api/door/info', { door, }).then(res => res.json()); // polling assumes that the door was opened and whatever closed it was the buzzer system... // ie. state transition from OPEN to CLOSED before timeout means that twilio opened the door // TODO: this may be a bad assumption in the future but works for now if (response.status === "CLOSED") { setStep(3); setPolling(false); setAlerts(alerts => [createAlert("success", "Buzzer successfully unlocked"), ...alerts.filter(alert => alert.type !== 'in-progress')] ); } }, 2000); return () => { clearInterval(timer); dismissInProgressAlerts(); } }, [polling]); return ( } notifications={ } content={ `Step ${stepNumber}`, collapsedStepsLabel: (stepNumber, stepsCount) => `Step ${stepNumber} of ${stepsCount}`, skipToButtonLabel: (step, stepNumber) => `Skip to ${step.title}`, navigationAriaLabel: "Steps", cancelButton: "Cancel", previousButton: "Previous", nextButton: "Next", submitButton: "Finish", optional: "optional" }} activeStepIndex={step} onNavigate={({ detail }) => { if (detail.requestedStepIndex < step) { dismissInProgressAlerts(); setSubmitPin(0); setPolling(false); setStep(detail.requestedStepIndex); return; } if (detail.requestedStepIndex === 2) { setSubmitPin(submitPin + 1); return; } if (detail.requestedStepIndex >= 2) { return; } setStep(detail.requestedStepIndex); }} onCancel={() => { setStep(0); dismissInProgressAlerts(); setSubmitPin(0); }} steps={[ { title: "Get to the buzzer", description:"Before unlocking this door, you should first arrive to the buzzer", content: ( Get to the buzzer } > Once you arrive at the buzzer, advance to the next step! ) }, { title: "Authenticate", description: "This door is locked by a software system, unlocking it will allow you access", content: ( Authenticate } > { setStep(2); dismissInProgressAlerts(); addAlert("in-progress", ( { setPolling(false); addAlert("error", "Authentication expired: please try again. If this was a one time link, you need to request a new one"); setStep(1); }} /> )); setPolling(true); }} /> ) }, { title: "Dial code", description: "Type in the buzzer code and the system should automatically allow you access to the building", content: ( Dial code } >

Enter the buzzer code at the front entrance

null} renderSeparator={() => -} renderInput={(props) => } />
) }, { title: "Enter the building", description: "The buzzer allowed you access, come on up!", content: ( Welcome } > {doorResponse.greeting || "The door is unlocked!"} ) }, ]} /> } /> ); };