A social knowledge tool for researchers built on ATProto
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}