launch edit api
This commit is contained in:
parent
9337846ad0
commit
25438f7901
103
packages/doorman-api/functions/api/door/edit.js
Normal file
103
packages/doorman-api/functions/api/door/edit.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Edit API for doors
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.handler = async function(context, event, callback) {
|
||||||
|
const response = new Twilio.Response();
|
||||||
|
|
||||||
|
let door = event.door;
|
||||||
|
let approvalId = event.approvalId;
|
||||||
|
let newConfig = event.newConfig;
|
||||||
|
|
||||||
|
const ddbPath = Runtime.getFunctions()['common/ddb'].path;
|
||||||
|
const discordPath = Runtime.getFunctions()['common/discord'].path;
|
||||||
|
const ddb = require(ddbPath);
|
||||||
|
const discord = require(discordPath);
|
||||||
|
const client = ddb.createDDBClient(context);
|
||||||
|
|
||||||
|
// approve path
|
||||||
|
if (door && approvalId) {
|
||||||
|
const newConfig = await client.send(ddb.getDoorConfigUpdateCommand(door));
|
||||||
|
|
||||||
|
if (!newConfig || newConfig.Item.approvalId.S !== approvalId) {
|
||||||
|
response.setStatusCode(400);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.send(ddb.replaceDoorConfigWithUpdateItem(newConfig));
|
||||||
|
|
||||||
|
const hostUrl = event.request.headers.host;
|
||||||
|
const scheme = hostUrl.startsWith("localhost") ? "http://": "https://";
|
||||||
|
|
||||||
|
// send update to discord users
|
||||||
|
const updateMessage = `Configuration change \`${approvalId}\` was approved @ Door "${door}"`;
|
||||||
|
|
||||||
|
const discordPromises = newConfig.Item.discordUsers.SS.map((user) => {
|
||||||
|
return discord.sendMessageToUser(
|
||||||
|
context,
|
||||||
|
user,
|
||||||
|
updateMessage,
|
||||||
|
).catch(e => console.error(e))
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(discordPromises);
|
||||||
|
|
||||||
|
response.setStatusCode(200);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!door || !newConfig) {
|
||||||
|
response.setStatusCode(400);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
newConfig = JSON.parse(event.newConfig);
|
||||||
|
|
||||||
|
const config = await client.send(ddb.getDoorConfigCommand(door));
|
||||||
|
|
||||||
|
if (!config.Item) {
|
||||||
|
response.setStatusCode(404);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set to old PIN if it is missing
|
||||||
|
if (newConfig.pin === "") {
|
||||||
|
newConfig.pin = config.Item.pin.S;
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = ddb.putDoorUpdateConfigCommand(door, newConfig);
|
||||||
|
|
||||||
|
const update = await client.send(input);
|
||||||
|
|
||||||
|
newConfig.discordUser = undefined;
|
||||||
|
newConfig.fallbackNumber = undefined;
|
||||||
|
newConfig.status = undefined;
|
||||||
|
|
||||||
|
const hostUrl = event.request.headers.host;
|
||||||
|
const scheme = hostUrl.startsWith("localhost") ? "http://": "https://";
|
||||||
|
|
||||||
|
const approvalUrl = `${scheme + hostUrl}/api/door/edit?door=${door}&approvalId=${input.input.Item.approvalId.S}`;
|
||||||
|
console.log(approvalUrl);
|
||||||
|
|
||||||
|
// send update to discord users
|
||||||
|
const approvalMessage = `Configuration change requested @ Door "${door}" [click to approve it](${approvalUrl})\`\`\`${JSON.stringify(newConfig, null, 4)}\`\`\``;
|
||||||
|
|
||||||
|
const discordPromises = config.Item.discordUsers.SS.map((user) => {
|
||||||
|
return discord.sendMessageToUser(
|
||||||
|
context,
|
||||||
|
user,
|
||||||
|
approvalMessage,
|
||||||
|
).catch(e => console.error(e))
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(discordPromises);
|
||||||
|
|
||||||
|
response
|
||||||
|
.setStatusCode(200)
|
||||||
|
.appendHeader('Content-Type', 'application/json')
|
||||||
|
.setBody({ msg: update });
|
||||||
|
|
||||||
|
await client.destroy();
|
||||||
|
return callback(null, response);
|
||||||
|
};
|
||||||
@ -41,6 +41,7 @@ exports.handler = async function(context, event, callback) {
|
|||||||
.setBody({ err: "This buzzer is not registered properly" });
|
.setBody({ err: "This buzzer is not registered properly" });
|
||||||
} else {
|
} else {
|
||||||
if (buzzer) {
|
if (buzzer) {
|
||||||
|
// respond to twilio CLIENT
|
||||||
response
|
response
|
||||||
.setStatusCode(200)
|
.setStatusCode(200)
|
||||||
.appendHeader('Content-Type', 'application/json')
|
.appendHeader('Content-Type', 'application/json')
|
||||||
@ -56,10 +57,20 @@ exports.handler = async function(context, event, callback) {
|
|||||||
.then(async (lock) => {
|
.then(async (lock) => {
|
||||||
const status = ddb.isLockOpen(lock) ? "OPEN": "CLOSED";
|
const status = ddb.isLockOpen(lock) ? "OPEN": "CLOSED";
|
||||||
|
|
||||||
|
// respond to UI
|
||||||
response
|
response
|
||||||
.setStatusCode(200)
|
.setStatusCode(200)
|
||||||
.appendHeader('Content-Type', 'application/json')
|
.appendHeader('Content-Type', 'application/json')
|
||||||
.setBody({ id: door, timeout: config.Item.timeout.N, status, buzzerCode: config.Item.buzzerCode.S });
|
.setBody({
|
||||||
|
id: door,
|
||||||
|
timeout: config.Item.timeout.N,
|
||||||
|
buzzer: config.Item.buzzer.S,
|
||||||
|
status,
|
||||||
|
buzzerCode: config.Item.buzzerCode.S,
|
||||||
|
fallbackNumbers: config.Item.fallbackNumbers.SS,
|
||||||
|
pressKey: config.Item.pressKey.S,
|
||||||
|
discordUsers: config.Item?.discordUsers?.SS || [],
|
||||||
|
});
|
||||||
|
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
const { DynamoDBClient, GetItemCommand, DeleteItemCommand, PutItemCommand } = require("@aws-sdk/client-dynamodb");
|
const { randomUUID } = require("crypto");
|
||||||
|
const { DynamoDBClient, GetItemCommand, DeleteItemCommand, PutItemCommand, UpdateItemCommand } = require("@aws-sdk/client-dynamodb");
|
||||||
|
|
||||||
exports.createDDBClient = (context) => {
|
exports.createDDBClient = (context) => {
|
||||||
return new DynamoDBClient({
|
return new DynamoDBClient({
|
||||||
@ -91,3 +92,86 @@ exports.setLockStatusCommand = (door, timeoutSeconds, fingerprintObj) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.putDoorUpdateConfigCommand = (door, config) => {
|
||||||
|
return new PutItemCommand({
|
||||||
|
TableName: "doorman",
|
||||||
|
Item: {
|
||||||
|
"PK": {
|
||||||
|
S: "door-" + door,
|
||||||
|
},
|
||||||
|
"SK": {
|
||||||
|
S: "config-update",
|
||||||
|
},
|
||||||
|
"buzzer": {
|
||||||
|
S: config.buzzer,
|
||||||
|
},
|
||||||
|
"buzzerCode": {
|
||||||
|
S: config.buzzerCode,
|
||||||
|
},
|
||||||
|
"discordUsers": {
|
||||||
|
SS: config.discordUsers,
|
||||||
|
},
|
||||||
|
"fallbackNumbers": {
|
||||||
|
SS: config.fallbackNumbers,
|
||||||
|
},
|
||||||
|
"pin": {
|
||||||
|
S: config.pin,
|
||||||
|
},
|
||||||
|
"pressKey": {
|
||||||
|
S: config.pressKey,
|
||||||
|
},
|
||||||
|
"timeout": {
|
||||||
|
N: `${config.timeout}`,
|
||||||
|
},
|
||||||
|
"greeting": {
|
||||||
|
S: config.greeting,
|
||||||
|
},
|
||||||
|
"approvalId": {
|
||||||
|
S: randomUUID().toString(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getDoorConfigCommand = (door) => {
|
||||||
|
return new GetItemCommand({
|
||||||
|
TableName: "doorman",
|
||||||
|
Key: {
|
||||||
|
"PK": {
|
||||||
|
S: `door-${door}`,
|
||||||
|
},
|
||||||
|
"SK": {
|
||||||
|
S: "config-update",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getDoorConfigUpdateCommand = (door) => {
|
||||||
|
return new GetItemCommand({
|
||||||
|
TableName: "doorman",
|
||||||
|
Key: {
|
||||||
|
"PK": {
|
||||||
|
S: `door-${door}`,
|
||||||
|
},
|
||||||
|
"SK": {
|
||||||
|
S: "config-update",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.replaceDoorConfigWithUpdateItem = (newConfigItem) => {
|
||||||
|
const newItem = {
|
||||||
|
...newConfigItem.Item,
|
||||||
|
SK: { S: "config" },
|
||||||
|
};
|
||||||
|
|
||||||
|
delete newItem.approvalId;
|
||||||
|
|
||||||
|
return new PutItemCommand({
|
||||||
|
TableName: "doorman",
|
||||||
|
Item: newItem,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
27
packages/doorman-ui/src/components/InputTokenGroup.tsx
Normal file
27
packages/doorman-ui/src/components/InputTokenGroup.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Button, ColumnLayout, Input, SpaceBetween, TokenGroup, TokenGroupProps } from "@cloudscape-design/components";
|
||||||
|
|
||||||
|
export type InputTokenGroupProps = {
|
||||||
|
type?: React.HTMLInputTypeAttribute;
|
||||||
|
registerInput: any,
|
||||||
|
tokenGroupProps: TokenGroupProps,
|
||||||
|
onAdd: () => void,
|
||||||
|
onDismiss: (i: number) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InputTokenGroup = (props: InputTokenGroupProps) => {
|
||||||
|
return (
|
||||||
|
<SpaceBetween size="xs">
|
||||||
|
<ColumnLayout columns={2}>
|
||||||
|
<input
|
||||||
|
type={props.type}
|
||||||
|
{...props.registerInput}
|
||||||
|
/>
|
||||||
|
<Button onClick={() => props.onAdd()}>Add</Button>
|
||||||
|
</ColumnLayout>
|
||||||
|
<TokenGroup
|
||||||
|
onDismiss={(e) => props.onDismiss(e.detail.itemIndex)}
|
||||||
|
{...props.tokenGroupProps}
|
||||||
|
/>
|
||||||
|
</SpaceBetween>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -4,7 +4,8 @@ import App from './App';
|
|||||||
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
|
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
|
||||||
import { DoorPage, loader as doorpageloader } from './pages/DoorPage';
|
import { DoorPage, loader as doorpageloader } from './pages/DoorPage';
|
||||||
import { ErrorPage } from './pages/ErrorPage';
|
import { ErrorPage } from './pages/ErrorPage';
|
||||||
|
import { QueryRouter } from './routers/QueryRouter';
|
||||||
|
import { EditPage } from './pages/EditPage';
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(
|
const root = ReactDOM.createRoot(
|
||||||
document.getElementById('root') as HTMLElement
|
document.getElementById('root') as HTMLElement
|
||||||
@ -19,7 +20,13 @@ const router = createBrowserRouter([
|
|||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
loader: doorpageloader,
|
loader: doorpageloader,
|
||||||
element: <DoorPage />
|
element:
|
||||||
|
<QueryRouter
|
||||||
|
mapping={{
|
||||||
|
edit: <EditPage />,
|
||||||
|
door: <DoorPage />
|
||||||
|
}}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -30,4 +37,3 @@ root.render(
|
|||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement, ReactNode, useEffect, useState } from "react";
|
import { ReactNode, useEffect, useState } from "react";
|
||||||
import { useLoaderData, useNavigate, useSearchParams } from "react-router-dom";
|
import { useLoaderData, useNavigate, useSearchParams } from "react-router-dom";
|
||||||
import { AppLayout, BreadcrumbGroup, Container, Flashbar, FlashbarProps, Header, SideNavigation, SpaceBetween, TextContent, Wizard } from "@cloudscape-design/components";
|
import { AppLayout, BreadcrumbGroup, Container, Flashbar, FlashbarProps, Header, SpaceBetween, TextContent, Wizard } from "@cloudscape-design/components";
|
||||||
import { AuthComponent, IAuthMode } from "../components/AuthComponent";
|
import { AuthComponent, IAuthMode } from "../components/AuthComponent";
|
||||||
import OtpInput from 'react-otp-input';
|
import OtpInput from 'react-otp-input';
|
||||||
import { CountdownBar } from "../components/CountdownBar";
|
import { CountdownBar } from "../components/CountdownBar";
|
||||||
@ -9,27 +9,21 @@ import { DoorResponse } from "../types/DoorResponse";
|
|||||||
export async function loader({ params, request }: any) {
|
export async function loader({ params, request }: any) {
|
||||||
const door = new URL(request.url).searchParams.get('door');
|
const door = new URL(request.url).searchParams.get('door');
|
||||||
|
|
||||||
|
if (!door) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(`/api/door/info?door=${door}`).then(res => res.json());
|
const response = await fetch(`/api/door/info?door=${door}`).then(res => res.json());
|
||||||
|
|
||||||
console.log(response);
|
console.log(response);
|
||||||
|
|
||||||
if (response.msg) {
|
if (response.err) {
|
||||||
throw new Error("Not a valid door");
|
throw new Error("Not a valid door");
|
||||||
}
|
}
|
||||||
|
|
||||||
return response as DoorResponse;
|
return response as DoorResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SelectOption {
|
|
||||||
label?: string;
|
|
||||||
value?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectOptions: SelectOption[] = [
|
|
||||||
{ label: "backup", value: "backup" },
|
|
||||||
{ label: "key", value: "key" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export function DoorPage() {
|
export function DoorPage() {
|
||||||
const doorResponse = useLoaderData() as DoorResponse;
|
const doorResponse = useLoaderData() as DoorResponse;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -106,7 +100,7 @@ export function DoorPage() {
|
|||||||
<BreadcrumbGroup
|
<BreadcrumbGroup
|
||||||
items={[
|
items={[
|
||||||
{ text: 'Door', href: '#' },
|
{ text: 'Door', href: '#' },
|
||||||
{ text: door, href: `?door=${door}&edit=true` },
|
{ text: door, href: `?edit&door=${door}` },
|
||||||
{ text: 'Unlock', href: '#' },
|
{ text: 'Unlock', href: '#' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@ -272,4 +266,4 @@ export function DoorPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
145
packages/doorman-ui/src/pages/EditPage.tsx
Normal file
145
packages/doorman-ui/src/pages/EditPage.tsx
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import { AppLayout, BreadcrumbGroup, Button, Container, Form, FormField, Header, 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";
|
||||||
|
|
||||||
|
export type DoorEditForm = DoorResponse & { pin: string, fallbackNumber: string, discordUser: string, greeting: string };
|
||||||
|
|
||||||
|
export const EditPage = () => {
|
||||||
|
const doorResponse = useLoaderData() as DoorResponse;
|
||||||
|
const door = doorResponse.id;
|
||||||
|
const { register, setValue, setError, getValues, watch, formState, clearErrors } = useForm<DoorEditForm>({
|
||||||
|
defaultValues: doorResponse,
|
||||||
|
mode: "all",
|
||||||
|
});
|
||||||
|
|
||||||
|
const fallbackNumbers = watch("fallbackNumbers");
|
||||||
|
const discordUsers = watch("discordUsers");
|
||||||
|
|
||||||
|
const fallbackNumbersError = formState.errors.fallbackNumbers?.message;
|
||||||
|
const discordUsersError = formState.errors.discordUsers?.message;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppLayout
|
||||||
|
contentType="form"
|
||||||
|
navigationHide
|
||||||
|
toolsHide
|
||||||
|
breadcrumbs={
|
||||||
|
<BreadcrumbGroup
|
||||||
|
items={[
|
||||||
|
{ text: 'Door', href: '#' },
|
||||||
|
{ text: door, href: `?edit&door=${door}` },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
content={
|
||||||
|
<Form
|
||||||
|
actions={
|
||||||
|
<SpaceBetween direction="horizontal" size="xs">
|
||||||
|
<Button formAction="none" variant="link" href={`?door=${door}`}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
onClick={() => {
|
||||||
|
fetch(`/api/door/edit?door=${door}&newConfig=${encodeURIComponent(JSON.stringify(getValues()))}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
console.log(res);
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</SpaceBetween>
|
||||||
|
}
|
||||||
|
header={<Header variant="h1">Edit Door</Header>}
|
||||||
|
>
|
||||||
|
<Container
|
||||||
|
header={
|
||||||
|
<Header variant="h2">
|
||||||
|
Door - {door}
|
||||||
|
</Header>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SpaceBetween direction="vertical" size="l">
|
||||||
|
<FormField label="Buzzer Number (read-only)" constraintText="The phone number of your buzzer system">
|
||||||
|
<input readOnly {...register("buzzer")} />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="PIN" constraintText={"The code to unlock the buzzer"}>
|
||||||
|
<input type="password" {...register("pin")}/>
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Buzzer Code" constraintText={"The number that you dial on your buzzer"}>
|
||||||
|
<input {...register("buzzerCode")} />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Timeout" constraintText="Time in seconds for the door to remain unlocked">
|
||||||
|
<input type="number" {...register("timeout", {
|
||||||
|
valueAsNumber: true,
|
||||||
|
})} />
|
||||||
|
</FormField>
|
||||||
|
<FormField
|
||||||
|
errorText={fallbackNumbersError}
|
||||||
|
label="Fallback numbers"
|
||||||
|
constraintText="Phone numbers to dial through in case door is not unlocked"
|
||||||
|
>
|
||||||
|
<InputTokenGroup
|
||||||
|
registerInput={register("fallbackNumber")}
|
||||||
|
tokenGroupProps={{ items: fallbackNumbers.map(n => ({ label: n }))}}
|
||||||
|
onAdd={() => {
|
||||||
|
clearErrors("fallbackNumbers");
|
||||||
|
setValue("fallbackNumbers", [...fallbackNumbers, getValues().fallbackNumber])
|
||||||
|
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>
|
||||||
|
<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
|
||||||
|
type="string"
|
||||||
|
registerInput={register("discordUser")}
|
||||||
|
tokenGroupProps={{ items: discordUsers.map(n => ({ label: n }))}}
|
||||||
|
onAdd={() => {
|
||||||
|
clearErrors("discordUsers");
|
||||||
|
setValue("discordUsers", [...discordUsers, getValues().discordUser])
|
||||||
|
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">
|
||||||
|
<textarea {...register("greeting")}/>
|
||||||
|
</FormField>
|
||||||
|
</SpaceBetween>
|
||||||
|
</Container>
|
||||||
|
</Form>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
24
packages/doorman-ui/src/routers/QueryRouter.tsx
Normal file
24
packages/doorman-ui/src/routers/QueryRouter.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import { useLocation, useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
|
export interface IQueryRouterProps {
|
||||||
|
mapping: Record<string, ReactNode>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const QueryRouter = ({ mapping }: IQueryRouterProps) => {
|
||||||
|
const [params] = useSearchParams();
|
||||||
|
let element = null;
|
||||||
|
|
||||||
|
for (const key of params.keys()) {
|
||||||
|
if (mapping[key]) {
|
||||||
|
element = mapping[key];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (element === null) {
|
||||||
|
throw new Error("missing mapping");
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
|
};
|
||||||
@ -1,5 +0,0 @@
|
|||||||
export enum Action {
|
|
||||||
DOWNLOAD = "download",
|
|
||||||
UPLOAD = "upload",
|
|
||||||
DELETE = "delete",
|
|
||||||
}
|
|
||||||
@ -2,4 +2,8 @@ export interface DoorResponse {
|
|||||||
id: string,
|
id: string,
|
||||||
timeout: number;
|
timeout: number;
|
||||||
buzzerCode: string;
|
buzzerCode: string;
|
||||||
|
fallbackNumbers: string[];
|
||||||
|
pressKey: string;
|
||||||
|
discordUsers: string[];
|
||||||
|
buzzer: string;
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user