@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 |
// 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 |
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 |
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 |
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 |
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 |
/**
* 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
/**
* 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 |
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 |
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>
);
}