@nuxt-customer-portal/planning 0.4.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/CHANGELOG.md +61 -0
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/app/components/PlanningAppointmentCreateForm.vue +49 -0
- package/app/components/PlanningAppointmentDetails.vue +163 -0
- package/app/components/PlanningAppointmentDrawer.vue +109 -0
- package/app/components/PlanningAvailabilityCalendar.vue +525 -0
- package/app/components/PlanningDashboardOverview.vue +62 -0
- package/app/components/PlanningDashboardUpcomingAppointments.vue +81 -0
- package/app/components/PlanningPersonalCalendarSettings.vue +218 -0
- package/app/components/PlanningPersonalConnection.vue +68 -0
- package/app/components/PlanningPersonalMeetingSettings.vue +1 -0
- package/app/components/PlanningPolicyFields.vue +196 -0
- package/app/components/PlanningSettingsMembers.vue +50 -0
- package/app/components/PlanningSettingsPolicy.vue +46 -0
- package/app/components/PlanningSettingsSynchronization.vue +433 -0
- package/app/components/PlanningSlotPicker.vue +588 -0
- package/app/components/PlanningTimezoneReadOnlyHover.vue +172 -0
- package/app/components/PlanningTimezoneSelect.vue +101 -0
- package/app/composables/usePlanning.ts +180 -0
- package/app/composables/usePlanningDashboard.ts +4 -0
- package/app/composables/usePlanningTimeDisplay.ts +15 -0
- package/app/pages/admin/planning.vue +36 -0
- package/app/pages/appointments/[id].vue +13 -0
- package/app/pages/appointments/index.vue +384 -0
- package/app/pages/availability.vue +585 -0
- package/app/pages/booking/[slug].vue +298 -0
- package/app/pages/my-appointment-settings.vue +33 -0
- package/app/plugins/planning.ts +5 -0
- package/i18n/locales/en.json +260 -0
- package/i18n/locales/nl.json +260 -0
- package/migrations/0000_baseline.sql +58 -0
- package/migrations/0001_availability_calendar_title.sql +2 -0
- package/migrations/0002_job_activity.sql +11 -0
- package/migrations/0003_deduplicate_availability_jobs.sql +19 -0
- package/migrations/0004_connection_label.sql +2 -0
- package/nuxt.config.ts +15 -0
- package/package.json +55 -0
- package/portal.manifest.mjs +8 -0
- package/server/api/planning/admin/appointments/[id]/audit.get.ts +11 -0
- package/server/api/planning/admin/appointments.get.ts +11 -0
- package/server/api/planning/admin/jobs/[id]/retry.post.ts +26 -0
- package/server/api/planning/admin/jobs.get.ts +199 -0
- package/server/api/planning/admin/policy.get.ts +6 -0
- package/server/api/planning/admin/policy.put.ts +8 -0
- package/server/api/planning/admin/products/[id].put.ts +8 -0
- package/server/api/planning/admin/providers/[userId].patch.ts +8 -0
- package/server/api/planning/admin/providers.get.ts +6 -0
- package/server/api/planning/admin/retry.post.ts +31 -0
- package/server/api/planning/appointments/[id]/availability.get.ts +19 -0
- package/server/api/planning/appointments/[id]/cancel.post.ts +8 -0
- package/server/api/planning/appointments/[id]/files/[assetId].get.ts +17 -0
- package/server/api/planning/appointments/[id]/files.get.ts +28 -0
- package/server/api/planning/appointments/[id]/pending.delete.ts +8 -0
- package/server/api/planning/appointments/[id]/reschedule.put.ts +8 -0
- package/server/api/planning/appointments/[id].get.ts +6 -0
- package/server/api/planning/appointments.get.ts +4 -0
- package/server/api/planning/availability/[id].delete.ts +12 -0
- package/server/api/planning/availability/[id].put.ts +8 -0
- package/server/api/planning/availability.get.ts +6 -0
- package/server/api/planning/availability.post.ts +8 -0
- package/server/api/planning/calendar-busy.get.ts +29 -0
- package/server/api/planning/calendars.get.ts +8 -0
- package/server/api/planning/dashboard.get.ts +36 -0
- package/server/api/planning/google/notifications.post.ts +24 -0
- package/server/api/planning/oauth/[provider]/callback.get.ts +18 -0
- package/server/api/planning/oauth/[provider]/start.post.ts +8 -0
- package/server/api/planning/oauth/[provider].delete.ts +15 -0
- package/server/api/planning/products.get.ts +11 -0
- package/server/api/planning/provider-appointments.get.ts +11 -0
- package/server/api/planning/provider.get.ts +6 -0
- package/server/api/planning/provider.put.ts +8 -0
- package/server/api/store/planning/[productId]/availability.get.ts +8 -0
- package/server/api/store/planning/hold.delete.ts +11 -0
- package/server/api/store/planning/hold.get.ts +36 -0
- package/server/api/store/planning/holds.post.ts +10 -0
- package/server/db/schema/planning.ts +213 -0
- package/server/plugins/openapi.ts +36 -0
- package/server/plugins/planning.ts +6 -0
- package/server/tasks/planning/reconcile.ts +30 -0
- package/server/utils/access.ts +80 -0
- package/server/utils/adapters.ts +468 -0
- package/server/utils/appointments.ts +37 -0
- package/server/utils/booking.ts +619 -0
- package/server/utils/crypto.ts +23 -0
- package/server/utils/jobs.ts +671 -0
- package/server/utils/management.ts +427 -0
- package/server/utils/oauth.ts +104 -0
- package/server/utils/staff-appointments.ts +158 -0
- package/shared/appointment-calendar.ts +82 -0
- package/shared/availability.ts +140 -0
- package/shared/emails.ts +99 -0
- package/shared/feature.ts +78 -0
- package/shared/invitation.ts +58 -0
- package/shared/types.ts +110 -0
- package/shared/validation.ts +77 -0
package/shared/types.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { PlanningPolicy } from '@nuxt-customer-portal/products/shared/planning'
|
|
2
|
+
|
|
3
|
+
export type { PlanningPolicy, ProductPlanning } from '@nuxt-customer-portal/products/shared/planning'
|
|
4
|
+
export interface Interval {
|
|
5
|
+
start: string
|
|
6
|
+
end: string
|
|
7
|
+
}
|
|
8
|
+
export interface AvailabilityWindow {
|
|
9
|
+
id: string
|
|
10
|
+
userId: string
|
|
11
|
+
timezone: string
|
|
12
|
+
date: string
|
|
13
|
+
endDate: string | null
|
|
14
|
+
startTime: string
|
|
15
|
+
endTime: string
|
|
16
|
+
recurring: boolean
|
|
17
|
+
productIds: string[] | null
|
|
18
|
+
exceptions: string[]
|
|
19
|
+
}
|
|
20
|
+
export interface Slot extends Interval {
|
|
21
|
+
providerUserId: string
|
|
22
|
+
providerName: string
|
|
23
|
+
}
|
|
24
|
+
export interface AppointmentSnapshot {
|
|
25
|
+
organizerEmail?: string
|
|
26
|
+
title: string
|
|
27
|
+
durationMinutes: number
|
|
28
|
+
graceMinutes: number
|
|
29
|
+
timezone: string
|
|
30
|
+
customerTimezone: string
|
|
31
|
+
policy: PlanningPolicy
|
|
32
|
+
currency: string
|
|
33
|
+
unitAmount: number
|
|
34
|
+
meetingProvider: 'none' | 'zoom'
|
|
35
|
+
customerNotificationRevision?: number
|
|
36
|
+
zoomLinkNotificationPending?: boolean
|
|
37
|
+
locale: 'en' | 'nl'
|
|
38
|
+
}
|
|
39
|
+
export interface Reservation {
|
|
40
|
+
id: string
|
|
41
|
+
store_id: string
|
|
42
|
+
user_id: string
|
|
43
|
+
product_id: string
|
|
44
|
+
token_hash: string
|
|
45
|
+
start_at: Date
|
|
46
|
+
end_at: Date
|
|
47
|
+
blocked_until: Date
|
|
48
|
+
confirmed_at: Date | null
|
|
49
|
+
expires_at: Date
|
|
50
|
+
status: 'reserved' | 'confirmed' | 'expired' | 'cancelled' | 'superseded'
|
|
51
|
+
order_id: string | null
|
|
52
|
+
replaces_id: string | null
|
|
53
|
+
snapshot: AppointmentSnapshot
|
|
54
|
+
}
|
|
55
|
+
export interface Appointment {
|
|
56
|
+
id: string
|
|
57
|
+
store_id: string
|
|
58
|
+
reservation_id: string
|
|
59
|
+
order_line_id: string
|
|
60
|
+
order_id: string
|
|
61
|
+
user_id: string
|
|
62
|
+
product_id: string
|
|
63
|
+
start_at: Date
|
|
64
|
+
end_at: Date
|
|
65
|
+
status: 'confirmed' | 'cancelled'
|
|
66
|
+
changes: number
|
|
67
|
+
revision: number
|
|
68
|
+
snapshot: AppointmentSnapshot
|
|
69
|
+
meeting_id: string | null
|
|
70
|
+
meeting_url: string | null
|
|
71
|
+
calendar_event_id: string | null
|
|
72
|
+
calendar_id: string | null
|
|
73
|
+
calendar_user_id: string | null
|
|
74
|
+
conflict: boolean
|
|
75
|
+
effects_error: string | null
|
|
76
|
+
}
|
|
77
|
+
export interface ProviderSettings {
|
|
78
|
+
userId: string
|
|
79
|
+
name: string
|
|
80
|
+
enabled: boolean
|
|
81
|
+
timezone: string
|
|
82
|
+
graceMinutes: number
|
|
83
|
+
availabilityCalendarTitle: string
|
|
84
|
+
busyCalendarIds: string[]
|
|
85
|
+
writeCalendarId: string | null
|
|
86
|
+
googleConnected: boolean
|
|
87
|
+
zoomConnected: boolean
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface PlanningDashboardAppointment {
|
|
91
|
+
id: string
|
|
92
|
+
title: string
|
|
93
|
+
start: string
|
|
94
|
+
end: string
|
|
95
|
+
meetingProvider: 'none' | 'zoom'
|
|
96
|
+
meetingUrl: string | null
|
|
97
|
+
providerName: string
|
|
98
|
+
providerTimezone: string
|
|
99
|
+
customerName: string
|
|
100
|
+
customerTimezone: string
|
|
101
|
+
conflict: boolean
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface PlanningDashboardDto {
|
|
105
|
+
access: { staff: boolean; canManage: boolean }
|
|
106
|
+
upcomingCount: number
|
|
107
|
+
todayCount: number
|
|
108
|
+
conflictCount: number
|
|
109
|
+
appointments: PlanningDashboardAppointment[]
|
|
110
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { isValidTimezone } from '@nuxt-customer-portal/core/shared/timezone'
|
|
3
|
+
|
|
4
|
+
export { planningPolicySchema } from '@nuxt-customer-portal/products/shared/planning'
|
|
5
|
+
export const timezoneSchema = z.string().max(100).refine(isValidTimezone, 'Choose an IANA timezone')
|
|
6
|
+
const date = z
|
|
7
|
+
.string()
|
|
8
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
9
|
+
.refine((v) => Number.isFinite(Date.parse(v)) && new Date(v).toISOString().slice(0, 10) === v, 'Invalid date')
|
|
10
|
+
const time = z.string().regex(/^(?:[01]\d|2[0-3]):[0-5]\d$/)
|
|
11
|
+
export const availabilitySchema = z
|
|
12
|
+
.object({
|
|
13
|
+
date,
|
|
14
|
+
endDate: date.nullable().default(null),
|
|
15
|
+
startTime: time,
|
|
16
|
+
endTime: z
|
|
17
|
+
.string()
|
|
18
|
+
.transform((value) => (value === '00:00' ? '24:00' : value))
|
|
19
|
+
.pipe(z.union([time, z.literal('24:00')])),
|
|
20
|
+
recurring: z.boolean().default(false),
|
|
21
|
+
productIds: z.array(z.string().min(1).max(100)).min(1).max(100).nullable().default(null),
|
|
22
|
+
exceptions: z.array(date).max(365).default([])
|
|
23
|
+
})
|
|
24
|
+
.superRefine((v, c) => {
|
|
25
|
+
if (v.endTime <= v.startTime) {
|
|
26
|
+
c.addIssue({ code: 'custom', path: ['endTime'], message: 'End must follow start on the same day' })
|
|
27
|
+
}
|
|
28
|
+
if (v.endDate && v.endDate < v.date) {
|
|
29
|
+
c.addIssue({ code: 'custom', path: ['endDate'], message: 'End date must follow start date' })
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
export const providerSettingsSchema = z.object({
|
|
33
|
+
timezone: timezoneSchema,
|
|
34
|
+
graceMinutes: z.number().int().min(0).max(1440),
|
|
35
|
+
availabilityCalendarTitle: z.string().trim().min(1).max(100),
|
|
36
|
+
busyCalendarIds: z.array(z.string().min(1).max(500)).min(1).max(20),
|
|
37
|
+
writeCalendarId: z.string().min(1).max(500)
|
|
38
|
+
})
|
|
39
|
+
export const availabilityQuerySchema = z
|
|
40
|
+
.object({
|
|
41
|
+
from: z.iso.datetime({ offset: true }),
|
|
42
|
+
to: z.iso.datetime({ offset: true }),
|
|
43
|
+
providerUserId: z.string().max(100).optional()
|
|
44
|
+
})
|
|
45
|
+
.refine(
|
|
46
|
+
(v) => Date.parse(v.to) > Date.parse(v.from) && Date.parse(v.to) - Date.parse(v.from) <= 31 * 86400000,
|
|
47
|
+
'Request at most 31 days'
|
|
48
|
+
)
|
|
49
|
+
export const holdSchema = z.object({
|
|
50
|
+
productId: z.string().min(1).max(100),
|
|
51
|
+
providerUserId: z.string().min(1).max(100),
|
|
52
|
+
start: z.iso.datetime({ offset: true }),
|
|
53
|
+
customerTimezone: timezoneSchema,
|
|
54
|
+
currency: z.string().regex(/^[A-Z]{3}$/),
|
|
55
|
+
locale: z.enum(['en', 'nl']).default('en'),
|
|
56
|
+
replacesId: z.uuid().optional(),
|
|
57
|
+
previousHoldToken: z.string().min(32).max(200).optional()
|
|
58
|
+
})
|
|
59
|
+
export const holdCredentialSchema = z.object({ holdToken: z.string().min(32).max(200) })
|
|
60
|
+
|
|
61
|
+
export const availabilityEditSchema = availabilitySchema.safeExtend({ occurrenceDate: date.optional() })
|
|
62
|
+
|
|
63
|
+
export const appointmentListSchema = z.object({
|
|
64
|
+
search: z.string().trim().max(200).default(''),
|
|
65
|
+
status: z.enum(['all', 'confirmed', 'cancelled']).default('all'),
|
|
66
|
+
conflicts: z.enum(['all', 'conflicts']).default('all'),
|
|
67
|
+
host: z.string().trim().min(1).max(100).default('all'),
|
|
68
|
+
sortBy: z.enum(['start', 'title']).default('start'),
|
|
69
|
+
sortOrder: z.enum(['asc', 'desc']).default('desc'),
|
|
70
|
+
page: z.coerce.number().int().min(1).max(100000).default(1)
|
|
71
|
+
})
|
|
72
|
+
export const staffRescheduleSchema = z.object({
|
|
73
|
+
providerUserId: z.string().min(1).max(100),
|
|
74
|
+
start: z.iso.datetime({ offset: true }),
|
|
75
|
+
customerTimezone: timezoneSchema,
|
|
76
|
+
revision: z.number().int().min(0)
|
|
77
|
+
})
|