@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.
- package/dist/advanced.d.mts +3 -3
- package/dist/advanced.d.ts +3 -3
- package/dist/{chunk-A7NODD4Q.mjs → chunk-2UDEWILL.mjs} +83 -17
- package/dist/{chunk-W5BVGAO6.js → chunk-BN4V4ONE.js} +83 -17
- package/dist/{chunk-6JA4HBBD.js → chunk-JVWFIFQ7.js} +4 -4
- package/dist/{chunk-ROBTTS3Q.js → chunk-KJP4U6L3.js} +28 -1
- package/dist/{chunk-VU5LFNEX.mjs → chunk-NGE37REW.mjs} +2 -2
- package/dist/{chunk-APBLFZ2I.mjs → chunk-NNGKC3MM.mjs} +28 -1
- package/dist/chunk-U4MW6PYU.mjs +23 -0
- package/dist/chunk-XREIZ56K.js +26 -0
- package/dist/{client-D0PrO1-Q.d.ts → client-CR1f94wY.d.ts} +97 -7
- package/dist/{client-B3FBDfTg.d.ts → client-CqpOcbin.d.ts} +2 -2
- package/dist/{client-B-8ceVyt.d.mts → client-DMttx1xk.d.mts} +2 -2
- package/dist/{client-r5VSGFRU.d.mts → client-YiSelM7x.d.mts} +97 -7
- package/dist/{index-C-Pxrzna.d.ts → index-CxTVu0HZ.d.ts} +3 -3
- package/dist/{index-DLulxmF1.d.mts → index-DD9z396M.d.mts} +3 -3
- package/dist/index.d.mts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +17 -10
- package/dist/index.mjs +8 -2
- package/dist/mock/cli.mjs +49 -1
- package/dist/mock/library.js +49 -1
- package/dist/mock/library.mjs +49 -1
- package/dist/mock/msw.js +49 -1
- package/dist/mock/msw.mjs +49 -1
- package/dist/{payment-TnJUoHqL.d.mts → payment-5d8FTA1q.d.mts} +1 -1
- package/dist/{payment-Cfoo9MT7.d.ts → payment-BX7ccIKY.d.ts} +1 -1
- package/dist/{price-CCDSrIsc.d.ts → price-DtfP_Xfq.d.ts} +1 -1
- package/dist/{price-CM0zVSyM.d.mts → price-arl_4Rzn.d.mts} +1 -1
- package/dist/{product-DoA36Eqh.d.mts → product-DZX4QuKS.d.mts} +68 -4
- package/dist/{product-DoA36Eqh.d.ts → product-DZX4QuKS.d.ts} +68 -4
- package/dist/react.d.mts +405 -7
- package/dist/react.d.ts +405 -7
- package/dist/react.js +2345 -74
- package/dist/react.mjs +2323 -72
- package/dist/{server-CI1CUFVu.d.mts → server-BceZMxcK.d.mts} +8 -1
- package/dist/{server-CHfYeTAa.d.ts → server-DoJGSC7L.d.ts} +8 -1
- package/dist/server.d.mts +4 -4
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/server.mjs +1 -1
- package/dist/styles.css +1 -1
- package/dist/testing/msw.d.mts +2 -2
- package/dist/testing/msw.d.ts +2 -2
- package/dist/testing/msw.js +3 -2
- package/dist/testing/msw.mjs +2 -1
- package/dist/testing/suite.d.mts +6 -6
- package/dist/testing/suite.d.ts +6 -6
- package/dist/testing/suite.js +23 -22
- package/dist/testing/suite.mjs +4 -3
- package/dist/testing.d.mts +5 -5
- package/dist/testing.d.ts +5 -5
- package/dist/testing.js +79 -78
- package/dist/testing.mjs +5 -4
- package/dist/utils.d.mts +4 -4
- package/dist/utils.d.ts +4 -4
- package/package.json +4 -1
- package/registry/chat-widget.json +1 -1
- 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-
|
|
4
|
-
import './client-
|
|
5
|
-
import './product-
|
|
6
|
-
import './payment-
|
|
7
|
-
import './server-
|
|
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
|
|
4
|
-
require('./chunk-
|
|
5
|
-
require('./chunk-
|
|
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:
|
|
14
|
-
AddItemPayload:
|
|
15
|
-
Cart:
|
|
16
|
-
CartItem:
|
|
17
|
-
CartPricing:
|
|
18
|
-
CheckoutBody:
|
|
19
|
-
CheckoutResponse:
|
|
20
|
-
VariantInfo:
|
|
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 =
|
|
26
|
-
const json = zod.z.toJSONSchema(schema, { metadata:
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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 =
|
|
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
|
|
56
|
+
get: function () { return chunkJVWFIFQ7_js.AddItemPayloadSchema; }
|
|
56
57
|
});
|
|
57
58
|
Object.defineProperty(exports, "AddOnDetailsSchema", {
|
|
58
59
|
enumerable: true,
|
|
59
|
-
get: function () { return
|
|
60
|
+
get: function () { return chunkJVWFIFQ7_js.AddOnDetailsSchema; }
|
|
60
61
|
});
|
|
61
62
|
Object.defineProperty(exports, "AddOnGroupDetailsSchema", {
|
|
62
63
|
enumerable: true,
|
|
63
|
-
get: function () { return
|
|
64
|
+
get: function () { return chunkJVWFIFQ7_js.AddOnGroupDetailsSchema; }
|
|
64
65
|
});
|
|
65
66
|
Object.defineProperty(exports, "AddOnInDetailsSchema", {
|
|
66
67
|
enumerable: true,
|
|
67
|
-
get: function () { return
|
|
68
|
+
get: function () { return chunkJVWFIFQ7_js.AddOnInDetailsSchema; }
|
|
68
69
|
});
|
|
69
70
|
Object.defineProperty(exports, "AddOnOptionDetailsSchema", {
|
|
70
71
|
enumerable: true,
|
|
71
|
-
get: function () { return
|
|
72
|
+
get: function () { return chunkJVWFIFQ7_js.AddOnOptionDetailsSchema; }
|
|
72
73
|
});
|
|
73
74
|
Object.defineProperty(exports, "AuthorizationTypeSchema", {
|
|
74
75
|
enumerable: true,
|
|
75
|
-
get: function () { return
|
|
76
|
+
get: function () { return chunkJVWFIFQ7_js.AuthorizationTypeSchema; }
|
|
76
77
|
});
|
|
77
78
|
Object.defineProperty(exports, "BrandContactSchema", {
|
|
78
79
|
enumerable: true,
|
|
79
|
-
get: function () { return
|
|
80
|
+
get: function () { return chunkJVWFIFQ7_js.BrandContactSchema; }
|
|
80
81
|
});
|
|
81
82
|
Object.defineProperty(exports, "BrandFaqSchema", {
|
|
82
83
|
enumerable: true,
|
|
83
|
-
get: function () { return
|
|
84
|
+
get: function () { return chunkJVWFIFQ7_js.BrandFaqSchema; }
|
|
84
85
|
});
|
|
85
86
|
Object.defineProperty(exports, "BrandHeroSchema", {
|
|
86
87
|
enumerable: true,
|
|
87
|
-
get: function () { return
|
|
88
|
+
get: function () { return chunkJVWFIFQ7_js.BrandHeroSchema; }
|
|
88
89
|
});
|
|
89
90
|
Object.defineProperty(exports, "BrandNavLinkSchema", {
|
|
90
91
|
enumerable: true,
|
|
91
|
-
get: function () { return
|
|
92
|
+
get: function () { return chunkJVWFIFQ7_js.BrandNavLinkSchema; }
|
|
92
93
|
});
|
|
93
94
|
Object.defineProperty(exports, "BrandPolicyPageSchema", {
|
|
94
95
|
enumerable: true,
|
|
95
|
-
get: function () { return
|
|
96
|
+
get: function () { return chunkJVWFIFQ7_js.BrandPolicyPageSchema; }
|
|
96
97
|
});
|
|
97
98
|
Object.defineProperty(exports, "BrandPolicySectionSchema", {
|
|
98
99
|
enumerable: true,
|
|
99
|
-
get: function () { return
|
|
100
|
+
get: function () { return chunkJVWFIFQ7_js.BrandPolicySectionSchema; }
|
|
100
101
|
});
|
|
101
102
|
Object.defineProperty(exports, "BrandSchema", {
|
|
102
103
|
enumerable: true,
|
|
103
|
-
get: function () { return
|
|
104
|
+
get: function () { return chunkJVWFIFQ7_js.BrandSchema; }
|
|
104
105
|
});
|
|
105
106
|
Object.defineProperty(exports, "BrandSocialSchema", {
|
|
106
107
|
enumerable: true,
|
|
107
|
-
get: function () { return
|
|
108
|
+
get: function () { return chunkJVWFIFQ7_js.BrandSocialSchema; }
|
|
108
109
|
});
|
|
109
110
|
Object.defineProperty(exports, "BundleResolvedSchema", {
|
|
110
111
|
enumerable: true,
|
|
111
|
-
get: function () { return
|
|
112
|
+
get: function () { return chunkJVWFIFQ7_js.BundleResolvedSchema; }
|
|
112
113
|
});
|
|
113
114
|
Object.defineProperty(exports, "BundleSelectionInputSchema", {
|
|
114
115
|
enumerable: true,
|
|
115
|
-
get: function () { return
|
|
116
|
+
get: function () { return chunkJVWFIFQ7_js.BundleSelectionInputSchema; }
|
|
116
117
|
});
|
|
117
118
|
Object.defineProperty(exports, "CartItemSchema", {
|
|
118
119
|
enumerable: true,
|
|
119
|
-
get: function () { return
|
|
120
|
+
get: function () { return chunkJVWFIFQ7_js.CartItemSchema; }
|
|
120
121
|
});
|
|
121
122
|
Object.defineProperty(exports, "CartPricingSchema", {
|
|
122
123
|
enumerable: true,
|
|
123
|
-
get: function () { return
|
|
124
|
+
get: function () { return chunkJVWFIFQ7_js.CartPricingSchema; }
|
|
124
125
|
});
|
|
125
126
|
Object.defineProperty(exports, "CartSchema", {
|
|
126
127
|
enumerable: true,
|
|
127
|
-
get: function () { return
|
|
128
|
+
get: function () { return chunkJVWFIFQ7_js.CartSchema; }
|
|
128
129
|
});
|
|
129
130
|
Object.defineProperty(exports, "CheckoutBodySchema", {
|
|
130
131
|
enumerable: true,
|
|
131
|
-
get: function () { return
|
|
132
|
+
get: function () { return chunkJVWFIFQ7_js.CheckoutBodySchema; }
|
|
132
133
|
});
|
|
133
134
|
Object.defineProperty(exports, "CheckoutCustomerSchema", {
|
|
134
135
|
enumerable: true,
|
|
135
|
-
get: function () { return
|
|
136
|
+
get: function () { return chunkJVWFIFQ7_js.CheckoutCustomerSchema; }
|
|
136
137
|
});
|
|
137
138
|
Object.defineProperty(exports, "CheckoutResponseSchema", {
|
|
138
139
|
enumerable: true,
|
|
139
|
-
get: function () { return
|
|
140
|
+
get: function () { return chunkJVWFIFQ7_js.CheckoutResponseSchema; }
|
|
140
141
|
});
|
|
141
142
|
Object.defineProperty(exports, "ChosenPriceSchema", {
|
|
142
143
|
enumerable: true,
|
|
143
|
-
get: function () { return
|
|
144
|
+
get: function () { return chunkJVWFIFQ7_js.ChosenPriceSchema; }
|
|
144
145
|
});
|
|
145
146
|
Object.defineProperty(exports, "CompositeResolvedSchema", {
|
|
146
147
|
enumerable: true,
|
|
147
|
-
get: function () { return
|
|
148
|
+
get: function () { return chunkJVWFIFQ7_js.CompositeResolvedSchema; }
|
|
148
149
|
});
|
|
149
150
|
Object.defineProperty(exports, "CompositeSelectionInputSchema", {
|
|
150
151
|
enumerable: true,
|
|
151
|
-
get: function () { return
|
|
152
|
+
get: function () { return chunkJVWFIFQ7_js.CompositeSelectionInputSchema; }
|
|
152
153
|
});
|
|
153
154
|
Object.defineProperty(exports, "CountryCodeSchema", {
|
|
154
155
|
enumerable: true,
|
|
155
|
-
get: function () { return
|
|
156
|
+
get: function () { return chunkJVWFIFQ7_js.CountryCodeSchema; }
|
|
156
157
|
});
|
|
157
158
|
Object.defineProperty(exports, "CurrencyCodeSchema", {
|
|
158
159
|
enumerable: true,
|
|
159
|
-
get: function () { return
|
|
160
|
+
get: function () { return chunkJVWFIFQ7_js.CurrencyCodeSchema; }
|
|
160
161
|
});
|
|
161
162
|
Object.defineProperty(exports, "CustomerInputValueSchema", {
|
|
162
163
|
enumerable: true,
|
|
163
|
-
get: function () { return
|
|
164
|
+
get: function () { return chunkJVWFIFQ7_js.CustomerInputValueSchema; }
|
|
164
165
|
});
|
|
165
166
|
Object.defineProperty(exports, "DiscountDetailsSchema", {
|
|
166
167
|
enumerable: true,
|
|
167
|
-
get: function () { return
|
|
168
|
+
get: function () { return chunkJVWFIFQ7_js.DiscountDetailsSchema; }
|
|
168
169
|
});
|
|
169
170
|
Object.defineProperty(exports, "IsoDateTimeSchema", {
|
|
170
171
|
enumerable: true,
|
|
171
|
-
get: function () { return
|
|
172
|
+
get: function () { return chunkJVWFIFQ7_js.IsoDateTimeSchema; }
|
|
172
173
|
});
|
|
173
174
|
Object.defineProperty(exports, "LineTypeSchema", {
|
|
174
175
|
enumerable: true,
|
|
175
|
-
get: function () { return
|
|
176
|
+
get: function () { return chunkJVWFIFQ7_js.LineTypeSchema; }
|
|
176
177
|
});
|
|
177
178
|
Object.defineProperty(exports, "LocaleSchema", {
|
|
178
179
|
enumerable: true,
|
|
179
|
-
get: function () { return
|
|
180
|
+
get: function () { return chunkJVWFIFQ7_js.LocaleSchema; }
|
|
180
181
|
});
|
|
181
182
|
Object.defineProperty(exports, "MoneyLooseSchema", {
|
|
182
183
|
enumerable: true,
|
|
183
|
-
get: function () { return
|
|
184
|
+
get: function () { return chunkJVWFIFQ7_js.MoneyLooseSchema; }
|
|
184
185
|
});
|
|
185
186
|
Object.defineProperty(exports, "MoneySchema", {
|
|
186
187
|
enumerable: true,
|
|
187
|
-
get: function () { return
|
|
188
|
+
get: function () { return chunkJVWFIFQ7_js.MoneySchema; }
|
|
188
189
|
});
|
|
189
190
|
Object.defineProperty(exports, "NextActionSchema", {
|
|
190
191
|
enumerable: true,
|
|
191
|
-
get: function () { return
|
|
192
|
+
get: function () { return chunkJVWFIFQ7_js.NextActionSchema; }
|
|
192
193
|
});
|
|
193
194
|
Object.defineProperty(exports, "PhoneE164Schema", {
|
|
194
195
|
enumerable: true,
|
|
195
|
-
get: function () { return
|
|
196
|
+
get: function () { return chunkJVWFIFQ7_js.PhoneE164Schema; }
|
|
196
197
|
});
|
|
197
198
|
Object.defineProperty(exports, "ProductTypeSchema", {
|
|
198
199
|
enumerable: true,
|
|
199
|
-
get: function () { return
|
|
200
|
+
get: function () { return chunkJVWFIFQ7_js.ProductTypeSchema; }
|
|
200
201
|
});
|
|
201
202
|
Object.defineProperty(exports, "SchemaViolationError", {
|
|
202
203
|
enumerable: true,
|
|
203
|
-
get: function () { return
|
|
204
|
+
get: function () { return chunkJVWFIFQ7_js.SchemaViolationError; }
|
|
204
205
|
});
|
|
205
206
|
Object.defineProperty(exports, "SeedNameSchema", {
|
|
206
207
|
enumerable: true,
|
|
207
|
-
get: function () { return
|
|
208
|
+
get: function () { return chunkJVWFIFQ7_js.SeedNameSchema; }
|
|
208
209
|
});
|
|
209
210
|
Object.defineProperty(exports, "SelectedAddOnOptionSchema", {
|
|
210
211
|
enumerable: true,
|
|
211
|
-
get: function () { return
|
|
212
|
+
get: function () { return chunkJVWFIFQ7_js.SelectedAddOnOptionSchema; }
|
|
212
213
|
});
|
|
213
214
|
Object.defineProperty(exports, "ServiceStatusSchema", {
|
|
214
215
|
enumerable: true,
|
|
215
|
-
get: function () { return
|
|
216
|
+
get: function () { return chunkJVWFIFQ7_js.ServiceStatusSchema; }
|
|
216
217
|
});
|
|
217
218
|
Object.defineProperty(exports, "TimestampSchema", {
|
|
218
219
|
enumerable: true,
|
|
219
|
-
get: function () { return
|
|
220
|
+
get: function () { return chunkJVWFIFQ7_js.TimestampSchema; }
|
|
220
221
|
});
|
|
221
222
|
Object.defineProperty(exports, "VariantDetailsSchema", {
|
|
222
223
|
enumerable: true,
|
|
223
|
-
get: function () { return
|
|
224
|
+
get: function () { return chunkJVWFIFQ7_js.VariantDetailsSchema; }
|
|
224
225
|
});
|
|
225
226
|
Object.defineProperty(exports, "VariantDisplayAttributeSchema", {
|
|
226
227
|
enumerable: true,
|
|
227
|
-
get: function () { return
|
|
228
|
+
get: function () { return chunkJVWFIFQ7_js.VariantDisplayAttributeSchema; }
|
|
228
229
|
});
|
|
229
230
|
Object.defineProperty(exports, "VariantInfoSchema", {
|
|
230
231
|
enumerable: true,
|
|
231
|
-
get: function () { return
|
|
232
|
+
get: function () { return chunkJVWFIFQ7_js.VariantInfoSchema; }
|
|
232
233
|
});
|
|
233
234
|
Object.defineProperty(exports, "assertAddItemPayload", {
|
|
234
235
|
enumerable: true,
|
|
235
|
-
get: function () { return
|
|
236
|
+
get: function () { return chunkJVWFIFQ7_js.assertAddItemPayload; }
|
|
236
237
|
});
|
|
237
238
|
Object.defineProperty(exports, "assertBrand", {
|
|
238
239
|
enumerable: true,
|
|
239
|
-
get: function () { return
|
|
240
|
+
get: function () { return chunkJVWFIFQ7_js.assertBrand; }
|
|
240
241
|
});
|
|
241
242
|
Object.defineProperty(exports, "assertCart", {
|
|
242
243
|
enumerable: true,
|
|
243
|
-
get: function () { return
|
|
244
|
+
get: function () { return chunkJVWFIFQ7_js.assertCart; }
|
|
244
245
|
});
|
|
245
246
|
Object.defineProperty(exports, "assertCartItem", {
|
|
246
247
|
enumerable: true,
|
|
247
|
-
get: function () { return
|
|
248
|
+
get: function () { return chunkJVWFIFQ7_js.assertCartItem; }
|
|
248
249
|
});
|
|
249
250
|
Object.defineProperty(exports, "assertCheckoutBody", {
|
|
250
251
|
enumerable: true,
|
|
251
|
-
get: function () { return
|
|
252
|
+
get: function () { return chunkJVWFIFQ7_js.assertCheckoutBody; }
|
|
252
253
|
});
|
|
253
254
|
Object.defineProperty(exports, "assertCheckoutResponse", {
|
|
254
255
|
enumerable: true,
|
|
255
|
-
get: function () { return
|
|
256
|
+
get: function () { return chunkJVWFIFQ7_js.assertCheckoutResponse; }
|
|
256
257
|
});
|
|
257
258
|
Object.defineProperty(exports, "cimplifyRegistry", {
|
|
258
259
|
enumerable: true,
|
|
259
|
-
get: function () { return
|
|
260
|
+
get: function () { return chunkJVWFIFQ7_js.cimplifyRegistry; }
|
|
260
261
|
});
|
|
261
262
|
Object.defineProperty(exports, "createTestClient", {
|
|
262
263
|
enumerable: true,
|
|
263
|
-
get: function () { return
|
|
264
|
+
get: function () { return chunkJVWFIFQ7_js.createTestClient; }
|
|
264
265
|
});
|
|
265
266
|
Object.defineProperty(exports, "fixtures", {
|
|
266
267
|
enumerable: true,
|
|
267
|
-
get: function () { return
|
|
268
|
+
get: function () { return chunkJVWFIFQ7_js.fixtures; }
|
|
268
269
|
});
|
|
269
270
|
Object.defineProperty(exports, "makeAssertion", {
|
|
270
271
|
enumerable: true,
|
|
271
|
-
get: function () { return
|
|
272
|
+
get: function () { return chunkJVWFIFQ7_js.makeAssertion; }
|
|
272
273
|
});
|
|
273
274
|
Object.defineProperty(exports, "registerSchema", {
|
|
274
275
|
enumerable: true,
|
|
275
|
-
get: function () { return
|
|
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-
|
|
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-
|
|
3
|
-
import './chunk-
|
|
4
|
-
import './chunk-
|
|
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-
|
|
2
|
-
import './product-
|
|
3
|
-
import './price-
|
|
4
|
-
import './payment-
|
|
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-
|
|
2
|
-
import './product-
|
|
3
|
-
import './price-
|
|
4
|
-
import './payment-
|
|
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.
|
|
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
|
}
|