@ossy/booking 1.16.2 → 3.0.1
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 +48 -21
- package/package.json +11 -8
- package/src/AvailabilityEditor.jsx +2 -13
- package/src/BookingList.jsx +1 -1
- package/src/BookingProductHome.jsx +38 -0
- package/src/BookingSalesPage.jsx +55 -0
- package/src/Definition.js +2 -2
- package/src/HeroCover.jsx +5 -5
- package/src/SalesSection.jsx +7 -15
- package/src/ServiceCard.jsx +2 -2
- package/src/availability-action-maps.js +39 -0
- package/src/availability-engine.js +18 -14
- package/src/availability-engine.spec.js +36 -4
- package/src/availability-setup.page.jsx +45 -46
- package/src/{availability.resource.js → availability.schema.js} +1 -5
- package/src/booking-cancellation.email.jsx +1 -1
- package/src/booking-card.component.jsx +3 -3
- package/src/booking-confirmation.email.jsx +5 -3
- package/src/booking-detail.page.jsx +15 -10
- package/src/booking-email-token.js +45 -0
- package/src/booking-home-content.js +50 -0
- package/src/booking-list.aggregate.js +65 -0
- package/src/booking-reminder.email.jsx +4 -3
- package/src/booking-request.email.jsx +5 -2
- package/src/booking-resources.js +3 -3
- package/src/{booking.resource.js → booking.schema.js} +12 -3
- package/src/bookings.component.jsx +26 -25
- package/src/bookings.page.jsx +29 -16
- package/src/cancel-booking.action.js +1 -1
- package/src/cancel-booking.task.js +14 -17
- package/src/confirm-booking.action.js +1 -1
- package/src/confirm-booking.api.js +35 -0
- package/src/confirm-booking.task.js +26 -24
- package/src/create-booking.action.js +1 -1
- package/src/create-booking.task.js +73 -37
- package/src/create-service.action.js +5 -1
- package/src/create-service.task.js +7 -7
- package/src/decline-booking.action.js +1 -1
- package/src/decline-booking.api.js +35 -0
- package/src/decline-booking.task.js +15 -18
- package/src/delete-service.action.js +1 -1
- package/src/delete-service.task.js +12 -15
- package/src/en.translations.json +83 -37
- package/src/get-availability.action.js +1 -1
- package/src/get-availability.task.js +5 -4
- package/src/get-available-slots.action.js +1 -1
- package/src/get-available-slots.task.js +16 -13
- package/src/get-services.action.js +1 -1
- package/src/get-services.task.js +13 -6
- package/src/home.page.jsx +14 -46
- package/src/index.js +17 -0
- package/src/list-bookings.action.js +1 -1
- package/src/list-bookings.task.js +33 -16
- package/src/locations.js +6 -4
- package/src/open-availability-setup.action.js +5 -0
- package/src/open-bookings.action.js +5 -0
- package/src/open-create-service.action.js +5 -0
- package/src/open-services.action.js +5 -0
- package/src/provider-availability.flow.js +39 -0
- package/src/provider-fields.js +34 -0
- package/src/provider-onboard.flow.js +25 -0
- package/src/public-booking-form.form.js +7 -0
- package/src/public-booking-form.schema.js +12 -0
- package/src/public-booking.page.jsx +60 -57
- package/src/resolve-workspace-contact.js +55 -0
- package/src/save-availability.action.js +5 -1
- package/src/save-availability.task.js +26 -28
- package/src/schema-ids.js +7 -0
- package/src/select-duration-30.action.js +4 -0
- package/src/select-duration-60.action.js +4 -0
- package/src/select-duration-90.action.js +4 -0
- package/src/send-booking-reminder.task.js +39 -14
- package/src/service-card-slot.component.jsx +4 -4
- package/src/service-detail.component.jsx +4 -4
- package/src/service.form.js +7 -0
- package/src/service.schema.js +14 -0
- package/src/services.page.jsx +62 -58
- package/src/stories/fixtures.js +149 -0
- package/src/sv.translations.json +83 -37
- package/src/time.js +27 -0
- package/src/toggle-availability-day-0.action.js +4 -0
- package/src/toggle-availability-day-1.action.js +4 -0
- package/src/toggle-availability-day-2.action.js +4 -0
- package/src/toggle-availability-day-3.action.js +4 -0
- package/src/toggle-availability-day-4.action.js +4 -0
- package/src/toggle-availability-day-5.action.js +4 -0
- package/src/toggle-availability-day-6.action.js +4 -0
- package/src/update-service.action.js +1 -1
- package/src/update-service.task.js +13 -16
- package/src/workspace-services.js +5 -0
- package/src/moduleStatus.js +0 -2
- package/src/service.resource.js +0 -32
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Resource, ResourcesEvents } from '@ossy/resources/server'
|
|
1
|
+
import { ResourcesEvents, mutateResource, viewResource } from '@ossy/resources/server'
|
|
3
2
|
import BookingCancellationEmail from './booking-cancellation.email.jsx'
|
|
4
3
|
|
|
5
|
-
export const metadata = { id: 'booking/decline' }
|
|
4
|
+
export const metadata = { id: '@ossy/booking/tasks/decline' }
|
|
6
5
|
|
|
7
|
-
export async function run({ payload, req, log, integrations }) {
|
|
6
|
+
export async function run ({ payload, req, log, integrations }) {
|
|
8
7
|
const bookingId = payload?.bookingId ?? req?.params?.bookingId
|
|
9
8
|
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
10
9
|
const reason = payload?.reason ?? null
|
|
11
10
|
|
|
12
11
|
if (!bookingId) throw Object.assign(new Error('bookingId is required'), { status: 400 })
|
|
13
12
|
|
|
14
|
-
log?.info(`[booking/decline] Declining booking ${bookingId}`)
|
|
13
|
+
log?.info(`[booking/tasks/decline] Declining booking ${bookingId}`)
|
|
15
14
|
|
|
16
|
-
const booking = await
|
|
15
|
+
const booking = await viewResource(bookingId)
|
|
17
16
|
if (!booking?.id) throw Object.assign(new Error('Booking not found'), { status: 404 })
|
|
18
17
|
|
|
19
18
|
if (booking.belongsTo !== workspaceId) {
|
|
@@ -28,18 +27,16 @@ export async function run({ payload, req, log, integrations }) {
|
|
|
28
27
|
throw Object.assign(new Error('Use cancel-booking to cancel an already confirmed booking'), { status: 409 })
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
await
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
.then(Aggregate.Save())
|
|
30
|
+
await mutateResource(
|
|
31
|
+
bookingId,
|
|
32
|
+
ResourcesEvents.Patched({
|
|
33
|
+
createdBy: req?.userId ?? 'system',
|
|
34
|
+
content: { ...booking.content, status: 'cancelled', declineReason: reason ?? '' },
|
|
35
|
+
}),
|
|
36
|
+
)
|
|
39
37
|
|
|
40
|
-
log?.info(`[booking/decline] Booking ${bookingId} declined`)
|
|
38
|
+
log?.info(`[booking/tasks/decline] Booking ${bookingId} declined`)
|
|
41
39
|
|
|
42
|
-
// Send decline email to client
|
|
43
40
|
const emailClient = integrations?.get?.('email')
|
|
44
41
|
if (emailClient && booking.content?.clientEmail) {
|
|
45
42
|
try {
|
|
@@ -57,9 +54,9 @@ export async function run({ payload, req, log, integrations }) {
|
|
|
57
54
|
subject: 'Din bokningsförfrågan avböjdes',
|
|
58
55
|
},
|
|
59
56
|
)
|
|
60
|
-
log?.info(`[booking/decline] Decline email sent to ${booking.content.clientEmail}`)
|
|
57
|
+
log?.info(`[booking/tasks/decline] Decline email sent to ${booking.content.clientEmail}`)
|
|
61
58
|
} catch (err) {
|
|
62
|
-
log?.error('[booking/decline] Failed to send decline email to client', err)
|
|
59
|
+
log?.error('[booking/tasks/decline] Failed to send decline email to client', err)
|
|
63
60
|
}
|
|
64
61
|
}
|
|
65
62
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'booking/delete-service', access: 'workspace' }
|
|
1
|
+
export const metadata = { id: '@ossy/booking/actions/delete-service', access: 'workspace' }
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ResourcesEvents, mutateResource } from '@ossy/resources/server'
|
|
2
|
+
import { BookingSchema } from './schema-ids.js'
|
|
3
3
|
import { getBookingResources } from './booking-resources.js'
|
|
4
4
|
|
|
5
|
-
export const metadata = { id: 'booking/delete-service' }
|
|
5
|
+
export const metadata = { id: '@ossy/booking/tasks/delete-service' }
|
|
6
6
|
|
|
7
7
|
export async function run ({ payload, req, log }) {
|
|
8
8
|
const workspaceId = req?.workspaceId
|
|
@@ -16,7 +16,7 @@ export async function run ({ payload, req, log }) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const existing = await getBookingResources({
|
|
19
|
-
|
|
19
|
+
schemaId: BookingSchema.service,
|
|
20
20
|
belongsTo: workspaceId,
|
|
21
21
|
})
|
|
22
22
|
|
|
@@ -25,17 +25,14 @@ export async function run ({ payload, req, log }) {
|
|
|
25
25
|
throw Object.assign(new Error('Service not found'), { status: 404 })
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
log?.info(`[booking/delete-service] Deleting service ${serviceId}`)
|
|
29
|
-
|
|
30
|
-
await
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
),
|
|
37
|
-
)
|
|
38
|
-
.then(Aggregate.Save())
|
|
28
|
+
log?.info(`[booking/tasks/delete-service] Deleting service ${serviceId}`)
|
|
29
|
+
|
|
30
|
+
await mutateResource(
|
|
31
|
+
serviceId,
|
|
32
|
+
ResourcesEvents.Deleted({
|
|
33
|
+
createdBy: req?.userId ?? 'system',
|
|
34
|
+
}),
|
|
35
|
+
)
|
|
39
36
|
|
|
40
37
|
return { id: serviceId, deleted: true }
|
|
41
38
|
}
|
package/src/en.translations.json
CHANGED
|
@@ -16,6 +16,15 @@
|
|
|
16
16
|
"booking.home.features.doubleBook.text": "One calendar here, one link there — we keep track so the same slot isn't booked twice.",
|
|
17
17
|
"booking.sales.overview": "Overview",
|
|
18
18
|
"booking.sales.features": "Features",
|
|
19
|
+
"booking.sales.enable": "Enable booking",
|
|
20
|
+
"booking.sales.enableDescription": "Activate booking for your workspace to share a booking page with clients and manage appointments.",
|
|
21
|
+
"booking.sales.enabling": "Enabling…",
|
|
22
|
+
"booking.sales.enableError": "Could not enable booking. Try again or contact support.",
|
|
23
|
+
"booking.product.title": "Booking is active",
|
|
24
|
+
"booking.product.description": "Set your availability, add services, and start accepting bookings from clients.",
|
|
25
|
+
"booking.product.setupAvailability": "Set up availability",
|
|
26
|
+
"booking.product.manageServices": "Manage services",
|
|
27
|
+
"booking.product.viewBookings": "View bookings",
|
|
19
28
|
"booking/bookings.documentTitle": "Bookings",
|
|
20
29
|
"booking.bookings.titleSuffix": "Bookings",
|
|
21
30
|
"booking.bookings.description": "Manage booking requests and confirmed meetings.",
|
|
@@ -29,12 +38,15 @@
|
|
|
29
38
|
"booking.bookings.empty.all.heading": "No bookings",
|
|
30
39
|
"booking.bookings.empty.all.body": "Bookings from your clients appear here.",
|
|
31
40
|
"booking.bookings.loading": "Loading…",
|
|
41
|
+
"booking.bookings.errorLoad.title": "Could not load bookings",
|
|
32
42
|
"booking.bookings.errorStatus": "Error {status}",
|
|
33
43
|
"booking.bookings.errorConfirm": "Confirmation failed",
|
|
34
44
|
"booking.bookings.errorDecline": "Decline failed",
|
|
35
45
|
"booking.bookings.errorCancel": "Cancellation failed",
|
|
36
46
|
"booking/booking-detail.documentTitle": "Booking details",
|
|
37
47
|
"booking.bookingDetail.back": "← All bookings",
|
|
48
|
+
"booking.bookingDetail.pageTitle": "Booking",
|
|
49
|
+
"booking.bookingDetail.pageDescription": "View and manage this booking request.",
|
|
38
50
|
"booking.bookingDetail.loading": "Loading…",
|
|
39
51
|
"booking.bookingDetail.notFound": "Booking not found",
|
|
40
52
|
"booking.bookingDetail.errorStatus": "Error {status}",
|
|
@@ -146,17 +158,11 @@
|
|
|
146
158
|
"publicBooking.durationMin": "({minutes} min)",
|
|
147
159
|
"publicBooking.continue": "Continue →",
|
|
148
160
|
"publicBooking.pickDate": "Select a highlighted date in the calendar",
|
|
149
|
-
"publicBooking.form.name": "Your name",
|
|
150
|
-
"publicBooking.form.namePlaceholder": "Anna Svensson",
|
|
151
|
-
"publicBooking.form.email": "Email address",
|
|
152
|
-
"publicBooking.form.emailPlaceholder": "anna@example.com",
|
|
153
|
-
"publicBooking.form.message": "Message (optional)",
|
|
154
|
-
"publicBooking.form.messagePlaceholder": "Tell us what you'd like to discuss…",
|
|
155
161
|
"publicBooking.form.submit": "Send request",
|
|
156
162
|
"publicBooking.form.submitting": "Sending…",
|
|
157
163
|
"publicBooking.form.error": "Booking failed ({status})",
|
|
158
164
|
"publicBooking.pending.title": "Your request has been sent!",
|
|
159
|
-
"publicBooking.pending.body": "The
|
|
165
|
+
"publicBooking.pending.body": "The provider will confirm shortly. You will receive an email with a calendar invite once the booking is confirmed.",
|
|
160
166
|
"publicBooking.pending.requestedTime": "Requested time",
|
|
161
167
|
"publicBooking.pending.duration": "{minutes} minutes",
|
|
162
168
|
"publicBooking.month.january": "January",
|
|
@@ -194,30 +200,76 @@
|
|
|
194
200
|
"booking.card.cancel": "Cancel",
|
|
195
201
|
"booking.card.waiting": "Waiting…",
|
|
196
202
|
"booking.card.errorGeneric": "Something went wrong",
|
|
197
|
-
"booking/create.label": "Create booking",
|
|
198
|
-
"booking/create.description": "Create a new booking request from a client",
|
|
199
|
-
"booking/get-available-slots.label": "Get available slots",
|
|
200
|
-
"booking/get-available-slots.description": "List bookable time slots for a
|
|
201
|
-
"booking/get-availability.label": "Get availability",
|
|
202
|
-
"booking/get-availability.description": "Load weekly availability settings for the workspace",
|
|
203
|
-
"booking/save-availability.label": "Save availability",
|
|
204
|
-
"booking/save-availability.description": "Save weekly availability and booking preferences",
|
|
205
|
-
"booking/list.label": "List bookings",
|
|
206
|
-
"booking/list.description": "List all bookings for the current workspace",
|
|
207
|
-
"booking/decline.label": "Decline booking",
|
|
208
|
-
"booking/decline.description": "Decline a pending booking request",
|
|
209
|
-
"booking/confirm.label": "Confirm booking",
|
|
210
|
-
"booking/confirm.description": "Confirm a pending booking request",
|
|
211
|
-
"booking/cancel.label": "Cancel booking",
|
|
212
|
-
"booking/cancel.description": "Cancel a confirmed booking",
|
|
213
|
-
"booking/get-services.label": "Get services",
|
|
214
|
-
"booking/get-services.description": "List bookable services for a
|
|
215
|
-
"booking/create-service.label": "Create service",
|
|
216
|
-
"booking/create-service.description": "Create a new bookable service",
|
|
217
|
-
"booking/
|
|
218
|
-
"booking/
|
|
219
|
-
"booking/
|
|
220
|
-
"booking/
|
|
203
|
+
"@ossy/booking/actions/create.label": "Create booking",
|
|
204
|
+
"@ossy/booking/actions/create.description": "Create a new booking request from a client",
|
|
205
|
+
"@ossy/booking/actions/get-available-slots.label": "Get available slots",
|
|
206
|
+
"@ossy/booking/actions/get-available-slots.description": "List bookable time slots for a provider",
|
|
207
|
+
"@ossy/booking/actions/get-availability.label": "Get availability",
|
|
208
|
+
"@ossy/booking/actions/get-availability.description": "Load weekly availability settings for the workspace",
|
|
209
|
+
"@ossy/booking/actions/save-availability.label": "Save availability",
|
|
210
|
+
"@ossy/booking/actions/save-availability.description": "Save weekly availability and booking preferences",
|
|
211
|
+
"@ossy/booking/actions/list.label": "List bookings",
|
|
212
|
+
"@ossy/booking/actions/list.description": "List all bookings for the current workspace",
|
|
213
|
+
"@ossy/booking/actions/decline.label": "Decline booking",
|
|
214
|
+
"@ossy/booking/actions/decline.description": "Decline a pending booking request",
|
|
215
|
+
"@ossy/booking/actions/confirm.label": "Confirm booking",
|
|
216
|
+
"@ossy/booking/actions/confirm.description": "Confirm a pending booking request",
|
|
217
|
+
"@ossy/booking/actions/cancel.label": "Cancel booking",
|
|
218
|
+
"@ossy/booking/actions/cancel.description": "Cancel a confirmed booking",
|
|
219
|
+
"@ossy/booking/actions/get-services.label": "Get services",
|
|
220
|
+
"@ossy/booking/actions/get-services.description": "List bookable services for a provider or workspace",
|
|
221
|
+
"@ossy/booking/actions/create-service.label": "Create service",
|
|
222
|
+
"@ossy/booking/actions/create-service.description": "Create a new bookable service",
|
|
223
|
+
"@ossy/booking/form/service.label": "Service",
|
|
224
|
+
"@ossy/booking/form/service.description": "Create or edit a bookable service",
|
|
225
|
+
"@ossy/booking/schema/service.name.label": "Name",
|
|
226
|
+
"@ossy/booking/schema/service.duration.label": "Duration (minutes)",
|
|
227
|
+
"@ossy/booking/schema/service.price.label": "Price (öre/cents)",
|
|
228
|
+
"@ossy/booking/schema/service.currency.label": "Currency",
|
|
229
|
+
"@ossy/booking/schema/service.description.label": "Description",
|
|
230
|
+
"@ossy/booking/schema/service.active.label": "Active",
|
|
231
|
+
"@ossy/booking/schema/service.active.description": "Visible to clients on the public booking page",
|
|
232
|
+
"@ossy/booking/schema/public-booking-form.clientName.label": "Your name",
|
|
233
|
+
"@ossy/booking/schema/public-booking-form.clientName.placeholder": "Anna Svensson",
|
|
234
|
+
"@ossy/booking/schema/public-booking-form.clientEmail.label": "Email address",
|
|
235
|
+
"@ossy/booking/schema/public-booking-form.clientEmail.placeholder": "anna@example.com",
|
|
236
|
+
"@ossy/booking/schema/public-booking-form.clientMessage.label": "Message (optional)",
|
|
237
|
+
"@ossy/booking/schema/public-booking-form.clientMessage.placeholder": "Tell us what you'd like to discuss…",
|
|
238
|
+
"@ossy/booking/schema/public-booking-form.attachments.label": "Attachments (optional)",
|
|
239
|
+
"@ossy/booking/form/public-booking.label": "Booking details",
|
|
240
|
+
"@ossy/booking/form/public-booking.description": "Client details for a booking request",
|
|
241
|
+
"@ossy/booking/actions/update-service.label": "Update service",
|
|
242
|
+
"@ossy/booking/actions/update-service.description": "Update an existing bookable service",
|
|
243
|
+
"@ossy/booking/actions/delete-service.label": "Delete service",
|
|
244
|
+
"@ossy/booking/actions/delete-service.description": "Remove a bookable service",
|
|
245
|
+
"@ossy/booking/actions/open-availability-setup.label": "Set up availability",
|
|
246
|
+
"@ossy/booking/actions/open-availability-setup.description": "Open availability settings",
|
|
247
|
+
"@ossy/booking/actions/open-services.label": "Manage services",
|
|
248
|
+
"@ossy/booking/actions/open-services.description": "Open the services page",
|
|
249
|
+
"@ossy/booking/actions/open-bookings.label": "View bookings",
|
|
250
|
+
"@ossy/booking/actions/open-bookings.description": "Open the bookings list",
|
|
251
|
+
"@ossy/booking/actions/open-create-service.label": "Add service",
|
|
252
|
+
"@ossy/booking/actions/open-create-service.description": "Open the form to create a new service",
|
|
253
|
+
"@ossy/booking/actions/toggle-day-0.label": "Toggle Sunday",
|
|
254
|
+
"@ossy/booking/actions/toggle-day-0.description": "Enable or disable availability on Sunday",
|
|
255
|
+
"@ossy/booking/actions/toggle-day-1.label": "Toggle Monday",
|
|
256
|
+
"@ossy/booking/actions/toggle-day-1.description": "Enable or disable availability on Monday",
|
|
257
|
+
"@ossy/booking/actions/toggle-day-2.label": "Toggle Tuesday",
|
|
258
|
+
"@ossy/booking/actions/toggle-day-2.description": "Enable or disable availability on Tuesday",
|
|
259
|
+
"@ossy/booking/actions/toggle-day-3.label": "Toggle Wednesday",
|
|
260
|
+
"@ossy/booking/actions/toggle-day-3.description": "Enable or disable availability on Wednesday",
|
|
261
|
+
"@ossy/booking/actions/toggle-day-4.label": "Toggle Thursday",
|
|
262
|
+
"@ossy/booking/actions/toggle-day-4.description": "Enable or disable availability on Thursday",
|
|
263
|
+
"@ossy/booking/actions/toggle-day-5.label": "Toggle Friday",
|
|
264
|
+
"@ossy/booking/actions/toggle-day-5.description": "Enable or disable availability on Friday",
|
|
265
|
+
"@ossy/booking/actions/toggle-day-6.label": "Toggle Saturday",
|
|
266
|
+
"@ossy/booking/actions/toggle-day-6.description": "Enable or disable availability on Saturday",
|
|
267
|
+
"@ossy/booking/actions/select-duration-30.label": "30 min sessions",
|
|
268
|
+
"@ossy/booking/actions/select-duration-30.description": "Offer 30-minute session length",
|
|
269
|
+
"@ossy/booking/actions/select-duration-60.label": "60 min sessions",
|
|
270
|
+
"@ossy/booking/actions/select-duration-60.description": "Offer 60-minute session length",
|
|
271
|
+
"@ossy/booking/actions/select-duration-90.label": "90 min sessions",
|
|
272
|
+
"@ossy/booking/actions/select-duration-90.description": "Offer 90-minute session length",
|
|
221
273
|
"booking.services.loading": "Loading…",
|
|
222
274
|
"booking.services.empty": "No services yet. Add your first service to get started.",
|
|
223
275
|
"booking.services.errorLoad": "Could not load services ({status})",
|
|
@@ -231,12 +283,6 @@
|
|
|
231
283
|
"booking.services.cancel": "Cancel",
|
|
232
284
|
"booking.services.edit": "Edit",
|
|
233
285
|
"booking.services.delete": "Delete",
|
|
234
|
-
"booking.services.field.name": "Name",
|
|
235
|
-
"booking.services.field.duration": "Duration (minutes)",
|
|
236
|
-
"booking.services.field.price": "Price (öre/cents)",
|
|
237
|
-
"booking.services.field.currency": "Currency",
|
|
238
|
-
"booking.services.field.description": "Description",
|
|
239
|
-
"booking.services.field.active": "Active (visible to clients)",
|
|
240
286
|
"publicBooking.chooseService": "Choose a service",
|
|
241
287
|
"publicBooking.loadingServices": "Loading services…",
|
|
242
288
|
"publicBooking.servicesError": "Could not load services.",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'booking/get-availability', access: 'workspace' }
|
|
1
|
+
export const metadata = { id: '@ossy/booking/actions/get-availability', access: 'workspace' }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getBookingResources } from './booking-resources.js'
|
|
2
|
+
import { BookingSchema } from './schema-ids.js'
|
|
2
3
|
|
|
3
|
-
export const metadata = { id: 'booking/get-availability' }
|
|
4
|
+
export const metadata = { id: '@ossy/booking/tasks/get-availability' }
|
|
4
5
|
|
|
5
6
|
export async function run({ payload, req, log }) {
|
|
6
7
|
const workspaceId = req?.workspaceId
|
|
@@ -9,15 +10,15 @@ export async function run({ payload, req, log }) {
|
|
|
9
10
|
throw Object.assign(new Error('workspaceId is required'), { status: 400 })
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
log?.info(`[booking/get-availability] Loading availability for workspace ${workspaceId}`)
|
|
13
|
+
log?.info(`[booking/tasks/get-availability] Loading availability for workspace ${workspaceId}`)
|
|
13
14
|
|
|
14
15
|
const resources = await getBookingResources({
|
|
15
|
-
|
|
16
|
+
schemaId: BookingSchema.availability,
|
|
16
17
|
belongsTo: workspaceId,
|
|
17
18
|
})
|
|
18
19
|
|
|
19
20
|
if (!resources.length) {
|
|
20
|
-
log?.info('[booking/get-availability] No availability configured')
|
|
21
|
+
log?.info('[booking/tasks/get-availability] No availability configured')
|
|
21
22
|
return null
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'booking/get-available-slots', access: 'public' }
|
|
1
|
+
export const metadata = { id: '@ossy/booking/actions/get-available-slots', access: 'public' }
|
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
import { getAvailableSlots } from './availability-engine.js'
|
|
2
2
|
import { getBookingResources } from './booking-resources.js'
|
|
3
|
+
import { BookingSchema } from './schema-ids.js'
|
|
4
|
+
import { resolveWorkspaceId } from './resolve-workspace-contact.js'
|
|
3
5
|
|
|
4
|
-
export const metadata = { id: 'booking/get-available-slots' }
|
|
6
|
+
export const metadata = { id: '@ossy/booking/tasks/get-available-slots' }
|
|
5
7
|
|
|
6
8
|
const DEFAULT_DURATION = 60 // minutes
|
|
7
9
|
const DEFAULT_RANGE_DAYS = 30
|
|
8
10
|
|
|
9
11
|
export async function run ({ payload, log }) {
|
|
10
|
-
const {
|
|
12
|
+
const { duration, serviceId, from, to } = payload ?? {}
|
|
13
|
+
const workspaceId = await resolveWorkspaceId(payload)
|
|
11
14
|
|
|
12
|
-
if (!
|
|
13
|
-
throw Object.assign(new Error('
|
|
15
|
+
if (!workspaceId) {
|
|
16
|
+
throw Object.assign(new Error('providerSlug, workspaceId, or providerId is required'), { status: 400 })
|
|
14
17
|
}
|
|
15
18
|
|
|
16
|
-
log?.info(`[booking/get-available-slots] Loading slots for
|
|
19
|
+
log?.info(`[booking/tasks/get-available-slots] Loading slots for workspace ${workspaceId}`)
|
|
17
20
|
|
|
18
21
|
let slotDuration = duration
|
|
19
22
|
|
|
20
23
|
if (serviceId) {
|
|
21
24
|
const serviceResources = await getBookingResources({
|
|
22
|
-
|
|
23
|
-
belongsTo:
|
|
25
|
+
schemaId: BookingSchema.service,
|
|
26
|
+
belongsTo: workspaceId,
|
|
24
27
|
})
|
|
25
28
|
const service = serviceResources.find(r => r.id === serviceId)
|
|
26
29
|
if (service?.content?.duration) {
|
|
@@ -29,12 +32,12 @@ export async function run ({ payload, log }) {
|
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
const availabilityResources = await getBookingResources({
|
|
32
|
-
|
|
33
|
-
belongsTo:
|
|
35
|
+
schemaId: BookingSchema.availability,
|
|
36
|
+
belongsTo: workspaceId,
|
|
34
37
|
})
|
|
35
38
|
|
|
36
39
|
if (!availabilityResources.length) {
|
|
37
|
-
log?.info('[booking/get-available-slots] No availability resources found, returning []')
|
|
40
|
+
log?.info('[booking/tasks/get-available-slots] No availability resources found, returning []')
|
|
38
41
|
return []
|
|
39
42
|
}
|
|
40
43
|
|
|
@@ -68,8 +71,8 @@ export async function run ({ payload, log }) {
|
|
|
68
71
|
: new Date(Date.now() + DEFAULT_RANGE_DAYS * 24 * 60 * 60 * 1000)
|
|
69
72
|
|
|
70
73
|
const allBookingResources = await getBookingResources({
|
|
71
|
-
|
|
72
|
-
belongsTo:
|
|
74
|
+
schemaId: BookingSchema.booking,
|
|
75
|
+
belongsTo: workspaceId,
|
|
73
76
|
})
|
|
74
77
|
|
|
75
78
|
const confirmedBookings = allBookingResources
|
|
@@ -94,6 +97,6 @@ export async function run ({ payload, log }) {
|
|
|
94
97
|
duration: resolvedDuration,
|
|
95
98
|
})
|
|
96
99
|
|
|
97
|
-
log?.info(`[booking/get-available-slots] Returning ${slots.length} slots`)
|
|
100
|
+
log?.info(`[booking/tasks/get-available-slots] Returning ${slots.length} slots`)
|
|
98
101
|
return slots
|
|
99
102
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'booking/get-services', access: 'public' }
|
|
1
|
+
export const metadata = { id: '@ossy/booking/actions/get-services', access: 'public' }
|
package/src/get-services.task.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { getBookingResources } from './booking-resources.js'
|
|
2
|
+
import { BookingSchema } from './schema-ids.js'
|
|
3
|
+
import { resolveWorkspaceContact, resolveWorkspaceId } from './resolve-workspace-contact.js'
|
|
2
4
|
|
|
3
|
-
export const metadata = { id: 'booking/get-services' }
|
|
5
|
+
export const metadata = { id: '@ossy/booking/tasks/get-services' }
|
|
4
6
|
|
|
5
7
|
export async function run ({ payload, req, log }) {
|
|
6
|
-
const workspaceId = payload
|
|
8
|
+
const workspaceId = await resolveWorkspaceId(payload) ?? payload?.workspaceId ?? req?.workspaceId
|
|
7
9
|
|
|
8
10
|
if (!workspaceId) {
|
|
9
|
-
throw Object.assign(new Error('
|
|
11
|
+
throw Object.assign(new Error('providerSlug, workspaceId, or providerId is required'), { status: 400 })
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
log?.info(`[booking/get-services] Loading services for ${workspaceId}`)
|
|
14
|
+
log?.info(`[booking/tasks/get-services] Loading services for ${workspaceId}`)
|
|
13
15
|
|
|
14
16
|
const resources = await getBookingResources({
|
|
15
|
-
|
|
17
|
+
schemaId: BookingSchema.service,
|
|
16
18
|
belongsTo: workspaceId,
|
|
17
19
|
})
|
|
18
20
|
|
|
@@ -31,7 +33,12 @@ export async function run ({ payload, req, log }) {
|
|
|
31
33
|
.filter(s => includeInactive || s.active)
|
|
32
34
|
.sort((a, b) => (a.name || '').localeCompare(b.name || ''))
|
|
33
35
|
|
|
34
|
-
log?.info(`[booking/get-services] Returning ${services.length} service(s)`)
|
|
36
|
+
log?.info(`[booking/tasks/get-services] Returning ${services.length} service(s)`)
|
|
37
|
+
|
|
38
|
+
if (payload?.includeContact === true) {
|
|
39
|
+
const contact = await resolveWorkspaceContact(workspaceId)
|
|
40
|
+
return { services, contact }
|
|
41
|
+
}
|
|
35
42
|
|
|
36
43
|
return services
|
|
37
44
|
}
|
package/src/home.page.jsx
CHANGED
|
@@ -1,56 +1,24 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { useApp } from '@ossy/app/shell'
|
|
3
|
+
import { GetWorkspace } from '@ossy/workspaces'
|
|
4
|
+
import { useSdk } from '@ossy/sdk-react'
|
|
5
|
+
import { isServiceOn } from './workspace-services.js'
|
|
6
|
+
import BookingSalesPage from './BookingSalesPage.jsx'
|
|
7
|
+
import BookingProductHome from './BookingProductHome.jsx'
|
|
4
8
|
|
|
5
9
|
export const metadata = {
|
|
6
10
|
id: 'booking/home',
|
|
7
11
|
path: { sv: '/boka', en: '/booking' },
|
|
8
12
|
}
|
|
9
13
|
|
|
10
|
-
function
|
|
11
|
-
const
|
|
14
|
+
export default function BookingHomePage () {
|
|
15
|
+
const app = useApp()
|
|
16
|
+
const sdk = useSdk()
|
|
17
|
+
const { data: workspace } = sdk.read(GetWorkspace)
|
|
18
|
+
const entitled = isServiceOn(workspace?.services, 'booking')
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
text: t('booking.home.cover.text'),
|
|
16
|
-
actions: [
|
|
17
|
-
{ label: t('booking.home.cover.ctaPrimary'), variant: 'cta', href: '/sign-up' },
|
|
18
|
-
{ label: t('booking.home.cover.ctaSecondary'), variant: 'secondary', href: '/boka/demo' },
|
|
19
|
-
],
|
|
20
|
+
if (app?.isAuthenticated && entitled) {
|
|
21
|
+
return <BookingProductHome />
|
|
20
22
|
}
|
|
21
|
-
|
|
22
|
-
const features = [
|
|
23
|
-
{
|
|
24
|
-
title: t('booking.home.features.page.title'),
|
|
25
|
-
icon: 'share',
|
|
26
|
-
text: t('booking.home.features.page.text'),
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
title: t('booking.home.features.confirmations.title'),
|
|
30
|
-
icon: 'check-circle',
|
|
31
|
-
text: t('booking.home.features.confirmations.text'),
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
title: t('booking.home.features.availability.title'),
|
|
35
|
-
icon: 'calendar',
|
|
36
|
-
text: t('booking.home.features.availability.text'),
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
title: t('booking.home.features.email.title'),
|
|
40
|
-
icon: 'mail',
|
|
41
|
-
text: t('booking.home.features.email.text'),
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
title: t('booking.home.features.doubleBook.title'),
|
|
45
|
-
icon: 'warning',
|
|
46
|
-
text: t('booking.home.features.doubleBook.text'),
|
|
47
|
-
},
|
|
48
|
-
]
|
|
49
|
-
|
|
50
|
-
return { cover, features }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export default function BookingHomePage () {
|
|
54
|
-
const { cover, features } = useBookingHomeContent()
|
|
55
|
-
return <SalesSection cover={cover} features={features} />
|
|
23
|
+
return <BookingSalesPage isAuthenticated={!!app?.isAuthenticated} />
|
|
56
24
|
}
|
package/src/index.js
CHANGED
|
@@ -8,6 +8,8 @@ export {
|
|
|
8
8
|
} from './locations.js'
|
|
9
9
|
export { metadata as CreateBooking } from './create-booking.action.js'
|
|
10
10
|
export { metadata as CreateService } from './create-service.action.js'
|
|
11
|
+
export { metadata as ServiceForm } from './service.form.js'
|
|
12
|
+
export { metadata as PublicBookingForm } from './public-booking-form.form.js'
|
|
11
13
|
export { metadata as UpdateService } from './update-service.action.js'
|
|
12
14
|
export { metadata as DeleteService } from './delete-service.action.js'
|
|
13
15
|
export { metadata as SaveAvailability } from './save-availability.action.js'
|
|
@@ -19,9 +21,24 @@ export { metadata as GetAvailability } from './get-availability.action.js'
|
|
|
19
21
|
export { metadata as ListBookings } from './list-bookings.action.js'
|
|
20
22
|
export { metadata as GetServices } from './get-services.action.js'
|
|
21
23
|
export { invokeErrorMessage } from './invoke-error-message.js'
|
|
24
|
+
export { metadata as OpenAvailabilitySetup } from './open-availability-setup.action.js'
|
|
25
|
+
export { metadata as OpenServices } from './open-services.action.js'
|
|
26
|
+
export { metadata as OpenBookings } from './open-bookings.action.js'
|
|
27
|
+
export { metadata as OpenCreateService } from './open-create-service.action.js'
|
|
28
|
+
export {
|
|
29
|
+
ToggleAvailabilityDay1,
|
|
30
|
+
ToggleAvailabilityDay2,
|
|
31
|
+
ToggleAvailabilityDay3,
|
|
32
|
+
ToggleAvailabilityDay4,
|
|
33
|
+
ToggleAvailabilityDay5,
|
|
34
|
+
SelectDuration60,
|
|
35
|
+
TOGGLE_AVAILABILITY_DAY,
|
|
36
|
+
SELECT_DURATION,
|
|
37
|
+
} from './availability-action-maps.js'
|
|
22
38
|
|
|
23
39
|
export { ServiceCard } from './ServiceCard.jsx'
|
|
24
40
|
export { BookingList } from './BookingList.jsx'
|
|
25
41
|
export { AvailabilityEditor } from './AvailabilityEditor.jsx'
|
|
26
42
|
|
|
27
43
|
export { getAvailableSlots } from './availability-engine.js'
|
|
44
|
+
export { toMs, fromMs } from './time.js'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'booking/list', access: 'workspace' }
|
|
1
|
+
export const metadata = { id: '@ossy/booking/actions/list', access: 'workspace' }
|
|
@@ -1,35 +1,52 @@
|
|
|
1
|
+
import { getProjection } from '@ossy/event-store'
|
|
1
2
|
import { getBookingResources } from './booking-resources.js'
|
|
3
|
+
import { BookingListProjection } from './booking-list.aggregate.js'
|
|
4
|
+
import { BookingSchema } from './schema-ids.js'
|
|
5
|
+
import { toMs } from './time.js'
|
|
2
6
|
|
|
3
|
-
export const metadata = { id: 'booking/list' }
|
|
7
|
+
export const metadata = { id: '@ossy/booking/tasks/list' }
|
|
4
8
|
|
|
5
9
|
export async function run({ payload, req, log }) {
|
|
6
10
|
const workspaceId = req?.workspaceId
|
|
7
11
|
const status = payload?.status ?? null
|
|
8
12
|
|
|
9
|
-
log?.info(`[booking/list] Loading bookings for workspace ${workspaceId}`, { status })
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
log?.info(`[booking/tasks/list] Loading bookings for workspace ${workspaceId}`, { status })
|
|
14
|
+
|
|
15
|
+
let bookings = await loadFromProjection(workspaceId)
|
|
16
|
+
|
|
17
|
+
if (bookings == null) {
|
|
18
|
+
log?.info('[booking/tasks/list] Projection miss — scanning resources')
|
|
19
|
+
const resources = await getBookingResources({
|
|
20
|
+
schemaId: BookingSchema.booking,
|
|
21
|
+
belongsTo: workspaceId,
|
|
22
|
+
})
|
|
23
|
+
bookings = resources.map(r => ({
|
|
24
|
+
id: r.id,
|
|
25
|
+
...r.content,
|
|
26
|
+
createdAt: r.createdAt,
|
|
27
|
+
}))
|
|
28
|
+
}
|
|
21
29
|
|
|
22
30
|
if (status) {
|
|
23
31
|
bookings = bookings.filter(b => b.status === status)
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
bookings.sort((a, b) => {
|
|
27
|
-
const dateA =
|
|
28
|
-
const dateB =
|
|
35
|
+
const dateA = toMs(a.startsAt) ?? toMs(a.createdAt) ?? 0
|
|
36
|
+
const dateB = toMs(b.startsAt) ?? toMs(b.createdAt) ?? 0
|
|
29
37
|
return dateB - dateA
|
|
30
38
|
})
|
|
31
39
|
|
|
32
|
-
log?.info(`[booking/list] Found ${bookings.length} booking(s)`)
|
|
40
|
+
log?.info(`[booking/tasks/list] Found ${bookings.length} booking(s)`)
|
|
33
41
|
|
|
34
42
|
return bookings
|
|
35
43
|
}
|
|
44
|
+
|
|
45
|
+
async function loadFromProjection (workspaceId) {
|
|
46
|
+
if (!workspaceId) return []
|
|
47
|
+
|
|
48
|
+
const snapshot = await getProjection(workspaceId, BookingListProjection.ProjectionId)
|
|
49
|
+
if (!snapshot) return null
|
|
50
|
+
|
|
51
|
+
return (snapshot.bookings ?? []).map(b => ({ ...b }))
|
|
52
|
+
}
|
package/src/locations.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { BookingSchema } from './schema-ids.js'
|
|
2
|
+
|
|
1
3
|
/** Booking feature root. Sub-areas use the paths below. */
|
|
2
4
|
export const location = '/@ossy/booking/'
|
|
3
5
|
|
|
@@ -6,9 +8,9 @@ export const services = '/@ossy/booking/services/'
|
|
|
6
8
|
export const bookings = '/@ossy/booking/bookings/'
|
|
7
9
|
export const availability = '/@ossy/booking/availability/'
|
|
8
10
|
|
|
9
|
-
/** Map
|
|
11
|
+
/** Map schema id → canonical location (for create actions). */
|
|
10
12
|
export const locationByType = {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
[BookingSchema.service]: services,
|
|
14
|
+
[BookingSchema.booking]: bookings,
|
|
15
|
+
[BookingSchema.availability]: availability,
|
|
14
16
|
}
|