diff --git a/graphql2/schema.graphql b/graphql2/schema.graphql
index 8588b115b8..e0b4036c99 100644
--- a/graphql2/schema.graphql
+++ b/graphql2/schema.graphql
@@ -1254,7 +1254,7 @@ type UserContactMethod {
value: String!
@deprecated(reason: "Use dest instead.")
@goField(forceResolver: true)
- formattedValue: String!
+ formattedValue: String! @deprecated(reason: "Use dest.displayInfo instead.")
disabled: Boolean!
pending: Boolean!
diff --git a/web/src/app/users/SendTestDialog.tsx b/web/src/app/users/SendTestDialog.tsx
index abb4b4577e..8ab7718b21 100644
--- a/web/src/app/users/SendTestDialog.tsx
+++ b/web/src/app/users/SendTestDialog.tsx
@@ -1,8 +1,6 @@
import React, { useEffect, MouseEvent, useState } from 'react'
import { gql, useQuery, useMutation } from 'urql'
-
import Spinner from '../loading/components/Spinner'
-
import {
Button,
Dialog,
@@ -12,16 +10,23 @@ import {
DialogContentText,
} from '@mui/material'
import toTitleCase from '../util/toTitleCase'
-import DialogContentError from '../dialogs/components/DialogContentError'
-import { DateTime } from 'luxon'
-import { ContactMethodType, UserContactMethod } from '../../schema'
+import { DateTime, Duration } from 'luxon'
+import {
+ DestinationInput,
+ NotificationState,
+ UserContactMethod,
+} from '../../schema'
+import DestinationInputChip from '../util/DestinationInputChip'
+import { Time } from '../util/Time'
const query = gql`
query ($id: ID!) {
userContactMethod(id: $id) {
id
- type
- formattedValue
+ dest {
+ type
+ args
+ }
lastTestVerifyAt
lastTestMessageState {
details
@@ -38,103 +43,137 @@ const mutation = gql`
}
`
+function getTestStatusColor(status?: string | null): string {
+ switch (status) {
+ case 'OK':
+ return 'success'
+ case 'ERROR':
+ return 'error'
+ default:
+ return 'warning'
+ }
+}
+
+type SendTestContentProps = {
+ dest: DestinationInput
+ isSending: boolean
+ isWaiting: boolean
+
+ sentTime?: string | null
+ sentState?: NotificationState | null
+}
+
+export function SendTestContent(props: SendTestContentProps): React.ReactNode {
+ return (
+
+
+
+ GoAlert is sending a test to{' '}
+ .
+
+
+ {props.sentTime && (
+
+ The test message was scheduled for delivery at{' '}
+ .
+
+ )}
+ {props.sentState?.formattedSrcValue && (
+
+ Sender: {props.sentState.formattedSrcValue}
+
+ )}
+
+ {props.isWaiting && }
+ {props.isSending && }
+ {props.sentState?.details === 'Pending' ? (
+
+ ) : (
+
+ Status: {toTitleCase(props.sentState?.details || '')}
+
+ )}
+
+
+ )
+}
+
export default function SendTestDialog(
props: SendTestDialogProps,
): JSX.Element {
- const { title = 'Test Delivery Status', onClose, messageID } = props
-
+ const { onClose, contactMethodID } = props
const [sendTestStatus, sendTest] = useMutation(mutation)
- const [{ data, fetching, error }] = useQuery<{
+ const [cmInfo, refreshCMInfo] = useQuery<{
userContactMethod: UserContactMethod
}>({
query,
variables: {
- id: messageID,
+ id: contactMethodID,
},
requestPolicy: 'network-only',
})
- // We keep a stable timestamp to track how long the dialog has been open
- const [now] = useState(DateTime.utc())
- const status = data?.userContactMethod?.lastTestMessageState?.status ?? ''
- const cmDestValue = data?.userContactMethod?.formattedValue ?? ''
- const cmType: ContactMethodType | '' = data?.userContactMethod.type ?? ''
- const lastSent = data?.userContactMethod?.lastTestVerifyAt
- ? DateTime.fromISO(data.userContactMethod.lastTestVerifyAt)
- : now.plus({ day: -1 })
- const fromValue =
- data?.userContactMethod?.lastTestMessageState?.formattedSrcValue ?? ''
- const errorMessage = (error?.message || sendTestStatus.error?.message) ?? ''
-
- const hasSent =
- Boolean(data?.userContactMethod.lastTestMessageState) &&
- now.diff(lastSent).as('seconds') < 60
+ // Should not happen, but just in case.
+ if (cmInfo.error) throw cmInfo.error
+ const cm = cmInfo.data?.userContactMethod
+ if (!cm) throw new Error('missing contact method') // should be impossible (since we already checked the error)
+
+ // We expect the status to update over time, so we manually refresh
+ // as long as the dialog is open.
useEffect(() => {
- if (fetching || !data?.userContactMethod) return
- if (errorMessage || sendTestStatus.data) return
-
- if (hasSent) return
-
- sendTest({ id: messageID })
- }, [lastSent.toISO(), fetching, data, errorMessage, sendTestStatus.data])
-
- const details =
- (hasSent && data?.userContactMethod?.lastTestMessageState?.details) || ''
-
- const isLoading =
- sendTestStatus.fetching ||
- (!!details && !!errorMessage) ||
- ['pending', 'sending'].includes(details.toLowerCase())
-
- const getTestStatusColor = (status: string): string => {
- switch (status) {
- case 'OK':
- return 'success'
- case 'ERROR':
- return 'error'
- default:
- return 'warning'
- }
- }
+ const t = setInterval(refreshCMInfo, 3000)
+ return () => clearInterval(t)
+ }, [])
- const msg = (): string => {
- switch (cmType) {
- case 'SMS':
- case 'VOICE':
- return `${
- cmType === 'SMS' ? 'SMS message' : 'voice call'
- } to ${cmDestValue}`
- case 'EMAIL':
- return `email to ${cmDestValue}`
- default:
- return `to ${cmDestValue}`
- }
- }
+ // We keep a stable timestamp to track how long the dialog has been open.
+ const [now] = useState(DateTime.utc())
- return (
-