Merge pull request #4180 from omnivore-app/feat/web-home-fallback
Add a fallback to library if Home is not created for user
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { styled } from '../tokens/stitches.config'
|
||||
import { styled, theme } from '../tokens/stitches.config'
|
||||
|
||||
export const Button = styled('button', {
|
||||
fontFamily: 'inter',
|
||||
@ -436,7 +436,12 @@ export const Button = styled('button', {
|
||||
cursor: 'pointer',
|
||||
p: '5px',
|
||||
borderRadius: '5px',
|
||||
'&:hover': { bg: '$homeActionHoverBg', opacity: '1' },
|
||||
'&:hover': {
|
||||
opacity: '1',
|
||||
bg: '$homeActionHoverBg',
|
||||
'--stroke': theme.colors.homeTextTitle.toString(),
|
||||
},
|
||||
'--stroke': theme.colors.homeActionIcons.toString(),
|
||||
},
|
||||
menuAction: {
|
||||
display: 'flex',
|
||||
|
||||
@ -6,8 +6,6 @@ import React from 'react'
|
||||
|
||||
export class AddToLibraryActionIcon extends React.Component<IconProps> {
|
||||
render() {
|
||||
const strokeColor = (this.props.color || '#D9D9D9').toString()
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="19"
|
||||
@ -19,7 +17,7 @@ export class AddToLibraryActionIcon extends React.Component<IconProps> {
|
||||
<g>
|
||||
<path
|
||||
d="M14.25 5.54167V16.625L9.5 13.4583L4.75 16.625V5.54167C4.75 4.70181 5.08363 3.89636 5.6775 3.3025C6.27136 2.70863 7.07681 2.375 7.91667 2.375H11.0833C11.9232 2.375 12.7286 2.70863 13.3225 3.3025C13.9164 3.89636 14.25 4.70181 14.25 5.54167Z"
|
||||
stroke={strokeColor}
|
||||
stroke="var(--stroke)"
|
||||
strokeWidth="1.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
|
||||
@ -6,8 +6,6 @@ import React from 'react'
|
||||
|
||||
export class ArchiveActionIcon extends React.Component<IconProps> {
|
||||
render() {
|
||||
const strokeColor = (this.props.color || '#D9D9D9').toString()
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="19"
|
||||
@ -19,21 +17,21 @@ export class ArchiveActionIcon extends React.Component<IconProps> {
|
||||
<g>
|
||||
<path
|
||||
d="M2.375 4.74984C2.375 4.32991 2.54181 3.92718 2.83875 3.63025C3.13568 3.33332 3.53841 3.1665 3.95833 3.1665H15.0417C15.4616 3.1665 15.8643 3.33332 16.1613 3.63025C16.4582 3.92718 16.625 4.32991 16.625 4.74984C16.625 5.16976 16.4582 5.57249 16.1613 5.86942C15.8643 6.16636 15.4616 6.33317 15.0417 6.33317H3.95833C3.53841 6.33317 3.13568 6.16636 2.83875 5.86942C2.54181 5.57249 2.375 5.16976 2.375 4.74984Z"
|
||||
stroke={strokeColor}
|
||||
stroke="var(--stroke)"
|
||||
strokeWidth="1.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M3.95801 6.3335V14.2502C3.95801 14.6701 4.12482 15.0728 4.42176 15.3697C4.71869 15.6667 5.12141 15.8335 5.54134 15.8335H13.458C13.8779 15.8335 14.2807 15.6667 14.5776 15.3697C14.8745 15.0728 15.0413 14.6701 15.0413 14.2502V6.3335"
|
||||
stroke={strokeColor}
|
||||
stroke="var(--stroke)"
|
||||
strokeWidth="1.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M7.91699 9.5H11.0837"
|
||||
stroke={strokeColor}
|
||||
stroke="var(--stroke)"
|
||||
strokeWidth="1.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
|
||||
@ -6,8 +6,6 @@ import React from 'react'
|
||||
|
||||
export class RemoveActionIcon extends React.Component<IconProps> {
|
||||
render() {
|
||||
const strokeColor = (this.props.color || '#D9D9D9').toString()
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="19"
|
||||
@ -19,35 +17,35 @@ export class RemoveActionIcon extends React.Component<IconProps> {
|
||||
<g>
|
||||
<path
|
||||
d="M3.16699 5.5415H15.8337"
|
||||
stroke={strokeColor}
|
||||
stroke="var(--stroke)"
|
||||
strokeWidth="1.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M7.91699 8.7085V13.4585"
|
||||
stroke={strokeColor}
|
||||
stroke="var(--stroke)"
|
||||
strokeWidth="1.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M11.083 8.7085V13.4585"
|
||||
stroke={strokeColor}
|
||||
stroke="var(--stroke)"
|
||||
strokeWidth="1.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M3.95801 5.5415L4.74967 15.0415C4.74967 15.4614 4.91649 15.8642 5.21342 16.1611C5.51035 16.458 5.91308 16.6248 6.33301 16.6248H12.6663C13.0863 16.6248 13.489 16.458 13.7859 16.1611C14.0829 15.8642 14.2497 15.4614 14.2497 15.0415L15.0413 5.5415"
|
||||
stroke={strokeColor}
|
||||
stroke="var(--stroke)"
|
||||
strokeWidth="1.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M7.125 5.54167V3.16667C7.125 2.9567 7.20841 2.75534 7.35687 2.60687C7.50534 2.45841 7.7067 2.375 7.91667 2.375H11.0833C11.2933 2.375 11.4947 2.45841 11.6431 2.60687C11.7916 2.75534 11.875 2.9567 11.875 3.16667V5.54167"
|
||||
stroke={strokeColor}
|
||||
stroke="var(--stroke)"
|
||||
strokeWidth="1.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
|
||||
@ -6,8 +6,6 @@ import React from 'react'
|
||||
|
||||
export class ShareActionIcon extends React.Component<IconProps> {
|
||||
render() {
|
||||
const strokeColor = (this.props.color || '#D9D9D9').toString()
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="19"
|
||||
@ -19,7 +17,7 @@ export class ShareActionIcon extends React.Component<IconProps> {
|
||||
<g>
|
||||
<path
|
||||
d="M10.2918 3.1665V6.33317C5.08661 7.147 3.15098 11.707 2.37515 15.8332C2.34586 15.9963 6.63748 11.1133 10.2918 11.0832V14.2498L16.6251 8.70817L10.2918 3.1665Z"
|
||||
stroke={strokeColor}
|
||||
stroke="var(--stroke)"
|
||||
strokeWidth="1.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
|
||||
@ -5,7 +5,6 @@ import { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
|
||||
import { Button } from '../elements/Button'
|
||||
import { AddToLibraryActionIcon } from '../elements/icons/home/AddToLibraryActionIcon'
|
||||
import { ArchiveActionIcon } from '../elements/icons/home/ArchiveActionIcon'
|
||||
import { CommentActionIcon } from '../elements/icons/home/CommentActionIcon'
|
||||
import { RemoveActionIcon } from '../elements/icons/home/RemoveActionIcon'
|
||||
import { ShareActionIcon } from '../elements/icons/home/ShareActionIcon'
|
||||
import Pagination from '../elements/Pagination'
|
||||
@ -29,6 +28,7 @@ import { Toaster } from 'react-hot-toast'
|
||||
import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery'
|
||||
import useLibraryItemActions from '../../lib/hooks/useLibraryItemActions'
|
||||
import { SyncLoader } from 'react-spinners'
|
||||
import { useGetLibraryItemsQuery } from '../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
|
||||
export function HomeContainer(): JSX.Element {
|
||||
const router = useRouter()
|
||||
@ -105,6 +105,9 @@ export function HomeContainer(): JSX.Element {
|
||||
/>
|
||||
)
|
||||
case 'top_picks':
|
||||
if (homeSection.items.length < 1) {
|
||||
return <FromYourLibraryHomeSection />
|
||||
}
|
||||
return (
|
||||
<TopPicksHomeSection
|
||||
key={`section-${idx}`}
|
||||
@ -113,6 +116,9 @@ export function HomeContainer(): JSX.Element {
|
||||
/>
|
||||
)
|
||||
case 'quick_links':
|
||||
if (homeSection.items.length < 1) {
|
||||
return <SpanBox key={`section-${idx}`}></SpanBox>
|
||||
}
|
||||
return (
|
||||
<QuickLinksHomeSection
|
||||
key={`section-${idx}`}
|
||||
@ -329,6 +335,118 @@ const TopPicksHomeSection = (props: HomeSectionProps): JSX.Element => {
|
||||
)
|
||||
}
|
||||
|
||||
const FromYourLibraryHomeSection = (): JSX.Element => {
|
||||
const { itemsPages } = useGetLibraryItemsQuery('all', {
|
||||
limit: 10,
|
||||
includeContent: false,
|
||||
sortDescending: true,
|
||||
})
|
||||
|
||||
const searchItems = useMemo(() => {
|
||||
return (
|
||||
itemsPages?.flatMap((ad) => {
|
||||
return ad.search.edges.map((it) => ({
|
||||
...it,
|
||||
isLoading: it.node.state === 'PROCESSING',
|
||||
}))
|
||||
}) || []
|
||||
).map((item) => {
|
||||
return {
|
||||
id: item.node.id,
|
||||
date: item.node.savedAt,
|
||||
title: item.node.title,
|
||||
url: item.node.url,
|
||||
slug: item.node.slug,
|
||||
score: 1.0,
|
||||
thumbnail: item.node.image,
|
||||
source: {
|
||||
name:
|
||||
item.node.folder == 'following'
|
||||
? item.node.subscription
|
||||
: item.node.siteName,
|
||||
icon: item.node.siteIcon,
|
||||
type: 'LIBRARY',
|
||||
},
|
||||
canArchive: true,
|
||||
canDelete: true,
|
||||
canShare: true,
|
||||
canMove: item.node.folder == 'following',
|
||||
} as HomeItem
|
||||
})
|
||||
}, [itemsPages])
|
||||
|
||||
const listReducer = (
|
||||
state: HomeItem[],
|
||||
action: {
|
||||
type: string
|
||||
itemId?: string
|
||||
items?: HomeItem[]
|
||||
}
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case 'RESET':
|
||||
return action.items ?? []
|
||||
case 'REMOVE_ITEM':
|
||||
return state.filter((item) => item.id !== action.itemId)
|
||||
default:
|
||||
throw new Error()
|
||||
}
|
||||
}
|
||||
|
||||
const [items, dispatchList] = useReducer(listReducer, [])
|
||||
|
||||
useEffect(() => {
|
||||
dispatchList({
|
||||
type: 'RESET',
|
||||
items: searchItems,
|
||||
})
|
||||
}, [searchItems])
|
||||
|
||||
console.log('items: ', items)
|
||||
|
||||
return (
|
||||
<VStack
|
||||
distribution="start"
|
||||
css={{
|
||||
width: '100%',
|
||||
gap: '20px',
|
||||
'@mdDown': {
|
||||
gap: '10px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{items.length > 0 && (
|
||||
<SpanBox
|
||||
css={{
|
||||
fontFamily: '$inter',
|
||||
fontSize: '16px',
|
||||
fontWeight: '600',
|
||||
color: '$homeTextTitle',
|
||||
'@mdDown': {
|
||||
px: '20px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
From your library
|
||||
</SpanBox>
|
||||
)}
|
||||
|
||||
<Pagination
|
||||
items={items}
|
||||
itemsPerPage={10}
|
||||
loadMoreButtonText="Load more Top Picks"
|
||||
render={(homeItem) => (
|
||||
<TopPicksItemView
|
||||
key={homeItem.id}
|
||||
homeItem={homeItem}
|
||||
dispatchList={dispatchList}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
const QuickLinksHomeSection = (props: HomeSectionProps): JSX.Element => {
|
||||
return (
|
||||
<VStack
|
||||
@ -701,9 +819,7 @@ const TopPicksItemView = (
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AddToLibraryActionIcon
|
||||
color={theme.colors.homeActionIcons.toString()}
|
||||
/>
|
||||
<AddToLibraryActionIcon />
|
||||
</Button>
|
||||
)}
|
||||
{props.homeItem.canArchive && (
|
||||
@ -726,9 +842,7 @@ const TopPicksItemView = (
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ArchiveActionIcon
|
||||
color={theme.colors.homeActionIcons.toString()}
|
||||
/>
|
||||
<ArchiveActionIcon />
|
||||
</Button>
|
||||
)}
|
||||
{props.homeItem.canDelete && (
|
||||
@ -757,7 +871,7 @@ const TopPicksItemView = (
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RemoveActionIcon color={theme.colors.homeActionIcons.toString()} />
|
||||
<RemoveActionIcon />
|
||||
</Button>
|
||||
)}
|
||||
{props.homeItem.canShare && (
|
||||
@ -767,11 +881,10 @@ const TopPicksItemView = (
|
||||
onClick={async (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
await shareItem(props.homeItem.title, props.homeItem.url)
|
||||
}}
|
||||
>
|
||||
<ShareActionIcon color={theme.colors.homeActionIcons.toString()} />
|
||||
<ShareActionIcon />
|
||||
</Button>
|
||||
)}
|
||||
</HStack>
|
||||
@ -847,47 +960,55 @@ type SourceInfoProps = {
|
||||
subtle?: boolean
|
||||
}
|
||||
|
||||
const SourceInfo = (props: HomeItemViewProps & SourceInfoProps) => (
|
||||
<HoverCard.Root>
|
||||
<HoverCard.Trigger asChild>
|
||||
<HStack
|
||||
distribution="start"
|
||||
alignment="center"
|
||||
css={{
|
||||
gap: '8px',
|
||||
height: '16px',
|
||||
cursor: 'pointer',
|
||||
flex: '1',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
>
|
||||
{props.homeItem.source.icon && (
|
||||
<SiteIconSmall src={props.homeItem.source.icon} />
|
||||
)}
|
||||
const SourceInfo = (props: HomeItemViewProps & SourceInfoProps) => {
|
||||
const hasHover = useMemo(() => {
|
||||
const type = props.homeItem.source.type
|
||||
return type == 'RSS' || type == 'NEWSLETTER'
|
||||
}, [props])
|
||||
return (
|
||||
<HoverCard.Root>
|
||||
<HoverCard.Trigger asChild>
|
||||
<HStack
|
||||
distribution="start"
|
||||
alignment="center"
|
||||
css={{
|
||||
lineHeight: '1',
|
||||
fontFamily: '$inter',
|
||||
fontWeight: '500',
|
||||
fontSize: props.subtle ? '12px' : '13px',
|
||||
color: props.subtle ? '$homeTextSubtle' : '$homeTextSource',
|
||||
textDecoration: 'underline',
|
||||
gap: '8px',
|
||||
height: '16px',
|
||||
cursor: 'pointer',
|
||||
flex: '1',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
>
|
||||
{props.homeItem.source.name}
|
||||
{props.homeItem.source.icon && (
|
||||
<SiteIconSmall src={props.homeItem.source.icon} />
|
||||
)}
|
||||
<HStack
|
||||
css={{
|
||||
lineHeight: '1',
|
||||
fontFamily: '$inter',
|
||||
fontWeight: '500',
|
||||
fontSize: props.subtle ? '12px' : '13px',
|
||||
color: props.subtle ? '$homeTextSubtle' : '$homeTextSource',
|
||||
textDecoration: hasHover ? 'underline' : 'unset',
|
||||
}}
|
||||
>
|
||||
{props.homeItem.source.name}
|
||||
</HStack>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</HoverCard.Trigger>
|
||||
<HoverCard.Portal>
|
||||
<HoverCard.Content sideOffset={5} style={{ zIndex: 5 }}>
|
||||
<SubscriptionSourceHoverContent source={props.homeItem.source} />
|
||||
<HoverCard.Arrow fill={theme.colors.thBackground2.toString()} />
|
||||
</HoverCard.Content>
|
||||
</HoverCard.Portal>
|
||||
</HoverCard.Root>
|
||||
)
|
||||
</HoverCard.Trigger>
|
||||
{hasHover && (
|
||||
<HoverCard.Portal>
|
||||
<HoverCard.Content sideOffset={5} style={{ zIndex: 5 }}>
|
||||
<SubscriptionSourceHoverContent source={props.homeItem.source} />
|
||||
<HoverCard.Arrow fill={theme.colors.thBackground2.toString()} />
|
||||
</HoverCard.Content>
|
||||
</HoverCard.Portal>
|
||||
)}
|
||||
</HoverCard.Root>
|
||||
)
|
||||
}
|
||||
|
||||
type SourceHoverContentProps = {
|
||||
source: HomeItemSource
|
||||
|
||||
Reference in New Issue
Block a user