This repository has no description
1'use client';
2
3import type { UrlCard, Collection, User } from '@/api-client';
4import { Anchor, Avatar, Card, Group, Stack, Text } from '@mantine/core';
5import UrlCardActions from '../urlCardActions/UrlCardActions';
6import { MouseEvent } from 'react';
7import UrlCardContent from '../urlCardContent/UrlCardContent';
8import { useRouter } from 'next/navigation';
9import { isCollectionPage, isProfilePage } from '@/lib/utils/link';
10import styles from './UrlCard.module.css';
11import { useUserSettings } from '@/features/settings/lib/queries/useUserSettings';
12import UrlCardDebugView from '../UrlCardDebugView/UrlCardDebugView';
13import Link from 'next/link';
14
15interface Props {
16 id: string;
17 url: string;
18 uri?: string;
19 cardContent: UrlCard['cardContent'];
20 note?: UrlCard['note'];
21 currentCollection?: Collection;
22 urlLibraryCount: number;
23 urlIsInLibrary?: boolean;
24 authorHandle?: string;
25 cardAuthor?: User;
26 viaCardId?: string;
27 showAuthor?: boolean;
28}
29
30export default function UrlCard(props: Props) {
31 const router = useRouter();
32 const { settings } = useUserSettings();
33
34 const handleNavigateToSemblePage = (e: MouseEvent<HTMLElement>) => {
35 e.stopPropagation();
36
37 let targetUrl: string;
38
39 if (isCollectionPage(props.url) || isProfilePage(props.url)) {
40 targetUrl = props.url;
41 } else {
42 // Build URL with viaCardId first, then id last (since id contains a URL that might have query params)
43 if (props.viaCardId) {
44 targetUrl = `/url?viaCardId=${props.id}&id=${props.cardContent.url}`;
45 } else {
46 targetUrl = `/url?id=${props.cardContent.url}`;
47 }
48 }
49
50 // Open in new tab if Cmd+Click (Mac), Ctrl+Click (Windows/Linux), or middle click
51 if (e.metaKey || e.ctrlKey || e.button === 1) {
52 window.open(targetUrl, '_blank', 'noopener,noreferrer');
53 return;
54 }
55
56 router.push(targetUrl);
57 };
58
59 const handleAuxClick = (e: MouseEvent<HTMLElement>) => {
60 // Handle middle mouse button (button 1)
61 if (e.button === 1) {
62 handleNavigateToSemblePage(e);
63 }
64 };
65
66 return (
67 <Card
68 component="article"
69 radius={'lg'}
70 p={'sm'}
71 flex={1}
72 h={'100%'}
73 withBorder
74 className={styles.root}
75 onClick={handleNavigateToSemblePage}
76 onAuxClick={handleAuxClick}
77 >
78 <Stack justify="space-between" flex={1}>
79 <UrlCardContent
80 url={props.url}
81 uri={props.uri}
82 cardContent={props.cardContent}
83 />
84
85 {settings.tinkerMode && (
86 <UrlCardDebugView
87 cardContent={props.cardContent}
88 cardAuthor={props.cardAuthor}
89 />
90 )}
91
92 <Stack>
93 {props.showAuthor && props.cardAuthor && (
94 <Group gap={'7'}>
95 <Text fz={'xs'} c={'dimmed'} fw={500}>
96 Added by{' '}
97 </Text>
98 <Group gap={'5'}>
99 <Avatar
100 component={Link}
101 href={`/profile/${props.cardAuthor?.handle}`}
102 src={props.cardAuthor?.avatarUrl?.replace(
103 'avatar',
104 'avatar_thumbnail',
105 )}
106 alt={`${props.cardAuthor?.handle}'s avatar`}
107 size={'xs'}
108 radius={'sm'}
109 />
110 <Anchor
111 component={Link}
112 href={`/profile/${props.cardAuthor.handle}`}
113 fz={'xs'}
114 fw={600}
115 c={'bright'}
116 underline="never"
117 onClick={(e) => e.stopPropagation()}
118 >
119 {props.cardAuthor.name || `@${props.cardAuthor.handle}`}
120 </Anchor>
121 </Group>
122 </Group>
123 )}
124
125 <UrlCardActions
126 cardAuthor={props.cardAuthor}
127 cardContent={props.cardContent}
128 cardCount={props.urlLibraryCount}
129 id={props.id}
130 authorHandle={props.authorHandle}
131 note={props.note}
132 currentCollection={props.currentCollection}
133 urlLibraryCount={props.urlLibraryCount}
134 urlIsInLibrary={props.urlIsInLibrary ?? false}
135 viaCardId={props.viaCardId}
136 />
137 </Stack>
138 </Stack>
139 </Card>
140 );
141}