diff --git a/backend/directus-config/development/snapshot/fields/types/Header.json b/backend/directus-config/development/snapshot/fields/types/Header.json new file mode 100644 index 00000000..c0bc8b9e --- /dev/null +++ b/backend/directus-config/development/snapshot/fields/types/Header.json @@ -0,0 +1,31 @@ +{ + "collection": "types", + "field": "Header", + "type": "alias", + "meta": { + "collection": "types", + "conditions": null, + "display": null, + "display_options": null, + "field": "Header", + "group": null, + "hidden": false, + "interface": "group-detail", + "note": null, + "options": { + "start": "closed" + }, + "readonly": false, + "required": false, + "sort": 7, + "special": [ + "alias", + "no-data", + "group" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + } +} diff --git a/backend/directus-config/development/snapshot/fields/types/cta_button_label.json b/backend/directus-config/development/snapshot/fields/types/cta_button_label.json new file mode 100644 index 00000000..88994e8b --- /dev/null +++ b/backend/directus-config/development/snapshot/fields/types/cta_button_label.json @@ -0,0 +1,43 @@ +{ + "collection": "types", + "field": "cta_button_label", + "type": "string", + "meta": { + "collection": "types", + "conditions": null, + "display": null, + "display_options": null, + "field": "cta_button_label", + "group": "Header", + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": false, + "sort": 1, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "cta_button_label", + "table": "types", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/backend/directus-config/development/snapshot/fields/types/show_address.json b/backend/directus-config/development/snapshot/fields/types/show_address.json new file mode 100644 index 00000000..2522c095 --- /dev/null +++ b/backend/directus-config/development/snapshot/fields/types/show_address.json @@ -0,0 +1,45 @@ +{ + "collection": "types", + "field": "show_address", + "type": "boolean", + "meta": { + "collection": "types", + "conditions": null, + "display": null, + "display_options": null, + "field": "show_address", + "group": "Header", + "hidden": false, + "interface": "boolean", + "note": null, + "options": null, + "readonly": false, + "required": false, + "sort": 2, + "special": [ + "cast-boolean" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "show_address", + "table": "types", + "data_type": "boolean", + "default_value": true, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/lib/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx b/lib/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx index 70bb62b9..6dba16e3 100644 --- a/lib/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx +++ b/lib/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx @@ -11,12 +11,13 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import EllipsisVerticalIcon from '@heroicons/react/16/solid/EllipsisVerticalIcon' -import { MapPinIcon, ShareIcon } from '@heroicons/react/24/solid' +import { MapPinIcon, ShareIcon, QrCodeIcon } from '@heroicons/react/24/solid' import PencilIcon from '@heroicons/react/24/solid/PencilIcon' import TrashIcon from '@heroicons/react/24/solid/TrashIcon' import { useState } from 'react' import { LuNavigation } from 'react-icons/lu' import SVG from 'react-inlinesvg' +import QRCode from 'react-qr-code' import { useNavigate } from 'react-router-dom' import { toast } from 'react-toastify' @@ -63,6 +64,7 @@ export function HeaderView({ showAddress?: boolean }) { const [modalOpen, setModalOpen] = useState(false) + const [qrModalOpen, setQrModalOpen] = useState(false) const hasUserPermission = useHasUserPermission() const navigate = useNavigate() @@ -80,6 +82,7 @@ export function HeaderView({ const { address } = useReverseGeocode( item?.position?.coordinates as [number, number] | undefined, showAddress, + 'municipality', ) const params = new URLSearchParams(window.location.search) @@ -130,10 +133,14 @@ export function HeaderView({ 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 copyLink = () => { navigator.clipboard - .writeText(shareUrl) + .writeText(inviteLink) .then(() => { toast.success('Link copied to clipboard') return null @@ -221,6 +228,7 @@ export function HeaderView({ {address} + {distance && distance >= 1 && ` (${formatDistance(distance)})`} )} @@ -303,18 +311,7 @@ export function HeaderView({ {big && (
-
- {' '} - {formatDistance(distance) && ( - - {formatDistance(distance)} - - )} -
+
)} +
@@ -432,6 +436,32 @@ export function HeaderView({
+ + setQrModalOpen(false)} + className='tw:w-[calc(100vw-2rem)] tw:max-w-96' + > +
e.stopPropagation()} className='tw:text-center tw:p-4'> +

Share your profile with others to expand your network.

+ +
+ +
+ +
+ {inviteLink} + +
+
+
) } diff --git a/lib/src/Components/Map/hooks/useReverseGeocode.ts b/lib/src/Components/Map/hooks/useReverseGeocode.ts index fba253bc..d820c589 100644 --- a/lib/src/Components/Map/hooks/useReverseGeocode.ts +++ b/lib/src/Components/Map/hooks/useReverseGeocode.ts @@ -7,6 +7,11 @@ interface GeocodeResult { city?: string town?: string village?: string + district?: string + suburb?: string + neighbourhood?: string + state?: string + country?: string } interface GeocodeFeature { @@ -17,7 +22,11 @@ interface GeocodeResponse { features?: GeocodeFeature[] } -export function useReverseGeocode(coordinates?: [number, number] | null, enabled: boolean = true) { +export function useReverseGeocode( + coordinates?: [number, number] | null, + enabled: boolean = true, + accuracy: 'municipality' | 'street' | 'house_number' = 'municipality' +) { const [address, setAddress] = useState('') const [loading, setLoading] = useState(false) const [error, setError] = useState(null) @@ -50,28 +59,33 @@ export function useReverseGeocode(coordinates?: [number, number] | null, enabled if (data.features && data.features.length > 0) { const props = data.features[0].properties - const parts: string[] = [] + const municipality = props.city || props.town || props.village - // Straße und Hausnummer zusammen - if (props.street) { - const streetPart = props.housenumber - ? `${props.street} ${props.housenumber}` - : props.street - parts.push(streetPart) - } else if (props.housenumber) { - parts.push(props.housenumber) + let addressString = '' + + switch (accuracy) { + case 'municipality': + addressString = municipality || '' + break + case 'street': + if (props.street && municipality) { + addressString = `${props.street}, ${municipality}` + } else { + addressString = municipality || '' + } + break + case 'house_number': + if (props.street && props.housenumber && municipality) { + addressString = `${props.street} ${props.housenumber}, ${municipality}` + } else if (props.street && municipality) { + addressString = `${props.street}, ${municipality}` + } else { + addressString = municipality || '' + } + break } - // PLZ und Ort zusammen - if (props.postcode || props.city || props.town || props.village) { - const locationPart = [ - props.postcode, - props.city || props.town || props.village - ].filter(Boolean).join(' ') - if (locationPart) parts.push(locationPart) - } - - setAddress(parts.join(', ')) + setAddress(addressString) } else { setAddress('') } @@ -84,7 +98,7 @@ export function useReverseGeocode(coordinates?: [number, number] | null, enabled } void reverseGeocode() - }, [coordinates, enabled]) + }, [coordinates, enabled, accuracy]) return { address, loading, error } } \ No newline at end of file diff --git a/lib/src/Components/Templates/DialogModal.tsx b/lib/src/Components/Templates/DialogModal.tsx index a0e87c26..ef90afae 100644 --- a/lib/src/Components/Templates/DialogModal.tsx +++ b/lib/src/Components/Templates/DialogModal.tsx @@ -9,7 +9,7 @@ const isClickInsideRectangle = (e: MouseEvent, element: HTMLElement) => { } interface Props { - title: string + title?: string isOpened: boolean onClose: () => void children: React.ReactNode @@ -52,7 +52,9 @@ const DialogModal = ({ } >
-

{title}

+ {title && ( +

{title}

+ )} {children} {showCloseButton && (