@airtable/blocks/ui#Text JavaScript Examples

The following examples show how to use @airtable/blocks/ui#Text. 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: HighlightWrapper.js    From apps-base-schema with MIT License 6 votes vote down vote up
/**
 * Displays a small tooltip in the bottom-left corner that shows the type of the currently
 * hovered link or field.
 *
 * Uses `ReactDOM#createPortal` to lift the HTMLElements out of SVG world.
 *
 * @param {string} props.text
 */
function Tooltip({text}) {
    const tooltipRoot = document.getElementById('index');
    return ReactDOM.createPortal(
        <Box
            position="absolute"
            bottom={0}
            left={0}
            margin={2}
            backgroundColor={colors.GRAY_DARK_1}
            borderRadius="default"
        >
            <Text padding={2} textColor="white">
                {text}
            </Text>
        </Box>,
        tooltipRoot,
    );
}
Example #2
Source File: CustomCellRenderer.js    From apps-flashcard with MIT License 6 votes vote down vote up
/**
 * Handles text and attachments to make them larger, but falls back to cell renderer for other
 * field types.
 */
export default function CustomCellRenderer({record, field}) {
    switch (field.type) {
        case FieldType.RICH_TEXT: {
            return <CellRenderer record={record} field={field} />;
        }
        case FieldType.MULTIPLE_ATTACHMENTS: {
            const attachmentCellValue = record.getCellValue(field);

            let attachmentObj;
            if (attachmentCellValue && attachmentCellValue.length > 0) {
                // Try to get the first attachment object from the cell value.
                attachmentObj = attachmentCellValue[attachmentCellValue.length - 1];
            }

            if (!attachmentObj || !attachmentObj.thumbnails || !attachmentObj.thumbnails.large) {
                // If there are no attachments present, use the default cell renderer
                return <CellRenderer record={record} field={field} />;
            }
            return <img src={attachmentObj.thumbnails.large.url} height="150px" />;
        }
        default: {
            return (
                <Text width="100%" size="xlarge">
                    {record.getCellValueAsString(field)}
                </Text>
            );
        }
    }
}
Example #3
Source File: Flashcard.js    From apps-flashcard with MIT License 6 votes vote down vote up
export default function Flashcard({record, settings, shouldShowAnswer}) {
    return (
        <Box display="flex" flex="auto" overflowY="auto">
            <Box marginY="auto" padding={5} width="100%">
                <Box display="flex" paddingBottom={2} borderBottom="default">
                    {record ? (
                        <CustomCellRenderer field={settings.questionField} record={record} />
                    ) : (
                        <Text width="100%" size="xlarge" textAlign="center">
                            All done!
                        </Text>
                    )}
                </Box>
                <Box display="flex" paddingTop={2}>
                    {settings.answerField && record && shouldShowAnswer ? (
                        <CustomCellRenderer field={settings.answerField} record={record} />
                    ) : null}
                </Box>
            </Box>
        </Box>
    );
}
Example #4
Source File: UpdateRecordsApp.js    From apps-update-records with MIT License 6 votes vote down vote up
function UpdateRecordsApp() {
    const base = useBase();
    const cursor = useCursor();

    const tableToUpdate = base.getTableByName(TABLE_NAME);

    const numberField = tableToUpdate.getFieldByName(FIELD_NAME);

    // cursor.selectedRecordIds isn't loaded by default, so we need to load it
    // explicitly with the useLoadable hook. The rest of the code in the
    // component will not run until it has loaded.
    useLoadable(cursor);

    // Re-render the app whenever the selected records change.
    useWatchable(cursor, ['selectedRecordIds']);

    if (cursor.activeTableId !== tableToUpdate.id) {
        return (
            <Container>
                <Text>Switch to the “{tableToUpdate.name}” table to use this app.</Text>
            </Container>
        );
    }

    return (
        <Container>
            <UpdateSelectedRecordsButton
                tableToUpdate={tableToUpdate}
                fieldToUpdate={numberField}
                selectedRecordIds={cursor.selectedRecordIds}
            />
        </Container>
    );
}
Example #5
Source File: index.js    From apps-flashcard with MIT License 5 votes vote down vote up
/**
 * A simple flashcard app that displays records from a chosen view.
 * Supports choosing a question field which is displayed by default and an optional answer field that
 * is hidden until shown by the user.
 */
function FlashcardApp() {
    const {isValid, message, settings} = useSettings();
    const [isSettingsVisible, setIsSettingsVisible] = useState(false);
    useSettingsButton(() => {
        if (!isSettingsVisible) {
            viewport.enterFullscreenIfPossible();
        }
        setIsSettingsVisible(!isSettingsVisible);
    });

    // Open the SettingsForm whenever the settings are not valid
    useEffect(() => {
        if (!isValid) {
            setIsSettingsVisible(true);
        }
    }, [isValid]);

    const records = useRecords(settings.view);

    return (
        <Box position="absolute" top="0" left="0" bottom="0" right="0" display="flex">
            <Box display="flex" flexDirection="column" flex="auto">
                {isValid ? (
                    <FlashcardContainer records={records} settings={settings} />
                ) : (
                    <Box display="flex" flex="auto" alignItems="center" justifyContent="center">
                        <Text textColor="light">{message}</Text>
                    </Box>
                )}
            </Box>
            {isSettingsVisible && (
                <SettingsForm setIsSettingsVisible={setIsSettingsVisible} settings={settings} />
            )}
        </Box>
    );
}
Example #6
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 #7
Source File: index.js    From apps-url-preview with MIT License 5 votes vote down vote up
// Shows a preview, or a dialog that displays information about what
// kind of services (URLs) are supported by this app.
function RecordPreviewWithDialog({
    activeTable,
    selectedRecordId,
    selectedFieldId,
    setIsSettingsOpen,
}) {
    const [isDialogOpen, setIsDialogOpen] = useState(false);

    // Close the dialog when the selected record is changed.
    // The new record might have a preview, so we don't want to hide it behind this dialog.
    useEffect(() => {
        setIsDialogOpen(false);
    }, [selectedRecordId]);

    return (
        <Fragment>
            <Box
                position="absolute"
                top={0}
                left={0}
                right={0}
                bottom={0}
                display="flex"
                flexDirection="column"
                alignItems="center"
                justifyContent="center"
            >
                <RecordPreview
                    activeTable={activeTable}
                    selectedRecordId={selectedRecordId}
                    selectedFieldId={selectedFieldId}
                    setIsDialogOpen={setIsDialogOpen}
                    setIsSettingsOpen={setIsSettingsOpen}
                />
            </Box>
            {isDialogOpen && (
                <Dialog onClose={() => setIsDialogOpen(false)} maxWidth={400}>
                    <Dialog.CloseButton />
                    <Heading size="small">Supported services</Heading>
                    <Text marginTop={2}>Previews are supported for these services:</Text>
                    <Text marginTop={2}>
                        <Link
                            href="https://support.airtable.com/hc/en-us/articles/205752117-Creating-a-base-share-link-or-a-view-share-link"
                            target="_blank"
                        >
                            Airtable share links
                        </Link>
                        , Figma, SoundCloud, Spotify, Vimeo, YouTube, Loom share links, Google Drive
                        share links, Google Docs, Google Sheets, Google Slides
                    </Text>
                    <Link
                        marginTop={2}
                        href="https://airtable.com/shrQSwIety6rqfJZX"
                        target="_blank"
                    >
                        Request a new service
                    </Link>
                </Dialog>
            )}
        </Fragment>
    );
}
Example #8
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 #9
Source File: settings.js    From neighbor-express with MIT License 5 votes vote down vote up
function TableTemplateVariables({ tableName }) {
  const globalConfig = useGlobalConfig();
  if (globalConfig.get(["template_variables", tableName]) === undefined) {
    globalConfig.setAsync(["template_variables", tableName], {});
  }
  const base = useBase();
  const table = base.getTableByNameIfExists(tableName);

  const tableNameToSendgrid = {
    Deliveries: "delivery",
    Volunteers: "volunteer",
  };

  return (
    <Box padding={3}>
      <Text>
        {" "}
        You can use these fields from the {table.name} table in sendgrid{" "}
      </Text>
      <ul>
        {Object.keys(globalConfig.get(["template_variables", table.name])).map(
          (f_id) => {
            const field = table.getFieldIfExists(f_id);
            // this should be deletable
            const sendgridValue = globalConfig.get([
              "template_variables",
              table.name,
              f_id,
            ]);
            const sendgridFormat = `{{${tableNameToSendgrid[tableName]}.${sendgridValue}}}`;
            const removeLink = (
              <TextButton
                onClick={() => {
                  globalConfig.setAsync(
                    ["template_variables", table.name, f_id],
                    undefined
                  );
                }}
              >
                (remove)
              </TextButton>
            );
            return (
              <li key={f_id}>
                {" "}
                {field.name} -> {sendgridFormat} {removeLink}
              </li>
            );
          }
        )}
      </ul>
      <AddTemplateVariableDialog table={table} />
    </Box>
  );
}
Example #10
Source File: index.js    From apps-print-records with MIT License 4 votes vote down vote up
// Renders a single record from the Collections table with each
// of its linked Artists records.
function Record({record}) {
    const base = useBase();

    // Each record in the "Collections" table is linked to records
    // in the "Artists" table. We want to show the Artists for
    // each collection.
    const linkedTable = base.getTableByName('Artists');
    const linkedRecords = useRecords(
        record.selectLinkedRecordsFromCell('Artists', {
            // Keep the linked records sorted by their primary field.
            sorts: [{field: linkedTable.primaryField, direction: 'asc'}],
        }),
    );

    return (
        <Box marginY={3}>
            <Heading>{record.name}</Heading>
            <table style={{borderCollapse: 'collapse', width: '100%'}}>
                <thead>
                    <tr>
                        <td
                            style={{
                                whiteSpace: 'nowrap',
                                verticalAlign: 'bottom',
                            }}
                        >
                            <Heading variant="caps" size="xsmall" marginRight={3} marginBottom={0}>
                                On display?
                            </Heading>
                        </td>
                        <td style={{width: '50%', verticalAlign: 'bottom'}}>
                            <Heading variant="caps" size="xsmall" marginRight={3} marginBottom={0}>
                                Artist name
                            </Heading>
                        </td>
                        <td style={{width: '50%', verticalAlign: 'bottom'}}>
                            <Heading variant="caps" size="xsmall" marginBottom={0}>
                                Artworks
                            </Heading>
                        </td>
                    </tr>
                </thead>
                <tbody>
                    {linkedRecords.map(linkedRecord => {
                        // Render a check or an x depending on if the artist is on display or not.
                        const isArtistOnDisplay = linkedRecord.getCellValue('On Display?');
                        return (
                            <tr key={linkedRecord.id} style={{borderTop: '2px solid #ddd'}}>
                                <td style={{textAlign: 'center', whiteSpace: 'nowrap'}}>
                                    <Box
                                        display="inline-flex"
                                        alignItems="center"
                                        justifyContent="center"
                                        width="16px"
                                        height="16px"
                                        marginRight={3}
                                        borderRadius="100%"
                                        backgroundColor={isArtistOnDisplay ? 'green' : 'red'}
                                        textColor="white"
                                    >
                                        <Icon name={isArtistOnDisplay ? 'check' : 'x'} size={12} />
                                    </Box>
                                </td>
                                <td style={{width: '50%'}}>
                                    <Text marginRight={3}>{linkedRecord.name}</Text>
                                </td>
                                <td style={{width: '50%'}}>
                                    <CellRenderer
                                        record={linkedRecord}
                                        field={linkedTable.getFieldByName('Attachments')}
                                    />
                                </td>
                            </tr>
                        );
                    })}
                </tbody>
            </table>
        </Box>
    );
}
Example #11
Source File: index.js    From apps-url-preview with MIT License 4 votes vote down vote up
// How this app chooses a preview to show:
//
// Without a specified Table & Field:
//
//  - When the user selects a cell in grid view and the field's content is
//    a supported preview URL, the app uses this URL to construct an embed
//    URL and inserts this URL into an iframe.
//
// To Specify a Table & Field:
//
//  - The user may use "Settings" to toggle a specified table and specified
//    field constraint. If the constraint switch is set to "Yes",he user must
//    set a specified table and specified field for URL previews.
//
// With a specified table & specified field:
//
//  - When the user selects a cell in grid view and the active table matches
//    the specified table or when the user opens a record from a button field
//    in the specified table:
//    The app looks in the selected record for the
//    specified field containing a supported URL (e.g. https://www.youtube.com/watch?v=KYz2wyBy3kc),
//    and uses this URL to construct an embed URL and inserts this URL into
//    an iframe.
//
function UrlPreviewApp() {
    const [isSettingsOpen, setIsSettingsOpen] = useState(false);
    useSettingsButton(() => setIsSettingsOpen(!isSettingsOpen));

    const {
        isValid,
        settings: {isEnforced, urlTable},
    } = useSettings();

    // Caches the currently selected record and field in state. If the user
    // selects a record and a preview appears, and then the user de-selects the
    // record (but does not select another), the preview will remain. This is
    // useful when, for example, the user resizes the apps pane.
    const [selectedRecordId, setSelectedRecordId] = useState(null);
    const [selectedFieldId, setSelectedFieldId] = useState(null);

    const [recordActionErrorMessage, setRecordActionErrorMessage] = useState('');

    // cursor.selectedRecordIds and selectedFieldIds aren't loaded by default,
    // so we need to load them explicitly with the useLoadable hook. The rest of
    // the code in the component will not run until they are loaded.
    useLoadable(cursor);

    // Update the selectedRecordId and selectedFieldId state when the selected
    // record or field change.
    useWatchable(cursor, ['selectedRecordIds', 'selectedFieldIds'], () => {
        // If the update was triggered by a record being de-selected,
        // the current selectedRecordId will be retained.  This is
        // what enables the caching described above.
        if (cursor.selectedRecordIds.length > 0) {
            // There might be multiple selected records. We'll use the first
            // one.
            setSelectedRecordId(cursor.selectedRecordIds[0]);
        }
        if (cursor.selectedFieldIds.length > 0) {
            // There might be multiple selected fields. We'll use the first
            // one.
            setSelectedFieldId(cursor.selectedFieldIds[0]);
        }
    });

    // Close the record action error dialog whenever settings are opened or the selected record
    // is updated. (This means you don't have to close the modal to see the settings, or when
    // you've opened a different record.)
    useEffect(() => {
        setRecordActionErrorMessage('');
    }, [isSettingsOpen, selectedRecordId]);

    // Register a callback to be called whenever a record action occurs (via button field)
    // useCallback is used to memoize the callback, to avoid having to register/unregister
    // it unnecessarily.
    const onRecordAction = useCallback(
        data => {
            // Ignore the event if settings are already open.
            // This means we can assume settings are valid (since we force settings to be open if
            // they are invalid).
            if (!isSettingsOpen) {
                if (isEnforced) {
                    if (data.tableId === urlTable.id) {
                        setSelectedRecordId(data.recordId);
                    } else {
                        // Record is from a mismatching table.
                        setRecordActionErrorMessage(
                            `This app is set up to preview URLs using records from the "${urlTable.name}" table, but was opened from a different table.`,
                        );
                    }
                } else {
                    // Preview is not supported in this case, as we wouldn't know what field to preview.
                    // Show a dialog to the user instead.
                    setRecordActionErrorMessage(
                        'You must enable "Use a specific field for previews" to preview URLs with a button field.',
                    );
                }
            }
        },
        [isSettingsOpen, isEnforced, urlTable],
    );
    useEffect(() => {
        // Return the unsubscribe function to ensure we clean up the handler.
        return registerRecordActionDataCallback(onRecordAction);
    }, [onRecordAction]);

    // This watch deletes the cached selectedRecordId and selectedFieldId when
    // the user moves to a new table or view. This prevents the following
    // scenario: User selects a record that contains a preview url. The preview appears.
    // User switches to a different table. The preview disappears. The user
    // switches back to the original table. Weirdly, the previously viewed preview
    // reappears, even though no record is selected.
    useWatchable(cursor, ['activeTableId', 'activeViewId'], () => {
        setSelectedRecordId(null);
        setSelectedFieldId(null);
    });

    const base = useBase();
    const activeTable = base.getTableByIdIfExists(cursor.activeTableId);

    useEffect(() => {
        // Display the settings form if the settings aren't valid.
        if (!isValid && !isSettingsOpen) {
            setIsSettingsOpen(true);
        }
    }, [isValid, isSettingsOpen]);

    // activeTable is briefly null when switching to a newly created table.
    if (!activeTable) {
        return null;
    }

    return (
        <Box>
            {isSettingsOpen ? (
                <SettingsForm setIsSettingsOpen={setIsSettingsOpen} />
            ) : (
                <RecordPreviewWithDialog
                    activeTable={activeTable}
                    selectedRecordId={selectedRecordId}
                    selectedFieldId={selectedFieldId}
                    setIsSettingsOpen={setIsSettingsOpen}
                />
            )}
            {recordActionErrorMessage && (
                <Dialog onClose={() => setRecordActionErrorMessage('')} maxWidth={400}>
                    <Dialog.CloseButton />
                    <Heading size="small">Can&apos;t preview URL</Heading>
                    <Text variant="paragraph" marginBottom={0}>
                        {recordActionErrorMessage}
                    </Text>
                </Dialog>
            )}
        </Box>
    );
}
Example #12
Source File: index.js    From apps-url-preview with MIT License 4 votes vote down vote up
// Shows a preview, or a message about what the user should do to see a preview.
function RecordPreview({
    activeTable,
    selectedRecordId,
    selectedFieldId,
    setIsDialogOpen,
    setIsSettingsOpen,
}) {
    const {
        settings: {isEnforced, urlField, urlTable},
    } = useSettings();

    const table = (isEnforced && urlTable) || activeTable;

    // We use getFieldByIdIfExists because the field might be deleted.
    const selectedField = selectedFieldId ? table.getFieldByIdIfExists(selectedFieldId) : null;
    // When using a specific field for previews is enabled and that field exists,
    // use the selectedField
    const previewField = (isEnforced && urlField) || selectedField;
    // Triggers a re-render if the record changes. Preview URL cell value
    // might have changed, or record might have been deleted.
    const selectedRecord = useRecordById(table, selectedRecordId ? selectedRecordId : '', {
        fields: [previewField],
    });

    // Triggers a re-render if the user switches table or view.
    // RecordPreview may now need to render a preview, or render nothing at all.
    useWatchable(cursor, ['activeTableId', 'activeViewId']);

    // This button is re-used in two states so it's pulled out in a constant here.
    const viewSupportedURLsButton = (
        <TextButton size="small" marginTop={3} onClick={() => setIsDialogOpen(true)}>
            View supported URLs
        </TextButton>
    );

    if (
        // If there is/was a specified table enforced, but the cursor
        // is not presently in the specified table, display a message to the user.
        // Exception: selected record is from the specified table (has been opened
        // via button field or other means while cursor is on a different table.)
        isEnforced &&
        cursor.activeTableId !== table.id &&
        !(selectedRecord && selectedRecord.parentTable.id === table.id)
    ) {
        return (
            <Fragment>
                <Text paddingX={3}>Switch to the “{table.name}” table to see previews.</Text>
                <TextButton size="small" marginTop={3} onClick={() => setIsSettingsOpen(true)}>
                    Settings
                </TextButton>
            </Fragment>
        );
    } else if (
        // activeViewId is briefly null when switching views
        selectedRecord === null &&
        (cursor.activeViewId === null ||
            table.getViewById(cursor.activeViewId).type !== ViewType.GRID)
    ) {
        return <Text>Switch to a grid view to see previews</Text>;
    } else if (
        // selectedRecord will be null on app initialization, after
        // the user switches table or view, or if it was deleted.
        selectedRecord === null ||
        // The preview field may have been deleted.
        previewField === null
    ) {
        return (
            <Fragment>
                <Text>Select a cell to see a preview</Text>
                {viewSupportedURLsButton}
            </Fragment>
        );
    } else {
        // Using getCellValueAsString guarantees we get a string back. If
        // we use getCellValue, we might get back numbers, booleans, or
        // arrays depending on the field type.
        const cellValue = selectedRecord.getCellValueAsString(previewField);

        if (!cellValue) {
            return (
                <Fragment>
                    <Text>The “{previewField.name}” field is empty</Text>
                    {viewSupportedURLsButton}
                </Fragment>
            );
        } else {
            const previewUrl = getPreviewUrlForCellValue(cellValue);

            // In this case, the FIELD_NAME field of the currently selected
            // record either contains no URL, or contains a that cannot be
            // resolved to a supported preview.
            if (!previewUrl) {
                return (
                    <Fragment>
                        <Text>No preview</Text>
                        {viewSupportedURLsButton}
                    </Fragment>
                );
            } else {
                return (
                    <iframe
                        // Using `key=previewUrl` will immediately unmount the
                        // old iframe when we're switching to a new
                        // preview. Otherwise, the old iframe would be reused,
                        // and the old preview would stay onscreen while the new
                        // one was loading, which would be a confusing user
                        // experience.
                        key={previewUrl}
                        style={{flex: 'auto', width: '100%'}}
                        src={previewUrl}
                        frameBorder="0"
                        allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
                        allowFullScreen
                    />
                );
            }
        }
    }
}
Example #13
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 #14
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>
  );
}