Spaces:
Running
Running
import { useState } from "react"; | |
import { useLocation } from "@/hooks/use-location"; | |
import { Button } from "@/components/ui/button"; | |
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; | |
import { Input } from "@/components/ui/input"; | |
import { Label } from "@/components/ui/label"; | |
import { Separator } from "@/components/ui/separator"; | |
import { MapPin, ExternalLink, Copy, Share, Loader2, AlertCircle } from "lucide-react"; | |
import { useToast } from "@/hooks/use-toast"; | |
interface LocationDetectorProps { | |
onLocationDetected: (locationData: { | |
latitude: number; | |
longitude: number; | |
googleMapsUrl: string; | |
locationDetectedAutomatically: boolean; | |
}) => void; | |
onManualLocationSubmit: (manualData: { | |
street: string; | |
city: string; | |
state: string; | |
pinCode: string; | |
country: string; | |
locationDetectedAutomatically: false; | |
}) => void; | |
} | |
export const LocationDetector: React.FC<LocationDetectorProps> = ({ | |
onLocationDetected, | |
onManualLocationSubmit | |
}) => { | |
const { location, error, isLoading, getLocation, copyToClipboard } = useLocation(); | |
const { toast } = useToast(); | |
const [showManualEntry, setShowManualEntry] = useState(false); | |
const [manualLocation, setManualLocation] = useState({ | |
street: "", | |
city: "", | |
state: "", | |
pinCode: "", | |
country: "" | |
}); | |
const handleGetLocation = () => { | |
getLocation(); | |
}; | |
const handleUseDetectedLocation = () => { | |
if (location) { | |
onLocationDetected({ | |
latitude: location.latitude, | |
longitude: location.longitude, | |
googleMapsUrl: location.googleMapsUrl, | |
locationDetectedAutomatically: true | |
}); | |
toast({ | |
title: "Location detected!", | |
description: "Your location has been automatically detected and saved." | |
}); | |
} | |
}; | |
const handleCopyUrl = async () => { | |
if (location) { | |
try { | |
await copyToClipboard(location.googleMapsUrl); | |
toast({ | |
title: "Link copied!", | |
description: "Google Maps link has been copied to your clipboard." | |
}); | |
} catch (err) { | |
toast({ | |
variant: "destructive", | |
title: "Failed to copy", | |
description: "Could not copy the link to clipboard." | |
}); | |
} | |
} | |
}; | |
const handleManualSubmit = () => { | |
if (!manualLocation.street || !manualLocation.city || !manualLocation.state || | |
!manualLocation.pinCode || !manualLocation.country) { | |
toast({ | |
variant: "destructive", | |
title: "Missing information", | |
description: "Please fill in all required fields." | |
}); | |
return; | |
} | |
onManualLocationSubmit({ | |
...manualLocation, | |
locationDetectedAutomatically: false | |
}); | |
toast({ | |
title: "Manual location saved!", | |
description: "Your location information has been saved." | |
}); | |
}; | |
return ( | |
<Card className="w-full max-w-md mx-auto glass-card border-0"> | |
<CardHeader> | |
<CardTitle className="flex items-center gap-2"> | |
<MapPin className="h-5 w-5 text-primary" /> | |
Location Detection | |
</CardTitle> | |
</CardHeader> | |
<CardContent className="space-y-4"> | |
{/* Automatic Location Detection */} | |
<div className="space-y-3"> | |
<Button | |
onClick={handleGetLocation} | |
disabled={isLoading} | |
className="w-full" | |
data-testid="button-get-location" | |
> | |
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} | |
<MapPin className="mr-2 h-4 w-4" /> | |
{isLoading ? "Getting Location..." : "Get My Location Automatically"} | |
</Button> | |
{location && ( | |
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg space-y-3" data-testid="location-detected"> | |
<div className="text-sm"> | |
<p className="font-semibold text-green-700 dark:text-green-300"> | |
Location detected successfully! | |
</p> | |
<p className="text-green-600 dark:text-green-400" data-testid="coordinates"> | |
Latitude: {location.latitude.toFixed(6)}, Longitude: {location.longitude.toFixed(6)} | |
</p> | |
{location.accuracy && ( | |
<p className="text-xs text-green-600 dark:text-green-400"> | |
Accuracy: ±{Math.round(location.accuracy)} meters | |
</p> | |
)} | |
</div> | |
<div className="flex gap-2"> | |
<Button | |
size="sm" | |
onClick={() => window.open(location.googleMapsUrl, '_blank')} | |
data-testid="button-open-maps" | |
> | |
<ExternalLink className="mr-1 h-3 w-3" /> | |
Open in Maps | |
</Button> | |
<Button | |
size="sm" | |
variant="outline" | |
onClick={handleCopyUrl} | |
data-testid="button-copy-link" | |
> | |
<Copy className="mr-1 h-3 w-3" /> | |
Copy Link | |
</Button> | |
</div> | |
<Button | |
onClick={handleUseDetectedLocation} | |
className="w-full" | |
data-testid="button-use-location" | |
> | |
Use This Location | |
</Button> | |
</div> | |
)} | |
{error && ( | |
<div className="p-4 bg-red-50 dark:bg-red-900/20 rounded-lg" data-testid="location-error"> | |
<div className="flex items-start gap-2"> | |
<AlertCircle className="h-4 w-4 text-red-500 mt-0.5" /> | |
<div className="text-sm"> | |
<p className="font-semibold text-red-700 dark:text-red-300"> | |
Location Detection Failed | |
</p> | |
<p className="text-red-600 dark:text-red-400">{error.message}</p> | |
</div> | |
</div> | |
</div> | |
)} | |
</div> | |
<div className="flex items-center gap-2"> | |
<Separator className="flex-1" /> | |
<span className="text-xs text-muted-foreground">OR</span> | |
<Separator className="flex-1" /> | |
</div> | |
{/* Manual Entry Option */} | |
<div className="space-y-3"> | |
{!showManualEntry ? ( | |
<Button | |
variant="outline" | |
onClick={() => setShowManualEntry(true)} | |
className="w-full" | |
data-testid="button-manual-entry" | |
> | |
Enter Location Manually | |
</Button> | |
) : ( | |
<div className="space-y-3" data-testid="manual-entry-form"> | |
<h4 className="text-sm font-semibold">Manual Location Entry</h4> | |
<div className="grid grid-cols-1 gap-3"> | |
<div> | |
<Label htmlFor="street">Street Address</Label> | |
<Input | |
id="street" | |
value={manualLocation.street} | |
onChange={(e) => setManualLocation(prev => ({...prev, street: e.target.value}))} | |
placeholder="Enter street address" | |
data-testid="input-street" | |
/> | |
</div> | |
<div className="grid grid-cols-2 gap-2"> | |
<div> | |
<Label htmlFor="city">City</Label> | |
<Input | |
id="city" | |
value={manualLocation.city} | |
onChange={(e) => setManualLocation(prev => ({...prev, city: e.target.value}))} | |
placeholder="City" | |
data-testid="input-city" | |
/> | |
</div> | |
<div> | |
<Label htmlFor="state">State</Label> | |
<Input | |
id="state" | |
value={manualLocation.state} | |
onChange={(e) => setManualLocation(prev => ({...prev, state: e.target.value}))} | |
placeholder="State" | |
data-testid="input-state" | |
/> | |
</div> | |
</div> | |
<div className="grid grid-cols-2 gap-2"> | |
<div> | |
<Label htmlFor="pinCode">PIN Code</Label> | |
<Input | |
id="pinCode" | |
value={manualLocation.pinCode} | |
onChange={(e) => setManualLocation(prev => ({...prev, pinCode: e.target.value}))} | |
placeholder="PIN Code" | |
data-testid="input-pincode" | |
/> | |
</div> | |
<div> | |
<Label htmlFor="country">Country</Label> | |
<Input | |
id="country" | |
value={manualLocation.country} | |
onChange={(e) => setManualLocation(prev => ({...prev, country: e.target.value}))} | |
placeholder="Country" | |
data-testid="input-country" | |
/> | |
</div> | |
</div> | |
</div> | |
<div className="flex gap-2"> | |
<Button | |
onClick={handleManualSubmit} | |
className="flex-1" | |
data-testid="button-submit-manual" | |
> | |
Use Manual Location | |
</Button> | |
<Button | |
variant="outline" | |
onClick={() => setShowManualEntry(false)} | |
data-testid="button-cancel-manual" | |
> | |
Cancel | |
</Button> | |
</div> | |
</div> | |
)} | |
</div> | |
</CardContent> | |
</Card> | |
); | |
}; |