This repository has no description
0

Configure Feed

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

semble / src / webapp / features / cards / components / urlCard / UrlCard.tsx
4.3 kB 141 lines
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}