@mobilizehub/payload-plugin 0.1.0 → 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.
Files changed (77) hide show
  1. package/dist/adapters/index.d.ts +1 -0
  2. package/dist/adapters/index.js +3 -0
  3. package/dist/adapters/index.js.map +1 -0
  4. package/dist/adapters/resend-adapter.d.ts +34 -0
  5. package/dist/adapters/resend-adapter.js +219 -0
  6. package/dist/adapters/resend-adapter.js.map +1 -0
  7. package/dist/collections/broadcasts/generateBroadcastsCollection.d.ts +3 -0
  8. package/dist/collections/broadcasts/generateBroadcastsCollection.js +241 -0
  9. package/dist/collections/broadcasts/generateBroadcastsCollection.js.map +1 -0
  10. package/dist/collections/emails/generateEmailsCollection.d.ts +3 -0
  11. package/dist/collections/emails/generateEmailsCollection.js +204 -0
  12. package/dist/collections/emails/generateEmailsCollection.js.map +1 -0
  13. package/dist/collections/emails/hooks/sync-status-from-activity.d.ts +5 -0
  14. package/dist/collections/emails/hooks/sync-status-from-activity.js +64 -0
  15. package/dist/collections/emails/hooks/sync-status-from-activity.js.map +1 -0
  16. package/dist/collections/unsubscribe-tokens/generateUnsubscribeTokens.d.ts +2 -0
  17. package/dist/collections/unsubscribe-tokens/generateUnsubscribeTokens.js +48 -0
  18. package/dist/collections/unsubscribe-tokens/generateUnsubscribeTokens.js.map +1 -0
  19. package/dist/components/broadcast-metrics-card.d.ts +7 -0
  20. package/dist/components/broadcast-metrics-card.js +159 -0
  21. package/dist/components/broadcast-metrics-card.js.map +1 -0
  22. package/dist/components/broadcast-send-modal.d.ts +9 -0
  23. package/dist/components/broadcast-send-modal.js +51 -0
  24. package/dist/components/broadcast-send-modal.js.map +1 -0
  25. package/dist/components/broadcast-send-test-drawer.d.ts +7 -0
  26. package/dist/components/broadcast-send-test-drawer.js +154 -0
  27. package/dist/components/broadcast-send-test-drawer.js.map +1 -0
  28. package/dist/components/email-activity.d.ts +4 -0
  29. package/dist/components/email-activity.js +359 -0
  30. package/dist/components/email-activity.js.map +1 -0
  31. package/dist/components/email-preview.d.ts +2 -0
  32. package/dist/components/email-preview.js +95 -0
  33. package/dist/components/email-preview.js.map +1 -0
  34. package/dist/endpoints/sendBroadcastHandler.d.ts +9 -0
  35. package/dist/endpoints/sendBroadcastHandler.js +107 -0
  36. package/dist/endpoints/sendBroadcastHandler.js.map +1 -0
  37. package/dist/endpoints/sendTestBroadcastHandler.d.ts +10 -0
  38. package/dist/endpoints/sendTestBroadcastHandler.js +143 -0
  39. package/dist/endpoints/sendTestBroadcastHandler.js.map +1 -0
  40. package/dist/endpoints/unsubscribeHandler.d.ts +9 -0
  41. package/dist/endpoints/unsubscribeHandler.js +153 -0
  42. package/dist/endpoints/unsubscribeHandler.js.map +1 -0
  43. package/dist/exports/client.d.ts +3 -1
  44. package/dist/exports/client.js +3 -0
  45. package/dist/exports/client.js.map +1 -1
  46. package/dist/exports/rsc.d.ts +2 -1
  47. package/dist/exports/rsc.js +2 -0
  48. package/dist/exports/rsc.js.map +1 -1
  49. package/dist/index.js +48 -3
  50. package/dist/index.js.map +1 -1
  51. package/dist/react/index.d.ts +1 -0
  52. package/dist/react/index.js +3 -0
  53. package/dist/react/index.js.map +1 -0
  54. package/dist/react/unsubscribe.d.ts +6 -0
  55. package/dist/react/unsubscribe.js +16 -0
  56. package/dist/react/unsubscribe.js.map +1 -0
  57. package/dist/tasks/sendBroadcastsTask.d.ts +11 -0
  58. package/dist/tasks/sendBroadcastsTask.js +196 -0
  59. package/dist/tasks/sendBroadcastsTask.js.map +1 -0
  60. package/dist/tasks/sendEmailTask.d.ts +9 -0
  61. package/dist/tasks/sendEmailTask.js +167 -0
  62. package/dist/tasks/sendEmailTask.js.map +1 -0
  63. package/dist/types/index.d.ts +124 -1
  64. package/dist/types/index.js.map +1 -1
  65. package/dist/utils/api-response.d.ts +72 -0
  66. package/dist/utils/api-response.js +66 -0
  67. package/dist/utils/api-response.js.map +1 -0
  68. package/dist/utils/email.d.ts +36 -0
  69. package/dist/utils/email.js +40 -0
  70. package/dist/utils/email.js.map +1 -0
  71. package/dist/utils/lexical.d.ts +13 -0
  72. package/dist/utils/lexical.js +27 -0
  73. package/dist/utils/lexical.js.map +1 -0
  74. package/dist/utils/unsubscribe-token.d.ts +67 -0
  75. package/dist/utils/unsubscribe-token.js +103 -0
  76. package/dist/utils/unsubscribe-token.js.map +1 -0
  77. package/package.json +20 -9
@@ -1,12 +1,135 @@
1
- import type { CollectionConfig, Field } from 'payload';
1
+ import type { BasePayload, CollectionConfig, Field, PayloadRequest } from 'payload';
2
2
  export type FieldsOverride = (args: {
3
3
  defaultFields: Field[];
4
4
  }) => Field[];
5
5
  export type CollectionOverride = {
6
6
  fields?: FieldsOverride;
7
7
  } & Partial<Omit<CollectionConfig, 'fields'>>;
8
+ /**
9
+ * Contact type
10
+ */
11
+ export type Contact = {
12
+ createdAt?: string;
13
+ email?: string;
14
+ emailOptIn: boolean;
15
+ firstName?: string;
16
+ id: number | string;
17
+ lastName?: string;
18
+ tags?: {
19
+ createdAt?: string;
20
+ id: number | string;
21
+ name?: string;
22
+ updatedAt?: string;
23
+ }[];
24
+ updatedAt?: string;
25
+ };
26
+ /**
27
+ * Unsubscribe token input structure
28
+ */
29
+ export interface UnsubscribeTokenInput {
30
+ timestamp: number;
31
+ tokenId: string;
32
+ }
33
+ /**
34
+ * Unsubscribe token record structure
35
+ */
36
+ export type UnsubscribeTokenRecord = {
37
+ emailId?: number | string;
38
+ expiresAt?: string;
39
+ id: string;
40
+ };
41
+ /**
42
+ * Email activity types
43
+ */
44
+ export type EmailStatus = 'bounced' | 'complained' | 'delivered' | 'failed' | 'queued' | 'sent' | 'unsubscribed';
45
+ /**
46
+ * Email activity types from providers
47
+ */
48
+ export type EmailActivityType = 'bounced' | 'clicked' | 'complained' | 'delivered' | 'delivery_delayed' | 'failed' | 'opened' | 'received' | 'sent';
49
+ /**
50
+ * Email message structure
51
+ */
52
+ export type EmailMessage = {
53
+ from: string;
54
+ html: string;
55
+ idempotencyKey?: string;
56
+ markdown?: string;
57
+ plainText?: string;
58
+ previewText?: string;
59
+ replyTo?: string;
60
+ subject: string;
61
+ to: string;
62
+ token?: string;
63
+ };
64
+ /**
65
+ * Result of a webhook call
66
+ */
67
+ export type WebhookResult = {
68
+ body?: unknown;
69
+ status: number;
70
+ };
71
+ /**
72
+ * Email adapter interface for sending emails
73
+ */
74
+ export type EmailAdapter = ({ payload }: {
75
+ payload: BasePayload;
76
+ }) => {
77
+ defaultFromAddress: string;
78
+ defaultFromName: string;
79
+ name: string;
80
+ render: (args: EmailMessage) => string;
81
+ sendEmail: (args: EmailMessage) => Promise<{
82
+ providerId: string;
83
+ } | void>;
84
+ webhookHandler?: (req: PayloadRequest) => Promise<void | WebhookResult>;
85
+ };
8
86
  export type MobilizehubPluginConfig = {
87
+ broadcastConfig?: {
88
+ /**
89
+ * Batch size for processing contacts in the broadcasts task.
90
+ * Higher values process faster but use more memory.
91
+ * @default 100
92
+ */
93
+ batchSize?: number;
94
+ /**
95
+ * Optional custom queue name for the broadcasts task
96
+ * @default 'send-broadcasts'
97
+ */
98
+ broadcastQueueName?: string;
99
+ /**
100
+ * Optional custom queue name for the email sending task
101
+ * @default 'send-email'
102
+ */
103
+ emailQueueName?: string;
104
+ /**
105
+ * Cron schedule for the broadcasts task
106
+ * On schedule the task will run to process and send pending broadcasts
107
+ * @default '5 * * * *' (every 5 minutes)
108
+ */
109
+ taskSchedule?: string;
110
+ };
111
+ /**
112
+ * Overrides for the broadcasts collection
113
+ */
114
+ broadcastsOverrides?: CollectionOverride;
115
+ /**
116
+ * Overrides for the contacts collection
117
+ */
9
118
  contactsOverrides?: CollectionOverride;
119
+ /**
120
+ * Disable the plugin
121
+ */
10
122
  disabled?: boolean;
123
+ /**
124
+ * Email adapter for sending emails
125
+ */
126
+ email: EmailAdapter;
127
+ /**
128
+ * Overrides for the emails collection
129
+ */
130
+ emailsOverrides?: CollectionOverride;
131
+ /**
132
+ * Overrides for the tags collection
133
+ */
11
134
  tagsOverrides?: CollectionOverride;
12
135
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/types/index.ts"],"sourcesContent":["import type { CollectionConfig, Field } from 'payload'\n\nexport type FieldsOverride = (args: { defaultFields: Field[] }) => Field[]\n\nexport type CollectionOverride = { fields?: FieldsOverride } & Partial<\n Omit<CollectionConfig, 'fields'>\n>\nexport type MobilizehubPluginConfig = {\n contactsOverrides?: CollectionOverride\n disabled?: boolean\n tagsOverrides?: CollectionOverride\n}\n"],"names":[],"mappings":"AAOA,WAIC"}
1
+ {"version":3,"sources":["../../src/types/index.ts"],"sourcesContent":["import type { BasePayload, CollectionConfig, Field, PayloadRequest } from 'payload'\n\nexport type FieldsOverride = (args: { defaultFields: Field[] }) => Field[]\n\nexport type CollectionOverride = { fields?: FieldsOverride } & Partial<\n Omit<CollectionConfig, 'fields'>\n>\n\n/**\n * Contact type\n */\nexport type Contact = {\n createdAt?: string\n email?: string\n emailOptIn: boolean\n firstName?: string\n id: number | string\n lastName?: string\n tags?: {\n createdAt?: string\n id: number | string\n name?: string\n updatedAt?: string\n }[]\n updatedAt?: string\n}\n\n/**\n * Unsubscribe token input structure\n */\nexport interface UnsubscribeTokenInput {\n timestamp: number\n tokenId: string\n}\n\n/**\n * Unsubscribe token record structure\n */\nexport type UnsubscribeTokenRecord = {\n emailId?: number | string\n expiresAt?: string\n id: string\n}\n\n/**\n * Email activity types\n */\nexport type EmailStatus =\n | 'bounced'\n | 'complained'\n | 'delivered'\n | 'failed'\n | 'queued'\n | 'sent'\n | 'unsubscribed'\n\n/**\n * Email activity types from providers\n */\nexport type EmailActivityType =\n | 'bounced'\n | 'clicked'\n | 'complained'\n | 'delivered'\n | 'delivery_delayed'\n | 'failed'\n | 'opened'\n | 'received'\n | 'sent'\n\n/**\n * Email message structure\n */\nexport type EmailMessage = {\n from: string\n html: string\n idempotencyKey?: string\n markdown?: string\n plainText?: string\n previewText?: string\n replyTo?: string\n subject: string\n to: string\n token?: string\n}\n\n/**\n * Result of a webhook call\n */\nexport type WebhookResult = {\n body?: unknown\n status: number\n}\n\n/**\n * Email adapter interface for sending emails\n */\nexport type EmailAdapter = ({ payload }: { payload: BasePayload }) => {\n defaultFromAddress: string\n defaultFromName: string\n name: string\n render: (args: EmailMessage) => string\n sendEmail: (args: EmailMessage) => Promise<{ providerId: string } | void>\n webhookHandler?: (req: PayloadRequest) => Promise<void | WebhookResult>\n}\n\nexport type MobilizehubPluginConfig = {\n broadcastConfig?: {\n /**\n * Batch size for processing contacts in the broadcasts task.\n * Higher values process faster but use more memory.\n * @default 100\n */\n batchSize?: number\n /**\n * Optional custom queue name for the broadcasts task\n * @default 'send-broadcasts'\n */\n broadcastQueueName?: string\n /**\n * Optional custom queue name for the email sending task\n * @default 'send-email'\n */\n emailQueueName?: string\n /**\n * Cron schedule for the broadcasts task\n * On schedule the task will run to process and send pending broadcasts\n * @default '5 * * * *' (every 5 minutes)\n */\n taskSchedule?: string\n }\n /**\n * Overrides for the broadcasts collection\n */\n broadcastsOverrides?: CollectionOverride\n\n /**\n * Overrides for the contacts collection\n */\n contactsOverrides?: CollectionOverride\n /**\n * Disable the plugin\n */\n disabled?: boolean\n /**\n * Email adapter for sending emails\n */\n email: EmailAdapter\n /**\n * Overrides for the emails collection\n */\n emailsOverrides?: CollectionOverride\n /**\n * Overrides for the tags collection\n */\n tagsOverrides?: CollectionOverride\n}\n"],"names":[],"mappings":"AA0GA,WAkDC"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Standard API success response format
3
+ * @template T - Type of the data payload
4
+ */
5
+ export type ApiSuccessResponse<T = unknown> = {
6
+ data?: T;
7
+ success: true;
8
+ };
9
+ /**
10
+ * Standard API error response format
11
+ */
12
+ export type ApiErrorResponse = {
13
+ error: {
14
+ /** Error code constant (e.g., 'BAD_REQUEST', 'UNAUTHORIZED') */
15
+ code: string;
16
+ /** Human-readable error message */
17
+ message: string;
18
+ };
19
+ success: false;
20
+ };
21
+ /**
22
+ * Union type for all API responses
23
+ * @template T - Type of the data payload for success responses
24
+ */
25
+ export type ApiResponse<T = unknown> = ApiErrorResponse | ApiSuccessResponse<T>;
26
+ /**
27
+ * Create a standardized success response
28
+ *
29
+ * @template T - Type of the response data
30
+ * @param data - Optional data payload to include in the response
31
+ * @param status - HTTP status code (default: 200)
32
+ * @returns Response object with JSON body
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * return successResponse({ message: 'Email sent successfully' }, 200)
37
+ * // Returns: { success: true, data: { message: 'Email sent successfully' } }
38
+ * ```
39
+ */
40
+ export declare function successResponse<T>(data?: T, status?: number): Response;
41
+ /**
42
+ * Create a standardized error response
43
+ *
44
+ * @param code - Error code from ErrorCodes constant
45
+ * @param message - Human-readable error message
46
+ * @param status - HTTP status code (default: 400)
47
+ * @returns Response object with JSON error body
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * return errorResponse(ErrorCodes.UNAUTHORIZED, 'You must be logged in', 401)
52
+ * // Returns: { success: false, error: { code: 'UNAUTHORIZED', message: '...' } }
53
+ * ```
54
+ */
55
+ export declare function errorResponse(code: string, message: string, status?: number): Response;
56
+ /**
57
+ * Standard error codes used across the MobilizeHub plugin API
58
+ */
59
+ export declare const ErrorCodes: {
60
+ readonly BAD_REQUEST: "BAD_REQUEST";
61
+ readonly BROADCAST_INVALID_STATUS: "BROADCAST_INVALID_STATUS";
62
+ readonly BROADCAST_NOT_FOUND: "BROADCAST_NOT_FOUND";
63
+ readonly CONTACT_NOT_FOUND: "CONTACT_NOT_FOUND";
64
+ readonly EMAIL_SEND_FAILED: "EMAIL_SEND_FAILED";
65
+ readonly INTERNAL_ERROR: "INTERNAL_ERROR";
66
+ readonly NOT_FOUND: "NOT_FOUND";
67
+ readonly RATE_LIMITED: "RATE_LIMITED";
68
+ readonly TOKEN_EXPIRED: "TOKEN_EXPIRED";
69
+ readonly TOKEN_INVALID: "TOKEN_INVALID";
70
+ readonly UNAUTHORIZED: "UNAUTHORIZED";
71
+ readonly VALIDATION_ERROR: "VALIDATION_ERROR";
72
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Standard API success response format
3
+ * @template T - Type of the data payload
4
+ */ /**
5
+ * Create a standardized success response
6
+ *
7
+ * @template T - Type of the response data
8
+ * @param data - Optional data payload to include in the response
9
+ * @param status - HTTP status code (default: 200)
10
+ * @returns Response object with JSON body
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * return successResponse({ message: 'Email sent successfully' }, 200)
15
+ * // Returns: { success: true, data: { message: 'Email sent successfully' } }
16
+ * ```
17
+ */ export function successResponse(data, status = 200) {
18
+ return Response.json({
19
+ data,
20
+ success: true
21
+ }, {
22
+ status
23
+ });
24
+ }
25
+ /**
26
+ * Create a standardized error response
27
+ *
28
+ * @param code - Error code from ErrorCodes constant
29
+ * @param message - Human-readable error message
30
+ * @param status - HTTP status code (default: 400)
31
+ * @returns Response object with JSON error body
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * return errorResponse(ErrorCodes.UNAUTHORIZED, 'You must be logged in', 401)
36
+ * // Returns: { success: false, error: { code: 'UNAUTHORIZED', message: '...' } }
37
+ * ```
38
+ */ export function errorResponse(code, message, status = 400) {
39
+ return Response.json({
40
+ error: {
41
+ code,
42
+ message
43
+ },
44
+ success: false
45
+ }, {
46
+ status
47
+ });
48
+ }
49
+ /**
50
+ * Standard error codes used across the MobilizeHub plugin API
51
+ */ export const ErrorCodes = {
52
+ BAD_REQUEST: 'BAD_REQUEST',
53
+ BROADCAST_INVALID_STATUS: 'BROADCAST_INVALID_STATUS',
54
+ BROADCAST_NOT_FOUND: 'BROADCAST_NOT_FOUND',
55
+ CONTACT_NOT_FOUND: 'CONTACT_NOT_FOUND',
56
+ EMAIL_SEND_FAILED: 'EMAIL_SEND_FAILED',
57
+ INTERNAL_ERROR: 'INTERNAL_ERROR',
58
+ NOT_FOUND: 'NOT_FOUND',
59
+ RATE_LIMITED: 'RATE_LIMITED',
60
+ TOKEN_EXPIRED: 'TOKEN_EXPIRED',
61
+ TOKEN_INVALID: 'TOKEN_INVALID',
62
+ UNAUTHORIZED: 'UNAUTHORIZED',
63
+ VALIDATION_ERROR: 'VALIDATION_ERROR'
64
+ };
65
+
66
+ //# sourceMappingURL=api-response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/api-response.ts"],"sourcesContent":["/**\n * Standard API success response format\n * @template T - Type of the data payload\n */\nexport type ApiSuccessResponse<T = unknown> = {\n data?: T\n success: true\n}\n\n/**\n * Standard API error response format\n */\nexport type ApiErrorResponse = {\n error: {\n /** Error code constant (e.g., 'BAD_REQUEST', 'UNAUTHORIZED') */\n code: string\n /** Human-readable error message */\n message: string\n }\n success: false\n}\n\n/**\n * Union type for all API responses\n * @template T - Type of the data payload for success responses\n */\nexport type ApiResponse<T = unknown> = ApiErrorResponse | ApiSuccessResponse<T>\n\n/**\n * Create a standardized success response\n *\n * @template T - Type of the response data\n * @param data - Optional data payload to include in the response\n * @param status - HTTP status code (default: 200)\n * @returns Response object with JSON body\n *\n * @example\n * ```typescript\n * return successResponse({ message: 'Email sent successfully' }, 200)\n * // Returns: { success: true, data: { message: 'Email sent successfully' } }\n * ```\n */\nexport function successResponse<T>(data?: T, status = 200): Response {\n return Response.json({ data, success: true }, { status })\n}\n\n/**\n * Create a standardized error response\n *\n * @param code - Error code from ErrorCodes constant\n * @param message - Human-readable error message\n * @param status - HTTP status code (default: 400)\n * @returns Response object with JSON error body\n *\n * @example\n * ```typescript\n * return errorResponse(ErrorCodes.UNAUTHORIZED, 'You must be logged in', 401)\n * // Returns: { success: false, error: { code: 'UNAUTHORIZED', message: '...' } }\n * ```\n */\nexport function errorResponse(code: string, message: string, status = 400): Response {\n return Response.json({ error: { code, message }, success: false }, { status })\n}\n\n/**\n * Standard error codes used across the MobilizeHub plugin API\n */\nexport const ErrorCodes = {\n BAD_REQUEST: 'BAD_REQUEST',\n BROADCAST_INVALID_STATUS: 'BROADCAST_INVALID_STATUS',\n BROADCAST_NOT_FOUND: 'BROADCAST_NOT_FOUND',\n CONTACT_NOT_FOUND: 'CONTACT_NOT_FOUND',\n EMAIL_SEND_FAILED: 'EMAIL_SEND_FAILED',\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n NOT_FOUND: 'NOT_FOUND',\n RATE_LIMITED: 'RATE_LIMITED',\n TOKEN_EXPIRED: 'TOKEN_EXPIRED',\n TOKEN_INVALID: 'TOKEN_INVALID',\n UNAUTHORIZED: 'UNAUTHORIZED',\n VALIDATION_ERROR: 'VALIDATION_ERROR',\n} as const\n"],"names":["successResponse","data","status","Response","json","success","errorResponse","code","message","error","ErrorCodes","BAD_REQUEST","BROADCAST_INVALID_STATUS","BROADCAST_NOT_FOUND","CONTACT_NOT_FOUND","EMAIL_SEND_FAILED","INTERNAL_ERROR","NOT_FOUND","RATE_LIMITED","TOKEN_EXPIRED","TOKEN_INVALID","UNAUTHORIZED","VALIDATION_ERROR"],"mappings":"AAAA;;;CAGC,GAyBD;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASA,gBAAmBC,IAAQ,EAAEC,SAAS,GAAG;IACvD,OAAOC,SAASC,IAAI,CAAC;QAAEH;QAAMI,SAAS;IAAK,GAAG;QAAEH;IAAO;AACzD;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASI,cAAcC,IAAY,EAAEC,OAAe,EAAEN,SAAS,GAAG;IACvE,OAAOC,SAASC,IAAI,CAAC;QAAEK,OAAO;YAAEF;YAAMC;QAAQ;QAAGH,SAAS;IAAM,GAAG;QAAEH;IAAO;AAC9E;AAEA;;CAEC,GACD,OAAO,MAAMQ,aAAa;IACxBC,aAAa;IACbC,0BAA0B;IAC1BC,qBAAqB;IACrBC,mBAAmB;IACnBC,mBAAmB;IACnBC,gBAAgB;IAChBC,WAAW;IACXC,cAAc;IACdC,eAAe;IACfC,eAAe;IACfC,cAAc;IACdC,kBAAkB;AACpB,EAAU"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Formats an email address with an optional display name according to RFC 5322.
3
+ *
4
+ * @param fromName - The display name to show (e.g., "John Doe")
5
+ * @param fromAddress - The actual email address (e.g., "john@example.com")
6
+ * @returns Formatted email string. If fromName is provided: `"John Doe" <john@example.com>`
7
+ * Otherwise just returns the email address.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * formatFromAddress("Acme Corp", "hello@acme.com")
12
+ * // Returns: '"Acme Corp" <hello@acme.com>'
13
+ *
14
+ * formatFromAddress("", "hello@acme.com")
15
+ * // Returns: 'hello@acme.com'
16
+ * ```
17
+ */
18
+ export declare function formatFromAddress(fromName: string, fromAddress: string): string;
19
+ /**
20
+ * Validates if a string is a properly formatted email address.
21
+ *
22
+ * Uses a permissive regex that catches most invalid formats while allowing
23
+ * valid edge cases like plus-addressing (user+tag@domain.com).
24
+ *
25
+ * @param email - The string to validate
26
+ * @returns `true` if the string appears to be a valid email format
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * isValidEmail("user@example.com") // true
31
+ * isValidEmail("user+tag@example.com") // true
32
+ * isValidEmail("invalid") // false
33
+ * isValidEmail("missing@domain") // false
34
+ * ```
35
+ */
36
+ export declare function isValidEmail(email: string): boolean;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Formats an email address with an optional display name according to RFC 5322.
3
+ *
4
+ * @param fromName - The display name to show (e.g., "John Doe")
5
+ * @param fromAddress - The actual email address (e.g., "john@example.com")
6
+ * @returns Formatted email string. If fromName is provided: `"John Doe" <john@example.com>`
7
+ * Otherwise just returns the email address.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * formatFromAddress("Acme Corp", "hello@acme.com")
12
+ * // Returns: '"Acme Corp" <hello@acme.com>'
13
+ *
14
+ * formatFromAddress("", "hello@acme.com")
15
+ * // Returns: 'hello@acme.com'
16
+ * ```
17
+ */ export function formatFromAddress(fromName, fromAddress) {
18
+ return fromName ? `"${fromName}" <${fromAddress}>` : fromAddress;
19
+ }
20
+ /**
21
+ * Validates if a string is a properly formatted email address.
22
+ *
23
+ * Uses a permissive regex that catches most invalid formats while allowing
24
+ * valid edge cases like plus-addressing (user+tag@domain.com).
25
+ *
26
+ * @param email - The string to validate
27
+ * @returns `true` if the string appears to be a valid email format
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * isValidEmail("user@example.com") // true
32
+ * isValidEmail("user+tag@example.com") // true
33
+ * isValidEmail("invalid") // false
34
+ * isValidEmail("missing@domain") // false
35
+ * ```
36
+ */ export function isValidEmail(email) {
37
+ return /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/.test(email);
38
+ }
39
+
40
+ //# sourceMappingURL=email.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/email.ts"],"sourcesContent":["/**\n * Formats an email address with an optional display name according to RFC 5322.\n *\n * @param fromName - The display name to show (e.g., \"John Doe\")\n * @param fromAddress - The actual email address (e.g., \"john@example.com\")\n * @returns Formatted email string. If fromName is provided: `\"John Doe\" <john@example.com>`\n * Otherwise just returns the email address.\n *\n * @example\n * ```typescript\n * formatFromAddress(\"Acme Corp\", \"hello@acme.com\")\n * // Returns: '\"Acme Corp\" <hello@acme.com>'\n *\n * formatFromAddress(\"\", \"hello@acme.com\")\n * // Returns: 'hello@acme.com'\n * ```\n */\nexport function formatFromAddress(fromName: string, fromAddress: string): string {\n return fromName ? `\"${fromName}\" <${fromAddress}>` : fromAddress\n}\n\n/**\n * Validates if a string is a properly formatted email address.\n *\n * Uses a permissive regex that catches most invalid formats while allowing\n * valid edge cases like plus-addressing (user+tag@domain.com).\n *\n * @param email - The string to validate\n * @returns `true` if the string appears to be a valid email format\n *\n * @example\n * ```typescript\n * isValidEmail(\"user@example.com\") // true\n * isValidEmail(\"user+tag@example.com\") // true\n * isValidEmail(\"invalid\") // false\n * isValidEmail(\"missing@domain\") // false\n * ```\n */\nexport function isValidEmail(email: string): boolean {\n return /^[^\\s@]+@[^\\s@][^\\s.@]*\\.[^\\s@]+$/.test(email)\n}\n"],"names":["formatFromAddress","fromName","fromAddress","isValidEmail","email","test"],"mappings":"AAAA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASA,kBAAkBC,QAAgB,EAAEC,WAAmB;IACrE,OAAOD,WAAW,CAAC,CAAC,EAAEA,SAAS,GAAG,EAAEC,YAAY,CAAC,CAAC,GAAGA;AACvD;AAEA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASC,aAAaC,KAAa;IACxC,OAAO,oCAAoCC,IAAI,CAACD;AAClD"}
@@ -0,0 +1,13 @@
1
+ import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical';
2
+ import type { Payload } from 'payload';
3
+ /**
4
+ * Parses Lexical editor content into multiple formats (HTML, Markdown, Plain Text)
5
+ * @param content - The serialized Lexical editor state
6
+ * @param payloadConfig - The Payload CMS configuration
7
+ * @returns Parsed content in HTML, Markdown, and Plain Text formats
8
+ */
9
+ export declare function parseLexicalContent(content: SerializedEditorState, payloadConfig: Payload['config']): Promise<{
10
+ html: string;
11
+ markdown: string;
12
+ plainText: string;
13
+ }>;
@@ -0,0 +1,27 @@
1
+ import { convertLexicalToMarkdown, editorConfigFactory } from '@payloadcms/richtext-lexical';
2
+ import { convertLexicalToHTML } from '@payloadcms/richtext-lexical/html';
3
+ import { convertLexicalToPlaintext } from '@payloadcms/richtext-lexical/plaintext';
4
+ /**
5
+ * Parses Lexical editor content into multiple formats (HTML, Markdown, Plain Text)
6
+ * @param content - The serialized Lexical editor state
7
+ * @param payloadConfig - The Payload CMS configuration
8
+ * @returns Parsed content in HTML, Markdown, and Plain Text formats
9
+ */ export async function parseLexicalContent(content, payloadConfig) {
10
+ const editorConfig = await editorConfigFactory.default({
11
+ config: payloadConfig
12
+ });
13
+ return {
14
+ html: convertLexicalToHTML({
15
+ data: content
16
+ }),
17
+ markdown: convertLexicalToMarkdown({
18
+ data: content,
19
+ editorConfig
20
+ }),
21
+ plainText: convertLexicalToPlaintext({
22
+ data: content
23
+ })
24
+ };
25
+ }
26
+
27
+ //# sourceMappingURL=lexical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/lexical.ts"],"sourcesContent":["import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'\nimport type { Payload } from 'payload'\n\nimport { convertLexicalToMarkdown, editorConfigFactory } from '@payloadcms/richtext-lexical'\nimport { convertLexicalToHTML } from '@payloadcms/richtext-lexical/html'\nimport { convertLexicalToPlaintext } from '@payloadcms/richtext-lexical/plaintext'\n\n/**\n * Parses Lexical editor content into multiple formats (HTML, Markdown, Plain Text)\n * @param content - The serialized Lexical editor state\n * @param payloadConfig - The Payload CMS configuration\n * @returns Parsed content in HTML, Markdown, and Plain Text formats\n */\nexport async function parseLexicalContent(\n content: SerializedEditorState,\n payloadConfig: Payload['config'],\n): Promise<{\n html: string\n markdown: string\n plainText: string\n}> {\n const editorConfig = await editorConfigFactory.default({ config: payloadConfig })\n\n return {\n html: convertLexicalToHTML({ data: content }),\n markdown: convertLexicalToMarkdown({ data: content, editorConfig }),\n plainText: convertLexicalToPlaintext({ data: content }),\n }\n}\n"],"names":["convertLexicalToMarkdown","editorConfigFactory","convertLexicalToHTML","convertLexicalToPlaintext","parseLexicalContent","content","payloadConfig","editorConfig","default","config","html","data","markdown","plainText"],"mappings":"AAGA,SAASA,wBAAwB,EAAEC,mBAAmB,QAAQ,+BAA8B;AAC5F,SAASC,oBAAoB,QAAQ,oCAAmC;AACxE,SAASC,yBAAyB,QAAQ,yCAAwC;AAElF;;;;;CAKC,GACD,OAAO,eAAeC,oBACpBC,OAA8B,EAC9BC,aAAgC;IAMhC,MAAMC,eAAe,MAAMN,oBAAoBO,OAAO,CAAC;QAAEC,QAAQH;IAAc;IAE/E,OAAO;QACLI,MAAMR,qBAAqB;YAAES,MAAMN;QAAQ;QAC3CO,UAAUZ,yBAAyB;YAAEW,MAAMN;YAASE;QAAa;QACjEM,WAAWV,0BAA0B;YAAEQ,MAAMN;QAAQ;IACvD;AACF"}
@@ -0,0 +1,67 @@
1
+ type UnsubscribeToken = {
2
+ timestamp: number;
3
+ tokenId: string;
4
+ };
5
+ /**
6
+ * Creates a secure unsubscribe token for the given token ID.
7
+ *
8
+ * ## Token Format
9
+ * The token consists of two parts separated by a dot:
10
+ * - Base64URL-encoded JSON payload containing tokenId and timestamp
11
+ * - HMAC-SHA256 signature of the payload
12
+ *
13
+ * ## Expiration
14
+ * Tokens expire after 30 days from creation
15
+ *
16
+ * ## Security Considerations
17
+ * - Tokens are signed with HMAC-SHA256 using PAYLOAD_SECRET
18
+ * - Verification uses timing-safe comparison to prevent timing attacks
19
+ * - Tokens cannot be forged without knowing PAYLOAD_SECRET
20
+ *
21
+ * @param args - Object containing tokenId
22
+ * @param args.tokenId - Unique identifier for the unsubscribe token
23
+ * @returns Base64URL-encoded token string in format: `{payload}.{signature}`
24
+ * @throws Error if PAYLOAD_SECRET environment variable is not defined
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const token = generateUnsubscribeToken({ tokenId: 'uuid-1234' })
29
+ * // Returns: 'eyJ0b2tlbklkIjoidXVpZC0xMjM0IiwidGltZXN0YW1wIjoxNzAwMDAwMDAwfQ.abc123...'
30
+ * ```
31
+ */
32
+ export declare function generateUnsubscribeToken(args: {
33
+ tokenId: string;
34
+ }): string;
35
+ /**
36
+ * Verifies an unsubscribe token and returns the decoded payload if valid.
37
+ *
38
+ * ## Verification Process
39
+ * 1. Splits token into payload and signature
40
+ * 2. Verifies HMAC signature using timing-safe comparison
41
+ * 3. Decodes and parses the JSON payload
42
+ * 4. Checks if token has expired (30 day limit)
43
+ *
44
+ * ## Security
45
+ * - Uses `crypto.timingSafeEqual()` to prevent timing attacks
46
+ * - Returns null for any invalid or expired token
47
+ * - Catches and handles all errors gracefully
48
+ *
49
+ * @param args - Object containing the token to verify
50
+ * @param args.token - The unsubscribe token to verify
51
+ * @returns Decoded token input with tokenId and timestamp if valid, null otherwise
52
+ * @throws Error if PAYLOAD_SECRET environment variable is not defined
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * const result = verifyUnsubscribeToken({ token: 'eyJ0b2...' })
57
+ * if (result) {
58
+ * console.log(`Token ID: ${result.tokenId}, Created: ${new Date(result.timestamp)}`)
59
+ * } else {
60
+ * console.log('Invalid or expired token')
61
+ * }
62
+ * ```
63
+ */
64
+ export declare function verifyUnsubscribeToken(args: {
65
+ token: string;
66
+ }): null | UnsubscribeToken;
67
+ export {};
@@ -0,0 +1,103 @@
1
+ import crypto from 'crypto';
2
+ const UNSUBSCRIBE_TOKEN_EXPIRATION = 30 * 24 * 60 * 60 * 1000 // 30 days in milliseconds
3
+ ;
4
+ const HMAC_ALGORITHM = 'sha256';
5
+ const TOKEN_ENCODING = 'base64url';
6
+ /**
7
+ * Creates a secure unsubscribe token for the given token ID.
8
+ *
9
+ * ## Token Format
10
+ * The token consists of two parts separated by a dot:
11
+ * - Base64URL-encoded JSON payload containing tokenId and timestamp
12
+ * - HMAC-SHA256 signature of the payload
13
+ *
14
+ * ## Expiration
15
+ * Tokens expire after 30 days from creation
16
+ *
17
+ * ## Security Considerations
18
+ * - Tokens are signed with HMAC-SHA256 using PAYLOAD_SECRET
19
+ * - Verification uses timing-safe comparison to prevent timing attacks
20
+ * - Tokens cannot be forged without knowing PAYLOAD_SECRET
21
+ *
22
+ * @param args - Object containing tokenId
23
+ * @param args.tokenId - Unique identifier for the unsubscribe token
24
+ * @returns Base64URL-encoded token string in format: `{payload}.{signature}`
25
+ * @throws Error if PAYLOAD_SECRET environment variable is not defined
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const token = generateUnsubscribeToken({ tokenId: 'uuid-1234' })
30
+ * // Returns: 'eyJ0b2tlbklkIjoidXVpZC0xMjM0IiwidGltZXN0YW1wIjoxNzAwMDAwMDAwfQ.abc123...'
31
+ * ```
32
+ */ export function generateUnsubscribeToken(args) {
33
+ const secret = process.env.PAYLOAD_SECRET;
34
+ if (!secret) {
35
+ throw new Error('PAYLOAD_SECRET environment variable is not defined');
36
+ }
37
+ const input = {
38
+ timestamp: Date.now(),
39
+ tokenId: args.tokenId
40
+ };
41
+ const inputBase64 = Buffer.from(JSON.stringify(input)).toString(TOKEN_ENCODING);
42
+ const hmac = crypto.createHmac(HMAC_ALGORITHM, secret);
43
+ hmac.update(inputBase64);
44
+ const signature = hmac.digest(TOKEN_ENCODING);
45
+ return `${inputBase64}.${signature}`;
46
+ }
47
+ /**
48
+ * Verifies an unsubscribe token and returns the decoded payload if valid.
49
+ *
50
+ * ## Verification Process
51
+ * 1. Splits token into payload and signature
52
+ * 2. Verifies HMAC signature using timing-safe comparison
53
+ * 3. Decodes and parses the JSON payload
54
+ * 4. Checks if token has expired (30 day limit)
55
+ *
56
+ * ## Security
57
+ * - Uses `crypto.timingSafeEqual()` to prevent timing attacks
58
+ * - Returns null for any invalid or expired token
59
+ * - Catches and handles all errors gracefully
60
+ *
61
+ * @param args - Object containing the token to verify
62
+ * @param args.token - The unsubscribe token to verify
63
+ * @returns Decoded token input with tokenId and timestamp if valid, null otherwise
64
+ * @throws Error if PAYLOAD_SECRET environment variable is not defined
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * const result = verifyUnsubscribeToken({ token: 'eyJ0b2...' })
69
+ * if (result) {
70
+ * console.log(`Token ID: ${result.tokenId}, Created: ${new Date(result.timestamp)}`)
71
+ * } else {
72
+ * console.log('Invalid or expired token')
73
+ * }
74
+ * ```
75
+ */ export function verifyUnsubscribeToken(args) {
76
+ try {
77
+ const secret = process.env.PAYLOAD_SECRET;
78
+ if (!secret) {
79
+ throw new Error('PAYLOAD_SECRET environment variable is not defined');
80
+ }
81
+ const parts = args.token.split('.');
82
+ if (parts.length !== 2) {
83
+ return null;
84
+ }
85
+ const [inputBase64, signature] = parts;
86
+ const hmac = crypto.createHmac(HMAC_ALGORITHM, secret);
87
+ hmac.update(inputBase64);
88
+ const expectedSignature = hmac.digest(TOKEN_ENCODING);
89
+ if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
90
+ return null;
91
+ }
92
+ const inputJson = Buffer.from(inputBase64, TOKEN_ENCODING).toString('utf-8');
93
+ const input = JSON.parse(inputJson);
94
+ if (Date.now() - input.timestamp > UNSUBSCRIBE_TOKEN_EXPIRATION) {
95
+ return null;
96
+ }
97
+ return input;
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+
103
+ //# sourceMappingURL=unsubscribe-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/unsubscribe-token.ts"],"sourcesContent":["import crypto from 'crypto'\n\nconst UNSUBSCRIBE_TOKEN_EXPIRATION = 30 * 24 * 60 * 60 * 1000 // 30 days in milliseconds\n\nconst HMAC_ALGORITHM = 'sha256'\n\nconst TOKEN_ENCODING = 'base64url'\n\ntype UnsubscribeToken = {\n timestamp: number\n tokenId: string\n}\n\n/**\n * Creates a secure unsubscribe token for the given token ID.\n *\n * ## Token Format\n * The token consists of two parts separated by a dot:\n * - Base64URL-encoded JSON payload containing tokenId and timestamp\n * - HMAC-SHA256 signature of the payload\n *\n * ## Expiration\n * Tokens expire after 30 days from creation\n *\n * ## Security Considerations\n * - Tokens are signed with HMAC-SHA256 using PAYLOAD_SECRET\n * - Verification uses timing-safe comparison to prevent timing attacks\n * - Tokens cannot be forged without knowing PAYLOAD_SECRET\n *\n * @param args - Object containing tokenId\n * @param args.tokenId - Unique identifier for the unsubscribe token\n * @returns Base64URL-encoded token string in format: `{payload}.{signature}`\n * @throws Error if PAYLOAD_SECRET environment variable is not defined\n *\n * @example\n * ```typescript\n * const token = generateUnsubscribeToken({ tokenId: 'uuid-1234' })\n * // Returns: 'eyJ0b2tlbklkIjoidXVpZC0xMjM0IiwidGltZXN0YW1wIjoxNzAwMDAwMDAwfQ.abc123...'\n * ```\n */\nexport function generateUnsubscribeToken(args: { tokenId: string }): string {\n const secret = process.env.PAYLOAD_SECRET\n if (!secret) {\n throw new Error('PAYLOAD_SECRET environment variable is not defined')\n }\n\n const input: UnsubscribeToken = {\n timestamp: Date.now(),\n tokenId: args.tokenId,\n }\n\n const inputBase64 = Buffer.from(JSON.stringify(input)).toString(TOKEN_ENCODING)\n\n const hmac = crypto.createHmac(HMAC_ALGORITHM, secret)\n hmac.update(inputBase64)\n const signature = hmac.digest(TOKEN_ENCODING)\n\n return `${inputBase64}.${signature}`\n}\n\n/**\n * Verifies an unsubscribe token and returns the decoded payload if valid.\n *\n * ## Verification Process\n * 1. Splits token into payload and signature\n * 2. Verifies HMAC signature using timing-safe comparison\n * 3. Decodes and parses the JSON payload\n * 4. Checks if token has expired (30 day limit)\n *\n * ## Security\n * - Uses `crypto.timingSafeEqual()` to prevent timing attacks\n * - Returns null for any invalid or expired token\n * - Catches and handles all errors gracefully\n *\n * @param args - Object containing the token to verify\n * @param args.token - The unsubscribe token to verify\n * @returns Decoded token input with tokenId and timestamp if valid, null otherwise\n * @throws Error if PAYLOAD_SECRET environment variable is not defined\n *\n * @example\n * ```typescript\n * const result = verifyUnsubscribeToken({ token: 'eyJ0b2...' })\n * if (result) {\n * console.log(`Token ID: ${result.tokenId}, Created: ${new Date(result.timestamp)}`)\n * } else {\n * console.log('Invalid or expired token')\n * }\n * ```\n */\nexport function verifyUnsubscribeToken(args: { token: string }): null | UnsubscribeToken {\n try {\n const secret = process.env.PAYLOAD_SECRET\n if (!secret) {\n throw new Error('PAYLOAD_SECRET environment variable is not defined')\n }\n\n const parts = args.token.split('.')\n if (parts.length !== 2) {\n return null\n }\n\n const [inputBase64, signature] = parts\n\n const hmac = crypto.createHmac(HMAC_ALGORITHM, secret)\n hmac.update(inputBase64)\n const expectedSignature = hmac.digest(TOKEN_ENCODING)\n\n if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {\n return null\n }\n\n const inputJson = Buffer.from(inputBase64, TOKEN_ENCODING).toString('utf-8')\n const input: UnsubscribeToken = JSON.parse(inputJson)\n\n if (Date.now() - input.timestamp > UNSUBSCRIBE_TOKEN_EXPIRATION) {\n return null\n }\n\n return input\n } catch {\n return null\n }\n}\n"],"names":["crypto","UNSUBSCRIBE_TOKEN_EXPIRATION","HMAC_ALGORITHM","TOKEN_ENCODING","generateUnsubscribeToken","args","secret","process","env","PAYLOAD_SECRET","Error","input","timestamp","Date","now","tokenId","inputBase64","Buffer","from","JSON","stringify","toString","hmac","createHmac","update","signature","digest","verifyUnsubscribeToken","parts","token","split","length","expectedSignature","timingSafeEqual","inputJson","parse"],"mappings":"AAAA,OAAOA,YAAY,SAAQ;AAE3B,MAAMC,+BAA+B,KAAK,KAAK,KAAK,KAAK,KAAK,0BAA0B;;AAExF,MAAMC,iBAAiB;AAEvB,MAAMC,iBAAiB;AAOvB;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BC,GACD,OAAO,SAASC,yBAAyBC,IAAyB;IAChE,MAAMC,SAASC,QAAQC,GAAG,CAACC,cAAc;IACzC,IAAI,CAACH,QAAQ;QACX,MAAM,IAAII,MAAM;IAClB;IAEA,MAAMC,QAA0B;QAC9BC,WAAWC,KAAKC,GAAG;QACnBC,SAASV,KAAKU,OAAO;IACvB;IAEA,MAAMC,cAAcC,OAAOC,IAAI,CAACC,KAAKC,SAAS,CAACT,QAAQU,QAAQ,CAAClB;IAEhE,MAAMmB,OAAOtB,OAAOuB,UAAU,CAACrB,gBAAgBI;IAC/CgB,KAAKE,MAAM,CAACR;IACZ,MAAMS,YAAYH,KAAKI,MAAM,CAACvB;IAE9B,OAAO,GAAGa,YAAY,CAAC,EAAES,WAAW;AACtC;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BC,GACD,OAAO,SAASE,uBAAuBtB,IAAuB;IAC5D,IAAI;QACF,MAAMC,SAASC,QAAQC,GAAG,CAACC,cAAc;QACzC,IAAI,CAACH,QAAQ;YACX,MAAM,IAAII,MAAM;QAClB;QAEA,MAAMkB,QAAQvB,KAAKwB,KAAK,CAACC,KAAK,CAAC;QAC/B,IAAIF,MAAMG,MAAM,KAAK,GAAG;YACtB,OAAO;QACT;QAEA,MAAM,CAACf,aAAaS,UAAU,GAAGG;QAEjC,MAAMN,OAAOtB,OAAOuB,UAAU,CAACrB,gBAAgBI;QAC/CgB,KAAKE,MAAM,CAACR;QACZ,MAAMgB,oBAAoBV,KAAKI,MAAM,CAACvB;QAEtC,IAAI,CAACH,OAAOiC,eAAe,CAAChB,OAAOC,IAAI,CAACO,YAAYR,OAAOC,IAAI,CAACc,qBAAqB;YACnF,OAAO;QACT;QAEA,MAAME,YAAYjB,OAAOC,IAAI,CAACF,aAAab,gBAAgBkB,QAAQ,CAAC;QACpE,MAAMV,QAA0BQ,KAAKgB,KAAK,CAACD;QAE3C,IAAIrB,KAAKC,GAAG,KAAKH,MAAMC,SAAS,GAAGX,8BAA8B;YAC/D,OAAO;QACT;QAEA,OAAOU;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF"}