Merge pull request #1666 from omnivore-app/fix/newsletter-email-metadata

Show email address metadata on the /settings/emails page
This commit is contained in:
Jackson Harper
2023-01-17 20:56:49 +08:00
committed by GitHub
7 changed files with 526 additions and 373 deletions

View File

@ -1,6 +1,6 @@
import Link from 'next/link'
import { Info } from 'phosphor-react'
import { Box } from '../elements/LayoutPrimitives'
import { Box, VStack } from '../elements/LayoutPrimitives'
import { theme } from '../tokens/stitches.config'
import { TooltipWrapped } from './Tooltip'
@ -15,9 +15,12 @@ const TooltipStyle = {
export function InfoLink(props: InfoLinkProps): JSX.Element {
return (
<Box style={{ flex: '1', marginLeft: '9px' }}>
<Link passHref href={props.href}>
<a style={{ textDecoration: 'none' }}>
<VStack
css={{
marginLeft: '10px',
}}
>
<a href={props.href} style={{ textDecoration: 'none' }}>
<TooltipWrapped
tooltipContent="Learn More"
tooltipSide={'top'}
@ -27,7 +30,6 @@ export function InfoLink(props: InfoLinkProps): JSX.Element {
<Info size={24} color={theme.colors.grayText.toString()} />
</TooltipWrapped>
</a>
</Link>
</Box>
</VStack>
)
}

View File

@ -0,0 +1,306 @@
import Link from 'next/link'
import { Plus, Trash } from 'phosphor-react'
import { Toaster } from 'react-hot-toast'
import { Button } from '../../elements/Button'
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
import { MoreOptionsIcon } from '../../elements/images/MoreOptionsIcon'
import { InfoLink } from '../../elements/InfoLink'
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { StyledText } from '../../elements/StyledText'
import { styled, theme } from '../../tokens/stitches.config'
import { PrimaryLayout } from '../PrimaryLayout'
type SettingsTableProps = {
pageId: string
pageHeadline: string
pageInfoLink: string
headerTitle: string
createTitle?: string
createAction?: () => void
children: React.ReactNode
}
type CreateButtonProps = {
title: string
action: () => void
}
type SettingsTableRowProps = {
key: string
title: string
isLast: boolean
sublineElement: JSX.Element
titleElement?: JSX.Element
extraElement?: JSX.Element
deleteTitle: string
onDelete: () => void
}
type MoreOptionsProps = {
title: string
onDelete: () => void
}
const MoreOptions = (props: MoreOptionsProps) => (
<Dropdown
align={'end'}
triggerElement={
<Box
css={{
'&:hover': {
cursor: 'pointer',
},
}}
>
<MoreOptionsIcon
size={24}
strokeColor={theme.colors.grayTextContrast.toString()}
orientation="horizontal"
/>
</Box>
}
>
<DropdownOption
onSelect={() => {
return true
}}
>
<HStack alignment={'center'} distribution={'start'}>
<Trash size={24} color={theme.colors.omnivoreRed.toString()} />
<Button
css={{
color: theme.colors.omnivoreRed.toString(),
marginLeft: '8px',
border: 'none',
backgroundColor: 'transparent',
'&:hover': {
border: 'none',
backgroundColor: 'transparent',
},
}}
onClick={props.onDelete}
>
{props.title}
</Button>
</HStack>
</DropdownOption>
</Dropdown>
)
type EmptySettingsRowProps = {
text: string
}
export const EmptySettingsRow = (props: EmptySettingsRowProps): JSX.Element => {
return (
<Box
css={{
backgroundColor: '$grayBg',
display: 'flex',
alignItems: 'center',
padding: '10px 12px',
border: '0.5px solid $grayBgActive',
width: '100%',
borderBottomLeftRadius: '5px',
borderBottomRightRadius: '5px',
'@md': {
paddingLeft: '0',
},
justifyContent: 'center',
}}
>
{props.text}
</Box>
)
}
export const SettingsTableRow = (props: SettingsTableRowProps): JSX.Element => {
return (
<Box
key={props.key}
css={{
backgroundColor: '$grayBg',
display: 'flex',
alignItems: 'center',
padding: '10px 12px',
border: '0.5px solid $grayBgActive',
width: '100%',
'&:hover': {
background: 'rgba(255, 234, 159, 0.12)',
},
borderBottomLeftRadius: props.isLast ? '5px' : '',
borderBottomRightRadius: props.isLast ? '5px' : '',
'@md': {
paddingLeft: '0',
},
}}
>
<Box
css={{
display: 'flex',
width: '100%',
flexDirection: 'column',
'@md': {
paddingLeft: '20px',
flexDirection: 'row',
},
}}
>
<HStack
distribution="start"
css={{
display: 'flex',
padding: '4px 4px 4px 0px',
'@md': {
width: '70%',
},
}}
>
<VStack>
<StyledText
css={{
m: '0px',
fontSize: '18px',
'@mdDown': {
fontSize: '12px',
fontWeight: 'bold',
},
}}
>
{props.title}
</StyledText>
{props.sublineElement}
</VStack>
<SpanBox css={{ marginLeft: 'auto' }}>{props.titleElement}</SpanBox>
<Box
css={{
marginLeft: 'auto',
textAlign: 'right',
display: 'flex',
'@md': {
display: 'none',
},
}}
>
<MoreOptions title={props.deleteTitle} onDelete={props.onDelete} />
</Box>
</HStack>
{props.extraElement}
</Box>
<HStack distribution="start" css={{ marginLeft: 'auto' }}>
<Box
css={{
textAlign: 'right',
display: 'none',
'@md': {
display: 'flex',
},
}}
>
<MoreOptions title={props.deleteTitle} onDelete={props.onDelete} />
</Box>
</HStack>
</Box>
)
}
const CreateButton = (props: CreateButtonProps): JSX.Element => {
return (
<Button
onClick={props.action}
style="ctaDarkYellow"
css={{
display: 'flex',
alignItems: 'center',
marginLeft: 'auto',
}}
>
<SpanBox>{props.title}</SpanBox>
</Button>
)
}
export const SettingsTable = (props: SettingsTableProps): JSX.Element => {
return (
<PrimaryLayout pageTestId={props.pageId}>
<Toaster
containerStyle={{
top: '5rem',
}}
/>
<HStack css={{ width: '100%' }} alignment="center">
<VStack
distribution="center"
css={{
mx: '10px',
width: '100%',
maxWidth: '865px',
color: '$grayText',
paddingBottom: '5rem',
paddingTop: '2rem',
alignSelf: 'center',
'@md': {
m: '16px',
alignSelf: 'center',
mx: '42px',
paddingTop: '0',
},
}}
>
<Box
css={{
width: '100%',
'@md': {
display: 'block',
},
}}
>
<Box
style={{
display: 'flex',
alignItems: 'center',
marginBottom: '10px',
}}
>
{props.createAction && props.createTitle && (
<CreateButton
action={props.createAction}
title={props.createTitle}
/>
)}
</Box>
<Box
css={{
backgroundColor: '$grayBgActive',
border: '1px solid rgba(0, 0, 0, 0.06)',
borderBottom: 'unset',
alignItems: 'center',
padding: '10px 0 10px 20px',
borderRadius: '5px 5px 0px 0px',
width: '100%',
}}
>
<HStack alignment="start" distribution="start">
<StyledText
style="menuTitle"
css={{
color: '$grayTextContrast',
}}
>
{props.headerTitle}
</StyledText>
<InfoLink href={props.pageInfoLink}></InfoLink>
</HStack>
</Box>
</Box>
{props.children}
</VStack>
</HStack>
<Box css={{ height: '120px' }} />
</PrimaryLayout>
)
}

View File

@ -1,6 +1,6 @@
//https://github.com/you-dont-need/You-Dont-Need-Momentjs
const locale = 'en-US' //navigator?.language ?? 'en-US'
const locale = Intl.DateTimeFormat().resolvedOptions().locale || 'en-US'
export function formattedLongDate(rawDate: string): string {
return new Intl.DateTimeFormat(locale, {
@ -8,6 +8,12 @@ export function formattedLongDate(rawDate: string): string {
}).format(new Date(rawDate))
}
export function formattedShortDate(rawDate: string): string {
return new Intl.DateTimeFormat(locale, {
dateStyle: 'short',
}).format(new Date(rawDate))
}
export function readableUpdatedAtMessage(
rawDate: string,
customPrefix?: string

View File

@ -19,6 +19,8 @@ type NewsletterEmailsData = {
export type NewsletterEmail = {
id: string
address: string
createdAt: string
subscriptionCount: number
confirmationCode?: string
}
@ -30,6 +32,8 @@ export function useGetNewsletterEmailsQuery(): NewsletterEmailsQueryResponse {
newsletterEmails {
id
address
createdAt
subscriptionCount
confirmationCode
}
}
@ -46,13 +50,14 @@ export function useGetNewsletterEmailsQuery(): NewsletterEmailsQueryResponse {
try {
if (data) {
const result = data as NewsletterEmailsResponseData
const emailAddresses = result.newsletterEmails?.newsletterEmails as NewsletterEmail[]
const emailAddresses = result.newsletterEmails
?.newsletterEmails as NewsletterEmail[]
return {
isValidating,
emailAddresses,
revalidate: () => {
mutate(undefined, true)
}
},
}
}
} catch (error) {
@ -62,6 +67,6 @@ export function useGetNewsletterEmailsQuery(): NewsletterEmailsQueryResponse {
isValidating: false,
emailAddresses: [],
// eslint-disable-next-line @typescript-eslint/no-empty-function
revalidate: () => {}
revalidate: () => {},
}
}

View File

@ -13,8 +13,8 @@ export type Subscription = {
description?: string
status: SubscriptionStatus
createdAt: Date
updatedAt: Date
createdAt: string
updatedAt: string
}
type SubscriptionsQueryResponse = {
@ -57,7 +57,6 @@ export function useGetSubscriptionsQuery(): SubscriptionsQueryResponse {
`
const { data, mutate, error, isValidating } = useSWR(query, publicGqlFetcher)
console.log('subscriptions data', data)
try {
if (data) {
@ -75,7 +74,7 @@ export function useGetSubscriptionsQuery(): SubscriptionsQueryResponse {
console.log('error', error)
}
return {
isValidating: false,
isValidating: true,
subscriptions: [],
// eslint-disable-next-line @typescript-eslint/no-empty-function
revalidate: () => {},

View File

@ -1,30 +1,28 @@
import { PrimaryLayout } from '../../components/templates/PrimaryLayout'
import { Button } from '../../components/elements/Button'
import { useGetNewsletterEmailsQuery } from '../../lib/networking/queries/useGetNewsletterEmailsQuery'
import { createNewsletterEmailMutation } from '../../lib/networking/mutations/createNewsletterEmailMutation'
import { deleteNewsletterEmailMutation } from '../../lib/networking/mutations/deleteNewsletterEmailMutation'
import { MoreOptionsIcon } from '../../components/elements/images/MoreOptionsIcon'
import { Info, Plus, Trash, Copy } from 'phosphor-react'
import { Trash, Copy } from 'phosphor-react'
import {
Dropdown,
DropdownOption,
} from '../../components/elements/DropdownElements'
import { TooltipWrapped } from '../../components/elements/Tooltip'
import { theme, styled } from '../../components/tokens/stitches.config'
import {
Box,
SpanBox,
HStack,
VStack,
} from '../../components/elements/LayoutPrimitives'
import { Box, HStack } from '../../components/elements/LayoutPrimitives'
import { useCopyLink } from '../../lib/hooks/useCopyLink'
import { Toaster } from 'react-hot-toast'
import { useCallback } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { StyledText } from '../../components/elements/StyledText'
import { applyStoredTheme } from '../../lib/themeUpdater'
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
import { formattedShortDate } from '../../lib/dateFormatting'
import Link from 'next/link'
import { InfoLink } from '../../components/elements/InfoLink'
import {
EmptySettingsRow,
SettingsTable,
SettingsTableRow,
} from '../../components/templates/settings/SettingsTable'
import { ConfirmationModal } from '../../components/patterns/ConfirmationModal'
enum TextType {
EmailAddress,
@ -36,117 +34,21 @@ type CopyTextButtonProps = {
type: TextType
}
const HeaderWrapper = styled(Box, {
width: '100%',
'@md': {
display: 'block',
},
})
const TableCard = styled(Box, {
backgroundColor: '$grayBg',
display: 'flex',
alignItems: 'center',
padding: '10px 12px',
border: '0.5px solid $grayBgActive',
width: '100%',
'&:hover': {
border: '0.5px solid #FFD234',
},
'@md': {
paddingLeft: '0',
},
})
const TableHeading = styled(Box, {
backgroundColor: '$grayBgActive',
border: '1px solid rgba(0, 0, 0, 0.06)',
display: 'none',
alignItems: 'center',
padding: '14px 0 14px 40px',
borderRadius: '5px 5px 0px 0px',
width: '100%',
'@md': {
display: 'flex',
}
})
const Input = styled('input', {
backgroundColor: 'transparent',
color: '$grayTextContrast',
marginTop: '5px',
marginLeft: '38px',
'&[disabled]': {
border: 'none',
},
})
const CopyTextBtnWrapper = styled(Box, {
padding: '1px',
background: '$grayBgActive',
borderRadius: '6px',
border: '1px solid rgba(0, 0, 0, 0.06)',
width: '32px',
height: '32px',
display: 'flex',
color: '#3D3D3D',
alignItems: 'center',
justifyContent: 'center',
})
const InfoIcon = styled(Info, {
marginTop: '8px',
'&:hover': {
cursor: 'pointer',
},
})
const TooltipStyle = {
backgroundColor: '#F9D354',
color: '#0A0806',
}
const MoreOptions = ({ onDelete }: { onDelete: () => void }) => (
<Dropdown
align={'end'}
triggerElement={
<Box
css={{
'&:hover': {
cursor: 'pointer',
},
}}
>
<MoreOptionsIcon
size={24}
strokeColor={theme.colors.grayTextContrast.toString()}
orientation="horizontal"
/>
</Box>
}
>
<DropdownOption
onSelect={() => {
return true
}}
>
<HStack alignment={'center'} distribution={'start'}>
<Trash size={24} color={theme.colors.omnivoreRed.toString()} />
<Button
css={{
color: theme.colors.omnivoreRed.toString(),
marginLeft: '8px',
border: 'none',
backgroundColor: 'transparent',
'&:hover': {
border: 'none',
backgroundColor: 'transparent',
},
}}
onClick={onDelete}
>
Delete
</Button>
</HStack>
</DropdownOption>
</Dropdown>
)
function CopyTextButton(props: CopyTextButtonProps): JSX.Element {
const { copyLink, isLinkCopied } = useCopyLink(
props.text,
@ -158,12 +60,20 @@ function CopyTextButton(props: CopyTextButtonProps): JSX.Element {
const copy = useCallback(() => {
copyLink()
showSuccessToast(props.type == TextType.EmailAddress ? 'Email Address Copied' : 'Confirmation Code Copied');
showSuccessToast(
props.type == TextType.EmailAddress
? 'Email Address Copied'
: 'Confirmation Code Copied'
)
}, [])
return (
<Button style="plainIcon" onClick={copy}>
<Copy color={theme.colors.grayTextContrast.toString()} />
<Copy
width={16}
height={16}
color={theme.colors.grayTextContrast.toString()}
/>
</Button>
)
}
@ -171,6 +81,8 @@ function CopyTextButton(props: CopyTextButtonProps): JSX.Element {
export default function EmailsPage(): JSX.Element {
const { emailAddresses, revalidate, isValidating } =
useGetNewsletterEmailsQuery()
const [confirmDeleteEmailId, setConfirmDeleteEmailId] =
useState<undefined | string>(undefined)
applyStoredTheme(false)
@ -193,228 +105,123 @@ export default function EmailsPage(): JSX.Element {
revalidate()
showSuccessToast('Email Deleted')
}
const sortedEmailAddresses = useMemo(() => {
return emailAddresses.sort((a, b) => a.createdAt.localeCompare(b.createdAt))
}, [emailAddresses])
return (
<PrimaryLayout pageTestId="settings-emails-tag">
<Toaster
containerStyle={{
top: '5rem',
}}
/>
<VStack
distribution="center"
css={{
mx: '10px',
maxWidth: '865px',
color: '$grayText',
paddingBottom: '5rem',
paddingTop: '2rem',
'@md': {
m: '16px',
alignSelf: 'center',
mx: '42px',
paddingTop: '0',
},
}}
<>
<SettingsTable
pageId="settings-emails-tag"
pageHeadline="Email Addresses"
pageInfoLink="/help/newsletters"
headerTitle="Address"
createTitle="Create a new email address"
createAction={createEmail}
>
<HeaderWrapper>
<Box style={{ display: 'flex', alignItems: 'center' }}>
<Box>
<StyledText
style="fixedHeadline"
>
Email Addresses{' '}
</StyledText>
</Box>
<InfoLink href="/help/newsletters" />
<Button
onClick={createEmail}
style="ctaDarkYellow"
css={{
display: 'flex',
alignItems: 'center',
marginLeft: 'auto',
}}
>
<SpanBox css={{
display: 'none',
'@md': {
display: 'flex',
},
}}>
<SpanBox>Create a new email address</SpanBox>
</SpanBox>
<SpanBox css={{
p: '0',
display: 'flex',
'@md': {
display: 'none',
},
}}>
<Plus size={24} />
</SpanBox>
</Button>
</Box>
<TableHeading>
<Box
css={{
flex: '49%',
}}
>
<StyledText
style="menuTitle"
css={{
color: '$grayTextContrast',
}}
>
EMAIL
</StyledText>
</Box>
<Box style={{ flex: '51%' }}>
<StyledText
style="menuTitle"
css={{
color: '$grayTextContrast',
}}
>
CONFIRMATION CODE
</StyledText>
</Box>
</TableHeading>
</HeaderWrapper>
{emailAddresses &&
emailAddresses.map((email, i) => {
const { address, confirmationCode, id } = email
const isLastChild = i === emailAddresses.length - 1
{sortedEmailAddresses.length > 0 ? (
sortedEmailAddresses.map((email, i) => {
return (
<TableCard
key={id}
css={{
'&:hover': {
background: 'rgba(255, 234, 159, 0.12)',
},
'@mdDown': {
borderTopLeftRadius: i === 0 ? '5px' : '',
borderTopRightRadius: i === 0 ? '5px' : '',
},
borderBottomLeftRadius: isLastChild ? '5px' : '',
borderBottomRightRadius: isLastChild ? '5px' : '',
}}
>
<Box
css={{
display: 'flex',
width: '100%',
flexDirection: 'column',
'@md': {
flexDirection: 'row',
},
}}
>
<HStack
distribution="start"
<SettingsTableRow
key={email.address}
title={email.address}
isLast={i === sortedEmailAddresses.length - 1}
onDelete={() => setConfirmDeleteEmailId(email.id)}
deleteTitle="Delete"
sublineElement={
<StyledText
css={{
display: 'flex',
padding: '4px 4px 4px 0px',
my: '5px',
fontSize: '11px',
}}
>
<Input
type="text"
value={address}
disabled
css={{
marginLeft: '0',
width: '100%',
'@md': {
marginLeft: '38px',
width: '320px',
},
}}
></Input>
<CopyTextBtnWrapper
css={{
'@mdDown': {
marginRight: '10px',
marginLeft: '18px',
},
}}
>
<CopyTextButton
text={address}
type={TextType.EmailAddress}
/>
</CopyTextBtnWrapper>
<Box
css={{
marginLeft: 'auto',
textAlign: 'right',
display: 'flex',
'@md': {
display: 'none',
},
}}
>
<MoreOptions onDelete={() => deleteEmail(id)} />
</Box>
</HStack>
{confirmationCode && (
{`created ${formattedShortDate(email.createdAt)}, `}
<Link href="/settings/subscriptions">{`${email.subscriptionCount} subscriptions`}</Link>
</StyledText>
}
titleElement={
<CopyTextBtnWrapper
css={{
marginLeft: '20px',
'@mdDown': {
marginRight: '10px',
},
}}
>
<CopyTextButton
text={email.address}
type={TextType.EmailAddress}
/>
</CopyTextBtnWrapper>
}
extraElement={
email.confirmationCode ? (
<HStack
distribution="start"
alignment="start"
distribution="center"
css={{
display: 'flex',
width: '100%',
backgroundColor: '$grayBgActive',
borderRadius: '6px',
padding: '8px 4px 4px 7px',
padding: '4px 4px 4px 0px',
'@md': {
padding: 'unset',
width: '30%',
backgroundColor: 'transparent',
},
}}
>
<>
<Input
type="text"
value={confirmationCode}
disabled
style={{ flex: '1' }}
></Input>
<StyledText
css={{
fontSize: '11px',
'@md': {
marginTop: '5px',
},
'@mdDown': {
marginLeft: 'auto',
},
marginRight: '10px',
}}
>
{`Gmail: ${email.confirmationCode}`}
</StyledText>
<Box>
<CopyTextBtnWrapper
css={{
'@mdDown': {
border: 'none',
}
}}
>
<CopyTextBtnWrapper>
<CopyTextButton
text={confirmationCode}
text={email.confirmationCode || ''}
type={TextType.ConfirmationCode}
/>
</CopyTextBtnWrapper>
</Box>
</>
</HStack>
)}
</Box>
<HStack distribution={'start'} css={{ marginLeft: 'auto' }}>
<Box
css={{
textAlign: 'right',
display: 'none',
'@md': {
display: 'flex',
},
}}
>
<MoreOptions onDelete={() => deleteEmail(id)} />
</Box>
</HStack>
</TableCard>
) : (
<></>
)
}
/>
)
})}
</VStack>
<Box css={{ height: '120px' }} />
</PrimaryLayout>
})
) : (
<EmptySettingsRow
text={isValidating ? '-' : 'No Email Addresses Found'}
/>
)}
</SettingsTable>
{confirmDeleteEmailId ? (
<ConfirmationModal
message={
'Are you sure? You will stop receiving emails sent to this address.'
}
onAccept={async () => {
await deleteEmail(confirmDeleteEmailId)
setConfirmDeleteEmailId(undefined)
}}
onOpenChange={() => setConfirmDeleteEmailId(undefined)}
/>
) : null}
</>
)
}

View File

@ -1,18 +1,22 @@
import { useState } from 'react'
import { PrimaryLayout } from '../../components/templates/PrimaryLayout'
import { Toaster } from 'react-hot-toast'
import { useMemo, useState } from 'react'
import { applyStoredTheme } from '../../lib/themeUpdater'
import { ConfirmationModal } from '../../components/patterns/ConfirmationModal'
import { useGetSubscriptionsQuery } from '../../lib/networking/queries/useGetSubscriptionsQuery'
import { unsubscribeMutation } from '../../lib/networking/mutations/unsubscribeMutation'
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
import { Table } from '../../components/elements/Table'
import {
EmptySettingsRow,
SettingsTable,
SettingsTableRow,
} from '../../components/templates/settings/SettingsTable'
import { StyledText } from '../../components/elements/StyledText'
import Link from 'next/link'
import { formattedShortDate } from '../../lib/dateFormatting'
export default function SubscriptionsPage(): JSX.Element {
const { subscriptions, revalidate } = useGetSubscriptionsQuery()
const [confirmUnsubscribeName, setConfirmUnsubscribeName] = useState<
string | null
>(null)
const { subscriptions, revalidate, isValidating } = useGetSubscriptionsQuery()
const [confirmUnsubscribeName, setConfirmUnsubscribeName] =
useState<string | null>(null)
applyStoredTheme(false)
@ -26,42 +30,66 @@ export default function SubscriptionsPage(): JSX.Element {
revalidate()
}
const headers = ['Name', 'Email', 'Updated Time']
const rows = new Map<string, string[]>()
subscriptions.forEach((subscription) =>
rows.set(subscription.name, [
subscription.name,
subscription.newsletterEmail,
subscription.updatedAt.toString(),
])
)
const sortedSubscriptions = useMemo(() => {
return subscriptions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
}, [subscriptions])
return (
<PrimaryLayout pageTestId="settings-subscriptions-tag">
<Toaster
containerStyle={{
top: '5rem',
}}
/>
<SettingsTable
pageId="settings-subscriptions-tag"
pageHeadline="Subscriptions"
pageInfoLink="/help/newsletters"
headerTitle="Subscriptions"
>
<>
{sortedSubscriptions.length > 0 ? (
sortedSubscriptions.map((subscription, i) => {
return (
<SettingsTableRow
key={subscription.id}
title={subscription.name}
isLast={i === sortedSubscriptions.length - 1}
onDelete={() => setConfirmUnsubscribeName(subscription.name)}
deleteTitle="Unsubscribe"
sublineElement={
<StyledText
css={{
my: '5px',
fontSize: '11px',
}}
>
{`Last received ${formattedShortDate(
subscription.updatedAt
)} at `}
<Link
href={`/settings/emails?address=${subscription.newsletterEmail}`}
>
{subscription.newsletterEmail}
</Link>
</StyledText>
}
/>
)
})
) : (
<EmptySettingsRow
text={isValidating ? '-' : 'No Email Subscriptions Found'}
/>
)}
{confirmUnsubscribeName ? (
<ConfirmationModal
message={
'Are you sure? You will stop receiving newsletters from this subscription.'
}
onAccept={async () => {
await onUnsubscribe(confirmUnsubscribeName)
setConfirmUnsubscribeName(null)
}}
onOpenChange={() => setConfirmUnsubscribeName(null)}
/>
) : null}
<Table
heading={'Subscriptions'}
headers={headers}
rows={rows}
onDelete={setConfirmUnsubscribeName}
/>
</PrimaryLayout>
{confirmUnsubscribeName ? (
<ConfirmationModal
message={
'Are you sure? You will stop receiving newsletters from this subscription.'
}
onAccept={async () => {
await onUnsubscribe(confirmUnsubscribeName)
setConfirmUnsubscribeName(null)
}}
onOpenChange={() => setConfirmUnsubscribeName(null)}
/>
) : null}
</>
</SettingsTable>
)
}