payload-reserve 1.5.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -3
- package/dist/collections/Reservations.js +19 -7
- package/dist/collections/Reservations.js.map +1 -1
- package/dist/collections/Resources.js +11 -8
- package/dist/collections/Resources.js.map +1 -1
- package/dist/collections/Schedules.js +12 -6
- package/dist/collections/Schedules.js.map +1 -1
- package/dist/collections/Services.js +19 -10
- package/dist/collections/Services.js.map +1 -1
- package/dist/components/AvailabilityOverview/index.js +70 -26
- package/dist/components/AvailabilityOverview/index.js.map +1 -1
- package/dist/components/CalendarView/CalendarView.module.css +9 -0
- package/dist/components/CalendarView/LaneTimelineView.d.ts +4 -1
- package/dist/components/CalendarView/LaneTimelineView.js +17 -12
- package/dist/components/CalendarView/LaneTimelineView.js.map +1 -1
- package/dist/components/CalendarView/index.js +154 -53
- package/dist/components/CalendarView/index.js.map +1 -1
- package/dist/components/CustomerField/index.js +8 -3
- package/dist/components/CustomerField/index.js.map +1 -1
- package/dist/components/DashboardWidget/DashboardWidgetServer.js +97 -21
- package/dist/components/DashboardWidget/DashboardWidgetServer.js.map +1 -1
- package/dist/defaults.js +46 -8
- package/dist/defaults.js.map +1 -1
- package/dist/endpoints/cancelBooking.js +1 -1
- package/dist/endpoints/cancelBooking.js.map +1 -1
- package/dist/endpoints/checkAvailability.js +56 -7
- package/dist/endpoints/checkAvailability.js.map +1 -1
- package/dist/endpoints/createBooking.js +19 -10
- package/dist/endpoints/createBooking.js.map +1 -1
- package/dist/endpoints/customerSearch.js +5 -2
- package/dist/endpoints/customerSearch.js.map +1 -1
- package/dist/endpoints/getSlots.js +56 -7
- package/dist/endpoints/getSlots.js.map +1 -1
- package/dist/endpoints/resourceAvailability.d.ts +2 -1
- package/dist/endpoints/resourceAvailability.js +85 -25
- package/dist/endpoints/resourceAvailability.js.map +1 -1
- package/dist/hooks/reservations/calculateEndTime.js +48 -20
- package/dist/hooks/reservations/calculateEndTime.js.map +1 -1
- package/dist/hooks/reservations/enforceCustomerOwnership.d.ts +11 -0
- package/dist/hooks/reservations/enforceCustomerOwnership.js +30 -0
- package/dist/hooks/reservations/enforceCustomerOwnership.js.map +1 -0
- package/dist/hooks/reservations/onStatusChange.js +10 -4
- package/dist/hooks/reservations/onStatusChange.js.map +1 -1
- package/dist/hooks/reservations/validateCancellation.js +3 -2
- package/dist/hooks/reservations/validateCancellation.js.map +1 -1
- package/dist/hooks/reservations/validateConflicts.js +23 -4
- package/dist/hooks/reservations/validateConflicts.js.map +1 -1
- package/dist/hooks/reservations/validateGuestBooking.js +3 -4
- package/dist/hooks/reservations/validateGuestBooking.js.map +1 -1
- package/dist/hooks/reservations/validateStatusTransition.js +2 -2
- package/dist/hooks/reservations/validateStatusTransition.js.map +1 -1
- package/dist/hooks/users/provisionStaffResource.js +5 -8
- package/dist/hooks/users/provisionStaffResource.js.map +1 -1
- package/dist/plugin.js +82 -13
- package/dist/plugin.js.map +1 -1
- package/dist/services/AvailabilityService.d.ts +54 -2
- package/dist/services/AvailabilityService.js +180 -46
- package/dist/services/AvailabilityService.js.map +1 -1
- package/dist/translations/ar.json +1 -0
- package/dist/translations/de.json +1 -0
- package/dist/translations/en.json +1 -0
- package/dist/translations/es.json +1 -0
- package/dist/translations/fa.json +1 -0
- package/dist/translations/fr.json +1 -0
- package/dist/translations/hi.json +1 -0
- package/dist/translations/id.json +1 -0
- package/dist/translations/pl.json +1 -0
- package/dist/translations/ru.json +1 -0
- package/dist/translations/tr.json +1 -0
- package/dist/translations/zh.json +1 -0
- package/dist/types.d.ts +50 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/dist/utilities/collectionOverrides.d.ts +14 -0
- package/dist/utilities/collectionOverrides.js +47 -0
- package/dist/utilities/collectionOverrides.js.map +1 -0
- package/dist/utilities/ownerAccess.d.ts +6 -0
- package/dist/utilities/ownerAccess.js +25 -12
- package/dist/utilities/ownerAccess.js.map +1 -1
- package/dist/utilities/reservationChanges.d.ts +17 -0
- package/dist/utilities/reservationChanges.js +88 -0
- package/dist/utilities/reservationChanges.js.map +1 -0
- package/dist/utilities/scheduleUtils.d.ts +14 -8
- package/dist/utilities/scheduleUtils.js +26 -19
- package/dist/utilities/scheduleUtils.js.map +1 -1
- package/dist/utilities/tenantFilter.d.ts +25 -0
- package/dist/utilities/tenantFilter.js +56 -0
- package/dist/utilities/tenantFilter.js.map +1 -0
- package/dist/utilities/timezoneUtils.d.ts +39 -0
- package/dist/utilities/timezoneUtils.js +134 -0
- package/dist/utilities/timezoneUtils.js.map +1 -0
- package/dist/utilities/useTenantFilter.d.ts +6 -0
- package/dist/utilities/useTenantFilter.js +28 -0
- package/dist/utilities/useTenantFilter.js.map +1 -0
- package/package.json +2 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { combineDayKeyAndTime, getDayKeyInTimezone, getDayOfWeekFromDayKey } from './timezoneUtils.js';
|
|
1
2
|
const DAY_MAP = {
|
|
2
3
|
fri: 5,
|
|
3
4
|
mon: 1,
|
|
@@ -8,7 +9,8 @@ const DAY_MAP = {
|
|
|
8
9
|
wed: 3
|
|
9
10
|
};
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
+
* @deprecated Server-TZ-dependent legacy helper — no longer used by the plugin's
|
|
13
|
+
* own resolution path. Use timezoneUtils (getDayOfWeekFromDayKey / combineDayKeyAndTime).
|
|
12
14
|
*/ export function getDayOfWeek(date) {
|
|
13
15
|
const jsDay = date.getDay();
|
|
14
16
|
const entries = Object.entries(DAY_MAP);
|
|
@@ -16,7 +18,8 @@ const DAY_MAP = {
|
|
|
16
18
|
return found ? found[0] : 'mon';
|
|
17
19
|
}
|
|
18
20
|
/**
|
|
19
|
-
*
|
|
21
|
+
* @deprecated Server-TZ-dependent legacy helper — no longer used by the plugin's
|
|
22
|
+
* own resolution path. Use timezoneUtils (getDayOfWeekFromDayKey / combineDayKeyAndTime).
|
|
20
23
|
*/ export function dateMatchesDay(date, day) {
|
|
21
24
|
return date.getDay() === DAY_MAP[day];
|
|
22
25
|
}
|
|
@@ -30,7 +33,8 @@ const DAY_MAP = {
|
|
|
30
33
|
};
|
|
31
34
|
}
|
|
32
35
|
/**
|
|
33
|
-
*
|
|
36
|
+
* @deprecated Server-TZ-dependent legacy helper — no longer used by the plugin's
|
|
37
|
+
* own resolution path. Use timezoneUtils (getDayOfWeekFromDayKey / combineDayKeyAndTime).
|
|
34
38
|
*/ export function combineDateAndTime(date, time) {
|
|
35
39
|
const { hours, minutes } = parseTime(time);
|
|
36
40
|
const combined = new Date(date);
|
|
@@ -38,47 +42,50 @@ const DAY_MAP = {
|
|
|
38
42
|
return combined;
|
|
39
43
|
}
|
|
40
44
|
/**
|
|
41
|
-
* Check if a
|
|
45
|
+
* Check if a calendar day is an exception day in the schedule.
|
|
42
46
|
* Supports range exceptions via optional endDate (inclusive on both ends).
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
* `date` may be a Date instant (keyed in `timeZone`) or a YYYY-MM-DD day key.
|
|
48
|
+
*/ export function isExceptionDate(date, exceptions, timeZone = 'UTC') {
|
|
49
|
+
const target = typeof date === 'string' ? date : getDayKeyInTimezone(date, timeZone);
|
|
45
50
|
return exceptions.some((exc)=>{
|
|
46
|
-
const start = new Date(exc.date)
|
|
47
|
-
const end = exc.endDate ? new Date(exc.endDate)
|
|
51
|
+
const start = getDayKeyInTimezone(new Date(exc.date), timeZone);
|
|
52
|
+
const end = exc.endDate ? getDayKeyInTimezone(new Date(exc.endDate), timeZone) : start;
|
|
48
53
|
return target >= start && target <= end;
|
|
49
54
|
});
|
|
50
55
|
}
|
|
51
56
|
/**
|
|
52
|
-
* Resolve a schedule to concrete available time ranges for a
|
|
53
|
-
|
|
57
|
+
* Resolve a schedule to concrete available time ranges for a calendar day.
|
|
58
|
+
* `date` may be a Date instant (keyed in `timeZone`) or a YYYY-MM-DD day key.
|
|
59
|
+
* All wall-clock times (HH:mm) are interpreted in `timeZone`.
|
|
60
|
+
*/ export function resolveScheduleForDate(schedule, date, timeZone = 'UTC') {
|
|
54
61
|
if (schedule.active === false) {
|
|
55
62
|
return [];
|
|
56
63
|
}
|
|
64
|
+
const dayKey = typeof date === 'string' ? date : getDayKeyInTimezone(date, timeZone);
|
|
57
65
|
const exceptions = schedule.exceptions ?? [];
|
|
58
|
-
if (isExceptionDate(
|
|
66
|
+
if (isExceptionDate(dayKey, exceptions, timeZone)) {
|
|
59
67
|
return [];
|
|
60
68
|
}
|
|
61
69
|
const ranges = [];
|
|
62
70
|
if (schedule.scheduleType === 'recurring') {
|
|
63
71
|
const slots = schedule.recurringSlots ?? [];
|
|
64
|
-
const dayOfWeek =
|
|
72
|
+
const dayOfWeek = getDayOfWeekFromDayKey(dayKey);
|
|
65
73
|
for (const slot of slots){
|
|
66
74
|
if (slot.day === dayOfWeek) {
|
|
67
75
|
ranges.push({
|
|
68
|
-
end:
|
|
69
|
-
start:
|
|
76
|
+
end: combineDayKeyAndTime(dayKey, slot.endTime, timeZone),
|
|
77
|
+
start: combineDayKeyAndTime(dayKey, slot.startTime, timeZone)
|
|
70
78
|
});
|
|
71
79
|
}
|
|
72
80
|
}
|
|
73
81
|
} else if (schedule.scheduleType === 'manual') {
|
|
74
82
|
const slots = schedule.manualSlots ?? [];
|
|
75
|
-
const dateStr = date.toISOString().split('T')[0];
|
|
76
83
|
for (const slot of slots){
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
84
|
+
const slotDayKey = getDayKeyInTimezone(new Date(slot.date), timeZone);
|
|
85
|
+
if (slotDayKey === dayKey) {
|
|
79
86
|
ranges.push({
|
|
80
|
-
end:
|
|
81
|
-
start:
|
|
87
|
+
end: combineDayKeyAndTime(dayKey, slot.endTime, timeZone),
|
|
88
|
+
start: combineDayKeyAndTime(dayKey, slot.startTime, timeZone)
|
|
82
89
|
});
|
|
83
90
|
}
|
|
84
91
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/scheduleUtils.ts"],"sourcesContent":["import type { DayOfWeek } from '../types.js'\n\nconst DAY_MAP: Record<DayOfWeek, number> = {\n fri: 5,\n mon: 1,\n sat: 6,\n sun: 0,\n thu: 4,\n tue: 2,\n wed: 3,\n}\n\n/**\n *
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/scheduleUtils.ts"],"sourcesContent":["import type { DayOfWeek } from '../types.js'\n\nimport {\n combineDayKeyAndTime,\n getDayKeyInTimezone,\n getDayOfWeekFromDayKey,\n} from './timezoneUtils.js'\n\nconst DAY_MAP: Record<DayOfWeek, number> = {\n fri: 5,\n mon: 1,\n sat: 6,\n sun: 0,\n thu: 4,\n tue: 2,\n wed: 3,\n}\n\n/**\n * @deprecated Server-TZ-dependent legacy helper — no longer used by the plugin's\n * own resolution path. Use timezoneUtils (getDayOfWeekFromDayKey / combineDayKeyAndTime).\n */\nexport function getDayOfWeek(date: Date): DayOfWeek {\n const jsDay = date.getDay()\n const entries = Object.entries(DAY_MAP) as [DayOfWeek, number][]\n const found = entries.find(([, num]) => num === jsDay)\n return found ? found[0] : 'mon'\n}\n\n/**\n * @deprecated Server-TZ-dependent legacy helper — no longer used by the plugin's\n * own resolution path. Use timezoneUtils (getDayOfWeekFromDayKey / combineDayKeyAndTime).\n */\nexport function dateMatchesDay(date: Date, day: DayOfWeek): boolean {\n return date.getDay() === DAY_MAP[day]\n}\n\n/**\n * Parse a HH:mm time string to hours and minutes.\n */\nexport function parseTime(time: string): { hours: number; minutes: number } {\n const [h, m] = time.split(':').map(Number)\n return { hours: h, minutes: m }\n}\n\n/**\n * @deprecated Server-TZ-dependent legacy helper — no longer used by the plugin's\n * own resolution path. Use timezoneUtils (getDayOfWeekFromDayKey / combineDayKeyAndTime).\n */\nexport function combineDateAndTime(date: Date, time: string): Date {\n const { hours, minutes } = parseTime(time)\n const combined = new Date(date)\n combined.setHours(hours, minutes, 0, 0)\n return combined\n}\n\n/**\n * Check if a calendar day is an exception day in the schedule.\n * Supports range exceptions via optional endDate (inclusive on both ends).\n * `date` may be a Date instant (keyed in `timeZone`) or a YYYY-MM-DD day key.\n */\nexport function isExceptionDate(\n date: Date | string,\n exceptions: Array<{ date: string; endDate?: string }>,\n timeZone: string = 'UTC',\n): boolean {\n const target = typeof date === 'string' ? date : getDayKeyInTimezone(date, timeZone)\n return exceptions.some((exc) => {\n const start = getDayKeyInTimezone(new Date(exc.date), timeZone)\n const end = exc.endDate ? getDayKeyInTimezone(new Date(exc.endDate), timeZone) : start\n return target >= start && target <= end\n })\n}\n\ntype RecurringSlot = {\n day: DayOfWeek\n endTime: string\n startTime: string\n}\n\ntype ManualSlot = {\n date: string\n endTime: string\n startTime: string\n}\n\ntype Schedule = {\n active?: boolean\n exceptions?: Array<{ date: string; endDate?: string }>\n manualSlots?: ManualSlot[]\n recurringSlots?: RecurringSlot[]\n scheduleType: 'manual' | 'recurring'\n}\n\ntype TimeRange = {\n end: Date\n start: Date\n}\n\n/**\n * Resolve a schedule to concrete available time ranges for a calendar day.\n * `date` may be a Date instant (keyed in `timeZone`) or a YYYY-MM-DD day key.\n * All wall-clock times (HH:mm) are interpreted in `timeZone`.\n */\nexport function resolveScheduleForDate(\n schedule: Schedule,\n date: Date | string,\n timeZone: string = 'UTC',\n): TimeRange[] {\n if (schedule.active === false) {return []}\n\n const dayKey = typeof date === 'string' ? date : getDayKeyInTimezone(date, timeZone)\n\n const exceptions = schedule.exceptions ?? []\n if (isExceptionDate(dayKey, exceptions, timeZone)) {return []}\n\n const ranges: TimeRange[] = []\n\n if (schedule.scheduleType === 'recurring') {\n const slots = schedule.recurringSlots ?? []\n const dayOfWeek = getDayOfWeekFromDayKey(dayKey)\n for (const slot of slots) {\n if (slot.day === dayOfWeek) {\n ranges.push({\n end: combineDayKeyAndTime(dayKey, slot.endTime, timeZone),\n start: combineDayKeyAndTime(dayKey, slot.startTime, timeZone),\n })\n }\n }\n } else if (schedule.scheduleType === 'manual') {\n const slots = schedule.manualSlots ?? []\n for (const slot of slots) {\n const slotDayKey = getDayKeyInTimezone(new Date(slot.date), timeZone)\n if (slotDayKey === dayKey) {\n ranges.push({\n end: combineDayKeyAndTime(dayKey, slot.endTime, timeZone),\n start: combineDayKeyAndTime(dayKey, slot.startTime, timeZone),\n })\n }\n }\n }\n\n return ranges\n}\n"],"names":["combineDayKeyAndTime","getDayKeyInTimezone","getDayOfWeekFromDayKey","DAY_MAP","fri","mon","sat","sun","thu","tue","wed","getDayOfWeek","date","jsDay","getDay","entries","Object","found","find","num","dateMatchesDay","day","parseTime","time","h","m","split","map","Number","hours","minutes","combineDateAndTime","combined","Date","setHours","isExceptionDate","exceptions","timeZone","target","some","exc","start","end","endDate","resolveScheduleForDate","schedule","active","dayKey","ranges","scheduleType","slots","recurringSlots","dayOfWeek","slot","push","endTime","startTime","manualSlots","slotDayKey"],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Where } from 'payload';
|
|
2
|
+
type CollectionLike = {
|
|
3
|
+
fields?: unknown[];
|
|
4
|
+
} | null | undefined;
|
|
5
|
+
type TenantGate = {
|
|
6
|
+
hasField: boolean;
|
|
7
|
+
tenantField: string;
|
|
8
|
+
tenantId: null | string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* True if the collection has a top-level field named `fieldName`.
|
|
12
|
+
*
|
|
13
|
+
* Intentionally scans only top-level fields — does NOT descend into groups,
|
|
14
|
+
* rows, or tabs. This is by design: the multi-tenant plugin injects its tenant
|
|
15
|
+
* field at the top level of the collection, so a shallow scan is both correct
|
|
16
|
+
* and sufficient.
|
|
17
|
+
*/
|
|
18
|
+
export declare function collectionHasTenantField(collection: CollectionLike, fieldName: string): boolean;
|
|
19
|
+
/** Parse a single cookie value from a Cookie header / document.cookie string. */
|
|
20
|
+
export declare function readCookie(cookieHeader: null | string | undefined, name: string): null | string;
|
|
21
|
+
/** REST query params: `{ 'where[<field>][equals]': id }`, or `{}` when not scoping. */
|
|
22
|
+
export declare function tenantQueryParams({ hasField, tenantField, tenantId }: TenantGate): Record<string, string>;
|
|
23
|
+
/** Local API `Where` clause, or `null` when not scoping. */
|
|
24
|
+
export declare function tenantWhereClause({ hasField, tenantField, tenantId }: TenantGate): null | Where;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True if the collection has a top-level field named `fieldName`.
|
|
3
|
+
*
|
|
4
|
+
* Intentionally scans only top-level fields — does NOT descend into groups,
|
|
5
|
+
* rows, or tabs. This is by design: the multi-tenant plugin injects its tenant
|
|
6
|
+
* field at the top level of the collection, so a shallow scan is both correct
|
|
7
|
+
* and sufficient.
|
|
8
|
+
*/ export function collectionHasTenantField(collection, fieldName) {
|
|
9
|
+
if (!collection || !Array.isArray(collection.fields)) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
return collection.fields.some((f)=>typeof f === 'object' && f !== null && 'name' in f && f.name === fieldName);
|
|
13
|
+
}
|
|
14
|
+
/** Parse a single cookie value from a Cookie header / document.cookie string. */ export function readCookie(cookieHeader, name) {
|
|
15
|
+
if (!cookieHeader) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
for (const part of cookieHeader.split(';')){
|
|
19
|
+
const idx = part.indexOf('=');
|
|
20
|
+
if (idx === -1) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (part.slice(0, idx).trim() === name) {
|
|
24
|
+
const raw = part.slice(idx + 1).trim();
|
|
25
|
+
if (!raw) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
return decodeURIComponent(raw);
|
|
30
|
+
} catch {
|
|
31
|
+
return raw;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/** REST query params: `{ 'where[<field>][equals]': id }`, or `{}` when not scoping. */ export function tenantQueryParams({ hasField, tenantField, tenantId }) {
|
|
38
|
+
if (!hasField || !tenantId) {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
[`where[${tenantField}][equals]`]: tenantId
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Local API `Where` clause, or `null` when not scoping. */ export function tenantWhereClause({ hasField, tenantField, tenantId }) {
|
|
46
|
+
if (!hasField || !tenantId) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
[tenantField]: {
|
|
51
|
+
equals: tenantId
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=tenantFilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/tenantFilter.ts"],"sourcesContent":["import type { Where } from 'payload'\n\ntype CollectionLike = { fields?: unknown[] } | null | undefined\n\ntype TenantGate = {\n hasField: boolean\n tenantField: string\n tenantId: null | string\n}\n\n/**\n * True if the collection has a top-level field named `fieldName`.\n *\n * Intentionally scans only top-level fields — does NOT descend into groups,\n * rows, or tabs. This is by design: the multi-tenant plugin injects its tenant\n * field at the top level of the collection, so a shallow scan is both correct\n * and sufficient.\n */\nexport function collectionHasTenantField(collection: CollectionLike, fieldName: string): boolean {\n if (!collection || !Array.isArray(collection.fields)) {\n return false\n }\n return collection.fields.some(\n (f) => typeof f === 'object' && f !== null && 'name' in f && (f as { name?: string }).name === fieldName,\n )\n}\n\n/** Parse a single cookie value from a Cookie header / document.cookie string. */\nexport function readCookie(cookieHeader: null | string | undefined, name: string): null | string {\n if (!cookieHeader) {\n return null\n }\n for (const part of cookieHeader.split(';')) {\n const idx = part.indexOf('=')\n if (idx === -1) {\n continue\n }\n if (part.slice(0, idx).trim() === name) {\n const raw = part.slice(idx + 1).trim()\n if (!raw) {\n return null\n }\n try {\n return decodeURIComponent(raw)\n } catch {\n return raw\n }\n }\n }\n return null\n}\n\n/** REST query params: `{ 'where[<field>][equals]': id }`, or `{}` when not scoping. */\nexport function tenantQueryParams({ hasField, tenantField, tenantId }: TenantGate): Record<string, string> {\n if (!hasField || !tenantId) {\n return {}\n }\n return { [`where[${tenantField}][equals]`]: tenantId }\n}\n\n/** Local API `Where` clause, or `null` when not scoping. */\nexport function tenantWhereClause({ hasField, tenantField, tenantId }: TenantGate): null | Where {\n if (!hasField || !tenantId) {\n return null\n }\n return { [tenantField]: { equals: tenantId } }\n}\n"],"names":["collectionHasTenantField","collection","fieldName","Array","isArray","fields","some","f","name","readCookie","cookieHeader","part","split","idx","indexOf","slice","trim","raw","decodeURIComponent","tenantQueryParams","hasField","tenantField","tenantId","tenantWhereClause","equals"],"mappings":"AAUA;;;;;;;CAOC,GACD,OAAO,SAASA,yBAAyBC,UAA0B,EAAEC,SAAiB;IACpF,IAAI,CAACD,cAAc,CAACE,MAAMC,OAAO,CAACH,WAAWI,MAAM,GAAG;QACpD,OAAO;IACT;IACA,OAAOJ,WAAWI,MAAM,CAACC,IAAI,CAC3B,CAACC,IAAM,OAAOA,MAAM,YAAYA,MAAM,QAAQ,UAAUA,KAAK,AAACA,EAAwBC,IAAI,KAAKN;AAEnG;AAEA,+EAA+E,GAC/E,OAAO,SAASO,WAAWC,YAAuC,EAAEF,IAAY;IAC9E,IAAI,CAACE,cAAc;QACjB,OAAO;IACT;IACA,KAAK,MAAMC,QAAQD,aAAaE,KAAK,CAAC,KAAM;QAC1C,MAAMC,MAAMF,KAAKG,OAAO,CAAC;QACzB,IAAID,QAAQ,CAAC,GAAG;YACd;QACF;QACA,IAAIF,KAAKI,KAAK,CAAC,GAAGF,KAAKG,IAAI,OAAOR,MAAM;YACtC,MAAMS,MAAMN,KAAKI,KAAK,CAACF,MAAM,GAAGG,IAAI;YACpC,IAAI,CAACC,KAAK;gBACR,OAAO;YACT;YACA,IAAI;gBACF,OAAOC,mBAAmBD;YAC5B,EAAE,OAAM;gBACN,OAAOA;YACT;QACF;IACF;IACA,OAAO;AACT;AAEA,qFAAqF,GACrF,OAAO,SAASE,kBAAkB,EAAEC,QAAQ,EAAEC,WAAW,EAAEC,QAAQ,EAAc;IAC/E,IAAI,CAACF,YAAY,CAACE,UAAU;QAC1B,OAAO,CAAC;IACV;IACA,OAAO;QAAE,CAAC,CAAC,MAAM,EAAED,YAAY,SAAS,CAAC,CAAC,EAAEC;IAAS;AACvD;AAEA,0DAA0D,GAC1D,OAAO,SAASC,kBAAkB,EAAEH,QAAQ,EAAEC,WAAW,EAAEC,QAAQ,EAAc;IAC/E,IAAI,CAACF,YAAY,CAACE,UAAU;QAC1B,OAAO;IACT;IACA,OAAO;QAAE,CAACD,YAAY,EAAE;YAAEG,QAAQF;QAAS;IAAE;AAC/C"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { DayOfWeek } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wall-clock hour (0-23) of an instant as seen in the given timezone.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getHourInTimezone(date: Date, timeZone: string): number;
|
|
6
|
+
/**
|
|
7
|
+
* True when the string is a real calendar date in YYYY-MM-DD form
|
|
8
|
+
* (round-trip check rejects shape-valid impossibilities like 2026-02-30).
|
|
9
|
+
*/
|
|
10
|
+
export declare function isValidDayKey(dayKey: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Throws when the given string is not a valid IANA timezone name.
|
|
13
|
+
*/
|
|
14
|
+
export declare function validateTimezone(timeZone: string): void;
|
|
15
|
+
/**
|
|
16
|
+
* Calendar day key (YYYY-MM-DD) of an instant as seen in the given timezone.
|
|
17
|
+
* en-CA locale formats dates as YYYY-MM-DD natively.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getDayKeyInTimezone(date: Date, timeZone: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Day of week for a calendar date — TZ-independent pure calendar math.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getDayOfWeekFromDayKey(dayKey: string): DayOfWeek;
|
|
24
|
+
/**
|
|
25
|
+
* The UTC instant of wall-clock `HH:mm` on calendar day `dayKey` in `timeZone`.
|
|
26
|
+
* Two-pass offset algorithm; DST-safe. Spring-forward gap times resolve to a
|
|
27
|
+
* nearby valid instant (for midnight-gap zones like America/Santiago this can be
|
|
28
|
+
* late on the prior calendar day); fall-back ambiguous times resolve to one
|
|
29
|
+
* consistent occurrence.
|
|
30
|
+
*/
|
|
31
|
+
export declare function combineDayKeyAndTime(dayKey: string, time: string, timeZone: string): Date;
|
|
32
|
+
/**
|
|
33
|
+
* Instant of 23:59:59.999 (in `timeZone`) on the day containing `date`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function endOfDayInTimezone(date: Date, timeZone: string): Date;
|
|
36
|
+
/**
|
|
37
|
+
* Pure calendar arithmetic on day keys — DST-proof day iteration.
|
|
38
|
+
*/
|
|
39
|
+
export declare function addDaysToDayKey(dayKey: string, days: number): string;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const DAY_BY_UTC_INDEX = [
|
|
2
|
+
'sun',
|
|
3
|
+
'mon',
|
|
4
|
+
'tue',
|
|
5
|
+
'wed',
|
|
6
|
+
'thu',
|
|
7
|
+
'fri',
|
|
8
|
+
'sat'
|
|
9
|
+
];
|
|
10
|
+
const DAY_KEY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
11
|
+
const TIME_RE = /^(?:[01]\d|2[0-3]):[0-5]\d$/;
|
|
12
|
+
const dayKeyFormatters = new Map();
|
|
13
|
+
const wallClockFormatters = new Map();
|
|
14
|
+
function getDayKeyFormatter(timeZone) {
|
|
15
|
+
let formatter = dayKeyFormatters.get(timeZone);
|
|
16
|
+
if (!formatter) {
|
|
17
|
+
formatter = new Intl.DateTimeFormat('en-CA', {
|
|
18
|
+
day: '2-digit',
|
|
19
|
+
month: '2-digit',
|
|
20
|
+
timeZone,
|
|
21
|
+
year: 'numeric'
|
|
22
|
+
});
|
|
23
|
+
dayKeyFormatters.set(timeZone, formatter);
|
|
24
|
+
}
|
|
25
|
+
return formatter;
|
|
26
|
+
}
|
|
27
|
+
function getWallClockFormatter(timeZone) {
|
|
28
|
+
let formatter = wallClockFormatters.get(timeZone);
|
|
29
|
+
if (!formatter) {
|
|
30
|
+
formatter = new Intl.DateTimeFormat('en-CA', {
|
|
31
|
+
day: '2-digit',
|
|
32
|
+
hour: '2-digit',
|
|
33
|
+
hourCycle: 'h23',
|
|
34
|
+
minute: '2-digit',
|
|
35
|
+
month: '2-digit',
|
|
36
|
+
second: '2-digit',
|
|
37
|
+
timeZone,
|
|
38
|
+
year: 'numeric'
|
|
39
|
+
});
|
|
40
|
+
wallClockFormatters.set(timeZone, formatter);
|
|
41
|
+
}
|
|
42
|
+
return formatter;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Wall-clock hour (0-23) of an instant as seen in the given timezone.
|
|
46
|
+
*/ export function getHourInTimezone(date, timeZone) {
|
|
47
|
+
const parts = getWallClockFormatter(timeZone).formatToParts(date);
|
|
48
|
+
return Number(parts.find((p)=>p.type === 'hour')?.value ?? '0');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* True when the string is a real calendar date in YYYY-MM-DD form
|
|
52
|
+
* (round-trip check rejects shape-valid impossibilities like 2026-02-30).
|
|
53
|
+
*/ export function isValidDayKey(dayKey) {
|
|
54
|
+
if (!DAY_KEY_RE.test(dayKey)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
return new Date(`${dayKey}T00:00:00Z`).toISOString().slice(0, 10) === dayKey;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Throws when the given string is not a valid IANA timezone name.
|
|
65
|
+
*/ export function validateTimezone(timeZone) {
|
|
66
|
+
try {
|
|
67
|
+
new Intl.DateTimeFormat('en-US', {
|
|
68
|
+
timeZone
|
|
69
|
+
});
|
|
70
|
+
} catch {
|
|
71
|
+
throw new Error(`Invalid timezone "${timeZone}" — use an IANA name like 'Europe/Paris' or 'UTC'`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Calendar day key (YYYY-MM-DD) of an instant as seen in the given timezone.
|
|
76
|
+
* en-CA locale formats dates as YYYY-MM-DD natively.
|
|
77
|
+
*/ export function getDayKeyInTimezone(date, timeZone) {
|
|
78
|
+
return getDayKeyFormatter(timeZone).format(date);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Day of week for a calendar date — TZ-independent pure calendar math.
|
|
82
|
+
*/ export function getDayOfWeekFromDayKey(dayKey) {
|
|
83
|
+
if (!DAY_KEY_RE.test(dayKey)) {
|
|
84
|
+
throw new Error(`Invalid day key "${dayKey}" — expected YYYY-MM-DD`);
|
|
85
|
+
}
|
|
86
|
+
return DAY_BY_UTC_INDEX[new Date(`${dayKey}T00:00:00Z`).getUTCDay()];
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Wall-clock parts of a UTC instant in the given timezone, reconstructed as a
|
|
90
|
+
* UTC timestamp — the difference to the instant is the zone's offset.
|
|
91
|
+
*/ function offsetMs(timeZone, utcDate) {
|
|
92
|
+
const parts = getWallClockFormatter(timeZone).formatToParts(utcDate);
|
|
93
|
+
const get = (type)=>Number(parts.find((p)=>p.type === type)?.value ?? '0');
|
|
94
|
+
const asUTC = Date.UTC(get('year'), get('month') - 1, get('day'), get('hour'), get('minute'), get('second'));
|
|
95
|
+
return asUTC - utcDate.getTime();
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The UTC instant of wall-clock `HH:mm` on calendar day `dayKey` in `timeZone`.
|
|
99
|
+
* Two-pass offset algorithm; DST-safe. Spring-forward gap times resolve to a
|
|
100
|
+
* nearby valid instant (for midnight-gap zones like America/Santiago this can be
|
|
101
|
+
* late on the prior calendar day); fall-back ambiguous times resolve to one
|
|
102
|
+
* consistent occurrence.
|
|
103
|
+
*/ export function combineDayKeyAndTime(dayKey, time, timeZone) {
|
|
104
|
+
if (!DAY_KEY_RE.test(dayKey)) {
|
|
105
|
+
throw new Error(`Invalid day key "${dayKey}" — expected YYYY-MM-DD`);
|
|
106
|
+
}
|
|
107
|
+
if (!TIME_RE.test(time)) {
|
|
108
|
+
throw new Error(`Invalid time "${time}" — expected HH:mm`);
|
|
109
|
+
}
|
|
110
|
+
const [y, mo, d] = dayKey.split('-').map(Number);
|
|
111
|
+
const [h, mi] = time.split(':').map(Number);
|
|
112
|
+
const guess = Date.UTC(y, mo - 1, d, h, mi);
|
|
113
|
+
const candidate = guess - offsetMs(timeZone, new Date(guess));
|
|
114
|
+
return new Date(guess - offsetMs(timeZone, new Date(candidate)));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Instant of 23:59:59.999 (in `timeZone`) on the day containing `date`.
|
|
118
|
+
*/ export function endOfDayInTimezone(date, timeZone) {
|
|
119
|
+
const dayKey = getDayKeyInTimezone(date, timeZone);
|
|
120
|
+
const lastMinute = combineDayKeyAndTime(dayKey, '23:59', timeZone);
|
|
121
|
+
return new Date(lastMinute.getTime() + 59_999);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Pure calendar arithmetic on day keys — DST-proof day iteration.
|
|
125
|
+
*/ export function addDaysToDayKey(dayKey, days) {
|
|
126
|
+
if (!DAY_KEY_RE.test(dayKey)) {
|
|
127
|
+
throw new Error(`Invalid day key "${dayKey}" — expected YYYY-MM-DD`);
|
|
128
|
+
}
|
|
129
|
+
const base = new Date(`${dayKey}T00:00:00Z`);
|
|
130
|
+
const shifted = new Date(base.getTime() + days * 86_400_000);
|
|
131
|
+
return shifted.toISOString().slice(0, 10);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
//# sourceMappingURL=timezoneUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/timezoneUtils.ts"],"sourcesContent":["import type { DayOfWeek } from '../types.js'\n\nconst DAY_BY_UTC_INDEX: DayOfWeek[] = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']\n\nconst DAY_KEY_RE = /^\\d{4}-\\d{2}-\\d{2}$/\n\nconst TIME_RE = /^(?:[01]\\d|2[0-3]):[0-5]\\d$/\n\nconst dayKeyFormatters = new Map<string, Intl.DateTimeFormat>()\nconst wallClockFormatters = new Map<string, Intl.DateTimeFormat>()\n\nfunction getDayKeyFormatter(timeZone: string): Intl.DateTimeFormat {\n let formatter = dayKeyFormatters.get(timeZone)\n if (!formatter) {\n formatter = new Intl.DateTimeFormat('en-CA', {\n day: '2-digit',\n month: '2-digit',\n timeZone,\n year: 'numeric',\n })\n dayKeyFormatters.set(timeZone, formatter)\n }\n return formatter\n}\n\nfunction getWallClockFormatter(timeZone: string): Intl.DateTimeFormat {\n let formatter = wallClockFormatters.get(timeZone)\n if (!formatter) {\n formatter = new Intl.DateTimeFormat('en-CA', {\n day: '2-digit',\n hour: '2-digit',\n hourCycle: 'h23',\n minute: '2-digit',\n month: '2-digit',\n second: '2-digit',\n timeZone,\n year: 'numeric',\n })\n wallClockFormatters.set(timeZone, formatter)\n }\n return formatter\n}\n\n/**\n * Wall-clock hour (0-23) of an instant as seen in the given timezone.\n */\nexport function getHourInTimezone(date: Date, timeZone: string): number {\n const parts = getWallClockFormatter(timeZone).formatToParts(date)\n return Number(parts.find((p) => p.type === 'hour')?.value ?? '0')\n}\n\n/**\n * True when the string is a real calendar date in YYYY-MM-DD form\n * (round-trip check rejects shape-valid impossibilities like 2026-02-30).\n */\nexport function isValidDayKey(dayKey: string): boolean {\n if (!DAY_KEY_RE.test(dayKey)) {\n return false\n }\n try {\n return new Date(`${dayKey}T00:00:00Z`).toISOString().slice(0, 10) === dayKey\n } catch {\n return false\n }\n}\n\n/**\n * Throws when the given string is not a valid IANA timezone name.\n */\nexport function validateTimezone(timeZone: string): void {\n try {\n new Intl.DateTimeFormat('en-US', { timeZone })\n } catch {\n throw new Error(\n `Invalid timezone \"${timeZone}\" — use an IANA name like 'Europe/Paris' or 'UTC'`,\n )\n }\n}\n\n/**\n * Calendar day key (YYYY-MM-DD) of an instant as seen in the given timezone.\n * en-CA locale formats dates as YYYY-MM-DD natively.\n */\nexport function getDayKeyInTimezone(date: Date, timeZone: string): string {\n return getDayKeyFormatter(timeZone).format(date)\n}\n\n/**\n * Day of week for a calendar date — TZ-independent pure calendar math.\n */\nexport function getDayOfWeekFromDayKey(dayKey: string): DayOfWeek {\n if (!DAY_KEY_RE.test(dayKey)) {\n throw new Error(`Invalid day key \"${dayKey}\" — expected YYYY-MM-DD`)\n }\n return DAY_BY_UTC_INDEX[new Date(`${dayKey}T00:00:00Z`).getUTCDay()]\n}\n\n/**\n * Wall-clock parts of a UTC instant in the given timezone, reconstructed as a\n * UTC timestamp — the difference to the instant is the zone's offset.\n */\nfunction offsetMs(timeZone: string, utcDate: Date): number {\n const parts = getWallClockFormatter(timeZone).formatToParts(utcDate)\n const get = (type: string): number =>\n Number(parts.find((p) => p.type === type)?.value ?? '0')\n const asUTC = Date.UTC(\n get('year'),\n get('month') - 1,\n get('day'),\n get('hour'),\n get('minute'),\n get('second'),\n )\n return asUTC - utcDate.getTime()\n}\n\n/**\n * The UTC instant of wall-clock `HH:mm` on calendar day `dayKey` in `timeZone`.\n * Two-pass offset algorithm; DST-safe. Spring-forward gap times resolve to a\n * nearby valid instant (for midnight-gap zones like America/Santiago this can be\n * late on the prior calendar day); fall-back ambiguous times resolve to one\n * consistent occurrence.\n */\nexport function combineDayKeyAndTime(dayKey: string, time: string, timeZone: string): Date {\n if (!DAY_KEY_RE.test(dayKey)) {\n throw new Error(`Invalid day key \"${dayKey}\" — expected YYYY-MM-DD`)\n }\n if (!TIME_RE.test(time)) {\n throw new Error(`Invalid time \"${time}\" — expected HH:mm`)\n }\n const [y, mo, d] = dayKey.split('-').map(Number)\n const [h, mi] = time.split(':').map(Number)\n const guess = Date.UTC(y, mo - 1, d, h, mi)\n const candidate = guess - offsetMs(timeZone, new Date(guess))\n return new Date(guess - offsetMs(timeZone, new Date(candidate)))\n}\n\n/**\n * Instant of 23:59:59.999 (in `timeZone`) on the day containing `date`.\n */\nexport function endOfDayInTimezone(date: Date, timeZone: string): Date {\n const dayKey = getDayKeyInTimezone(date, timeZone)\n const lastMinute = combineDayKeyAndTime(dayKey, '23:59', timeZone)\n return new Date(lastMinute.getTime() + 59_999)\n}\n\n/**\n * Pure calendar arithmetic on day keys — DST-proof day iteration.\n */\nexport function addDaysToDayKey(dayKey: string, days: number): string {\n if (!DAY_KEY_RE.test(dayKey)) {\n throw new Error(`Invalid day key \"${dayKey}\" — expected YYYY-MM-DD`)\n }\n const base = new Date(`${dayKey}T00:00:00Z`)\n const shifted = new Date(base.getTime() + days * 86_400_000)\n return shifted.toISOString().slice(0, 10)\n}\n"],"names":["DAY_BY_UTC_INDEX","DAY_KEY_RE","TIME_RE","dayKeyFormatters","Map","wallClockFormatters","getDayKeyFormatter","timeZone","formatter","get","Intl","DateTimeFormat","day","month","year","set","getWallClockFormatter","hour","hourCycle","minute","second","getHourInTimezone","date","parts","formatToParts","Number","find","p","type","value","isValidDayKey","dayKey","test","Date","toISOString","slice","validateTimezone","Error","getDayKeyInTimezone","format","getDayOfWeekFromDayKey","getUTCDay","offsetMs","utcDate","asUTC","UTC","getTime","combineDayKeyAndTime","time","y","mo","d","split","map","h","mi","guess","candidate","endOfDayInTimezone","lastMinute","addDaysToDayKey","days","base","shifted"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST query params scoping a collection's fetch to the selected tenant.
|
|
3
|
+
* Returns `{}` when the collection has no tenant field or no tenant cookie is set,
|
|
4
|
+
* so single-tenant installs build identical URLs to before.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useTenantFilter(collectionSlug: string): Record<string, string>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useConfig } from '@payloadcms/ui';
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import { collectionHasTenantField, readCookie, tenantQueryParams } from './tenantFilter.js';
|
|
5
|
+
/**
|
|
6
|
+
* REST query params scoping a collection's fetch to the selected tenant.
|
|
7
|
+
* Returns `{}` when the collection has no tenant field or no tenant cookie is set,
|
|
8
|
+
* so single-tenant installs build identical URLs to before.
|
|
9
|
+
*/ export function useTenantFilter(collectionSlug) {
|
|
10
|
+
const { config } = useConfig();
|
|
11
|
+
const tenantConfig = config.admin?.custom?.reservationTenant ?? {};
|
|
12
|
+
const cookieName = tenantConfig.cookieName ?? 'payload-tenant';
|
|
13
|
+
const tenantField = tenantConfig.tenantField ?? 'tenant';
|
|
14
|
+
const collection = config.collections?.find((c)=>c.slug === collectionSlug);
|
|
15
|
+
const hasField = collectionHasTenantField(collection, tenantField);
|
|
16
|
+
const tenantId = typeof document !== 'undefined' ? readCookie(document.cookie, cookieName) : null;
|
|
17
|
+
return useMemo(()=>tenantQueryParams({
|
|
18
|
+
hasField,
|
|
19
|
+
tenantField,
|
|
20
|
+
tenantId
|
|
21
|
+
}), [
|
|
22
|
+
hasField,
|
|
23
|
+
tenantField,
|
|
24
|
+
tenantId
|
|
25
|
+
]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=useTenantFilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/useTenantFilter.ts"],"sourcesContent":["'use client'\nimport { useConfig } from '@payloadcms/ui'\nimport { useMemo } from 'react'\n\nimport { collectionHasTenantField, readCookie, tenantQueryParams } from './tenantFilter.js'\n\n/**\n * REST query params scoping a collection's fetch to the selected tenant.\n * Returns `{}` when the collection has no tenant field or no tenant cookie is set,\n * so single-tenant installs build identical URLs to before.\n */\nexport function useTenantFilter(collectionSlug: string): Record<string, string> {\n const { config } = useConfig()\n const tenantConfig =\n (config.admin?.custom?.reservationTenant as\n | { cookieName?: string; tenantField?: string }\n | undefined) ?? {}\n const cookieName = tenantConfig.cookieName ?? 'payload-tenant'\n const tenantField = tenantConfig.tenantField ?? 'tenant'\n const collection = config.collections?.find((c) => c.slug === collectionSlug)\n const hasField = collectionHasTenantField(collection as { fields?: unknown[] } | undefined, tenantField)\n const tenantId = typeof document !== 'undefined' ? readCookie(document.cookie, cookieName) : null\n\n return useMemo(\n () => tenantQueryParams({ hasField, tenantField, tenantId }),\n [hasField, tenantField, tenantId],\n )\n}\n"],"names":["useConfig","useMemo","collectionHasTenantField","readCookie","tenantQueryParams","useTenantFilter","collectionSlug","config","tenantConfig","admin","custom","reservationTenant","cookieName","tenantField","collection","collections","find","c","slug","hasField","tenantId","document","cookie"],"mappings":"AAAA;AACA,SAASA,SAAS,QAAQ,iBAAgB;AAC1C,SAASC,OAAO,QAAQ,QAAO;AAE/B,SAASC,wBAAwB,EAAEC,UAAU,EAAEC,iBAAiB,QAAQ,oBAAmB;AAE3F;;;;CAIC,GACD,OAAO,SAASC,gBAAgBC,cAAsB;IACpD,MAAM,EAAEC,MAAM,EAAE,GAAGP;IACnB,MAAMQ,eACJ,AAACD,OAAOE,KAAK,EAAEC,QAAQC,qBAEL,CAAC;IACrB,MAAMC,aAAaJ,aAAaI,UAAU,IAAI;IAC9C,MAAMC,cAAcL,aAAaK,WAAW,IAAI;IAChD,MAAMC,aAAaP,OAAOQ,WAAW,EAAEC,KAAK,CAACC,IAAMA,EAAEC,IAAI,KAAKZ;IAC9D,MAAMa,WAAWjB,yBAAyBY,YAAkDD;IAC5F,MAAMO,WAAW,OAAOC,aAAa,cAAclB,WAAWkB,SAASC,MAAM,EAAEV,cAAc;IAE7F,OAAOX,QACL,IAAMG,kBAAkB;YAAEe;YAAUN;YAAaO;QAAS,IAC1D;QAACD;QAAUN;QAAaO;KAAS;AAErC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payload-reserve",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "A Payload CMS 3.x plugin for reservation and booking management with conflict detection, status workflows, and calendar UI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"payload",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"@payloadcms/db-sqlite": "3.79.0",
|
|
55
55
|
"@payloadcms/eslint-config": "3.9.0",
|
|
56
56
|
"@payloadcms/next": "3.79.0",
|
|
57
|
+
"@payloadcms/plugin-multi-tenant": "3.79.0",
|
|
57
58
|
"@payloadcms/richtext-lexical": "3.79.0",
|
|
58
59
|
"@payloadcms/translations": "3.79.0",
|
|
59
60
|
"@payloadcms/ui": "3.79.0",
|