adding lcoation

This commit is contained in:
zmeyer44 2023-10-23 14:14:56 -04:00
parent ee9edc466e
commit f047fd0304
14 changed files with 1097 additions and 2 deletions

3
.env
View File

@ -4,4 +4,5 @@ MY_AWS_ACCESS_KEY="AKIAT2UDOJMC4RSXQO5Y"
MY_AWS_SECRET_KEY="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" MY_AWS_SECRET_KEY="SwS8fm1+pKlrWU8pzuRCUYqyJ5roNwu9AZRhbWMu"
REGION="us-east-1" REGION="us-east-1"
S3_BUCKET_URL="https://flockstr.s3.amazonaws.com" S3_BUCKET_URL="https://flockstr.s3.amazonaws.com"
NEXT_PUBLIC_S3_BUCKET_URL="https://flockstr.s3.amazonaws.com" NEXT_PUBLIC_S3_BUCKET_URL="https://flockstr.s3.amazonaws.com"
NEXT_PUBLIC_GOOGLE_MAPS_KEY=AIzaSyDehwC0_6GIfPRZgNm4cpWuQ_Dz1HcEoYg

View File

@ -123,3 +123,7 @@
@apply flex items-center justify-center; @apply flex items-center justify-center;
} }
} }
input[type="time"]::-webkit-calendar-picker-indicator {
background: none;
display: none;
}

BIN
bun.lockb

Binary file not shown.

View File

@ -0,0 +1,19 @@
import { formatDate } from "@/lib/utils/dates";
type SmallCalendarIconProps = {
date: Date;
};
export default function SmallCalendarIcon({ date }: SmallCalendarIconProps) {
return (
<div className="center h-10 w-10 overflow-hidden rounded-sm border text-muted-foreground">
<div className="w-full text-center">
<div className="bg-muted p-[2px] text-[10px] font-semibold uppercase">
{formatDate(date, "MMM")}
</div>
<div className="text-center text-[14px] font-medium">
{formatDate(date, "D")}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,13 @@
import { HiOutlineMapPin } from "react-icons/hi2";
export default function LocationIcon() {
return (
<div className="center h-10 w-10 overflow-hidden rounded-sm border bg-muted text-muted-foreground">
<div className="center w-full text-center">
<div className="text-center text-[14px] font-medium">
<HiOutlineMapPin className="h-5 w-5" />
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,121 @@
"use client";
import { useMemo } from "react";
import usePlacesAutocomplete from "use-places-autocomplete";
import { Input } from "../ui/input";
import { cn } from "@/lib/utils";
import { HiOutlineBuildingStorefront } from "react-icons/hi2";
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";
export default function LocationSearchInput() {
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>;
}
if (!isLoaded) {
return <p>Loading...</p>;
}
return <CommandSearch />;
}
function CommandSearch() {
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,
});
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant={"ghost"}
className={cn(
"justify-start p-0 text-left font-normal",
!value && "text-muted-foreground",
)}
>
Add a location...
</Button>
</PopoverTrigger>
<PopoverContent className="z-modal+ w-auto p-0" align="start">
<Command className="rounded-lg border shadow-md">
<CommandInput
disabled={!ready}
onChangeCapture={(e) =>
setValue((e.target as unknown as { value: string }).value)
}
value={value}
placeholder="Search places..."
/>
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
{data.map(
({
description,
place_id,
structured_formatting: { main_text, secondary_text },
}) => (
<CommandItem key={place_id}>
<HiOutlineBuildingStorefront className="mr-2 h-4 w-4" />
<span>{main_text}</span>
<span>{description}</span>
</CommandItem>
),
)}
</CommandGroup>
{/* <CommandSeparator />
<CommandGroup heading="Vitrual">
<CommandItem>
<HiOutlineBuildingStorefront className="mr-2 h-4 w-4" />
<span>Profile</span>
</CommandItem>
<CommandItem>
<HiOutlineBuildingStorefront className="mr-2 h-4 w-4" />
<span>Mail</span>
</CommandItem>
<CommandItem>
<HiOutlineBuildingStorefront className="mr-2 h-4 w-4" />
<span>Settings</span>
</CommandItem>
</CommandGroup> */}
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View File

@ -0,0 +1,207 @@
"use client";
import { useState, useRef, useEffect } from "react";
import Template from "./Template";
import { Textarea } from "@/components/ui/textarea";
import useAutosizeTextArea from "@/lib/hooks/useAutoSizeTextArea";
import { Button } from "@/components/ui/button";
import { HiX } from "react-icons/hi";
import { cn } from "@/lib/utils";
import {
addMinutesToDate,
convertToTimezoneDate,
convertToTimezone,
} from "@/lib/utils/dates";
import { DatePicker } from "@/components/ui/date-picker";
import { TimePicker } from "@/components/ui/time-picker";
import { TimezoneSelector } from "../ui/timezone";
import SmallCalendarIcon from "../EventIcons/DateIcon";
import LocationIcon from "../EventIcons/LocationIcon";
import LocationSearchInput from "../LocationSearch";
import { useModal } from "@/app/_providers/modal/provider";
export default function CreateCalendarEventModal() {
const modal = useModal();
const now = new Date(new Date().setHours(12, 0, 0, 0));
const [title, setTitle] = useState("");
const [startDate, setStartDate] = useState<Date>(now);
const startTime = `${
startDate?.getHours().toLocaleString().length === 1
? "0" + startDate?.getHours().toLocaleString()
: startDate?.getHours()
}:${
startDate?.getMinutes().toLocaleString().length === 1
? "0" + startDate?.getMinutes().toLocaleString()
: startDate?.getMinutes()
}`;
const [endDate, setEndDate] = useState<Date | undefined>(
new Date(new Date().setHours(13)),
);
const endTime = `${
endDate?.getHours().toLocaleString().length === 1
? "0" + endDate?.getHours().toLocaleString()
: endDate?.getHours()
}:${
endDate?.getMinutes().toLocaleString().length === 1
? "0" + endDate?.getMinutes().toLocaleString()
: endDate?.getMinutes()
}`;
const [timezone, setTimezone] = useState(
Intl.DateTimeFormat().resolvedOptions().timeZone,
);
useEffect(() => {
if (startDate && endDate) {
if (startDate.getTime() > endDate.getTime()) {
setEndDate(addMinutesToDate(startDate, 60));
}
}
}, [startDate]);
const titleRef = useRef<HTMLTextAreaElement>(null);
useAutosizeTextArea(titleRef.current, title);
console.log(Intl.DateTimeFormat().resolvedOptions().timeZone);
return (
<div
className={cn(
"relative w-full grow bg-background p-4 shadow md:rounded-lg md:border md:p-6",
"md:max-w-[600px]",
)}
>
<button
onClick={() => modal?.hide()}
className="absolute right-4 top-4 hidden text-muted-foreground transition-all hover:text-primary md:flex"
>
<HiX className="h-4 w-4" />
</button>
<div className="">
<Textarea
ref={titleRef}
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Event Name"
className={cn(
"resize-none break-words border-0 bg-transparent p-0 text-3xl font-bold text-foreground shadow-none outline-none placeholder:text-muted-foreground/50 placeholder:hover:text-muted-foreground/80 focus-visible:ring-0",
title === "" && "max-h-[60px]",
)}
/>
<div className="space-y-4">
<div className="flex w-full items-start gap-x-3">
<div className="shrink-0">
<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 w-[70px] shrink-0 items-center">Start</div>
<div className="flex-1">
<div className="flex max-w-full bg-secondary">
<DatePicker
displayFormat="ddd, MMM D"
date={startDate}
onDateChange={(newDate) =>
setStartDate((prev) => {
if (!prev || !newDate) return newDate ?? now;
return new Date(
newDate.setHours(
prev.getHours(),
prev.getMinutes(),
),
);
})
}
hideIcon={true}
/>
<TimePicker
className="max-w-fit pl-0 pr-1"
value={startTime}
onChange={(newTime) =>
setStartDate(
(prev) =>
new Date(
prev!.setHours(
parseInt(newTime.split(":")[0] as string),
parseInt(newTime.split(":")[1] as string),
),
),
)
}
hideIcon={true}
/>
</div>
</div>
</div>
<div className="flex justify-between p-0.5 px-1 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">
<DatePicker
displayFormat="ddd, MMM D"
date={endDate}
onDateChange={(newDate) =>
setEndDate((prev) => {
if (!prev || !newDate) return newDate ?? now;
return new Date(
newDate.setHours(
prev.getHours(),
prev.getMinutes(),
),
);
})
}
hideIcon={true}
/>
<TimePicker
className="max-w-fit pl-0 pr-1"
value={endTime}
onChange={(newTime) =>
setEndDate(
(prev) =>
new Date(
prev!.setHours(
parseInt(newTime.split(":")[0] as string),
parseInt(newTime.split(":")[1] as string),
),
),
)
}
hideIcon={true}
/>
</div>
</div>
</div>
<div className="flex justify-between 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
hideIcon={false}
className="px-0 pr-1 font-normal"
value={timezone}
onChange={(newTimezone) => setTimezone(newTimezone)}
/>
</div>
</div>
</div>
</div>
</div>
<div className="flex w-full items-start gap-x-3">
<div className="shrink-0">
<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-1">
<div className="flex max-w-full bg-secondary">
<LocationSearchInput />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@ -19,6 +19,7 @@ import { formatCount } from "@/lib/utils";
import LoginModal from "./Login"; import LoginModal from "./Login";
import CreateList from "./CreateList"; import CreateList from "./CreateList";
import ShortTextNoteModal from "./ShortTextNote"; import ShortTextNoteModal from "./ShortTextNote";
import CreateCalendarEventModal from "./CreateCalendarEvent";
export default function NewEventModal() { export default function NewEventModal() {
const modal = useModal(); const modal = useModal();
return ( return (
@ -33,8 +34,17 @@ export default function NewEventModal() {
<span>Short Text</span> <span>Short Text</span>
<HiChatBubbleLeftEllipsis className="h-4 w-4" /> <HiChatBubbleLeftEllipsis className="h-4 w-4" />
</Button> </Button>
<Button
onClick={() => {
modal?.swap(<CreateCalendarEventModal />);
}}
className="w-full gap-x-1"
>
<span>Calendar Event</span>
<HiChatBubbleLeftEllipsis className="h-4 w-4" />
</Button>
<Link href={`/article/new`}> <Link href={`/article/new`}>
<Button className="w-full gap-x-1"> <Button onClick={() => modal?.hide()} className="w-full gap-x-1">
<span>Long Form</span> <span>Long Form</span>
<HiNewspaper className="h-4 w-4" /> <HiNewspaper className="h-4 w-4" />
</Button> </Button>

View File

@ -0,0 +1,58 @@
"use client";
import * as React from "react";
import { CalendarIcon } from "@radix-ui/react-icons";
import { cn } from "@/lib/utils";
import { formatDate } from "@/lib/utils/dates";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
type DatePickerProps = {
date: Date | undefined;
onDateChange: (newDate: Date | undefined) => void;
initialFocus?: boolean;
placeholder?: string;
hideIcon?: boolean;
displayFormat?: string;
};
export function DatePicker({
date,
onDateChange,
placeholder = "Pick a date",
initialFocus,
hideIcon = false,
displayFormat,
}: DatePickerProps) {
// const [date, setDate] = React.useState<Date>();
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant={"ghost"}
className={cn(
"w-full justify-end pr-1 text-left font-normal",
!date && "text-muted-foreground",
)}
>
{!hideIcon && <CalendarIcon className="mr-2 h-4 w-4" />}
{date ? formatDate(date, displayFormat) : <span>{placeholder}</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="z-modal+ w-auto p-0" align="start">
<Calendar
mode="single"
selected={date}
onSelect={onDateChange}
initialFocus={initialFocus}
/>
</PopoverContent>
</Popover>
);
}

View File

@ -0,0 +1,133 @@
"use client";
import * as React from "react";
import { CalendarIcon } from "@radix-ui/react-icons";
import { cn } from "@/lib/utils";
import { formatDate } from "@/lib/utils/dates";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Input } from "@/components/ui/input";
import {
Command,
CommandEmpty,
CommandGroup,
CommandItem,
} from "@/components/ui/command";
const times = [
{ label: "12:00 AM", value: "00:00" },
{ label: "12:30 AM", value: "00:30" },
{ label: "1:00 AM", value: "01:00" },
{ label: "1:30 AM", value: "01:30" },
{ label: "2:00 AM", value: "02:00" },
{ label: "2:30 AM", value: "02:30" },
{ label: "3:00 AM", value: "03:00" },
{ label: "3:30 AM", value: "03:30" },
{ label: "4:00 AM", value: "04:00" },
{ label: "4:30 AM", value: "04:30" },
{ label: "5:00 AM", value: "05:00" },
{ label: "5:30 AM", value: "05:30" },
{ label: "6:00 AM", value: "06:00" },
{ label: "6:30 AM", value: "06:30" },
{ label: "7:00 AM", value: "07:00" },
{ label: "7:30 AM", value: "07:30" },
{ label: "8:00 AM", value: "08:00" },
{ label: "8:30 AM", value: "08:30" },
{ label: "9:00 AM", value: "09:00" },
{ label: "9:30 AM", value: "09:30" },
{ label: "10:00 AM", value: "10:00" },
{ label: "10:30 AM", value: "10:30" },
{ label: "11:00 AM", value: "11:00" },
{ label: "11:30 AM", value: "11:30" },
{ label: "12:00 PM", value: "12:00" },
{ label: "12:30 PM", value: "12:30" },
{ label: "1:00 PM", value: "13:00" },
{ label: "1:30 PM", value: "13:30" },
{ label: "2:00 PM", value: "14:00" },
{ label: "2:30 PM", value: "14:30" },
{ label: "3:00 PM", value: "15:00" },
{ label: "3:30 PM", value: "15:30" },
{ label: "4:00 PM", value: "16:00" },
{ label: "4:30 PM", value: "16:30" },
{ label: "5:00 PM", value: "17:00" },
{ label: "5:30 PM", value: "17:30" },
{ label: "6:00 PM", value: "18:00" },
{ label: "6:30 PM", value: "18:30" },
{ label: "7:00 PM", value: "19:00" },
{ label: "7:30 PM", value: "19:30" },
{ label: "8:00 PM", value: "20:00" },
{ label: "8:30 PM", value: "20:30" },
{ label: "9:00 PM", value: "21:00" },
{ label: "9:30 PM", value: "21:30" },
{ label: "10:00 PM", value: "22:00" },
{ label: "10:30 PM", value: "22:30" },
{ label: "11:00 PM", value: "23:00" },
{ label: "11:30 PM", value: "23:30" },
] as const;
type TimePickerProps = {
value: string | undefined;
onChange: (newTime: string) => void;
initialFocus?: boolean;
placeholder?: string;
hideIcon?: boolean;
displayFormat?: string;
className?: string;
};
export function TimePicker({
value,
onChange,
hideIcon = false,
className,
}: TimePickerProps) {
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant={"ghost"}
className={cn(
"justify-start p-0 text-left font-normal",
!value && "text-muted-foreground",
)}
>
{!hideIcon && <CalendarIcon className="mr-2 h-4 w-4" />}
<Input
type="time"
placeholder="HH:MM"
value={value}
onChange={(e) => {
console.log(e.target.valueAsNumber);
onChange(e.target.value);
}}
className={cn(
"border-0 shadow-none outline-none ring-0 focus-visible:ring-0",
className,
)}
/>
</Button>
</PopoverTrigger>
<PopoverContent className="z-modal+ w-auto p-0" align="start">
<Command className="max-h-[200px]">
<CommandGroup className="overflow-y-auto">
{times.map((time) => (
<CommandItem
value={time.label}
key={time.value}
onSelect={() => {
onChange(time.value);
}}
>
{time.label}
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
);
}

View File

@ -0,0 +1,90 @@
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { HiOutlineGlobeAlt, HiChevronDown, HiCheck } from "react-icons/hi2";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { TIMEZONES } from "@/constants/timezones";
const timezones = TIMEZONES.filter((t) =>
Intl.supportedValuesOf("timeZone").includes(t.name),
).map((t) => ({
label: `(${t.offset}) ${t.name.split("/").pop()?.replaceAll("_", " ")}`,
value: t.name,
}));
type TimezoneSelectorProps = {
value: string;
onChange: (val: string) => void;
className?: string;
hideIcon?: boolean;
};
export function TimezoneSelector({
value,
onChange,
className,
hideIcon = false,
}: TimezoneSelectorProps) {
const [open, setOpen] = React.useState(false);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="ghost"
role="combobox"
aria-expanded={open}
className={cn("flex items-center gap-2", className)}
>
{!hideIcon && <HiOutlineGlobeAlt className="h-4 w-4 shrink-0" />}
<span className="shrink truncate">
{value
? timezones.find((timezone) => timezone.value === value)?.label
: "Select timezone..."}
</span>
{!hideIcon && (
<HiChevronDown className="h-4 w-4 shrink-0 opacity-50" />
)}
</Button>
</PopoverTrigger>
<PopoverContent className="z-modal+ w-[250px] p-0">
<Command className="max-h-[200px]">
<CommandInput placeholder="Search timezone..." className="h-9" />
<CommandEmpty>No timezone found.</CommandEmpty>
<CommandGroup className="overflow-y-auto">
{timezones.map((timezone) => (
<CommandItem
key={timezone.value}
value={timezone.value}
onSelect={() => {
onChange(timezone.value);
setOpen(false);
}}
>
{timezone.label}
<HiCheck
className={cn(
"ml-auto h-4 w-4 text-primary",
value === timezone.value ? "opacity-100" : "opacity-0",
)}
/>
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
);
}

374
constants/timezones.ts Normal file
View File

@ -0,0 +1,374 @@
export const TIMEZONES = [
{
offset: "GMT-12:00",
name: "Etc/GMT-12",
},
{
offset: "GMT-11:00",
name: "Etc/GMT-11",
},
{
offset: "GMT-11:00",
name: "Pacific/Midway",
},
{
offset: "GMT-10:00",
name: "America/Adak",
},
{
offset: "GMT-09:00",
name: "America/Anchorage",
},
{
offset: "GMT-09:00",
name: "Pacific/Gambier",
},
{
offset: "GMT-08:00",
name: "America/Dawson_Creek",
},
{
offset: "GMT-08:00",
name: "America/Ensenada",
},
{
offset: "GMT-08:00",
name: "America/Los_Angeles",
},
{
offset: "GMT-07:00",
name: "America/Chihuahua",
},
{
offset: "GMT-07:00",
name: "America/Denver",
},
{
offset: "GMT-06:00",
name: "America/Belize",
},
{
offset: "GMT-06:00",
name: "America/Cancun",
},
{
offset: "GMT-06:00",
name: "America/Chicago",
},
{
offset: "GMT-06:00",
name: "Chile/EasterIsland",
},
{
offset: "GMT-05:00",
name: "America/Bogota",
},
{
offset: "GMT-05:00",
name: "America/Havana",
},
{
offset: "GMT-05:00",
name: "America/New_York",
},
{
offset: "GMT-04:30",
name: "America/Caracas",
},
{
offset: "GMT-04:00",
name: "America/Campo_Grande",
},
{
offset: "GMT-04:00",
name: "America/Glace_Bay",
},
{
offset: "GMT-04:00",
name: "America/Goose_Bay",
},
{
offset: "GMT-04:00",
name: "America/Santiago",
},
{
offset: "GMT-04:00",
name: "America/La_Paz",
},
{
offset: "GMT-03:00",
name: "America/Argentina/Buenos_Aires",
},
{
offset: "GMT-03:00",
name: "America/Montevideo",
},
{
offset: "GMT-03:00",
name: "America/Araguaina",
},
{
offset: "GMT-03:00",
name: "America/Godthab",
},
{
offset: "GMT-03:00",
name: "America/Miquelon",
},
{
offset: "GMT-03:00",
name: "America/Sao_Paulo",
},
{
offset: "GMT-03:30",
name: "America/St_Johns",
},
{
offset: "GMT-02:00",
name: "America/Noronha",
},
{
offset: "GMT-01:00",
name: "Atlantic/Cape_Verde",
},
{
offset: "GMT",
name: "Europe/Belfast",
},
{
offset: "GMT",
name: "Africa/Abidjan",
},
{
offset: "GMT",
name: "Europe/Dublin",
},
{
offset: "GMT",
name: "Europe/Lisbon",
},
{
offset: "GMT",
name: "Europe/London",
},
{
offset: "UTC",
name: "UTC",
},
{
offset: "GMT+01:00",
name: "Africa/Algiers",
},
{
offset: "GMT+01:00",
name: "Africa/Windhoek",
},
{
offset: "GMT+01:00",
name: "Atlantic/Azores",
},
{
offset: "GMT+01:00",
name: "Atlantic/Stanley",
},
{
offset: "GMT+01:00",
name: "Europe/Amsterdam",
},
{
offset: "GMT+01:00",
name: "Europe/Belgrade",
},
{
offset: "GMT+01:00",
name: "Europe/Brussels",
},
{
offset: "GMT+02:00",
name: "Africa/Cairo",
},
{
offset: "GMT+02:00",
name: "Africa/Blantyre",
},
{
offset: "GMT+02:00",
name: "Asia/Beirut",
},
{
offset: "GMT+02:00",
name: "Asia/Damascus",
},
{
offset: "GMT+02:00",
name: "Asia/Gaza",
},
{
offset: "GMT+02:00",
name: "Asia/Jerusalem",
},
{
offset: "GMT+03:00",
name: "Africa/Addis_Ababa",
},
{
offset: "GMT+03:00",
name: "Asia/Riyadh89",
},
{
offset: "GMT+03:00",
name: "Europe/Minsk",
},
{
offset: "GMT+03:30",
name: "Asia/Tehran",
},
{
offset: "GMT+04:00",
name: "Asia/Dubai",
},
{
offset: "GMT+04:00",
name: "Asia/Yerevan",
},
{
offset: "GMT+04:00",
name: "Europe/Moscow",
},
{
offset: "GMT+04:30",
name: "Asia/Kabul",
},
{
offset: "GMT+05:00",
name: "Asia/Tashkent",
},
{
offset: "GMT+05:30",
name: "Asia/Kolkata",
},
{
offset: "GMT+05:45",
name: "Asia/Katmandu",
},
{
offset: "GMT+06:00",
name: "Asia/Dhaka",
},
{
offset: "GMT+06:00",
name: "Asia/Yekaterinburg",
},
{
offset: "GMT+06:30",
name: "Asia/Rangoon",
},
{
offset: "GMT+07:00",
name: "Asia/Bangkok",
},
{
offset: "GMT+07:00",
name: "Asia/Novosibirsk",
},
{
offset: "GMT+08:00",
name: "Etc/GMT+8",
},
{
offset: "GMT+08:00",
name: "Asia/Hong_Kong",
},
{
offset: "GMT+08:00",
name: "Asia/Krasnoyarsk",
},
{
offset: "GMT+08:00",
name: "Australia/Perth",
},
{
offset: "GMT+08:45",
name: "Australia/Eucla",
},
{
offset: "GMT+09:00",
name: "Asia/Irkutsk",
},
{
offset: "GMT+09:00",
name: "Asia/Seoul",
},
{
offset: "GMT+09:00",
name: "Asia/Tokyo",
},
{
offset: "GMT+09:30",
name: "Australia/Adelaide",
},
{
offset: "GMT+09:30",
name: "Australia/Darwin",
},
{
offset: "GMT+09:30",
name: "Pacific/Marquesas",
},
{
offset: "GMT+10:00",
name: "Etc/GMT+10",
},
{
offset: "GMT+10:00",
name: "Australia/Brisbane",
},
{
offset: "GMT+10:00",
name: "Australia/Hobart",
},
{
offset: "GMT+10:00",
name: "Asia/Yakutsk",
},
{
offset: "GMT+10:30",
name: "Australia/Lord_Howe",
},
{
offset: "GMT+11:00",
name: "Asia/Vladivostok",
},
{
offset: "GMT+11:30",
name: "Pacific/Norfolk",
},
{
offset: "GMT+12:00",
name: "Etc/GMT+12",
},
{
offset: "GMT+12:00",
name: "Asia/Anadyr",
},
{
offset: "GMT+12:00",
name: "Asia/Magadan",
},
{
offset: "GMT+12:00",
name: "Pacific/Auckland",
},
{
offset: "GMT+12:45",
name: "Pacific/Chatham",
},
{
offset: "GMT+13:00",
name: "Pacific/Tongatapu",
},
{
offset: "GMT+14:00",
name: "Pacific/Kiritimati",
},
];

View File

@ -3,6 +3,7 @@ import relative from "dayjs/plugin/relativeTime";
import updateLocale from "dayjs/plugin/updateLocale"; import updateLocale from "dayjs/plugin/updateLocale";
import advancedFormat from "dayjs/plugin/advancedFormat"; import advancedFormat from "dayjs/plugin/advancedFormat";
import timezone from "dayjs/plugin/timezone"; import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
export function relativeTimeUnix(timestamp: number) { export function relativeTimeUnix(timestamp: number) {
const config = { const config = {
@ -79,3 +80,63 @@ export function formatDate(timestamp: Date, format?: string) {
dayjs.extend(timezone); dayjs.extend(timezone);
return dayjs(timestamp).format(format ?? "MMMM Do, YYYY"); return dayjs(timestamp).format(format ?? "MMMM Do, YYYY");
} }
export function convertToTimezoneDate(inputDate: Date, _timezone: string) {
dayjs.extend(utc);
dayjs.extend(timezone);
console.log("time", _timezone);
return dayjs(inputDate).tz(_timezone).toDate();
}
export function addMinutesToDate(inputDate: Date, minutesToAdd: number) {
if (!(inputDate instanceof Date)) {
throw new Error("Invalid date input");
}
if (typeof minutesToAdd !== "number" || isNaN(minutesToAdd)) {
throw new Error("Invalid minutes input");
}
// Copy the input date to avoid modifying the original date
const resultDate = new Date(inputDate);
// Add the specified number of minutes
resultDate.setMinutes(resultDate.getMinutes() + minutesToAdd);
return resultDate;
}
export function toUnix(inputDate: Date) {
return dayjs(inputDate).unix();
}
function timezoneDiff(ianatz: string) {
const date = new Date();
// suppose the date is 12:00 UTC
var invdate = new Date(
date.toLocaleString("en-US", {
timeZone: ianatz,
}),
);
// then invdate will be 07:00 in Toronto
// and the diff is 5 hours
var diff = date.getTime() - invdate.getTime();
return diff;
}
export function convertToTimezone(inputDate: Date, targetTimezone: string) {
if (!(inputDate instanceof Date)) {
throw new Error("Invalid date input");
}
if (typeof targetTimezone !== "string") {
throw new Error("Invalid timezone input");
}
dayjs.extend(utc);
dayjs.extend(timezone);
// Get plain date w/o timezones
const initialDate = new Date(inputDate + "Z");
const plainString = initialDate.toISOString().split(".")[0] as string;
const plain = dayjs.tz(plainString, targetTimezone);
return plain.toDate();
}

View File

@ -29,6 +29,7 @@
"@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7", "@radix-ui/react-tooltip": "^1.0.7",
"@react-google-maps/api": "^2.19.2",
"@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/container-queries": "^0.1.1",
"aws-sdk": "^2.1475.0", "aws-sdk": "^2.1475.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
@ -50,16 +51,19 @@
"nostr-tools": "^1.16.0", "nostr-tools": "^1.16.0",
"ramda": "^0.29.1", "ramda": "^0.29.1",
"react": "^18", "react": "^18",
"react-aria": "^3.29.1",
"react-day-picker": "^8.9.1", "react-day-picker": "^8.9.1",
"react-dom": "^18", "react-dom": "^18",
"react-hook-form": "^7.47.0", "react-hook-form": "^7.47.0",
"react-icons": "^4.11.0", "react-icons": "^4.11.0",
"react-player": "^2.13.0", "react-player": "^2.13.0",
"react-stately": "^3.27.1",
"recharts": "^2.9.0", "recharts": "^2.9.0",
"sonner": "^1.0.3", "sonner": "^1.0.3",
"tailwind-merge": "^1.14.0", "tailwind-merge": "^1.14.0",
"tailwind-scrollbar": "^3.0.5", "tailwind-scrollbar": "^3.0.5",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"use-places-autocomplete": "^4.0.1",
"zod": "^3.22.4", "zod": "^3.22.4",
"zod-fetch": "^0.1.1", "zod-fetch": "^0.1.1",
"zustand": "^4.4.3" "zustand": "^4.4.3"