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:
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
306
packages/web/components/templates/settings/SettingsTable.tsx
Normal file
306
packages/web/components/templates/settings/SettingsTable.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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: () => {},
|
||||
}
|
||||
}
|
||||
|
||||
@ -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: () => {},
|
||||
|
||||
@ -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}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user