- update({ interval: Number.parseInt(event.target.value, 10) || 1 })
+ update({
+ interval: Number.parseInt(event.target.value, 10) || 1,
+ })
}
className="mt-1.5 w-24"
/>
@@ -125,7 +131,9 @@ export function RecurrencePicker({ value, start, onChange }: Props) {
{recurrence.freq === "WEEKLY" && (
-
+
{weekdayOptions.map(({ value: day, label }) => (
@@ -136,7 +144,10 @@ export function RecurrencePicker({ value, start, onChange }: Props) {
update({ byDay: updateWeekdays(recurrence.byDay, day) })
}
/>
-
@@ -186,10 +197,14 @@ export function RecurrencePicker({ value, start, onChange }: Props) {
{validation.errors.count && (
-
{validation.errors.count}
+
+ {validation.errors.count}
+
)}
{validation.errors.until && (
-
{validation.errors.until}
+
+ {validation.errors.until}
+
)}
{validation.errors.rule && (
{validation.errors.rule}
diff --git a/src/components/rrule-display.tsx b/src/components/rrule-display.tsx
index 64113cc..268d314 100644
--- a/src/components/rrule-display.tsx
+++ b/src/components/rrule-display.tsx
@@ -1,6 +1,10 @@
import { format, parseISO } from "date-fns";
import { Badge } from "@/components/ui/badge";
-import { formatRecurrenceText, getRecurrencePreview, parseRecurrenceRule } from "@/lib/recurrence";
+import {
+ formatRecurrenceText,
+ getRecurrencePreview,
+ parseRecurrenceRule,
+} from "@/lib/recurrence";
interface RRuleDisplayProps {
rrule?: string;
@@ -12,17 +16,25 @@ export function RRuleDisplay({ rrule, className, start }: RRuleDisplayProps) {
if (!rrule) return null;
const humanText = formatRecurrenceText(rrule);
- const preview = start ? getRecurrencePreview(parseRecurrenceRule(rrule), start, 3) : [];
+ const preview = start
+ ? getRecurrencePreview(parseRecurrenceRule(rrule), start, 3)
+ : [];
return (
-
+
{humanText ?? rrule}
{preview.length > 0 && (
- Next: {preview.map((value) => format(parseISO(value), "MMM d")).join(", ")}
+ Next:{" "}
+ {preview
+ .map((value) => format(parseISO(value), "MMM d"))
+ .join(", ")}
)}
diff --git a/src/lib/event-date-format.ts b/src/lib/event-date-format.ts
index 98280fc..cc4bec6 100644
--- a/src/lib/event-date-format.ts
+++ b/src/lib/event-date-format.ts
@@ -1,10 +1,4 @@
-import {
- format,
- isSameDay,
- isToday,
- isTomorrow,
- parseISO,
-} from "date-fns";
+import { format, isSameDay, isToday, isTomorrow, parseISO } from "date-fns";
import type { CalendarEvent } from "@/lib/types";
const getFriendlyDayLabel = (value: Date): string => {
@@ -13,7 +7,10 @@ const getFriendlyDayLabel = (value: Date): string => {
return format(value, "MMM d, yyyy");
};
-export const formatEventStartLabel = (start: string, allDay?: boolean): string => {
+export const formatEventStartLabel = (
+ start: string,
+ allDay?: boolean,
+): string => {
const parsed = parseISO(start);
const dayLabel = getFriendlyDayLabel(parsed);
@@ -21,12 +18,16 @@ export const formatEventStartLabel = (start: string, allDay?: boolean): string =
return `${dayLabel} · ${format(parsed, "HH:mm")}`;
};
-export const formatEventRangeLabel = (event: Pick
): string => {
+export const formatEventRangeLabel = (
+ event: Pick,
+): string => {
const startDate = parseISO(event.start);
const startLabel = getFriendlyDayLabel(startDate);
if (event.allDay || !event.end) {
- return event.allDay ? startLabel : `${startLabel} · ${format(startDate, "HH:mm")}`;
+ return event.allDay
+ ? startLabel
+ : `${startLabel} · ${format(startDate, "HH:mm")}`;
}
const endDate = parseISO(event.end);
diff --git a/src/lib/event-form.ts b/src/lib/event-form.ts
index 84ab337..d31e407 100644
--- a/src/lib/event-form.ts
+++ b/src/lib/event-form.ts
@@ -39,7 +39,10 @@ const eventFormSchema = z
if (value.end) {
const startDate = parseISO(value.start);
const endDate = parseISO(value.end);
- if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime())) {
+ if (
+ Number.isNaN(startDate.getTime()) ||
+ Number.isNaN(endDate.getTime())
+ ) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["end"],
diff --git a/src/lib/recurrence.ts b/src/lib/recurrence.ts
index 0251315..acd7d6f 100644
--- a/src/lib/recurrence.ts
+++ b/src/lib/recurrence.ts
@@ -2,7 +2,11 @@ import { format, parseISO } from "date-fns";
import { RRule, rrulestr } from "rrule";
import type { Frequency, Weekday } from "@/lib/rfc5545-types";
-export type SupportedRecurrenceFrequency = "NONE" | "DAILY" | "WEEKLY" | "MONTHLY";
+export type SupportedRecurrenceFrequency =
+ | "NONE"
+ | "DAILY"
+ | "WEEKLY"
+ | "MONTHLY";
export interface RecurrenceFormValue {
freq: SupportedRecurrenceFrequency;
@@ -23,7 +27,10 @@ const EMPTY_RECURRENCE: RecurrenceFormValue = {
byDay: [],
};
-const RULE_FREQUENCIES: Record, number> = {
+const RULE_FREQUENCIES: Record<
+ Exclude,
+ number
+> = {
DAILY: RRule.DAILY,
WEEKLY: RRule.WEEKLY,
MONTHLY: RRule.MONTHLY,
@@ -58,7 +65,10 @@ const sortWeekdays = (days: string[] | undefined): Weekday[] => {
if (!days?.length) return [];
return [...days]
.filter((day): day is Weekday => WEEKDAY_ORDER.includes(day as Weekday))
- .sort((left, right) => WEEKDAY_ORDER.indexOf(left) - WEEKDAY_ORDER.indexOf(right));
+ .sort(
+ (left, right) =>
+ WEEKDAY_ORDER.indexOf(left) - WEEKDAY_ORDER.indexOf(right),
+ );
};
const toUntilDate = (until: string): Date => {
@@ -105,7 +115,10 @@ export const validateRecurrence = (
errors.rule = "Interval must be at least 1.";
}
- if (value.count !== undefined && (!Number.isInteger(value.count) || value.count < 1)) {
+ if (
+ value.count !== undefined &&
+ (!Number.isInteger(value.count) || value.count < 1)
+ ) {
errors.count = "Count must be a whole number greater than 0.";
}
@@ -164,14 +177,22 @@ export const serializeRecurrenceRule = (
const validation = validateRecurrence(value);
if (!validation.isValid) {
- throw new Error(validation.errors.rule || validation.errors.count || validation.errors.until || "Invalid recurrence.");
+ throw new Error(
+ validation.errors.rule ||
+ validation.errors.count ||
+ validation.errors.until ||
+ "Invalid recurrence.",
+ );
}
const options = toRRuleOptions(value, dtstart);
if (!options) return undefined;
const rule = new RRule(options);
- const ruleLine = rule.toString().split("\n").find((line) => line.startsWith("RRULE:"));
+ const ruleLine = rule
+ .toString()
+ .split("\n")
+ .find((line) => line.startsWith("RRULE:"));
if (!ruleLine) return undefined;
const entries = ruleLine.replace(/^RRULE:/, "").split(";");
@@ -181,9 +202,14 @@ export const serializeRecurrenceRule = (
const rightKey = right.split("=")[0] ?? "";
const leftIndex = orderedKeys.indexOf(leftKey);
const rightIndex = orderedKeys.indexOf(rightKey);
- const normalizedLeftIndex = leftIndex === -1 ? Number.MAX_SAFE_INTEGER : leftIndex;
- const normalizedRightIndex = rightIndex === -1 ? Number.MAX_SAFE_INTEGER : rightIndex;
- return normalizedLeftIndex - normalizedRightIndex || leftKey.localeCompare(rightKey);
+ const normalizedLeftIndex =
+ leftIndex === -1 ? Number.MAX_SAFE_INTEGER : leftIndex;
+ const normalizedRightIndex =
+ rightIndex === -1 ? Number.MAX_SAFE_INTEGER : rightIndex;
+ return (
+ normalizedLeftIndex - normalizedRightIndex ||
+ leftKey.localeCompare(rightKey)
+ );
});
return sorted.join(";");
@@ -213,7 +239,10 @@ export const formatRecurrenceText = (rule?: string): string | null => {
return rrulestr(`RRULE:${rule}`).toText();
};
-export const getWeekdayOptions = (): Array<{ value: Weekday; label: string }> => [
+export const getWeekdayOptions = (): Array<{
+ value: Weekday;
+ label: string;
+}> => [
{ value: "MO", label: "Mon" },
{ value: "TU", label: "Tue" },
{ value: "WE", label: "Wed" },
@@ -228,7 +257,10 @@ export const isWeekdayPreset = (value: RecurrenceFormValue): boolean =>
value.interval === 1 &&
value.byDay.join(",") === ["MO", "TU", "WE", "TH", "FR"].join(",");
-export const recurrenceFrequencyLabels: Record = {
+export const recurrenceFrequencyLabels: Record<
+ SupportedRecurrenceFrequency,
+ string
+> = {
NONE: "Does not repeat",
DAILY: "Daily",
WEEKLY: "Weekly",