diff --git a/packages/web/components/elements/DropdownElements.tsx b/packages/web/components/elements/DropdownElements.tsx index c7703b5ee..43b25fff5 100644 --- a/packages/web/components/elements/DropdownElements.tsx +++ b/packages/web/components/elements/DropdownElements.tsx @@ -63,10 +63,17 @@ const StyledLabel = styled(Label, { cursor: 'default', }) +export type DropdownAlignment = + | 'start' + | 'end' + | 'center' + type DropdownProps = { labelText?: string triggerElement: React.ReactNode - children: React.ReactNode + children: React.ReactNode, + styledArrow?: boolean + align?: DropdownAlignment } export const DropdownSeparator = styled(Separator, { @@ -102,10 +109,11 @@ export function Dropdown(props: DropdownProps): JSX.Element { // remove focus from dropdown ;(document.activeElement as HTMLElement).blur() }} + align={props.align ? props.align : 'center'} > {props.labelText && {props.labelText}} {props.children} - + {props.styledArrow && } ) diff --git a/packages/web/components/elements/Tooltip.tsx b/packages/web/components/elements/Tooltip.tsx new file mode 100644 index 000000000..3af1b84c4 --- /dev/null +++ b/packages/web/components/elements/Tooltip.tsx @@ -0,0 +1,77 @@ +import React, { FC } from 'react'; +import { styled, keyframes } from '@stitches/react'; +import * as TooltipPrimitive from '@radix-ui/react-tooltip'; + +const slideUpAndFade = keyframes({ + '0%': { opacity: 0, transform: 'translateY(2px)' }, + '100%': { opacity: 1, transform: 'translateY(0)' }, +}); + +const slideRightAndFade = keyframes({ + '0%': { opacity: 0, transform: 'translateX(-2px)' }, + '100%': { opacity: 1, transform: 'translateX(0)' }, +}); + +const slideDownAndFade = keyframes({ + '0%': { opacity: 0, transform: 'translateY(-2px)' }, + '100%': { opacity: 1, transform: 'translateY(0)' }, +}); + +const slideLeftAndFade = keyframes({ + '0%': { opacity: 0, transform: 'translateX(2px)' }, + '100%': { opacity: 1, transform: 'translateX(0)' }, +}); + +const StyledContent = styled(TooltipPrimitive.Content, { + borderRadius: 4, + padding: '8px 13px', + fontSize: 12, + color: '#FFFFFF', + backgroundColor: '#1C1C1E', + '@media (prefers-reduced-motion: no-preference)': { + animationDuration: '400ms', + animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)', + animationFillMode: 'forwards', + willChange: 'transform, opacity', + '&[data-state="delayed-open"]': { + '&[data-side="top"]': { animationName: slideDownAndFade }, + '&[data-side="right"]': { animationName: slideLeftAndFade }, + '&[data-side="bottom"]': { animationName: slideUpAndFade }, + '&[data-side="left"]': { animationName: slideRightAndFade }, + }, + }, +}); + +const StyledArrow = styled(TooltipPrimitive.Arrow, { + fill: '#1C1C1E', +}); + +export const TooltipProvider = TooltipPrimitive.Provider; +export const Tooltip = TooltipPrimitive.Root; +export const TooltipTrigger = TooltipPrimitive.Trigger; +export const TooltipContent = StyledContent; +export const TooltipArrow = StyledArrow; + + +type TooltipWrappedProps = { + tooltipContent: string, + tooltipSide ?: TooltipPrimitive.TooltipContentProps['side'], + align?: TooltipPrimitive.TooltipContentProps['align'], + alignOffset?: TooltipPrimitive.TooltipContentProps['alignOffset'] + arrowStyles?: TooltipPrimitive.TooltipArrowProps['style'] + style?: TooltipPrimitive.TooltipContentProps['style'] +} + +export const TooltipWrapped: FC = ({children, tooltipContent, tooltipSide, ...props}) => { + return ( + + + {children} + + + {tooltipContent} + + + + ) +} diff --git a/packages/web/components/elements/images/MoreOptionsIcon.tsx b/packages/web/components/elements/images/MoreOptionsIcon.tsx index e29580982..c51229426 100644 --- a/packages/web/components/elements/images/MoreOptionsIcon.tsx +++ b/packages/web/components/elements/images/MoreOptionsIcon.tsx @@ -1,3 +1,5 @@ +import { DotsThree, DotsThreeVertical } from 'phosphor-react' + type Orientation = 'horizontal' | 'vertical' type MoreOptionsIconProps = { @@ -7,48 +9,9 @@ type MoreOptionsIconProps = { } export function MoreOptionsIcon(props: MoreOptionsIconProps): JSX.Element { - const dots = - props.orientation == 'horizontal' ? ( - <> - - - - + return props.orientation == 'horizontal' ? ( + ) : ( - <> - - - - + ) - - return ( - - {dots} - - ) } diff --git a/packages/web/lib/networking/mutations/deleteNewsletterEmailMutation.ts b/packages/web/lib/networking/mutations/deleteNewsletterEmailMutation.ts new file mode 100644 index 000000000..3a4e51e1a --- /dev/null +++ b/packages/web/lib/networking/mutations/deleteNewsletterEmailMutation.ts @@ -0,0 +1,31 @@ +import { gql } from 'graphql-request' +import { gqlFetcher } from '../networkHelpers' + +export async function deleteNewsletterEmailMutation( + newsletterEmailId: string +): Promise { + const mutation = gql` + mutation DeleteNewsletterEmailMutation($newsletterEmailId: ID!) { + deleteNewsletterEmail(newsletterEmailId: $newsletterEmailId) { + ... on DeleteNewsletterEmailSuccess { + newsletterEmail { + id + address + } + } + ... on DeleteNewsletterEmailError { + errorCodes + } + } + } + ` + + try { + const data = await gqlFetcher(mutation, { newsletterEmailId }) + console.log('delete email', data) + return 'data' + } catch (error) { + console.log('deleteNewsletterEmailMutation error', error) + return undefined + } +} diff --git a/packages/web/package.json b/packages/web/package.json index 49a6a9737..8990886d0 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -23,6 +23,7 @@ "@radix-ui/react-id": "^0.1.1", "@radix-ui/react-popover": "^0.1.1", "@radix-ui/react-separator": "^0.1.0", + "@radix-ui/react-tooltip": "^0.1.7", "@segment/analytics-next": "^1.33.5", "@sentry/nextjs": "^6.16.1", "@stitches/react": "^1.2.5", diff --git a/packages/web/pages/_app.tsx b/packages/web/pages/_app.tsx index 1fcf26ba8..0c29e4a55 100644 --- a/packages/web/pages/_app.tsx +++ b/packages/web/pages/_app.tsx @@ -8,6 +8,7 @@ import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import { Analytics, AnalyticsBrowser } from '@segment/analytics-next' import { segmentApiKey } from '../lib/appConfig' +import { TooltipProvider } from '../components/elements/Tooltip' function OmnivoreApp({ Component, pageProps }: AppProps): JSX.Element { const router = useRouter() @@ -48,7 +49,9 @@ function OmnivoreApp({ Component, pageProps }: AppProps): JSX.Element { defaultLocale="en" messages={englishTranslations} > - + + + ) diff --git a/packages/web/pages/settings/emails.tsx b/packages/web/pages/settings/emails.tsx index 8992bf804..665be7765 100644 --- a/packages/web/pages/settings/emails.tsx +++ b/packages/web/pages/settings/emails.tsx @@ -2,19 +2,30 @@ 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 { CopyLinkIcon } from '../../components/elements/images/CopyLinkIcon' -import { theme } from '../../components/tokens/stitches.config' -import { Box, HStack, VStack } from '../../components/elements/LayoutPrimitives' +import { deleteNewsletterEmailMutation } from '../../lib/networking/mutations/deleteNewsletterEmailMutation' +import { MoreOptionsIcon } from '../../components/elements/images/MoreOptionsIcon' +import { Info, Plus, 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 { useCopyLink } from '../../lib/hooks/useCopyLink' import { toast, Toaster } from 'react-hot-toast' import { useCallback } from 'react' -import Link from 'next/link' import { StyledText } from '../../components/elements/StyledText' import { applyStoredTheme } from '../../lib/themeUpdater' enum TextType { EmailAddress, - ConfirmationCode + ConfirmationCode, } type CopyTextButtonProps = { @@ -22,76 +33,383 @@ type CopyTextButtonProps = { type: TextType } +const HeaderWrapper = styled(Box, { + display: 'none', + width: '863px', + '@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', + width: '863px', + }, +}) + +const TableHeading = styled(Box, { + backgroundColor: '$grayBgActive', + border: '1px solid rgba(0, 0, 0, 0.06)', + display: 'flex', + alignItems: 'center', + padding: '14px 0 14px 40px', + borderRadius: '5px 5px 0px 0px', + width: '863px', +}) + +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)', +}) + +const MobileBtnWrapper = styled(Box, { + display: 'flex', + position: 'fixed', + bottom: '0', + right: '25px', + '@md': { + display: 'none', + }, +}) + +const InfoIcon = styled(Info, { + marginTop: '8px', + '&:hover': { + cursor: 'pointer', + }, +}) + +const TooltipStyle = { + backgroundColor: '#F9D354', + color: '#0A0806', +} + +const MoreOptions = ({ onDelete }: { onDelete: () => void }) => ( + + + + } + > + { + return true + }} + > + + + + + + +) + function CopyTextButton(props: CopyTextButtonProps): JSX.Element { - const { copyLink, isLinkCopied } = useCopyLink(props.text, - 'newsletter_' + (props.type == TextType.EmailAddress ? 'email_address' : 'confirmation_code') + const { copyLink, isLinkCopied } = useCopyLink( + props.text, + 'newsletter_' + + (props.type == TextType.EmailAddress + ? 'email_address' + : 'confirmation_code') ) const copy = useCallback(() => { copyLink() - toast(props.type == TextType.EmailAddress ? 'Email Address Copied' : 'Confirmation Code Copied') + toast( + props.type == TextType.EmailAddress + ? 'Email Address Copied' + : 'Confirmation Code Copied' + ) }, []) return ( - + ) } export default function EmailsPage(): JSX.Element { - const { emailAddresses, revalidate, isValidating } = useGetNewsletterEmailsQuery() + const { emailAddresses, revalidate, isValidating } = + useGetNewsletterEmailsQuery() applyStoredTheme(false) async function createEmail(): Promise { await createNewsletterEmailMutation() revalidate() + toast.success('Email Created') + } + async function deleteEmail(id: string): Promise { + await deleteNewsletterEmailMutation(id) + revalidate() + toast.success('Email Deleted!') } - return ( - + - Email Addresses - - Add PDFs to your library, or subscribe to emails using an Omnivore email address. - - {emailAddresses && emailAddresses.map((email) => { + + + + + Email Addresses{' '} + + + + + + + + + + + + + + + EMAIL + + + + + CONFIRMATION CODE + + + + + {emailAddresses && + emailAddresses.map((email, i) => { + const { address, confirmationCode, id } = email + const isLastChild = i === emailAddresses.length - 1 + return ( - - - - - - - {email.confirmationCode && ( - <> - - - + + + + + + + + + deleteEmail(id)} /> + + + {confirmationCode && ( + + <> + + + + + + + + )} + + + + deleteEmail(id)} /> + - + ) - })} - - - -

Lean more about setting up Newsletter Email Addresses

+
) diff --git a/yarn.lock b/yarn.lock index bd1b1dd0f..8bfa49bb2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3673,6 +3673,13 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-use-previous@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-0.1.1.tgz#0226017f72267200f6e832a7103760e96a6db5d0" + integrity sha512-O/ZgrDBr11dR8rhO59ED8s5zIXBRFi8MiS+CmFGfi7MJYdLbfqVOmQU90Ghf87aifEgWe6380LA69KBneaShAg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-rect@0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-0.1.1.tgz#6c15384beee59c086e75b89a7e66f3d2e583a856" @@ -3695,6 +3702,14 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-visually-hidden@0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.1.4.tgz#6c75eae34fb5d084b503506fbfc05587ced05f03" + integrity sha512-K/q6AEEzqeeEq/T0NPChvBqnwlp8Tl4NnQdrI/y8IOY7BRR+Ug0PEsVk6g48HJ7cA1//COugdxXXVVK/m0X1mA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "0.1.4" + "@radix-ui/rect@0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-0.1.1.tgz#95b5ba51f469bea6b1b841e2d427e17e37d38419"