mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2026-03-01 12:44:17 +00:00
added qr code dialog, optimized address
This commit is contained in:
parent
085be8cf0e
commit
5b958a26ff
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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<boolean>(false)
|
||||
const [qrModalOpen, setQrModalOpen] = useState<boolean>(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({
|
||||
<MapPinIcon className='tw:w-4 tw:mr-1 tw:flex-shrink-0' />
|
||||
<span title={address} className='tw:truncate'>
|
||||
{address}
|
||||
{distance && distance >= 1 && ` (${formatDistance(distance)})`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@ -303,18 +311,7 @@ export function HeaderView({
|
||||
</div>
|
||||
{big && (
|
||||
<div className='tw:flex tw:row tw:mt-2 '>
|
||||
<div className='tw:w-16 tw:text-center tw:font-bold tw:text-primary '>
|
||||
{' '}
|
||||
{formatDistance(distance) && (
|
||||
<span
|
||||
style={{
|
||||
color: `${item?.color ?? (item && (getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : (item?.layer?.markerDefaultColor ?? '#000')))}`,
|
||||
}}
|
||||
>
|
||||
{formatDistance(distance)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className='tw:w-16 tw:text-center tw:font-bold tw:text-primary '></div>
|
||||
<div className='tw:grow'></div>
|
||||
<div className=''>
|
||||
<button
|
||||
@ -341,6 +338,13 @@ export function HeaderView({
|
||||
<LuNavigation className='tw:h-4 tw:w-4' />
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setQrModalOpen(true)}
|
||||
className='tw:btn tw:mr-2 tw:px-3'
|
||||
title='QR-Code teilen'
|
||||
>
|
||||
<QrCodeIcon className='tw:h-4 tw:w-4' />
|
||||
</button>
|
||||
<div className='tw:dropdown tw:dropdown-end'>
|
||||
<div tabIndex={0} role='button' className='tw:btn tw:px-3'>
|
||||
<ShareIcon className='tw:w-4 tw:h-4' />
|
||||
@ -432,6 +436,32 @@ export function HeaderView({
|
||||
</div>
|
||||
</div>
|
||||
</DialogModal>
|
||||
|
||||
<DialogModal
|
||||
isOpened={qrModalOpen}
|
||||
showCloseButton={true}
|
||||
onClose={() => setQrModalOpen(false)}
|
||||
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>
|
||||
|
||||
<div className='tw:p-8 tw:my-8 tw:rounded-lg tw:inline-block tw:border-base-300 tw:border-2 '>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</DialogModal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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<string>('')
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<string | null>(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 }
|
||||
}
|
||||
@ -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 = ({
|
||||
}
|
||||
>
|
||||
<div className='tw:card-body tw:p-2'>
|
||||
<h2 className='tw:text-2xl tw:font-semibold tw:mb-2 tw:text-center'>{title}</h2>
|
||||
{title && (
|
||||
<h2 className='tw:text-2xl tw:font-semibold tw:mb-2 tw:text-center'>{title}</h2>
|
||||
)}
|
||||
|
||||
{children}
|
||||
{showCloseButton && (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user