♻️ Switch to using new startTime column (#1041)

This commit is contained in:
Luke Vella 2024-03-17 11:21:54 +07:00 committed by GitHub
parent bbbbf5ff49
commit a3e26bcc9a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 57 additions and 137 deletions

View file

@ -16,8 +16,16 @@ import { usePoll } from "@/components/poll-context";
import { Trans } from "@/components/trans"; import { Trans } from "@/components/trans";
import { encodeDateOption } from "@/utils/date-time-utils"; import { encodeDateOption } from "@/utils/date-time-utils";
const convertOptionToString = (option: { start: Date; duration: number }) => { const convertOptionToString = (
const start = dayjs(option.start).utc(); option: { startTime: Date; duration: number },
timeZone: string | null,
) => {
let start = dayjs(option.startTime);
if (timeZone) {
start = start.tz(timeZone);
} else {
start = start.utc();
}
return option.duration === 0 return option.duration === 0
? start.format("YYYY-MM-DD") ? start.format("YYYY-MM-DD")
: `${start.format("YYYY-MM-DDTHH:mm:ss")}/${start : `${start.format("YYYY-MM-DDTHH:mm:ss")}/${start
@ -37,12 +45,26 @@ const Page = () => {
const redirectBackToPoll = () => { const redirectBackToPoll = () => {
router.push(pollLink); router.push(pollLink);
}; };
let firstDate = dayjs(poll.options[0]?.startTime);
if (poll.timeZone) {
firstDate = firstDate.tz(poll.timeZone);
} else {
firstDate = firstDate.utc();
}
const form = useForm<PollOptionsData>({ const form = useForm<PollOptionsData>({
defaultValues: { defaultValues: {
navigationDate: dayjs(poll.options[0]?.start).utc().format("YYYY-MM-DD"), navigationDate: firstDate.format("YYYY-MM-DD"),
view: "month", view: "month",
options: poll.options.map((option) => { options: poll.options.map((option) => {
const start = dayjs(option.start).utc(); let start = dayjs(option.startTime);
if (poll.timeZone) {
start = start.tz(poll.timeZone);
} else {
start = start.utc();
}
return option.duration > 0 return option.duration > 0
? { ? {
type: "timeSlot", type: "timeSlot",
@ -68,13 +90,16 @@ const Page = () => {
onSubmit={form.handleSubmit((data) => { onSubmit={form.handleSubmit((data) => {
const encodedOptions = data.options.map(encodeDateOption); const encodedOptions = data.options.map(encodeDateOption);
const optionsToDelete = poll.options.filter((option) => { const optionsToDelete = poll.options.filter((option) => {
return !encodedOptions.includes(convertOptionToString(option)); return !encodedOptions.includes(
convertOptionToString(option, data.timeZone),
);
}); });
const optionsToAdd = encodedOptions.filter( const optionsToAdd = encodedOptions.filter(
(encodedOption) => (encodedOption) =>
!poll.options.find( !poll.options.find(
(o) => convertOptionToString(o) === encodedOption, (o) =>
convertOptionToString(o, data.timeZone) === encodedOption,
), ),
); );

View file

@ -5,9 +5,7 @@ import { TrashIcon } from "lucide-react";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import React from "react"; import React from "react";
import { useUser } from "@/components/user-provider";
import { import {
decodeOptions,
getDuration, getDuration,
ParsedDateOption, ParsedDateOption,
ParsedTimeSlotOption, ParsedTimeSlotOption,
@ -223,27 +221,15 @@ function createOptionsContextValue(
export const OptionsProvider = (props: React.PropsWithChildren) => { export const OptionsProvider = (props: React.PropsWithChildren) => {
const { poll } = usePoll(); const { poll } = usePoll();
const { timeZone: targetTimeZone, timeFormat } = useDayjs(); const { timeZone: targetTimeZone, timeFormat } = useDayjs();
const { isInternalUser } = useUser();
const options = React.useMemo(() => { const options = React.useMemo(() => {
let res: OptionsContextValue; return createOptionsContextValue(
if (isInternalUser) {
res = createOptionsContextValue(
poll.options, poll.options,
targetTimeZone, targetTimeZone,
poll.timeZone, poll.timeZone,
); );
} else { // eslint-disable-next-line react-hooks/exhaustive-deps
// @deprecated - Stop using this method and drop the start column from the database in favor of startTime }, [poll.options, poll.timeZone, targetTimeZone, timeFormat]);
res = decodeOptions(
poll.options,
poll.timeZone,
targetTimeZone,
timeFormat,
);
}
return res;
}, [isInternalUser, poll.options, poll.timeZone, targetTimeZone, timeFormat]);
return ( return (
<OptionsContext.Provider value={options}> <OptionsContext.Provider value={options}>

View file

@ -18,7 +18,8 @@ import { DateIcon } from "@/components/date-icon";
import { useParticipants } from "@/components/participants-provider"; import { useParticipants } from "@/components/participants-provider";
import { ConnectedScoreSummary } from "@/components/poll/score-summary"; import { ConnectedScoreSummary } from "@/components/poll/score-summary";
import { VoteSummaryProgressBar } from "@/components/vote-summary-progress-bar"; import { VoteSummaryProgressBar } from "@/components/vote-summary-progress-bar";
import { useDateFormatter, usePoll } from "@/contexts/poll"; import { usePoll } from "@/contexts/poll";
import { useDayjs } from "@/utils/dayjs";
const formSchema = z.object({ const formSchema = z.object({
selectedOptionId: z.string(), selectedOptionId: z.string(),
@ -69,6 +70,7 @@ export const FinalizePollForm = ({
const poll = usePoll(); const poll = usePoll();
const [max, setMax] = React.useState(pageSize); const [max, setMax] = React.useState(pageSize);
const { adjustTimeZone } = useDayjs();
const scoreByOptionId = useScoreByOptionId(); const scoreByOptionId = useScoreByOptionId();
const { participants } = useParticipants(); const { participants } = useParticipants();
@ -96,7 +98,6 @@ export const FinalizePollForm = ({
return { ...option, votes: scoreByOptionId[option.id] }; return { ...option, votes: scoreByOptionId[option.id] };
}); });
const dateFormatter = useDateFormatter();
const form = useForm<FinalizeFormData>({ const form = useForm<FinalizeFormData>({
defaultValues: { defaultValues: {
selectedOptionId: options[0].id, selectedOptionId: options[0].id,
@ -127,10 +128,16 @@ export const FinalizePollForm = ({
className="grid gap-2" className="grid gap-2"
> >
{options.slice(0, max).map((option) => { {options.slice(0, max).map((option) => {
const start = dateFormatter(option.start); const start = adjustTimeZone(
const end = dateFormatter( option.startTime,
dayjs(option.start).add(option.duration, "minute"), !poll.timeZone,
); );
const end = adjustTimeZone(
dayjs(option.startTime).add(option.duration, "minute"),
!poll.timeZone,
);
return ( return (
<label <label
key={option.id} key={option.id}

View file

@ -27,17 +27,7 @@ export const UserContext = React.createContext<{
} | null>(null); } | null>(null);
export const useUser = () => { export const useUser = () => {
const value = useRequiredContext(UserContext, "UserContext"); return useRequiredContext(UserContext, "UserContext");
return { ...value, isInternalUser: value.user.email?.endsWith("@rallly.co") };
};
export const useAuthenticatedUser = () => {
const { user, ...rest } = useRequiredContext(UserContext, "UserContext");
if (user.isGuest) {
throw new Error("Forget to prefetch user identity");
}
return { user, ...rest };
}; };
export const IfAuthenticated = (props: { children?: React.ReactNode }) => { export const IfAuthenticated = (props: { children?: React.ReactNode }) => {

View file

@ -1,8 +1,6 @@
import dayjs, { Dayjs } from "dayjs";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import React from "react"; import React from "react";
import { useDayjs } from "@/utils/dayjs";
import { trpc } from "@/utils/trpc/client"; import { trpc } from "@/utils/trpc/client";
export const usePoll = () => { export const usePoll = () => {
@ -23,16 +21,3 @@ export const usePoll = () => {
return pollQuery.data; return pollQuery.data;
}; };
export const useDateFormatter = () => {
const { timeZone } = usePoll();
const { timeZone: preferredTimeZone } = useDayjs();
return (date: Date | Dayjs) => {
if (timeZone) {
return dayjs(date).utc().tz(timeZone, true).tz(preferredTimeZone);
}
return dayjs(date).utc();
};
};

View file

@ -1,4 +1,3 @@
import { TimeFormat } from "@rallly/database";
import dayjs from "dayjs"; import dayjs from "dayjs";
import soft from "timezone-soft"; import soft from "timezone-soft";
@ -9,8 +8,6 @@ import {
TimeOption, TimeOption,
} from "../components/forms/poll-options-form"; } from "../components/forms/poll-options-form";
type Option = { id: string; start: Date; duration: number };
export function parseIanaTimezone(timezone: string): { export function parseIanaTimezone(timezone: string): {
region: string; region: string;
city: string; city: string;
@ -86,80 +83,6 @@ export const getDuration = (startTime: dayjs.Dayjs, endTime: dayjs.Dayjs) => {
return res; return res;
}; };
export const decodeOptions = (
options: Option[],
timeZone: string | null,
targetTimeZone: string,
timeFormat: TimeFormat, // TODO (Luke Vella) [2022-06-28]: Need to pass timeFormat so that we recalculate the options when timeFormat changes. There is definitely a better way to do this
):
| { pollType: "date"; options: ParsedDateOption[] }
| { pollType: "timeSlot"; options: ParsedTimeSlotOption[] } => {
const pollType = options.some(({ duration }) => duration > 0)
? "timeSlot"
: "date";
if (pollType === "timeSlot") {
return {
pollType,
options: options.map((option) =>
parseTimeSlotOption(option, timeZone, targetTimeZone, timeFormat),
),
};
} else {
return {
pollType,
options: options.map((option) => parseDateOption(option)),
};
}
};
export const parseDateOption = (option: Option): ParsedDateOption => {
const date = dayjs(option.start).utc();
return {
type: "date",
optionId: option.id,
day: date.format("D"),
dow: date.format("ddd"),
month: date.format("MMM"),
year: date.format("YYYY"),
};
};
export const parseTimeSlotOption = (
option: Option,
timeZone: string | null,
targetTimeZone: string,
timeFormat: TimeFormat,
): ParsedTimeSlotOption => {
const adjustTimeZone = (date: string) => {
if (!timeZone) {
return dayjs(date);
}
return dayjs.tz(date, timeZone).tz(targetTimeZone);
};
const start = dayjs(option.start).utc().format("YYYY-MM-DD HH:mm");
const startDate = adjustTimeZone(start);
const endDate = adjustTimeZone(
dayjs(start).add(option.duration, "minute").format("YYYY-MM-DD HH:mm"),
);
return {
type: "timeSlot",
optionId: option.id,
startTime: startDate.format(timeFormat === "hours12" ? "h:mm A" : "HH:mm"),
endTime: endDate.format(timeFormat === "hours12" ? "h:mm A" : "HH:mm"),
day: startDate.format("D"),
dow: startDate.format("ddd"),
month: startDate.format("MMM"),
duration: getDuration(startDate, endDate),
year: startDate.format("YYYY"),
};
};
export const removeAllOptionsForDay = ( export const removeAllOptionsForDay = (
options: DateTimeOption[], options: DateTimeOption[],
date: Date, date: Date,

View file

@ -217,8 +217,12 @@ export const DayjsProvider: React.FunctionComponent<{
); );
const adjustTimeZone = React.useCallback( const adjustTimeZone = React.useCallback(
(date: dayjs.ConfigType, keepLocalTime = false) => { (date: dayjs.ConfigType, localTime = false) => {
return dayjs(date).tz(preferredTimeZone, keepLocalTime); if (!localTime) {
return dayjs(date).tz(preferredTimeZone);
} else {
return dayjs(date).utc();
}
}, },
[preferredTimeZone], [preferredTimeZone],
); );