Files
IdP/apps/frontend/components/chat/chat-location-share-dialog.tsx
lendry a4b4577c55 fix
2026-07-10 10:37:22 +03:00

175 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useCallback, useEffect, useState } from 'react';
import { Loader2, LocateFixed, MapPin, Send } from 'lucide-react';
import { AddressMapPicker } from '@/components/addresses/address-map-picker';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { reverseGeocode } from '@/lib/geocoding';
export interface ChatLocationSharePayload {
latitude: number;
longitude: number;
address?: string;
label?: string;
}
export function ChatLocationShareDialog({
open,
sending,
onOpenChange,
onSend,
onLocateError
}: {
open: boolean;
sending?: boolean;
onOpenChange: (open: boolean) => void;
onSend: (payload: ChatLocationSharePayload) => Promise<void>;
onLocateError?: (message: string) => void;
}) {
const [latitude, setLatitude] = useState<number | undefined>();
const [longitude, setLongitude] = useState<number | undefined>();
const [address, setAddress] = useState('');
const [label, setLabel] = useState('');
const [locating, setLocating] = useState(false);
const [resolvingAddress, setResolvingAddress] = useState(false);
const resetState = useCallback(() => {
setLatitude(undefined);
setLongitude(undefined);
setAddress('');
setLabel('');
setLocating(false);
setResolvingAddress(false);
}, []);
useEffect(() => {
if (!open) {
resetState();
}
}, [open, resetState]);
const handlePick = useCallback(async (lat: number, lng: number) => {
setLatitude(lat);
setLongitude(lng);
setResolvingAddress(true);
try {
const parsed = await reverseGeocode(lat, lng);
if (parsed?.fullAddress) {
setAddress(parsed.fullAddress);
}
} finally {
setResolvingAddress(false);
}
}, []);
async function handleUseCurrentLocation() {
if (!navigator.geolocation) return;
setLocating(true);
try {
const position = await new Promise<GeolocationPosition>((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject, {
enableHighAccuracy: true,
timeout: 12_000,
maximumAge: 30_000
});
});
await handlePick(position.coords.latitude, position.coords.longitude);
} catch {
// ошибка геолокации обрабатывается на уровне вызывающего через toast
throw new Error('Не удалось определить текущее местоположение');
} finally {
setLocating(false);
}
}
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
if (latitude == null || longitude == null) return;
await onSend({
latitude,
longitude,
address: address.trim() || undefined,
label: label.trim() || undefined
});
}
const canSend = latitude != null && longitude != null && !sending;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-[min(92vw,480px)]">
<DialogHeader>
<div className="mx-auto mb-2 flex h-12 w-12 items-center justify-center rounded-full bg-[#f4f5f8]">
<MapPin className="h-6 w-6 text-[#3390ec]" />
</div>
<DialogTitle className="text-center">Отправить геопозицию</DialogTitle>
<p className="text-center text-sm text-[#6b6f7b]">
Выберите точку на карте или используйте текущее местоположение
</p>
</DialogHeader>
<form onSubmit={(event) => void handleSubmit(event)} className="space-y-3">
<div className="overflow-hidden rounded-2xl border border-[#dce3ec]">
<AddressMapPicker latitude={latitude} longitude={longitude} onPick={(lat, lng) => void handlePick(lat, lng)} />
</div>
<div className="flex flex-wrap gap-2">
<Button
type="button"
variant="outline"
className="rounded-xl"
disabled={locating || sending}
onClick={() => {
void handleUseCurrentLocation().catch((error) => {
onLocateError?.(error instanceof Error ? error.message : 'Не удалось определить текущее местоположение');
});
}}
>
{locating ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <LocateFixed className="mr-2 h-4 w-4" />}
Моё местоположение
</Button>
{resolvingAddress ? (
<span className="flex items-center text-xs text-[#667085]">
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
Определяем адрес...
</span>
) : null}
</div>
<Input
value={label}
onChange={(event) => setLabel(event.target.value)}
placeholder="Подпись (необязательно)"
className="h-11 rounded-xl"
disabled={sending}
/>
<Input
value={address}
onChange={(event) => setAddress(event.target.value)}
placeholder="Адрес"
className="h-11 rounded-xl"
disabled={sending || resolvingAddress}
/>
<Button type="submit" size="lg" className="w-full rounded-[18px]" disabled={!canSend}>
{sending ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Отправляем...
</>
) : (
<>
<Send className="mr-2 h-4 w-4" />
Отправить
</>
)}
</Button>
</form>
</DialogContent>
</Dialog>
);
}