@airtable/blocks/ui#Button JavaScript Examples

The following examples show how to use @airtable/blocks/ui#Button. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.js    From apps-print-records with MIT License 6 votes vote down vote up
// The toolbar contains the view picker and print button.
function Toolbar({table}) {
    return (
        <Box className="print-hide" padding={2} borderBottom="thick" display="flex">
            <ViewPickerSynced table={table} globalConfigKey={GlobalConfigKeys.VIEW_ID} />
            <Button
                onClick={() => {
                    // Inject CSS to hide elements with the "print-hide" class name
                    // when the app gets printed. This lets us hide the toolbar from
                    // the print output.
                    printWithoutElementsWithClass('print-hide');
                }}
                marginLeft={2}
            >
                Print
            </Button>
        </Box>
    );
}
Example #2
Source File: todo-app.js    From apps-todo-list with MIT License 6 votes vote down vote up
function TaskDeleteButton({table, record}) {
    function onClick() {
        table.deleteRecordAsync(record);
    }

    return (
        <Button
            variant="secondary"
            marginLeft={1}
            onClick={onClick}
            disabled={!table.hasPermissionToDeleteRecord(record)}
        >
            <Icon name="x" style={{display: 'flex'}} />
        </Button>
    );
}
Example #3
Source File: todo-app.js    From apps-todo-list with MIT License 6 votes vote down vote up
function AddTaskForm({table}) {
    const [taskName, setTaskName] = useState('');

    function onInputChange(event) {
        setTaskName(event.currentTarget.value);
    }

    function onSubmit(event) {
        event.preventDefault();
        table.createRecordAsync({
            [table.primaryField.id]: taskName,
        });
        setTaskName('');
    }

    // check whether or not the user is allowed to create records with values in the primary field.
    // if not, disable the form.
    const isFormEnabled = table.hasPermissionToCreateRecord({
        [table.primaryField.id]: undefined,
    });
    return (
        <form onSubmit={onSubmit}>
            <Box display="flex" padding={3}>
                <Input
                    flex="auto"
                    value={taskName}
                    placeholder="New task"
                    onChange={onInputChange}
                    disabled={!isFormEnabled}
                />
                <Button variant="primary" marginLeft={2} type="submit" disabled={!isFormEnabled}>
                    Add
                </Button>
            </Box>
        </form>
    );
}
Example #4
Source File: settings.js    From neighbor-express with MIT License 6 votes vote down vote up
function AddEmailTypeDialog() {
  const [isDialogOpen, setIsDialogOpen] = useState(false);
  const [name, setName] = useState("");
  const globalConfig = useGlobalConfig();

  function save() {
    globalConfig.setAsync(["email_types", name], {});
    setIsDialogOpen(false);
  }
  return (
    <>
      <Button onClick={() => setIsDialogOpen(true)}>Add new email type</Button>
      {isDialogOpen && (
        <Dialog onClose={() => setIsDialogOpen(false)} width="320px">
          <Dialog.CloseButton />
          <Heading>New Email Type</Heading>
          <FormField
            label="Name"
            description="A short descriptive name of the new type of email"
          >
            <Input value={name} onChange={(e) => setName(e.target.value)} />
          </FormField>
          <Button onClick={save}>Save</Button>
        </Dialog>
      )}
    </>
  );
}
Example #5
Source File: settings.js    From neighbor-express with MIT License 6 votes vote down vote up
export function SettingsComponent({ exit }) {
  const globalConfig = useGlobalConfig();

  return (
    <Box padding={3}>
      <Button variant="primary" onClick={exit} style={{ float: "right" }}>
        Exit settings
      </Button>
      <h1> Settings </h1>
      <p>
        You probably won't need to do anything here unless you're just starting
        out.
      </p>
      <FormField label="Galaxy Digital API Key">
        <InputSynced globalConfigKey="GALAXY_DIGITAL_API_KEY" />
      </FormField>
    </Box>
  );
}
Example #6
Source File: SettingsForm.js    From apps-base-schema with MIT License 5 votes vote down vote up
/**
 * Settings form component.
 * Allows the user to toggle link types.
 *
 * @param {Function} props.setShouldShowSettings Function to toggle settings visibility
 */
export default function SettingsForm({setShouldShowSettings}) {
    return (
        <FullscreenBox
            left="initial" // show settings in right sidebar
            width="360px"
            backgroundColor="white"
            display="flex"
            flexDirection="column"
            borderLeft="thick"
        >
            <Box flex="auto" display="flex" justifyContent="center" overflow="auto">
                <Box paddingTop={4} paddingBottom={2} maxWidth={300} flex="auto">
                    <Heading marginBottom={2}>Settings</Heading>
                    <SwitchSynced
                        marginY={3}
                        label="Show linked record relationships"
                        globalConfigKey={[
                            ConfigKeys.ENABLED_LINKS_BY_TYPE,
                            FieldType.MULTIPLE_RECORD_LINKS,
                        ]}
                    />
                    <SwitchSynced
                        marginY={3}
                        label="Show formula relationships"
                        globalConfigKey={[ConfigKeys.ENABLED_LINKS_BY_TYPE, FieldType.FORMULA]}
                    />
                    <SwitchSynced
                        marginY={3}
                        label="Show rollup relationships"
                        globalConfigKey={[ConfigKeys.ENABLED_LINKS_BY_TYPE, FieldType.ROLLUP]}
                    />
                    <SwitchSynced
                        marginY={3}
                        label="Show lookup relationships"
                        globalConfigKey={[
                            ConfigKeys.ENABLED_LINKS_BY_TYPE,
                            FieldType.MULTIPLE_LOOKUP_VALUES,
                        ]}
                    />
                    <SwitchSynced
                        marginY={3}
                        label="Show count relationships"
                        globalConfigKey={[ConfigKeys.ENABLED_LINKS_BY_TYPE, FieldType.COUNT]}
                    />
                </Box>
            </Box>
            <Box
                flex="none"
                borderTop="thick"
                display="flex"
                justifyContent="flex-end"
                alignItems="center"
            >
                <Button
                    margin={3}
                    variant="primary"
                    size="large"
                    onClick={() => setShouldShowSettings(false)}
                >
                    Done
                </Button>
            </Box>
        </FullscreenBox>
    );
}
Example #7
Source File: SettingsForm.js    From apps-flashcard with MIT License 5 votes vote down vote up
export default function SettingsForm({setIsSettingsVisible, settings}) {
    return (
        <Box
            flex="none"
            display="flex"
            flexDirection="column"
            width="300px"
            backgroundColor="white"
            maxHeight="100vh"
            borderLeft="thick"
        >
            <Box
                flex="auto"
                display="flex"
                flexDirection="column"
                minHeight="0"
                padding={3}
                overflowY="auto"
            >
                <Heading marginBottom={3}>Settings</Heading>
                <FormField label="Table">
                    <TablePickerSynced globalConfigKey={ConfigKeys.TABLE_ID} />
                </FormField>
                {settings.table && (
                    <Fragment>
                        <FormField
                            label="View"
                            description="Only records in this view will be used"
                        >
                            <ViewPickerSynced
                                table={settings.table}
                                globalConfigKey={ConfigKeys.VIEW_ID}
                            />
                        </FormField>
                        <FormField label="Question field">
                            <FieldPickerSynced
                                table={settings.table}
                                globalConfigKey={ConfigKeys.QUESTION_FIELD_ID}
                            />
                        </FormField>
                        <FormField label="Answer field (optional)">
                            <FieldPickerSynced
                                table={settings.table}
                                shouldAllowPickingNone={true}
                                globalConfigKey={ConfigKeys.ANSWER_FIELD_ID}
                            />
                        </FormField>
                    </Fragment>
                )}
            </Box>
            <Box
                flex="none"
                display="flex"
                justifyContent="flex-end"
                paddingY={3}
                marginX={3}
                borderTop="thick"
            >
                <Button variant="primary" size="large" onClick={() => setIsSettingsVisible(false)}>
                    Done
                </Button>
            </Box>
        </Box>
    );
}
Example #8
Source File: UpdateRecordsApp.js    From apps-update-records with MIT License 5 votes vote down vote up
function UpdateSelectedRecordsButton({tableToUpdate, fieldToUpdate, selectedRecordIds}) {
    // Triggers a re-render if records values change. This makes sure the record values are
    // up to date when calculating their new values.
    const records = useRecords(tableToUpdate, {fields: [fieldToUpdate]});

    // Track whether we're currently in the middle of performing an update.
    // We use this to disable the button during an update.
    // We also use this to show the correct number of records being updated on
    // the button: when the update starts, we store the number here. If the user
    // changes their selected records during the update, the number shown on the
    // button will remain accurate.
    const [numRecordsBeingUpdated, setNumRecordsBeingUpdated] = useState(null);

    const isUpdateInProgress = numRecordsBeingUpdated !== null;

    let buttonText;
    const recordsText = `record${selectedRecordIds.length === 1 ? '' : 's'}`;
    if (isUpdateInProgress) {
        buttonText = `Updating ${numRecordsBeingUpdated} ${recordsText}`;
    } else {
        buttonText = `Click to update ${selectedRecordIds.length} ${recordsText}`;
    }

    // Prepare the updates that we are going to perform. (Required to check permissions)
    // We need to get all of the selected records to get the current values of numberField.
    // .filter narrows the list of all records down to just the records with id
    // in selectedRecordIdSet.
    const selectedRecordIdsSet = new Set(selectedRecordIds);
    const recordsToUpdate = records.filter(record => selectedRecordIdsSet.has(record.id));

    const updates = recordsToUpdate.map(record => ({
        id: record.id,
        fields: {
            // Here, we add 1 to the current value, but you could extend this to support
            // different operations.
            // [fieldToUpdate.id] is used to use the value of fieldToUpdate.id as the key
            [fieldToUpdate.id]: record.getCellValue(fieldToUpdate) + 1,
        },
    }));

    // Disable the button if any of these are true:
    // - an update is in progress,
    // - no records are selected,
    // - the user doesn't have permission to perform the update.
    // (Phew!)
    const shouldButtonBeDisabled =
        isUpdateInProgress ||
        selectedRecordIds.length === 0 ||
        !tableToUpdate.hasPermissionToUpdateRecords(updates);

    return (
        <Button
            variant="primary"
            onClick={async function() {
                // Mark the update as started.
                setNumRecordsBeingUpdated(updates.length);

                // Update the records!
                // await is used to wait for all of the updates to finish saving
                // to Airtable servers. This keeps the button disabled until the
                // update is finished.
                await updateRecordsInBatches(tableToUpdate, updates);

                // We're done! Mark the update as finished.
                setNumRecordsBeingUpdated(null);
            }}
            disabled={shouldButtonBeDisabled}
        >
            {buttonText}
        </Button>
    );
}
Example #9
Source File: SettingsForm.js    From apps-url-preview with MIT License 5 votes vote down vote up
function SettingsForm({setIsSettingsOpen}) {
    const globalConfig = useGlobalConfig();
    const {
        isValid,
        message,
        settings: {isEnforced, urlTable},
    } = useSettings();

    const canUpdateSettings = globalConfig.hasPermissionToSet();

    return (
        <Box
            position="absolute"
            top={0}
            bottom={0}
            left={0}
            right={0}
            display="flex"
            flexDirection="column"
        >
            <Box flex="auto" padding={4} paddingBottom={2}>
                <Heading marginBottom={3}>Settings</Heading>
                <FormField label="">
                    <Switch
                        aria-label="When enabled, the app will only show previews for the specified table and field, regardless of what field is selected."
                        value={isEnforced}
                        onChange={value => {
                            globalConfig.setAsync(ConfigKeys.IS_ENFORCED, value);
                        }}
                        disabled={!canUpdateSettings}
                        label="Use a specific field for previews"
                    />
                    <Text paddingY={1} textColor="light">
                        {isEnforced
                            ? 'The app will show previews for the selected record in grid view if the table has a supported URL in the specified field.'
                            : 'The app will show previews if the selected cell in grid view has a supported URL.'}
                    </Text>
                </FormField>
                {isEnforced && (
                    <FormField label="Preview table">
                        <TablePickerSynced globalConfigKey={ConfigKeys.URL_TABLE_ID} />
                    </FormField>
                )}
                {isEnforced && urlTable && (
                    <FormField label="Preview field">
                        <FieldPickerSynced
                            table={urlTable}
                            globalConfigKey={ConfigKeys.URL_FIELD_ID}
                            allowedTypes={allowedUrlFieldTypes}
                        />
                    </FormField>
                )}
            </Box>
            <Box display="flex" flex="none" padding={3} borderTop="thick">
                <Box
                    flex="auto"
                    display="flex"
                    alignItems="center"
                    justifyContent="flex-end"
                    paddingRight={2}
                >
                    <Text textColor="light">{message}</Text>
                </Box>
                <Button
                    disabled={!isValid}
                    size="large"
                    variant="primary"
                    onClick={() => setIsSettingsOpen(false)}
                >
                    Done
                </Button>
            </Box>
        </Box>
    );
}
Example #10
Source File: index.js    From apps-wikipedia-enrichment with MIT License 5 votes vote down vote up
function WikipediaEnrichmentApp() {
    const base = useBase();

    const table = base.getTableByName(TABLE_NAME);
    const titleField = table.getFieldByName(TITLE_FIELD_NAME);

    // load the records ready to be updated
    // we only need to load the word field - the others don't get read, only written to.
    const records = useRecords(table, {fields: [titleField]});

    // keep track of whether we have up update currently in progress - if there is, we want to hide
    // the update button so you can't have two updates running at once.
    const [isUpdateInProgress, setIsUpdateInProgress] = useState(false);

    // check whether we have permission to update our records or not. Any time we do a permissions
    // check like this, we can pass in undefined for values we don't yet know. Here, as we want to
    // make sure we can update the summary and image fields, we make sure to include them even
    // though we don't know the values we want to use for them yet.
    const permissionCheck = table.checkPermissionsForUpdateRecord(undefined, {
        [EXTRACT_FIELD_NAME]: undefined,
        [IMAGE_FIELD_NAME]: undefined,
    });

    async function onButtonClick() {
        setIsUpdateInProgress(true);
        const recordUpdates = await getExtractAndImageUpdatesAsync(table, titleField, records);
        await updateRecordsInBatchesAsync(table, recordUpdates);
        setIsUpdateInProgress(false);
    }

    return (
        <Box
            // center the button/loading spinner horizontally and vertically.
            position="absolute"
            top="0"
            bottom="0"
            left="0"
            right="0"
            display="flex"
            flexDirection="column"
            justifyContent="center"
            alignItems="center"
        >
            {isUpdateInProgress ? (
                <Loader />
            ) : (
                <Fragment>
                    <Button
                        variant="primary"
                        onClick={onButtonClick}
                        disabled={!permissionCheck.hasPermission}
                        marginBottom={3}
                    >
                        Update summaries and images
                    </Button>
                    {!permissionCheck.hasPermission &&
                        // when we don't have permission to perform the update, we want to tell the
                        // user why. `reasonDisplayString` is a human-readable string that will
                        // explain why the button is disabled.
                        permissionCheck.reasonDisplayString}
                </Fragment>
            )}
        </Box>
    );
}
Example #11
Source File: refresh-queue.js    From neighbor-express with MIT License 5 votes vote down vote up
export function RefreshQueueStep({ nextStep }) {
  const [result, setResult] = useState(undefined);
  const [warnings, setWarnings] = useState([]);

  function addWarning(newWarning) {
    if (newWarning) {
      setWarnings(warnings.concat(newWarning));
    }
  }

  async function refreshQueue() {
    // Clear out the warnings from any previous run
    setWarnings([]);
    let { messagesToCreate, warnings } = await computeMessagesToCreate();
    setWarnings(warnings);

    if (messagesToCreate.length == 0) {
      setResult(`No new messages to enqueue.`);
      return;
    }
    await batchedCreate(messagesToCreate);
    setResult(`Enqueued ${messagesToCreate.length} new messages`);
  }

  return (
    <Box>
      <h2> Step 1: Refresh Queue </h2>
      <Text>
        {" "}
        Open the Messages tab to get started. Click below to refresh queue
        (won't send emails yet).{" "}
      </Text>
      <Button variant="primary" margin={3} onClick={refreshQueue}>
        Refresh queue
      </Button>
      {result && (
        <Box>
          <Button
            style={{ float: "right" }}
            marginX={3}
            variant="primary"
            onClick={nextStep}
          >
            Continue
          </Button>
          <Text> {result} </Text>
          <WarningAccordion warnings={warnings} />
        </Box>
      )}
    </Box>
  );
}
Example #12
Source File: settings.js    From neighbor-express with MIT License 5 votes vote down vote up
function AddTemplateVariableDialog({ table }) {
  const [isDialogOpen, setIsDialogOpen] = useState(false);
  const [field, setField] = useState("");
  const [sendgrid, setSendgrid] = useState("");
  const globalConfig = useGlobalConfig();

  function save() {
    globalConfig.setAsync(
      ["template_variables", table.name, field.id],
      sendgrid
    );
    setField("");
    setSendgrid("");
    setIsDialogOpen(false);
  }

  return (
    <>
      <Button onClick={() => setIsDialogOpen(true)}>
        Add new template variable
      </Button>
      {isDialogOpen && (
        <Dialog onClose={() => setIsDialogOpen(false)} width="320px">
          <Dialog.CloseButton />
          <FormField
            label="Airtable field"
            description="What field contains the data you want to send to sendgrid?"
          >
            <FieldPicker
              table={table}
              field={field}
              onChange={(newField) => setField(newField)}
            />
          </FormField>
          <FormField
            label="Sendgrid reference"
            description="How does the sengrid template refer to this data?"
          >
            <Input
              value={sendgrid}
              onChange={(e) => setSendgrid(e.target.value)}
            />
          </FormField>
          <Button onClick={save}>Save</Button>
        </Dialog>
      )}
    </>
  );
}
Example #13
Source File: sync-data.js    From neighbor-express with MIT License 5 votes vote down vote up
export function SyncData() {
  const messagesTable = useBase().getTable(DESTINATION_TABLE);
  const records = useRecords(messagesTable);
  const [completed, setCompleted] = useState(false);
  const [syncing, setSyncing] = useState(false);

  const [succesfulCount, setSuccesfulCount] = useState(0);
  const [syncError, setSyncError] = useState(null);

  async function syncData() {
    setCompleted(false);
    setSyncing(true);

    const { total, err } = await SyncVolunteerData(messagesTable, records);
    setSuccesfulCount(total);
    setSyncError(err);

    setSyncing(false);
    setCompleted(true);
  }

  return (
    <Box>
      <h2> Sync Galaxy Digital Data </h2>
      {syncing && !completed && <Loader />}
      {!syncing && (
        <>
          {completed && !syncError && (
            <p>Successfully updated all volunteer data</p>
          )}
          {completed && syncError && (
            <p>
              `Sync completed for ${succesfulCount} with error ${syncError}`
            </p>
          )}
          <Box
            margin={2}
            display="flex"
            flexDirection="row"
            justifyContent="flex-start"
            alignItems="center"
          >
            <Button
              marginX={2}
              variant="primary"
              onClick={syncData}
              disabled={syncing}
            >
              Sync
            </Button>
          </Box>
        </>
      )}
    </Box>
  );
}
Example #14
Source File: FlashcardContainer.js    From apps-flashcard with MIT License 4 votes vote down vote up
/**
 * Responsible for picking a random record from the given records.
 * Keeps track of removed records.
 */
export default function FlashcardContainer({records, settings}) {
    const [record, setRecord] = useState(_.sample(records));
    const [removedRecordsSet, setRemovedRecordsSet] = useState(new Set());
    const [shouldShowAnswer, setShouldShowAnswer] = useState(false);

    function handleRemoveRecord() {
        const newRemovedRecordsSet = new Set(removedRecordsSet);
        setRemovedRecordsSet(newRemovedRecordsSet.add(record));
        setShouldShowAnswer(false);
        handleNewRecord();
    }

    function handleNewRecord() {
        setRecord(_.sample(records.filter(r => r !== record && !removedRecordsSet.has(r))));
    }

    function reset() {
        setRemovedRecordsSet(new Set());
        // Can't use handleNewRecord here because setting state is async, so removedRecordsSet won't
        // be updated yet.
        setRecord(_.sample(records));
    }

    // Handle updating record and removedRecordsSet due to records changing
    useEffect(() => {
        const allRecordsSet = new Set(records);
        const newRemovedRecordsSet = new Set();
        for (const removedRecord of removedRecordsSet) {
            if (allRecordsSet.has(removedRecord)) {
                newRemovedRecordsSet.add(removedRecord);
            }
        }
        if (newRemovedRecordsSet.size !== removedRecordsSet.size) {
            setRemovedRecordsSet(newRemovedRecordsSet);
        }

        if (!allRecordsSet.has(record)) {
            handleNewRecord();
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [records]);

    let primaryButton;
    if (record) {
        if (shouldShowAnswer || !settings.answerField) {
            // Either already showing the answer, or there's no answer
            // field. So show the "Next" button to go to the next question.
            primaryButton = (
                <Button marginLeft={3} variant="primary" size="large" onClick={handleRemoveRecord}>
                    Next question
                </Button>
            );
        } else {
            primaryButton = (
                <Button
                    marginLeft={3}
                    variant="primary"
                    size="large"
                    onClick={() => setShouldShowAnswer(true)}
                >
                    Show answer
                </Button>
            );
        }
    } else {
        // No records left.
        primaryButton = (
            <Button marginLeft={3} variant="primary" size="large" icon="redo" onClick={reset}>
                Start over
            </Button>
        );
    }

    return (
        <Fragment>
            <Flashcard record={record} settings={settings} shouldShowAnswer={shouldShowAnswer} />
            <Box flex="none" borderTop="thick" display="flex" marginX={3} paddingY={3}>
                {record && (
                    <Button icon="expand" variant="secondary" onClick={() => expandRecord(record)}>
                        Expand record
                    </Button>
                )}
                <Box flexGrow={1} />
                {primaryButton}
            </Box>
        </Fragment>
    );
}
Example #15
Source File: send-messages.js    From neighbor-express with MIT License 4 votes vote down vote up
export function SendMessagesStep({ previousStep }) {
  const [completed, setCompleted] = useState(false);
  const [sending, setSending] = useState(false);
  const [progress, setProgress] = useState(0);

  const messagesTable = useBase().getTable("Messages");
  const statusField = messagesTable.getFieldByName("Status");
  const queuedOption = statusField.options.choices.find(
    (c) => c.name == "Queued"
  );

  const messagesToSend = useRecords(messagesTable).filter(
    (m) => m.getCellValue("Status").name === "Queued"
  );

  async function sendMessages() {
    setCompleted(false);
    setSending(true);
    const total = messagesToSend.length;
    let i = 0;
    for (const messageToSend of messagesToSend) {
      const response = await sendMessage(messageToSend);

      if (response.status === 202) {
        messagesTable.updateRecordAsync(messageToSend.id, {
          Status: { name: "Sent" },
          "Sent Time": new Date(),
        });
      } else {
        messagesTable.updateRecordAsync(messageToSend.id, {
          Status: { name: "Errored" },
        });
      }
      i += 1;
      setProgress(i / total);
    }
    setSending(false);
    setCompleted(true);
  }

  async function goBack() {
    // Clear any non-persistent data before leaving
    setCompleted(false);
    setSending(false);
    setProgress(0);
    previousStep();
  }

  return (
    <Box>
      <h2> Step 2: Send emails </h2>
      {(sending || completed) && <ProgressBar progress={progress} />}
      {completed && <p> Successfully sent all messages </p>}
      {messagesToSend.length === 0 ? (
        <p>
          {" "}
          There are no messages with status{" "}
          <ChoiceToken choice={queuedOption} marginRight={1} />{" "}
        </p>
      ) : (
        <>
          <Box
            margin={2}
            display="flex"
            flexDirection="row"
            justifyContent="flex-start"
            alignItems="center"
          >
            <Text>
              {" "}
              The following {messagesToSend.length} messages will be sent:
            </Text>
            <Button
              marginX={2}
              variant="primary"
              onClick={sendMessages}
              disabled={sending}
            >
              Send All Messages
            </Button>
          </Box>
          <Box height="300px" border="thick" backgroundColor="lightGray1">
            <RecordCardList
              records={messagesToSend}
              fields={["Email type", "Recipient", "Delivery"].map((f) =>
                messagesTable.getFieldByName(f)
              )}
            />
          </Box>
        </>
      )}
      <Button onClick={goBack}> Go Back </Button>
    </Box>
  );
}
Example #16
Source File: settings.js    From neighbor-express with MIT License 4 votes vote down vote up
export function SettingsComponent({ exit }) {
  const globalConfig = useGlobalConfig();

  if (globalConfig.get("template_variables") === undefined) {
    globalConfig.setAsync("template_variables", {});
  }

  if (globalConfig.get("email_types") === undefined) {
    globalConfig.setAsync("email_types", {});
  }

  return (
    <Box padding={3}>
      <Button variant="primary" onClick={exit} style={{ float: "right" }}>
        Exit settings
      </Button>
      <h1> Settings </h1>
      <p>
        {" "}
        You probably won't need to do anything here unless you're just starting
        out.{" "}
      </p>
      <Accordion title="Global">
        <InputSetter
          label="Organization name"
          description="When people reply to your emails, what is the name they will see?"
          keyOrPath={["reply_to", "name"]}
        />
        <InputSetter
          label="Reply email"
          description="What email address should people use to reply to your emails?"
          keyOrPath={["reply_to", "email"]}
        />
        <InputSetter
          label="Sendgrid proxy token"
          description="This is a secret token that is used to authenticate sending the email"
          keyOrPath="SENDGRID_PROXY_TOKEN"
        />
        <FieldSetter
          label="Trigger column"
          description="Which column should be used to determine whether an email is sent?"
          keyOrPath="trigger_column"
          tableName="Deliveries"
          allowedTypes={[FieldType.SINGLE_SELECT]}
        />
      </Accordion>
      <Accordion title="Email Types">
        <h4>Delivery Emails</h4>
        <Text>
          Here you can configure emails to go out at various stages of a
          delivery. Emails can be set up for both the delivery recipient and the
          volunteer.
        </Text>
        {Object.keys(globalConfig.get("email_types")).map((emailType) => {
          return <EmailTypeSettings key={emailType} emailType={emailType} />;
        })}
        <AddEmailTypeDialog />
        <Box>
          <h4>Volunteer Emails</h4>
          <Text>
            Enable the setting below if you want to send volunteers a welcome
            email when they first sign up.
          </Text>
          <Switch
            value={!!globalConfig.get("enable_volunteer_welcome_email")}
            onChange={(newValue) =>
              globalConfig.setAsync("enable_volunteer_welcome_email", newValue)
            }
            label="Email volunteers on inital signup"
            width="320px"
          />
          {globalConfig.get("enable_volunteer_welcome_email") ? (
            <>
              <Label htmlFor="volunteer-welcome-template-id-input">
                Volunteer welcome email sendgrid template ID
              </Label>
              <Input
                id="volunteer-welcome-template-id-input"
                value={
                  globalConfig.get("volunteer_welcome_email_template_id") || ""
                }
                onChange={(e) =>
                  globalConfig.setAsync(
                    "volunteer_welcome_email_template_id",
                    e.target.value
                  )
                }
                placeholder="Enter Sendgrid Template ID here"
              />
              <FieldSetter
                label="Volunteer name field"
                description="The field containing volunteers names"
                keyOrPath="volunteer_name_field"
                tableName="Volunteers"
              />
            </>
          ) : null}
        </Box>
      </Accordion>
      <Accordion title="Template Variables">
        {["Deliveries", "Volunteers"].map((tableName) => {
          return (
            <TableTemplateVariables key={tableName} tableName={tableName} />
          );
        })}
      </Accordion>
    </Box>
  );
}