From a3da7e0ab32ba145903fc07d9382d137267494a0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 5 Sep 2025 23:19:35 +0000
Subject: [PATCH] Add routing service links to item profiles
Co-authored-by: antontranelis <31516529+antontranelis@users.noreply.github.com>
---
.../Subcomponents/RoutingLinksView.spec.tsx | 93 +++++++++++++++++++
.../Subcomponents/RoutingLinksView.tsx | 52 +++++++++++
.../Components/Profile/Templates/FlexView.tsx | 2 +
.../Profile/Templates/OnepagerView.tsx | 2 +
.../Components/Profile/Templates/TabsView.tsx | 4 +
5 files changed, 153 insertions(+)
create mode 100644 lib/src/Components/Profile/Subcomponents/RoutingLinksView.spec.tsx
create mode 100644 lib/src/Components/Profile/Subcomponents/RoutingLinksView.tsx
diff --git a/lib/src/Components/Profile/Subcomponents/RoutingLinksView.spec.tsx b/lib/src/Components/Profile/Subcomponents/RoutingLinksView.spec.tsx
new file mode 100644
index 00000000..41f281e3
--- /dev/null
+++ b/lib/src/Components/Profile/Subcomponents/RoutingLinksView.spec.tsx
@@ -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('', () => {
+ describe('when item has position coordinates', () => {
+ it('renders routing service links', () => {
+ const wrapper = render()
+
+ 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()
+ 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()
+ 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()
+ 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()
+ 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()
+ expect(wrapper.container.firstChild).toBeNull()
+ })
+ })
+
+ describe('when item has null position', () => {
+ it('does not render anything', () => {
+ const wrapper = render()
+ expect(wrapper.container.firstChild).toBeNull()
+ })
+ })
+})
diff --git a/lib/src/Components/Profile/Subcomponents/RoutingLinksView.tsx b/lib/src/Components/Profile/Subcomponents/RoutingLinksView.tsx
new file mode 100644
index 00000000..f2a1753a
--- /dev/null
+++ b/lib/src/Components/Profile/Subcomponents/RoutingLinksView.tsx
@@ -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 (
+
+
+
+ Get Directions
+
+
+ {routingServices.map((service) => (
+
+ ))}
+
+
+ )
+}
diff --git a/lib/src/Components/Profile/Templates/FlexView.tsx b/lib/src/Components/Profile/Templates/FlexView.tsx
index e3024c45..71e3d60c 100644
--- a/lib/src/Components/Profile/Templates/FlexView.tsx
+++ b/lib/src/Components/Profile/Templates/FlexView.tsx
@@ -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
}
diff --git a/lib/src/Components/Profile/Templates/OnepagerView.tsx b/lib/src/Components/Profile/Templates/OnepagerView.tsx
index 7c2d8077..adb72089 100644
--- a/lib/src/Components/Profile/Templates/OnepagerView.tsx
+++ b/lib/src/Components/Profile/Templates/OnepagerView.tsx
@@ -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 && }
+
{/* Description Section */}
diff --git a/lib/src/Components/Profile/Templates/TabsView.tsx b/lib/src/Components/Profile/Templates/TabsView.tsx
index 0844f8fa..f1250e6e 100644
--- a/lib/src/Components/Profile/Templates/TabsView.tsx
+++ b/lib/src/Components/Profile/Templates/TabsView.tsx
@@ -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 = ({
+
+
+
{item.layer?.itemType.questlog && (
<>