loction working

This commit is contained in:
zmeyer44 2023-10-23 16:04:12 -04:00
parent af33ba0527
commit 9b0996b18f
2 changed files with 146 additions and 53 deletions

View File

@ -1,45 +1,55 @@
"use client"; "use client";
import { useMemo } from "react"; import { useMemo, useState } from "react";
import usePlacesAutocomplete from "use-places-autocomplete"; import usePlacesAutocomplete, {
getGeocode,
getLatLng,
} from "use-places-autocomplete";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
import { cn } from "@/lib/utils"; 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 { import {
Popover, Popover,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { Button } from "@/components/ui/button"; 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 { 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 libraries = useMemo(() => ["places"], []);
const { isLoaded, loadError } = useLoadScript({ const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_KEY as string, googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_KEY as string,
libraries: libraries as any, 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) { if (loadError) {
return <p>hello{JSON.stringify(loadError)}</p>; return <p>{JSON.stringify(loadError)}</p>;
} }
if (!isLoaded) { if (!isLoaded) {
return ( return (
@ -48,80 +58,155 @@ export default function LocationSearchInput() {
disabled={true} disabled={true}
className={cn( className={cn(
"justify-start p-0 text-left font-normal", "justify-start p-0 text-left font-normal",
!value && "text-muted-foreground", "text-muted-foreground",
)} )}
> >
Add a location... Add a location...
</Button> </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 { const {
ready, ready,
value, 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 setValue, // use this method to link input value with the autocomplete hook
clearSuggestions, clearSuggestions,
} = usePlacesAutocomplete({ } = usePlacesAutocomplete({
requestOptions: { componentRestrictions: { country: "us" } }, // restrict search to US requestOptions: {},
debounce: 300, debounce: 300,
cache: 86400, 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 ( return (
<Popover> <Popover open={open}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
onClick={() => setOpen(true)}
variant={"ghost"} variant={"ghost"}
className={cn( 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", !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> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent <PopoverContent
className="z-modal+ w-auto max-w-[300px] p-0" className="z-modal+ w-[280px] max-w-[300px] p-0"
align="start" align="start"
> >
<div className="rounded-lg border shadow-md"> <div className="rounded-lg border shadow-md">
<div className="relative flex">
<Input <Input
className="border-0 shadow-none focus-visible:ring-0" className="border-0 pl-[30px] shadow-none outline-none focus-visible:ring-0"
value={value} value={value}
onChange={(e) => setValue(e.target.value)} onChange={(e) => setValue(e.target.value)}
placeholder="Search places..." placeholder="Search places..."
disabled={!ready} 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")}> <ul className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden")}>
{data.map((place) => { {data.map((place) => {
const { const {
description,
place_id, place_id,
structured_formatting: { main_text, secondary_text }, structured_formatting: { main_text, secondary_text },
types,
} = place; } = place;
return ( return (
<li <li
key={place_id} key={place_id}
onClick={() => console.log(place)} onClick={() => handleSelect(place_id, main_text)}
className={cn( 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" /> <RenderIcon types={types} className="mr-2 h-4 w-4" />
<div className="line-clamp-1"> <div className="flex-1 space-y-0.5">
<p>{main_text}</p> <p className="line-clamp-1">{main_text}</p>
<span className="text-[10px] text-muted-foreground"> <span className=" line-clamp-1 text-[10px] leading-none text-muted-foreground">
{secondary_text} {secondary_text}
</span> </span>
</div> </div>
</li> </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> </ul>
</div> </div>
</PopoverContent> </PopoverContent>
</Popover> </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} />;
}

View File

@ -49,6 +49,11 @@ export default function CreateCalendarEventModal() {
const [timezone, setTimezone] = useState( const [timezone, setTimezone] = useState(
Intl.DateTimeFormat().resolvedOptions().timeZone, Intl.DateTimeFormat().resolvedOptions().timeZone,
); );
const [location, setLocation] = useState<{
address: string;
name: string;
coordinates: { lat: number; lng: number };
}>();
useEffect(() => { useEffect(() => {
if (startDate && endDate) { if (startDate && endDate) {
@ -93,7 +98,7 @@ export default function CreateCalendarEventModal() {
<SmallCalendarIcon date={startDate ?? new Date()} /> <SmallCalendarIcon date={startDate ?? new Date()} />
</div> </div>
<div className="max-w-[300px] flex-1 divide-y overflow-hidden rounded-md bg-muted"> <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 w-[70px] shrink-0 items-center">Start</div>
<div className="flex-1"> <div className="flex-1">
<div className="flex max-w-full bg-secondary"> <div className="flex max-w-full bg-secondary">
@ -132,7 +137,7 @@ export default function CreateCalendarEventModal() {
</div> </div>
</div> </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 w-[70px] shrink-0 items-center">End</div>
<div className="flex-1"> <div className="flex-1">
<div className="flex max-w-full bg-secondary"> <div className="flex max-w-full bg-secondary">
@ -172,7 +177,7 @@ export default function CreateCalendarEventModal() {
</div> </div>
</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-1 text-xs text-muted-foreground">
<div className="flex max-w-full justify-start bg-secondary"> <div className="flex max-w-full justify-start bg-secondary">
<TimezoneSelector <TimezoneSelector
@ -191,10 +196,13 @@ export default function CreateCalendarEventModal() {
<LocationIcon /> <LocationIcon />
</div> </div>
<div className="max-w-[300px] flex-1 divide-y overflow-hidden rounded-md bg-muted"> <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-1">
<div className="flex max-w-full bg-secondary"> <div className="flex max-w-full bg-secondary">
<LocationSearchInput /> <LocationSearchInput
location={location}
onSelect={(l) => setLocation(l)}
/>
</div> </div>
</div> </div>
</div> </div>