diff --git a/packages/web/components/elements/icons/EmptyHighlightsIcon.tsx b/packages/web/components/elements/icons/EmptyHighlightsIcon.tsx
new file mode 100644
index 000000000..7cc465a81
--- /dev/null
+++ b/packages/web/components/elements/icons/EmptyHighlightsIcon.tsx
@@ -0,0 +1,110 @@
+/* eslint-disable functional/no-class */
+/* eslint-disable functional/no-this-expression */
+import { useCurrentTheme } from '../../../lib/hooks/useCurrentTheme'
+import { IconProps } from './IconProps'
+
+import React from 'react'
+
+export function EmptyHighlightsIcon(): JSX.Element {
+ const { currentTheme } = useCurrentTheme()
+ switch (currentTheme) {
+ case 'Sepia':
+ return
+ case 'Apollo':
+ case 'Dark':
+ return
+ case 'Light':
+ return
+ }
+ return
+}
+
+class EmptyHighlightsIconDark extends React.Component {
+ render() {
+ return (
+
+ )
+ }
+}
+
+class EmptyHighlightsIconLight extends React.Component {
+ render() {
+ return (
+
+ )
+ }
+}
+
+class EmptyHighlightsIconSepia extends React.Component {
+ render() {
+ return (
+
+ )
+ }
+}
diff --git a/packages/web/components/elements/icons/EmptyLibraryNotes.tsx b/packages/web/components/elements/icons/EmptyLibraryNotes.tsx
new file mode 100644
index 000000000..e8437f301
--- /dev/null
+++ b/packages/web/components/elements/icons/EmptyLibraryNotes.tsx
@@ -0,0 +1,175 @@
+/* eslint-disable functional/no-class */
+/* eslint-disable functional/no-this-expression */
+import { useCurrentTheme } from '../../../lib/hooks/useCurrentTheme'
+import { IconProps } from './IconProps'
+
+import React from 'react'
+
+export function EmptyLibraryIcon(): JSX.Element {
+ const { currentTheme } = useCurrentTheme()
+ switch (currentTheme) {
+ case 'Sepia':
+ return
+ case 'Apollo':
+ case 'Dark':
+ return
+ case 'Light':
+ return
+ }
+ return
+}
+
+class EmptyLibraryIconDark extends React.Component {
+ render() {
+ return (
+
+ )
+ }
+}
+
+class EmptyLibraryIconLight extends React.Component {
+ render() {
+ return (
+
+ )
+ }
+}
+
+class EmptyLibraryIconSepia extends React.Component {
+ render() {
+ console.log('rendering sepia icon')
+
+ return (
+
+ )
+ }
+}
diff --git a/packages/web/components/elements/icons/EmptyTrashIcon.tsx b/packages/web/components/elements/icons/EmptyTrashIcon.tsx
new file mode 100644
index 000000000..f79980909
--- /dev/null
+++ b/packages/web/components/elements/icons/EmptyTrashIcon.tsx
@@ -0,0 +1,159 @@
+/* eslint-disable functional/no-class */
+/* eslint-disable functional/no-this-expression */
+import { useCurrentTheme } from '../../../lib/hooks/useCurrentTheme'
+import { IconProps } from './IconProps'
+
+import React from 'react'
+
+export function EmptyTrashIcon(): JSX.Element {
+ const { currentTheme } = useCurrentTheme()
+ switch (currentTheme) {
+ case 'Sepia':
+ return
+ case 'Apollo':
+ case 'Dark':
+ return
+ case 'Light':
+ return
+ }
+ return
+}
+
+class EmptyTrashIconDark extends React.Component {
+ render() {
+ return (
+
+ )
+ }
+}
+
+class EmptyTrashIconLight extends React.Component {
+ render() {
+ return (
+
+ )
+ }
+}
+
+class EmptyTrashIconSepia extends React.Component {
+ render() {
+ return (
+
+ )
+ }
+}
diff --git a/packages/web/components/nav-containers/HighlightsContainer.tsx b/packages/web/components/nav-containers/HighlightsContainer.tsx
index 84792e30e..a6a03716c 100644
--- a/packages/web/components/nav-containers/HighlightsContainer.tsx
+++ b/packages/web/components/nav-containers/HighlightsContainer.tsx
@@ -32,14 +32,13 @@ import { highlightColor } from '../../lib/themeUpdater'
import { HighlightViewNote } from '../patterns/HighlightNotes'
import { theme } from '../tokens/stitches.config'
import { useDeleteHighlight } from '../../lib/networking/highlights/useItemHighlights'
+import { EmptyLibrary } from '../templates/homeFeed/EmptyLibrary'
const PAGE_SIZE = 10
export function HighlightsContainer(): JSX.Element {
const router = useRouter()
const viewer = useGetViewerQuery()
- const [showFilterMenu, setShowFilterMenu] = useState(false)
- const [_, setShowAddLinkModal] = useState(false)
const { isLoading, setSize, size, data, mutate } = useGetHighlights({
first: PAGE_SIZE,
@@ -73,12 +72,18 @@ export function HighlightsContainer(): JSX.Element {
css={{
padding: '20px',
margin: '30px',
+ width: '100%',
'@mdDown': {
margin: '0px',
marginTop: '50px',
},
}}
>
+ {!isLoading && highlights.length < 1 && (
+
+
+
+ )}
{highlights.map((highlight) => {
return (
viewer.viewerData?.me && (
@@ -132,9 +137,8 @@ function HighlightCard(props: HighlightCardProps): JSX.Element {
const [isOpen, setIsOpen] = useState(false)
const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] =
useState(undefined)
- const [labelsTarget, setLabelsTarget] = useState(
- undefined
- )
+ const [labelsTarget, setLabelsTarget] =
+ useState(undefined)
const deleteHighlight = useDeleteHighlight()
const viewInReader = useCallback(
diff --git a/packages/web/components/templates/homeFeed/EmptyLibrary.tsx b/packages/web/components/templates/homeFeed/EmptyLibrary.tsx
index 058fb8f48..6a413b0a6 100644
--- a/packages/web/components/templates/homeFeed/EmptyLibrary.tsx
+++ b/packages/web/components/templates/homeFeed/EmptyLibrary.tsx
@@ -1,58 +1,55 @@
import { Box, VStack } from '../../elements/LayoutPrimitives'
import { useMemo } from 'react'
import { SuggestionBox, SuggestionAction } from '../../elements/SuggestionBox'
-import { ConfusedSlothIcon } from '../../elements/icons/ConfusedSlothIcon'
import { DEFAULT_HEADER_HEIGHT } from './HeaderSpacer'
+import { EmptyLibraryIcon } from '../../elements/icons/EmptyLibraryNotes'
+import { EmptyHighlightsIcon } from '../../elements/icons/EmptyHighlightsIcon'
+import { EmptyTrashIcon } from '../../elements/icons/EmptyTrashIcon'
+import { useGetSubscriptions } from '../../../lib/networking/subscriptions/useGetSubscriptions'
type EmptyLibraryProps = {
- searchTerm: string | undefined
- onAddLinkClicked: () => void
+ folder: string | undefined
+ onAddLinkClicked?: () => void | undefined
}
-type MessageType =
- | 'inbox'
- | 'continue'
- | 'non-feed'
- | 'highlight'
- | 'unlabeled'
- | 'files'
- | 'archive'
- | 'feed'
- | 'subscription'
- | 'newsletter'
- | 'library'
-
-type HelpMessageProps = {
- type: MessageType
+const Icon = (props: EmptyLibraryProps) => {
+ console.log('empty icon:', props.folder)
+ switch (props.folder) {
+ case 'highlights':
+ return
+ case 'trash':
+ return
+ default:
+ return
+ }
}
-export const ErrorBox = (props: HelpMessageProps) => {
- const errorTitle = useMemo(() => {
- switch (props.type) {
- case 'inbox':
- return 'Your inbox is empty. The inbox will contain all your non-archived saved items.'
- case 'continue':
- return "No continue reading items. Continue Reading items are items you have started but haven't finished reading."
- case 'non-feed':
- return "No non-feed items found. Non-feed items are items you've add to the library using the mobile apps, browser extensions, or Add Link button. Not newsletter or feed items."
- case 'highlight':
- return 'No highlights found. Add highlights to your library by highlighting text in the reader view.'
- case 'unlabeled':
- return 'No unlabeled items found. Items without labels can be found here. Use this query to easily triage your library.'
+const Title = (props: EmptyLibraryProps) => {
+ // If its the subscriptions folder we fetch the subscriptions
+ // to prevent a better message
+ const { data: subscriptions, isLoading } = useGetSubscriptions(
+ {},
+ props.folder == 'subscriptions'
+ )
+
+ const titleText = (folder: string | undefined) => {
+ switch (folder) {
+ case 'highlights':
+ return 'You do not have any highlights yet.'
+ case 'trash':
+ return 'Your trash 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 'subscription':
+ case 'following':
+ if (isLoading || (subscriptions?.length ?? 0) > 0) {
+ return 'No subscription items found.'
+ }
return 'You do not have any subscriptions.'
- case 'newsletter':
- return 'You do not have any newsletter items matching this query.'
+ case 'home':
+ return 'Your homepage is empty.'
}
- return 'No results found for this query.'
- }, [props.type])
-
+ return 'No results found.'
+ }
return (
{
marginBlockStart: '0px',
marginBlockEnd: '10px',
lineHeight: '125%',
- fontSize: '20px',
+ fontSize: '23px',
fontFamily: '$inter',
fontWeight: 'bold',
+ textAlign: 'center',
}}
>
- {errorTitle}
+ {titleText(props.folder)}
)
}
-type SuggestionMessage = {
- message: string
- actions: SuggestionAction[]
-}
-
-export const Suggestion = (props: HelpMessageProps) => {
- const helpMessage = useMemo(() => {
- switch (props.type) {
- case 'feed':
- return {
- message: 'Want to add an RSS or Atom Subscription?',
- actions: [
- { text: 'Add an RSS or Atom feed', url: '/settings/feeds' },
- ],
- }
+const Subtitle = (props: EmptyLibraryProps) => {
+ // If its the subscriptions folder we fetch the subscriptions
+ // to prevent a better message
+ const { data: subscriptions, isLoading } = useGetSubscriptions(
+ {},
+ props.folder == 'subscriptions'
+ )
+ const titleText = (folder: string | undefined) => {
+ switch (folder) {
+ case 'highlights':
+ return 'highlight text while reading to start building your highlights library.'
+ case 'trash':
+ return 'Deleted items will appear here before they are permanently deleted.'
case 'archive':
- return {
- message:
- 'When you are done reading something archive it and it will be saved in Omnivore forever.',
- actions: [
- {
- text: 'Read the docs',
- url: 'https://docs.omnivore.app/using/saving',
- },
- ],
- }
- case 'files':
- return {
- message:
- 'Drag PDFs into the library to add them to your Omnivore account.',
- actions: [],
- }
- case 'newsletter':
- return {
- message:
- 'Create an Omnivore email address and subscribe to newsletters.',
- actions: [
- {
- text: 'Create an email address for newsletters',
- url: '/settings/emails',
- },
- ],
- }
- case 'subscription':
- return {
- message:
- 'Create an Omnivore email address and subscribe to newsletters or add a feed from the Feeds page.',
- actions: [
- { text: 'Add an RSS or Atom feed', url: '/settings/feeds' },
- {
- text: 'Create an email address for newsletters',
- url: '/settings/emails',
- },
- ],
+ return 'Archived items are hidden from your library but saved forever. You can always access them here.'
+ case 'following':
+ if (isLoading || (subscriptions?.length ?? 0) > 0) {
+ return 'No subscription items found.'
}
+ return 'Get started by subscribing to newsletters or feeds from the settings menu.'
}
- return {
- message: "Add a link or read more about Omnivore's Advanced Search.",
- actions: [
- {
- text: 'Read the Docs',
- url: 'https://docs.omnivore.app/using/search.html',
- },
- ],
- }
- }, [props.type])
-
+ return 'No results found.'
+ }
return (
- <>
- {helpMessage ? (
-
- ) : (
- <>>
- )}
- >
+
+ {titleText(props.folder)}
+
)
}
export const EmptyLibrary = (props: EmptyLibraryProps) => {
- const type = useMemo(() => {
- if (props.searchTerm) {
- switch (props.searchTerm) {
- case 'in:inbox':
- return 'inbox'
- case 'in:inbox sort:read-desc is:reading':
- return 'continue'
- case 'no:subscription':
- return 'non-feed'
- // Handle both cases while we migrate
- case 'has:highlights mode:highlights':
- case 'in:all has:highlights mode:highlights':
- return 'highlight'
- case 'no:label':
- return 'unlabeled'
- case 'type:file':
- return 'files'
- case 'in:archive':
- return 'archive'
- case 'in:following use:folders':
- case 'label:RSS':
- return 'feed'
- case 'has:subscriptions':
- return 'subscription'
- case 'label:Newsletter':
- return 'newsletter'
- }
- }
- return 'library'
- }, [props])
-
return (
{
width: '100%',
height: '100%',
pb: '100px',
+ px: '25px',
+ color: '$thLibraryMenuSecondary',
minHeight: `calc(100vh - ${DEFAULT_HEADER_HEIGHT})`,
}}
>
-
-
-
+
+
+
)
}
diff --git a/packages/web/components/templates/library/LibraryContainer.tsx b/packages/web/components/templates/library/LibraryContainer.tsx
index 74fd822d8..64d1f8ce4 100644
--- a/packages/web/components/templates/library/LibraryContainer.tsx
+++ b/packages/web/components/templates/library/LibraryContainer.tsx
@@ -945,7 +945,7 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
{!showItems && props.fetchItemsError && }
{!showItems && !props.fetchItemsError && props.items.length <= 0 && (
{
props.setShowAddLinkModal(true)
}}
diff --git a/packages/web/lib/networking/subscriptions/useGetSubscriptions.tsx b/packages/web/lib/networking/subscriptions/useGetSubscriptions.tsx
new file mode 100644
index 000000000..6f8d8a694
--- /dev/null
+++ b/packages/web/lib/networking/subscriptions/useGetSubscriptions.tsx
@@ -0,0 +1,71 @@
+import { useQuery } from '@tanstack/react-query'
+import { gqlFetcher } from '../networkHelpers'
+import { gql } from 'graphql-request'
+import {
+ Subscription,
+ SubscriptionType,
+} from '../queries/useGetSubscriptionsQuery'
+
+export function useGetSubscriptions(
+ variables: {
+ type?: SubscriptionType | undefined
+ sortBy?: string | undefined
+ },
+ enabled = true
+) {
+ return useQuery({
+ queryKey: ['subscriptions'],
+ queryFn: async () => {
+ const response = (await gqlFetcher(GQL_GET_SUBSCRIPTIONS, {
+ type: variables.type,
+ sort: {
+ by: variables.sortBy ?? 'UPDATED_TIME',
+ },
+ })) as SubscriptionsData
+ if (response.subscriptions?.errorCodes?.length) {
+ throw new Error(response.subscriptions.errorCodes[0])
+ }
+ return response.subscriptions.subscriptions
+ },
+ enabled,
+ })
+}
+
+type SubscriptionsResult = {
+ errorCodes?: string[]
+ subscriptions: Subscription[]
+}
+
+type SubscriptionsData = {
+ subscriptions: SubscriptionsResult
+}
+
+const GQL_GET_SUBSCRIPTIONS = gql`
+ query GetSubscriptions($type: SubscriptionType, $sort: SortParams) {
+ subscriptions(type: $type, sort: $sort) {
+ ... on SubscriptionsSuccess {
+ subscriptions {
+ id
+ name
+ type
+ newsletterEmail
+ url
+ icon
+ description
+ status
+ unsubscribeMailTo
+ unsubscribeHttpUrl
+ createdAt
+ updatedAt
+ lastFetchedAt
+ fetchContentType
+ mostRecentItemDate
+ failedAt
+ }
+ }
+ ... on SubscriptionsError {
+ errorCodes
+ }
+ }
+ }
+`