mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-19 17:27:26 +02:00
Switch from date-fns to day.js (#213)
This commit is contained in:
parent
368f324865
commit
707eae68c1
19 changed files with 549 additions and 229 deletions
|
@ -27,8 +27,7 @@
|
|||
"@trpc/server": "^9.23.2",
|
||||
"axios": "^0.24.0",
|
||||
"clsx": "^1.1.1",
|
||||
"date-fns": "^2.28.0",
|
||||
"date-fns-tz": "^1.2.2",
|
||||
"dayjs": "^1.11.3",
|
||||
"eta": "^1.12.3",
|
||||
"framer-motion": "^6.3.11",
|
||||
"iron-session": "^6.1.3",
|
||||
|
@ -52,7 +51,7 @@
|
|||
"react-query": "^3.34.12",
|
||||
"react-use": "^17.3.2",
|
||||
"smoothscroll-polyfill": "^0.4.4",
|
||||
"spacetime": "^7.1.2",
|
||||
"spacetime": "^7.1.4",
|
||||
"superjson": "^1.9.1",
|
||||
"timezone-soft": "^1.3.1",
|
||||
"typescript": "^4.5.2",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import clsx from "clsx";
|
||||
import { formatRelative } from "date-fns";
|
||||
import dayjs from "dayjs";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { usePlausible } from "next-plausible";
|
||||
import * as React from "react";
|
||||
|
@ -16,7 +16,6 @@ import NameInput from "../name-input";
|
|||
import TruncatedLinkify from "../poll/truncated-linkify";
|
||||
import UserAvatar from "../poll/user-avatar";
|
||||
import { usePoll } from "../poll-context";
|
||||
import { usePreferences } from "../preferences/use-preferences";
|
||||
import { isUnclaimed, useSession } from "../session";
|
||||
|
||||
interface CommentForm {
|
||||
|
@ -25,7 +24,6 @@ interface CommentForm {
|
|||
}
|
||||
|
||||
const Discussion: React.VoidFunctionComponent = () => {
|
||||
const { locale } = usePreferences();
|
||||
const queryClient = trpc.useContext();
|
||||
const { poll } = usePoll();
|
||||
|
||||
|
@ -122,13 +120,7 @@ const Discussion: React.VoidFunctionComponent = () => {
|
|||
<div className="mb-1">
|
||||
<span className="mr-1 text-slate-400">•</span>
|
||||
<span className="text-sm text-slate-500">
|
||||
{formatRelative(
|
||||
new Date(comment.createdAt),
|
||||
Date.now(),
|
||||
{
|
||||
locale,
|
||||
},
|
||||
)}
|
||||
{dayjs(new Date(comment.createdAt)).fromNow()}
|
||||
</span>
|
||||
</div>
|
||||
<Dropdown
|
||||
|
|
357
src/components/forms/poll-options-form/dayjs-localizer.ts
Normal file
357
src/components/forms/poll-options-form/dayjs-localizer.ts
Normal file
|
@ -0,0 +1,357 @@
|
|||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
import { DateLocalizer } from "react-big-calendar";
|
||||
|
||||
const weekRangeFormat = ({ start, end }, culture, local) =>
|
||||
local.format(start, "MMMM DD", culture) +
|
||||
" – " +
|
||||
local.format(end, local.eq(start, end, "month") ? "DD" : "MMMM DD", culture);
|
||||
|
||||
const dateRangeFormat = ({ start, end }, culture, local) =>
|
||||
local.format(start, "L", culture) + " – " + local.format(end, "L", culture);
|
||||
|
||||
const timeRangeFormat = ({ start, end }, culture, local) =>
|
||||
local.format(start, "LT", culture) + " – " + local.format(end, "LT", culture);
|
||||
|
||||
const timeRangeStartFormat = ({ start }, culture, local) =>
|
||||
local.format(start, "LT", culture) + " – ";
|
||||
|
||||
const timeRangeEndFormat = ({ end }, culture, local) =>
|
||||
" – " + local.format(end, "LT", culture);
|
||||
|
||||
export const formats = {
|
||||
dateFormat: "DD",
|
||||
dayFormat: "DD ddd",
|
||||
weekdayFormat: "ddd",
|
||||
|
||||
selectRangeFormat: timeRangeFormat,
|
||||
eventTimeRangeFormat: timeRangeFormat,
|
||||
eventTimeRangeStartFormat: timeRangeStartFormat,
|
||||
eventTimeRangeEndFormat: timeRangeEndFormat,
|
||||
|
||||
timeGutterFormat: "LT",
|
||||
|
||||
monthHeaderFormat: "MMMM YYYY",
|
||||
dayHeaderFormat: "dddd MMM DD",
|
||||
dayRangeHeaderFormat: weekRangeFormat,
|
||||
agendaHeaderFormat: dateRangeFormat,
|
||||
|
||||
agendaDateFormat: "ddd MMM DD",
|
||||
agendaTimeFormat: "LT",
|
||||
agendaTimeRangeFormat: timeRangeFormat,
|
||||
};
|
||||
|
||||
function fixUnit(unit) {
|
||||
let datePart = unit ? unit.toLowerCase() : unit;
|
||||
if (datePart === "FullYear") {
|
||||
datePart = "year";
|
||||
} else if (!datePart) {
|
||||
datePart = undefined;
|
||||
}
|
||||
return datePart;
|
||||
}
|
||||
|
||||
export default function (dayjs) {
|
||||
const locale = (m, c) => (c ? m.locale(c) : m);
|
||||
|
||||
/*** BEGIN localized date arithmetic methods with dayjs ***/
|
||||
function defineComparators(a, b, unit) {
|
||||
const datePart = fixUnit(unit);
|
||||
const dtA = datePart ? dayjs(a).startOf(datePart) : dayjs(a);
|
||||
const dtB = datePart ? dayjs(b).startOf(datePart) : dayjs(b);
|
||||
return [dtA, dtB, datePart];
|
||||
}
|
||||
|
||||
function startOf(date = null, unit) {
|
||||
const datePart = fixUnit(unit);
|
||||
if (datePart) {
|
||||
return dayjs(date).startOf(datePart).toDate();
|
||||
}
|
||||
return dayjs(date).toDate();
|
||||
}
|
||||
|
||||
function endOf(date = null, unit) {
|
||||
const datePart = fixUnit(unit);
|
||||
if (datePart) {
|
||||
return dayjs(date).endOf(datePart).toDate();
|
||||
}
|
||||
return dayjs(date).toDate();
|
||||
}
|
||||
|
||||
// dayjs comparison operations *always* convert both sides to dayjs objects
|
||||
// prior to running the comparisons
|
||||
function eq(a, b, unit) {
|
||||
const [dtA, dtB, datePart] = defineComparators(a, b, unit);
|
||||
return dtA.isSame(dtB, datePart);
|
||||
}
|
||||
|
||||
function neq(a, b, unit) {
|
||||
return !eq(a, b, unit);
|
||||
}
|
||||
|
||||
function gt(a, b, unit) {
|
||||
const [dtA, dtB, datePart] = defineComparators(a, b, unit);
|
||||
return dtA.isAfter(dtB, datePart);
|
||||
}
|
||||
|
||||
function lt(a, b, unit) {
|
||||
const [dtA, dtB, datePart] = defineComparators(a, b, unit);
|
||||
return dtA.isBefore(dtB, datePart);
|
||||
}
|
||||
|
||||
function gte(a, b, unit) {
|
||||
const [dtA, dtB, datePart] = defineComparators(a, b, unit);
|
||||
return dtA.isSameOrBefore(dtB, datePart);
|
||||
}
|
||||
|
||||
function lte(a, b, unit) {
|
||||
const [dtA, dtB, datePart] = defineComparators(a, b, unit);
|
||||
return dtA.isSameOrBefore(dtB, datePart);
|
||||
}
|
||||
|
||||
function inRange(day, min, max, unit = "day") {
|
||||
const datePart = fixUnit(unit);
|
||||
const mDay = dayjs(day);
|
||||
const mMin = dayjs(min);
|
||||
const mMax = dayjs(max);
|
||||
return mDay.isBetween(mMin, mMax, datePart, "[]");
|
||||
}
|
||||
|
||||
function min(dateA, dateB) {
|
||||
const dtA = dayjs(dateA);
|
||||
const dtB = dayjs(dateB);
|
||||
const minDt = dayjs.min(dtA, dtB);
|
||||
return minDt.toDate();
|
||||
}
|
||||
|
||||
function max(dateA, dateB) {
|
||||
const dtA = dayjs(dateA);
|
||||
const dtB = dayjs(dateB);
|
||||
const maxDt = dayjs.max(dtA, dtB);
|
||||
return maxDt.toDate();
|
||||
}
|
||||
|
||||
function merge(date, time) {
|
||||
if (!date && !time) return null;
|
||||
|
||||
const tm = dayjs(time).format("HH:mm:ss");
|
||||
const dt = dayjs(date).startOf("day").format("MM/DD/YYYY");
|
||||
// We do it this way to avoid issues when timezone switching
|
||||
return dayjs(`${dt} ${tm}`, "MM/DD/YYYY HH:mm:ss").toDate();
|
||||
}
|
||||
|
||||
function add(date, adder, unit) {
|
||||
const datePart = fixUnit(unit);
|
||||
return dayjs(date).add(adder, datePart).toDate();
|
||||
}
|
||||
|
||||
function range(start, end, unit = "day") {
|
||||
const datePart = fixUnit(unit);
|
||||
// because the add method will put these in tz, we have to start that way
|
||||
let current = dayjs(start).toDate();
|
||||
const days = [];
|
||||
|
||||
while (lte(current, end)) {
|
||||
days.push(current);
|
||||
current = add(current, 1, datePart);
|
||||
}
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
function ceil(date, unit) {
|
||||
const datePart = fixUnit(unit);
|
||||
const floor = startOf(date, datePart);
|
||||
|
||||
return eq(floor, date) ? floor : add(floor, 1, datePart);
|
||||
}
|
||||
|
||||
function diff(a, b, unit = "day") {
|
||||
const datePart = fixUnit(unit);
|
||||
// don't use 'defineComparators' here, as we don't want to mutate the values
|
||||
const dtA = dayjs(a);
|
||||
const dtB = dayjs(b);
|
||||
return dtB.diff(dtA, datePart);
|
||||
}
|
||||
|
||||
function minutes(date) {
|
||||
const dt = dayjs(date);
|
||||
return dt.minutes();
|
||||
}
|
||||
|
||||
function firstOfWeek() {
|
||||
const data = dayjs.localeData();
|
||||
return data ? data.firstDayOfWeek() : 0;
|
||||
}
|
||||
|
||||
function firstVisibleDay(date) {
|
||||
return dayjs(date).startOf("month").startOf("week").toDate();
|
||||
}
|
||||
|
||||
function lastVisibleDay(date) {
|
||||
return dayjs(date).endOf("month").endOf("week").toDate();
|
||||
}
|
||||
|
||||
function visibleDays(date) {
|
||||
let current = firstVisibleDay(date);
|
||||
const last = lastVisibleDay(date);
|
||||
const days = [];
|
||||
|
||||
while (lte(current, last)) {
|
||||
days.push(current);
|
||||
current = add(current, 1, "d");
|
||||
}
|
||||
|
||||
return days;
|
||||
}
|
||||
/*** END localized date arithmetic methods with dayjs ***/
|
||||
|
||||
/**
|
||||
* Moved from TimeSlots.js, this method overrides the method of the same name
|
||||
* in the localizer.js, using dayjs to construct the js Date
|
||||
* @param {Date} dt - date to start with
|
||||
* @param {Number} minutesFromMidnight
|
||||
* @param {Number} offset
|
||||
* @returns {Date}
|
||||
*/
|
||||
function getSlotDate(dt, minutesFromMidnight, offset) {
|
||||
return dayjs(dt)
|
||||
.startOf("day")
|
||||
.minute(minutesFromMidnight + offset)
|
||||
.toDate();
|
||||
}
|
||||
|
||||
// dayjs will automatically handle DST differences in it's calculations
|
||||
function getTotalMin(start, end) {
|
||||
return diff(start, end, "minutes");
|
||||
}
|
||||
|
||||
function getMinutesFromMidnight(start) {
|
||||
const dayStart = dayjs(start).startOf("day");
|
||||
const day = dayjs(start);
|
||||
return day.diff(dayStart, "minutes");
|
||||
}
|
||||
|
||||
// These two are used by DateSlotMetrics
|
||||
function continuesPrior(start, first) {
|
||||
const mStart = dayjs(start);
|
||||
const mFirst = dayjs(first);
|
||||
return mStart.isBefore(mFirst, "day");
|
||||
}
|
||||
|
||||
function continuesAfter(start, end, last) {
|
||||
const mEnd = dayjs(end);
|
||||
const mLast = dayjs(last);
|
||||
return mEnd.isSameOrAfter(mLast, "minutes");
|
||||
}
|
||||
|
||||
// These two are used by eventLevels
|
||||
function sortEvents({
|
||||
evtA: { start: aStart, end: aEnd, allDay: aAllDay },
|
||||
evtB: { start: bStart, end: bEnd, allDay: bAllDay },
|
||||
}) {
|
||||
const startSort = +startOf(aStart, "day") - +startOf(bStart, "day");
|
||||
|
||||
const durA = diff(aStart, ceil(aEnd, "day"), "day");
|
||||
|
||||
const durB = diff(bStart, ceil(bEnd, "day"), "day");
|
||||
|
||||
return (
|
||||
startSort || // sort by start Day first
|
||||
Math.max(durB, 1) - Math.max(durA, 1) || // events spanning multiple days go first
|
||||
!!bAllDay - !!aAllDay || // then allDay single day events
|
||||
+aStart - +bStart || // then sort by start time *don't need dayjs conversion here
|
||||
+aEnd - +bEnd // then sort by end time *don't need dayjs conversion here either
|
||||
);
|
||||
}
|
||||
|
||||
function inEventRange({
|
||||
event: { start, end },
|
||||
range: { start: rangeStart, end: rangeEnd },
|
||||
}) {
|
||||
const startOfDay = dayjs(start).startOf("day");
|
||||
const eEnd = dayjs(end);
|
||||
const rStart = dayjs(rangeStart);
|
||||
const rEnd = dayjs(rangeEnd);
|
||||
|
||||
const startsBeforeEnd = startOfDay.isSameOrBefore(rEnd, "day");
|
||||
// when the event is zero duration we need to handle a bit differently
|
||||
const sameMin = !startOfDay.isSame(eEnd, "minutes");
|
||||
const endsAfterStart = sameMin
|
||||
? eEnd.isAfter(rStart, "minutes")
|
||||
: eEnd.isSameOrAfter(rStart, "minutes");
|
||||
|
||||
return startsBeforeEnd && endsAfterStart;
|
||||
}
|
||||
|
||||
// dayjs treats 'day' and 'date' equality very different
|
||||
// dayjs(date1).isSame(date2, 'day') would test that they were both the same day of the week
|
||||
// dayjs(date1).isSame(date2, 'date') would test that they were both the same date of the month of the year
|
||||
function isSameDate(date1, date2) {
|
||||
const dt = dayjs(date1);
|
||||
const dt2 = dayjs(date2);
|
||||
return dt.isSame(dt2, "date");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method, called once in the localizer constructor, is used by eventLevels
|
||||
* 'eventSegments()' to assist in determining the 'span' of the event in the display,
|
||||
* specifically when using a timezone that is greater than the browser native timezone.
|
||||
* @returns number
|
||||
*/
|
||||
function browserTZOffset() {
|
||||
/**
|
||||
* Date.prototype.getTimezoneOffset horrifically flips the positive/negative from
|
||||
* what you see in it's string, so we have to jump through some hoops to get a value
|
||||
* we can actually compare.
|
||||
*/
|
||||
const dt = new Date();
|
||||
const neg = /-/.test(dt.toString()) ? "-" : "";
|
||||
const dtOffset = dt.getTimezoneOffset();
|
||||
const comparator = Number(`${neg}${Math.abs(dtOffset)}`);
|
||||
// dayjs correctly provides positive/negative offset, as expected
|
||||
const mtOffset = dayjs().utcOffset();
|
||||
return mtOffset > comparator ? 1 : 0;
|
||||
}
|
||||
|
||||
return new DateLocalizer({
|
||||
formats,
|
||||
|
||||
firstOfWeek,
|
||||
firstVisibleDay,
|
||||
lastVisibleDay,
|
||||
visibleDays,
|
||||
|
||||
format(value, format, culture) {
|
||||
return locale(dayjs(value), culture).format(format);
|
||||
},
|
||||
|
||||
lt,
|
||||
lte,
|
||||
gt,
|
||||
gte,
|
||||
eq,
|
||||
neq,
|
||||
merge,
|
||||
inRange,
|
||||
startOf,
|
||||
endOf,
|
||||
range,
|
||||
add,
|
||||
diff,
|
||||
ceil,
|
||||
min,
|
||||
max,
|
||||
minutes,
|
||||
|
||||
getSlotDate,
|
||||
getTotalMin,
|
||||
getMinutesFromMidnight,
|
||||
continuesPrior,
|
||||
continuesAfter,
|
||||
sortEvents,
|
||||
inEventRange,
|
||||
isSameDate,
|
||||
browserTZOffset,
|
||||
});
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
import clsx from "clsx";
|
||||
import differenceInMinutes from "date-fns/differenceInMinutes";
|
||||
import { addMinutes, setHours } from "date-fns/esm";
|
||||
import isSameDay from "date-fns/isSameDay";
|
||||
import dayjs from "dayjs";
|
||||
import { usePlausible } from "next-plausible";
|
||||
import * as React from "react";
|
||||
|
||||
|
@ -126,12 +124,14 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
onClick={() => {
|
||||
if (
|
||||
datepicker.selection.some((selectedDate) =>
|
||||
isSameDay(selectedDate, day.date),
|
||||
dayjs(selectedDate).isSame(day.date, "day"),
|
||||
)
|
||||
) {
|
||||
onChange(removeAllOptionsForDay(options, day.date));
|
||||
} else {
|
||||
const selectedDate = setHours(day.date, 12);
|
||||
const selectedDate = dayjs(day.date)
|
||||
.set("hour", 12)
|
||||
.toDate();
|
||||
const newOption: DateTimeOption = !isTimedEvent
|
||||
? {
|
||||
type: "date",
|
||||
|
@ -141,7 +141,9 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
type: "timeSlot",
|
||||
start: formatDateWithoutTz(selectedDate),
|
||||
end: formatDateWithoutTz(
|
||||
addMinutes(selectedDate, duration),
|
||||
dayjs(selectedDate)
|
||||
.add(duration, "minutes")
|
||||
.toDate(),
|
||||
),
|
||||
};
|
||||
|
||||
|
@ -208,7 +210,9 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
);
|
||||
}
|
||||
const startDate = new Date(`${option.date}T12:00:00`);
|
||||
const endDate = addMinutes(startDate, duration);
|
||||
const endDate = dayjs(startDate)
|
||||
.add(duration, "minutes")
|
||||
.toDate();
|
||||
return {
|
||||
type: "timeSlot",
|
||||
start: formatDateWithoutTz(startDate),
|
||||
|
@ -260,7 +264,9 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
<TimePicker
|
||||
value={startDate}
|
||||
onChange={(newStart) => {
|
||||
const newEnd = addMinutes(newStart, duration);
|
||||
const newEnd = dayjs(newStart)
|
||||
.add(duration, "minutes")
|
||||
.toDate();
|
||||
// replace enter with updated start time
|
||||
onChange([
|
||||
...options.slice(0, index),
|
||||
|
@ -273,13 +279,15 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
]);
|
||||
onNavigate(newStart);
|
||||
onChangeDuration(
|
||||
differenceInMinutes(newEnd, newStart),
|
||||
dayjs(newEnd).diff(newStart, "minutes"),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<TimePicker
|
||||
value={new Date(option.end)}
|
||||
startFrom={addMinutes(startDate, 15)}
|
||||
startFrom={dayjs(startDate)
|
||||
.add(15, "minutes")
|
||||
.toDate()}
|
||||
onChange={(newEnd) => {
|
||||
onChange([
|
||||
...options.slice(0, index),
|
||||
|
@ -291,7 +299,7 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
]);
|
||||
onNavigate(newEnd);
|
||||
onChangeDuration(
|
||||
differenceInMinutes(newEnd, startDate),
|
||||
dayjs(newEnd).diff(startDate, "minutes"),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
@ -322,7 +330,9 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
type: "timeSlot",
|
||||
start: startTime,
|
||||
end: formatDateWithoutTz(
|
||||
addMinutes(new Date(startTime), duration),
|
||||
dayjs(new Date(startTime))
|
||||
.add(duration, "minutes")
|
||||
.toDate(),
|
||||
),
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -7,10 +7,9 @@ import {
|
|||
} from "@floating-ui/react-dom-interactions";
|
||||
import { Listbox } from "@headlessui/react";
|
||||
import clsx from "clsx";
|
||||
import { addMinutes, format, isSameDay, setHours, setMinutes } from "date-fns";
|
||||
import dayjs from "dayjs";
|
||||
import * as React from "react";
|
||||
|
||||
import { usePreferences } from "@/components/preferences/use-preferences";
|
||||
import { stopPropagation } from "@/utils/stop-propagation";
|
||||
|
||||
import ChevronDown from "../../../icons/chevron-down.svg";
|
||||
|
@ -27,9 +26,8 @@ const TimePicker: React.VoidFunctionComponent<TimePickerProps> = ({
|
|||
value,
|
||||
onChange,
|
||||
className,
|
||||
startFrom = setMinutes(setHours(value, 0), 0),
|
||||
startFrom,
|
||||
}) => {
|
||||
const { locale } = usePreferences();
|
||||
const { reference, floating, x, y, strategy, refs } = useFloating({
|
||||
strategy: "fixed",
|
||||
middleware: [
|
||||
|
@ -47,10 +45,14 @@ const TimePicker: React.VoidFunctionComponent<TimePickerProps> = ({
|
|||
],
|
||||
});
|
||||
|
||||
const startFromDate = startFrom
|
||||
? dayjs(startFrom)
|
||||
: dayjs(value).startOf("day");
|
||||
|
||||
const options: React.ReactNode[] = [];
|
||||
for (let i = 0; i < 96; i++) {
|
||||
const optionValue = addMinutes(startFrom, i * 15);
|
||||
if (!isSameDay(value, optionValue)) {
|
||||
const optionValue = startFromDate.add(i * 15, "minutes");
|
||||
if (!optionValue.isSame(value, "day")) {
|
||||
// we only support event that start and end on the same day for now
|
||||
// because react-big-calendar does not support events that span days
|
||||
break;
|
||||
|
@ -61,7 +63,7 @@ const TimePicker: React.VoidFunctionComponent<TimePickerProps> = ({
|
|||
className={styleMenuItem}
|
||||
value={optionValue.toISOString()}
|
||||
>
|
||||
{format(optionValue, "p", { locale })}
|
||||
{optionValue.format("LT")}
|
||||
</Listbox.Option>,
|
||||
);
|
||||
}
|
||||
|
@ -77,9 +79,7 @@ const TimePicker: React.VoidFunctionComponent<TimePickerProps> = ({
|
|||
<>
|
||||
<div ref={reference} className={clsx("relative", className)}>
|
||||
<Listbox.Button className="btn-default text-left">
|
||||
<span className="grow truncate">
|
||||
{format(value, "p", { locale })}
|
||||
</span>
|
||||
<span className="grow truncate">{dayjs(value).format("LT")}</span>
|
||||
<span className="pointer-events-none ml-2 flex">
|
||||
<ChevronDown className="h-5 w-5" />
|
||||
</span>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { format } from "date-fns";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export const formatDateWithoutTz = (date: Date): string => {
|
||||
return format(date, "yyyy-MM-dd'T'HH:mm:ss");
|
||||
return dayjs(date).format("YYYY-MM-DDTHH:mm:ss");
|
||||
};
|
||||
|
||||
export const formatDateWithoutTime = (date: Date): string => {
|
||||
return format(date, "yyyy-MM-dd");
|
||||
return dayjs(date).format("YYYY-MM-DD");
|
||||
};
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
import clsx from "clsx";
|
||||
import {
|
||||
addMinutes,
|
||||
differenceInMinutes,
|
||||
format,
|
||||
getDay,
|
||||
parse,
|
||||
startOfWeek,
|
||||
} from "date-fns";
|
||||
import dayjs from "dayjs";
|
||||
import React from "react";
|
||||
import { Calendar, dateFnsLocalizer } from "react-big-calendar";
|
||||
import { Calendar } from "react-big-calendar";
|
||||
import { useMount } from "react-use";
|
||||
|
||||
import { usePreferences } from "@/components/preferences/use-preferences";
|
||||
|
||||
import { getDuration } from "../../../utils/date-time-utils";
|
||||
import { usePreferences } from "../../preferences/use-preferences";
|
||||
import DateNavigationToolbar from "./date-navigation-toolbar";
|
||||
import dayjsLocalizer from "./dayjs-localizer";
|
||||
import { DateTimeOption, DateTimePickerProps } from "./types";
|
||||
import { formatDateWithoutTime, formatDateWithoutTz } from "./utils";
|
||||
|
||||
const localizer = dayjsLocalizer(dayjs);
|
||||
|
||||
const WeekCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
||||
title,
|
||||
options,
|
||||
|
@ -28,30 +24,12 @@ const WeekCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
}) => {
|
||||
const [scrollToTime, setScrollToTime] = React.useState<Date>();
|
||||
|
||||
const { timeFormat } = usePreferences();
|
||||
useMount(() => {
|
||||
// Bit of a hack to force rbc to scroll to the right time when we close/open a modal
|
||||
setScrollToTime(addMinutes(date, -60));
|
||||
setScrollToTime(dayjs(date).add(-60, "minutes").toDate());
|
||||
});
|
||||
|
||||
const { weekStartsOn, timeFormat, locale } = usePreferences();
|
||||
|
||||
const localizer = React.useMemo(
|
||||
() =>
|
||||
dateFnsLocalizer({
|
||||
format,
|
||||
parse,
|
||||
startOfWeek: (date: Date | number) =>
|
||||
startOfWeek(date, {
|
||||
weekStartsOn: weekStartsOn === "monday" ? 1 : 0,
|
||||
}),
|
||||
getDay,
|
||||
locales: {
|
||||
default: locale,
|
||||
},
|
||||
}),
|
||||
[locale, weekStartsOn],
|
||||
);
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
key={timeFormat}
|
||||
|
@ -106,11 +84,13 @@ const WeekCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
);
|
||||
},
|
||||
eventWrapper: (props) => {
|
||||
const start = dayjs(props.event.start);
|
||||
const end = dayjs(props.event.end);
|
||||
return (
|
||||
<div
|
||||
// onClick prop doesn't work properly. Seems like some other element is cancelling the event before it reaches this element
|
||||
onMouseUp={props.onClick}
|
||||
className="absolute ml-1 max-h-full overflow-hidden rounded-md bg-green-100 bg-opacity-80 p-1 text-xs text-green-500 transition-colors hover:bg-opacity-50"
|
||||
className="absolute ml-1 max-h-full overflow-hidden rounded-md bg-green-100 p-1 text-xs text-green-500 transition-colors"
|
||||
style={{
|
||||
top: `calc(${props.style?.top}% + 4px)`,
|
||||
height: `calc(${props.style?.height}% - 8px)`,
|
||||
|
@ -118,10 +98,8 @@ const WeekCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
width: `calc(${props.style?.width}%)`,
|
||||
}}
|
||||
>
|
||||
<div>{format(props.event.start, "p", { locale })}</div>
|
||||
<div className="w-full truncate font-bold">
|
||||
{props.event.title}
|
||||
</div>
|
||||
<div>{start.format("LT")}</div>
|
||||
<div className="font-semibold">{getDuration(start, end)}</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
@ -158,15 +136,15 @@ const WeekCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
)}
|
||||
>
|
||||
<span className="mr-1 font-normal opacity-50">
|
||||
{format(date, "E")}
|
||||
{dayjs(date).format("ddd")}
|
||||
</span>
|
||||
<span className="font-medium">{format(date, "dd")}</span>
|
||||
<span className="font-medium">{dayjs(date).format("DD")}</span>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
timeSlotWrapper: ({ children }) => {
|
||||
return <div className="h-12 text-xs text-gray-500">{children}</div>;
|
||||
return <div className="h-8 text-xs text-gray-500">{children}</div>;
|
||||
},
|
||||
}}
|
||||
step={15}
|
||||
|
@ -182,12 +160,14 @@ const WeekCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
|||
};
|
||||
|
||||
if (action === "select") {
|
||||
const diff = differenceInMinutes(endDate, startDate);
|
||||
const diff = dayjs(endDate).diff(startDate, "minutes");
|
||||
if (diff < 60 * 24) {
|
||||
onChangeDuration(diff);
|
||||
}
|
||||
} else {
|
||||
newEvent.end = formatDateWithoutTz(addMinutes(startDate, duration));
|
||||
newEvent.end = formatDateWithoutTz(
|
||||
dayjs(startDate).add(duration, "minutes").toDate(),
|
||||
);
|
||||
}
|
||||
|
||||
const alreadyExists = options.some(
|
||||
|
|
|
@ -1,13 +1,4 @@
|
|||
import {
|
||||
addDays,
|
||||
addMonths,
|
||||
format,
|
||||
getMonth,
|
||||
isSameDay,
|
||||
isWeekend,
|
||||
startOfMonth,
|
||||
startOfWeek,
|
||||
} from "date-fns";
|
||||
import dayjs from "dayjs";
|
||||
import React from "react";
|
||||
|
||||
interface DayProps {
|
||||
|
@ -45,52 +36,50 @@ export const useHeadlessDatePicker = (
|
|||
const [localSelection, setSelection] = React.useState<Date[]>([]);
|
||||
const selection = options?.selection ?? localSelection;
|
||||
const [localNavigationDate, setNavigationDate] = React.useState(today);
|
||||
const navigationDate = options?.date ?? localNavigationDate;
|
||||
const navigationDate = dayjs(options?.date ?? localNavigationDate);
|
||||
|
||||
const firstDayOfMonth = startOfMonth(navigationDate);
|
||||
const firstDayOfFirstWeek = startOfWeek(firstDayOfMonth, {
|
||||
weekStartsOn: options?.weekStartsOn === "monday" ? 1 : 0,
|
||||
});
|
||||
const firstDayOfMonth = navigationDate.startOf("month");
|
||||
const firstDayOfFirstWeek = firstDayOfMonth.startOf("week");
|
||||
|
||||
const currentMonth = getMonth(navigationDate);
|
||||
const currentMonth = navigationDate.get("month");
|
||||
|
||||
const days: DayProps[] = [];
|
||||
|
||||
const daysOfWeek: string[] = [];
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
daysOfWeek.push(format(addDays(firstDayOfFirstWeek, i), "EE"));
|
||||
daysOfWeek.push(firstDayOfFirstWeek.add(i, "days").format("dd"));
|
||||
}
|
||||
|
||||
let reachedEnd = false;
|
||||
let i = 0;
|
||||
do {
|
||||
const d = addDays(firstDayOfFirstWeek, i);
|
||||
const d = firstDayOfFirstWeek.add(i, "days");
|
||||
days.push({
|
||||
date: d,
|
||||
day: format(d, "d"),
|
||||
weekend: isWeekend(d),
|
||||
outOfMonth: getMonth(d) !== currentMonth,
|
||||
today: isSameDay(d, today),
|
||||
selected: selection.some((selectedDate) => isSameDay(selectedDate, d)),
|
||||
date: d.toDate(),
|
||||
day: d.format("D"),
|
||||
weekend: d.day() === 0 || d.day() === 6,
|
||||
outOfMonth: d.month() !== currentMonth,
|
||||
today: d.isSame(today, "day"),
|
||||
selected: selection.some((selectedDate) => d.isSame(selectedDate, "day")),
|
||||
});
|
||||
i++;
|
||||
reachedEnd =
|
||||
i > 34 && i % 7 === 0 && addDays(d, 1).getMonth() !== currentMonth;
|
||||
i > 34 && i % 7 === 0 && d.add(1, "day").month() !== currentMonth;
|
||||
} while (reachedEnd === false);
|
||||
|
||||
return {
|
||||
navigationDate,
|
||||
label: format(navigationDate, "MMMM yyyy"),
|
||||
navigationDate: navigationDate.toDate(),
|
||||
label: navigationDate.format("MMMM YYYY"),
|
||||
next: () => {
|
||||
const newDate = startOfMonth(addMonths(navigationDate, 1));
|
||||
const newDate = navigationDate.add(1, "month").startOf("month").toDate();
|
||||
if (!options?.date) {
|
||||
setNavigationDate(newDate);
|
||||
}
|
||||
options?.onNavigationChange?.(newDate);
|
||||
},
|
||||
prev: () => {
|
||||
const newDate = startOfMonth(addMonths(navigationDate, -1));
|
||||
const newDate = navigationDate.add(-1, "month").startOf("month").toDate();
|
||||
if (!options?.date) {
|
||||
setNavigationDate(newDate);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { format } from "date-fns";
|
||||
import dayjs from "dayjs";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import * as React from "react";
|
||||
|
||||
|
@ -66,11 +66,11 @@ const PollDemo: React.VoidFunctionComponent = () => {
|
|||
<div>
|
||||
<div className="font-semibold leading-9">
|
||||
<div className="text-sm uppercase text-slate-400">
|
||||
{format(d, "E")}
|
||||
{dayjs(d).format("ddd")}
|
||||
</div>
|
||||
<div className="text-2xl">{format(d, "dd")}</div>
|
||||
<div className="text-2xl">{dayjs(d).format("DD")}</div>
|
||||
<div className="text-xs font-medium uppercase text-slate-400/75">
|
||||
{format(d, "MMM")}
|
||||
{dayjs(d).format("MMM")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -63,8 +63,6 @@ export const PollContextProvider: React.VoidFunctionComponent<{
|
|||
const [targetTimeZone, setTargetTimeZone] =
|
||||
React.useState(getBrowserTimeZone);
|
||||
|
||||
const { locale } = usePreferences();
|
||||
|
||||
const getScore = React.useCallback(
|
||||
(optionId: string) => {
|
||||
return (participants ?? []).reduce(
|
||||
|
@ -88,6 +86,8 @@ export const PollContextProvider: React.VoidFunctionComponent<{
|
|||
[participants],
|
||||
);
|
||||
|
||||
const { timeFormat } = usePreferences();
|
||||
|
||||
const contextValue = React.useMemo<PollContextValue>(() => {
|
||||
const highScore = poll.options.reduce((acc, curr) => {
|
||||
const score = getScore(curr.id).yes;
|
||||
|
@ -99,7 +99,7 @@ export const PollContextProvider: React.VoidFunctionComponent<{
|
|||
poll.options,
|
||||
poll.timeZone,
|
||||
targetTimeZone,
|
||||
locale,
|
||||
timeFormat,
|
||||
);
|
||||
const getParticipantById = (participantId: string) => {
|
||||
// TODO (Luke Vella) [2022-04-16]: Build an index instead
|
||||
|
@ -164,10 +164,10 @@ export const PollContextProvider: React.VoidFunctionComponent<{
|
|||
admin,
|
||||
getScore,
|
||||
isDeleted,
|
||||
locale,
|
||||
participants,
|
||||
poll,
|
||||
targetTimeZone,
|
||||
timeFormat,
|
||||
urlId,
|
||||
user,
|
||||
]);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { format } from "date-fns";
|
||||
import dayjs from "dayjs";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import { usePoll } from "@/components/poll-context";
|
||||
|
@ -50,10 +50,7 @@ export const useCsvExporter = () => {
|
|||
link.setAttribute("href", encodedCsv);
|
||||
link.setAttribute(
|
||||
"download",
|
||||
`${poll.title.replace(/\s/g, "_")}-${format(
|
||||
Date.now(),
|
||||
"yyyyMMddhhmm",
|
||||
)}`,
|
||||
`${poll.title.replace(/\s/g, "_")}-${dayjs().format("YYYYMMDDHHmm")}`,
|
||||
);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
import { formatRelative } from "date-fns";
|
||||
import dayjs from "dayjs";
|
||||
import { Trans, useTranslation } from "next-i18next";
|
||||
import * as React from "react";
|
||||
|
||||
import Badge from "../badge";
|
||||
import { usePoll } from "../poll-context";
|
||||
import { usePreferences } from "../preferences/use-preferences";
|
||||
import Tooltip from "../tooltip";
|
||||
|
||||
const PollSubheader: React.VoidFunctionComponent = () => {
|
||||
const { poll } = usePoll();
|
||||
const { t } = useTranslation("app");
|
||||
const { locale } = usePreferences();
|
||||
|
||||
return (
|
||||
<div className="text-slate-500/75 lg:text-lg">
|
||||
|
@ -45,9 +43,7 @@ const PollSubheader: React.VoidFunctionComponent = () => {
|
|||
</div>
|
||||
<span className="hidden md:inline"> • </span>
|
||||
<span className="whitespace-nowrap">
|
||||
{formatRelative(poll.createdAt, new Date(), {
|
||||
locale,
|
||||
})}
|
||||
{dayjs(poll.createdAt).fromNow()}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,15 +1,32 @@
|
|||
import { Locale } from "date-fns";
|
||||
import enGB from "date-fns/locale/en-GB";
|
||||
import enUS from "date-fns/locale/en-US";
|
||||
import dayjs from "dayjs";
|
||||
import en from "dayjs/locale/en";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import isBetween from "dayjs/plugin/isBetween";
|
||||
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
|
||||
import localeData from "dayjs/plugin/localeData";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import minMax from "dayjs/plugin/minMax";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import * as React from "react";
|
||||
import { useLocalStorage } from "react-use";
|
||||
|
||||
type TimeFormat = "12h" | "24h";
|
||||
type StartOfWeek = "monday" | "sunday";
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(relativeTime);
|
||||
dayjs.extend(localeData);
|
||||
dayjs.extend(isSameOrBefore);
|
||||
dayjs.extend(isBetween);
|
||||
dayjs.extend(minMax);
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(duration);
|
||||
|
||||
export const PreferencesContext =
|
||||
React.createContext<{
|
||||
locale: Locale;
|
||||
weekStartsOn: StartOfWeek;
|
||||
timeFormat: TimeFormat;
|
||||
setWeekStartsOn: React.Dispatch<
|
||||
|
@ -29,13 +46,18 @@ const PreferencesProvider: React.VoidFunctionComponent<{
|
|||
const [timeFormat = "12h", setTimeFormat] =
|
||||
useLocalStorage<TimeFormat>("rallly-time-format");
|
||||
|
||||
dayjs.locale({
|
||||
...en,
|
||||
weekStart: weekStartsOn === "monday" ? 1 : 0,
|
||||
formats: { LT: timeFormat === "12h" ? "h:mm A" : "HH:mm" },
|
||||
});
|
||||
|
||||
const contextValue = React.useMemo(
|
||||
() => ({
|
||||
weekStartsOn,
|
||||
timeFormat,
|
||||
setWeekStartsOn,
|
||||
setTimeFormat,
|
||||
locale: timeFormat === "12h" ? enUS : enGB,
|
||||
}),
|
||||
[setTimeFormat, setWeekStartsOn, timeFormat, weekStartsOn],
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { formatRelative } from "date-fns";
|
||||
import dayjs from "dayjs";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
@ -83,7 +83,7 @@ export const Profile: React.VoidFunctionComponent = () => {
|
|||
</Link>
|
||||
</div>
|
||||
<div className="ml-7 text-sm text-slate-500">
|
||||
{formatRelative(poll.createdAt, new Date())}
|
||||
{dayjs(poll.createdAt).fromNow()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Prisma } from "@prisma/client";
|
||||
import { addDays } from "date-fns";
|
||||
import dayjs from "dayjs";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { prisma } from "~/prisma/db";
|
||||
|
@ -29,7 +29,7 @@ export default async function handler(
|
|||
where: {
|
||||
deleted: false,
|
||||
touchedAt: {
|
||||
lte: addDays(new Date(), -30),
|
||||
lte: dayjs().add(-30, "days").toDate(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -42,14 +42,14 @@ export default async function handler(
|
|||
{
|
||||
deleted: true,
|
||||
deletedAt: {
|
||||
lte: addDays(new Date(), -7),
|
||||
lte: dayjs().add(-7, "days").toDate(),
|
||||
},
|
||||
},
|
||||
// demo polls that are 1 day old
|
||||
{
|
||||
demo: true,
|
||||
createdAt: {
|
||||
lte: addDays(new Date(), -1),
|
||||
lte: dayjs().add(-1, "days").toDate(),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { VoteType } from "@prisma/client";
|
||||
import addMinutes from "date-fns/addMinutes";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { prisma } from "~/prisma/db";
|
||||
|
||||
|
@ -31,7 +31,6 @@ export const demo = createRouter().mutation("create", {
|
|||
resolve: async () => {
|
||||
const adminUrlId = await nanoid();
|
||||
const demoUser = { name: "John Example", email: "noreply@rallly.co" };
|
||||
const today = new Date();
|
||||
|
||||
const options: Array<{ value: string; id: string }> = [];
|
||||
|
||||
|
@ -59,7 +58,9 @@ export const demo = createRouter().mutation("create", {
|
|||
id: participantId,
|
||||
name,
|
||||
userId: "user-demo",
|
||||
createdAt: addMinutes(today, i * -1),
|
||||
createdAt: dayjs()
|
||||
.add(i * -1, "minutes")
|
||||
.toDate(),
|
||||
});
|
||||
|
||||
options.forEach((option, index) => {
|
||||
|
|
|
@ -1,14 +1,5 @@
|
|||
import { Option } from "@prisma/client";
|
||||
import {
|
||||
differenceInHours,
|
||||
differenceInMinutes,
|
||||
format,
|
||||
formatDuration,
|
||||
isSameDay,
|
||||
Locale,
|
||||
} from "date-fns";
|
||||
import { formatInTimeZone } from "date-fns-tz";
|
||||
import spacetime from "spacetime";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import {
|
||||
DateTimeOption,
|
||||
|
@ -49,19 +40,28 @@ export type ParsedDateTimeOpton = ParsedDateOption | ParsedTimeSlotOption;
|
|||
|
||||
const isTimeSlot = (value: string) => value.indexOf("/") !== -1;
|
||||
|
||||
const getDuration = (startTime: Date, endTime: Date) => {
|
||||
const hours = Math.floor(differenceInHours(endTime, startTime));
|
||||
const minutes = Math.floor(
|
||||
differenceInMinutes(endTime, startTime) - hours * 60,
|
||||
);
|
||||
return formatDuration({ hours, minutes });
|
||||
export const getDuration = (startTime: dayjs.Dayjs, endTime: dayjs.Dayjs) => {
|
||||
const hours = Math.floor(endTime.diff(startTime, "hours"));
|
||||
const minutes = Math.floor(endTime.diff(startTime, "minute") - hours * 60);
|
||||
let res = "";
|
||||
if (hours) {
|
||||
res += `${hours}h`;
|
||||
}
|
||||
if (hours && minutes) {
|
||||
res += " ";
|
||||
}
|
||||
if (minutes) {
|
||||
res += `${minutes}m`;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
export const decodeOptions = (
|
||||
options: Option[],
|
||||
timeZone: string | null,
|
||||
targetTimeZone: string,
|
||||
locale: Locale,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_timeFormat: string, // 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[] } => {
|
||||
|
@ -71,7 +71,7 @@ export const decodeOptions = (
|
|||
return {
|
||||
pollType,
|
||||
options: options.map((option) =>
|
||||
parseTimeSlotOption(option, timeZone, targetTimeZone, locale),
|
||||
parseTimeSlotOption(option, timeZone, targetTimeZone),
|
||||
),
|
||||
};
|
||||
} else {
|
||||
|
@ -88,14 +88,14 @@ const parseDateOption = (option: Option): ParsedDateOption => {
|
|||
? // we add the time because otherwise Date will assume UTC time which might change the day for some time zones
|
||||
option.value + "T00:00:00"
|
||||
: option.value;
|
||||
const date = new Date(dateString);
|
||||
const date = dayjs(dateString);
|
||||
return {
|
||||
type: "date",
|
||||
optionId: option.id,
|
||||
day: format(date, "d"),
|
||||
dow: format(date, "EEE"),
|
||||
month: format(date, "MMM"),
|
||||
year: format(date, "yyyy"),
|
||||
day: date.format("D"),
|
||||
dow: date.format("ddd"),
|
||||
month: date.format("MMM"),
|
||||
year: date.format("YYYY"),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -103,48 +103,29 @@ const parseTimeSlotOption = (
|
|||
option: Option,
|
||||
timeZone: string | null,
|
||||
targetTimeZone: string,
|
||||
locale: Locale,
|
||||
): ParsedTimeSlotOption => {
|
||||
const localeFormatInTimezone = (
|
||||
date: Date,
|
||||
timezone: string,
|
||||
formatString: string,
|
||||
) => {
|
||||
return formatInTimeZone(date, timezone, formatString, {
|
||||
locale,
|
||||
});
|
||||
};
|
||||
|
||||
const [start, end] = option.value.split("/");
|
||||
if (timeZone && targetTimeZone) {
|
||||
const startDate = spacetime(start, timeZone).toNativeDate();
|
||||
const endDate = spacetime(end, timeZone).toNativeDate();
|
||||
return {
|
||||
type: "timeSlot",
|
||||
optionId: option.id,
|
||||
startTime: localeFormatInTimezone(startDate, targetTimeZone, "p"),
|
||||
endTime: localeFormatInTimezone(endDate, targetTimeZone, "p"),
|
||||
day: localeFormatInTimezone(startDate, targetTimeZone, "d"),
|
||||
dow: localeFormatInTimezone(startDate, targetTimeZone, "EEE"),
|
||||
month: localeFormatInTimezone(startDate, targetTimeZone, "MMM"),
|
||||
duration: getDuration(startDate, endDate),
|
||||
year: localeFormatInTimezone(startDate, targetTimeZone, "yyyy"),
|
||||
};
|
||||
} else {
|
||||
const startDate = new Date(start);
|
||||
const endDate = new Date(end);
|
||||
return {
|
||||
type: "timeSlot",
|
||||
optionId: option.id,
|
||||
startTime: format(startDate, "p"),
|
||||
endTime: format(endDate, "p"),
|
||||
day: format(startDate, "d"),
|
||||
dow: format(startDate, "E"),
|
||||
month: format(startDate, "MMM"),
|
||||
duration: getDuration(startDate, endDate),
|
||||
year: format(startDate, "yyyy"),
|
||||
};
|
||||
}
|
||||
|
||||
const startDate =
|
||||
timeZone && targetTimeZone
|
||||
? dayjs(start).tz(timeZone, true).tz(targetTimeZone)
|
||||
: dayjs(start);
|
||||
const endDate =
|
||||
timeZone && targetTimeZone
|
||||
? dayjs(end).tz(timeZone, true).tz(targetTimeZone)
|
||||
: dayjs(end);
|
||||
|
||||
return {
|
||||
type: "timeSlot",
|
||||
optionId: option.id,
|
||||
startTime: startDate.format("LT"),
|
||||
endTime: endDate.format("LT"),
|
||||
day: startDate.format("D"),
|
||||
dow: startDate.format("ddd"),
|
||||
month: startDate.format("MMM"),
|
||||
duration: getDuration(startDate, endDate),
|
||||
year: startDate.format("YYYY"),
|
||||
};
|
||||
};
|
||||
|
||||
export const removeAllOptionsForDay = (
|
||||
|
@ -152,18 +133,19 @@ export const removeAllOptionsForDay = (
|
|||
date: Date,
|
||||
) => {
|
||||
return options.filter((option) => {
|
||||
const optionDate = spacetime(
|
||||
const optionDate = new Date(
|
||||
option.type === "date" ? option.date : option.start,
|
||||
).toNativeDate();
|
||||
return !isSameDay(date, optionDate);
|
||||
);
|
||||
return !dayjs(date).isSame(optionDate, "day");
|
||||
});
|
||||
};
|
||||
|
||||
export const getDateProps = (date: Date) => {
|
||||
const d = dayjs(date);
|
||||
return {
|
||||
day: format(date, "d"),
|
||||
dow: format(date, "E"),
|
||||
month: format(date, "MMM"),
|
||||
day: d.format("D"),
|
||||
dow: d.format("ddd"),
|
||||
month: d.format("MMM"),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { addDays } from "date-fns";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { prisma } from "../prisma/db";
|
||||
|
||||
|
@ -29,7 +29,7 @@ test.beforeAll(async ({ request, baseURL }) => {
|
|||
type: "date",
|
||||
userId: "user1",
|
||||
deleted: true,
|
||||
deletedAt: addDays(new Date(), -6),
|
||||
deletedAt: dayjs().add(-6, "days").toDate(),
|
||||
participantUrlId: "p2",
|
||||
adminUrlId: "a2",
|
||||
},
|
||||
|
@ -40,7 +40,7 @@ test.beforeAll(async ({ request, baseURL }) => {
|
|||
type: "date",
|
||||
userId: "user1",
|
||||
deleted: true,
|
||||
deletedAt: addDays(new Date(), -7),
|
||||
deletedAt: dayjs().add(-7, "days").toDate(),
|
||||
participantUrlId: "p3",
|
||||
adminUrlId: "a3",
|
||||
},
|
||||
|
@ -50,7 +50,7 @@ test.beforeAll(async ({ request, baseURL }) => {
|
|||
id: "still-active-poll",
|
||||
type: "date",
|
||||
userId: "user1",
|
||||
touchedAt: addDays(new Date(), -29),
|
||||
touchedAt: dayjs().add(-29, "days").toDate(),
|
||||
participantUrlId: "p4",
|
||||
adminUrlId: "a4",
|
||||
},
|
||||
|
@ -60,7 +60,7 @@ test.beforeAll(async ({ request, baseURL }) => {
|
|||
id: "inactive-poll",
|
||||
type: "date",
|
||||
userId: "user1",
|
||||
touchedAt: addDays(new Date(), -30),
|
||||
touchedAt: dayjs().add(-30, "days").toDate(),
|
||||
participantUrlId: "p5",
|
||||
adminUrlId: "a5",
|
||||
},
|
||||
|
@ -82,7 +82,7 @@ test.beforeAll(async ({ request, baseURL }) => {
|
|||
id: "demo-poll-old",
|
||||
type: "date",
|
||||
userId: "user1",
|
||||
createdAt: addDays(new Date(), -2),
|
||||
createdAt: dayjs().add(-2, "days").toDate(),
|
||||
participantUrlId: "p7",
|
||||
adminUrlId: "a7",
|
||||
},
|
||||
|
|
21
yarn.lock
21
yarn.lock
|
@ -2527,15 +2527,10 @@ date-arithmetic@^4.1.0:
|
|||
resolved "https://registry.npmjs.org/date-arithmetic/-/date-arithmetic-4.1.0.tgz"
|
||||
integrity sha512-QWxYLR5P/6GStZcdem+V1xoto6DMadYWpMXU82ES3/RfR3Wdwr3D0+be7mgOJ+Ov0G9D5Dmb9T17sNLQYj9XOg==
|
||||
|
||||
date-fns-tz@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.2.2.tgz#89432b54ce3fa7d050a2039e997e5b6a96df35dd"
|
||||
integrity sha512-vWtn44eEqnLbkACb7T5G5gPgKR4nY8NkNMOCyoY49NsRGHrcDmY2aysCyzDeA+u+vcDBn/w6nQqEDyouRs4m8w==
|
||||
|
||||
date-fns@^2.28.0:
|
||||
version "2.28.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2"
|
||||
integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==
|
||||
dayjs@^1.11.3:
|
||||
version "1.11.3"
|
||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.3.tgz#4754eb694a624057b9ad2224b67b15d552589258"
|
||||
integrity sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A==
|
||||
|
||||
debug@4:
|
||||
version "4.3.3"
|
||||
|
@ -5023,10 +5018,10 @@ sourcemap-codec@1.4.8, sourcemap-codec@^1.4.8:
|
|||
resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz"
|
||||
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
||||
|
||||
spacetime@^7.1.2:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/spacetime/-/spacetime-7.1.2.tgz#73a94f0ba7f0c0b2230259b5ccf78dd9fd34cd16"
|
||||
integrity sha512-MUTgK9KU9gMXhZddwe0nlgFnCJXT4RfuIRqyo8VcUexZa94zkxk1WpVv3THgvMFz1+Hq9okoNiGTvj6qBdN4Cg==
|
||||
spacetime@^7.1.4:
|
||||
version "7.1.4"
|
||||
resolved "https://registry.yarnpkg.com/spacetime/-/spacetime-7.1.4.tgz#f288e4dcecbb0ec56e76c3b2a09c55badc18cebe"
|
||||
integrity sha512-ZzYuGjaMPE42p/fVU9Qcd+aMhgYzC83hgqMKyWlGILtponsLm5KJZgWzblLnOU8OblMQa3YXo/0IiZZ5JsTDfA==
|
||||
|
||||
spdx-correct@^3.0.0:
|
||||
version "3.1.1"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue