@cimplify/sdk 0.88.0 → 0.90.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 (59) hide show
  1. package/dist/advanced.d.mts +3 -3
  2. package/dist/advanced.d.ts +3 -3
  3. package/dist/{chunk-A7NODD4Q.mjs → chunk-2UDEWILL.mjs} +83 -17
  4. package/dist/{chunk-W5BVGAO6.js → chunk-BN4V4ONE.js} +83 -17
  5. package/dist/{chunk-6JA4HBBD.js → chunk-JVWFIFQ7.js} +4 -4
  6. package/dist/{chunk-ROBTTS3Q.js → chunk-KJP4U6L3.js} +28 -1
  7. package/dist/{chunk-VU5LFNEX.mjs → chunk-NGE37REW.mjs} +2 -2
  8. package/dist/{chunk-APBLFZ2I.mjs → chunk-NNGKC3MM.mjs} +28 -1
  9. package/dist/chunk-U4MW6PYU.mjs +23 -0
  10. package/dist/chunk-XREIZ56K.js +26 -0
  11. package/dist/{client-D0PrO1-Q.d.ts → client-CR1f94wY.d.ts} +97 -7
  12. package/dist/{client-B3FBDfTg.d.ts → client-CqpOcbin.d.ts} +2 -2
  13. package/dist/{client-B-8ceVyt.d.mts → client-DMttx1xk.d.mts} +2 -2
  14. package/dist/{client-r5VSGFRU.d.mts → client-YiSelM7x.d.mts} +97 -7
  15. package/dist/{index-C-Pxrzna.d.ts → index-CxTVu0HZ.d.ts} +3 -3
  16. package/dist/{index-DLulxmF1.d.mts → index-DD9z396M.d.mts} +3 -3
  17. package/dist/index.d.mts +6 -6
  18. package/dist/index.d.ts +6 -6
  19. package/dist/index.js +17 -10
  20. package/dist/index.mjs +8 -2
  21. package/dist/mock/cli.mjs +49 -1
  22. package/dist/mock/library.js +49 -1
  23. package/dist/mock/library.mjs +49 -1
  24. package/dist/mock/msw.js +49 -1
  25. package/dist/mock/msw.mjs +49 -1
  26. package/dist/{payment-TnJUoHqL.d.mts → payment-5d8FTA1q.d.mts} +1 -1
  27. package/dist/{payment-Cfoo9MT7.d.ts → payment-BX7ccIKY.d.ts} +1 -1
  28. package/dist/{price-CCDSrIsc.d.ts → price-DtfP_Xfq.d.ts} +1 -1
  29. package/dist/{price-CM0zVSyM.d.mts → price-arl_4Rzn.d.mts} +1 -1
  30. package/dist/{product-DoA36Eqh.d.mts → product-DZX4QuKS.d.mts} +68 -4
  31. package/dist/{product-DoA36Eqh.d.ts → product-DZX4QuKS.d.ts} +68 -4
  32. package/dist/react.d.mts +405 -7
  33. package/dist/react.d.ts +405 -7
  34. package/dist/react.js +2345 -74
  35. package/dist/react.mjs +2323 -72
  36. package/dist/{server-CI1CUFVu.d.mts → server-BceZMxcK.d.mts} +8 -1
  37. package/dist/{server-CHfYeTAa.d.ts → server-DoJGSC7L.d.ts} +8 -1
  38. package/dist/server.d.mts +4 -4
  39. package/dist/server.d.ts +4 -4
  40. package/dist/server.js +2 -2
  41. package/dist/server.mjs +1 -1
  42. package/dist/styles.css +1 -1
  43. package/dist/testing/msw.d.mts +2 -2
  44. package/dist/testing/msw.d.ts +2 -2
  45. package/dist/testing/msw.js +3 -2
  46. package/dist/testing/msw.mjs +2 -1
  47. package/dist/testing/suite.d.mts +6 -6
  48. package/dist/testing/suite.d.ts +6 -6
  49. package/dist/testing/suite.js +23 -22
  50. package/dist/testing/suite.mjs +4 -3
  51. package/dist/testing.d.mts +5 -5
  52. package/dist/testing.d.ts +5 -5
  53. package/dist/testing.js +79 -78
  54. package/dist/testing.mjs +5 -4
  55. package/dist/utils.d.mts +4 -4
  56. package/dist/utils.d.ts +4 -4
  57. package/package.json +4 -1
  58. package/registry/chat-widget.json +1 -1
  59. package/registry/product-customizer.json +1 -1
package/dist/testing.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { z } from 'zod';
2
2
  import { $ZodType } from 'zod/v4/core';
3
- export { A as AddFirstProductOptions, a as AddFirstProductResult, P as ProductSummary, T as TestClientHandle, c as createTestClient, f as fixtures } from './client-B3FBDfTg.js';
4
- import './client-D0PrO1-Q.js';
5
- import './product-DoA36Eqh.js';
6
- import './payment-Cfoo9MT7.js';
7
- import './server-CHfYeTAa.js';
3
+ export { A as AddFirstProductOptions, a as AddFirstProductResult, P as ProductSummary, T as TestClientHandle, c as createTestClient, f as fixtures } from './client-CqpOcbin.js';
4
+ import './client-CR1f94wY.js';
5
+ import './product-DZX4QuKS.js';
6
+ import './payment-BX7ccIKY.js';
7
+ import './server-DoJGSC7L.js';
8
8
  import 'hono';
9
9
 
10
10
  /**
package/dist/testing.js CHANGED
@@ -1,8 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var chunk6JA4HBBD_js = require('./chunk-6JA4HBBD.js');
4
- require('./chunk-ROBTTS3Q.js');
5
- require('./chunk-W5BVGAO6.js');
3
+ var chunkJVWFIFQ7_js = require('./chunk-JVWFIFQ7.js');
4
+ require('./chunk-KJP4U6L3.js');
5
+ require('./chunk-XREIZ56K.js');
6
+ require('./chunk-BN4V4ONE.js');
6
7
  require('./chunk-GHFDYM5K.js');
7
8
  require('./chunk-PTPOQVXS.js');
8
9
  require('./chunk-AETVRBKI.js');
@@ -10,20 +11,20 @@ require('./chunk-OWW5GUSB.js');
10
11
  var zod = require('zod');
11
12
 
12
13
  var PUBLIC_SCHEMAS = {
13
- Brand: chunk6JA4HBBD_js.BrandSchema,
14
- AddItemPayload: chunk6JA4HBBD_js.AddItemPayloadSchema,
15
- Cart: chunk6JA4HBBD_js.CartSchema,
16
- CartItem: chunk6JA4HBBD_js.CartItemSchema,
17
- CartPricing: chunk6JA4HBBD_js.CartPricingSchema,
18
- CheckoutBody: chunk6JA4HBBD_js.CheckoutBodySchema,
19
- CheckoutResponse: chunk6JA4HBBD_js.CheckoutResponseSchema,
20
- VariantInfo: chunk6JA4HBBD_js.VariantInfoSchema
14
+ Brand: chunkJVWFIFQ7_js.BrandSchema,
15
+ AddItemPayload: chunkJVWFIFQ7_js.AddItemPayloadSchema,
16
+ Cart: chunkJVWFIFQ7_js.CartSchema,
17
+ CartItem: chunkJVWFIFQ7_js.CartItemSchema,
18
+ CartPricing: chunkJVWFIFQ7_js.CartPricingSchema,
19
+ CheckoutBody: chunkJVWFIFQ7_js.CheckoutBodySchema,
20
+ CheckoutResponse: chunkJVWFIFQ7_js.CheckoutResponseSchema,
21
+ VariantInfo: chunkJVWFIFQ7_js.VariantInfoSchema
21
22
  };
22
23
  function exportJsonSchemas() {
23
24
  const out = {};
24
25
  for (const [name, schema] of Object.entries(PUBLIC_SCHEMAS)) {
25
- const meta = chunk6JA4HBBD_js.cimplifyRegistry.get(schema);
26
- const json = zod.z.toJSONSchema(schema, { metadata: chunk6JA4HBBD_js.cimplifyRegistry });
26
+ const meta = chunkJVWFIFQ7_js.cimplifyRegistry.get(schema);
27
+ const json = zod.z.toJSONSchema(schema, { metadata: chunkJVWFIFQ7_js.cimplifyRegistry });
27
28
  if (meta) {
28
29
  json.$id = meta.id;
29
30
  json["x-cimplify-version"] = meta.version;
@@ -35,16 +36,16 @@ function exportJsonSchemas() {
35
36
  function listRegisteredSchemas() {
36
37
  const entries = [];
37
38
  for (const schema of [
38
- chunk6JA4HBBD_js.BrandSchema,
39
- chunk6JA4HBBD_js.AddItemPayloadSchema,
40
- chunk6JA4HBBD_js.CartSchema,
41
- chunk6JA4HBBD_js.CartItemSchema,
42
- chunk6JA4HBBD_js.CartPricingSchema,
43
- chunk6JA4HBBD_js.CheckoutBodySchema,
44
- chunk6JA4HBBD_js.CheckoutResponseSchema,
45
- chunk6JA4HBBD_js.VariantInfoSchema
39
+ chunkJVWFIFQ7_js.BrandSchema,
40
+ chunkJVWFIFQ7_js.AddItemPayloadSchema,
41
+ chunkJVWFIFQ7_js.CartSchema,
42
+ chunkJVWFIFQ7_js.CartItemSchema,
43
+ chunkJVWFIFQ7_js.CartPricingSchema,
44
+ chunkJVWFIFQ7_js.CheckoutBodySchema,
45
+ chunkJVWFIFQ7_js.CheckoutResponseSchema,
46
+ chunkJVWFIFQ7_js.VariantInfoSchema
46
47
  ]) {
47
- const meta = chunk6JA4HBBD_js.cimplifyRegistry.get(schema);
48
+ const meta = chunkJVWFIFQ7_js.cimplifyRegistry.get(schema);
48
49
  if (meta) entries.push({ schema, meta });
49
50
  }
50
51
  return entries;
@@ -52,227 +53,227 @@ function listRegisteredSchemas() {
52
53
 
53
54
  Object.defineProperty(exports, "AddItemPayloadSchema", {
54
55
  enumerable: true,
55
- get: function () { return chunk6JA4HBBD_js.AddItemPayloadSchema; }
56
+ get: function () { return chunkJVWFIFQ7_js.AddItemPayloadSchema; }
56
57
  });
57
58
  Object.defineProperty(exports, "AddOnDetailsSchema", {
58
59
  enumerable: true,
59
- get: function () { return chunk6JA4HBBD_js.AddOnDetailsSchema; }
60
+ get: function () { return chunkJVWFIFQ7_js.AddOnDetailsSchema; }
60
61
  });
61
62
  Object.defineProperty(exports, "AddOnGroupDetailsSchema", {
62
63
  enumerable: true,
63
- get: function () { return chunk6JA4HBBD_js.AddOnGroupDetailsSchema; }
64
+ get: function () { return chunkJVWFIFQ7_js.AddOnGroupDetailsSchema; }
64
65
  });
65
66
  Object.defineProperty(exports, "AddOnInDetailsSchema", {
66
67
  enumerable: true,
67
- get: function () { return chunk6JA4HBBD_js.AddOnInDetailsSchema; }
68
+ get: function () { return chunkJVWFIFQ7_js.AddOnInDetailsSchema; }
68
69
  });
69
70
  Object.defineProperty(exports, "AddOnOptionDetailsSchema", {
70
71
  enumerable: true,
71
- get: function () { return chunk6JA4HBBD_js.AddOnOptionDetailsSchema; }
72
+ get: function () { return chunkJVWFIFQ7_js.AddOnOptionDetailsSchema; }
72
73
  });
73
74
  Object.defineProperty(exports, "AuthorizationTypeSchema", {
74
75
  enumerable: true,
75
- get: function () { return chunk6JA4HBBD_js.AuthorizationTypeSchema; }
76
+ get: function () { return chunkJVWFIFQ7_js.AuthorizationTypeSchema; }
76
77
  });
77
78
  Object.defineProperty(exports, "BrandContactSchema", {
78
79
  enumerable: true,
79
- get: function () { return chunk6JA4HBBD_js.BrandContactSchema; }
80
+ get: function () { return chunkJVWFIFQ7_js.BrandContactSchema; }
80
81
  });
81
82
  Object.defineProperty(exports, "BrandFaqSchema", {
82
83
  enumerable: true,
83
- get: function () { return chunk6JA4HBBD_js.BrandFaqSchema; }
84
+ get: function () { return chunkJVWFIFQ7_js.BrandFaqSchema; }
84
85
  });
85
86
  Object.defineProperty(exports, "BrandHeroSchema", {
86
87
  enumerable: true,
87
- get: function () { return chunk6JA4HBBD_js.BrandHeroSchema; }
88
+ get: function () { return chunkJVWFIFQ7_js.BrandHeroSchema; }
88
89
  });
89
90
  Object.defineProperty(exports, "BrandNavLinkSchema", {
90
91
  enumerable: true,
91
- get: function () { return chunk6JA4HBBD_js.BrandNavLinkSchema; }
92
+ get: function () { return chunkJVWFIFQ7_js.BrandNavLinkSchema; }
92
93
  });
93
94
  Object.defineProperty(exports, "BrandPolicyPageSchema", {
94
95
  enumerable: true,
95
- get: function () { return chunk6JA4HBBD_js.BrandPolicyPageSchema; }
96
+ get: function () { return chunkJVWFIFQ7_js.BrandPolicyPageSchema; }
96
97
  });
97
98
  Object.defineProperty(exports, "BrandPolicySectionSchema", {
98
99
  enumerable: true,
99
- get: function () { return chunk6JA4HBBD_js.BrandPolicySectionSchema; }
100
+ get: function () { return chunkJVWFIFQ7_js.BrandPolicySectionSchema; }
100
101
  });
101
102
  Object.defineProperty(exports, "BrandSchema", {
102
103
  enumerable: true,
103
- get: function () { return chunk6JA4HBBD_js.BrandSchema; }
104
+ get: function () { return chunkJVWFIFQ7_js.BrandSchema; }
104
105
  });
105
106
  Object.defineProperty(exports, "BrandSocialSchema", {
106
107
  enumerable: true,
107
- get: function () { return chunk6JA4HBBD_js.BrandSocialSchema; }
108
+ get: function () { return chunkJVWFIFQ7_js.BrandSocialSchema; }
108
109
  });
109
110
  Object.defineProperty(exports, "BundleResolvedSchema", {
110
111
  enumerable: true,
111
- get: function () { return chunk6JA4HBBD_js.BundleResolvedSchema; }
112
+ get: function () { return chunkJVWFIFQ7_js.BundleResolvedSchema; }
112
113
  });
113
114
  Object.defineProperty(exports, "BundleSelectionInputSchema", {
114
115
  enumerable: true,
115
- get: function () { return chunk6JA4HBBD_js.BundleSelectionInputSchema; }
116
+ get: function () { return chunkJVWFIFQ7_js.BundleSelectionInputSchema; }
116
117
  });
117
118
  Object.defineProperty(exports, "CartItemSchema", {
118
119
  enumerable: true,
119
- get: function () { return chunk6JA4HBBD_js.CartItemSchema; }
120
+ get: function () { return chunkJVWFIFQ7_js.CartItemSchema; }
120
121
  });
121
122
  Object.defineProperty(exports, "CartPricingSchema", {
122
123
  enumerable: true,
123
- get: function () { return chunk6JA4HBBD_js.CartPricingSchema; }
124
+ get: function () { return chunkJVWFIFQ7_js.CartPricingSchema; }
124
125
  });
125
126
  Object.defineProperty(exports, "CartSchema", {
126
127
  enumerable: true,
127
- get: function () { return chunk6JA4HBBD_js.CartSchema; }
128
+ get: function () { return chunkJVWFIFQ7_js.CartSchema; }
128
129
  });
129
130
  Object.defineProperty(exports, "CheckoutBodySchema", {
130
131
  enumerable: true,
131
- get: function () { return chunk6JA4HBBD_js.CheckoutBodySchema; }
132
+ get: function () { return chunkJVWFIFQ7_js.CheckoutBodySchema; }
132
133
  });
133
134
  Object.defineProperty(exports, "CheckoutCustomerSchema", {
134
135
  enumerable: true,
135
- get: function () { return chunk6JA4HBBD_js.CheckoutCustomerSchema; }
136
+ get: function () { return chunkJVWFIFQ7_js.CheckoutCustomerSchema; }
136
137
  });
137
138
  Object.defineProperty(exports, "CheckoutResponseSchema", {
138
139
  enumerable: true,
139
- get: function () { return chunk6JA4HBBD_js.CheckoutResponseSchema; }
140
+ get: function () { return chunkJVWFIFQ7_js.CheckoutResponseSchema; }
140
141
  });
141
142
  Object.defineProperty(exports, "ChosenPriceSchema", {
142
143
  enumerable: true,
143
- get: function () { return chunk6JA4HBBD_js.ChosenPriceSchema; }
144
+ get: function () { return chunkJVWFIFQ7_js.ChosenPriceSchema; }
144
145
  });
145
146
  Object.defineProperty(exports, "CompositeResolvedSchema", {
146
147
  enumerable: true,
147
- get: function () { return chunk6JA4HBBD_js.CompositeResolvedSchema; }
148
+ get: function () { return chunkJVWFIFQ7_js.CompositeResolvedSchema; }
148
149
  });
149
150
  Object.defineProperty(exports, "CompositeSelectionInputSchema", {
150
151
  enumerable: true,
151
- get: function () { return chunk6JA4HBBD_js.CompositeSelectionInputSchema; }
152
+ get: function () { return chunkJVWFIFQ7_js.CompositeSelectionInputSchema; }
152
153
  });
153
154
  Object.defineProperty(exports, "CountryCodeSchema", {
154
155
  enumerable: true,
155
- get: function () { return chunk6JA4HBBD_js.CountryCodeSchema; }
156
+ get: function () { return chunkJVWFIFQ7_js.CountryCodeSchema; }
156
157
  });
157
158
  Object.defineProperty(exports, "CurrencyCodeSchema", {
158
159
  enumerable: true,
159
- get: function () { return chunk6JA4HBBD_js.CurrencyCodeSchema; }
160
+ get: function () { return chunkJVWFIFQ7_js.CurrencyCodeSchema; }
160
161
  });
161
162
  Object.defineProperty(exports, "CustomerInputValueSchema", {
162
163
  enumerable: true,
163
- get: function () { return chunk6JA4HBBD_js.CustomerInputValueSchema; }
164
+ get: function () { return chunkJVWFIFQ7_js.CustomerInputValueSchema; }
164
165
  });
165
166
  Object.defineProperty(exports, "DiscountDetailsSchema", {
166
167
  enumerable: true,
167
- get: function () { return chunk6JA4HBBD_js.DiscountDetailsSchema; }
168
+ get: function () { return chunkJVWFIFQ7_js.DiscountDetailsSchema; }
168
169
  });
169
170
  Object.defineProperty(exports, "IsoDateTimeSchema", {
170
171
  enumerable: true,
171
- get: function () { return chunk6JA4HBBD_js.IsoDateTimeSchema; }
172
+ get: function () { return chunkJVWFIFQ7_js.IsoDateTimeSchema; }
172
173
  });
173
174
  Object.defineProperty(exports, "LineTypeSchema", {
174
175
  enumerable: true,
175
- get: function () { return chunk6JA4HBBD_js.LineTypeSchema; }
176
+ get: function () { return chunkJVWFIFQ7_js.LineTypeSchema; }
176
177
  });
177
178
  Object.defineProperty(exports, "LocaleSchema", {
178
179
  enumerable: true,
179
- get: function () { return chunk6JA4HBBD_js.LocaleSchema; }
180
+ get: function () { return chunkJVWFIFQ7_js.LocaleSchema; }
180
181
  });
181
182
  Object.defineProperty(exports, "MoneyLooseSchema", {
182
183
  enumerable: true,
183
- get: function () { return chunk6JA4HBBD_js.MoneyLooseSchema; }
184
+ get: function () { return chunkJVWFIFQ7_js.MoneyLooseSchema; }
184
185
  });
185
186
  Object.defineProperty(exports, "MoneySchema", {
186
187
  enumerable: true,
187
- get: function () { return chunk6JA4HBBD_js.MoneySchema; }
188
+ get: function () { return chunkJVWFIFQ7_js.MoneySchema; }
188
189
  });
189
190
  Object.defineProperty(exports, "NextActionSchema", {
190
191
  enumerable: true,
191
- get: function () { return chunk6JA4HBBD_js.NextActionSchema; }
192
+ get: function () { return chunkJVWFIFQ7_js.NextActionSchema; }
192
193
  });
193
194
  Object.defineProperty(exports, "PhoneE164Schema", {
194
195
  enumerable: true,
195
- get: function () { return chunk6JA4HBBD_js.PhoneE164Schema; }
196
+ get: function () { return chunkJVWFIFQ7_js.PhoneE164Schema; }
196
197
  });
197
198
  Object.defineProperty(exports, "ProductTypeSchema", {
198
199
  enumerable: true,
199
- get: function () { return chunk6JA4HBBD_js.ProductTypeSchema; }
200
+ get: function () { return chunkJVWFIFQ7_js.ProductTypeSchema; }
200
201
  });
201
202
  Object.defineProperty(exports, "SchemaViolationError", {
202
203
  enumerable: true,
203
- get: function () { return chunk6JA4HBBD_js.SchemaViolationError; }
204
+ get: function () { return chunkJVWFIFQ7_js.SchemaViolationError; }
204
205
  });
205
206
  Object.defineProperty(exports, "SeedNameSchema", {
206
207
  enumerable: true,
207
- get: function () { return chunk6JA4HBBD_js.SeedNameSchema; }
208
+ get: function () { return chunkJVWFIFQ7_js.SeedNameSchema; }
208
209
  });
209
210
  Object.defineProperty(exports, "SelectedAddOnOptionSchema", {
210
211
  enumerable: true,
211
- get: function () { return chunk6JA4HBBD_js.SelectedAddOnOptionSchema; }
212
+ get: function () { return chunkJVWFIFQ7_js.SelectedAddOnOptionSchema; }
212
213
  });
213
214
  Object.defineProperty(exports, "ServiceStatusSchema", {
214
215
  enumerable: true,
215
- get: function () { return chunk6JA4HBBD_js.ServiceStatusSchema; }
216
+ get: function () { return chunkJVWFIFQ7_js.ServiceStatusSchema; }
216
217
  });
217
218
  Object.defineProperty(exports, "TimestampSchema", {
218
219
  enumerable: true,
219
- get: function () { return chunk6JA4HBBD_js.TimestampSchema; }
220
+ get: function () { return chunkJVWFIFQ7_js.TimestampSchema; }
220
221
  });
221
222
  Object.defineProperty(exports, "VariantDetailsSchema", {
222
223
  enumerable: true,
223
- get: function () { return chunk6JA4HBBD_js.VariantDetailsSchema; }
224
+ get: function () { return chunkJVWFIFQ7_js.VariantDetailsSchema; }
224
225
  });
225
226
  Object.defineProperty(exports, "VariantDisplayAttributeSchema", {
226
227
  enumerable: true,
227
- get: function () { return chunk6JA4HBBD_js.VariantDisplayAttributeSchema; }
228
+ get: function () { return chunkJVWFIFQ7_js.VariantDisplayAttributeSchema; }
228
229
  });
229
230
  Object.defineProperty(exports, "VariantInfoSchema", {
230
231
  enumerable: true,
231
- get: function () { return chunk6JA4HBBD_js.VariantInfoSchema; }
232
+ get: function () { return chunkJVWFIFQ7_js.VariantInfoSchema; }
232
233
  });
233
234
  Object.defineProperty(exports, "assertAddItemPayload", {
234
235
  enumerable: true,
235
- get: function () { return chunk6JA4HBBD_js.assertAddItemPayload; }
236
+ get: function () { return chunkJVWFIFQ7_js.assertAddItemPayload; }
236
237
  });
237
238
  Object.defineProperty(exports, "assertBrand", {
238
239
  enumerable: true,
239
- get: function () { return chunk6JA4HBBD_js.assertBrand; }
240
+ get: function () { return chunkJVWFIFQ7_js.assertBrand; }
240
241
  });
241
242
  Object.defineProperty(exports, "assertCart", {
242
243
  enumerable: true,
243
- get: function () { return chunk6JA4HBBD_js.assertCart; }
244
+ get: function () { return chunkJVWFIFQ7_js.assertCart; }
244
245
  });
245
246
  Object.defineProperty(exports, "assertCartItem", {
246
247
  enumerable: true,
247
- get: function () { return chunk6JA4HBBD_js.assertCartItem; }
248
+ get: function () { return chunkJVWFIFQ7_js.assertCartItem; }
248
249
  });
249
250
  Object.defineProperty(exports, "assertCheckoutBody", {
250
251
  enumerable: true,
251
- get: function () { return chunk6JA4HBBD_js.assertCheckoutBody; }
252
+ get: function () { return chunkJVWFIFQ7_js.assertCheckoutBody; }
252
253
  });
253
254
  Object.defineProperty(exports, "assertCheckoutResponse", {
254
255
  enumerable: true,
255
- get: function () { return chunk6JA4HBBD_js.assertCheckoutResponse; }
256
+ get: function () { return chunkJVWFIFQ7_js.assertCheckoutResponse; }
256
257
  });
257
258
  Object.defineProperty(exports, "cimplifyRegistry", {
258
259
  enumerable: true,
259
- get: function () { return chunk6JA4HBBD_js.cimplifyRegistry; }
260
+ get: function () { return chunkJVWFIFQ7_js.cimplifyRegistry; }
260
261
  });
261
262
  Object.defineProperty(exports, "createTestClient", {
262
263
  enumerable: true,
263
- get: function () { return chunk6JA4HBBD_js.createTestClient; }
264
+ get: function () { return chunkJVWFIFQ7_js.createTestClient; }
264
265
  });
265
266
  Object.defineProperty(exports, "fixtures", {
266
267
  enumerable: true,
267
- get: function () { return chunk6JA4HBBD_js.fixtures; }
268
+ get: function () { return chunkJVWFIFQ7_js.fixtures; }
268
269
  });
269
270
  Object.defineProperty(exports, "makeAssertion", {
270
271
  enumerable: true,
271
- get: function () { return chunk6JA4HBBD_js.makeAssertion; }
272
+ get: function () { return chunkJVWFIFQ7_js.makeAssertion; }
272
273
  });
273
274
  Object.defineProperty(exports, "registerSchema", {
274
275
  enumerable: true,
275
- get: function () { return chunk6JA4HBBD_js.registerSchema; }
276
+ get: function () { return chunkJVWFIFQ7_js.registerSchema; }
276
277
  });
277
278
  exports.exportJsonSchemas = exportJsonSchemas;
278
279
  exports.listRegisteredSchemas = listRegisteredSchemas;
package/dist/testing.mjs CHANGED
@@ -1,7 +1,8 @@
1
- import { VariantInfoSchema, CheckoutResponseSchema, CheckoutBodySchema, CartPricingSchema, CartItemSchema, CartSchema, AddItemPayloadSchema, BrandSchema, cimplifyRegistry } from './chunk-VU5LFNEX.mjs';
2
- export { AddItemPayloadSchema, AddOnDetailsSchema, AddOnGroupDetailsSchema, AddOnInDetailsSchema, AddOnOptionDetailsSchema, AuthorizationTypeSchema, BrandContactSchema, BrandFaqSchema, BrandHeroSchema, BrandNavLinkSchema, BrandPolicyPageSchema, BrandPolicySectionSchema, BrandSchema, BrandSocialSchema, BundleResolvedSchema, BundleSelectionInputSchema, CartItemSchema, CartPricingSchema, CartSchema, CheckoutBodySchema, CheckoutCustomerSchema, CheckoutResponseSchema, ChosenPriceSchema, CompositeResolvedSchema, CompositeSelectionInputSchema, CountryCodeSchema, CurrencyCodeSchema, CustomerInputValueSchema, DiscountDetailsSchema, IsoDateTimeSchema, LineTypeSchema, LocaleSchema, MoneyLooseSchema, MoneySchema, NextActionSchema, PhoneE164Schema, ProductTypeSchema, SchemaViolationError, SeedNameSchema, SelectedAddOnOptionSchema, ServiceStatusSchema, TimestampSchema, VariantDetailsSchema, VariantDisplayAttributeSchema, VariantInfoSchema, assertAddItemPayload, assertBrand, assertCart, assertCartItem, assertCheckoutBody, assertCheckoutResponse, cimplifyRegistry, createTestClient, fixtures, makeAssertion, registerSchema } from './chunk-VU5LFNEX.mjs';
3
- import './chunk-APBLFZ2I.mjs';
4
- import './chunk-A7NODD4Q.mjs';
1
+ import { VariantInfoSchema, CheckoutResponseSchema, CheckoutBodySchema, CartPricingSchema, CartItemSchema, CartSchema, AddItemPayloadSchema, BrandSchema, cimplifyRegistry } from './chunk-NGE37REW.mjs';
2
+ export { AddItemPayloadSchema, AddOnDetailsSchema, AddOnGroupDetailsSchema, AddOnInDetailsSchema, AddOnOptionDetailsSchema, AuthorizationTypeSchema, BrandContactSchema, BrandFaqSchema, BrandHeroSchema, BrandNavLinkSchema, BrandPolicyPageSchema, BrandPolicySectionSchema, BrandSchema, BrandSocialSchema, BundleResolvedSchema, BundleSelectionInputSchema, CartItemSchema, CartPricingSchema, CartSchema, CheckoutBodySchema, CheckoutCustomerSchema, CheckoutResponseSchema, ChosenPriceSchema, CompositeResolvedSchema, CompositeSelectionInputSchema, CountryCodeSchema, CurrencyCodeSchema, CustomerInputValueSchema, DiscountDetailsSchema, IsoDateTimeSchema, LineTypeSchema, LocaleSchema, MoneyLooseSchema, MoneySchema, NextActionSchema, PhoneE164Schema, ProductTypeSchema, SchemaViolationError, SeedNameSchema, SelectedAddOnOptionSchema, ServiceStatusSchema, TimestampSchema, VariantDetailsSchema, VariantDisplayAttributeSchema, VariantInfoSchema, assertAddItemPayload, assertBrand, assertCart, assertCartItem, assertCheckoutBody, assertCheckoutResponse, cimplifyRegistry, createTestClient, fixtures, makeAssertion, registerSchema } from './chunk-NGE37REW.mjs';
3
+ import './chunk-NNGKC3MM.mjs';
4
+ import './chunk-U4MW6PYU.mjs';
5
+ import './chunk-2UDEWILL.mjs';
5
6
  import './chunk-ZU4ZRYUR.mjs';
6
7
  import './chunk-KFLGQGMC.mjs';
7
8
  import './chunk-K5EN2W2Q.mjs';
package/dist/utils.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- export { C as CURRENCY_SYMBOLS, M as MOBILE_MONEY_PROVIDERS, c as categorizePaymentError, d as detectMobileMoneyProvider, f as formatMoney, a as formatNumberCompact, b as formatPrice, e as formatPriceAdjustment, g as formatPriceCompact, i as formatPriceWithTax, j as formatProductPrice, k as getBasePrice, l as getCurrencySymbol, m as getDiscountPercentage, n as getDisplayPrice, o as getMarkupPercentage, q as getProductCurrency, r as getTaxAmount, t as hasTaxInfo, u as isOnSale, v as isPaymentStatusFailure, w as isPaymentStatusRequiresAction, x as isPaymentStatusSuccess, y as isTaxInclusive, z as isValidPhoneForContext, A as normalizePaymentResponse, B as normalizePhoneToE164, D as normalizeStatusResponse, E as parsePrice, F as resolvePhoneCountryCode } from './index-DLulxmF1.mjs';
2
- import './product-DoA36Eqh.mjs';
3
- import './price-CM0zVSyM.mjs';
4
- import './payment-TnJUoHqL.mjs';
1
+ export { C as CURRENCY_SYMBOLS, M as MOBILE_MONEY_PROVIDERS, c as categorizePaymentError, d as detectMobileMoneyProvider, f as formatMoney, a as formatNumberCompact, b as formatPrice, e as formatPriceAdjustment, g as formatPriceCompact, i as formatPriceWithTax, j as formatProductPrice, k as getBasePrice, l as getCurrencySymbol, m as getDiscountPercentage, n as getDisplayPrice, o as getMarkupPercentage, q as getProductCurrency, r as getTaxAmount, t as hasTaxInfo, u as isOnSale, v as isPaymentStatusFailure, w as isPaymentStatusRequiresAction, x as isPaymentStatusSuccess, y as isTaxInclusive, z as isValidPhoneForContext, A as normalizePaymentResponse, B as normalizePhoneToE164, D as normalizeStatusResponse, E as parsePrice, F as resolvePhoneCountryCode } from './index-DD9z396M.mjs';
2
+ import './product-DZX4QuKS.mjs';
3
+ import './price-arl_4Rzn.mjs';
4
+ import './payment-5d8FTA1q.mjs';
5
5
  import 'libphonenumber-js';
package/dist/utils.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { C as CURRENCY_SYMBOLS, M as MOBILE_MONEY_PROVIDERS, c as categorizePaymentError, d as detectMobileMoneyProvider, f as formatMoney, a as formatNumberCompact, b as formatPrice, e as formatPriceAdjustment, g as formatPriceCompact, i as formatPriceWithTax, j as formatProductPrice, k as getBasePrice, l as getCurrencySymbol, m as getDiscountPercentage, n as getDisplayPrice, o as getMarkupPercentage, q as getProductCurrency, r as getTaxAmount, t as hasTaxInfo, u as isOnSale, v as isPaymentStatusFailure, w as isPaymentStatusRequiresAction, x as isPaymentStatusSuccess, y as isTaxInclusive, z as isValidPhoneForContext, A as normalizePaymentResponse, B as normalizePhoneToE164, D as normalizeStatusResponse, E as parsePrice, F as resolvePhoneCountryCode } from './index-C-Pxrzna.js';
2
- import './product-DoA36Eqh.js';
3
- import './price-CCDSrIsc.js';
4
- import './payment-Cfoo9MT7.js';
1
+ export { C as CURRENCY_SYMBOLS, M as MOBILE_MONEY_PROVIDERS, c as categorizePaymentError, d as detectMobileMoneyProvider, f as formatMoney, a as formatNumberCompact, b as formatPrice, e as formatPriceAdjustment, g as formatPriceCompact, i as formatPriceWithTax, j as formatProductPrice, k as getBasePrice, l as getCurrencySymbol, m as getDiscountPercentage, n as getDisplayPrice, o as getMarkupPercentage, q as getProductCurrency, r as getTaxAmount, t as hasTaxInfo, u as isOnSale, v as isPaymentStatusFailure, w as isPaymentStatusRequiresAction, x as isPaymentStatusSuccess, y as isTaxInclusive, z as isValidPhoneForContext, A as normalizePaymentResponse, B as normalizePhoneToE164, D as normalizeStatusResponse, E as parsePrice, F as resolvePhoneCountryCode } from './index-CxTVu0HZ.js';
2
+ import './product-DZX4QuKS.js';
3
+ import './price-DtfP_Xfq.js';
4
+ import './payment-BX7ccIKY.js';
5
5
  import 'libphonenumber-js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cimplify/sdk",
3
- "version": "0.88.0",
3
+ "version": "0.90.0",
4
4
  "description": "Cimplify Commerce SDK for storefronts",
5
5
  "keywords": [
6
6
  "cimplify",
@@ -162,9 +162,12 @@
162
162
  "@base-ui/react": "^1.5.0",
163
163
  "clsx": "^2.1.1",
164
164
  "date-fns": "^4.4.0",
165
+ "dompurify": "3.4.10",
165
166
  "jose": "^6.2.3",
166
167
  "libphonenumber-js": "1.13.6",
168
+ "marked": "18.0.5",
167
169
  "react-day-picker": "^10.0.1",
170
+ "streamdown": "2.5.0",
168
171
  "tailwind-merge": "^3.6.0",
169
172
  "xss": "^1.0.15",
170
173
  "zod": "^4.4.3"
@@ -9,7 +9,7 @@
9
9
  "files": [
10
10
  {
11
11
  "path": "chat-widget.tsx",
12
- "content": "\"use client\";\n\nimport React, {\n useState,\n useRef,\n useEffect,\n useCallback,\n type KeyboardEvent,\n} from \"react\";\nimport type { CimplifyClient } from \"../client\";\nimport type { CimplifyError } from \"@cimplify/sdk\";\nimport type { ChatMessage, ChatWidgetStarter } from \"../types/support\";\nimport { useChat } from \"./hooks/use-chat\";\nimport { useOptionalCimplifyClient } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\n/* ─── Public API ──────────────────────────────────────────── */\n\nexport interface ChatWidgetClassNames {\n root?: string;\n bubble?: string;\n panel?: string;\n header?: string;\n messages?: string;\n input?: string;\n welcome?: string;\n}\n\nexport interface ChatWidgetProps {\n client?: CimplifyClient;\n /** Business display name shown in the header */\n businessName?: string;\n /** Greeting text on the welcome screen */\n greeting?: string;\n /** Subtitle on the welcome screen */\n subtitle?: string;\n /** Conversation starters */\n starters?: ChatWidgetStarter[];\n /** Input placeholder */\n placeholder?: string;\n /** Position of the bubble */\n position?: \"bottom-right\" | \"bottom-left\";\n /** Start with the panel open */\n defaultOpen?: boolean;\n /** Poll interval in ms (default: 3000) */\n pollInterval?: number;\n className?: string;\n classNames?: ChatWidgetClassNames;\n}\n\nexport function ChatWidget({\n client: clientProp,\n businessName = \"Support\",\n greeting = \"Hi there!\",\n subtitle = \"Ask us anything — we typically reply in a few seconds.\",\n starters,\n placeholder = \"Type a message\\u2026\",\n position = \"bottom-right\",\n defaultOpen = false,\n pollInterval,\n className,\n classNames,\n}: ChatWidgetProps) {\n const context = useOptionalCimplifyClient();\n const client = clientProp ?? context?.client;\n\n const [open, setOpen] = useState(defaultOpen);\n const [unread, setUnread] = useState(0);\n\n const { messages, isLoading, isSending, error, isActive, send, startConversation } =\n useChat({ client, starters, pollInterval });\n\n const prevCountRef = useRef(messages.length);\n\n // Track unread when panel is closed\n useEffect(() => {\n if (!open && messages.length > prevCountRef.current) {\n const newMessages = messages.slice(prevCountRef.current);\n const incomingCount = newMessages.filter(\n (m) => m.sender_type !== \"customer\",\n ).length;\n if (incomingCount > 0) setUnread((n) => n + incomingCount);\n }\n prevCountRef.current = messages.length;\n }, [messages, open]);\n\n const handleOpen = useCallback(() => {\n setOpen(true);\n setUnread(0);\n }, []);\n\n const handleClose = useCallback(() => setOpen(false), []);\n\n const isLeft = position === \"bottom-left\";\n\n return (\n <div\n className={cn(\n \"fixed bottom-6 z-[9999]\",\n isLeft ? \"left-6\" : \"right-6\",\n className,\n classNames?.root,\n )}\n >\n {/* Bubble */}\n {!open && (\n <Bubble\n unread={unread}\n onClick={handleOpen}\n className={classNames?.bubble}\n />\n )}\n\n {/* Panel */}\n {open && (\n <Panel\n businessName={businessName}\n greeting={greeting}\n subtitle={subtitle}\n starters={starters}\n placeholder={placeholder}\n messages={messages}\n isLoading={isLoading}\n isSending={isSending}\n error={error}\n isActive={isActive}\n isLeft={isLeft}\n onClose={handleClose}\n onSend={send}\n onStartConversation={startConversation}\n classNames={classNames}\n />\n )}\n </div>\n );\n}\n\n/* ─── Bubble ──────────────────────────────────────────────── */\n\nfunction Bubble({\n unread,\n onClick,\n className,\n}: {\n unread: number;\n onClick: () => void;\n className?: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label=\"Open chat\"\n className={cn(\n \"relative flex h-14 w-14 items-center justify-center rounded-full\",\n \"bg-foreground text-background shadow-lg\",\n \"transition-transform duration-300 ease-out\",\n \"hover:scale-[1.08] active:scale-95\",\n className,\n )}\n >\n <ChatIcon className=\"h-6 w-6\" />\n {unread > 0 && (\n <span\n className={cn(\n \"absolute -right-0.5 -top-0.5 flex h-5 w-5 items-center justify-center\",\n \"rounded-full bg-red-500 text-[10px] font-bold text-white\",\n \"ring-2 ring-background\",\n )}\n >\n {unread > 9 ? \"9+\" : unread}\n </span>\n )}\n </button>\n );\n}\n\n/* ─── Panel ───────────────────────────────────────────────── */\n\nfunction Panel({\n businessName,\n greeting,\n subtitle,\n starters,\n placeholder,\n messages,\n isLoading,\n isSending,\n error,\n isActive,\n isLeft,\n onClose,\n onSend,\n onStartConversation,\n classNames,\n}: {\n businessName: string;\n greeting: string;\n subtitle: string;\n starters?: ChatWidgetStarter[];\n placeholder: string;\n messages: ChatMessage[];\n isLoading: boolean;\n isSending: boolean;\n error: CimplifyError | null;\n isActive: boolean;\n isLeft: boolean;\n onClose: () => void;\n onSend: (content: string) => Promise<void>;\n onStartConversation: (text?: string) => Promise<void>;\n classNames?: ChatWidgetClassNames;\n}) {\n return (\n <div\n className={cn(\n \"flex flex-col overflow-hidden rounded-2xl bg-background shadow-2xl\",\n \"w-[400px] max-w-[calc(100vw-3rem)]\",\n \"h-[min(600px,calc(100vh-6rem))]\",\n \"animate-in fade-in slide-in-from-bottom-4 duration-300\",\n isLeft ? \"origin-bottom-left\" : \"origin-bottom-right\",\n classNames?.panel,\n )}\n >\n {/* Header */}\n <Header\n businessName={businessName}\n onClose={onClose}\n className={classNames?.header}\n />\n\n {/* Body */}\n {!isActive && !isLoading ? (\n <Welcome\n greeting={greeting}\n subtitle={subtitle}\n starters={starters}\n onStarter={onStartConversation}\n className={classNames?.welcome}\n />\n ) : (\n <MessageList\n messages={messages}\n isLoading={isLoading}\n isSending={isSending}\n className={classNames?.messages}\n />\n )}\n\n {/* Error */}\n {error != null && (\n <div className=\"border-t border-red-200 bg-red-50 px-4 py-2 text-xs text-red-600\">\n Something went wrong. Please try again.\n </div>\n )}\n\n {/* Input — shown once conversation is active or loading */}\n {(isActive || isLoading) && (\n <ChatInput\n placeholder={placeholder}\n isSending={isSending}\n onSend={onSend}\n className={classNames?.input}\n />\n )}\n\n {/* Footer */}\n <div className=\"flex-shrink-0 border-t border-border bg-background px-4 py-1.5 text-center text-[10px] text-muted-foreground\">\n Powered by{\" \"}\n <span className=\"font-semibold text-foreground\">Cimplify</span>\n </div>\n </div>\n );\n}\n\n/* ─── Header ──────────────────────────────────────────────── */\n\nfunction Header({\n businessName,\n onClose,\n className,\n}: {\n businessName: string;\n onClose: () => void;\n className?: string;\n}) {\n return (\n <div\n className={cn(\n \"flex flex-shrink-0 items-center gap-3 bg-foreground px-5 py-4 text-background\",\n className,\n )}\n >\n <div className=\"flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] bg-white/15\">\n <ChatIcon className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate text-[15px] font-semibold leading-tight\">\n {businessName}\n </div>\n <div className=\"flex items-center gap-1.5 text-xs text-white/60\">\n <span className=\"h-[7px] w-[7px] flex-shrink-0 rounded-full bg-emerald-400\" />\n Online\n </div>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close chat\"\n className={cn(\n \"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg\",\n \"bg-white/10 text-white/70 transition-colors hover:bg-white/20 hover:text-white\",\n )}\n >\n <CloseIcon className=\"h-[18px] w-[18px]\" />\n </button>\n </div>\n );\n}\n\n/* ─── Welcome ─────────────────────────────────────────────── */\n\nfunction Welcome({\n greeting,\n subtitle,\n starters,\n onStarter,\n className,\n}: {\n greeting: string;\n subtitle: string;\n starters?: ChatWidgetStarter[];\n onStarter: (text?: string) => Promise<void>;\n className?: string;\n}) {\n const defaultStarters: ChatWidgetStarter[] = [\n { icon: \"\\ud83d\\udd0d\", text: \"Help me find a product\" },\n { icon: \"\\ud83d\\uded2\", text: \"Check my cart\" },\n { icon: \"\\ud83d\\udce6\", text: \"Track my order\" },\n ];\n\n const items = starters ?? defaultStarters;\n\n return (\n <div\n className={cn(\n \"flex flex-1 flex-col items-center justify-center overflow-y-auto bg-muted/50 px-8 py-10 text-center\",\n className,\n )}\n >\n <div className=\"mb-5 flex h-16 w-16 items-center justify-center rounded-2xl bg-muted\">\n <ChatIcon className=\"h-7 w-7 text-muted-foreground\" />\n </div>\n <h3 className=\"mb-2 text-xl font-bold text-foreground\">{greeting}</h3>\n <p className=\"mb-6 max-w-[280px] text-sm leading-relaxed text-muted-foreground\">\n {subtitle}\n </p>\n <div className=\"flex w-full max-w-[300px] flex-col gap-2\">\n {items.map((s) => (\n <button\n key={s.text}\n type=\"button\"\n onClick={() => onStarter(s.text)}\n className={cn(\n \"flex items-center gap-3 rounded-xl border border-border bg-background px-4 py-3\",\n \"text-left text-[13px] font-medium text-foreground\",\n \"transition-all hover:-translate-y-px hover:border-foreground hover:shadow-sm\",\n )}\n >\n {s.icon && (\n <span className=\"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-muted text-[15px]\">\n {s.icon}\n </span>\n )}\n <span>{s.text}</span>\n </button>\n ))}\n </div>\n </div>\n );\n}\n\n/* ─── Messages ────────────────────────────────────────────── */\n\nfunction MessageList({\n messages,\n isLoading,\n isSending,\n className,\n}: {\n messages: ChatMessage[];\n isLoading: boolean;\n isSending: boolean;\n className?: string;\n}) {\n const bottomRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n bottomRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [messages.length, isSending]);\n\n return (\n <div\n className={cn(\n \"flex flex-1 flex-col gap-1 overflow-y-auto bg-muted/50 px-5 py-5\",\n className,\n )}\n >\n {isLoading && messages.length === 0 && (\n <div className=\"flex flex-1 items-center justify-center\">\n <TypingDots />\n </div>\n )}\n\n {messages.map((msg, i) => {\n const isCustomer = msg.sender_type === \"customer\";\n const prev = messages[i - 1];\n const sameSenderAsPrev = prev?.sender_type === msg.sender_type;\n const isOptimistic = msg.id.startsWith(\"opt_\");\n\n return (\n <div key={msg.id}>\n {!sameSenderAsPrev && i > 0 && <div className=\"h-3\" />}\n <div\n className={cn(\n \"flex max-w-[85%]\",\n isCustomer ? \"ml-auto\" : \"mr-auto\",\n )}\n >\n <div\n className={cn(\n \"rounded-2xl px-3.5 py-2.5 text-sm leading-relaxed\",\n isCustomer\n ? \"bg-foreground text-background\"\n : \"border border-border bg-background text-foreground\",\n isCustomer\n ? sameSenderAsPrev\n ? \"rounded-tr\"\n : \"rounded-br\"\n : sameSenderAsPrev\n ? \"rounded-tl\"\n : \"rounded-bl\",\n isOptimistic && \"opacity-70\",\n )}\n >\n {!isCustomer && !sameSenderAsPrev && (\n <span className=\"mb-1 inline-flex items-center gap-1 rounded bg-violet-100 px-1.5 py-0.5 text-[10px] font-semibold text-violet-600\">\n <SparkleIcon className=\"h-[11px] w-[11px]\" />\n AI\n </span>\n )}\n <div className=\"whitespace-pre-wrap\">{msg.content}</div>\n </div>\n </div>\n </div>\n );\n })}\n\n {isSending && (\n <div className=\"mr-auto mt-1\">\n <TypingDots />\n </div>\n )}\n\n <div ref={bottomRef} />\n </div>\n );\n}\n\n/* ─── Input ───────────────────────────────────────────────── */\n\nfunction ChatInput({\n placeholder,\n isSending,\n onSend,\n className,\n}: {\n placeholder: string;\n isSending: boolean;\n onSend: (content: string) => Promise<void>;\n className?: string;\n}) {\n const [value, setValue] = useState(\"\");\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n\n const handleSend = useCallback(() => {\n const trimmed = value.trim();\n if (!trimmed || isSending) return;\n setValue(\"\");\n if (textareaRef.current) {\n textareaRef.current.style.height = \"auto\";\n }\n onSend(trimmed);\n }, [value, isSending, onSend]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSend();\n }\n },\n [handleSend],\n );\n\n const handleInput = useCallback(() => {\n const el = textareaRef.current;\n if (!el) return;\n el.style.height = \"auto\";\n el.style.height = `${Math.min(el.scrollHeight, 100)}px`;\n }, []);\n\n const hasContent = value.trim().length > 0;\n\n return (\n <div\n className={cn(\n \"flex flex-shrink-0 items-end gap-2 border-t border-border bg-background px-4 py-3\",\n className,\n )}\n >\n <div\n className={cn(\n \"flex flex-1 items-end rounded-xl border-[1.5px] border-transparent bg-muted px-1\",\n \"transition-colors focus-within:border-foreground focus-within:bg-background\",\n )}\n >\n <textarea\n ref={textareaRef}\n value={value}\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={handleKeyDown}\n onInput={handleInput}\n placeholder={placeholder}\n rows={1}\n className={cn(\n \"max-h-[100px] min-h-[20px] flex-1 resize-none bg-transparent px-2.5 py-2\",\n \"text-sm text-foreground outline-none placeholder:text-muted-foreground\",\n )}\n />\n </div>\n\n <button\n type=\"button\"\n onClick={handleSend}\n disabled={!hasContent || isSending}\n aria-label=\"Send message\"\n className={cn(\n \"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-[10px]\",\n \"transition-all duration-200\",\n hasContent && !isSending\n ? \"bg-foreground text-background hover:scale-105 active:scale-95\"\n : \"bg-muted text-muted-foreground\",\n )}\n >\n <SendIcon className=\"h-[18px] w-[18px]\" />\n </button>\n </div>\n );\n}\n\n/* ─── Typing dots ─────────────────────────────────────────── */\n\nfunction TypingDots() {\n return (\n <div className=\"flex items-center gap-1 px-4 py-3\">\n {[0, 1, 2].map((i) => (\n <span\n key={i}\n className=\"h-[7px] w-[7px] animate-bounce rounded-full bg-muted-foreground/40\"\n style={{ animationDelay: `${i * 160}ms` }}\n />\n ))}\n </div>\n );\n}\n\n/* ─── Icons (inline SVG — no icon library) ────────────────── */\n\nfunction ChatIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z\" />\n </svg>\n );\n}\n\nfunction CloseIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n );\n}\n\nfunction SendIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\" />\n <polygon points=\"22 2 15 22 11 13 2 9 22 2\" />\n </svg>\n );\n}\n\nfunction SparkleIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2.5}\n >\n <path d=\"M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83\" />\n </svg>\n );\n}\n"
12
+ "content": "\"use client\";\n\nimport React, {\n useState,\n useRef,\n useEffect,\n useCallback,\n type KeyboardEvent,\n} from \"react\";\nimport type { CimplifyClient } from \"../client\";\nimport type { CimplifyError } from \"@cimplify/sdk\";\nimport type { ChatMessage, ChatUploadAttachment, ChatWidgetStarter } from \"../types/support\";\nimport { ChatMarkdown } from \"./chat-markdown\";\nimport { partitionMessages } from \"./chat-model\";\nimport { useChat } from \"./hooks/use-chat\";\nimport { useOptionalCimplifyClient } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\n/* ─── Public API ──────────────────────────────────────────── */\n\nexport interface ChatWidgetClassNames {\n root?: string;\n bubble?: string;\n panel?: string;\n header?: string;\n messages?: string;\n input?: string;\n welcome?: string;\n}\n\nexport interface ChatWidgetProps {\n client?: CimplifyClient;\n /** Business display name shown in the header */\n businessName?: string;\n /** Greeting text on the welcome screen */\n greeting?: string;\n /** Subtitle on the welcome screen */\n subtitle?: string;\n /** Conversation starters */\n starters?: ChatWidgetStarter[];\n /** Input placeholder */\n placeholder?: string;\n /** Position of the bubble */\n position?: \"bottom-right\" | \"bottom-left\";\n /** Start with the panel open */\n defaultOpen?: boolean;\n /** Poll interval in ms (default: 3000) */\n pollInterval?: number;\n className?: string;\n classNames?: ChatWidgetClassNames;\n}\n\nexport function ChatWidget({\n client: clientProp,\n businessName = \"Support\",\n greeting = \"Hi there!\",\n subtitle = \"Ask us anything — we typically reply in a few seconds.\",\n starters,\n placeholder = \"Type a message\\u2026\",\n position = \"bottom-right\",\n defaultOpen = false,\n pollInterval,\n className,\n classNames,\n}: ChatWidgetProps) {\n const context = useOptionalCimplifyClient();\n const client = clientProp ?? context?.client;\n\n const [open, setOpen] = useState(defaultOpen);\n const [unread, setUnread] = useState(0);\n\n const {\n messages,\n isLoading,\n isSending,\n awaitingReply,\n seenAt,\n error,\n isActive,\n send,\n react,\n startConversation,\n } = useChat({ client, starters, pollInterval });\n const showTyping = isSending || awaitingReply;\n\n const attachPhoto = useCallback(\n async (file: File) => {\n if (!client || !file.type.startsWith(\"image/\") || file.size > 5 * 1024 * 1024) return;\n const uploaded = await client.uploads.upload(file);\n if (!uploaded.ok) return;\n const attachment: ChatUploadAttachment = {\n upload_id: uploaded.value.id,\n url: uploaded.value.url,\n mime_type: uploaded.value.content_type,\n filename: uploaded.value.filename,\n size: uploaded.value.size_bytes,\n };\n await send(\"\", { attachments: [attachment] });\n },\n [client, send],\n );\n\n const prevCountRef = useRef(messages.length);\n\n // Track unread when panel is closed\n useEffect(() => {\n if (!open && messages.length > prevCountRef.current) {\n const newMessages = messages.slice(prevCountRef.current);\n const incomingCount = newMessages.filter(\n (m) => m.sender_type !== \"customer\",\n ).length;\n if (incomingCount > 0) setUnread((n) => n + incomingCount);\n }\n prevCountRef.current = messages.length;\n }, [messages, open]);\n\n const handleOpen = useCallback(() => {\n setOpen(true);\n setUnread(0);\n }, []);\n\n const handleClose = useCallback(() => setOpen(false), []);\n\n const isLeft = position === \"bottom-left\";\n\n return (\n <div\n className={cn(\n \"fixed bottom-6 z-[9999]\",\n isLeft ? \"left-6\" : \"right-6\",\n className,\n classNames?.root,\n )}\n >\n {/* Bubble */}\n {!open && (\n <Bubble\n unread={unread}\n onClick={handleOpen}\n className={classNames?.bubble}\n />\n )}\n\n {/* Panel */}\n {open && (\n <Panel\n businessName={businessName}\n greeting={greeting}\n subtitle={subtitle}\n starters={starters}\n placeholder={placeholder}\n messages={messages}\n isLoading={isLoading}\n isSending={isSending}\n isTyping={showTyping}\n seenAt={seenAt}\n error={error}\n isActive={isActive}\n isLeft={isLeft}\n onClose={handleClose}\n onSend={send}\n onReact={react}\n onAttach={attachPhoto}\n onStartConversation={startConversation}\n classNames={classNames}\n />\n )}\n </div>\n );\n}\n\n/* ─── Bubble ──────────────────────────────────────────────── */\n\nfunction Bubble({\n unread,\n onClick,\n className,\n}: {\n unread: number;\n onClick: () => void;\n className?: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label=\"Open chat\"\n className={cn(\n \"relative flex h-14 w-14 items-center justify-center rounded-full\",\n \"bg-foreground text-background shadow-lg\",\n \"transition-transform duration-300 ease-out\",\n \"hover:scale-[1.08] active:scale-95\",\n className,\n )}\n >\n <ChatIcon className=\"h-6 w-6\" />\n {unread > 0 && (\n <span\n className={cn(\n \"absolute -right-0.5 -top-0.5 flex h-5 w-5 items-center justify-center\",\n \"rounded-full bg-red-500 text-[10px] font-bold text-white\",\n \"ring-2 ring-background\",\n )}\n >\n {unread > 9 ? \"9+\" : unread}\n </span>\n )}\n </button>\n );\n}\n\n/* ─── Panel ───────────────────────────────────────────────── */\n\nfunction Panel({\n businessName,\n greeting,\n subtitle,\n starters,\n placeholder,\n messages,\n isLoading,\n isSending,\n isTyping,\n seenAt,\n error,\n isActive,\n isLeft,\n onClose,\n onSend,\n onReact,\n onAttach,\n onStartConversation,\n classNames,\n}: {\n businessName: string;\n greeting: string;\n subtitle: string;\n starters?: ChatWidgetStarter[];\n placeholder: string;\n messages: ChatMessage[];\n isLoading: boolean;\n isSending: boolean;\n isTyping: boolean;\n seenAt: string | null;\n error: CimplifyError | null;\n isActive: boolean;\n isLeft: boolean;\n onClose: () => void;\n onSend: (content: string) => Promise<unknown>;\n onReact: (message: ChatMessage, emoji: string) => Promise<boolean>;\n onAttach: (file: File) => Promise<void>;\n onStartConversation: (text?: string) => Promise<void>;\n classNames?: ChatWidgetClassNames;\n}) {\n return (\n <div\n className={cn(\n \"flex flex-col overflow-hidden rounded-2xl bg-background shadow-2xl\",\n \"w-[400px] max-w-[calc(100vw-3rem)]\",\n \"h-[min(600px,calc(100vh-6rem))]\",\n \"animate-in fade-in slide-in-from-bottom-4 duration-300\",\n isLeft ? \"origin-bottom-left\" : \"origin-bottom-right\",\n classNames?.panel,\n )}\n >\n {/* Header */}\n <Header\n businessName={businessName}\n onClose={onClose}\n className={classNames?.header}\n />\n\n {/* Body */}\n {!isActive && !isLoading ? (\n <Welcome\n greeting={greeting}\n subtitle={subtitle}\n starters={starters}\n onStarter={onStartConversation}\n className={classNames?.welcome}\n />\n ) : (\n <MessageList\n messages={messages}\n isLoading={isLoading}\n isSending={isTyping}\n seenAt={seenAt}\n onReact={onReact}\n className={classNames?.messages}\n />\n )}\n\n {/* Error */}\n {error != null && (\n <div className=\"border-t border-red-200 bg-red-50 px-4 py-2 text-xs text-red-600\">\n Something went wrong. Please try again.\n </div>\n )}\n\n {/* Input — shown once conversation is active or loading */}\n {(isActive || isLoading) && (\n <ChatInput\n placeholder={placeholder}\n isSending={isSending}\n onSend={onSend}\n onAttach={onAttach}\n className={classNames?.input}\n />\n )}\n\n {/* Footer */}\n <div className=\"flex-shrink-0 border-t border-border bg-background px-4 py-1.5 text-center text-[10px] text-muted-foreground\">\n Powered by{\" \"}\n <span className=\"font-semibold text-foreground\">Cimplify</span>\n </div>\n </div>\n );\n}\n\n/* ─── Header ──────────────────────────────────────────────── */\n\nfunction Header({\n businessName,\n onClose,\n className,\n}: {\n businessName: string;\n onClose: () => void;\n className?: string;\n}) {\n return (\n <div\n className={cn(\n \"flex flex-shrink-0 items-center gap-3 bg-foreground px-5 py-4 text-background\",\n className,\n )}\n >\n <div className=\"flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] bg-white/15\">\n <ChatIcon className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate text-[15px] font-semibold leading-tight\">\n {businessName}\n </div>\n <div className=\"flex items-center gap-1.5 text-xs text-white/60\">\n <span className=\"h-[7px] w-[7px] flex-shrink-0 rounded-full bg-emerald-400\" />\n Online\n </div>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close chat\"\n className={cn(\n \"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg\",\n \"bg-white/10 text-white/70 transition-colors hover:bg-white/20 hover:text-white\",\n )}\n >\n <CloseIcon className=\"h-[18px] w-[18px]\" />\n </button>\n </div>\n );\n}\n\n/* ─── Welcome ─────────────────────────────────────────────── */\n\nfunction Welcome({\n greeting,\n subtitle,\n starters,\n onStarter,\n className,\n}: {\n greeting: string;\n subtitle: string;\n starters?: ChatWidgetStarter[];\n onStarter: (text?: string) => Promise<void>;\n className?: string;\n}) {\n const defaultStarters: ChatWidgetStarter[] = [\n { icon: \"\\ud83d\\udd0d\", text: \"Help me find a product\" },\n { icon: \"\\ud83d\\uded2\", text: \"Check my cart\" },\n { icon: \"\\ud83d\\udce6\", text: \"Track my order\" },\n ];\n\n const items = starters ?? defaultStarters;\n\n return (\n <div\n className={cn(\n \"flex flex-1 flex-col items-center justify-center overflow-y-auto bg-muted/50 px-8 py-10 text-center\",\n className,\n )}\n >\n <div className=\"mb-5 flex h-16 w-16 items-center justify-center rounded-2xl bg-muted\">\n <ChatIcon className=\"h-7 w-7 text-muted-foreground\" />\n </div>\n <h3 className=\"mb-2 text-xl font-bold text-foreground\">{greeting}</h3>\n <p className=\"mb-6 max-w-[280px] text-sm leading-relaxed text-muted-foreground\">\n {subtitle}\n </p>\n <div className=\"flex w-full max-w-[300px] flex-col gap-2\">\n {items.map((s) => (\n <button\n key={s.text}\n type=\"button\"\n onClick={() => onStarter(s.text)}\n className={cn(\n \"flex items-center gap-3 rounded-xl border border-border bg-background px-4 py-3\",\n \"text-left text-[13px] font-medium text-foreground\",\n \"transition-all hover:-translate-y-px hover:border-foreground hover:shadow-sm\",\n )}\n >\n {s.icon && (\n <span className=\"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-muted text-[15px]\">\n {s.icon}\n </span>\n )}\n <span>{s.text}</span>\n </button>\n ))}\n </div>\n </div>\n );\n}\n\n/* ─── Messages ────────────────────────────────────────────── */\n\nconst QUICK_REACTIONS = [\"👍\", \"❤️\"] as const;\n\nfunction lastSeenCustomerMessageId(\n bubbles: ChatMessage[],\n seenAt: string | null,\n): string | null {\n if (!seenAt || !bubbles.some((m) => m.sender_type === \"agent\")) return null;\n for (let i = bubbles.length - 1; i >= 0; i--) {\n if (bubbles[i].sender_type !== \"customer\") continue;\n return bubbles[i].created_at <= seenAt ? bubbles[i].id : null;\n }\n return null;\n}\n\nfunction MessageList({\n messages,\n isLoading,\n isSending,\n seenAt,\n onReact,\n className,\n}: {\n messages: ChatMessage[];\n isLoading: boolean;\n isSending: boolean;\n seenAt: string | null;\n onReact: (message: ChatMessage, emoji: string) => Promise<boolean>;\n className?: string;\n}) {\n const bottomRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n bottomRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [messages.length, isSending]);\n\n const { bubbles, reactionsByTarget } = partitionMessages(messages);\n const seenMessageId = lastSeenCustomerMessageId(bubbles, seenAt);\n\n return (\n <div\n className={cn(\n \"flex flex-1 flex-col gap-1 overflow-y-auto bg-muted/50 px-5 py-5\",\n className,\n )}\n >\n {isLoading && messages.length === 0 && (\n <div className=\"flex flex-1 items-center justify-center\">\n <TypingDots />\n </div>\n )}\n\n {bubbles.map((msg, i) => {\n const isCustomer = msg.sender_type === \"customer\";\n const prev = bubbles[i - 1];\n const sameSenderAsPrev = prev?.sender_type === msg.sender_type;\n const isOptimistic = msg.id.startsWith(\"opt_\");\n const reactions = reactionsByTarget.get(msg.id);\n\n return (\n <div key={msg.id}>\n {!sameSenderAsPrev && i > 0 && <div className=\"h-3\" />}\n <div\n className={cn(\n \"group relative flex max-w-[85%]\",\n isCustomer ? \"ml-auto\" : \"mr-auto\",\n )}\n >\n {!isCustomer && !isOptimistic && (\n <div className=\"absolute -top-3 right-0 z-10 hidden gap-0.5 rounded-full border border-border bg-background px-1 py-0.5 shadow-sm group-hover:flex\">\n {QUICK_REACTIONS.map((emoji) => (\n <button\n key={emoji}\n type=\"button\"\n aria-label={`React ${emoji}`}\n onClick={() => void onReact(msg, emoji)}\n className=\"flex h-6 w-6 items-center justify-center rounded-full text-[13px] hover:bg-muted\"\n >\n {emoji}\n </button>\n ))}\n </div>\n )}\n <div\n className={cn(\n \"rounded-2xl px-3.5 py-2.5 text-sm leading-relaxed\",\n isCustomer\n ? \"bg-foreground text-background\"\n : \"border border-border bg-background text-foreground\",\n isCustomer\n ? sameSenderAsPrev\n ? \"rounded-tr\"\n : \"rounded-br\"\n : sameSenderAsPrev\n ? \"rounded-tl\"\n : \"rounded-bl\",\n isOptimistic && \"opacity-70\",\n )}\n >\n {!isCustomer && !sameSenderAsPrev && (\n <span className=\"mb-1 inline-flex items-center gap-1 rounded bg-violet-100 px-1.5 py-0.5 text-[10px] font-semibold text-violet-600\">\n <SparkleIcon className=\"h-[11px] w-[11px]\" />\n AI\n </span>\n )}\n {msg.reply_to && (\n <div\n className={cn(\n \"mb-1.5 flex gap-1.5 rounded-lg px-2 py-1 text-xs\",\n isCustomer ? \"bg-background/15\" : \"bg-muted\",\n )}\n >\n <span\n className={cn(\n \"w-[3px] shrink-0 self-stretch rounded-full\",\n isCustomer ? \"bg-background/40\" : \"bg-muted-foreground/40\",\n )}\n />\n <span className=\"min-w-0 truncate opacity-80\">{msg.reply_to.preview}</span>\n </div>\n )}\n {msg.attachments.length > 0 && (\n <div className={cn(\"flex flex-wrap gap-1.5\", msg.content && \"mb-1.5\")}>\n {msg.attachments.map((attachment, index) =>\n attachment.mime_type?.startsWith(\"image/\") ? (\n <a key={index} href={attachment.url} target=\"_blank\" rel=\"noreferrer\">\n <img\n src={attachment.url}\n alt={attachment.filename ?? \"Photo\"}\n loading=\"lazy\"\n className=\"max-h-48 max-w-full rounded-lg object-cover\"\n />\n </a>\n ) : (\n <a\n key={index}\n href={attachment.url}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"truncate text-xs underline\"\n >\n {attachment.filename ?? \"Attachment\"}\n </a>\n ),\n )}\n </div>\n )}\n {isCustomer ? (\n msg.content && <div className=\"whitespace-pre-wrap\">{msg.content}</div>\n ) : (\n <ChatMarkdown\n text={msg.content}\n className=\"[&_p]:my-0 [&_p+p]:mt-1.5 [&_ul]:my-1 [&_ul]:list-disc [&_ul]:pl-4 [&_ol]:my-1 [&_ol]:list-decimal [&_ol]:pl-4 [&_li]:my-0.5 [&_h1]:text-sm [&_h1]:font-bold [&_h2]:text-sm [&_h2]:font-bold [&_h3]:text-sm [&_h3]:font-semibold [&_hr]:my-2 [&_table]:my-1 [&_table]:w-full [&_th]:pb-1 [&_th]:text-left [&_th]:text-[10px] [&_th]:font-semibold [&_th]:uppercase [&_td]:py-1 [&_td:last-child]:text-right [&_td:last-child]:font-semibold [&_td:last-child]:tabular-nums [&_a]:underline\"\n />\n )}\n </div>\n </div>\n {reactions && reactions.length > 0 && (\n <div className={cn(\"mt-0.5 flex gap-1\", isCustomer ? \"justify-end\" : \"justify-start\")}>\n {reactions.map((reaction) => (\n <span\n key={reaction.id}\n className=\"rounded-full border border-border bg-background px-1.5 py-0.5 text-xs shadow-sm\"\n >\n {reaction.content}\n </span>\n ))}\n </div>\n )}\n {msg.id === seenMessageId && (\n <div className=\"mt-0.5 text-right text-[10px] font-medium text-muted-foreground\">\n Seen\n </div>\n )}\n </div>\n );\n })}\n\n {isSending && (\n <div className=\"mr-auto mt-1\">\n <TypingDots />\n </div>\n )}\n\n <div ref={bottomRef} />\n </div>\n );\n}\n\n/* ─── Input ───────────────────────────────────────────────── */\n\nfunction ChatInput({\n placeholder,\n isSending,\n onSend,\n onAttach,\n className,\n}: {\n placeholder: string;\n isSending: boolean;\n onSend: (content: string) => Promise<unknown>;\n onAttach: (file: File) => Promise<void>;\n className?: string;\n}) {\n const [value, setValue] = useState(\"\");\n const [uploading, setUploading] = useState(false);\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const fileRef = useRef<HTMLInputElement>(null);\n\n const handleAttach = useCallback(\n async (file: File) => {\n setUploading(true);\n try {\n await onAttach(file);\n } finally {\n setUploading(false);\n }\n },\n [onAttach],\n );\n\n const handleSend = useCallback(() => {\n const trimmed = value.trim();\n if (!trimmed || isSending) return;\n setValue(\"\");\n if (textareaRef.current) {\n textareaRef.current.style.height = \"auto\";\n }\n onSend(trimmed);\n }, [value, isSending, onSend]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSend();\n }\n },\n [handleSend],\n );\n\n const handleInput = useCallback(() => {\n const el = textareaRef.current;\n if (!el) return;\n el.style.height = \"auto\";\n el.style.height = `${Math.min(el.scrollHeight, 100)}px`;\n }, []);\n\n const hasContent = value.trim().length > 0;\n\n return (\n <div\n className={cn(\n \"flex flex-shrink-0 items-end gap-2 border-t border-border bg-background px-4 py-3\",\n className,\n )}\n >\n <input\n ref={fileRef}\n type=\"file\"\n accept=\"image/*\"\n className=\"hidden\"\n aria-hidden=\"true\"\n tabIndex={-1}\n onChange={(e) => {\n const file = e.target.files?.[0];\n e.target.value = \"\";\n if (file) void handleAttach(file);\n }}\n />\n <button\n type=\"button\"\n aria-label=\"Attach a photo\"\n disabled={uploading}\n onClick={() => fileRef.current?.click()}\n className={cn(\n \"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-[10px]\",\n \"bg-muted text-muted-foreground transition-colors hover:text-foreground\",\n uploading && \"animate-pulse\",\n )}\n >\n <ImageIcon className=\"h-[18px] w-[18px]\" />\n </button>\n <div\n className={cn(\n \"flex flex-1 items-end rounded-xl border-[1.5px] border-transparent bg-muted px-1\",\n \"transition-colors focus-within:border-foreground focus-within:bg-background\",\n )}\n >\n <textarea\n ref={textareaRef}\n value={value}\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={handleKeyDown}\n onInput={handleInput}\n placeholder={placeholder}\n rows={1}\n className={cn(\n \"max-h-[100px] min-h-[20px] flex-1 resize-none bg-transparent px-2.5 py-2\",\n \"text-sm text-foreground outline-none placeholder:text-muted-foreground\",\n )}\n />\n </div>\n\n <button\n type=\"button\"\n onClick={handleSend}\n disabled={!hasContent || isSending}\n aria-label=\"Send message\"\n className={cn(\n \"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-[10px]\",\n \"transition-all duration-200\",\n hasContent && !isSending\n ? \"bg-foreground text-background hover:scale-105 active:scale-95\"\n : \"bg-muted text-muted-foreground\",\n )}\n >\n <SendIcon className=\"h-[18px] w-[18px]\" />\n </button>\n </div>\n );\n}\n\n/* ─── Typing dots ─────────────────────────────────────────── */\n\nfunction TypingDots() {\n return (\n <div className=\"flex items-center gap-1 px-4 py-3\">\n {[0, 1, 2].map((i) => (\n <span\n key={i}\n className=\"h-[7px] w-[7px] animate-bounce rounded-full bg-muted-foreground/40\"\n style={{ animationDelay: `${i * 160}ms` }}\n />\n ))}\n </div>\n );\n}\n\n/* ─── Icons (inline SVG — no icon library) ────────────────── */\n\nfunction ChatIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z\" />\n </svg>\n );\n}\n\nfunction CloseIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n );\n}\n\nfunction SendIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\" />\n <polygon points=\"22 2 15 22 11 13 2 9 22 2\" />\n </svg>\n );\n}\n\nfunction ImageIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\" />\n <circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\" />\n <polyline points=\"21 15 16 10 5 21\" />\n </svg>\n );\n}\n\nfunction SparkleIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2.5}\n >\n <path d=\"M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83\" />\n </svg>\n );\n}\n"
13
13
  }
14
14
  ]
15
15
  }