Add routing service links to item profiles

Co-authored-by: antontranelis <31516529+antontranelis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-09-05 23:19:35 +00:00
parent 1219460691
commit a3da7e0ab3
5 changed files with 153 additions and 0 deletions

View File

@ -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()
})
})
})

View File

@ -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>
)
}

View File

@ -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
}

View File

@ -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'} />

View File

@ -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 && (
<>