Merge pull request #2713 from omnivore-app/fix/web-suggestion-boxes

Add suggestion boxes for new users to the web
This commit is contained in:
Jackson Harper
2023-09-04 17:09:15 +08:00
committed by GitHub
38 changed files with 512 additions and 138 deletions

View File

@ -20,7 +20,7 @@ export const Button = styled('button', {
},
ctaDarkYellow: {
border: '1px solid transparent',
fontSize: '13px',
fontSize: '14px',
fontWeight: 500,
fontFamily: 'Inter',
borderRadius: '5px',
@ -77,8 +77,8 @@ export const Button = styled('button', {
fontFamily: 'Inter',
borderRadius: '8px',
cursor: 'pointer',
color: 'white',
p: '10px 12px',
color: '$thTextContrast2',
bg: 'rgb(125, 125, 125, 0.3)',
'&:hover': {
bg: 'rgb(47, 47, 47, 0.1)',

View File

@ -53,7 +53,7 @@ export const DropdownContent = styled(Content, {
borderRadius: '6px',
outline: '1px solid #323232',
border: '1px solid $grayBorder',
boxShadow: '$cardBoxShadow',
boxShadow: '0px 1px 2px 0px rgba(0, 0, 0, 0.05);',
'--arrow-visibility': '',
'&[data-side="top"]': {
'--arrow-visibility': 'collapse',

View File

@ -0,0 +1,131 @@
import { HStack, SpanBox, VStack } from './LayoutPrimitives'
import { theme } from '../tokens/stitches.config'
import { Button } from './Button'
import { CloseIcon } from './icons/CloseIcon'
import { HelpfulOwlImage } from './images/HelpfulOwlImage'
import { ArrowSquareOut } from 'phosphor-react'
import { useEffect, useState } from 'react'
type FeatureHelpBoxProps = {
helpTitle: string
helpMessage: string
helpCTAText?: string
onClickCTA?: () => void
docsMessage: string
docsDestination: string
onDismiss: () => void
}
export const FeatureHelpBox = (props: FeatureHelpBoxProps) => {
const [display, setDisplay] = useState(false)
useEffect(() => {
setDisplay(true)
}, [])
if (!display) {
return <></>
}
return (
<HStack
css={{
gap: '10px',
my: '40px',
display: 'flex',
width: 'fit-content',
borderRadius: '5px',
background: '$thBackground5',
fontSize: '15px',
fontFamily: '$inter',
fontWeight: '500',
color: '$grayText',
px: '20px',
py: '20px',
}}
alignment="start"
distribution="start"
>
<HStack css={{ gap: '30px' }}>
<SpanBox
css={{
pt: '7px',
alignSelf: 'center',
'@smDown': { display: 'none' },
}}
>
<HelpfulOwlImage width={254} height={333} />
</SpanBox>
<HelpSection {...props} />
</HStack>
</HStack>
)
}
const HelpSection = (props: FeatureHelpBoxProps) => {
return (
<VStack css={{ gap: '20px' }}>
<HStack css={{ width: '100%', gap: '20px' }} distribution="between">
<SpanBox
css={{
fontSize: '22px',
fontFamily: '$display',
color: '$thTextContrast2',
}}
>
{props.helpTitle}
</SpanBox>
<Button
style="plainIcon"
title="Hide this tip"
css={{ pt: '7px' }}
onClick={(event) => {
props.onDismiss()
event.preventDefault()
}}
>
<CloseIcon
size={25}
color={theme.colors.thTextContrast2.toString()}
/>
</Button>
</HStack>
<SpanBox>{props.helpMessage}</SpanBox>
<HStack css={{ gap: '20px' }}>
{props.helpCTAText && props.onClickCTA && (
<Button
style="ctaDarkYellow"
onClick={(event) => {
if (props.onClickCTA) {
props.onClickCTA()
event.preventDefault()
}
}}
css={{ '@smDown': { display: 'none' } }}
>
{props.helpCTAText}
</Button>
)}
<Button
style="ctaLightGray"
onClick={(event) => {
window.open(props.docsDestination, '_blank', 'noreferrer')
event.preventDefault()
}}
css={{ display: 'flex', flexDirection: 'row', gap: '10px' }}
>
{props.docsMessage}
<SpanBox css={{ alignSelf: 'center' }}>
<ArrowSquareOut
size={12}
color={theme.colors.thTextContrast2.toString()}
/>
</SpanBox>
</Button>
</HStack>
</VStack>
)
}

View File

@ -0,0 +1,118 @@
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 './icons/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 (
<SpanBox
css={{
cursor: 'pointer',
a: {
color: '$omnivoreCtaYellow',
},
}}
>
{!isExternal ? (
<Link href={props.link}>{props.children}</Link>
) : (
<a href={props.link} target="_blank" rel="noreferrer">
{props.children}
</a>
)}
</SpanBox>
)
}
export const SuggestionBox = (props: SuggestionBoxProps) => {
return (
<HStack
css={{
gap: '10px',
display: 'flex',
flexDirection: props.size == 'large' ? 'column' : 'row',
width: 'fit-content',
borderRadius: '5px',
background: props.background ?? '$thBackground3',
fontSize: '15px',
fontFamily: '$inter',
fontWeight: '500',
color: '$thTextContrast',
px: '15px',
py: props.size == 'large' ? '15px' : '10px',
justifyContent: 'flex-start',
'@smDown': {
flexDirection: 'column',
alignItems: 'center',
width: '100%',
},
}}
>
<VStack>
{props.dismissible && (
<SpanBox
css={{
marginLeft: 'auto',
lineHeight: '2',
}}
>
<Button
style="plainIcon"
css={{
fontSize: '10',
fontWeight: '600',
}}
>
<CloseIcon size={2} color="white" />
</Button>
</SpanBox>
)}
{props.helpMessage}
{props.helpTarget && (
<InternalOrExternalLink link={props.helpTarget}>
<SpanBox
css={{
display: 'flex',
alignItems: 'center',
color: '$omnivoreCtaYellow',
pt: '15px',
gap: '2px',
'&:hover': {
textDecoration: 'underline',
},
}}
>
<>{props.helpCTAText}</>
<ArrowRightIcon
size={25}
color={theme.colors.omnivoreCtaYellow.toString()}
/>
</SpanBox>
</InternalOrExternalLink>
)}
</VStack>
</HStack>
)
}

View File

@ -0,0 +1,39 @@
/* eslint-disable functional/no-class */
/* eslint-disable functional/no-this-expression */
import { IconProps } from './IconProps'
import React from 'react'
export class CloseIcon extends React.Component<IconProps> {
render() {
const size = (this.props.size || 26).toString()
const color = (this.props.color || '#2A2A2A').toString()
return (
<svg
width={size}
height={size}
viewBox={`0 0 26 26`}
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g>
<path
d="M10.7441 10.9399L14.9108 15.1066M14.9108 10.9399L10.7441 15.1066"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12.8281 3.64844C20.3281 3.64844 22.2031 5.52344 22.2031 13.0234C22.2031 20.5234 20.3281 22.3984 12.8281 22.3984C5.32813 22.3984 3.45312 20.5234 3.45312 13.0234C3.45312 5.52344 5.32813 3.64844 12.8281 3.64844Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
</svg>
)
}
}

View File

@ -0,0 +1,17 @@
import Image from 'next/image'
type HelpfulOwlImageProps = {
width: number
height: number
}
export const HelpfulOwlImage = (props: HelpfulOwlImageProps) => {
return (
<Image
src="/static/images/helpful-owl@2x.png"
width={200}
height={200}
alt="Picture of an owl reading"
/>
)
}

View File

@ -25,7 +25,6 @@ MdEditor.use(Plugins.TabInsert, {
tabMapValue: 1, // note that 1 means a '\t' instead of ' '.
})
console.log()
MdEditor.use(Counter)
type NoteSectionProps = {

View File

@ -3,6 +3,7 @@ import { OmnivoreNameLogo } from '../elements/images/OmnivoreNameLogo'
import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery'
import { PrimaryDropdown } from '../templates/PrimaryDropdown'
import { HEADER_HEIGHT } from '../templates/homeFeed/HeaderSpacer'
import { LogoBox } from '../elements/LogoBox'
type HeaderProps = {
user?: UserBasicData
@ -21,25 +22,14 @@ export function SettingsHeader(props: HeaderProps): JSX.Element {
display: 'flex',
position: 'fixed',
width: '100%',
px: '25px',
pr: '25px',
height: HEADER_HEIGHT,
bg: '$thBackground3',
borderBottom: '1px solid $thBorderColor',
'@mdDown': {
px: '15px',
pr: '15px',
},
}}
>
<Box
css={{
display: 'flex',
alignItems: 'center',
paddingRight: '10px',
}}
>
<OmnivoreNameLogo href={props.user ? '/home' : '/login'} />
</Box>
<LogoBox />
<HStack css={{ ml: 'auto' }}>
<PrimaryDropdown showThemeSection={true} />
</HStack>

View File

@ -13,7 +13,6 @@ type ErrorLayoutProps = {
export function ErrorLayout(props: ErrorLayoutProps): JSX.Element {
const { viewerData } = useGetViewerQuery()
console.log(viewerData?.me)
return (
<VStack alignment="center" distribution="start" css={{ height: '100%' }}>

View File

@ -49,7 +49,6 @@ export function SettingsLayout(props: SettingsLayoutProps): JSX.Element {
<Box
css={{
height: HEADER_HEIGHT,
bg: '$grayBase',
}}
></Box>
{props.children}

View File

@ -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 (
<HStack
css={{
gap: '10px',
width: 'fit-content',
borderRadius: '5px',
background: '$thBackground3',
fontSize: '15px',
fontFamily: '$inter',
fontWeight: '500',
color: '$thTextContrast',
padding: '10px',
justifyContent: 'flex-start',
'@smDown': {
flexDirection: 'column',
alignItems: 'center',
width: '100%',
},
}}
>
{helpMessage}
<SpanBox css={{ cursor: 'pointer' }}>
<Link href={helpTarget} passHref>
<SpanBox
css={{
display: 'flex',
alignItems: 'center',
color: '$omnivoreCtaYellow',
gap: '2px',
'&:hover': {
textDecoration: 'underline',
},
}}
>
<>Click Here</>
<ArrowRightIcon
size={25}
color={theme.colors.omnivoreCtaYellow.toString()}
/>
</SpanBox>
</Link>
</SpanBox>
</HStack>
<>
{helpMessage[0] ? (
<SuggestionBox
helpMessage={helpMessage[0]}
helpCTAText={helpMessage[1]}
helpTarget={helpTarget}
/>
) : (
<></>
)}
</>
)
}
@ -187,6 +200,12 @@ export const EmptyLibrary = (props: EmptyLibraryProps) => {
const type = useMemo<MessageType>(() => {
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) => {
}}
>
<ErrorBox type={type} />
<SuggestionBox type={type} />
<Suggestion type={type} />
</Box>
)
}

View File

@ -8,6 +8,9 @@ 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'
import { FeatureHelpBox } from '../../elements/FeatureHelpBox'
type SettingsTableProps = {
pageId: string
@ -17,6 +20,8 @@ type SettingsTableProps = {
createTitle?: string
createAction?: () => void
suggestionInfo: SuggestionInfo
children: React.ReactNode
}
@ -52,6 +57,16 @@ type MoreOptionsProps = {
onEdit?: () => void
}
type SuggestionInfo = {
title: string
message: string
docs: string
key: string
CTAText?: string
onClickCTA?: () => void
}
const MoreOptions = (props: MoreOptionsProps) => (
<Dropdown
align={'end'}
@ -273,6 +288,11 @@ const CreateButton = (props: CreateButtonProps): JSX.Element => {
}
export const SettingsTable = (props: SettingsTableProps): JSX.Element => {
const [showSuggestion, setShowSuggestion] = usePersistedState<boolean>({
key: props.suggestionInfo.key,
initialValue: !!props.suggestionInfo,
})
return (
<SettingsLayout>
<Toaster
@ -299,6 +319,19 @@ export const SettingsTable = (props: SettingsTableProps): JSX.Element => {
},
}}
>
{props.suggestionInfo && showSuggestion && (
<FeatureHelpBox
helpTitle={props.suggestionInfo.title}
helpMessage={props.suggestionInfo.message}
docsMessage={'Read the Docs'}
docsDestination={props.suggestionInfo.docs}
onDismiss={() => {
setShowSuggestion(false)
}}
helpCTAText={props.suggestionInfo.CTAText}
onClickCTA={props.suggestionInfo.onClickCTA}
/>
)}
<Box
css={{
width: '100%',

View File

@ -103,8 +103,7 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
borderWidths: {},
borderStyles: {},
shadows: {
panelShadow: '0px 4px 18px rgba(120, 123, 134, 0.12)',
cardBoxShadow: '0px 16px 25px 16px rgba(32, 31, 29, 0.1)',
cardBoxShadow: '0px 1px 2px 0px rgba(0, 0, 0, 0.05);',
},
zIndices: {},
transitions: {},

View File

@ -99,7 +99,6 @@ export const useReaderSettings = (): ReaderSettings => {
break
case 'setMarginWidth': {
const value = Number(arg)
console.log('setMarginWidth: ', value)
if (value >= 200 && value <= 560) {
setMarginWidth(value)
}

View File

@ -26,11 +26,8 @@ export async function addPopularReadMutation(
}
`
console.log('addPopularReadMutation', mutation)
try {
const response = await gqlFetcher(mutation, { readName })
console.log('response', response)
const data = response as AddPopularReadResponse | undefined
return data?.addPopularRead?.pageId
} catch (error) {

View File

@ -45,8 +45,6 @@ export async function bulkActionMutation(
}
`
console.log('bulkActionbulkActionMutation', mutation)
try {
const response = await gqlFetcher(mutation, {
action,
@ -54,7 +52,6 @@ export async function bulkActionMutation(
labelIds,
expectedCount,
})
console.log('response', response)
const data = response as BulkActionResponse | undefined
return data?.bulkAction?.success ?? false
} catch (error) {

View File

@ -11,7 +11,9 @@ type CreateNewsletterEmail = {
newsletterEmail: NewsletterEmail
}
export async function createNewsletterEmailMutation(): Promise<string | undefined> {
export async function createNewsletterEmailMutation(): Promise<
string | undefined
> {
const mutation = gql`
mutation createNewsletterEmailMutation {
createNewsletterEmail {
@ -29,9 +31,10 @@ export async function createNewsletterEmailMutation(): Promise<string | undefine
`
try {
const data = await gqlFetcher(mutation) as CreateNewsletterEmailResult
console.log('created email', data)
return data.errorCodes ? undefined : data.createNewsletterEmail.newsletterEmail.id
const data = (await gqlFetcher(mutation)) as CreateNewsletterEmailResult
return data.errorCodes
? undefined
: data.createNewsletterEmail.newsletterEmail.id
} catch (error) {
console.log('createNewsletterEmailMutation error', error)
return undefined

View File

@ -12,7 +12,8 @@ export async function createReminderMutation(
linkId: string,
reminderType: ReminderType,
archiveUntil: boolean,
sendNotification: boolean): Promise<string | undefined> {
sendNotification: boolean
): Promise<string | undefined> {
const mutation = gql`
mutation createReminderMutation($input: CreateReminderInput!) {
createReminder(input: $input) {
@ -35,13 +36,12 @@ export async function createReminderMutation(
reminderType,
archiveUntil,
sendNotification,
scheduledAt: new Date()
scheduledAt: new Date(),
}
const data = await gqlFetcher(mutation, { input })
console.log('created reminder', data)
return 'data'
} catch (error) {
console.log('createReminder error', error)
return undefined
}
}
}

View File

@ -26,8 +26,6 @@ export async function deleteAccountMutation(
}
`
console.log('deleteAccountMutation', mutation)
try {
const response = await gqlFetcher(mutation, { userId })
console.log('response', response)

View File

@ -35,7 +35,6 @@ export async function deleteRuleMutation(id: string): Promise<Rule> {
const data = (await gqlFetcher(mutation, { id })) as DeleteRuleResult
const output = data as any
const error = data.deleteRule?.errorCodes?.find(() => true)
console.log('DATA: ', output.deleteRule)
if (error) {
throw error
}

View File

@ -25,7 +25,6 @@ export async function importFromIntegrationMutation(
}`
const data = await gqlFetcher(mutation, { integrationId })
console.log('integrationId: ', data)
const output = data as ImportFromIntegrationDataResponseData | undefined
const error = output?.importFromIntegration?.errorCodes?.find(() => true)
console.log('error: ', error)

View File

@ -38,10 +38,7 @@ export async function joinGroupMutation(
}
`
console.log('JoinGroupMutation', mutation)
const response = await gqlFetcher(mutation, { inviteCode })
console.log(' -- response', response)
const data = response as JoinGroupResponse | undefined
const error = data?.errorCodes?.find(() => true)
if (error) {

View File

@ -25,7 +25,6 @@ export async function markEmailAsItemMutation(
}`
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)

View File

@ -31,18 +31,10 @@ export async function setLabelsForHighlight(
${labelFragment}
`
console.log(
'setting label for highlight id: ',
highlightId,
'labelIds',
labelIds
)
try {
const data = (await gqlFetcher(mutation, {
input: { highlightId, labelIds },
})) as SetLabelsForHighlightResult
console.log(' -- errorCodes', data.setLabelsForHighlight.errorCodes)
return data.setLabelsForHighlight.errorCodes
? undefined

View File

@ -3,8 +3,8 @@ import { gqlFetcher } from '../networkHelpers'
export type UpdateLabelInput = {
labelId: string
name: string,
color: string,
name: string
color: string
description?: string
}
@ -39,9 +39,7 @@ export async function updateLabelMutation(
try {
const data = await gqlFetcher(mutation)
console.log(input, data);
const output = data as any
console.log(output)
return output?.updatedLabel
} catch (err) {
return undefined

View File

@ -32,10 +32,8 @@ export async function uploadImportFileRequestMutation(
}`
const data = await gqlFetcher(mutation, { type, contentType })
console.log('UploadImportFile: ', data)
const output = data as UploadImportFileResponseData | undefined
const error = output?.uploadImportFile?.errorCodes?.find(() => true)
console.log('error: ', error)
if (error) {
throw error
}

View File

@ -53,8 +53,6 @@ export function useGetArticleOriginalHtmlQuery({
)
const resultData: ArticleData | undefined = data as ArticleData
console.log('RESULT', JSON.stringify(data))
return resultData?.article.article.originalHtml
}

View File

@ -53,8 +53,6 @@ export function useGetIntegrationsQuery(): IntegrationsQueryResponse {
`
const { data, mutate, isValidating } = useSWR(query, publicGqlFetcher)
console.log('integrations data', data)
try {
if (data) {
const result = data as IntegrationsQueryResponseData

View File

@ -62,8 +62,6 @@ export function useGetWebhooksQuery(): WebhooksQueryResponse {
`
const { data, mutate, isValidating } = useSWR(query, publicGqlFetcher)
console.log('webhooks data', data)
try {
if (data) {
const result = data as WebhooksQueryResponseData

View File

@ -2,7 +2,7 @@ const ContentSecurityPolicy = `
default-src 'self';
base-uri 'self';
block-all-mixed-content;
connect-src 'self' ${process.env.NEXT_PUBLIC_SERVER_BASE_URL} https://proxy-prod.omnivore-image-cache.app https://accounts.google.com https://proxy-demo.omnivore-image-cache.app https://storage.googleapis.com https://api.segment.io https://cdn.segment.com https://widget.intercom.io https://api-iam.intercom.io https://static.intercomassets.com https://downloads.intercomcdn.com https://platform.twitter.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io wss://nexus-europe-websocket.intercom.io wss://nexus-australia-websocket.intercom.io;
connect-src 'self' ${process.env.NEXT_PUBLIC_SERVER_BASE_URL} https://proxy-prod.omnivore-image-cache.app https://accounts.google.com https://proxy-demo.omnivore-image-cache.app https://storage.googleapis.com https://api.segment.io https://cdn.segment.com https://widget.intercom.io https://api-iam.intercom.io https://static.intercomassets.com https://downloads.intercomcdn.com https://platform.twitter.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io wss://nexus-europe-websocket.intercom.io wss://nexus-australia-websocket.intercom.io https://tools.applemediaservices.com;
font-src 'self' data: https://cdn.jsdelivr.net https://js.intercomcdn.com https://fonts.intercomcdn.com;
form-action 'self' ${process.env.NEXT_PUBLIC_SERVER_BASE_URL} https://getpocket.com/auth/authorize https://intercom.help https://api-iam.intercom.io https://api-iam.eu.intercom.io https://api-iam.au.intercom.io;
frame-ancestors 'none';

View File

@ -65,7 +65,6 @@ export default function Api(): JSX.Element {
name: 'expiredAt',
required: true,
onChange: (e) => {
console.log('onChange: ', e)
let additionalDays = 0
switch (e.target.value) {
case 'in 7 days':
@ -114,13 +113,28 @@ export default function Api(): JSX.Element {
pageId="api-keys"
pageInfoLink="https://docs.omnivore.app/integrations/api.html"
headerTitle="API Keys"
createTitle="Generate API Key"
createTitle="Create an API Key"
createAction={() => {
onAdd()
setName('')
setExpiresAt(neverExpiresDate)
setAddModalOpen(true)
}}
suggestionInfo={{
title:
'Use API keys to Integrate Omnivore with other apps and services',
message:
'Create API keys to connect Omnivore to other apps such as Logseq and Obsidian or to query the API. Check out the integrations documentation for more info on connecting to Omnivore via the API.',
docs: 'https://docs.omnivore.app/integrations/api.html',
key: '--settings-apikeys-show-help',
CTAText: 'Create an API Key',
onClickCTA: () => {
onAdd()
setName('')
setExpiresAt(neverExpiresDate)
setAddModalOpen(true)
},
}}
>
{sortedApiKeys.length > 0 ? (
sortedApiKeys.map((apiKey, i) => {
@ -163,7 +177,7 @@ export default function Api(): JSX.Element {
{addModalOpen && (
<FormModal
title={'Generate API Key'}
title={'Create an API Key'}
onSubmit={onCreate}
onOpenChange={setAddModalOpen}
inputs={formInputs}

View File

@ -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,17 @@ export default function EmailsPage(): JSX.Element {
headerTitle="Address"
createTitle="Create a new email address"
createAction={createEmail}
suggestionInfo={{
title: 'Subscribe to newsletters with an Omnivore Email Address',
message:
'Create an Omnivore email address and use it to subscribe to newsletters or send yourself documents. Newsletters and documents will be categorized and added to your library when we receive a message. View all received emails with the "Recently Received Emails" link at the bottom of this page.',
docs: 'https://docs.omnivore.app/using/inbox.html',
key: '--settings-emails-show-help',
CTAText: 'Create an email address',
onClickCTA: () => {
createEmail()
},
}}
>
{sortedEmailAddresses.length > 0 ? (
sortedEmailAddresses.map((email, i) => {

View File

@ -168,9 +168,17 @@ export default function RecentEmails(): JSX.Element {
return (
<SettingsTable
pageId="api-keys"
pageId="recent-emails"
pageInfoLink="https://docs.omnivore.app/using/inbox.html"
headerTitle="Recently Received Emails"
suggestionInfo={{
title:
'View original emails that have been recently received in your Omnivore inbox.',
message:
"Your 30 most recent emails are stored below. You can click on each email to view its original content or it's text content. If an email was not correctly classified as an article you can mark it as an article and it will be added to your library.",
docs: 'https://docs.omnivore.app/using/inbox.html',
key: '--settings-recent-emails-show-help',
}}
>
{sortedRecentEmails.length > 0 ? (
sortedRecentEmails.map((recentEmail: RecentEmail, i) => {

View File

@ -92,10 +92,21 @@ export default function Rss(): JSX.Element {
pageId={'feeds'}
pageInfoLink="https://docs.omnivore.app/using/feeds.html"
headerTitle="Subscribed feeds"
createTitle="Add feed"
createTitle="Add a feed"
createAction={() => {
router.push('/settings/feeds/add')
}}
suggestionInfo={{
title: 'Add RSS and Atom feeds to your Omnivore account',
message:
'When you add a new feed the last 24hrs of items, or at least one item will be added to your account. Feeds will be checked for updates every hour, and new items will be added to your library.',
docs: 'https://docs.omnivore.app/using/feeds.html',
key: '--settings-feeds-show-help',
CTAText: 'Add a feed',
onClickCTA: () => {
router.push('/settings/feeds/add')
},
}}
>
{subscriptions.length === 0 ? (
<EmptySettingsRow text={isValidating ? '-' : 'No feeds subscribed'} />

View File

@ -35,6 +35,9 @@ 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'
import { FeatureHelpBox } from '../../components/elements/FeatureHelpBox'
const HeaderWrapper = styled(Box, {
width: '100%',
@ -153,6 +156,10 @@ export default function LabelsPage(): JSX.Element {
const [confirmRemoveLabelId, setConfirmRemoveLabelId] = useState<
string | null
>(null)
const [showLabelPageHelp, setShowLabelPageHelp] = usePersistedState<boolean>({
key: `--settings-labels-show-help`,
initialValue: true,
})
const breakpoint = 768
applyStoredTheme(false)
@ -270,6 +277,23 @@ export default function LabelsPage(): JSX.Element {
onOpenChange={() => setConfirmRemoveLabelId(null)}
/>
) : null}
{showLabelPageHelp && (
<FeatureHelpBox
helpTitle="Use labels to organize your library and optimize your workflow."
helpMessage="Use this page to view and edit all your labels. Labels can be attached to individual library items, or your highlights, and are used to keep your library organized."
docsMessage={'Read the Docs'}
docsDestination="https://docs.omnivore.app/using/organizing.html#labels"
onDismiss={() => {
setShowLabelPageHelp(false)
}}
helpCTAText="Create a label"
onClickCTA={() => {
resetLabelState()
handleGenerateRandomColor()
setIsCreateMode(true)
}}
/>
)}
<HeaderWrapper>
<Box
style={{
@ -305,24 +329,11 @@ export default function LabelsPage(): JSX.Element {
>
<SpanBox
css={{
display: 'none',
'@md': {
display: 'flex',
},
}}
>
<SpanBox>Add Label</SpanBox>
</SpanBox>
<SpanBox
css={{
p: '0',
display: 'flex',
'@md': {
display: 'none',
},
'@md': {},
}}
>
<Plus size={24} />
<SpanBox>Create a label</SpanBox>
</SpanBox>
</Button>
</>

View File

@ -44,8 +44,15 @@ export default function SubscriptionsPage(): JSX.Element {
return (
<SettingsTable
pageId="settings-subscriptions-tag"
pageInfoLink="https://docs.omnivore.app/using/inbox.html"
pageInfoLink="https://docs.omnivore.app/using/feeds.html"
headerTitle="Subscriptions"
suggestionInfo={{
title: 'View and manage all your Feed and Newsletter subscriptions',
message:
'Use this page to view and manage all the Feeds (RSS & Atom) and Newsletters you have subscribed to.',
docs: 'https://docs.omnivore.app/using/inbox.html',
key: '--settings-recent-subscriptions-show-help',
}}
>
<>
{sortedSubscriptions.length > 0 ? (

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB