mirror of
https://github.com/lukevella/rallly.git
synced 2025-05-02 03:36:33 +02:00
♻️ Switch to using new startTime column (#1041)
This commit is contained in:
parent
bbbbf5ff49
commit
a3e26bcc9a
7 changed files with 57 additions and 137 deletions
|
@ -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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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 }) => {
|
||||||
|
|
|
@ -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();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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],
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Reference in a new issue