A social knowledge tool for researchers built on ATProto
at main 6.4 kB view raw
1import useGetCardFromMyLibrary from '@/features/cards/lib/queries/useGetCardFromMyLibrary'; 2import { 3 Anchor, 4 AspectRatio, 5 Avatar, 6 Card, 7 Group, 8 Stack, 9 Tooltip, 10 Text, 11 Image, 12 Textarea, 13 Button, 14} from '@mantine/core'; 15import { UrlCard, User } from '@semble/types'; 16import Link from 'next/link'; 17import { Fragment, useState } from 'react'; 18import useUpdateNote from '../../lib/mutations/useUpdateNote'; 19import { notifications } from '@mantine/notifications'; 20import useRemoveCardFromLibrary from '@/features/cards/lib/mutations/useRemoveCardFromLibrary'; 21 22interface Props { 23 onClose: () => void; 24 note: UrlCard['note']; 25 cardContent: UrlCard['cardContent']; 26 cardAuthor?: User; 27 domain: string; 28} 29 30export default function NoteCardModalContent(props: Props) { 31 const cardStatus = useGetCardFromMyLibrary({ url: props.cardContent.url }); 32 const isMyCard = props.cardAuthor?.id === cardStatus.data.card?.author.id; 33 const [note, setNote] = useState(isMyCard ? props.note?.text : ''); 34 const [editMode, setEditMode] = useState(false); 35 const [showDeleteWarning, setShowDeleteWarning] = useState(false); 36 37 const removeNote = useRemoveCardFromLibrary(); 38 const updateNote = useUpdateNote(); 39 40 const handleDeleteNote = () => { 41 if (!isMyCard || !props.note) return; 42 43 removeNote.mutate(props.note.id, { 44 onError: () => { 45 notifications.show({ 46 message: 'Could not delete note.', 47 position: 'top-center', 48 }); 49 }, 50 onSettled: () => { 51 props.onClose(); 52 }, 53 }); 54 }; 55 56 const handleUpdateNote = () => { 57 if (!props.note || !note) { 58 props.onClose(); 59 return; 60 } 61 62 if (props.note.text === note) { 63 props.onClose(); 64 return; 65 } 66 67 updateNote.mutate( 68 { 69 cardId: props.note?.id, 70 note: note, 71 }, 72 { 73 onError: () => { 74 notifications.show({ 75 message: 'Could not update note.', 76 position: 'top-center', 77 }); 78 }, 79 onSettled: () => { 80 setEditMode(false); 81 }, 82 }, 83 ); 84 }; 85 86 if (editMode) { 87 return ( 88 <Stack gap={'xs'}> 89 <Textarea 90 id="note" 91 label="Your note" 92 placeholder="Add a note about this card" 93 variant="filled" 94 size="md" 95 autosize 96 minRows={3} 97 maxRows={8} 98 maxLength={500} 99 value={note} 100 onChange={(e) => setNote(e.currentTarget.value)} 101 /> 102 <Group gap={'xs'} grow> 103 <Button 104 variant="light" 105 color="gray" 106 onClick={() => { 107 setEditMode(false); 108 setNote(props.note?.text); 109 }} 110 > 111 Cancel 112 </Button> 113 <Button 114 onClick={handleUpdateNote} 115 loading={updateNote.isPending} 116 disabled={note?.trimEnd() === ''} 117 > 118 Save 119 </Button> 120 </Group> 121 </Stack> 122 ); 123 } 124 return ( 125 <Stack gap={'xs'}> 126 {props.cardAuthor && ( 127 <Group gap={5}> 128 <Avatar 129 size={'sm'} 130 component={Link} 131 href={`/profile/${props.cardAuthor.handle}`} 132 target="_blank" 133 src={props.cardAuthor.avatarUrl} 134 alt={`${props.cardAuthor.name}'s' avatar`} 135 /> 136 <Anchor 137 component={Link} 138 href={`/profile/${props.cardAuthor.handle}`} 139 target="_blank" 140 fw={700} 141 c="blue" 142 lineClamp={1} 143 > 144 {props.cardAuthor.name} 145 </Anchor> 146 </Group> 147 )} 148 {props.note && <Text fs={'italic'}>{props.note.text}</Text>} 149 <Card withBorder component="article" p={'xs'} radius={'lg'}> 150 <Stack> 151 <Group gap={'sm'} justify="space-between"> 152 {props.cardContent.thumbnailUrl && ( 153 <AspectRatio ratio={1 / 1} flex={0.1}> 154 <Image 155 src={props.cardContent.thumbnailUrl} 156 alt={`${props.cardContent.url} social preview image`} 157 radius={'md'} 158 w={50} 159 h={50} 160 /> 161 </AspectRatio> 162 )} 163 <Stack gap={0} flex={0.9}> 164 <Tooltip label={props.cardContent.url}> 165 <Anchor 166 component={Link} 167 href={props.cardContent.url} 168 target="_blank" 169 c={'gray'} 170 lineClamp={1} 171 onClick={(e) => e.stopPropagation()} 172 > 173 {props.domain} 174 </Anchor> 175 </Tooltip> 176 {props.cardContent.title && ( 177 <Text fw={500} lineClamp={1} c="var(--mantine-color-bright)"> 178 {props.cardContent.title} 179 </Text> 180 )} 181 </Stack> 182 </Group> 183 </Stack> 184 </Card> 185 {isMyCard && ( 186 <Fragment> 187 {showDeleteWarning ? ( 188 <Group justify="space-between" gap={'xs'}> 189 <Text>Delete note?</Text> 190 <Group gap={'xs'}> 191 <Button 192 color="red" 193 onClick={handleDeleteNote} 194 loading={removeNote.isPending} 195 > 196 Delete 197 </Button> 198 <Button 199 variant="light" 200 color="gray" 201 onClick={() => setShowDeleteWarning(false)} 202 > 203 Cancel 204 </Button> 205 </Group> 206 </Group> 207 ) : ( 208 <Group gap={'xs'} grow> 209 <Button 210 variant="light" 211 color="gray" 212 onClick={(e) => { 213 e.stopPropagation(); 214 setEditMode(true); 215 }} 216 > 217 Edit note 218 </Button> 219 220 <Button 221 variant="light" 222 color="red" 223 onClick={(e) => { 224 e.stopPropagation(); 225 setShowDeleteWarning(true); 226 }} 227 > 228 Delete note 229 </Button> 230 </Group> 231 )} 232 </Fragment> 233 )} 234 </Stack> 235 ); 236}