From e8fc315301985e2aded5f71d63e5537874822570 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 31 Aug 2023 17:18:24 +0800 Subject: [PATCH] Add suggestion boxes for the major pages --- .../web/components/elements/SuggestionBox.tsx | 116 +++++++++++++++++ .../components/templates/SettingsLayout.tsx | 1 - .../templates/homeFeed/EmptyLibrary.tsx | 119 ++++++++++-------- .../templates/settings/SettingsTable.tsx | 30 +++++ packages/web/pages/settings/api.tsx | 5 + packages/web/pages/settings/emails/index.tsx | 6 + packages/web/pages/settings/emails/recent.tsx | 7 +- packages/web/pages/settings/feeds/index.tsx | 5 + packages/web/pages/settings/labels.tsx | 21 ++++ packages/web/pages/settings/subscriptions.tsx | 7 +- 10 files changed, 263 insertions(+), 54 deletions(-) create mode 100644 packages/web/components/elements/SuggestionBox.tsx diff --git a/packages/web/components/elements/SuggestionBox.tsx b/packages/web/components/elements/SuggestionBox.tsx new file mode 100644 index 000000000..595ce482a --- /dev/null +++ b/packages/web/components/elements/SuggestionBox.tsx @@ -0,0 +1,116 @@ +import Link from 'next/link' +import { HStack, SpanBox, VStack } from './LayoutPrimitives' +import { ArrowRightIcon } from './icons/ArrowRightIcon' +import { theme } from '../tokens/stitches.config' +import { ReactNode } from 'react' +import { Button } from './Button' +import { CloseIcon } from './images/CloseIcon' + +type SuggestionBoxProps = { + helpMessage: string + helpCTAText: string | undefined + helpTarget: string | undefined + + size?: 'large' | 'small' + background?: string + + dismissible?: boolean + onDismiss?: () => void +} + +type InternalOrExternalLinkProps = { + link: string + children: ReactNode +} + +const InternalOrExternalLink = (props: InternalOrExternalLinkProps) => { + const isExternal = props.link.startsWith('https') + + return ( + + {!isExternal ? ( + {props.children} + ) : ( + + {props.children} + + )} + + ) +} + +export const SuggestionBox = (props: SuggestionBoxProps) => { + return ( + + + {props.dismissible && ( + + + + )} + {props.helpMessage} + {props.helpTarget && ( + + + <>{props.helpCTAText} + + + + )} + + + ) +} diff --git a/packages/web/components/templates/SettingsLayout.tsx b/packages/web/components/templates/SettingsLayout.tsx index 7ec179af3..eb5c291a7 100644 --- a/packages/web/components/templates/SettingsLayout.tsx +++ b/packages/web/components/templates/SettingsLayout.tsx @@ -49,7 +49,6 @@ export function SettingsLayout(props: SettingsLayoutProps): JSX.Element { {props.children} diff --git a/packages/web/components/templates/homeFeed/EmptyLibrary.tsx b/packages/web/components/templates/homeFeed/EmptyLibrary.tsx index 7e342ef24..8f227a4fe 100644 --- a/packages/web/components/templates/homeFeed/EmptyLibrary.tsx +++ b/packages/web/components/templates/homeFeed/EmptyLibrary.tsx @@ -9,6 +9,7 @@ import { searchQuery } from '../../../lib/networking/queries/search' import { LIBRARY_LEFT_MENU_WIDTH } from './LibraryFilterMenu' import { LayoutType } from './HomeFeedContainer' import { ArrowRightIcon } from '../../elements/icons/ArrowRightIcon' +import { SuggestionBox } from '../../elements/SuggestionBox' type EmptyLibraryProps = { searchTerm: string | undefined @@ -17,7 +18,13 @@ type EmptyLibraryProps = { layoutType: LayoutType } -type MessageType = 'feed' | 'newsletter' | 'library' +type MessageType = + | 'inbox' + | 'files' + | 'archive' + | 'feed' + | 'newsletter' + | 'library' type HelpMessageProps = { type: MessageType @@ -83,6 +90,12 @@ const HelpMessage = (props: HelpMessageProps) => { export const ErrorBox = (props: HelpMessageProps) => { const errorTitle = useMemo(() => { switch (props.type) { + case 'inbox': + return 'Your inbox is empty.' + case 'archive': + return 'You do not have any archived items.' + case 'files': + return 'No files found.' case 'feed': return 'You do not have any feed items matching this query.' case 'newsletter': @@ -116,70 +129,70 @@ export const ErrorBox = (props: HelpMessageProps) => { ) } -export const SuggestionBox = (props: HelpMessageProps) => { +export const Suggestion = (props: HelpMessageProps) => { const helpMessage = useMemo(() => { switch (props.type) { case 'feed': - return 'Want to add an RSS or Atom Subscription?' + return ['Want to add an RSS or Atom Subscription?', 'Click Here'] + case 'archive': + return [ + 'When you are done reading something archive it and it will be saved in Omnivore forever.', + 'Read the Docs', + ] + case 'files': + return [ + 'Drag PDFs into the library to add them to your Omnivore account.', + undefined, + ] case 'newsletter': - return 'Create an Omnivore email address and subscribe to newsletters.' + return [ + 'Create an Omnivore email address and subscribe to newsletters.', + 'Click Here', + ] } - return "Add a link or read more about Omnivore's Advanced Search." + return [ + "Add a link or read more about Omnivore's Advanced Search.", + 'Read the Docs', + ] }, [props.type]) const helpTarget = useMemo(() => { switch (props.type) { case 'feed': return '/settings/feeds' + case 'archive': + case 'files': + return undefined + case 'archive': + case 'inbox': + return 'https://docs.omnivore.app/using/saving' case 'newsletter': return '/settings/emails' } return 'https://docs.omnivore.app/' }, [props.type]) + const helpTargetWindow = useMemo(() => { + switch (props.type) { + case 'archive': + case 'inbox': + return '_blank' + } + return undefined + }, [props.type]) + return ( - - {helpMessage} - - - - <>Click Here - - - - - + <> + {helpMessage[0] ? ( + + ) : ( + <> + )} + ) } @@ -187,6 +200,12 @@ export const EmptyLibrary = (props: EmptyLibraryProps) => { const type = useMemo(() => { if (props.searchTerm) { switch (props.searchTerm) { + case 'in:archive': + return 'archive' + case 'in:inbox': + return 'inbox' + case 'type:file': + return 'files' case 'label:RSS': return 'feed' case 'label:Newsletter': @@ -205,9 +224,7 @@ export const EmptyLibrary = (props: EmptyLibraryProps) => { pl: '0px', width: '100%', - '@media (max-width: 1300px)': { - flexDirection: 'column', - }, + flexDirection: 'column', '@media (max-width: 768px)': { p: '15px', @@ -232,7 +249,7 @@ export const EmptyLibrary = (props: EmptyLibraryProps) => { }} > - + ) } diff --git a/packages/web/components/templates/settings/SettingsTable.tsx b/packages/web/components/templates/settings/SettingsTable.tsx index a71e290d1..0460ab8a0 100644 --- a/packages/web/components/templates/settings/SettingsTable.tsx +++ b/packages/web/components/templates/settings/SettingsTable.tsx @@ -8,6 +8,8 @@ import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' import { StyledText } from '../../elements/StyledText' import { theme } from '../../tokens/stitches.config' import { SettingsLayout } from '../SettingsLayout' +import { SuggestionBox } from '../../elements/SuggestionBox' +import { usePersistedState } from '../../../lib/hooks/usePersistedState' type SettingsTableProps = { pageId: string @@ -17,6 +19,8 @@ type SettingsTableProps = { createTitle?: string createAction?: () => void + suggestionInfo: SuggestionInfo + children: React.ReactNode } @@ -52,6 +56,12 @@ type MoreOptionsProps = { onEdit?: () => void } +type SuggestionInfo = { + text: string + docs: string + key: string +} + const MoreOptions = (props: MoreOptionsProps) => ( { } export const SettingsTable = (props: SettingsTableProps): JSX.Element => { + const [showSuggestion, setShowSuggestion] = usePersistedState({ + key: props.suggestionInfo.key, + initialValue: !!props.suggestionInfo, + }) + return ( { }, }} > + {props.suggestionInfo && showSuggestion && ( + + { + setShowSuggestion(false) + }} + /> + + )} {sortedApiKeys.length > 0 ? ( sortedApiKeys.map((apiKey, i) => { diff --git a/packages/web/pages/settings/emails/index.tsx b/packages/web/pages/settings/emails/index.tsx index fae30528e..fee815935 100644 --- a/packages/web/pages/settings/emails/index.tsx +++ b/packages/web/pages/settings/emails/index.tsx @@ -22,6 +22,7 @@ import { SettingsTableRow, } from '../../../components/templates/settings/SettingsTable' import { ConfirmationModal } from '../../../components/patterns/ConfirmationModal' +import { SuggestionBox } from '../../../components/elements/SuggestionBox' enum TextType { EmailAddress, @@ -121,6 +122,11 @@ export default function EmailsPage(): JSX.Element { headerTitle="Address" createTitle="Create a new email address" createAction={createEmail} + suggestionInfo={{ + text: 'Create an Omnivore email address and use it to subscribe to newsletters. The newsletters will be automatically added to your library.', + docs: 'https://docs.omnivore.app/using/inbox.html', + key: '--settings-emails-show-help', + }} > {sortedEmailAddresses.length > 0 ? ( sortedEmailAddresses.map((email, i) => { diff --git a/packages/web/pages/settings/emails/recent.tsx b/packages/web/pages/settings/emails/recent.tsx index 3dd1107b4..ce1dace90 100644 --- a/packages/web/pages/settings/emails/recent.tsx +++ b/packages/web/pages/settings/emails/recent.tsx @@ -168,9 +168,14 @@ export default function RecentEmails(): JSX.Element { return ( {sortedRecentEmails.length > 0 ? ( sortedRecentEmails.map((recentEmail: RecentEmail, i) => { diff --git a/packages/web/pages/settings/feeds/index.tsx b/packages/web/pages/settings/feeds/index.tsx index cbe310e8c..7abae14d2 100644 --- a/packages/web/pages/settings/feeds/index.tsx +++ b/packages/web/pages/settings/feeds/index.tsx @@ -96,6 +96,11 @@ export default function Rss(): JSX.Element { createAction={() => { router.push('/settings/feeds/add') }} + suggestionInfo={{ + text: 'Add RSS and Atom feeds to your Omnivore account. When you add a new feed the last 24hrs of items, or at least one item will be added to your account.', + docs: 'https://docs.omnivore.app/using/feeds.html', + key: '--settings-feeds-show-help', + }} > {subscriptions.length === 0 ? ( diff --git a/packages/web/pages/settings/labels.tsx b/packages/web/pages/settings/labels.tsx index 572002fc2..bfbd31665 100644 --- a/packages/web/pages/settings/labels.tsx +++ b/packages/web/pages/settings/labels.tsx @@ -35,6 +35,8 @@ import { import { LabelChip } from '../../components/elements/LabelChip' import { ConfirmationModal } from '../../components/patterns/ConfirmationModal' import { InfoLink } from '../../components/elements/InfoLink' +import { SuggestionBox } from '../../components/elements/SuggestionBox' +import { usePersistedState } from '../../lib/hooks/usePersistedState' const HeaderWrapper = styled(Box, { width: '100%', @@ -153,6 +155,10 @@ export default function LabelsPage(): JSX.Element { const [confirmRemoveLabelId, setConfirmRemoveLabelId] = useState< string | null >(null) + const [showLabelPageHelp, setShowLabelPageHelp] = usePersistedState({ + key: `--settings-labels-show-help`, + initialValue: true, + }) const breakpoint = 768 applyStoredTheme(false) @@ -270,6 +276,21 @@ export default function LabelsPage(): JSX.Element { onOpenChange={() => setConfirmRemoveLabelId(null)} /> ) : null} + {showLabelPageHelp && ( + + { + setShowLabelPageHelp(false) + }} + /> + + )} <> {sortedSubscriptions.length > 0 ? (