mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2026-04-05 00:56:32 +00:00
Compare commits
6 Commits
eb87b9344a
...
a53ef66490
| Author | SHA1 | Date | |
|---|---|---|---|
| a53ef66490 | |||
| a9b7e5086f | |||
| 882c403d06 | |||
| afc5b2ae97 | |||
| ef6643e0f3 | |||
| 3a690aaffa |
@ -120,7 +120,7 @@ function MapContainer({ layers, map }: { layers: LayerProps[]; map: any }) {
|
||||
parameterField={
|
||||
layer.itemType.custom_profile_url ? 'extended.external_profile_id' : 'id'
|
||||
}
|
||||
text={layer.itemType.botton_label ?? 'Profile'}
|
||||
text={layer.itemType.button_label ?? 'Profile'}
|
||||
target={layer.itemType.custom_profile_url ? '_blank' : '_self'}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -1,71 +1,21 @@
|
||||
import { QrCodeIcon, ShareIcon } from '@heroicons/react/24/solid'
|
||||
import { LuNavigation } from 'react-icons/lu'
|
||||
|
||||
import ChevronSVG from '#assets/chevron.svg'
|
||||
import ClipboardSVG from '#assets/share/clipboard.svg'
|
||||
import FacebookSVG from '#assets/share/facebook.svg'
|
||||
import LinkedinSVG from '#assets/share/linkedin.svg'
|
||||
import TelegramSVG from '#assets/share/telegram.svg'
|
||||
import TwitterSVG from '#assets/share/twitter.svg'
|
||||
import WhatsappSVG from '#assets/share/whatsapp.svg'
|
||||
import XingSVG from '#assets/share/xing.svg'
|
||||
import { useMyProfile } from '#components/Map/hooks/useMyProfile'
|
||||
|
||||
import { useNavigationUrl, useShareLogic } from './hooks'
|
||||
import { useNavigationUrl } from './hooks'
|
||||
import { ShareButton } from './ShareButton'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { PlatformConfig, SharePlatformConfigs } from './types'
|
||||
|
||||
interface ActionButtonsProps {
|
||||
item: Item
|
||||
onQrModalOpen: () => void
|
||||
}
|
||||
|
||||
export function ActionButtons({ item, onQrModalOpen }: ActionButtonsProps) {
|
||||
export function ActionButtons({ item }: ActionButtonsProps) {
|
||||
const myProfile = useMyProfile()
|
||||
const { getNavigationUrl, isMobile, isIOS } = useNavigationUrl(
|
||||
item.position?.coordinates as [number, number] | undefined,
|
||||
)
|
||||
const { shareUrl, shareTitle, copyLink, getShareUrl } = useShareLogic(item)
|
||||
|
||||
const platformConfigs: SharePlatformConfigs = {
|
||||
facebook: {
|
||||
shareUrl: 'https://www.facebook.com/sharer/sharer.php?u={url}',
|
||||
icon: <img src={FacebookSVG} alt='Facebook' className='tw:w-4 tw:h-4' />,
|
||||
label: 'Facebook',
|
||||
bgColor: '#3b5998',
|
||||
},
|
||||
twitter: {
|
||||
shareUrl: 'https://twitter.com/intent/tweet?text={title}:%20{url}',
|
||||
icon: <img src={TwitterSVG} alt='Twitter' className='tw:w-4 tw:h-4' />,
|
||||
label: 'Twitter',
|
||||
bgColor: '#55acee',
|
||||
},
|
||||
linkedin: {
|
||||
shareUrl: 'http://www.linkedin.com/shareArticle?mini=true&url={url}&title={title}',
|
||||
icon: <img src={LinkedinSVG} alt='Linkedin' className='tw:w-4 tw:h-4' />,
|
||||
label: 'LinkedIn',
|
||||
bgColor: '#4875b4',
|
||||
},
|
||||
whatsapp: {
|
||||
shareUrl: 'https://api.whatsapp.com/send?text={title}%20{url}',
|
||||
icon: <img src={WhatsappSVG} alt='Whatsapp' className='tw:w-4 tw:h-4' />,
|
||||
label: 'WhatsApp',
|
||||
bgColor: '#25D366',
|
||||
},
|
||||
telegram: {
|
||||
shareUrl: 'https://t.me/share/url?url={url}&text={title}',
|
||||
icon: <img src={TelegramSVG} alt='Telegram' className='tw:w-4 tw:h-4' />,
|
||||
label: 'Telegram',
|
||||
bgColor: '#0088cc',
|
||||
},
|
||||
xing: {
|
||||
shareUrl: 'https://www.xing-share.com/app/user?op=share;sc_p=xing-share;url={url}',
|
||||
icon: <img src={XingSVG} alt='Xing' className='tw:w-4 tw:h-4' />,
|
||||
label: 'Xing',
|
||||
bgColor: '#026466',
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -82,82 +32,7 @@ export function ActionButtons({ item, onQrModalOpen }: ActionButtonsProps) {
|
||||
<LuNavigation className='tw:h-4 tw:w-4' />
|
||||
</a>
|
||||
)}
|
||||
{myProfile.myProfile?.id === item.id && (
|
||||
<button
|
||||
onClick={onQrModalOpen}
|
||||
className='tw:btn tw:mr-2 tw:px-3 tw:tooltip tw:tooltip-top'
|
||||
title='QR-Code'
|
||||
data-tip='QR Code'
|
||||
>
|
||||
<QrCodeIcon className='tw:h-4 tw:w-4' />
|
||||
</button>
|
||||
)}
|
||||
{myProfile.myProfile?.id !== item.id && (
|
||||
<div className='tw:dropdown tw:dropdown-end'>
|
||||
<div
|
||||
tabIndex={0}
|
||||
role='button'
|
||||
className='tw:btn tw:px-3 tw:tooltip tw:tooltip-top'
|
||||
data-tip='Share'
|
||||
>
|
||||
<ShareIcon className='tw:w-4 tw:h-4' />
|
||||
</div>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className='tw:dropdown-content tw:menu tw:bg-base-100 tw:rounded-box tw:z-[1] tw:p-2 tw:shadow-sm'
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
onClick={copyLink}
|
||||
className='tw:flex tw:items-center tw:gap-3'
|
||||
style={{ color: 'inherit' }}
|
||||
>
|
||||
<div
|
||||
className='tw:w-6 tw:h-6 tw:rounded-full tw:flex tw:items-center tw:justify-center'
|
||||
style={{ backgroundColor: '#888' }}
|
||||
>
|
||||
<img src={ClipboardSVG} className='tw:w-3 tw:h-3' alt='Copy' />
|
||||
</div>
|
||||
Copy Link
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={`mailto:?subject=${encodeURIComponent(shareTitle)}&body=${encodeURIComponent(shareUrl)}`}
|
||||
className='tw:flex tw:items-center tw:gap-3'
|
||||
style={{ color: 'inherit' }}
|
||||
>
|
||||
<div
|
||||
className='tw:w-6 tw:h-6 tw:rounded-full tw:flex tw:items-center tw:justify-center tw:text-white'
|
||||
style={{ backgroundColor: '#444' }}
|
||||
>
|
||||
<img src={ChevronSVG} className='tw:w-3 tw:h-3' alt='Copy' />
|
||||
</div>
|
||||
Email
|
||||
</a>
|
||||
</li>
|
||||
{Object.entries(platformConfigs).map(([platform, config]) => (
|
||||
<li key={platform}>
|
||||
<a
|
||||
href={getShareUrl(platform as keyof SharePlatformConfigs, platformConfigs)}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='tw:flex tw:items-center tw:gap-3'
|
||||
style={{ color: 'inherit' }}
|
||||
>
|
||||
<div
|
||||
className='tw:w-6 tw:h-6 tw:rounded-full tw:flex tw:items-center tw:justify-center'
|
||||
style={{ backgroundColor: (config as PlatformConfig).bgColor }}
|
||||
>
|
||||
{(config as PlatformConfig).icon}
|
||||
</div>
|
||||
{(config as PlatformConfig).label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{myProfile.myProfile?.id !== item.id && <ShareButton item={item} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -23,6 +23,10 @@ export function ConnectionStatus({ item }: ConnectionStatusProps) {
|
||||
r.related_items_id === myProfile.myProfile?.id,
|
||||
)
|
||||
|
||||
if (!item.layer?.itemType.show_cta_button) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (isConnected) {
|
||||
return <p className='tw:flex tw:items-center tw:mr-2'>✅ Connected</p>
|
||||
}
|
||||
@ -30,7 +34,7 @@ export function ConnectionStatus({ item }: ConnectionStatusProps) {
|
||||
return (
|
||||
<button
|
||||
style={{
|
||||
backgroundColor: `${item.color ?? (getItemTags(item)[0]?.color ? getItemTags(item)[0].color : (item.layer?.markerDefaultColor ?? '#000'))}`,
|
||||
backgroundColor: `${item.color ?? (getItemTags(item)[0]?.color ? getItemTags(item)[0].color : item.layer.markerDefaultColor || '#000')}`,
|
||||
}}
|
||||
className='tw:btn tw:text-white tw:mr-2 tw:tooltip tw:tooltip-top '
|
||||
data-tip={'Connect'}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { QrCodeIcon } from '@heroicons/react/24/solid'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||
@ -7,9 +8,16 @@ import type { Item } from '#types/Item'
|
||||
interface ItemAvatarProps {
|
||||
item: Item
|
||||
big?: boolean
|
||||
showQrButton?: boolean
|
||||
onQrClick?: () => void
|
||||
}
|
||||
|
||||
export function ItemAvatar({ item, big = false }: ItemAvatarProps) {
|
||||
export function ItemAvatar({
|
||||
item,
|
||||
big = false,
|
||||
showQrButton = false,
|
||||
onQrClick,
|
||||
}: ItemAvatarProps) {
|
||||
const appState = useAppState()
|
||||
const [imageLoaded, setImageLoaded] = useState(false)
|
||||
|
||||
@ -17,10 +25,21 @@ export function ItemAvatar({ item, big = false }: ItemAvatarProps) {
|
||||
(item.image && appState.assetsApi.url + item.image + '?width=160&heigth=160') ??
|
||||
item.image_external
|
||||
|
||||
if (!avatar) return null
|
||||
const hasAvatar = !!avatar
|
||||
|
||||
// If no avatar but QR button should be shown, show only the QR button
|
||||
if (!hasAvatar && showQrButton) {
|
||||
return (
|
||||
<button onClick={onQrClick} className='tw:btn tw:btn-lg tw:p-3 tw:mr-2' title='QR-Code'>
|
||||
<QrCodeIcon className='tw:h-6 tw:w-6' />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
if (!hasAvatar) return null
|
||||
|
||||
return (
|
||||
<div className='tw:avatar'>
|
||||
<div className='tw:avatar tw:relative'>
|
||||
<div
|
||||
className={`${
|
||||
big ? 'tw:w-16' : 'tw:w-10'
|
||||
@ -36,6 +55,15 @@ export function ItemAvatar({ item, big = false }: ItemAvatarProps) {
|
||||
/>
|
||||
{!imageLoaded && <div className='tw:w-full tw:h-full tw:bg-gray-200 tw:rounded-full' />}
|
||||
</div>
|
||||
{showQrButton && (
|
||||
<button
|
||||
onClick={onQrClick}
|
||||
className='tw:btn tw:p-1 tw:btn-sm tw:absolute tw:bottom-[-6px] tw:right-[-6px]'
|
||||
title='QR-Code'
|
||||
>
|
||||
<QrCodeIcon className='tw:h-5 tw:w-5' />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { MapPinIcon } from '@heroicons/react/24/solid'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { useGeoDistance } from '#components/Map/hooks/useGeoDistance'
|
||||
import { useReverseGeocode } from '#components/Map/hooks/useReverseGeocode'
|
||||
@ -24,6 +25,9 @@ export function ItemTitle({
|
||||
}: ItemTitleProps) {
|
||||
const { distance } = useGeoDistance(item.position ?? undefined)
|
||||
const { formatDistance } = useFormatDistance()
|
||||
const titleRef = useRef<HTMLDivElement>(null)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const [fontSize, setFontSize] = useState<string>('tw:text-xl')
|
||||
|
||||
const { address } = useReverseGeocode(
|
||||
item.position?.coordinates as [number, number] | undefined,
|
||||
@ -34,10 +38,74 @@ export function ItemTitle({
|
||||
const title = item.name ?? item.layer?.item_default_name
|
||||
const subtitle = item.subname
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current || !title) {
|
||||
return
|
||||
}
|
||||
|
||||
const calculateFontSize = () => {
|
||||
const container = containerRef.current
|
||||
if (!container) return
|
||||
|
||||
const containerWidth = container.offsetWidth
|
||||
|
||||
// Create temporary element to measure text width
|
||||
const measureElement = document.createElement('span')
|
||||
measureElement.style.position = 'absolute'
|
||||
measureElement.style.visibility = 'hidden'
|
||||
measureElement.style.whiteSpace = 'nowrap'
|
||||
measureElement.style.fontWeight = '700' // font-bold
|
||||
measureElement.textContent = title
|
||||
document.body.appendChild(measureElement)
|
||||
|
||||
// Measure at different font sizes - include larger sizes only if big is true
|
||||
const fontSizes = big
|
||||
? [
|
||||
{ class: 'tw:text-2xl', pixels: 24 },
|
||||
{ class: 'tw:text-xl', pixels: 20 },
|
||||
{ class: 'tw:text-lg', pixels: 18 },
|
||||
]
|
||||
: [
|
||||
{ class: 'tw:text-xl', pixels: 20 },
|
||||
{ class: 'tw:text-lg', pixels: 18 },
|
||||
]
|
||||
|
||||
let selectedSize = 'tw:text-lg'
|
||||
|
||||
for (const size of fontSizes) {
|
||||
measureElement.style.fontSize = `${size.pixels}px`
|
||||
const textWidth = measureElement.offsetWidth
|
||||
|
||||
if (textWidth <= containerWidth) {
|
||||
selectedSize = size.class
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
document.body.removeChild(measureElement)
|
||||
setFontSize(selectedSize)
|
||||
}
|
||||
|
||||
// Initial calculation
|
||||
calculateFontSize()
|
||||
|
||||
// Watch for container size changes
|
||||
const resizeObserver = new ResizeObserver(calculateFontSize)
|
||||
resizeObserver.observe(containerRef.current)
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
}, [title, big])
|
||||
|
||||
return (
|
||||
<div className={`${hasAvatar ? 'tw:ml-3' : ''} tw:overflow-hidden tw:flex-1 tw:min-w-0 `}>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`${hasAvatar ? 'tw:ml-3' : ''} tw:overflow-hidden tw:flex-1 tw:min-w-0 `}
|
||||
>
|
||||
<div
|
||||
className={`${big ? 'tw:xl:text-3xl tw:text-2xl' : 'tw:text-xl'} tw:font-bold`}
|
||||
ref={titleRef}
|
||||
className={`${fontSize} tw:font-bold`}
|
||||
title={title}
|
||||
data-cy='profile-title'
|
||||
>
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import QRCode from 'react-qr-code'
|
||||
|
||||
import ClipboardSVG from '#assets/share/clipboard.svg'
|
||||
import DialogModal from '#components/Templates/DialogModal'
|
||||
|
||||
import { useShareLogic } from './hooks'
|
||||
import { ShareButton } from './ShareButton'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
@ -14,7 +14,7 @@ interface QRModalProps {
|
||||
}
|
||||
|
||||
export function QRModal({ item, isOpen, onClose }: QRModalProps) {
|
||||
const { inviteLink, copyLink } = useShareLogic(item)
|
||||
const { inviteLink } = useShareLogic(item)
|
||||
|
||||
return (
|
||||
<DialogModal
|
||||
@ -24,21 +24,15 @@ export function QRModal({ item, isOpen, onClose }: QRModalProps) {
|
||||
className='tw:w-[calc(100vw-2rem)] tw:max-w-96'
|
||||
>
|
||||
<div onClick={(e) => e.stopPropagation()} className='tw:text-center tw:p-4'>
|
||||
<p className='tw:text-xl'>Share your profile with others to expand your network.</p>
|
||||
<p className='tw:text-xl tw:font-bold'>Share your Profile to expand your Network!</p>
|
||||
|
||||
<div className='tw:p-8 tw:my-8 tw:rounded-lg tw:inline-block tw:border-base-300 tw:border-2 '>
|
||||
<div className='tw:p-8 tw:my-8 tw:rounded-lg tw:inline-block tw:border-base-300 tw:bg-base-200 tw:border-1 '>
|
||||
<QRCode value={inviteLink} size={192} />
|
||||
</div>
|
||||
|
||||
<div className='tw:flex tw:items-center tw:gap-2 tw:w-full tw:border-base-300 tw:border-2 tw:rounded-lg tw:p-3'>
|
||||
<span className='tw:text-sm tw:truncate tw:flex-1 tw:min-w-0'>{inviteLink}</span>
|
||||
<button
|
||||
onClick={copyLink}
|
||||
className='tw:btn tw:btn-primary tw:btn-sm tw:flex-shrink-0'
|
||||
title='Link kopieren'
|
||||
>
|
||||
<img src={ClipboardSVG} className='tw:w-4 tw:h-4' alt='Copy' />
|
||||
</button>
|
||||
<ShareButton item={item} dropdownDirection='up' />
|
||||
</div>
|
||||
</div>
|
||||
</DialogModal>
|
||||
|
||||
@ -0,0 +1,168 @@
|
||||
import { ShareIcon } from '@heroicons/react/24/solid'
|
||||
import { useRef } from 'react'
|
||||
|
||||
import ChevronSVG from '#assets/chevron.svg'
|
||||
import ClipboardSVG from '#assets/share/clipboard.svg'
|
||||
import FacebookSVG from '#assets/share/facebook.svg'
|
||||
import LinkedinSVG from '#assets/share/linkedin.svg'
|
||||
import TelegramSVG from '#assets/share/telegram.svg'
|
||||
import TwitterSVG from '#assets/share/twitter.svg'
|
||||
import WhatsappSVG from '#assets/share/whatsapp.svg'
|
||||
import XingSVG from '#assets/share/xing.svg'
|
||||
|
||||
import { useShareLogic } from './hooks'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { PlatformConfig, SharePlatformConfigs } from './types'
|
||||
|
||||
interface ShareButtonProps {
|
||||
item: Item
|
||||
dropdownDirection?: 'up' | 'down'
|
||||
}
|
||||
|
||||
export function ShareButton({ item, dropdownDirection = 'down' }: ShareButtonProps) {
|
||||
const { shareUrl, shareTitle, copyLink, getShareUrl } = useShareLogic(item)
|
||||
const detailsRef = useRef<HTMLDetailsElement>(null)
|
||||
|
||||
const closeDropdown = () => {
|
||||
if (detailsRef.current) {
|
||||
detailsRef.current.open = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopyLink = () => {
|
||||
copyLink()
|
||||
closeDropdown()
|
||||
}
|
||||
|
||||
const canUseNativeShare =
|
||||
typeof navigator !== 'undefined' && typeof navigator.share !== 'undefined'
|
||||
|
||||
const handleNativeShare = () => {
|
||||
void navigator
|
||||
.share({
|
||||
title: shareTitle,
|
||||
url: shareUrl,
|
||||
})
|
||||
.then(closeDropdown)
|
||||
.catch(() => {
|
||||
// User cancelled or error occurred - ignore
|
||||
})
|
||||
}
|
||||
|
||||
const platformConfigs: SharePlatformConfigs = {
|
||||
facebook: {
|
||||
shareUrl: 'https://www.facebook.com/sharer/sharer.php?u={url}',
|
||||
icon: <img src={FacebookSVG} alt='Facebook' className='tw:w-4 tw:h-4' />,
|
||||
label: 'Facebook',
|
||||
bgColor: '#3b5998',
|
||||
},
|
||||
twitter: {
|
||||
shareUrl: 'https://twitter.com/intent/tweet?text={title}:%20{url}',
|
||||
icon: <img src={TwitterSVG} alt='Twitter' className='tw:w-4 tw:h-4' />,
|
||||
label: 'Twitter',
|
||||
bgColor: '#55acee',
|
||||
},
|
||||
linkedin: {
|
||||
shareUrl: 'http://www.linkedin.com/shareArticle?mini=true&url={url}&title={title}',
|
||||
icon: <img src={LinkedinSVG} alt='Linkedin' className='tw:w-4 tw:h-4' />,
|
||||
label: 'LinkedIn',
|
||||
bgColor: '#4875b4',
|
||||
},
|
||||
whatsapp: {
|
||||
shareUrl: 'https://api.whatsapp.com/send?text={title}%20{url}',
|
||||
icon: <img src={WhatsappSVG} alt='Whatsapp' className='tw:w-4 tw:h-4' />,
|
||||
label: 'WhatsApp',
|
||||
bgColor: '#25D366',
|
||||
},
|
||||
telegram: {
|
||||
shareUrl: 'https://t.me/share/url?url={url}&text={title}',
|
||||
icon: <img src={TelegramSVG} alt='Telegram' className='tw:w-4 tw:h-4' />,
|
||||
label: 'Telegram',
|
||||
bgColor: '#0088cc',
|
||||
},
|
||||
xing: {
|
||||
shareUrl: 'https://www.xing-share.com/app/user?op=share;sc_p=xing-share;url={url}',
|
||||
icon: <img src={XingSVG} alt='Xing' className='tw:w-4 tw:h-4' />,
|
||||
label: 'Xing',
|
||||
bgColor: '#026466',
|
||||
},
|
||||
}
|
||||
|
||||
const dropdownClass = dropdownDirection === 'up' ? 'tw:dropdown-top' : ''
|
||||
|
||||
// If native share is available, render a simple button instead of dropdown
|
||||
if (canUseNativeShare) {
|
||||
return (
|
||||
<button
|
||||
onClick={handleNativeShare}
|
||||
className='tw:btn tw:px-3 tw:tooltip tw:tooltip-top'
|
||||
data-tip='Share'
|
||||
>
|
||||
<ShareIcon className='tw:w-4 tw:h-4' />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
// Otherwise, render the dropdown with manual share options
|
||||
return (
|
||||
<details ref={detailsRef} className={`tw:dropdown tw:dropdown-end ${dropdownClass}`}>
|
||||
<summary className='tw:btn tw:px-3 tw:tooltip tw:tooltip-top' data-tip='Share'>
|
||||
<ShareIcon className='tw:w-4 tw:h-4' />
|
||||
</summary>
|
||||
<ul className='tw:dropdown-content tw:menu tw:bg-base-100 tw:rounded-box tw:z-[1] tw:p-2 tw:shadow-sm'>
|
||||
<li>
|
||||
<a
|
||||
onClick={handleCopyLink}
|
||||
className='tw:flex tw:items-center tw:gap-3'
|
||||
style={{ color: 'inherit' }}
|
||||
>
|
||||
<div
|
||||
className='tw:w-6 tw:h-6 tw:rounded-full tw:flex tw:items-center tw:justify-center'
|
||||
style={{ backgroundColor: '#888' }}
|
||||
>
|
||||
<img src={ClipboardSVG} className='tw:w-3 tw:h-3' alt='Copy' />
|
||||
</div>
|
||||
Copy Link
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={`mailto:?subject=${encodeURIComponent(shareTitle)}&body=${encodeURIComponent(shareUrl)}`}
|
||||
onClick={closeDropdown}
|
||||
className='tw:flex tw:items-center tw:gap-3'
|
||||
style={{ color: 'inherit' }}
|
||||
>
|
||||
<div
|
||||
className='tw:w-6 tw:h-6 tw:rounded-full tw:flex tw:items-center tw:justify-center tw:text-white'
|
||||
style={{ backgroundColor: '#444' }}
|
||||
>
|
||||
<img src={ChevronSVG} className='tw:w-3 tw:h-3' alt='Copy' />
|
||||
</div>
|
||||
Email
|
||||
</a>
|
||||
</li>
|
||||
{Object.entries(platformConfigs).map(([platform, config]) => (
|
||||
<li key={platform}>
|
||||
<a
|
||||
href={getShareUrl(platform as keyof SharePlatformConfigs, platformConfigs)}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
onClick={closeDropdown}
|
||||
className='tw:flex tw:items-center tw:gap-3'
|
||||
style={{ color: 'inherit' }}
|
||||
>
|
||||
<div
|
||||
className='tw:w-6 tw:h-6 tw:rounded-full tw:flex tw:items-center tw:justify-center'
|
||||
style={{ backgroundColor: (config as PlatformConfig).bgColor }}
|
||||
>
|
||||
{(config as PlatformConfig).icon}
|
||||
</div>
|
||||
{(config as PlatformConfig).label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</details>
|
||||
)
|
||||
}
|
||||
@ -35,10 +35,7 @@ export const useNavigationUrl = (coordinates?: [number, number]) => {
|
||||
export const useShareLogic = (item?: Item) => {
|
||||
const shareUrl = window.location.href
|
||||
const shareTitle = item?.name ?? 'Utopia Map Item'
|
||||
const inviteLink =
|
||||
item?.secrets && item.secrets.length > 0
|
||||
? `${window.location.origin}/invite/${item.secrets[0].secret}`
|
||||
: shareUrl
|
||||
const inviteLink = shareUrl
|
||||
|
||||
const copyLink = () => {
|
||||
navigator.clipboard
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
import { useMyProfile } from '#components/Map/hooks/useMyProfile'
|
||||
|
||||
import { ActionButtons } from './ActionButtons'
|
||||
import { ConnectionStatus } from './ConnectionStatus'
|
||||
import { DeleteModal } from './DeleteModal'
|
||||
@ -24,17 +26,25 @@ export function HeaderView({
|
||||
}: HeaderViewProps) {
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false)
|
||||
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
|
||||
const myProfile = useMyProfile()
|
||||
|
||||
if (!item) return null
|
||||
|
||||
const hasAvatar = !!(item.image ?? item.image_external)
|
||||
const isMyProfile = myProfile.myProfile?.id === item.id
|
||||
const showQrButton = big && isMyProfile
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='tw:flex tw:flex-row'>
|
||||
<div className={'tw:grow tw:flex tw:flex-1 tw:min-w-0'}>
|
||||
<div className='tw:flex tw:flex-1 tw:min-w-0 tw:items-center'>
|
||||
{hasAvatar && <ItemAvatar item={item} big={big} />}
|
||||
<ItemAvatar
|
||||
item={item}
|
||||
big={big}
|
||||
showQrButton={showQrButton}
|
||||
onQrClick={() => setQrModalOpen(true)}
|
||||
/>
|
||||
<ItemTitle
|
||||
item={item}
|
||||
big={big}
|
||||
@ -62,7 +72,7 @@ export function HeaderView({
|
||||
<div className='tw:grow'></div>
|
||||
<div className='tw:flex'>
|
||||
<ConnectionStatus item={item} />
|
||||
<ActionButtons item={item} onQrModalOpen={() => setQrModalOpen(true)} />
|
||||
<ActionButtons item={item} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -8,25 +8,19 @@ import type { Item } from '#types/Item'
|
||||
*/
|
||||
export const StartEndView = ({ item }: { item?: Item }) => {
|
||||
return (
|
||||
<div className='tw:flex tw:flex-row tw:mb-4 tw:mt-4'>
|
||||
<div className='tw:basis-2/5 tw:flex tw:flex-row'>
|
||||
<CalendarIcon className='tw:h-4 tw:w-4 tw:mr-2' />
|
||||
<time
|
||||
className='tw:align-middle'
|
||||
dateTime={item && item.start ? item.start.substring(0, 10) : ''}
|
||||
>
|
||||
<div className='tw:flex tw:flex-row tw:mb-2.5 tw:mt-2.5 tw:bg-base-200 tw:px-3 tw:py-2.5 tw:rounded-selector tw:w-full'>
|
||||
<div className='tw:basis-2/5 tw:flex tw:flex-row tw:items-center tw:font-bold'>
|
||||
<CalendarIcon className='tw:h-5 tw:w-5 tw:mr-2' />
|
||||
<time dateTime={item && item.start ? item.start.substring(0, 10) : ''}>
|
||||
{item && item.start ? new Date(item.start).toLocaleDateString() : ''}
|
||||
</time>
|
||||
</div>
|
||||
<div className='tw:basis-1/5 tw:place-content-center'>
|
||||
<div className='tw:basis-1/5 tw:flex tw:items-center tw:justify-center'>
|
||||
<span>-</span>
|
||||
</div>
|
||||
<div className='tw:basis-2/5 tw:flex tw:flex-row'>
|
||||
<CalendarIcon className='tw:h-4 tw:w-4 tw:mr-2' />
|
||||
<time
|
||||
className='tw:align-middle'
|
||||
dateTime={item && item.end ? item.end.substring(0, 10) : ''}
|
||||
>
|
||||
<div className='tw:basis-2/5 tw:flex tw:flex-row tw:items-center tw:font-bold'>
|
||||
<CalendarIcon className='tw:h-5 tw:w-5 tw:mr-2' />
|
||||
<time dateTime={item && item.end ? item.end.substring(0, 10) : ''}>
|
||||
{item && item.end ? new Date(item.end).toLocaleDateString() : ''}
|
||||
</time>
|
||||
</div>
|
||||
|
||||
@ -174,7 +174,7 @@ export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any>
|
||||
<MapOverlayPage
|
||||
key={item.id}
|
||||
data-cy='profile-view'
|
||||
className={`tw:p-0! tw:overflow-scroll tw:m-4! tw:md:w-[calc(50%-32px)] tw:w-[calc(100%-32px)] tw:min-w-80 tw:max-w-3xl tw:left-0! tw:sm:left-auto! tw:top-0 tw:bottom-0 tw:transition-opacity tw:duration-500 ${!selectPosition ? 'tw:opacity-100 tw:pointer-events-auto' : 'tw:opacity-0 tw:pointer-events-none'} tw:max-h-[1000px]`}
|
||||
className={`tw:@container tw:p-0! tw:overflow-scroll tw:m-4! tw:md:w-[calc(50%-32px)] tw:w-[calc(100%-32px)] tw:min-w-80 tw:max-w-3xl tw:left-0! tw:sm:left-auto! tw:top-0 tw:bottom-0 tw:transition-opacity tw:duration-500 ${!selectPosition ? 'tw:opacity-100 tw:pointer-events-auto' : 'tw:opacity-0 tw:pointer-events-none'} tw:max-h-[1000px]`}
|
||||
>
|
||||
<>
|
||||
<div className={'tw:px-6 tw:pt-6'} data-cy='profile-header'>
|
||||
|
||||
@ -4,7 +4,7 @@ import type { Item } from '#types/Item'
|
||||
|
||||
export const ProfileStartEndView = ({ item }: { item: Item }) => {
|
||||
return (
|
||||
<div className='tw:mt-2 tw:px-6 tw:max-w-xs'>
|
||||
<div className='tw:mt-2 tw:px-6'>
|
||||
<StartEndView item={item}></StartEndView>
|
||||
</div>
|
||||
)
|
||||
|
||||
3
lib/src/types/ItemType.d.ts
vendored
3
lib/src/types/ItemType.d.ts
vendored
@ -18,10 +18,11 @@ export interface ItemType {
|
||||
questlog: boolean
|
||||
custom_profile_url?: string
|
||||
small_form_edit?: boolean
|
||||
botton_label?: string
|
||||
button_label?: string
|
||||
text_input_label?: string
|
||||
show_header_view_in_form?: boolean
|
||||
cta_button_label?: string
|
||||
show_address?: boolean
|
||||
cta_relation?: string
|
||||
show_cta_button?: boolean
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user