This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

feat: add to semble button

+91 -37
+36 -19
src/webapp/features/cards/components/addCardToModal/AddCardToModal.tsx
··· 1 - import type { UrlCard } from '@/api-client'; 2 1 import { DEFAULT_OVERLAY_PROPS } from '@/styles/overlays'; 3 2 import { Modal, Stack, Text } from '@mantine/core'; 4 3 import { Suspense } from 'react'; 5 4 import CollectionSelectorSkeleton from '@/features/collections/components/collectionSelector/Skeleton.CollectionSelector'; 6 - import AddCardToModalContent from './AddCardToModalContent'; // new file or inline 5 + import AddCardToModalContent from './AddCardToModalContent'; 7 6 8 7 interface Props { 9 8 isOpen: boolean; 10 9 onClose: () => void; 11 - cardContent: UrlCard['cardContent']; 12 - urlLibraryCount: number; 13 - cardId: string; 10 + url: string; 11 + cardId?: string; 14 12 note?: string; 15 - isInYourLibrary: boolean; 13 + urlLibraryCount?: number; 14 + isInYourLibrary?: boolean; 16 15 } 17 16 18 17 export default function AddCardToModal(props: Props) { 18 + const { 19 + isOpen, 20 + onClose, 21 + url, 22 + cardId, 23 + note, 24 + urlLibraryCount, 25 + isInYourLibrary, 26 + } = props; 27 + 28 + const count = urlLibraryCount ?? 0; 29 + 30 + const subtitle = (() => { 31 + if (count === 0) return 'Not saved by anyone yet'; 32 + 33 + if (isInYourLibrary) { 34 + if (count === 1) return 'Saved by you'; 35 + return `Saved by you and ${count - 1} other${count - 1 > 1 ? 's' : ''}`; 36 + } else { 37 + if (count === 1) return 'Saved by 1 person'; 38 + return `Saved by ${count} people`; 39 + } 40 + })(); 41 + 19 42 return ( 20 43 <Modal 21 - opened={props.isOpen} 22 - onClose={props.onClose} 44 + opened={isOpen} 45 + onClose={onClose} 23 46 title={ 24 47 <Stack gap={0}> 25 48 <Text fw={600}>Add or update card</Text> 26 49 <Text c="gray" fw={500}> 27 - {props.isInYourLibrary 28 - ? props.urlLibraryCount === 1 29 - ? 'Saved by you' 30 - : `Saved by you and ${props.urlLibraryCount - 1} other${props.urlLibraryCount - 1 > 1 ? 's' : ''}` 31 - : props.urlLibraryCount === 1 32 - ? 'Saved by 1 person' 33 - : `Saved by ${props.urlLibraryCount} people`} 50 + {subtitle} 34 51 </Text> 35 52 </Stack> 36 53 } ··· 40 57 > 41 58 <Suspense fallback={<CollectionSelectorSkeleton />}> 42 59 <AddCardToModalContent 43 - onClose={props.onClose} 44 - cardId={props.cardId} 45 - cardContent={props.cardContent} 46 - note={props.note} 60 + onClose={onClose} 61 + url={url} 62 + cardId={cardId} 63 + note={note} 47 64 /> 48 65 </Suspense> 49 66 </Modal>
+12 -6
src/webapp/features/cards/components/addCardToModal/AddCardToModalContent.tsx
··· 11 11 import useMyCollections from '@/features/collections/lib/queries/useMyCollections'; 12 12 import useUpdateCardAssociations from '@/features/cards/lib/mutations/useUpdateCardAssociations'; 13 13 import useAddCard from '@/features/cards/lib/mutations/useAddCard'; 14 + import useUrlMetadata from '../../lib/queries/useUrlMetadata'; 14 15 15 16 interface SelectableCollectionItem { 16 17 id: string; ··· 20 21 21 22 interface Props { 22 23 onClose: () => void; 23 - cardContent: UrlCard['cardContent']; 24 - cardId: string; 24 + url: string; 25 + cardId?: string; 25 26 note?: string; 26 27 } 27 28 28 29 export default function AddCardToModalContent(props: Props) { 29 - const cardStatus = useGetCardFromMyLibrary({ url: props.cardContent.url }); 30 - const isMyCard = props.cardId === cardStatus.data.card?.id; 30 + const { 31 + data: { metadata }, 32 + } = useUrlMetadata({ url: props.url }); 33 + const cardStatus = useGetCardFromMyLibrary({ url: props.url }); 34 + const isMyCard = props?.cardId === cardStatus.data.card?.id; 31 35 const [note, setNote] = useState(isMyCard ? props.note : ''); 32 36 const { data, error } = useMyCollections(); 33 37 ··· 77 81 if (!cardStatus.data.card) { 78 82 addCard.mutate( 79 83 { 80 - url: props.cardContent.url, 84 + url: props.url, 81 85 note: trimmedNote, 82 86 collectionIds: selectedCollections.map((c) => c.id), 83 87 }, ··· 122 126 return ( 123 127 <Stack justify="space-between"> 124 128 <CardToBeAddedPreview 125 - cardContent={props.cardContent} 129 + url={props.url} 130 + thumbnailUrl={metadata.imageUrl} 131 + title={metadata.title} 126 132 note={isMyCard ? note : cardStatus.data.card?.note?.text} 127 133 onUpdateNote={setNote} 128 134 />
+11 -9
src/webapp/features/cards/components/cardToBeAddedPreview/CardToBeAddedPreview.tsx
··· 16 16 import { getDomain } from '@/lib/utils/link'; 17 17 18 18 interface Props { 19 - cardContent: UrlCard['cardContent']; 19 + url: string; 20 + thumbnailUrl?: string; 21 + title?: string; 20 22 note?: string; 21 23 onUpdateNote: Dispatch<SetStateAction<string | undefined>>; 22 24 } ··· 24 26 export default function CardToBeAddedPreview(props: Props) { 25 27 const [noteMode, setNoteMode] = useState(false); 26 28 const [note, setNote] = useState(props.note); 27 - const domain = getDomain(props.cardContent.url); 29 + const domain = getDomain(props.url); 28 30 29 31 if (noteMode) { 30 32 return ( ··· 77 79 <Card withBorder component="article" p={'xs'} radius={'lg'}> 78 80 <Stack> 79 81 <Group gap={'sm'} justify="space-between"> 80 - {props.cardContent.thumbnailUrl && ( 82 + {props.thumbnailUrl && ( 81 83 <AspectRatio ratio={1 / 1} flex={0.1}> 82 84 <Image 83 - src={props.cardContent.thumbnailUrl} 84 - alt={`${props.cardContent.url} social preview image`} 85 + src={props.thumbnailUrl} 86 + alt={`${props.url} social preview image`} 85 87 radius={'md'} 86 88 w={50} 87 89 h={50} ··· 89 91 </AspectRatio> 90 92 )} 91 93 <Stack gap={0} flex={0.9}> 92 - <Tooltip label={props.cardContent.url}> 94 + <Tooltip label={props.url}> 93 95 <Anchor 94 96 component={Link} 95 - href={props.cardContent.url} 97 + href={props.url} 96 98 target="_blank" 97 99 c={'gray'} 98 100 lineClamp={1} ··· 101 103 {domain} 102 104 </Anchor> 103 105 </Tooltip> 104 - {props.cardContent.title && ( 106 + {props.title && ( 105 107 <Text fw={500} lineClamp={1}> 106 - {props.cardContent.title} 108 + {props.title} 107 109 </Text> 108 110 )} 109 111 </Stack>
+1 -1
src/webapp/features/cards/components/urlCardActions/UrlCardActions.tsx
··· 118 118 <AddCardToModal 119 119 isOpen={showAddToModal} 120 120 onClose={() => setShowAddToModal(false)} 121 - cardContent={props.cardContent} 121 + url={props.cardContent.url} 122 122 cardId={props.id} 123 123 note={props.note?.text} 124 124 urlLibraryCount={props.urlLibraryCount}
+14
src/webapp/features/cards/lib/queries/useUrlMetadata.tsx
··· 1 + import { useSuspenseQuery } from '@tanstack/react-query'; 2 + import { getUrlMetadata } from '../dal'; 3 + 4 + interface Props { 5 + url: string; 6 + } 7 + 8 + export default function useUrlMetadata(props: Props) { 9 + const metadata = useSuspenseQuery({ 10 + queryKey: [props.url], 11 + queryFn: () => getUrlMetadata(props.url), 12 + }); 13 + return metadata; 14 + }
+3 -1
src/webapp/features/semble/components/SembleHeader/SembleHeader.tsx
··· 15 15 import { getDomain } from '@/lib/utils/link'; 16 16 import UrlAddedBySummary from '../urlAddedBySummary/UrlAddedBySummary'; 17 17 import SembleActions from '../sembleActions/SembleActions'; 18 + import { verifySessionOnServer } from '@/lib/auth/dal.server'; 18 19 19 20 interface Props { 20 21 url: string; ··· 22 23 23 24 export default async function SembleHeader(props: Props) { 24 25 const { metadata } = await getUrlMetadata(props.url); 26 + const session = await verifySessionOnServer(); 25 27 26 28 return ( 27 29 <Stack gap={'xl'}> ··· 74 76 /> 75 77 </Card> 76 78 )} 77 - <SembleActions url={props.url} /> 79 + {session && <SembleActions url={props.url} />} 78 80 </Stack> 79 81 </GridCol> 80 82 </Grid>
+14 -1
src/webapp/features/semble/components/sembleActions/SembleActions.tsx
··· 1 1 'use client'; 2 2 3 + import AddCardToModal from '@/features/cards/components/addCardToModal/AddCardToModal'; 3 4 import useGetCardFromMyLibrary from '@/features/cards/lib/queries/useGetCardFromMyLibrary'; 4 5 import { Button, Group } from '@mantine/core'; 5 - import { Fragment } from 'react'; 6 + import { Fragment, useState } from 'react'; 6 7 import { FiPlus } from 'react-icons/fi'; 7 8 import { IoMdCheckmark } from 'react-icons/io'; 8 9 ··· 13 14 export default function SembleActions(props: Props) { 14 15 const cardStatus = useGetCardFromMyLibrary({ url: props.url }); 15 16 const isInYourLibrary = cardStatus.data.card?.urlInLibrary; 17 + const [showAddToModal, setShowAddToModal] = useState(false); 16 18 17 19 if (cardStatus.error) { 18 20 return null; ··· 27 29 leftSection={ 28 30 isInYourLibrary ? <IoMdCheckmark size={18} /> : <FiPlus size={18} /> 29 31 } 32 + onClick={() => setShowAddToModal(true)} 30 33 > 31 34 {isInYourLibrary ? 'In library' : 'Add to library'} 32 35 </Button> 33 36 </Group> 37 + 38 + <AddCardToModal 39 + isOpen={showAddToModal} 40 + onClose={() => setShowAddToModal(false)} 41 + url={props.url} 42 + cardId={cardStatus.data.card?.id} 43 + note={cardStatus.data.card?.note?.text} 44 + urlLibraryCount={cardStatus.data.card?.urlLibraryCount} 45 + isInYourLibrary={cardStatus.data.card?.urlInLibrary} 46 + /> 34 47 </Fragment> 35 48 ); 36 49 }