diff --git a/packages/web/components/templates/settings/SettingsTable.tsx b/packages/web/components/templates/settings/SettingsTable.tsx index 705b34252..ed63058f1 100644 --- a/packages/web/components/templates/settings/SettingsTable.tsx +++ b/packages/web/components/templates/settings/SettingsTable.tsx @@ -12,7 +12,6 @@ import { PrimaryLayout } from '../PrimaryLayout' type SettingsTableProps = { pageId: string - pageHeadline: string pageInfoLink: string headerTitle: string @@ -35,13 +34,16 @@ type SettingsTableRowProps = { titleElement?: JSX.Element extraElement?: JSX.Element - deleteTitle: string - onDelete: () => void + deleteTitle?: string + onDelete?: () => void + + dropdownItems?: JSX.Element } type MoreOptionsProps = { - title: string - onDelete: () => void + title?: string + onDelete?: () => void + dropdownItems?: JSX.Element } const MoreOptions = (props: MoreOptionsProps) => ( @@ -63,29 +65,32 @@ const MoreOptions = (props: MoreOptionsProps) => ( } > - { - props.onDelete() - }} - > - - - { + props.onDelete && props.onDelete() + }} + > + + + - {props.title} - - - + '&:hover': { + border: 'none', + backgroundColor: 'transparent', + }, + }} + > + {props.title} + + + + )} + {props.dropdownItems && props.dropdownItems} ) @@ -183,7 +188,11 @@ export const SettingsTableRow = (props: SettingsTableRowProps): JSX.Element => { }, }} > - + {props.extraElement} @@ -198,7 +207,11 @@ export const SettingsTableRow = (props: SettingsTableRowProps): JSX.Element => { }, }} > - + @@ -261,6 +274,7 @@ export const SettingsTable = (props: SettingsTableProps): JSX.Element => { display: 'flex', alignItems: 'center', marginBottom: '10px', + height: '60px', }} > {props.createAction && props.createTitle && ( diff --git a/packages/web/lib/networking/mutations/markEmailAsItemMutation.ts b/packages/web/lib/networking/mutations/markEmailAsItemMutation.ts new file mode 100644 index 000000000..fc6e60690 --- /dev/null +++ b/packages/web/lib/networking/mutations/markEmailAsItemMutation.ts @@ -0,0 +1,35 @@ +import { gqlFetcher } from '../networkHelpers' + +type MarkEmailAsItemDataResponseData = { + markEmailAsItem?: MarkEmailAsItemData +} + +type MarkEmailAsItemData = { + success: boolean + errorCodes?: unknown[] +} + +export async function markEmailAsItemMutation( + recentEmailId: string +): Promise { + const mutation = ` + mutation MarkRecentEmailAsItem($recentEmailId: ID!) { + markEmailAsItem(recentEmailId:$recentEmailId) { + ... on MarkEmailAsItemError { + errorCodes + } + ... on MarkEmailAsItemSuccess { + success + } + } + }` + + const data = await gqlFetcher(mutation, { recentEmailId }) + console.log('recentEmailId: ', data) + const output = data as MarkEmailAsItemDataResponseData | undefined + const error = output?.markEmailAsItem?.errorCodes?.find(() => true) + console.log('error: ', error) + if (error) { + throw error + } +} diff --git a/packages/web/lib/networking/queries/useGetRecentEmails.tsx b/packages/web/lib/networking/queries/useGetRecentEmails.tsx new file mode 100644 index 000000000..71d68f886 --- /dev/null +++ b/packages/web/lib/networking/queries/useGetRecentEmails.tsx @@ -0,0 +1,78 @@ +import { gql } from 'graphql-request' +import useSWR from 'swr' +import { publicGqlFetcher } from '../networkHelpers' + +export interface RecentEmail { + id: string + from: string + to: string + subject: string + type: string + text: string + createdAt: string +} + +interface RecentEmailsResponse { + isValidating: boolean + recentEmails: RecentEmail[] + revalidate: () => void +} + +interface RecentEmailsResponseData { + recentEmails: RecentEmailsData +} + +interface RecentEmailsData { + recentEmails: RecentEmail[] +} + +export function useGetRecentEmailsQuery(): RecentEmailsResponse { + const query = gql` + query GetRecentEmails { + recentEmails { + ... on RecentEmailsSuccess { + recentEmails { + id + from + to + subject + type + text + createdAt + } + } + ... on RecentEmailsError { + errorCodes + } + } + } + ` + + const { data, mutate, error, isValidating } = useSWR(query, publicGqlFetcher) + + try { + if (error) { + throw error + } + + if (data) { + const result = data as RecentEmailsResponseData + const recentEmails = result.recentEmails.recentEmails as RecentEmail[] + return { + isValidating, + recentEmails, + revalidate: () => { + mutate() + }, + } + } + } catch (error) { + console.log('error', error) + } + return { + isValidating: true, + recentEmails: [], + // eslint-disable-next-line @typescript-eslint/no-empty-function + revalidate: () => {}, + } +} diff --git a/packages/web/pages/settings/api.tsx b/packages/web/pages/settings/api.tsx index a258505ed..43d0d20ee 100644 --- a/packages/web/pages/settings/api.tsx +++ b/packages/web/pages/settings/api.tsx @@ -114,7 +114,6 @@ export default function Api(): JSX.Element { return ( (undefined) + const [confirmDeleteEmailId, setConfirmDeleteEmailId] = useState< + undefined | string + >(undefined) applyStoredTheme(false) @@ -117,7 +117,6 @@ export default function EmailsPage(): JSX.Element { <> )} + + + View recently received emails + + {confirmDeleteEmailId ? ( diff --git a/packages/web/pages/settings/emails/recent.tsx b/packages/web/pages/settings/emails/recent.tsx new file mode 100644 index 000000000..c842937d0 --- /dev/null +++ b/packages/web/pages/settings/emails/recent.tsx @@ -0,0 +1,226 @@ +import { useMemo, useState } from 'react' +import { applyStoredTheme } from '../../../lib/themeUpdater' + +import { + EmptySettingsRow, + SettingsTable, + SettingsTableRow, +} from '../../../components/templates/settings/SettingsTable' +import { StyledText } from '../../../components/elements/StyledText' +import { + RecentEmail, + useGetRecentEmailsQuery, +} from '../../../lib/networking/queries/useGetRecentEmails' +import { + Box, + HStack, + SpanBox, + VStack, +} from '../../../components/elements/LayoutPrimitives' +import { DropdownOption } from '../../../components/elements/DropdownElements' +import { theme } from '../../../components/tokens/stitches.config' +import { + ModalContent, + ModalOverlay, + ModalRoot, + ModalTitleBar, +} from '../../../components/elements/ModalPrimitives' +import { markEmailAsItemMutation } from '../../../lib/networking/mutations/markEmailAsItemMutation' +import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers' + +type TypeChipProps = { + type: string +} + +const TypeChip = (props: TypeChipProps): JSX.Element => { + const backgroundColor = props.type == 'article' ? '$omnivoreCtaYellow' : 'red' + return ( + + {props.type} + + ) +} + +type MoreOptionItemProps = { + text: string + action: () => void +} + +const MoreOptionItem = (props: MoreOptionItemProps): JSX.Element => { + return ( + { + props.action() + }} + > + + + {props.text} + + + + ) +} + +type ViewRecentEmailModalProps = { + recentEmail: RecentEmail + onOpenChange: (open: boolean) => void +} + +const ViewRecentEmailModal = ( + props: ViewRecentEmailModalProps +): JSX.Element => { + return ( + + + { + // remove focus from modal + ;(document.activeElement as HTMLElement).blur() + }} + > + + + + {props.recentEmail.text} + + + + + ) +} + +export default function RecentEmails(): JSX.Element { + const { recentEmails, isValidating } = useGetRecentEmailsQuery() + const [viewingEmail, setViewingEmail] = useState( + undefined + ) + + applyStoredTheme(false) + + const sortedRecentEmails = useMemo(() => { + return recentEmails.sort((a, b) => b.createdAt.localeCompare(a.createdAt)) + }, [recentEmails]) + + return ( + + {sortedRecentEmails.length > 0 ? ( + sortedRecentEmails.map((recentEmail: RecentEmail, i) => { + return ( + + + {recentEmail.subject} + + + + + + } + dropdownItems={ + <> + { + console.log('viewing text: ', recentEmail) + setViewingEmail(recentEmail) + }} + /> + {recentEmail.type != 'article' && ( + { + console.log('marking as email', recentEmail) + showSuccessToast('Marking email as article') + try { + await markEmailAsItemMutation(recentEmail.id) + } catch (err) { + console.log('error marking as article: ', err) + showErrorToast('Error marking item as article') + return + } + showSuccessToast('Email added to library') + }} + /> + )} + + } + /> + ) + }) + ) : ( + + )} + + {viewingEmail && ( + setViewingEmail(undefined)} + /> + )} + + ) +} diff --git a/packages/web/pages/settings/subscriptions.tsx b/packages/web/pages/settings/subscriptions.tsx index ee0bab5f4..7a7826053 100644 --- a/packages/web/pages/settings/subscriptions.tsx +++ b/packages/web/pages/settings/subscriptions.tsx @@ -15,8 +15,9 @@ import { formattedShortDate } from '../../lib/dateFormatting' export default function SubscriptionsPage(): JSX.Element { const { subscriptions, revalidate, isValidating } = useGetSubscriptionsQuery() - const [confirmUnsubscribeName, setConfirmUnsubscribeName] = - useState(null) + const [confirmUnsubscribeName, setConfirmUnsubscribeName] = useState< + string | null + >(null) applyStoredTheme(false) @@ -40,7 +41,6 @@ export default function SubscriptionsPage(): JSX.Element { return (