mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
Add routing service links to item profiles
Co-authored-by: antontranelis <31516529+antontranelis@users.noreply.github.com>
This commit is contained in:
parent
1219460691
commit
a3da7e0ab3
@ -0,0 +1,93 @@
|
||||
import { render } from '@testing-library/react'
|
||||
import { describe, it, expect } from 'vitest'
|
||||
|
||||
import { RoutingLinksView } from './RoutingLinksView'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
const itemWithPosition: Item = {
|
||||
id: '1',
|
||||
name: 'Test Location',
|
||||
position: {
|
||||
type: 'Point',
|
||||
coordinates: [9.667615, 50.588632], // longitude, latitude format
|
||||
},
|
||||
}
|
||||
|
||||
const itemWithoutPosition: Item = {
|
||||
id: '2',
|
||||
name: 'Test Item Without Position',
|
||||
}
|
||||
|
||||
const itemWithNullPosition: Item = {
|
||||
id: '3',
|
||||
name: 'Test Item With Null Position',
|
||||
position: null,
|
||||
}
|
||||
|
||||
describe('<RoutingLinksView />', () => {
|
||||
describe('when item has position coordinates', () => {
|
||||
it('renders routing service links', () => {
|
||||
const wrapper = render(<RoutingLinksView item={itemWithPosition} />)
|
||||
|
||||
expect(wrapper.getByText('Get Directions')).toBeInTheDocument()
|
||||
expect(wrapper.getByText('Google Maps')).toBeInTheDocument()
|
||||
expect(wrapper.getByText('Apple Maps')).toBeInTheDocument()
|
||||
expect(wrapper.getByText('OpenStreetMap')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('generates correct Google Maps URL', () => {
|
||||
const wrapper = render(<RoutingLinksView item={itemWithPosition} />)
|
||||
const googleMapsLink = wrapper.getByText('Google Maps').closest('a')
|
||||
|
||||
expect(googleMapsLink).toHaveAttribute(
|
||||
'href',
|
||||
'https://www.google.com/maps/dir/?api=1&destination=50.588632,9.667615',
|
||||
)
|
||||
})
|
||||
|
||||
it('generates correct Apple Maps URL', () => {
|
||||
const wrapper = render(<RoutingLinksView item={itemWithPosition} />)
|
||||
const appleMapsLink = wrapper.getByText('Apple Maps').closest('a')
|
||||
|
||||
expect(appleMapsLink).toHaveAttribute(
|
||||
'href',
|
||||
'https://maps.apple.com/?daddr=50.588632,9.667615',
|
||||
)
|
||||
})
|
||||
|
||||
it('generates correct OpenStreetMap URL', () => {
|
||||
const wrapper = render(<RoutingLinksView item={itemWithPosition} />)
|
||||
const osmLink = wrapper.getByText('OpenStreetMap').closest('a')
|
||||
|
||||
expect(osmLink).toHaveAttribute(
|
||||
'href',
|
||||
'https://www.openstreetmap.org/directions?to=50.588632,9.667615',
|
||||
)
|
||||
})
|
||||
|
||||
it('opens links in new tab', () => {
|
||||
const wrapper = render(<RoutingLinksView item={itemWithPosition} />)
|
||||
const links = wrapper.container.querySelectorAll('a')
|
||||
|
||||
links.forEach((link) => {
|
||||
expect(link).toHaveAttribute('target', '_blank')
|
||||
expect(link).toHaveAttribute('rel', 'noopener noreferrer')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when item does not have position', () => {
|
||||
it('does not render anything', () => {
|
||||
const wrapper = render(<RoutingLinksView item={itemWithoutPosition} />)
|
||||
expect(wrapper.container.firstChild).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('when item has null position', () => {
|
||||
it('does not render anything', () => {
|
||||
const wrapper = render(<RoutingLinksView item={itemWithNullPosition} />)
|
||||
expect(wrapper.container.firstChild).toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,52 @@
|
||||
import MapPinIcon from '@heroicons/react/24/outline/MapPinIcon'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const RoutingLinksView = ({ item }: { item: Item }) => {
|
||||
// Only show if item has position coordinates
|
||||
if (!item.position?.coordinates) return null
|
||||
|
||||
const [longitude, latitude] = item.position.coordinates
|
||||
|
||||
const routingServices = [
|
||||
{
|
||||
name: 'Google Maps',
|
||||
url: `https://www.google.com/maps/dir/?api=1&destination=${latitude},${longitude}`,
|
||||
icon: '🗺️',
|
||||
},
|
||||
{
|
||||
name: 'Apple Maps',
|
||||
url: `https://maps.apple.com/?daddr=${latitude},${longitude}`,
|
||||
icon: '🍎',
|
||||
},
|
||||
{
|
||||
name: 'OpenStreetMap',
|
||||
url: `https://www.openstreetmap.org/directions?to=${latitude},${longitude}`,
|
||||
icon: '🌍',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className='tw:bg-base-200 tw:mb-6 tw:mt-6 tw:p-6'>
|
||||
<h2 className='tw:text-lg tw:font-semibold tw:inline-flex tw:items-center'>
|
||||
<MapPinIcon className='tw:w-5 tw:h-5 tw:mr-2' />
|
||||
Get Directions
|
||||
</h2>
|
||||
<div className='tw:mt-4 tw:space-y-2'>
|
||||
{routingServices.map((service) => (
|
||||
<div key={service.name}>
|
||||
<a
|
||||
href={service.url}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='tw:text-green-500 tw:inline-flex tw:items-center tw:hover:text-green-600 tw:transition-colors'
|
||||
>
|
||||
<span className='tw:mr-2 tw:text-lg'>{service.icon}</span>
|
||||
{service.name}
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -8,6 +8,7 @@ import { InviteLinkView } from '#components/Profile/Subcomponents/InviteLinkView
|
||||
import { ProfileStartEndView } from '#components/Profile/Subcomponents/ProfileStartEndView'
|
||||
import { ProfileTextView } from '#components/Profile/Subcomponents/ProfileTextView'
|
||||
import { RelationsView } from '#components/Profile/Subcomponents/RelationsView'
|
||||
import { RoutingLinksView } from '#components/Profile/Subcomponents/RoutingLinksView'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { Key } from 'react'
|
||||
@ -21,6 +22,7 @@ const componentMap = {
|
||||
crowdfundings: CrowdfundingView,
|
||||
inviteLinks: InviteLinkView,
|
||||
relations: RelationsView,
|
||||
routingLinks: RoutingLinksView,
|
||||
// weitere Komponenten hier
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
import { TextView } from '#components/Map/Subcomponents/ItemPopupComponents'
|
||||
import { ContactInfoView } from '#components/Profile/Subcomponents/ContactInfoView'
|
||||
import { GroupSubHeaderView } from '#components/Profile/Subcomponents/GroupSubHeaderView'
|
||||
import { RoutingLinksView } from '#components/Profile/Subcomponents/RoutingLinksView'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
@ -13,6 +14,7 @@ export const OnepagerView = ({ item }: { item: Item }) => {
|
||||
shareBaseUrl={`https://www.wuerdekompass.org/aktivitaeten/gruppensuche/#/gruppe/${item.slug}`}
|
||||
/>
|
||||
{item.user_created?.first_name && <ContactInfoView heading='Du hast Fragen?' item={item} />}
|
||||
<RoutingLinksView item={item} />
|
||||
{/* Description Section */}
|
||||
<div className='tw:my-10 tw:mt-2 tw:px-6 tw:text-sm '>
|
||||
<TextView itemId={item.id} rawText={item.text ?? 'Keine Beschreibung vorhanden'} />
|
||||
|
||||
@ -15,6 +15,7 @@ import { useItems } from '#components/Map/hooks/useItems'
|
||||
import { StartEndView, TextView } from '#components/Map/Subcomponents/ItemPopupComponents'
|
||||
import { ActionButton } from '#components/Profile/Subcomponents/ActionsButton'
|
||||
import { LinkedItemsHeaderView } from '#components/Profile/Subcomponents/LinkedItemsHeaderView'
|
||||
import { RoutingLinksView } from '#components/Profile/Subcomponents/RoutingLinksView'
|
||||
import { TagView } from '#components/Templates/TagView'
|
||||
import { timeAgo } from '#utils/TimeAgo'
|
||||
|
||||
@ -107,6 +108,9 @@ export const TabsView = ({
|
||||
<TextView text={item.text} itemId={item.id} />
|
||||
<div className='tw:h-4'></div>
|
||||
<TextView text={item.contact} itemId={item.id} />
|
||||
<div className='tw:px-6'>
|
||||
<RoutingLinksView item={item} />
|
||||
</div>
|
||||
</div>
|
||||
{item.layer?.itemType.questlog && (
|
||||
<>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user