Rename web container files

This commit is contained in:
Jackson Harper 2024-06-21 12:36:40 +08:00
parent bc0eeba158
commit 80bded24db
3 changed files with 2 additions and 1208 deletions

View file

@ -1,319 +0,0 @@
import { NavigationLayout } from '../templates/NavigationLayout'
import { Box, HStack, VStack } from '../elements/LayoutPrimitives'
import { useFetchMore } from '../../lib/hooks/useFetchMoreScroll'
import { useCallback, useMemo, useState } from 'react'
import { useGetHighlights } from '../../lib/networking/queries/useGetHighlights'
import { Highlight } from '../../lib/networking/fragments/highlightFragment'
import { NextRouter, useRouter } from 'next/router'
import {
UserBasicData,
useGetViewerQuery,
} from '../../lib/networking/queries/useGetViewerQuery'
import { SetHighlightLabelsModalPresenter } from '../templates/article/SetLabelsModalPresenter'
import { TrashIcon } from '../elements/icons/TrashIcon'
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
import { ConfirmationModal } from '../patterns/ConfirmationModal'
import { deleteHighlightMutation } from '../../lib/networking/mutations/deleteHighlightMutation'
import { LabelChip } from '../elements/LabelChip'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { timeAgo } from '../patterns/LibraryCards/LibraryCardStyles'
import { HighlightHoverActions } from '../patterns/HighlightHoverActions'
import {
autoUpdate,
offset,
size,
useFloating,
useHover,
useInteractions,
} from '@floating-ui/react'
import { highlightColor } from '../../lib/themeUpdater'
import { HighlightViewNote } from '../patterns/HighlightNotes'
import { theme } from '../tokens/stitches.config'
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,
})
const hasMore = useMemo(() => {
if (!data) {
return false
}
return data[data.length - 1].highlights.pageInfo.hasNextPage
}, [data])
const handleFetchMore = useCallback(() => {
if (isLoading || !hasMore) {
return
}
setSize(size + 1)
}, [isLoading, hasMore, setSize, size])
useFetchMore(handleFetchMore)
const highlights = useMemo(() => {
if (!data) {
return []
}
return data.flatMap((res) => res.highlights.edges.map((edge) => edge.node))
}, [data])
return (
<VStack
css={{
maxWidth: '70%',
padding: '20px',
margin: '30px 50px 0 0',
}}
>
{highlights.map((highlight) => {
return (
viewer.viewerData?.me && (
<HighlightCard
key={highlight.id}
highlight={highlight}
viewer={viewer.viewerData.me}
router={router}
mutate={mutate}
/>
)
)
})}
</VStack>
)
}
type HighlightCardProps = {
highlight: Highlight
viewer: UserBasicData
router: NextRouter
mutate: () => void
}
type HighlightAnnotationProps = {
highlight: Highlight
}
function HighlightAnnotation({
highlight,
}: HighlightAnnotationProps): JSX.Element {
const [noteMode, setNoteMode] = useState<'edit' | 'preview'>('preview')
const [annotation, setAnnotation] = useState(highlight.annotation)
return (
<HighlightViewNote
targetId={highlight.id}
text={annotation}
placeHolder="Add notes to this highlight..."
highlight={highlight}
mode={noteMode}
setEditMode={setNoteMode}
updateHighlight={(highlight) => {
setAnnotation(highlight.annotation)
}}
/>
)
}
function HighlightCard(props: HighlightCardProps): JSX.Element {
const [isOpen, setIsOpen] = useState(false)
const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] =
useState<undefined | string>(undefined)
const [labelsTarget, setLabelsTarget] = useState<Highlight | undefined>(
undefined
)
const viewInReader = useCallback(
(highlightId: string) => {
const router = props.router
const viewer = props.viewer
const item = props.highlight.libraryItem
if (!router || !router.isReady || !viewer || !item) {
showErrorToast('Error navigating to highlight')
return
}
router.push(
{
pathname: '/[username]/[slug]',
query: {
username: viewer.profile.username,
slug: item.slug,
},
hash: highlightId,
},
`${viewer.profile.username}/${item.slug}#${highlightId}`,
{
scroll: false,
}
)
},
[props.highlight.libraryItem, props.viewer, props.router]
)
const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
middleware: [
offset({
mainAxis: -25,
}),
size(),
],
placement: 'top-end',
whileElementsMounted: autoUpdate,
})
const hover = useHover(context)
const { getReferenceProps, getFloatingProps } = useInteractions([hover])
return (
<VStack
ref={refs.setReference}
{...getReferenceProps()}
css={{
width: '100%',
fontFamily: '$inter',
padding: '20px',
marginBottom: '20px',
bg: '$thBackground2',
borderRadius: '8px',
cursor: 'pointer',
}}
>
<Box
ref={refs.setFloating}
style={floatingStyles}
{...getFloatingProps()}
>
<HighlightHoverActions
viewer={props.viewer}
highlight={props.highlight}
isHovered={isOpen ?? false}
viewInReader={viewInReader}
setLabelsTarget={setLabelsTarget}
setShowConfirmDeleteHighlightId={setShowConfirmDeleteHighlightId}
/>
</Box>
<Box
css={{
width: '30px',
height: '5px',
backgroundColor: highlightColor(props.highlight.color),
borderRadius: '2px',
}}
/>
<Box
css={{
color: '$thText',
fontSize: '11px',
marginTop: '10px',
fontWeight: 300,
}}
>
{timeAgo(props.highlight.updatedAt)}
</Box>
{props.highlight.quote && (
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{props.highlight.quote}
</ReactMarkdown>
)}
<HighlightAnnotation highlight={props.highlight} />
{props.highlight.labels && (
<HStack
css={{
marginBottom: '10px',
}}
>
{props.highlight.labels.map((label) => {
return (
<LabelChip key={label.id} color={label.color} text={label.name} />
)
})}
</HStack>
)}
<Box
css={{
color: '$thText',
fontSize: '12px',
lineHeight: '20px',
fontWeight: 300,
marginBottom: '10px',
}}
>
{props.highlight.libraryItem?.title}
</Box>
<Box
css={{
color: '$grayText',
fontSize: '12px',
lineHeight: '20px',
fontWeight: 300,
}}
>
{props.highlight.libraryItem?.author}
</Box>
{showConfirmDeleteHighlightId && (
<ConfirmationModal
message={'Are you sure you want to delete this highlight?'}
onAccept={() => {
;(async () => {
const highlightId = showConfirmDeleteHighlightId
const success = await deleteHighlightMutation(
props.highlight.libraryItem?.id || '',
showConfirmDeleteHighlightId
)
props.mutate()
if (success) {
showSuccessToast('Highlight deleted.', {
position: 'bottom-right',
})
const event = new CustomEvent('deleteHighlightbyId', {
detail: highlightId,
})
document.dispatchEvent(event)
} else {
showErrorToast('Error deleting highlight', {
position: 'bottom-right',
})
}
})()
setShowConfirmDeleteHighlightId(undefined)
}}
onOpenChange={() => setShowConfirmDeleteHighlightId(undefined)}
icon={
<TrashIcon
size={40}
color={theme.colors.grayTextContrast.toString()}
/>
}
/>
)}
{labelsTarget && (
<SetHighlightLabelsModalPresenter
highlight={labelsTarget}
highlightId={labelsTarget.id}
onUpdate={(highlight) => {
// Don't actually need to do something here
console.log('update highlight: ', highlight)
}}
onOpenChange={() => {
props.mutate()
setLabelsTarget(undefined)
}}
/>
)}
</VStack>
)
}

View file

@ -1,887 +0,0 @@
import * as HoverCard from '@radix-ui/react-hover-card'
import { styled } from '@stitches/react'
import { useRouter } from 'next/router'
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'
import { timeAgo } from '../patterns/LibraryCards/LibraryCardStyles'
import { theme } from '../tokens/stitches.config'
import { useApplyLocalTheme } from '../../lib/hooks/useApplyLocalTheme'
import { useGetHiddenHomeSection } from '../../lib/networking/queries/useGetHiddenHomeSection'
import {
HomeItem,
HomeItemSource,
HomeItemSourceType,
HomeSection,
useGetHomeItems,
} from '../../lib/networking/queries/useGetHome'
import {
SubscriptionType,
useGetSubscriptionsQuery,
} from '../../lib/networking/queries/useGetSubscriptionsQuery'
import { Box, HStack, SpanBox, VStack } from '../elements/LayoutPrimitives'
import { Toaster } from 'react-hot-toast'
import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery'
export function HomeContainer(): JSX.Element {
const router = useRouter()
const homeData = useGetHomeItems()
const { viewerData } = useGetViewerQuery()
useApplyLocalTheme()
const viewerUsername = useMemo(() => {
return viewerData?.me?.profile.username
}, [viewerData])
useEffect(() => {
window.localStorage.setItem('nav-return', router.asPath)
}, [router.asPath])
return (
<VStack
distribution="start"
alignment="center"
css={{
width: '100%',
bg: '$readerBg',
pt: '45px',
minHeight: '100vh',
minWidth: '320px',
'@mdDown': {
pt: '0px',
mt: '80px',
},
}}
>
<Toaster />
<VStack
distribution="start"
css={{
width: '680px',
gap: '50px',
minHeight: '100vh',
'@mdDown': {
gap: '40px',
width: '100%',
},
}}
>
{homeData.sections?.map((homeSection, idx) => {
if (homeSection.items.length < 1) {
console.log('empty home section: ', homeSection)
return <SpanBox key={`section-${idx}`}></SpanBox>
}
switch (homeSection.layout) {
case 'just_added':
return (
<JustAddedHomeSection
key={`section-${idx}`}
homeSection={homeSection}
viewerUsername={viewerUsername}
/>
)
case 'top_picks':
return (
<TopPicksHomeSection
key={`section-${idx}`}
homeSection={homeSection}
viewerUsername={viewerUsername}
/>
)
case 'quick_links':
return (
<QuickLinksHomeSection
key={`section-${idx}`}
homeSection={homeSection}
viewerUsername={viewerUsername}
/>
)
case 'hidden':
return (
<HiddenHomeSection
key={`section-${idx}`}
homeSection={homeSection}
viewerUsername={viewerUsername}
/>
)
default:
console.log('unknown home section: ', homeSection)
return <SpanBox key={`section-${idx}`}></SpanBox>
}
})}
</VStack>
</VStack>
)
}
type HomeSectionProps = {
homeSection: HomeSection
viewerUsername: string | undefined
}
const JustAddedHomeSection = (props: HomeSectionProps): JSX.Element => {
const router = useRouter()
return (
<VStack
distribution="start"
css={{
width: '100%',
height: '100%',
gap: '20px',
}}
>
<HStack
css={{
width: '100%',
lineHeight: '1',
'@mdDown': {
px: '20px',
},
}}
distribution="start"
alignment="start"
>
<SpanBox
css={{
fontFamily: '$inter',
fontSize: '16px',
fontWeight: '600',
color: '$homeTextTitle',
}}
>
{props.homeSection.title}
</SpanBox>
<SpanBox
css={{
ml: 'auto',
fontFamily: '$inter',
fontSize: '13px',
fontWeight: '400',
color: '$homeTextTitle',
}}
>
<Button
style="link"
onClick={(event) => {
router.push('/l/library')
event.preventDefault()
}}
css={{
'&:hover': {
textDecoration: 'underline',
},
}}
>
View All
</Button>
</SpanBox>
</HStack>
<HStack
css={{
width: '100%',
height: '100%',
lineHeight: '1',
overflowX: 'scroll',
gap: '25px',
scrollbarWidth: 'none',
'::-webkit-scrollbar': {
display: 'none',
},
'@mdDown': {
px: '20px',
},
}}
distribution="start"
alignment="start"
>
{props.homeSection.items.map((homeItem) => {
return <JustAddedItemView key={homeItem.id} homeItem={homeItem} />
})}
</HStack>
</VStack>
)
}
const TopPicksHomeSection = (props: HomeSectionProps): JSX.Element => {
const listReducer = (
state: HomeItem[],
action: {
type: string
itemId?: string
items?: HomeItem[]
}
) => {
console.log('handling action: ', action)
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, [])
function handleDelete(item: HomeItem) {
dispatchList({
type: 'REMOVE_ITEM',
itemId: item.id,
})
}
useEffect(() => {
dispatchList({
type: 'RESET',
items: props.homeSection.items,
})
}, [props])
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',
},
}}
>
{props.homeSection.title}
</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
distribution="start"
css={{
width: '100%',
gap: '10px',
bg: '$homeCardHover',
py: '20px',
px: '20px',
borderRadius: '5px',
}}
>
<SpanBox
css={{
fontFamily: '$inter',
fontSize: '12px',
fontWeight: '500',
textTransform: 'uppercase',
color: '$ctaBlue',
bg: '#007AFF20',
px: '10px',
py: '5px',
borderRadius: '5px',
}}
>
{props.homeSection.title}
</SpanBox>
<Pagination
items={props.homeSection.items}
itemsPerPage={8}
render={(homeItem) => (
<QuickLinkHomeItemView key={homeItem.id} homeItem={homeItem} />
)}
/>
</VStack>
)
}
const HiddenHomeSection = (props: HomeSectionProps): JSX.Element => {
const [isHidden, setIsHidden] = useState(true)
return (
<VStack
distribution="start"
css={{
width: '100%',
gap: '20px',
marginBottom: '40px',
}}
>
<HStack
distribution="start"
alignment="center"
css={{
gap: '10px',
cursor: 'pointer',
}}
onClick={() => setIsHidden(!isHidden)}
>
<SpanBox
css={{
fontFamily: '$inter',
fontSize: '16px',
fontWeight: '600',
color: '$homeTextTitle',
}}
>
{props.homeSection.title}
</SpanBox>
<SpanBox
css={{
fontFamily: '$inter',
fontSize: '13px',
color: '$readerFont',
}}
>
{isHidden ? 'Show' : 'Hide'}
</SpanBox>
</HStack>
{isHidden ? <></> : <HiddenHomeSectionView />}
</VStack>
)
}
const HiddenHomeSectionView = (): JSX.Element => {
const hiddenSectionData = useGetHiddenHomeSection()
if (hiddenSectionData.error) {
return <SpanBox>Error loading hidden section</SpanBox>
}
if (hiddenSectionData.isValidating) {
return <SpanBox>Loading...</SpanBox>
}
if (!hiddenSectionData.section) {
return <SpanBox>No hidden section data</SpanBox>
}
return (
<VStack
distribution="start"
css={{
width: '100%',
}}
>
{hiddenSectionData.section.items.map((homeItem) => {
return <QuickLinkHomeItemView key={homeItem.id} homeItem={homeItem} />
})}
</VStack>
)
}
const CoverImage = styled('img', {
objectFit: 'cover',
})
type HomeItemViewProps = {
homeItem: HomeItem
viewerUsername?: string | undefined
}
const TimeAgo = (props: HomeItemViewProps): JSX.Element => {
return (
<HStack
distribution="start"
alignment="center"
css={{
fontSize: '12px',
fontWeight: 'medium',
fontFamily: '$inter',
color: '$homeTextSubtle',
flexShrink: '0',
}}
>
{timeAgo(props.homeItem.date)}
</HStack>
)
}
const Title = (props: HomeItemViewProps): JSX.Element => {
return (
<HStack
className="title-text"
distribution="start"
alignment="center"
css={{
mb: '6px',
fontSize: '18px',
lineHeight: '24px',
fontWeight: '600',
fontFamily: '$inter',
color: '$homeTextTitle',
overflow: 'hidden',
textOverflow: 'ellipsis',
wordBreak: 'break-word',
display: '-webkit-box',
'-webkit-line-clamp': '3',
'-webkit-box-orient': 'vertical',
'&:title-text': {
transition: 'text-decoration 0.3s ease',
},
'@mdDown': {
fontSize: '16px',
lineHeight: '20px',
},
}}
>
{props.homeItem.title}
</HStack>
)
}
const TitleSmall = (props: HomeItemViewProps): JSX.Element => {
return (
<HStack
className="title-text"
distribution="start"
alignment="center"
css={{
fontSize: '14px',
lineHeight: '21px',
minHeight: '42px', // always have two lines of space
fontWeight: '500',
fontFamily: '$inter',
color: '$homeTextTitle',
overflow: 'hidden',
textOverflow: 'ellipsis',
wordBreak: 'break-word',
display: '-webkit-box',
'-webkit-line-clamp': '3',
'-webkit-box-orient': 'vertical',
}}
>
{props.homeItem.title}
</HStack>
)
}
type PreviewContentProps = {
previewContent?: string
maxLines?: string
}
const PreviewContent = (props: PreviewContentProps): JSX.Element => {
return (
<SpanBox
css={{
fontFamily: '$inter',
fontSize: '14px',
lineHeight: '21px',
overflow: 'hidden',
textOverflow: 'ellipsis',
wordBreak: 'break-word',
display: '-webkit-box',
'-webkit-line-clamp': props.maxLines ?? '3',
'-webkit-box-orient': 'vertical',
'@mdDown': {
'-webkit-line-clamp': '3',
},
}}
>
{props.previewContent ?? ''}
</SpanBox>
)
}
const JustAddedItemView = (props: HomeItemViewProps): JSX.Element => {
const router = useRouter()
return (
<VStack
css={{
minWidth: '377px',
gap: '5px',
padding: '12px',
cursor: 'pointer',
bg: '$homeCardHover',
borderRadius: '5px',
'&:hover': {
bg: '$homeCardHover',
},
'&:hover .title-text': {
textDecoration: 'underline',
},
'@mdDown': {
minWidth: '282px',
},
}}
onClick={(event) => {
const path = `/${props.viewerUsername ?? 'me'}/${props.homeItem.slug}`
if (event.metaKey || event.ctrlKey) {
window.open(path, '_blank')
} else {
router.push(path)
}
}}
>
<HStack
distribution="start"
alignment="center"
css={{ width: '100%', gap: '5px', lineHeight: '1' }}
>
<SourceInfo homeItem={props.homeItem} subtle={true} />
<SpanBox css={{ ml: 'auto', flexShrink: '0' }}>
<TimeAgo homeItem={props.homeItem} />
</SpanBox>
</HStack>
<TitleSmall homeItem={props.homeItem} />
</VStack>
)
}
type TopPicksItemViewProps = {
dispatchList: (args: { type: string; itemId?: string }) => void
}
const TopPicksItemView = (
props: HomeItemViewProps & TopPicksItemViewProps
): JSX.Element => {
const router = useRouter()
return (
<VStack
css={{
width: '100%',
pt: '15px',
cursor: 'pointer',
borderRadius: '5px',
'@mdDown': {
borderRadius: '0px',
},
'&:hover': {
bg: '$homeCardHover',
},
'&:hover .title-text': {
textDecoration: 'underline',
},
}}
onClick={(event) => {
const path = `/${props.viewerUsername ?? 'me'}/${props.homeItem.slug}`
if (event.metaKey || event.ctrlKey) {
window.open(path, '_blank')
} else {
router.push(path)
}
}}
alignment="start"
>
<Box css={{ width: '100%', gap: '10px', px: '20px' }}>
<HStack
distribution="start"
alignment="center"
css={{ gap: '5px', lineHeight: '1', mb: '10px' }}
>
<SourceInfo homeItem={props.homeItem} />
<SpanBox css={{ '@mdDown': { ml: 'auto' } }}>
<TimeAgo homeItem={props.homeItem} />
</SpanBox>
</HStack>
{props.homeItem.thumbnail && (
<CoverImage
css={{
width: '120px',
height: '70px',
borderRadius: '4px',
marginLeft: '10px',
float: 'right',
}}
src={props.homeItem.thumbnail}
></CoverImage>
)}
<Title homeItem={props.homeItem} />
<PreviewContent
previewContent={props.homeItem.previewContent}
maxLines="6"
/>
</Box>
<SpanBox css={{ px: '20px' }}></SpanBox>
<HStack css={{ gap: '10px', my: '15px', px: '20px' }}>
{props.homeItem.canSave && (
<Button
style="homeAction"
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
props.dispatchList({
type: 'REMOVE_ITEM',
itemId: props.homeItem.id,
})
}}
>
<AddToLibraryActionIcon
color={theme.colors.homeActionIcons.toString()}
/>
</Button>
)}
{props.homeItem.canArchive && (
<Button
style="homeAction"
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
props.dispatchList({
type: 'REMOVE_ITEM',
itemId: props.homeItem.id,
})
}}
>
<ArchiveActionIcon
color={theme.colors.homeActionIcons.toString()}
/>
</Button>
)}
{props.homeItem.canDelete && (
<Button
style="homeAction"
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
props.dispatchList({
type: 'REMOVE_ITEM',
itemId: props.homeItem.id,
})
}}
>
<RemoveActionIcon color={theme.colors.homeActionIcons.toString()} />
</Button>
)}
{props.homeItem.canShare && (
<Button
style="homeAction"
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
}}
>
<ShareActionIcon color={theme.colors.homeActionIcons.toString()} />
</Button>
)}
</HStack>
<Box
css={{ mt: '15px', width: '100%', height: '1px', bg: '$homeDivider' }}
/>
</VStack>
)
}
const QuickLinkHomeItemView = (props: HomeItemViewProps): JSX.Element => {
const router = useRouter()
return (
<VStack
css={{
mt: '10px',
width: '100%',
px: '10px',
py: '10px',
gap: '5px',
borderRadius: '5px',
'&:hover': {
bg: '#007AFF10',
cursor: 'pointer',
},
'&:hover .title-text': {
textDecoration: 'underline',
},
}}
onClick={(event) => {
const path = `/${props.viewerUsername ?? 'me'}/${props.homeItem.slug}`
if (event.metaKey || event.ctrlKey) {
window.open(path, '_blank')
} else {
router.push(path)
}
}}
>
<HStack
distribution="start"
alignment="center"
css={{ width: '100%', gap: '5px', lineHeight: '1' }}
>
<SourceInfo homeItem={props.homeItem} subtle={true} />
<SpanBox css={{ ml: 'auto', flexShrink: '0' }}>
<TimeAgo homeItem={props.homeItem} />
</SpanBox>
</HStack>
<Title homeItem={props.homeItem} />
<PreviewContent
previewContent={props.homeItem.previewContent}
maxLines="2"
/>
</VStack>
)
}
const SiteIconSmall = styled('img', {
width: '16px',
height: '16px',
borderRadius: '100px',
})
const SiteIconLarge = styled('img', {
width: '25px',
height: '25px',
borderRadius: '100px',
})
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} />
)}
<HStack
css={{
lineHeight: '1',
fontFamily: '$inter',
fontWeight: '500',
fontSize: props.subtle ? '12px' : '13px',
color: props.subtle ? '$homeTextSubtle' : '$homeTextSource',
textDecoration: 'underline',
}}
>
{props.homeItem.source.name}
</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>
)
type SourceHoverContentProps = {
source: HomeItemSource
}
const SubscriptionSourceHoverContent = (
props: SourceHoverContentProps
): JSX.Element => {
const mapSourceType = (
sourceType: HomeItemSourceType
): SubscriptionType | undefined => {
switch (sourceType) {
case 'RSS':
case 'NEWSLETTER':
return sourceType as SubscriptionType
default:
return undefined
}
}
const { subscriptions, isValidating } = useGetSubscriptionsQuery(
mapSourceType(props.source.type)
)
const subscription = useMemo(() => {
if (props.source.id && subscriptions) {
return subscriptions.find((sub) => sub.id == props.source.id)
}
return undefined
}, [subscriptions])
return (
<VStack
alignment="start"
distribution="start"
css={{
width: '380px',
height: '200px',
bg: '$thBackground2',
borderRadius: '10px',
padding: '15px',
gap: '10px',
boxShadow: theme.shadows.cardBoxShadow.toString(),
}}
>
<HStack
distribution="start"
alignment="center"
css={{ width: '100%', gap: '10px', height: '35px' }}
>
{props.source.icon && <SiteIconLarge src={props.source.icon} />}
<SpanBox
css={{
fontFamily: '$inter',
fontWeight: '500',
fontSize: '14px',
}}
>
{props.source.name}
</SpanBox>
<SpanBox css={{ ml: 'auto', minWidth: '100px' }}>
{subscription && subscription.status == 'ACTIVE' && (
<Button style="ctaSubtle" css={{ fontSize: '12px' }}>
Unsubscribe
</Button>
)}
</SpanBox>
</HStack>
<SpanBox
css={{
fontFamily: '$inter',
fontSize: '13px',
color: '$homeTextBody',
}}
>
{subscription ? <>{subscription.description}</> : <></>}
</SpanBox>
</VStack>
)
}

View file

@ -4,10 +4,10 @@ import {
NavigationLayout,
NavigationSection,
} from '../../components/templates/NavigationLayout'
import { HomeContainer } from '../../components/nav-containers/home'
import { HomeContainer } from '../../components/nav-containers/HomeContainer'
import { LibraryContainer } from '../../components/templates/library/LibraryContainer'
import { useMemo } from 'react'
import { HighlightsContainer } from '../../components/nav-containers/highlights'
import { HighlightsContainer } from '../../components/nav-containers/HighlightsContainer'
export default function Home(): JSX.Element {
const router = useRouter()