mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-20 17:57:22 +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",
|
"@trpc/server": "^9.23.2",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"date-fns": "^2.28.0",
|
"dayjs": "^1.11.3",
|
||||||
"date-fns-tz": "^1.2.2",
|
|
||||||
"eta": "^1.12.3",
|
"eta": "^1.12.3",
|
||||||
"framer-motion": "^6.3.11",
|
"framer-motion": "^6.3.11",
|
||||||
"iron-session": "^6.1.3",
|
"iron-session": "^6.1.3",
|
||||||
|
@ -52,7 +51,7 @@
|
||||||
"react-query": "^3.34.12",
|
"react-query": "^3.34.12",
|
||||||
"react-use": "^17.3.2",
|
"react-use": "^17.3.2",
|
||||||
"smoothscroll-polyfill": "^0.4.4",
|
"smoothscroll-polyfill": "^0.4.4",
|
||||||
"spacetime": "^7.1.2",
|
"spacetime": "^7.1.4",
|
||||||
"superjson": "^1.9.1",
|
"superjson": "^1.9.1",
|
||||||
"timezone-soft": "^1.3.1",
|
"timezone-soft": "^1.3.1",
|
||||||
"typescript": "^4.5.2",
|
"typescript": "^4.5.2",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { formatRelative } from "date-fns";
|
import dayjs from "dayjs";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { usePlausible } from "next-plausible";
|
import { usePlausible } from "next-plausible";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
@ -16,7 +16,6 @@ import NameInput from "../name-input";
|
||||||
import TruncatedLinkify from "../poll/truncated-linkify";
|
import TruncatedLinkify from "../poll/truncated-linkify";
|
||||||
import UserAvatar from "../poll/user-avatar";
|
import UserAvatar from "../poll/user-avatar";
|
||||||
import { usePoll } from "../poll-context";
|
import { usePoll } from "../poll-context";
|
||||||
import { usePreferences } from "../preferences/use-preferences";
|
|
||||||
import { isUnclaimed, useSession } from "../session";
|
import { isUnclaimed, useSession } from "../session";
|
||||||
|
|
||||||
interface CommentForm {
|
interface CommentForm {
|
||||||
|
@ -25,7 +24,6 @@ interface CommentForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Discussion: React.VoidFunctionComponent = () => {
|
const Discussion: React.VoidFunctionComponent = () => {
|
||||||
const { locale } = usePreferences();
|
|
||||||
const queryClient = trpc.useContext();
|
const queryClient = trpc.useContext();
|
||||||
const { poll } = usePoll();
|
const { poll } = usePoll();
|
||||||
|
|
||||||
|
@ -122,13 +120,7 @@ const Discussion: React.VoidFunctionComponent = () => {
|
||||||
<div className="mb-1">
|
<div className="mb-1">
|
||||||
<span className="mr-1 text-slate-400">•</span>
|
<span className="mr-1 text-slate-400">•</span>
|
||||||
<span className="text-sm text-slate-500">
|
<span className="text-sm text-slate-500">
|
||||||
{formatRelative(
|
{dayjs(new Date(comment.createdAt)).fromNow()}
|
||||||
new Date(comment.createdAt),
|
|
||||||
Date.now(),
|
|
||||||
{
|
|
||||||
locale,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Dropdown
|
<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 clsx from "clsx";
|
||||||
import differenceInMinutes from "date-fns/differenceInMinutes";
|
import dayjs from "dayjs";
|
||||||
import { addMinutes, setHours } from "date-fns/esm";
|
|
||||||
import isSameDay from "date-fns/isSameDay";
|
|
||||||
import { usePlausible } from "next-plausible";
|
import { usePlausible } from "next-plausible";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
|
@ -126,12 +124,14 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (
|
if (
|
||||||
datepicker.selection.some((selectedDate) =>
|
datepicker.selection.some((selectedDate) =>
|
||||||
isSameDay(selectedDate, day.date),
|
dayjs(selectedDate).isSame(day.date, "day"),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
onChange(removeAllOptionsForDay(options, day.date));
|
onChange(removeAllOptionsForDay(options, day.date));
|
||||||
} else {
|
} else {
|
||||||
const selectedDate = setHours(day.date, 12);
|
const selectedDate = dayjs(day.date)
|
||||||
|
.set("hour", 12)
|
||||||
|
.toDate();
|
||||||
const newOption: DateTimeOption = !isTimedEvent
|
const newOption: DateTimeOption = !isTimedEvent
|
||||||
? {
|
? {
|
||||||
type: "date",
|
type: "date",
|
||||||
|
@ -141,7 +141,9 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
||||||
type: "timeSlot",
|
type: "timeSlot",
|
||||||
start: formatDateWithoutTz(selectedDate),
|
start: formatDateWithoutTz(selectedDate),
|
||||||
end: formatDateWithoutTz(
|
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 startDate = new Date(`${option.date}T12:00:00`);
|
||||||
const endDate = addMinutes(startDate, duration);
|
const endDate = dayjs(startDate)
|
||||||
|
.add(duration, "minutes")
|
||||||
|
.toDate();
|
||||||
return {
|
return {
|
||||||
type: "timeSlot",
|
type: "timeSlot",
|
||||||
start: formatDateWithoutTz(startDate),
|
start: formatDateWithoutTz(startDate),
|
||||||
|
@ -260,7 +264,9 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
||||||
<TimePicker
|
<TimePicker
|
||||||
value={startDate}
|
value={startDate}
|
||||||
onChange={(newStart) => {
|
onChange={(newStart) => {
|
||||||
const newEnd = addMinutes(newStart, duration);
|
const newEnd = dayjs(newStart)
|
||||||
|
.add(duration, "minutes")
|
||||||
|
.toDate();
|
||||||
// replace enter with updated start time
|
// replace enter with updated start time
|
||||||
onChange([
|
onChange([
|
||||||
...options.slice(0, index),
|
...options.slice(0, index),
|
||||||
|
@ -273,13 +279,15 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
||||||
]);
|
]);
|
||||||
onNavigate(newStart);
|
onNavigate(newStart);
|
||||||
onChangeDuration(
|
onChangeDuration(
|
||||||
differenceInMinutes(newEnd, newStart),
|
dayjs(newEnd).diff(newStart, "minutes"),
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TimePicker
|
<TimePicker
|
||||||
value={new Date(option.end)}
|
value={new Date(option.end)}
|
||||||
startFrom={addMinutes(startDate, 15)}
|
startFrom={dayjs(startDate)
|
||||||
|
.add(15, "minutes")
|
||||||
|
.toDate()}
|
||||||
onChange={(newEnd) => {
|
onChange={(newEnd) => {
|
||||||
onChange([
|
onChange([
|
||||||
...options.slice(0, index),
|
...options.slice(0, index),
|
||||||
|
@ -291,7 +299,7 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
||||||
]);
|
]);
|
||||||
onNavigate(newEnd);
|
onNavigate(newEnd);
|
||||||
onChangeDuration(
|
onChangeDuration(
|
||||||
differenceInMinutes(newEnd, startDate),
|
dayjs(newEnd).diff(startDate, "minutes"),
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -322,7 +330,9 @@ const MonthCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
||||||
type: "timeSlot",
|
type: "timeSlot",
|
||||||
start: startTime,
|
start: startTime,
|
||||||
end: formatDateWithoutTz(
|
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";
|
} from "@floating-ui/react-dom-interactions";
|
||||||
import { Listbox } from "@headlessui/react";
|
import { Listbox } from "@headlessui/react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { addMinutes, format, isSameDay, setHours, setMinutes } from "date-fns";
|
import dayjs from "dayjs";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import { usePreferences } from "@/components/preferences/use-preferences";
|
|
||||||
import { stopPropagation } from "@/utils/stop-propagation";
|
import { stopPropagation } from "@/utils/stop-propagation";
|
||||||
|
|
||||||
import ChevronDown from "../../../icons/chevron-down.svg";
|
import ChevronDown from "../../../icons/chevron-down.svg";
|
||||||
|
@ -27,9 +26,8 @@ const TimePicker: React.VoidFunctionComponent<TimePickerProps> = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
className,
|
className,
|
||||||
startFrom = setMinutes(setHours(value, 0), 0),
|
startFrom,
|
||||||
}) => {
|
}) => {
|
||||||
const { locale } = usePreferences();
|
|
||||||
const { reference, floating, x, y, strategy, refs } = useFloating({
|
const { reference, floating, x, y, strategy, refs } = useFloating({
|
||||||
strategy: "fixed",
|
strategy: "fixed",
|
||||||
middleware: [
|
middleware: [
|
||||||
|
@ -47,10 +45,14 @@ const TimePicker: React.VoidFunctionComponent<TimePickerProps> = ({
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const startFromDate = startFrom
|
||||||
|
? dayjs(startFrom)
|
||||||
|
: dayjs(value).startOf("day");
|
||||||
|
|
||||||
const options: React.ReactNode[] = [];
|
const options: React.ReactNode[] = [];
|
||||||
for (let i = 0; i < 96; i++) {
|
for (let i = 0; i < 96; i++) {
|
||||||
const optionValue = addMinutes(startFrom, i * 15);
|
const optionValue = startFromDate.add(i * 15, "minutes");
|
||||||
if (!isSameDay(value, optionValue)) {
|
if (!optionValue.isSame(value, "day")) {
|
||||||
// we only support event that start and end on the same day for now
|
// 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
|
// because react-big-calendar does not support events that span days
|
||||||
break;
|
break;
|
||||||
|
@ -61,7 +63,7 @@ const TimePicker: React.VoidFunctionComponent<TimePickerProps> = ({
|
||||||
className={styleMenuItem}
|
className={styleMenuItem}
|
||||||
value={optionValue.toISOString()}
|
value={optionValue.toISOString()}
|
||||||
>
|
>
|
||||||
{format(optionValue, "p", { locale })}
|
{optionValue.format("LT")}
|
||||||
</Listbox.Option>,
|
</Listbox.Option>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -77,9 +79,7 @@ const TimePicker: React.VoidFunctionComponent<TimePickerProps> = ({
|
||||||
<>
|
<>
|
||||||
<div ref={reference} className={clsx("relative", className)}>
|
<div ref={reference} className={clsx("relative", className)}>
|
||||||
<Listbox.Button className="btn-default text-left">
|
<Listbox.Button className="btn-default text-left">
|
||||||
<span className="grow truncate">
|
<span className="grow truncate">{dayjs(value).format("LT")}</span>
|
||||||
{format(value, "p", { locale })}
|
|
||||||
</span>
|
|
||||||
<span className="pointer-events-none ml-2 flex">
|
<span className="pointer-events-none ml-2 flex">
|
||||||
<ChevronDown className="h-5 w-5" />
|
<ChevronDown className="h-5 w-5" />
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { format } from "date-fns";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
export const formatDateWithoutTz = (date: Date): string => {
|
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 => {
|
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 clsx from "clsx";
|
||||||
import {
|
import dayjs from "dayjs";
|
||||||
addMinutes,
|
|
||||||
differenceInMinutes,
|
|
||||||
format,
|
|
||||||
getDay,
|
|
||||||
parse,
|
|
||||||
startOfWeek,
|
|
||||||
} from "date-fns";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Calendar, dateFnsLocalizer } from "react-big-calendar";
|
import { Calendar } from "react-big-calendar";
|
||||||
import { useMount } from "react-use";
|
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 DateNavigationToolbar from "./date-navigation-toolbar";
|
||||||
|
import dayjsLocalizer from "./dayjs-localizer";
|
||||||
import { DateTimeOption, DateTimePickerProps } from "./types";
|
import { DateTimeOption, DateTimePickerProps } from "./types";
|
||||||
import { formatDateWithoutTime, formatDateWithoutTz } from "./utils";
|
import { formatDateWithoutTime, formatDateWithoutTz } from "./utils";
|
||||||
|
|
||||||
|
const localizer = dayjsLocalizer(dayjs);
|
||||||
|
|
||||||
const WeekCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
const WeekCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
||||||
title,
|
title,
|
||||||
options,
|
options,
|
||||||
|
@ -28,30 +24,12 @@ const WeekCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [scrollToTime, setScrollToTime] = React.useState<Date>();
|
const [scrollToTime, setScrollToTime] = React.useState<Date>();
|
||||||
|
|
||||||
|
const { timeFormat } = usePreferences();
|
||||||
useMount(() => {
|
useMount(() => {
|
||||||
// Bit of a hack to force rbc to scroll to the right time when we close/open a modal
|
// 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 (
|
return (
|
||||||
<Calendar
|
<Calendar
|
||||||
key={timeFormat}
|
key={timeFormat}
|
||||||
|
@ -106,11 +84,13 @@ const WeekCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
eventWrapper: (props) => {
|
eventWrapper: (props) => {
|
||||||
|
const start = dayjs(props.event.start);
|
||||||
|
const end = dayjs(props.event.end);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
// onClick prop doesn't work properly. Seems like some other element is cancelling the event before it reaches this element
|
// onClick prop doesn't work properly. Seems like some other element is cancelling the event before it reaches this element
|
||||||
onMouseUp={props.onClick}
|
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={{
|
style={{
|
||||||
top: `calc(${props.style?.top}% + 4px)`,
|
top: `calc(${props.style?.top}% + 4px)`,
|
||||||
height: `calc(${props.style?.height}% - 8px)`,
|
height: `calc(${props.style?.height}% - 8px)`,
|
||||||
|
@ -118,10 +98,8 @@ const WeekCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
||||||
width: `calc(${props.style?.width}%)`,
|
width: `calc(${props.style?.width}%)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>{format(props.event.start, "p", { locale })}</div>
|
<div>{start.format("LT")}</div>
|
||||||
<div className="w-full truncate font-bold">
|
<div className="font-semibold">{getDuration(start, end)}</div>
|
||||||
{props.event.title}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -158,15 +136,15 @@ const WeekCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="mr-1 font-normal opacity-50">
|
<span className="mr-1 font-normal opacity-50">
|
||||||
{format(date, "E")}
|
{dayjs(date).format("ddd")}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-medium">{format(date, "dd")}</span>
|
<span className="font-medium">{dayjs(date).format("DD")}</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
timeSlotWrapper: ({ children }) => {
|
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}
|
step={15}
|
||||||
|
@ -182,12 +160,14 @@ const WeekCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
if (action === "select") {
|
if (action === "select") {
|
||||||
const diff = differenceInMinutes(endDate, startDate);
|
const diff = dayjs(endDate).diff(startDate, "minutes");
|
||||||
if (diff < 60 * 24) {
|
if (diff < 60 * 24) {
|
||||||
onChangeDuration(diff);
|
onChangeDuration(diff);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newEvent.end = formatDateWithoutTz(addMinutes(startDate, duration));
|
newEvent.end = formatDateWithoutTz(
|
||||||
|
dayjs(startDate).add(duration, "minutes").toDate(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const alreadyExists = options.some(
|
const alreadyExists = options.some(
|
||||||
|
|
|
@ -1,13 +1,4 @@
|
||||||
import {
|
import dayjs from "dayjs";
|
||||||
addDays,
|
|
||||||
addMonths,
|
|
||||||
format,
|
|
||||||
getMonth,
|
|
||||||
isSameDay,
|
|
||||||
isWeekend,
|
|
||||||
startOfMonth,
|
|
||||||
startOfWeek,
|
|
||||||
} from "date-fns";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
interface DayProps {
|
interface DayProps {
|
||||||
|
@ -45,52 +36,50 @@ export const useHeadlessDatePicker = (
|
||||||
const [localSelection, setSelection] = React.useState<Date[]>([]);
|
const [localSelection, setSelection] = React.useState<Date[]>([]);
|
||||||
const selection = options?.selection ?? localSelection;
|
const selection = options?.selection ?? localSelection;
|
||||||
const [localNavigationDate, setNavigationDate] = React.useState(today);
|
const [localNavigationDate, setNavigationDate] = React.useState(today);
|
||||||
const navigationDate = options?.date ?? localNavigationDate;
|
const navigationDate = dayjs(options?.date ?? localNavigationDate);
|
||||||
|
|
||||||
const firstDayOfMonth = startOfMonth(navigationDate);
|
const firstDayOfMonth = navigationDate.startOf("month");
|
||||||
const firstDayOfFirstWeek = startOfWeek(firstDayOfMonth, {
|
const firstDayOfFirstWeek = firstDayOfMonth.startOf("week");
|
||||||
weekStartsOn: options?.weekStartsOn === "monday" ? 1 : 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentMonth = getMonth(navigationDate);
|
const currentMonth = navigationDate.get("month");
|
||||||
|
|
||||||
const days: DayProps[] = [];
|
const days: DayProps[] = [];
|
||||||
|
|
||||||
const daysOfWeek: string[] = [];
|
const daysOfWeek: string[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < 7; i++) {
|
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 reachedEnd = false;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
do {
|
do {
|
||||||
const d = addDays(firstDayOfFirstWeek, i);
|
const d = firstDayOfFirstWeek.add(i, "days");
|
||||||
days.push({
|
days.push({
|
||||||
date: d,
|
date: d.toDate(),
|
||||||
day: format(d, "d"),
|
day: d.format("D"),
|
||||||
weekend: isWeekend(d),
|
weekend: d.day() === 0 || d.day() === 6,
|
||||||
outOfMonth: getMonth(d) !== currentMonth,
|
outOfMonth: d.month() !== currentMonth,
|
||||||
today: isSameDay(d, today),
|
today: d.isSame(today, "day"),
|
||||||
selected: selection.some((selectedDate) => isSameDay(selectedDate, d)),
|
selected: selection.some((selectedDate) => d.isSame(selectedDate, "day")),
|
||||||
});
|
});
|
||||||
i++;
|
i++;
|
||||||
reachedEnd =
|
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);
|
} while (reachedEnd === false);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
navigationDate,
|
navigationDate: navigationDate.toDate(),
|
||||||
label: format(navigationDate, "MMMM yyyy"),
|
label: navigationDate.format("MMMM YYYY"),
|
||||||
next: () => {
|
next: () => {
|
||||||
const newDate = startOfMonth(addMonths(navigationDate, 1));
|
const newDate = navigationDate.add(1, "month").startOf("month").toDate();
|
||||||
if (!options?.date) {
|
if (!options?.date) {
|
||||||
setNavigationDate(newDate);
|
setNavigationDate(newDate);
|
||||||
}
|
}
|
||||||
options?.onNavigationChange?.(newDate);
|
options?.onNavigationChange?.(newDate);
|
||||||
},
|
},
|
||||||
prev: () => {
|
prev: () => {
|
||||||
const newDate = startOfMonth(addMonths(navigationDate, -1));
|
const newDate = navigationDate.add(-1, "month").startOf("month").toDate();
|
||||||
if (!options?.date) {
|
if (!options?.date) {
|
||||||
setNavigationDate(newDate);
|
setNavigationDate(newDate);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { format } from "date-fns";
|
import dayjs from "dayjs";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
|
@ -66,11 +66,11 @@ const PollDemo: React.VoidFunctionComponent = () => {
|
||||||
<div>
|
<div>
|
||||||
<div className="font-semibold leading-9">
|
<div className="font-semibold leading-9">
|
||||||
<div className="text-sm uppercase text-slate-400">
|
<div className="text-sm uppercase text-slate-400">
|
||||||
{format(d, "E")}
|
{dayjs(d).format("ddd")}
|
||||||
</div>
|
</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">
|
<div className="text-xs font-medium uppercase text-slate-400/75">
|
||||||
{format(d, "MMM")}
|
{dayjs(d).format("MMM")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -63,8 +63,6 @@ export const PollContextProvider: React.VoidFunctionComponent<{
|
||||||
const [targetTimeZone, setTargetTimeZone] =
|
const [targetTimeZone, setTargetTimeZone] =
|
||||||
React.useState(getBrowserTimeZone);
|
React.useState(getBrowserTimeZone);
|
||||||
|
|
||||||
const { locale } = usePreferences();
|
|
||||||
|
|
||||||
const getScore = React.useCallback(
|
const getScore = React.useCallback(
|
||||||
(optionId: string) => {
|
(optionId: string) => {
|
||||||
return (participants ?? []).reduce(
|
return (participants ?? []).reduce(
|
||||||
|
@ -88,6 +86,8 @@ export const PollContextProvider: React.VoidFunctionComponent<{
|
||||||
[participants],
|
[participants],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { timeFormat } = usePreferences();
|
||||||
|
|
||||||
const contextValue = React.useMemo<PollContextValue>(() => {
|
const contextValue = React.useMemo<PollContextValue>(() => {
|
||||||
const highScore = poll.options.reduce((acc, curr) => {
|
const highScore = poll.options.reduce((acc, curr) => {
|
||||||
const score = getScore(curr.id).yes;
|
const score = getScore(curr.id).yes;
|
||||||
|
@ -99,7 +99,7 @@ export const PollContextProvider: React.VoidFunctionComponent<{
|
||||||
poll.options,
|
poll.options,
|
||||||
poll.timeZone,
|
poll.timeZone,
|
||||||
targetTimeZone,
|
targetTimeZone,
|
||||||
locale,
|
timeFormat,
|
||||||
);
|
);
|
||||||
const getParticipantById = (participantId: string) => {
|
const getParticipantById = (participantId: string) => {
|
||||||
// TODO (Luke Vella) [2022-04-16]: Build an index instead
|
// TODO (Luke Vella) [2022-04-16]: Build an index instead
|
||||||
|
@ -164,10 +164,10 @@ export const PollContextProvider: React.VoidFunctionComponent<{
|
||||||
admin,
|
admin,
|
||||||
getScore,
|
getScore,
|
||||||
isDeleted,
|
isDeleted,
|
||||||
locale,
|
|
||||||
participants,
|
participants,
|
||||||
poll,
|
poll,
|
||||||
targetTimeZone,
|
targetTimeZone,
|
||||||
|
timeFormat,
|
||||||
urlId,
|
urlId,
|
||||||
user,
|
user,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { format } from "date-fns";
|
import dayjs from "dayjs";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
|
|
||||||
import { usePoll } from "@/components/poll-context";
|
import { usePoll } from "@/components/poll-context";
|
||||||
|
@ -50,10 +50,7 @@ export const useCsvExporter = () => {
|
||||||
link.setAttribute("href", encodedCsv);
|
link.setAttribute("href", encodedCsv);
|
||||||
link.setAttribute(
|
link.setAttribute(
|
||||||
"download",
|
"download",
|
||||||
`${poll.title.replace(/\s/g, "_")}-${format(
|
`${poll.title.replace(/\s/g, "_")}-${dayjs().format("YYYYMMDDHHmm")}`,
|
||||||
Date.now(),
|
|
||||||
"yyyyMMddhhmm",
|
|
||||||
)}`,
|
|
||||||
);
|
);
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
import { formatRelative } from "date-fns";
|
import dayjs from "dayjs";
|
||||||
import { Trans, useTranslation } from "next-i18next";
|
import { Trans, useTranslation } from "next-i18next";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import Badge from "../badge";
|
import Badge from "../badge";
|
||||||
import { usePoll } from "../poll-context";
|
import { usePoll } from "../poll-context";
|
||||||
import { usePreferences } from "../preferences/use-preferences";
|
|
||||||
import Tooltip from "../tooltip";
|
import Tooltip from "../tooltip";
|
||||||
|
|
||||||
const PollSubheader: React.VoidFunctionComponent = () => {
|
const PollSubheader: React.VoidFunctionComponent = () => {
|
||||||
const { poll } = usePoll();
|
const { poll } = usePoll();
|
||||||
const { t } = useTranslation("app");
|
const { t } = useTranslation("app");
|
||||||
const { locale } = usePreferences();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="text-slate-500/75 lg:text-lg">
|
<div className="text-slate-500/75 lg:text-lg">
|
||||||
|
@ -45,9 +43,7 @@ const PollSubheader: React.VoidFunctionComponent = () => {
|
||||||
</div>
|
</div>
|
||||||
<span className="hidden md:inline"> • </span>
|
<span className="hidden md:inline"> • </span>
|
||||||
<span className="whitespace-nowrap">
|
<span className="whitespace-nowrap">
|
||||||
{formatRelative(poll.createdAt, new Date(), {
|
{dayjs(poll.createdAt).fromNow()}
|
||||||
locale,
|
|
||||||
})}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,15 +1,32 @@
|
||||||
import { Locale } from "date-fns";
|
import dayjs from "dayjs";
|
||||||
import enGB from "date-fns/locale/en-GB";
|
import en from "dayjs/locale/en";
|
||||||
import enUS from "date-fns/locale/en-US";
|
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 * as React from "react";
|
||||||
import { useLocalStorage } from "react-use";
|
import { useLocalStorage } from "react-use";
|
||||||
|
|
||||||
type TimeFormat = "12h" | "24h";
|
type TimeFormat = "12h" | "24h";
|
||||||
type StartOfWeek = "monday" | "sunday";
|
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 =
|
export const PreferencesContext =
|
||||||
React.createContext<{
|
React.createContext<{
|
||||||
locale: Locale;
|
|
||||||
weekStartsOn: StartOfWeek;
|
weekStartsOn: StartOfWeek;
|
||||||
timeFormat: TimeFormat;
|
timeFormat: TimeFormat;
|
||||||
setWeekStartsOn: React.Dispatch<
|
setWeekStartsOn: React.Dispatch<
|
||||||
|
@ -29,13 +46,18 @@ const PreferencesProvider: React.VoidFunctionComponent<{
|
||||||
const [timeFormat = "12h", setTimeFormat] =
|
const [timeFormat = "12h", setTimeFormat] =
|
||||||
useLocalStorage<TimeFormat>("rallly-time-format");
|
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(
|
const contextValue = React.useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
weekStartsOn,
|
weekStartsOn,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
setWeekStartsOn,
|
setWeekStartsOn,
|
||||||
setTimeFormat,
|
setTimeFormat,
|
||||||
locale: timeFormat === "12h" ? enUS : enGB,
|
|
||||||
}),
|
}),
|
||||||
[setTimeFormat, setWeekStartsOn, timeFormat, weekStartsOn],
|
[setTimeFormat, setWeekStartsOn, timeFormat, weekStartsOn],
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { formatRelative } from "date-fns";
|
import dayjs from "dayjs";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
|
@ -83,7 +83,7 @@ export const Profile: React.VoidFunctionComponent = () => {
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-7 text-sm text-slate-500">
|
<div className="ml-7 text-sm text-slate-500">
|
||||||
{formatRelative(poll.createdAt, new Date())}
|
{dayjs(poll.createdAt).fromNow()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { addDays } from "date-fns";
|
import dayjs from "dayjs";
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
import { prisma } from "~/prisma/db";
|
import { prisma } from "~/prisma/db";
|
||||||
|
@ -29,7 +29,7 @@ export default async function handler(
|
||||||
where: {
|
where: {
|
||||||
deleted: false,
|
deleted: false,
|
||||||
touchedAt: {
|
touchedAt: {
|
||||||
lte: addDays(new Date(), -30),
|
lte: dayjs().add(-30, "days").toDate(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -42,14 +42,14 @@ export default async function handler(
|
||||||
{
|
{
|
||||||
deleted: true,
|
deleted: true,
|
||||||
deletedAt: {
|
deletedAt: {
|
||||||
lte: addDays(new Date(), -7),
|
lte: dayjs().add(-7, "days").toDate(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// demo polls that are 1 day old
|
// demo polls that are 1 day old
|
||||||
{
|
{
|
||||||
demo: true,
|
demo: true,
|
||||||
createdAt: {
|
createdAt: {
|
||||||
lte: addDays(new Date(), -1),
|
lte: dayjs().add(-1, "days").toDate(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { VoteType } from "@prisma/client";
|
import { VoteType } from "@prisma/client";
|
||||||
import addMinutes from "date-fns/addMinutes";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
import { prisma } from "~/prisma/db";
|
import { prisma } from "~/prisma/db";
|
||||||
|
|
||||||
|
@ -31,7 +31,6 @@ export const demo = createRouter().mutation("create", {
|
||||||
resolve: async () => {
|
resolve: async () => {
|
||||||
const adminUrlId = await nanoid();
|
const adminUrlId = await nanoid();
|
||||||
const demoUser = { name: "John Example", email: "noreply@rallly.co" };
|
const demoUser = { name: "John Example", email: "noreply@rallly.co" };
|
||||||
const today = new Date();
|
|
||||||
|
|
||||||
const options: Array<{ value: string; id: string }> = [];
|
const options: Array<{ value: string; id: string }> = [];
|
||||||
|
|
||||||
|
@ -59,7 +58,9 @@ export const demo = createRouter().mutation("create", {
|
||||||
id: participantId,
|
id: participantId,
|
||||||
name,
|
name,
|
||||||
userId: "user-demo",
|
userId: "user-demo",
|
||||||
createdAt: addMinutes(today, i * -1),
|
createdAt: dayjs()
|
||||||
|
.add(i * -1, "minutes")
|
||||||
|
.toDate(),
|
||||||
});
|
});
|
||||||
|
|
||||||
options.forEach((option, index) => {
|
options.forEach((option, index) => {
|
||||||
|
|
|
@ -1,14 +1,5 @@
|
||||||
import { Option } from "@prisma/client";
|
import { Option } from "@prisma/client";
|
||||||
import {
|
import dayjs from "dayjs";
|
||||||
differenceInHours,
|
|
||||||
differenceInMinutes,
|
|
||||||
format,
|
|
||||||
formatDuration,
|
|
||||||
isSameDay,
|
|
||||||
Locale,
|
|
||||||
} from "date-fns";
|
|
||||||
import { formatInTimeZone } from "date-fns-tz";
|
|
||||||
import spacetime from "spacetime";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DateTimeOption,
|
DateTimeOption,
|
||||||
|
@ -49,19 +40,28 @@ export type ParsedDateTimeOpton = ParsedDateOption | ParsedTimeSlotOption;
|
||||||
|
|
||||||
const isTimeSlot = (value: string) => value.indexOf("/") !== -1;
|
const isTimeSlot = (value: string) => value.indexOf("/") !== -1;
|
||||||
|
|
||||||
const getDuration = (startTime: Date, endTime: Date) => {
|
export const getDuration = (startTime: dayjs.Dayjs, endTime: dayjs.Dayjs) => {
|
||||||
const hours = Math.floor(differenceInHours(endTime, startTime));
|
const hours = Math.floor(endTime.diff(startTime, "hours"));
|
||||||
const minutes = Math.floor(
|
const minutes = Math.floor(endTime.diff(startTime, "minute") - hours * 60);
|
||||||
differenceInMinutes(endTime, startTime) - hours * 60,
|
let res = "";
|
||||||
);
|
if (hours) {
|
||||||
return formatDuration({ hours, minutes });
|
res += `${hours}h`;
|
||||||
|
}
|
||||||
|
if (hours && minutes) {
|
||||||
|
res += " ";
|
||||||
|
}
|
||||||
|
if (minutes) {
|
||||||
|
res += `${minutes}m`;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const decodeOptions = (
|
export const decodeOptions = (
|
||||||
options: Option[],
|
options: Option[],
|
||||||
timeZone: string | null,
|
timeZone: string | null,
|
||||||
targetTimeZone: string,
|
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: "date"; options: ParsedDateOption[] }
|
||||||
| { pollType: "timeSlot"; options: ParsedTimeSlotOption[] } => {
|
| { pollType: "timeSlot"; options: ParsedTimeSlotOption[] } => {
|
||||||
|
@ -71,7 +71,7 @@ export const decodeOptions = (
|
||||||
return {
|
return {
|
||||||
pollType,
|
pollType,
|
||||||
options: options.map((option) =>
|
options: options.map((option) =>
|
||||||
parseTimeSlotOption(option, timeZone, targetTimeZone, locale),
|
parseTimeSlotOption(option, timeZone, targetTimeZone),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
} else {
|
} 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
|
? // 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 + "T00:00:00"
|
||||||
: option.value;
|
: option.value;
|
||||||
const date = new Date(dateString);
|
const date = dayjs(dateString);
|
||||||
return {
|
return {
|
||||||
type: "date",
|
type: "date",
|
||||||
optionId: option.id,
|
optionId: option.id,
|
||||||
day: format(date, "d"),
|
day: date.format("D"),
|
||||||
dow: format(date, "EEE"),
|
dow: date.format("ddd"),
|
||||||
month: format(date, "MMM"),
|
month: date.format("MMM"),
|
||||||
year: format(date, "yyyy"),
|
year: date.format("YYYY"),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -103,48 +103,29 @@ const parseTimeSlotOption = (
|
||||||
option: Option,
|
option: Option,
|
||||||
timeZone: string | null,
|
timeZone: string | null,
|
||||||
targetTimeZone: string,
|
targetTimeZone: string,
|
||||||
locale: Locale,
|
|
||||||
): ParsedTimeSlotOption => {
|
): ParsedTimeSlotOption => {
|
||||||
const localeFormatInTimezone = (
|
|
||||||
date: Date,
|
|
||||||
timezone: string,
|
|
||||||
formatString: string,
|
|
||||||
) => {
|
|
||||||
return formatInTimeZone(date, timezone, formatString, {
|
|
||||||
locale,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const [start, end] = option.value.split("/");
|
const [start, end] = option.value.split("/");
|
||||||
if (timeZone && targetTimeZone) {
|
|
||||||
const startDate = spacetime(start, timeZone).toNativeDate();
|
const startDate =
|
||||||
const endDate = spacetime(end, timeZone).toNativeDate();
|
timeZone && targetTimeZone
|
||||||
return {
|
? dayjs(start).tz(timeZone, true).tz(targetTimeZone)
|
||||||
type: "timeSlot",
|
: dayjs(start);
|
||||||
optionId: option.id,
|
const endDate =
|
||||||
startTime: localeFormatInTimezone(startDate, targetTimeZone, "p"),
|
timeZone && targetTimeZone
|
||||||
endTime: localeFormatInTimezone(endDate, targetTimeZone, "p"),
|
? dayjs(end).tz(timeZone, true).tz(targetTimeZone)
|
||||||
day: localeFormatInTimezone(startDate, targetTimeZone, "d"),
|
: dayjs(end);
|
||||||
dow: localeFormatInTimezone(startDate, targetTimeZone, "EEE"),
|
|
||||||
month: localeFormatInTimezone(startDate, targetTimeZone, "MMM"),
|
return {
|
||||||
duration: getDuration(startDate, endDate),
|
type: "timeSlot",
|
||||||
year: localeFormatInTimezone(startDate, targetTimeZone, "yyyy"),
|
optionId: option.id,
|
||||||
};
|
startTime: startDate.format("LT"),
|
||||||
} else {
|
endTime: endDate.format("LT"),
|
||||||
const startDate = new Date(start);
|
day: startDate.format("D"),
|
||||||
const endDate = new Date(end);
|
dow: startDate.format("ddd"),
|
||||||
return {
|
month: startDate.format("MMM"),
|
||||||
type: "timeSlot",
|
duration: getDuration(startDate, endDate),
|
||||||
optionId: option.id,
|
year: startDate.format("YYYY"),
|
||||||
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"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeAllOptionsForDay = (
|
export const removeAllOptionsForDay = (
|
||||||
|
@ -152,18 +133,19 @@ export const removeAllOptionsForDay = (
|
||||||
date: Date,
|
date: Date,
|
||||||
) => {
|
) => {
|
||||||
return options.filter((option) => {
|
return options.filter((option) => {
|
||||||
const optionDate = spacetime(
|
const optionDate = new Date(
|
||||||
option.type === "date" ? option.date : option.start,
|
option.type === "date" ? option.date : option.start,
|
||||||
).toNativeDate();
|
);
|
||||||
return !isSameDay(date, optionDate);
|
return !dayjs(date).isSame(optionDate, "day");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDateProps = (date: Date) => {
|
export const getDateProps = (date: Date) => {
|
||||||
|
const d = dayjs(date);
|
||||||
return {
|
return {
|
||||||
day: format(date, "d"),
|
day: d.format("D"),
|
||||||
dow: format(date, "E"),
|
dow: d.format("ddd"),
|
||||||
month: format(date, "MMM"),
|
month: d.format("MMM"),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { addDays } from "date-fns";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
import { prisma } from "../prisma/db";
|
import { prisma } from "../prisma/db";
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ test.beforeAll(async ({ request, baseURL }) => {
|
||||||
type: "date",
|
type: "date",
|
||||||
userId: "user1",
|
userId: "user1",
|
||||||
deleted: true,
|
deleted: true,
|
||||||
deletedAt: addDays(new Date(), -6),
|
deletedAt: dayjs().add(-6, "days").toDate(),
|
||||||
participantUrlId: "p2",
|
participantUrlId: "p2",
|
||||||
adminUrlId: "a2",
|
adminUrlId: "a2",
|
||||||
},
|
},
|
||||||
|
@ -40,7 +40,7 @@ test.beforeAll(async ({ request, baseURL }) => {
|
||||||
type: "date",
|
type: "date",
|
||||||
userId: "user1",
|
userId: "user1",
|
||||||
deleted: true,
|
deleted: true,
|
||||||
deletedAt: addDays(new Date(), -7),
|
deletedAt: dayjs().add(-7, "days").toDate(),
|
||||||
participantUrlId: "p3",
|
participantUrlId: "p3",
|
||||||
adminUrlId: "a3",
|
adminUrlId: "a3",
|
||||||
},
|
},
|
||||||
|
@ -50,7 +50,7 @@ test.beforeAll(async ({ request, baseURL }) => {
|
||||||
id: "still-active-poll",
|
id: "still-active-poll",
|
||||||
type: "date",
|
type: "date",
|
||||||
userId: "user1",
|
userId: "user1",
|
||||||
touchedAt: addDays(new Date(), -29),
|
touchedAt: dayjs().add(-29, "days").toDate(),
|
||||||
participantUrlId: "p4",
|
participantUrlId: "p4",
|
||||||
adminUrlId: "a4",
|
adminUrlId: "a4",
|
||||||
},
|
},
|
||||||
|
@ -60,7 +60,7 @@ test.beforeAll(async ({ request, baseURL }) => {
|
||||||
id: "inactive-poll",
|
id: "inactive-poll",
|
||||||
type: "date",
|
type: "date",
|
||||||
userId: "user1",
|
userId: "user1",
|
||||||
touchedAt: addDays(new Date(), -30),
|
touchedAt: dayjs().add(-30, "days").toDate(),
|
||||||
participantUrlId: "p5",
|
participantUrlId: "p5",
|
||||||
adminUrlId: "a5",
|
adminUrlId: "a5",
|
||||||
},
|
},
|
||||||
|
@ -82,7 +82,7 @@ test.beforeAll(async ({ request, baseURL }) => {
|
||||||
id: "demo-poll-old",
|
id: "demo-poll-old",
|
||||||
type: "date",
|
type: "date",
|
||||||
userId: "user1",
|
userId: "user1",
|
||||||
createdAt: addDays(new Date(), -2),
|
createdAt: dayjs().add(-2, "days").toDate(),
|
||||||
participantUrlId: "p7",
|
participantUrlId: "p7",
|
||||||
adminUrlId: "a7",
|
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"
|
resolved "https://registry.npmjs.org/date-arithmetic/-/date-arithmetic-4.1.0.tgz"
|
||||||
integrity sha512-QWxYLR5P/6GStZcdem+V1xoto6DMadYWpMXU82ES3/RfR3Wdwr3D0+be7mgOJ+Ov0G9D5Dmb9T17sNLQYj9XOg==
|
integrity sha512-QWxYLR5P/6GStZcdem+V1xoto6DMadYWpMXU82ES3/RfR3Wdwr3D0+be7mgOJ+Ov0G9D5Dmb9T17sNLQYj9XOg==
|
||||||
|
|
||||||
date-fns-tz@^1.2.2:
|
dayjs@^1.11.3:
|
||||||
version "1.2.2"
|
version "1.11.3"
|
||||||
resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.2.2.tgz#89432b54ce3fa7d050a2039e997e5b6a96df35dd"
|
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.3.tgz#4754eb694a624057b9ad2224b67b15d552589258"
|
||||||
integrity sha512-vWtn44eEqnLbkACb7T5G5gPgKR4nY8NkNMOCyoY49NsRGHrcDmY2aysCyzDeA+u+vcDBn/w6nQqEDyouRs4m8w==
|
integrity sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A==
|
||||||
|
|
||||||
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==
|
|
||||||
|
|
||||||
debug@4:
|
debug@4:
|
||||||
version "4.3.3"
|
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"
|
resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz"
|
||||||
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
||||||
|
|
||||||
spacetime@^7.1.2:
|
spacetime@^7.1.4:
|
||||||
version "7.1.2"
|
version "7.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/spacetime/-/spacetime-7.1.2.tgz#73a94f0ba7f0c0b2230259b5ccf78dd9fd34cd16"
|
resolved "https://registry.yarnpkg.com/spacetime/-/spacetime-7.1.4.tgz#f288e4dcecbb0ec56e76c3b2a09c55badc18cebe"
|
||||||
integrity sha512-MUTgK9KU9gMXhZddwe0nlgFnCJXT4RfuIRqyo8VcUexZa94zkxk1WpVv3THgvMFz1+Hq9okoNiGTvj6qBdN4Cg==
|
integrity sha512-ZzYuGjaMPE42p/fVU9Qcd+aMhgYzC83hgqMKyWlGILtponsLm5KJZgWzblLnOU8OblMQa3YXo/0IiZZ5JsTDfA==
|
||||||
|
|
||||||
spdx-correct@^3.0.0:
|
spdx-correct@^3.0.0:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue