loction working
This commit is contained in:
parent
af33ba0527
commit
9b0996b18f
@ -1,45 +1,55 @@
|
||||
"use client";
|
||||
import { useMemo } from "react";
|
||||
import usePlacesAutocomplete from "use-places-autocomplete";
|
||||
import { useMemo, useState } from "react";
|
||||
import usePlacesAutocomplete, {
|
||||
getGeocode,
|
||||
getLatLng,
|
||||
} from "use-places-autocomplete";
|
||||
import { Input } from "../ui/input";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { HiOutlineBuildingStorefront } from "react-icons/hi2";
|
||||
import {
|
||||
HiOutlineBuildingStorefront,
|
||||
HiOutlineMapPin,
|
||||
HiOutlineHomeModern,
|
||||
HiOutlineFilm,
|
||||
HiOutlineShoppingBag,
|
||||
HiOutlineBuildingLibrary,
|
||||
} from "react-icons/hi2";
|
||||
import { RxMagnifyingGlass } from "react-icons/rx";
|
||||
import { LiaGlassMartiniSolid } from "react-icons/lia";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandItem,
|
||||
CommandInput,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
} from "@/components/ui/command";
|
||||
import { useLoadScript } from "@react-google-maps/api";
|
||||
import Spinner from "../spinner";
|
||||
|
||||
export default function LocationSearchInput() {
|
||||
type LocationSearchInputProps = {
|
||||
onSelect: (location: {
|
||||
name: string;
|
||||
address: string;
|
||||
coordinates: { lat: number; lng: number };
|
||||
}) => void;
|
||||
location?: {
|
||||
name: string;
|
||||
address: string;
|
||||
coordinates: { lat: number; lng: number };
|
||||
};
|
||||
};
|
||||
export default function LocationSearchInput({
|
||||
onSelect,
|
||||
location,
|
||||
}: LocationSearchInputProps) {
|
||||
console.log("LOCATION", location);
|
||||
const libraries = useMemo(() => ["places"], []);
|
||||
const { isLoaded, loadError } = useLoadScript({
|
||||
googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_KEY as string,
|
||||
libraries: libraries as any,
|
||||
});
|
||||
const {
|
||||
ready,
|
||||
value,
|
||||
suggestions: { status, data }, // results from Google Places API for the given search term
|
||||
setValue, // use this method to link input value with the autocomplete hook
|
||||
clearSuggestions,
|
||||
} = usePlacesAutocomplete({
|
||||
requestOptions: { componentRestrictions: { country: "us" } }, // restrict search to US
|
||||
debounce: 300,
|
||||
cache: 86400,
|
||||
});
|
||||
|
||||
if (loadError) {
|
||||
return <p>hello{JSON.stringify(loadError)}</p>;
|
||||
return <p>{JSON.stringify(loadError)}</p>;
|
||||
}
|
||||
if (!isLoaded) {
|
||||
return (
|
||||
@ -48,80 +58,155 @@ export default function LocationSearchInput() {
|
||||
disabled={true}
|
||||
className={cn(
|
||||
"justify-start p-0 text-left font-normal",
|
||||
!value && "text-muted-foreground",
|
||||
"text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
Add a location...
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return <CommandSearch />;
|
||||
return <CommandSearch location={location} onSelect={onSelect} />;
|
||||
}
|
||||
function CommandSearch() {
|
||||
|
||||
type CommandSearchProps = {
|
||||
onSelect: (location: {
|
||||
name: string;
|
||||
address: string;
|
||||
coordinates: { lat: number; lng: number };
|
||||
}) => void;
|
||||
location?: {
|
||||
name: string;
|
||||
address: string;
|
||||
coordinates: { lat: number; lng: number };
|
||||
};
|
||||
};
|
||||
function CommandSearch({ location, onSelect }: CommandSearchProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const {
|
||||
ready,
|
||||
value,
|
||||
suggestions: { status, data }, // results from Google Places API for the given search term
|
||||
suggestions: { status, data, loading }, // results from Google Places API for the given search term
|
||||
setValue, // use this method to link input value with the autocomplete hook
|
||||
clearSuggestions,
|
||||
} = usePlacesAutocomplete({
|
||||
requestOptions: { componentRestrictions: { country: "us" } }, // restrict search to US
|
||||
requestOptions: {},
|
||||
debounce: 300,
|
||||
cache: 86400,
|
||||
});
|
||||
async function handleSelect(placeId: string, name: string) {
|
||||
const result = await getGeocode({
|
||||
placeId,
|
||||
});
|
||||
if (!result[0]) return;
|
||||
const coordinates = getLatLng(result[0]);
|
||||
setOpen(false);
|
||||
return onSelect({
|
||||
name,
|
||||
coordinates,
|
||||
address: result[0].formatted_address,
|
||||
});
|
||||
}
|
||||
return (
|
||||
<Popover>
|
||||
<Popover open={open}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
onClick={() => setOpen(true)}
|
||||
variant={"ghost"}
|
||||
className={cn(
|
||||
"justify-start p-0 text-left font-normal",
|
||||
"w-full justify-start p-0 pl-2 text-left font-normal",
|
||||
!value && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
Add a location...
|
||||
{location ? (
|
||||
<div className="flex max-w-full items-baseline gap-x-2">
|
||||
{location.name}
|
||||
<span className="truncate text-xs text-muted-foreground">
|
||||
{location.address}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
"Add a location..."
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="z-modal+ w-auto max-w-[300px] p-0"
|
||||
className="z-modal+ w-[280px] max-w-[300px] p-0"
|
||||
align="start"
|
||||
>
|
||||
<div className="rounded-lg border shadow-md">
|
||||
<Input
|
||||
className="border-0 shadow-none focus-visible:ring-0"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="Search places..."
|
||||
disabled={!ready}
|
||||
/>
|
||||
<div className="relative flex">
|
||||
<Input
|
||||
className="border-0 pl-[30px] shadow-none outline-none focus-visible:ring-0"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="Search places..."
|
||||
disabled={!ready}
|
||||
/>
|
||||
<div className="center absolute inset-y-0 left-[8px]">
|
||||
<RxMagnifyingGlass className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
<ul className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden")}>
|
||||
{data.map((place) => {
|
||||
const {
|
||||
description,
|
||||
place_id,
|
||||
structured_formatting: { main_text, secondary_text },
|
||||
types,
|
||||
} = place;
|
||||
return (
|
||||
<li
|
||||
key={place_id}
|
||||
onClick={() => console.log(place)}
|
||||
onClick={() => handleSelect(place_id, main_text)}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 hover:bg-muted",
|
||||
"relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 hover:bg-muted",
|
||||
)}
|
||||
>
|
||||
<HiOutlineBuildingStorefront className="mr-2 h-4 w-4" />
|
||||
<div className="line-clamp-1">
|
||||
<p>{main_text}</p>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
<RenderIcon types={types} className="mr-2 h-4 w-4" />
|
||||
<div className="flex-1 space-y-0.5">
|
||||
<p className="line-clamp-1">{main_text}</p>
|
||||
<span className=" line-clamp-1 text-[10px] leading-none text-muted-foreground">
|
||||
{secondary_text}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{data.length === 0 && status === "ZERO_RESULTS" && (
|
||||
<div className="p-3 text-center">
|
||||
<p>No results found</p>
|
||||
</div>
|
||||
)}
|
||||
{loading && (
|
||||
<div className="center p-3 text-primary">
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
function RenderIcon({
|
||||
types,
|
||||
className,
|
||||
}: {
|
||||
types: string[];
|
||||
className?: string;
|
||||
}) {
|
||||
if (types.includes("restaurant")) {
|
||||
return <HiOutlineBuildingStorefront className={className} />;
|
||||
} else if (types.includes("art_gallery")) {
|
||||
return <HiOutlineHomeModern className={className} />;
|
||||
} else if (types.includes("bar")) {
|
||||
return <LiaGlassMartiniSolid className={className} />;
|
||||
} else if (types.includes("city_hall")) {
|
||||
return <HiOutlineBuildingLibrary className={className} />;
|
||||
} else if (types.includes("store")) {
|
||||
return <HiOutlineShoppingBag className={className} />;
|
||||
} else if (types.includes("movie_theater")) {
|
||||
return <HiOutlineFilm className={className} />;
|
||||
}
|
||||
return <HiOutlineMapPin className={className} />;
|
||||
}
|
||||
|
@ -49,6 +49,11 @@ export default function CreateCalendarEventModal() {
|
||||
const [timezone, setTimezone] = useState(
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
);
|
||||
const [location, setLocation] = useState<{
|
||||
address: string;
|
||||
name: string;
|
||||
coordinates: { lat: number; lng: number };
|
||||
}>();
|
||||
|
||||
useEffect(() => {
|
||||
if (startDate && endDate) {
|
||||
@ -93,7 +98,7 @@ export default function CreateCalendarEventModal() {
|
||||
<SmallCalendarIcon date={startDate ?? new Date()} />
|
||||
</div>
|
||||
<div className="max-w-[300px] flex-1 divide-y overflow-hidden rounded-md bg-muted">
|
||||
<div className="flex justify-between p-0.5 px-1 pl-3">
|
||||
<div className="flex justify-between p-0.5 px-2 pl-3">
|
||||
<div className="flex w-[70px] shrink-0 items-center">Start</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex max-w-full bg-secondary">
|
||||
@ -132,7 +137,7 @@ export default function CreateCalendarEventModal() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between p-0.5 px-1 pl-3">
|
||||
<div className="flex justify-between p-0.5 px-2 pl-3">
|
||||
<div className="flex w-[70px] shrink-0 items-center">End</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex max-w-full bg-secondary">
|
||||
@ -172,7 +177,7 @@ export default function CreateCalendarEventModal() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between p-0.5 px-1 pl-3">
|
||||
<div className="flex justify-between overflow-hidden p-0.5 px-1 pl-3">
|
||||
<div className="flex-1 text-xs text-muted-foreground">
|
||||
<div className="flex max-w-full justify-start bg-secondary">
|
||||
<TimezoneSelector
|
||||
@ -191,10 +196,13 @@ export default function CreateCalendarEventModal() {
|
||||
<LocationIcon />
|
||||
</div>
|
||||
<div className="max-w-[300px] flex-1 divide-y overflow-hidden rounded-md bg-muted">
|
||||
<div className="flex justify-between p-0.5 px-1 pl-3">
|
||||
<div className="flex justify-between p-0.5 px-1">
|
||||
<div className="flex-1">
|
||||
<div className="flex max-w-full bg-secondary">
|
||||
<LocationSearchInput />
|
||||
<LocationSearchInput
|
||||
location={location}
|
||||
onSelect={(l) => setLocation(l)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user