@voyantjs/customer-portal 0.2.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.
@@ -0,0 +1,277 @@
1
+ import { z } from "zod";
2
+ const bookingStatusSchema = z.enum([
3
+ "draft",
4
+ "on_hold",
5
+ "confirmed",
6
+ "in_progress",
7
+ "completed",
8
+ "expired",
9
+ "cancelled",
10
+ ]);
11
+ const bookingParticipantTypeSchema = z.enum([
12
+ "traveler",
13
+ "booker",
14
+ "contact",
15
+ "occupant",
16
+ "staff",
17
+ "other",
18
+ ]);
19
+ const bookingItemTypeSchema = z.enum([
20
+ "unit",
21
+ "extra",
22
+ "service",
23
+ "fee",
24
+ "tax",
25
+ "discount",
26
+ "adjustment",
27
+ "accommodation",
28
+ "transport",
29
+ "other",
30
+ ]);
31
+ const bookingItemStatusSchema = z.enum([
32
+ "draft",
33
+ "on_hold",
34
+ "confirmed",
35
+ "cancelled",
36
+ "expired",
37
+ "fulfilled",
38
+ ]);
39
+ const bookingItemParticipantRoleSchema = z.enum([
40
+ "traveler",
41
+ "occupant",
42
+ "primary_contact",
43
+ "service_assignee",
44
+ "beneficiary",
45
+ "other",
46
+ ]);
47
+ const bookingDocumentTypeSchema = z.enum(["visa", "insurance", "health", "passport_copy", "other"]);
48
+ const bookingFulfillmentTypeSchema = z.enum([
49
+ "voucher",
50
+ "ticket",
51
+ "pdf",
52
+ "qr_code",
53
+ "barcode",
54
+ "mobile",
55
+ "other",
56
+ ]);
57
+ const bookingFulfillmentDeliveryChannelSchema = z.enum([
58
+ "download",
59
+ "email",
60
+ "api",
61
+ "wallet",
62
+ "other",
63
+ ]);
64
+ const bookingFulfillmentStatusSchema = z.enum([
65
+ "pending",
66
+ "issued",
67
+ "reissued",
68
+ "revoked",
69
+ "failed",
70
+ ]);
71
+ const seatingPreferenceSchema = z.enum(["aisle", "window", "middle", "no_preference"]);
72
+ export const customerPortalRecordSchema = z.object({
73
+ id: z.string(),
74
+ firstName: z.string(),
75
+ lastName: z.string(),
76
+ preferredLanguage: z.string().nullable(),
77
+ preferredCurrency: z.string().nullable(),
78
+ birthday: z.string().nullable(),
79
+ email: z.string().nullable(),
80
+ phone: z.string().nullable(),
81
+ address: z.string().nullable(),
82
+ city: z.string().nullable(),
83
+ country: z.string().nullable(),
84
+ relation: z.string().nullable(),
85
+ status: z.string(),
86
+ });
87
+ export const customerPortalBootstrapCandidateSchema = customerPortalRecordSchema.extend({
88
+ linkable: z.boolean(),
89
+ claimedByAnotherUser: z.boolean(),
90
+ });
91
+ export const customerPortalProfileSchema = z.object({
92
+ userId: z.string(),
93
+ email: z.string().email(),
94
+ emailVerified: z.boolean(),
95
+ firstName: z.string().nullable(),
96
+ lastName: z.string().nullable(),
97
+ avatarUrl: z.string().nullable(),
98
+ locale: z.string(),
99
+ timezone: z.string().nullable(),
100
+ seatingPreference: seatingPreferenceSchema.nullable(),
101
+ marketingConsent: z.boolean(),
102
+ marketingConsentAt: z.string().nullable(),
103
+ notificationDefaults: z.record(z.string(), z.unknown()).nullable(),
104
+ uiPrefs: z.record(z.string(), z.unknown()).nullable(),
105
+ customerRecord: customerPortalRecordSchema.nullable(),
106
+ });
107
+ export const updateCustomerPortalRecordSchema = z.object({
108
+ preferredLanguage: z.string().max(35).nullable().optional(),
109
+ preferredCurrency: z.string().min(3).max(3).nullable().optional(),
110
+ birthday: z.string().date().nullable().optional(),
111
+ phone: z.string().max(50).nullable().optional(),
112
+ address: z.string().nullable().optional(),
113
+ city: z.string().nullable().optional(),
114
+ country: z.string().nullable().optional(),
115
+ });
116
+ export const updateCustomerPortalProfileSchema = z
117
+ .object({
118
+ firstName: z.string().max(200).nullable().optional(),
119
+ lastName: z.string().max(200).nullable().optional(),
120
+ avatarUrl: z.string().url().nullable().optional(),
121
+ locale: z.string().max(10).optional(),
122
+ timezone: z.string().max(64).nullable().optional(),
123
+ seatingPreference: seatingPreferenceSchema.nullable().optional(),
124
+ marketingConsent: z.boolean().optional(),
125
+ notificationDefaults: z.record(z.string(), z.unknown()).nullable().optional(),
126
+ uiPrefs: z.record(z.string(), z.unknown()).nullable().optional(),
127
+ customerRecord: updateCustomerPortalRecordSchema.optional(),
128
+ })
129
+ .refine((value) => Object.keys(value).length > 0, {
130
+ message: "At least one field must be provided",
131
+ });
132
+ export const customerPortalContactExistsQuerySchema = z.object({
133
+ email: z.string().email(),
134
+ });
135
+ export const customerPortalContactExistsResultSchema = z.object({
136
+ email: z.string().email(),
137
+ authAccountExists: z.boolean(),
138
+ customerRecordExists: z.boolean(),
139
+ linkedCustomerRecordExists: z.boolean(),
140
+ });
141
+ export const bootstrapCustomerPortalSchema = z
142
+ .object({
143
+ customerRecordId: z.string().optional(),
144
+ createCustomerIfMissing: z.boolean().default(true),
145
+ firstName: z.string().max(200).nullable().optional(),
146
+ lastName: z.string().max(200).nullable().optional(),
147
+ marketingConsent: z.boolean().optional(),
148
+ customerRecord: updateCustomerPortalRecordSchema.optional(),
149
+ })
150
+ .refine((value) => value.customerRecordId || value.createCustomerIfMissing !== false, {
151
+ message: "Provide a customerRecordId or allow customer creation",
152
+ });
153
+ export const bootstrapCustomerPortalResultSchema = z.object({
154
+ status: z.enum([
155
+ "already_linked",
156
+ "linked_existing_customer",
157
+ "created_customer",
158
+ "customer_selection_required",
159
+ ]),
160
+ profile: customerPortalProfileSchema.nullable(),
161
+ candidates: z.array(customerPortalBootstrapCandidateSchema).default([]),
162
+ });
163
+ export const customerPortalCompanionSchema = z.object({
164
+ id: z.string(),
165
+ role: z.string(),
166
+ name: z.string(),
167
+ title: z.string().nullable(),
168
+ email: z.string().nullable(),
169
+ phone: z.string().nullable(),
170
+ isPrimary: z.boolean(),
171
+ notes: z.string().nullable(),
172
+ metadata: z.record(z.string(), z.unknown()).nullable(),
173
+ });
174
+ export const createCustomerPortalCompanionSchema = z.object({
175
+ role: z
176
+ .enum([
177
+ "general",
178
+ "primary",
179
+ "reservations",
180
+ "operations",
181
+ "front_desk",
182
+ "sales",
183
+ "emergency",
184
+ "accounting",
185
+ "legal",
186
+ "other",
187
+ ])
188
+ .default("other"),
189
+ name: z.string().min(1).max(255),
190
+ title: z.string().max(255).nullable().optional(),
191
+ email: z.string().email().nullable().optional(),
192
+ phone: z.string().max(50).nullable().optional(),
193
+ isPrimary: z.boolean().default(false),
194
+ notes: z.string().nullable().optional(),
195
+ metadata: z.record(z.string(), z.unknown()).nullable().optional(),
196
+ });
197
+ export const updateCustomerPortalCompanionSchema = createCustomerPortalCompanionSchema
198
+ .partial()
199
+ .refine((value) => Object.keys(value).length > 0, {
200
+ message: "At least one field must be provided",
201
+ });
202
+ export const customerPortalBookingSummarySchema = z.object({
203
+ bookingId: z.string(),
204
+ bookingNumber: z.string(),
205
+ status: bookingStatusSchema,
206
+ sellCurrency: z.string(),
207
+ sellAmountCents: z.number().int().nullable(),
208
+ startDate: z.string().nullable(),
209
+ endDate: z.string().nullable(),
210
+ pax: z.number().int().nullable(),
211
+ confirmedAt: z.string().nullable(),
212
+ completedAt: z.string().nullable(),
213
+ participantCount: z.number().int(),
214
+ primaryTravelerName: z.string().nullable(),
215
+ });
216
+ export const customerPortalBookingItemParticipantSchema = z.object({
217
+ id: z.string(),
218
+ participantId: z.string(),
219
+ role: bookingItemParticipantRoleSchema,
220
+ isPrimary: z.boolean(),
221
+ });
222
+ export const customerPortalBookingItemSchema = z.object({
223
+ id: z.string(),
224
+ title: z.string(),
225
+ description: z.string().nullable(),
226
+ itemType: bookingItemTypeSchema,
227
+ status: bookingItemStatusSchema,
228
+ serviceDate: z.string().nullable(),
229
+ startsAt: z.string().nullable(),
230
+ endsAt: z.string().nullable(),
231
+ quantity: z.number().int(),
232
+ sellCurrency: z.string(),
233
+ unitSellAmountCents: z.number().int().nullable(),
234
+ totalSellAmountCents: z.number().int().nullable(),
235
+ notes: z.string().nullable(),
236
+ participantLinks: z.array(customerPortalBookingItemParticipantSchema),
237
+ });
238
+ export const customerPortalBookingParticipantSchema = z.object({
239
+ id: z.string(),
240
+ participantType: bookingParticipantTypeSchema,
241
+ firstName: z.string(),
242
+ lastName: z.string(),
243
+ isPrimary: z.boolean(),
244
+ });
245
+ export const customerPortalBookingDocumentSchema = z.object({
246
+ id: z.string(),
247
+ participantId: z.string().nullable(),
248
+ type: bookingDocumentTypeSchema,
249
+ fileName: z.string(),
250
+ fileUrl: z.string(),
251
+ });
252
+ export const customerPortalBookingFulfillmentSchema = z.object({
253
+ id: z.string(),
254
+ bookingItemId: z.string().nullable(),
255
+ participantId: z.string().nullable(),
256
+ fulfillmentType: bookingFulfillmentTypeSchema,
257
+ deliveryChannel: bookingFulfillmentDeliveryChannelSchema,
258
+ status: bookingFulfillmentStatusSchema,
259
+ artifactUrl: z.string().nullable(),
260
+ });
261
+ export const customerPortalBookingDetailSchema = z.object({
262
+ bookingId: z.string(),
263
+ bookingNumber: z.string(),
264
+ status: bookingStatusSchema,
265
+ sellCurrency: z.string(),
266
+ sellAmountCents: z.number().int().nullable(),
267
+ startDate: z.string().nullable(),
268
+ endDate: z.string().nullable(),
269
+ pax: z.number().int().nullable(),
270
+ confirmedAt: z.string().nullable(),
271
+ cancelledAt: z.string().nullable(),
272
+ completedAt: z.string().nullable(),
273
+ participants: z.array(customerPortalBookingParticipantSchema),
274
+ items: z.array(customerPortalBookingItemSchema),
275
+ documents: z.array(customerPortalBookingDocumentSchema),
276
+ fulfillments: z.array(customerPortalBookingFulfillmentSchema),
277
+ });
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@voyantjs/customer-portal",
3
+ "version": "0.2.0",
4
+ "license": "FSL-1.1-Apache-2.0",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/voyantjs/voyant.git",
8
+ "directory": "packages/customer-portal"
9
+ },
10
+ "type": "module",
11
+ "exports": {
12
+ ".": "./src/index.ts",
13
+ "./routes": "./src/routes.ts",
14
+ "./public-routes": "./src/routes-public.ts",
15
+ "./public-validation": "./src/validation-public.ts"
16
+ },
17
+ "scripts": {
18
+ "build": "tsc -p tsconfig.json",
19
+ "clean": "rm -rf dist",
20
+ "prepack": "pnpm run build",
21
+ "typecheck": "tsc --noEmit",
22
+ "lint": "biome check src/",
23
+ "test": "vitest run"
24
+ },
25
+ "dependencies": {
26
+ "@voyantjs/bookings": "workspace:*",
27
+ "@voyantjs/core": "workspace:*",
28
+ "@voyantjs/crm": "workspace:*",
29
+ "@voyantjs/db": "workspace:*",
30
+ "@voyantjs/hono": "workspace:*",
31
+ "@voyantjs/identity": "workspace:*",
32
+ "drizzle-orm": "^0.45.2",
33
+ "hono": "^4.12.10",
34
+ "zod": "^4.3.6"
35
+ },
36
+ "devDependencies": {
37
+ "@voyantjs/voyant-typescript-config": "workspace:*",
38
+ "typescript": "^6.0.2",
39
+ "vitest": "^4.1.2"
40
+ },
41
+ "files": [
42
+ "dist"
43
+ ],
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "exports": {
47
+ ".": {
48
+ "types": "./dist/index.d.ts",
49
+ "import": "./dist/index.js"
50
+ },
51
+ "./routes": {
52
+ "types": "./dist/routes.d.ts",
53
+ "import": "./dist/routes.js"
54
+ },
55
+ "./public-routes": {
56
+ "types": "./dist/routes-public.d.ts",
57
+ "import": "./dist/routes-public.js"
58
+ },
59
+ "./public-validation": {
60
+ "types": "./dist/validation-public.d.ts",
61
+ "import": "./dist/validation-public.js"
62
+ }
63
+ },
64
+ "main": "./dist/index.js",
65
+ "types": "./dist/index.d.ts"
66
+ }
67
+ }