@wewear/virtual-try-on 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -1,9 +1,28 @@
1
- /**
2
- * WeWear Virtual Try-On Widget Implementation
3
- *
4
- * Core widget class that handles virtual try-on functionality integration.
5
- * Manages button creation, API communication, and modal display.
6
- */
1
+ async function callVirtualTryOnApi(baseUrl, ww_access_token, ww_user_id, ww_product_id, ww_image) {
2
+ try {
3
+ const formData = new FormData();
4
+ formData.append("ww_user_id", ww_user_id);
5
+ formData.append("ww_product_id", ww_product_id);
6
+ formData.append("ww_image", ww_image, "captured-image.jpg");
7
+ formData.append("ww_access_token", ww_access_token);
8
+ const response = await fetch(`${baseUrl}/api/virtual-try-on`, {
9
+ method: "POST",
10
+ body: formData,
11
+ });
12
+ if (!response.ok) {
13
+ console.error("[WeWear VTO] API request failed:", response.status, response.statusText);
14
+ return null;
15
+ }
16
+ const result = await response.json();
17
+ console.log("[WeWear VTO] API response:", result);
18
+ return result;
19
+ }
20
+ catch (error) {
21
+ console.error("[WeWear VTO] API call failed:", error);
22
+ return null;
23
+ }
24
+ }
25
+
7
26
  /** Default configuration constants */
8
27
  const DEFAULT_CONFIG = {
9
28
  BASE_URL: "https://virtual-try-on-widget.vercel.app",
@@ -23,249 +42,498 @@ const Z_INDEX = {
23
42
  BUTTON: 10,
24
43
  MODAL: 99999,
25
44
  };
26
- class VirtualTryOnWidget {
27
- constructor(config = {}) {
28
- this.config = {
29
- baseUrl: config.baseUrl || DEFAULT_CONFIG.BASE_URL,
30
- productPageSelector: config.productPageSelector || DEFAULT_CONFIG.PRODUCT_PAGE_SELECTOR,
31
- gallerySelector: config.gallerySelector || DEFAULT_CONFIG.GALLERY_SELECTOR,
32
- skuSelector: config.skuSelector || DEFAULT_CONFIG.SKU_SELECTOR,
33
- buttonPosition: config.buttonPosition || DEFAULT_CONFIG.BUTTON_POSITION,
45
+ /** Camera constraints for optimal capture */
46
+ const CAMERA_CONSTRAINTS = {
47
+ video: {
48
+ width: { ideal: 1280 },
49
+ height: { ideal: 720 },
50
+ facingMode: "user", // Front-facing camera
51
+ },
52
+ audio: false,
53
+ };
54
+ /** Image capture settings */
55
+ const IMAGE_SETTINGS = {
56
+ FORMAT: "image/jpeg",
57
+ QUALITY: 0.8,
58
+ };
59
+
60
+ async function startCamera(video, loadingIndicator, captureButton) {
61
+ console.log("[WeWear VTO] Starting camera...");
62
+ try {
63
+ console.log("[WeWear VTO] Requesting camera access...");
64
+ const stream = await navigator.mediaDevices.getUserMedia(CAMERA_CONSTRAINTS);
65
+ video.srcObject = stream;
66
+ video.onloadedmetadata = () => {
67
+ console.log("[WeWear VTO] Camera stream loaded successfully");
68
+ loadingIndicator.style.display = "none";
69
+ captureButton.style.display = "flex";
34
70
  };
35
71
  }
36
- /**
37
- * Retrieves a cookie value by name
38
- * @param name - Cookie name to retrieve
39
- * @returns Cookie value or null if not found
40
- */
41
- getCookie(name) {
42
- var _a;
43
- if (typeof document === "undefined")
44
- return null;
45
- const value = `; ${document.cookie}`;
46
- const parts = value.split(`; ${name}=`);
47
- if (parts.length === 2) {
48
- const part = parts.pop();
49
- return part ? ((_a = part.split(";").shift()) === null || _a === void 0 ? void 0 : _a.trim()) || null : null;
50
- }
51
- return null;
72
+ catch (error) {
73
+ console.error("[WeWear VTO] Camera access error:", error);
74
+ loadingIndicator.innerHTML =
75
+ "Camera access denied. Please allow camera permissions.";
52
76
  }
53
- /**
54
- * Makes API call to virtual try-on service
55
- * @param ww_access_token - Optional authentication token
56
- * @param ww_user_id - User identifier
57
- * @param ww_product_id - Product identifier
58
- * @returns Promise with virtual try-on result or null if failed
59
- */
60
- async callVirtualTryOnApi(ww_access_token, ww_user_id, ww_product_id) {
61
- try {
62
- const response = await fetch(`${this.config.baseUrl}/api/virtual-try-on`, {
63
- method: "POST",
64
- headers: {
65
- "Content-Type": "application/json",
66
- Accept: "application/json",
67
- },
68
- body: JSON.stringify({
69
- ww_access_token,
70
- ww_user_id,
71
- ww_product_id,
72
- }),
73
- });
74
- if (!response.ok) {
75
- throw new Error(`API responded with status ${response.status}: ${response.statusText}`);
77
+ }
78
+ function stopCamera(video) {
79
+ const stream = video.srcObject;
80
+ if (stream) {
81
+ const tracks = stream.getTracks();
82
+ tracks.forEach((track) => {
83
+ track.stop();
84
+ });
85
+ video.srcObject = null;
86
+ }
87
+ }
88
+ async function captureImageFromVideo(video, canvas) {
89
+ // Set canvas dimensions to match video
90
+ canvas.width = video.videoWidth;
91
+ canvas.height = video.videoHeight;
92
+ // Draw video frame to canvas
93
+ const ctx = canvas.getContext("2d");
94
+ if (!ctx) {
95
+ throw new Error("Could not get canvas context");
96
+ }
97
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
98
+ // Convert canvas to blob
99
+ return new Promise((resolve, reject) => {
100
+ canvas.toBlob((blob) => {
101
+ if (blob) {
102
+ resolve(blob);
76
103
  }
77
- const result = await response.json();
78
- if (!result.imageUrl) {
79
- throw new Error("API response missing imageUrl");
104
+ else {
105
+ reject(new Error("Failed to create blob from canvas"));
80
106
  }
81
- return result;
82
- }
83
- catch (error) {
84
- console.error("[WeWear VTO] API call failed:", error);
85
- return null;
86
- }
87
- }
88
- /**
89
- * Creates the virtual try-on button element
90
- * @param onClick - Click handler function
91
- * @returns Button container element
92
- */
93
- createButton(onClick) {
94
- const container = document.createElement("div");
95
- container.className = CSS_CLASSES.BUTTON_CONTAINER;
96
- const positionStyles = this.getPositionStyles();
97
- container.style.cssText = `
98
- position: absolute;
99
- ${positionStyles}
100
- display: flex;
101
- border-radius: 50%;
102
- background: #000000;
103
- padding: 4px;
104
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
105
- z-index: ${Z_INDEX.BUTTON};
106
- transition: transform 0.2s ease, box-shadow 0.2s ease;
107
- `;
108
- const button = document.createElement("button");
109
- button.type = "button";
110
- button.className = CSS_CLASSES.BUTTON;
111
- button.setAttribute("aria-label", "Virtual Try-On");
112
- button.setAttribute("title", "Try this product virtually");
113
- button.style.cssText = `
114
- display: flex;
115
- width: 36px;
116
- height: 36px;
117
- cursor: pointer;
118
- align-items: center;
119
- justify-content: center;
120
- border-radius: 50%;
121
- padding: 10px;
122
- border: none;
123
- background: transparent;
124
- color: #ffffff;
125
- `;
126
- // Add hover effects
127
- container.addEventListener("mouseenter", () => {
128
- container.style.transform = "scale(1.05)";
129
- container.style.boxShadow = "0 30px 60px -12px rgba(0, 0, 0, 0.35)";
130
- });
131
- container.addEventListener("mouseleave", () => {
132
- container.style.transform = "scale(1)";
133
- container.style.boxShadow = "0 25px 50px -12px rgba(0, 0, 0, 0.25)";
134
- });
135
- button.innerHTML = `
136
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
137
- <path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"></path>
138
- <circle cx="12" cy="13" r="3"></circle>
139
- </svg>
140
- `;
141
- button.onclick = onClick;
142
- container.appendChild(button);
143
- return container;
107
+ }, IMAGE_SETTINGS.FORMAT, IMAGE_SETTINGS.QUALITY);
108
+ });
109
+ }
110
+
111
+ function getCookie(name) {
112
+ var _a;
113
+ if (typeof document === "undefined")
114
+ return null;
115
+ const value = `; ${document.cookie}`;
116
+ const parts = value.split(`; ${name}=`);
117
+ if (parts.length === 2) {
118
+ const part = parts.pop();
119
+ return part ? ((_a = part.split(";").shift()) === null || _a === void 0 ? void 0 : _a.trim()) || null : null;
144
120
  }
145
- /**
146
- * Gets CSS position styles based on button position configuration
147
- * @returns CSS position string
148
- */
149
- getPositionStyles() {
150
- switch (this.config.buttonPosition) {
151
- case "bottom-left":
152
- return "left: 20px; bottom: 20px;";
153
- case "top-right":
154
- return "right: 20px; top: 20px;";
155
- case "top-left":
156
- return "left: 20px; top: 20px;";
157
- default:
158
- return "right: 20px; bottom: 20px;";
159
- }
121
+ return null;
122
+ }
123
+ function getPositionStyles(position) {
124
+ switch (position) {
125
+ case "bottom-left":
126
+ return "left: 20px; bottom: 20px;";
127
+ case "top-right":
128
+ return "right: 20px; top: 20px;";
129
+ case "top-left":
130
+ return "left: 20px; top: 20px;";
131
+ default:
132
+ return "right: 20px; bottom: 20px;";
160
133
  }
161
- /**
162
- * Displays the virtual try-on result in a modal
163
- * @param imageUrl - URL of the virtual try-on image
164
- */
165
- showImageModal(imageUrl) {
166
- // Remove any existing modals first
167
- const existingModals = document.querySelectorAll(`.${CSS_CLASSES.MODAL}`);
168
- existingModals.forEach((modal) => {
134
+ }
135
+ function removeElements(selector) {
136
+ const elements = document.querySelectorAll(selector);
137
+ elements.forEach((element) => {
138
+ element.remove();
139
+ });
140
+ }
141
+
142
+ function createButton(buttonPosition, onClick) {
143
+ const container = document.createElement("div");
144
+ container.className = CSS_CLASSES.BUTTON_CONTAINER;
145
+ const positionStyles = getPositionStyles(buttonPosition);
146
+ container.style.cssText = `
147
+ position: absolute;
148
+ ${positionStyles}
149
+ display: flex;
150
+ border-radius: 50%;
151
+ background: #000000;
152
+ padding: 4px;
153
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
154
+ z-index: ${Z_INDEX.BUTTON};
155
+ `;
156
+ const button = document.createElement("button");
157
+ button.type = "button";
158
+ button.className = CSS_CLASSES.BUTTON;
159
+ button.setAttribute("aria-label", "Virtual Try-On");
160
+ button.style.cssText = `
161
+ display: flex;
162
+ width: 36px;
163
+ height: 36px;
164
+ cursor: pointer;
165
+ align-items: center;
166
+ justify-content: center;
167
+ border-radius: 50%;
168
+ padding: 10px;
169
+ border: none;
170
+ background: transparent;
171
+ color: #ffffff;
172
+ `;
173
+ button.innerHTML = `
174
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-camera-icon lucide-camera"><path d="M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z"/><circle cx="12" cy="13" r="3"/></svg>
175
+ `;
176
+ button.onclick = onClick;
177
+ container.appendChild(button);
178
+ return container;
179
+ }
180
+
181
+ function showCameraModal(callbacks) {
182
+ console.log("[WeWear VTO] Opening camera modal...");
183
+ // Remove any existing modals first
184
+ removeElements(`.${CSS_CLASSES.MODAL}`);
185
+ // Create modal container
186
+ const modal = document.createElement("div");
187
+ modal.className = CSS_CLASSES.MODAL;
188
+ modal.style.cssText = `
189
+ position: fixed;
190
+ top: 0;
191
+ left: 0;
192
+ width: 100%;
193
+ height: 100%;
194
+ background-color: rgba(0, 0, 0, 0.95);
195
+ display: flex;
196
+ flex-direction: column;
197
+ align-items: center;
198
+ justify-content: center;
199
+ z-index: ${Z_INDEX.MODAL};
200
+ padding: 20px;
201
+ box-sizing: border-box;
202
+ `;
203
+ // Create camera container
204
+ const cameraContainer = document.createElement("div");
205
+ cameraContainer.style.cssText = `
206
+ position: relative;
207
+ width: 100%;
208
+ max-width: 500px;
209
+ height: 70vh;
210
+ background-color: #000;
211
+ border-radius: 12px;
212
+ overflow: hidden;
213
+ display: flex;
214
+ flex-direction: column;
215
+ align-items: center;
216
+ justify-content: center;
217
+ `;
218
+ // Create video element
219
+ const video = document.createElement("video");
220
+ video.autoplay = true;
221
+ video.playsInline = true;
222
+ video.muted = true;
223
+ video.style.cssText = `
224
+ width: 100%;
225
+ height: 100%;
226
+ object-fit: cover;
227
+ border-radius: 12px;
228
+ `;
229
+ // Create canvas for capturing
230
+ const canvas = document.createElement("canvas");
231
+ canvas.style.display = "none";
232
+ // Create capture button
233
+ const captureButton = document.createElement("button");
234
+ captureButton.innerHTML = "";
235
+ captureButton.style.cssText = `
236
+ position: absolute;
237
+ bottom: 20px;
238
+ left: 50%;
239
+ transform: translateX(-50%);
240
+ width: 60px;
241
+ height: 60px;
242
+ border: 3px solid white;
243
+ background-color: rgba(0, 0, 0, 0.7);
244
+ border-radius: 50%;
245
+ cursor: pointer;
246
+ display: none;
247
+ align-items: center;
248
+ justify-content: center;
249
+ transition: all 0.2s ease;
250
+ `;
251
+ // Create close button
252
+ const closeButton = document.createElement("button");
253
+ closeButton.innerHTML = "×";
254
+ closeButton.style.cssText = `
255
+ position: absolute;
256
+ top: 15px;
257
+ right: 15px;
258
+ width: 40px;
259
+ height: 40px;
260
+ border: none;
261
+ background-color: rgba(0, 0, 0, 0.7);
262
+ color: white;
263
+ font-size: 24px;
264
+ font-weight: bold;
265
+ cursor: pointer;
266
+ border-radius: 50%;
267
+ display: flex;
268
+ align-items: center;
269
+ justify-content: center;
270
+ `;
271
+ // Create loading indicator
272
+ const loadingIndicator = document.createElement("div");
273
+ loadingIndicator.innerHTML = "Initializing camera...";
274
+ loadingIndicator.style.cssText = `
275
+ color: white;
276
+ font-size: 16px;
277
+ position: absolute;
278
+ top: 50%;
279
+ left: 50%;
280
+ transform: translate(-50%, -50%);
281
+ `;
282
+ // Add event listeners
283
+ closeButton.onclick = () => {
284
+ stopCamera(video);
285
+ modal.remove();
286
+ };
287
+ captureButton.onclick = async () => {
288
+ await callbacks.onCapture(video, canvas);
289
+ };
290
+ // Assemble the camera modal
291
+ cameraContainer.appendChild(video);
292
+ cameraContainer.appendChild(canvas);
293
+ cameraContainer.appendChild(captureButton);
294
+ cameraContainer.appendChild(closeButton);
295
+ cameraContainer.appendChild(loadingIndicator);
296
+ modal.appendChild(cameraContainer);
297
+ document.body.appendChild(modal);
298
+ console.log("[WeWear VTO] Camera modal added to DOM");
299
+ // Start camera
300
+ startCamera(video, loadingIndicator, captureButton);
301
+ }
302
+
303
+ function showImageModal(imageUrl) {
304
+ // Remove any existing modals first
305
+ removeElements(`.${CSS_CLASSES.MODAL}`);
306
+ // Create modal container
307
+ const modal = document.createElement("div");
308
+ modal.className = CSS_CLASSES.MODAL;
309
+ modal.style.cssText = `
310
+ position: fixed;
311
+ top: 0;
312
+ left: 0;
313
+ width: 100%;
314
+ height: 100%;
315
+ background-color: rgba(0, 0, 0, 0.9);
316
+ display: flex;
317
+ align-items: center;
318
+ justify-content: center;
319
+ z-index: ${Z_INDEX.MODAL};
320
+ padding: 20px;
321
+ box-sizing: border-box;
322
+ `;
323
+ // Create image container
324
+ const imageContainer = document.createElement("div");
325
+ imageContainer.style.cssText = `
326
+ position: relative;
327
+ width: 100%;
328
+ max-width: 500px;
329
+ height: 70vh;
330
+ background-color: #000;
331
+ border-radius: 12px;
332
+ overflow: hidden;
333
+ display: flex;
334
+ flex-direction: column;
335
+ align-items: center;
336
+ justify-content: center;
337
+ `;
338
+ // Create image element
339
+ const image = document.createElement("img");
340
+ image.src = imageUrl;
341
+ image.alt = "Virtual Try-On Result";
342
+ image.style.cssText = `
343
+ width: 100%;
344
+ height: 100%;
345
+ object-fit: cover;
346
+ border-radius: 12px;
347
+ `;
348
+ // Create close button
349
+ const closeButton = document.createElement("button");
350
+ closeButton.innerHTML = "×";
351
+ closeButton.style.cssText = `
352
+ position: absolute;
353
+ top: 15px;
354
+ right: 15px;
355
+ width: 40px;
356
+ height: 40px;
357
+ border: none;
358
+ background-color: rgba(0, 0, 0, 0.7);
359
+ color: white;
360
+ font-size: 24px;
361
+ font-weight: bold;
362
+ cursor: pointer;
363
+ border-radius: 50%;
364
+ display: flex;
365
+ align-items: center;
366
+ justify-content: center;
367
+ `;
368
+ closeButton.onclick = () => {
369
+ modal.remove();
370
+ };
371
+ // Close on backdrop click
372
+ modal.onclick = (e) => {
373
+ if (e.target === modal) {
169
374
  modal.remove();
170
- });
171
- const modal = document.createElement("div");
172
- modal.className = CSS_CLASSES.MODAL;
173
- modal.setAttribute("role", "dialog");
174
- modal.setAttribute("aria-modal", "true");
175
- modal.setAttribute("aria-label", "Virtual Try-On Result");
176
- modal.style.cssText = `
177
- position: fixed;
178
- top: 0;
179
- left: 0;
180
- width: 100%;
181
- height: 100%;
182
- background: rgba(0, 0, 0, 0.8);
183
- display: flex;
184
- align-items: center;
185
- justify-content: center;
186
- z-index: ${Z_INDEX.MODAL};
187
- animation: fadeIn 0.3s ease;
188
- `;
189
- modal.innerHTML = `
190
- <div style="
191
- background: #ffffff;
192
- padding: 20px;
193
- border-radius: 12px;
194
- max-width: 90vw;
195
- max-height: 90vh;
196
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
197
- animation: slideIn 0.3s ease;
198
- ">
199
- <img
200
- src="${imageUrl}"
201
- alt="Virtual Try-On Result"
202
- style="
203
- max-width: 100%;
204
- max-height: 80vh;
205
- border-radius: 8px;
206
- display: block;
207
- "
208
- />
209
- <div style="
210
- text-align: right;
211
- margin-top: 15px;
212
- ">
213
- <button
214
- type="button"
215
- style="
216
- padding: 8px 16px;
217
- border: none;
218
- background: #000000;
219
- color: #ffffff;
220
- border-radius: 6px;
221
- cursor: pointer;
222
- font-size: 14px;
223
- font-weight: 500;
224
- transition: background-color 0.2s ease;
225
- "
226
- onmouseover="this.style.backgroundColor='#333333'"
227
- onmouseout="this.style.backgroundColor='#000000'"
228
- >
229
- Close
230
- </button>
231
- </div>
232
- </div>
233
- <style>
234
- @keyframes fadeIn {
235
- from { opacity: 0; }
236
- to { opacity: 1; }
237
- }
238
- @keyframes slideIn {
239
- from { transform: scale(0.9) translateY(20px); opacity: 0; }
240
- to { transform: scale(1) translateY(0); opacity: 1; }
241
375
  }
242
- </style>
243
- `;
244
- const closeButton = modal.querySelector("button");
245
- const modalContent = modal.querySelector("div");
246
- // Close on button click
247
- if (closeButton) {
248
- closeButton.onclick = () => modal.remove();
249
- }
250
- // Close on backdrop click
251
- modal.onclick = (e) => {
252
- if (e.target === modal) {
253
- modal.remove();
254
- }
255
- };
256
- // Prevent content clicks from closing modal
257
- if (modalContent) {
258
- modalContent.onclick = (e) => e.stopPropagation();
376
+ };
377
+ // Close on Escape key
378
+ const handleEscape = (e) => {
379
+ if (e.key === "Escape") {
380
+ modal.remove();
381
+ document.removeEventListener("keydown", handleEscape);
259
382
  }
260
- // Close on Escape key
261
- const handleEscape = (e) => {
262
- if (e.key === "Escape") {
263
- modal.remove();
264
- document.removeEventListener("keydown", handleEscape);
265
- }
383
+ };
384
+ document.addEventListener("keydown", handleEscape);
385
+ imageContainer.appendChild(image);
386
+ imageContainer.appendChild(closeButton);
387
+ modal.appendChild(imageContainer);
388
+ document.body.appendChild(modal);
389
+ }
390
+
391
+ function showReviewModal(imageBlob, callbacks) {
392
+ console.log("[WeWear VTO] Opening review modal...");
393
+ // Remove any existing modals first
394
+ removeElements(`.${CSS_CLASSES.MODAL}`);
395
+ // Create image URL for preview
396
+ const imageUrl = URL.createObjectURL(imageBlob);
397
+ // Create modal container
398
+ const modal = document.createElement("div");
399
+ modal.className = CSS_CLASSES.MODAL;
400
+ modal.style.cssText = `
401
+ position: fixed;
402
+ top: 0;
403
+ left: 0;
404
+ width: 100%;
405
+ height: 100%;
406
+ background-color: rgba(0, 0, 0, 0.95);
407
+ display: flex;
408
+ flex-direction: column;
409
+ align-items: center;
410
+ justify-content: center;
411
+ z-index: ${Z_INDEX.MODAL};
412
+ padding: 20px;
413
+ box-sizing: border-box;
414
+ `;
415
+ // Create review container
416
+ const reviewContainer = document.createElement("div");
417
+ reviewContainer.style.cssText = `
418
+ position: relative;
419
+ width: 100%;
420
+ max-width: 500px;
421
+ height: 70vh;
422
+ background-color: #000;
423
+ border-radius: 12px;
424
+ overflow: hidden;
425
+ display: flex;
426
+ flex-direction: column;
427
+ align-items: center;
428
+ justify-content: center;
429
+ `;
430
+ // Create image element
431
+ const image = document.createElement("img");
432
+ image.src = imageUrl;
433
+ image.alt = "Review your photo";
434
+ image.style.cssText = `
435
+ width: 100%;
436
+ height: 100%;
437
+ object-fit: cover;
438
+ border-radius: 12px;
439
+ `;
440
+ // Create close button
441
+ const closeButton = document.createElement("button");
442
+ closeButton.innerHTML = "×";
443
+ closeButton.style.cssText = `
444
+ position: absolute;
445
+ top: 15px;
446
+ right: 15px;
447
+ width: 40px;
448
+ height: 40px;
449
+ border: none;
450
+ background-color: rgba(0, 0, 0, 0.7);
451
+ color: white;
452
+ font-size: 24px;
453
+ font-weight: bold;
454
+ cursor: pointer;
455
+ border-radius: 50%;
456
+ display: flex;
457
+ align-items: center;
458
+ justify-content: center;
459
+ `;
460
+ // Create button container
461
+ const buttonContainer = document.createElement("div");
462
+ buttonContainer.style.cssText = `
463
+ position: absolute;
464
+ bottom: 20px;
465
+ left: 50%;
466
+ transform: translateX(-50%);
467
+ display: flex;
468
+ gap: 15px;
469
+ width: calc(100% - 40px);
470
+ max-width: 300px;
471
+ `;
472
+ // Create retake button
473
+ const retakeButton = document.createElement("button");
474
+ retakeButton.textContent = "Retake";
475
+ retakeButton.style.cssText = `
476
+ flex: 1;
477
+ padding: 12px 24px;
478
+ background-color: rgba(0, 0, 0, 0.7);
479
+ color: white;
480
+ border-radius: 8px;
481
+ border: none;
482
+ font-size: 16px;
483
+ font-weight: 600;
484
+ cursor: pointer;
485
+ transition: all 0.2s ease;
486
+ `;
487
+ // Create use photo button
488
+ const usePhotoButton = document.createElement("button");
489
+ usePhotoButton.textContent = "Use Photo";
490
+ usePhotoButton.style.cssText = `
491
+ flex: 1;
492
+ padding: 12px 24px;
493
+ background-color: rgba(0, 0, 0, 0.7);
494
+ color: white;
495
+ border-radius: 8px;
496
+ border: none;
497
+ font-size: 16px;
498
+ font-weight: 600;
499
+ cursor: pointer;
500
+ transition: all 0.2s ease;
501
+ `;
502
+ // Add event listeners
503
+ closeButton.onclick = () => {
504
+ URL.revokeObjectURL(imageUrl); // Clean up
505
+ modal.remove();
506
+ };
507
+ retakeButton.onclick = () => {
508
+ URL.revokeObjectURL(imageUrl); // Clean up
509
+ modal.remove();
510
+ callbacks.onRetake();
511
+ };
512
+ usePhotoButton.onclick = async () => {
513
+ URL.revokeObjectURL(imageUrl); // Clean up
514
+ modal.remove();
515
+ await callbacks.onAccept(imageBlob);
516
+ };
517
+ // Assemble the review modal
518
+ buttonContainer.appendChild(retakeButton);
519
+ buttonContainer.appendChild(usePhotoButton);
520
+ reviewContainer.appendChild(image);
521
+ reviewContainer.appendChild(closeButton);
522
+ reviewContainer.appendChild(buttonContainer);
523
+ modal.appendChild(reviewContainer);
524
+ document.body.appendChild(modal);
525
+ console.log("[WeWear VTO] Review modal added to DOM");
526
+ }
527
+
528
+ class VirtualTryOnWidget {
529
+ constructor(config = {}) {
530
+ this.config = {
531
+ baseUrl: config.baseUrl || DEFAULT_CONFIG.BASE_URL,
532
+ productPageSelector: config.productPageSelector || DEFAULT_CONFIG.PRODUCT_PAGE_SELECTOR,
533
+ gallerySelector: config.gallerySelector || DEFAULT_CONFIG.GALLERY_SELECTOR,
534
+ skuSelector: config.skuSelector || DEFAULT_CONFIG.SKU_SELECTOR,
535
+ buttonPosition: config.buttonPosition || DEFAULT_CONFIG.BUTTON_POSITION,
266
536
  };
267
- document.addEventListener("keydown", handleEscape);
268
- document.body.appendChild(modal);
269
537
  }
270
538
  /**
271
539
  * Initializes the virtual try-on widget on the current page
@@ -288,7 +556,7 @@ class VirtualTryOnWidget {
288
556
  container.style.position = "relative";
289
557
  }
290
558
  // Create and add the virtual try-on button
291
- const button = this.createButton(async () => {
559
+ const button = createButton(this.config.buttonPosition, async () => {
292
560
  await this.handleTryOnClick();
293
561
  });
294
562
  container.appendChild(button);
@@ -304,47 +572,95 @@ class VirtualTryOnWidget {
304
572
  */
305
573
  async handleTryOnClick() {
306
574
  var _a;
575
+ console.log("[WeWear VTO] Button clicked, starting try-on process...");
307
576
  try {
308
577
  // Get required data
309
- const ww_access_token = this.getCookie("ww_access_token");
310
- const ww_user_id = this.getCookie("ww_user_id");
578
+ const ww_access_token = getCookie("ww_access_token");
579
+ const ww_user_id = getCookie("ww_user_id");
311
580
  const skuElement = document.querySelector(this.config.skuSelector);
312
581
  const ww_product_id = (_a = skuElement === null || skuElement === void 0 ? void 0 : skuElement.textContent) === null || _a === void 0 ? void 0 : _a.trim();
582
+ console.log("[WeWear VTO] Retrieved data:", {
583
+ ww_user_id,
584
+ ww_product_id,
585
+ ww_access_token,
586
+ });
313
587
  // Validate required data
314
588
  if (!ww_user_id) {
315
589
  console.warn("[WeWear VTO] Missing required cookie: ww_user_id");
316
- this.showError("Please sign in to use virtual try-on");
317
590
  return;
318
591
  }
319
592
  if (!ww_product_id) {
320
593
  console.warn("[WeWear VTO] Product SKU not found:", this.config.skuSelector);
321
- this.showError("Product information not available");
322
594
  return;
323
595
  }
324
- // Show loading state (could be implemented)
325
- console.log("[WeWear VTO] Processing virtual try-on request...");
326
- // Make API call
327
- const result = await this.callVirtualTryOnApi(ww_access_token, ww_user_id, ww_product_id);
328
- if (result === null || result === void 0 ? void 0 : result.imageUrl) {
329
- this.showImageModal(result.imageUrl);
330
- }
331
- else {
332
- this.showError("Virtual try-on service is currently unavailable. Please try again later.");
596
+ if (!ww_access_token) {
597
+ console.warn("[WeWear VTO] Missing required cookie: ww_access_token");
598
+ return;
333
599
  }
600
+ // Show camera capture modal
601
+ this.showCameraModalWithCallbacks(ww_access_token, ww_user_id, ww_product_id);
334
602
  }
335
603
  catch (error) {
336
604
  console.error("[WeWear VTO] Try-on request failed:", error);
337
- this.showError("An unexpected error occurred. Please try again.");
338
605
  }
339
606
  }
340
607
  /**
341
- * Shows an error message to the user
342
- * @param message - Error message to display
608
+ * Shows camera modal with appropriate callbacks
609
+ * @private
610
+ */
611
+ showCameraModalWithCallbacks(ww_access_token, ww_user_id, ww_product_id) {
612
+ const callbacks = {
613
+ onCapture: async (video, canvas) => {
614
+ try {
615
+ // Capture image from video
616
+ const imageBlob = await captureImageFromVideo(video, canvas);
617
+ // Stop camera and show review modal
618
+ stopCamera(video);
619
+ // Show review modal instead of immediately processing
620
+ this.showReviewModalWithCallbacks(imageBlob, ww_access_token, ww_user_id, ww_product_id);
621
+ }
622
+ catch (error) {
623
+ console.error("[WeWear VTO] Image capture error:", error);
624
+ }
625
+ },
626
+ };
627
+ showCameraModal(callbacks);
628
+ }
629
+ /**
630
+ * Shows review modal with appropriate callbacks
343
631
  * @private
344
632
  */
345
- showError(message) {
346
- // Simple alert for now - could be enhanced with a custom modal
347
- alert(`WeWear Virtual Try-On: ${message}`);
633
+ showReviewModalWithCallbacks(imageBlob, ww_access_token, ww_user_id, ww_product_id) {
634
+ const callbacks = {
635
+ onRetake: () => {
636
+ // Reopen camera modal
637
+ this.showCameraModalWithCallbacks(ww_access_token, ww_user_id, ww_product_id);
638
+ },
639
+ onAccept: async (acceptedImageBlob) => {
640
+ await this.processAcceptedImage(acceptedImageBlob, ww_access_token, ww_user_id, ww_product_id);
641
+ },
642
+ };
643
+ showReviewModal(imageBlob, callbacks);
644
+ }
645
+ /**
646
+ * Processes the accepted image by calling the API
647
+ * @private
648
+ */
649
+ async processAcceptedImage(imageBlob, ww_access_token, ww_user_id, ww_product_id) {
650
+ try {
651
+ console.log("[WeWear VTO] Processing accepted image...");
652
+ // Call the API with the accepted image
653
+ const result = await callVirtualTryOnApi(this.config.baseUrl, ww_access_token, ww_user_id, ww_product_id, imageBlob);
654
+ if (result === null || result === void 0 ? void 0 : result.imageUrl) {
655
+ showImageModal(result.imageUrl);
656
+ }
657
+ else {
658
+ console.error("[WeWear VTO] Invalid API response:", result);
659
+ }
660
+ }
661
+ catch (error) {
662
+ console.error("[WeWear VTO] Error processing accepted image:", error);
663
+ }
348
664
  }
349
665
  /**
350
666
  * Destroys the widget and cleans up resources
@@ -352,15 +668,9 @@ class VirtualTryOnWidget {
352
668
  destroy() {
353
669
  try {
354
670
  // Remove all buttons
355
- const buttons = document.querySelectorAll(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
356
- buttons.forEach((button) => {
357
- button.remove();
358
- });
671
+ removeElements(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
359
672
  // Remove all modals
360
- const modals = document.querySelectorAll(`.${CSS_CLASSES.MODAL}`);
361
- modals.forEach((modal) => {
362
- modal.remove();
363
- });
673
+ removeElements(`.${CSS_CLASSES.MODAL}`);
364
674
  console.log("[WeWear VTO] Widget destroyed successfully");
365
675
  }
366
676
  catch (error) {
@@ -369,17 +679,7 @@ class VirtualTryOnWidget {
369
679
  }
370
680
  }
371
681
 
372
- /**
373
- * WeWear Virtual Try-On Widget Auto-Installer
374
- *
375
- * Provides automatic initialization and management of the virtual try-on widget.
376
- * Handles DOM ready states and widget lifecycle management.
377
- */
378
682
  let widgetInstance = null;
379
- /**
380
- * Initializes the virtual try-on widget with optional configuration
381
- * @param config - Widget configuration options
382
- */
383
683
  function initVirtualTryOn(config) {
384
684
  try {
385
685
  // Clean up any existing instance
@@ -407,9 +707,6 @@ function initVirtualTryOn(config) {
407
707
  console.error("[WeWear VTO] Initialization error:", error);
408
708
  }
409
709
  }
410
- /**
411
- * Destroys the current widget instance and cleans up resources
412
- */
413
710
  function destroyVirtualTryOn() {
414
711
  try {
415
712
  if (widgetInstance) {
@@ -422,10 +719,12 @@ function destroyVirtualTryOn() {
422
719
  console.error("[WeWear VTO] Error destroying widget:", error);
423
720
  }
424
721
  }
425
- // Auto-initialize if window object exists (browser environment)
722
+ function getWidgetInstance() {
723
+ return widgetInstance;
724
+ }
426
725
  if (typeof window !== "undefined") {
427
726
  initVirtualTryOn();
428
727
  }
429
728
 
430
- export { VirtualTryOnWidget, destroyVirtualTryOn, initVirtualTryOn };
729
+ export { VirtualTryOnWidget, destroyVirtualTryOn, getWidgetInstance, initVirtualTryOn };
431
730
  //# sourceMappingURL=index.esm.js.map