@wewear/virtual-try-on 1.0.0 → 1.1.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.js CHANGED
@@ -60,37 +60,6 @@
60
60
  * Makes API call to virtual try-on service
61
61
  * @param ww_access_token - Optional authentication token
62
62
  * @param ww_user_id - User identifier
63
- * @param ww_product_id - Product identifier
64
- * @returns Promise with virtual try-on result or null if failed
65
- */
66
- async callVirtualTryOnApi(ww_access_token, ww_user_id, ww_product_id) {
67
- try {
68
- const response = await fetch(`${this.config.baseUrl}/api/virtual-try-on`, {
69
- method: "POST",
70
- headers: {
71
- "Content-Type": "application/json",
72
- Accept: "application/json",
73
- },
74
- body: JSON.stringify({
75
- ww_access_token,
76
- ww_user_id,
77
- ww_product_id,
78
- }),
79
- });
80
- if (!response.ok) {
81
- throw new Error(`API responded with status ${response.status}: ${response.statusText}`);
82
- }
83
- const result = await response.json();
84
- if (!result.imageUrl) {
85
- throw new Error("API response missing imageUrl");
86
- }
87
- return result;
88
- }
89
- catch (error) {
90
- console.error("[WeWear VTO] API call failed:", error);
91
- return null;
92
- }
93
- }
94
63
  /**
95
64
  * Creates the virtual try-on button element
96
65
  * @param onClick - Click handler function
@@ -174,95 +143,73 @@
174
143
  existingModals.forEach((modal) => {
175
144
  modal.remove();
176
145
  });
146
+ // Create modal container
177
147
  const modal = document.createElement("div");
178
148
  modal.className = CSS_CLASSES.MODAL;
179
- modal.setAttribute("role", "dialog");
180
- modal.setAttribute("aria-modal", "true");
181
- modal.setAttribute("aria-label", "Virtual Try-On Result");
182
149
  modal.style.cssText = `
183
150
  position: fixed;
184
151
  top: 0;
185
152
  left: 0;
186
153
  width: 100%;
187
154
  height: 100%;
188
- background: rgba(0, 0, 0, 0.8);
155
+ background-color: rgba(0, 0, 0, 0.9);
189
156
  display: flex;
190
157
  align-items: center;
191
158
  justify-content: center;
192
159
  z-index: ${Z_INDEX.MODAL};
193
- animation: fadeIn 0.3s ease;
160
+ padding: 20px;
161
+ box-sizing: border-box;
194
162
  `;
195
- modal.innerHTML = `
196
- <div style="
197
- background: #ffffff;
198
- padding: 20px;
199
- border-radius: 12px;
200
- max-width: 90vw;
201
- max-height: 90vh;
202
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
203
- animation: slideIn 0.3s ease;
204
- ">
205
- <img
206
- src="${imageUrl}"
207
- alt="Virtual Try-On Result"
208
- style="
209
- max-width: 100%;
210
- max-height: 80vh;
211
- border-radius: 8px;
212
- display: block;
213
- "
214
- />
215
- <div style="
216
- text-align: right;
217
- margin-top: 15px;
218
- ">
219
- <button
220
- type="button"
221
- style="
222
- padding: 8px 16px;
223
- border: none;
224
- background: #000000;
225
- color: #ffffff;
226
- border-radius: 6px;
227
- cursor: pointer;
228
- font-size: 14px;
229
- font-weight: 500;
230
- transition: background-color 0.2s ease;
231
- "
232
- onmouseover="this.style.backgroundColor='#333333'"
233
- onmouseout="this.style.backgroundColor='#000000'"
234
- >
235
- Close
236
- </button>
237
- </div>
238
- </div>
239
- <style>
240
- @keyframes fadeIn {
241
- from { opacity: 0; }
242
- to { opacity: 1; }
243
- }
244
- @keyframes slideIn {
245
- from { transform: scale(0.9) translateY(20px); opacity: 0; }
246
- to { transform: scale(1) translateY(0); opacity: 1; }
247
- }
248
- </style>
163
+ // Create image container
164
+ const imageContainer = document.createElement("div");
165
+ imageContainer.style.cssText = `
166
+ position: relative;
167
+ max-width: 90%;
168
+ max-height: 90%;
169
+ background-color: white;
170
+ border-radius: 8px;
171
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
172
+ overflow: hidden;
249
173
  `;
250
- const closeButton = modal.querySelector("button");
251
- const modalContent = modal.querySelector("div");
252
- // Close on button click
253
- if (closeButton) {
254
- closeButton.onclick = () => modal.remove();
255
- }
174
+ // Create image element
175
+ const image = document.createElement("img");
176
+ image.src = imageUrl;
177
+ image.alt = "Virtual Try-On Result";
178
+ image.style.cssText = `
179
+ width: 100%;
180
+ height: 100%;
181
+ object-fit: contain;
182
+ display: block;
183
+ `;
184
+ // Create close button
185
+ const closeButton = document.createElement("button");
186
+ closeButton.innerHTML = "×";
187
+ closeButton.style.cssText = `
188
+ position: absolute;
189
+ top: 10px;
190
+ right: 10px;
191
+ width: 30px;
192
+ height: 30px;
193
+ border: none;
194
+ background-color: rgba(0, 0, 0, 0.7);
195
+ color: white;
196
+ font-size: 20px;
197
+ font-weight: bold;
198
+ cursor: pointer;
199
+ border-radius: 50%;
200
+ display: flex;
201
+ align-items: center;
202
+ justify-content: center;
203
+ `;
204
+ closeButton.onclick = () => {
205
+ modal.remove();
206
+ };
256
207
  // Close on backdrop click
257
208
  modal.onclick = (e) => {
258
209
  if (e.target === modal) {
259
210
  modal.remove();
260
211
  }
261
212
  };
262
- // Prevent content clicks from closing modal
263
- if (modalContent) {
264
- modalContent.onclick = (e) => e.stopPropagation();
265
- }
266
213
  // Close on Escape key
267
214
  const handleEscape = (e) => {
268
215
  if (e.key === "Escape") {
@@ -271,9 +218,146 @@
271
218
  }
272
219
  };
273
220
  document.addEventListener("keydown", handleEscape);
221
+ imageContainer.appendChild(image);
222
+ imageContainer.appendChild(closeButton);
223
+ modal.appendChild(imageContainer);
274
224
  document.body.appendChild(modal);
275
225
  }
276
226
  /**
227
+ * Shows the camera capture modal
228
+ * @param ww_access_token - Access token for API
229
+ * @param ww_user_id - User identifier
230
+ * @param ww_product_id - Product identifier
231
+ */
232
+ showCameraModal(ww_access_token, ww_user_id, ww_product_id) {
233
+ console.log("[WeWear VTO] Opening camera modal...");
234
+ // Remove any existing modals first
235
+ const existingModals = document.querySelectorAll(`.${CSS_CLASSES.MODAL}`);
236
+ existingModals.forEach((modal) => {
237
+ modal.remove();
238
+ });
239
+ // Create modal container
240
+ const modal = document.createElement("div");
241
+ modal.className = CSS_CLASSES.MODAL;
242
+ modal.style.cssText = `
243
+ position: fixed;
244
+ top: 0;
245
+ left: 0;
246
+ width: 100%;
247
+ height: 100%;
248
+ background-color: rgba(0, 0, 0, 0.95);
249
+ display: flex;
250
+ flex-direction: column;
251
+ align-items: center;
252
+ justify-content: center;
253
+ z-index: ${Z_INDEX.MODAL};
254
+ padding: 20px;
255
+ box-sizing: border-box;
256
+ `;
257
+ // Create camera container
258
+ const cameraContainer = document.createElement("div");
259
+ cameraContainer.style.cssText = `
260
+ position: relative;
261
+ width: 100%;
262
+ max-width: 500px;
263
+ height: 70vh;
264
+ background-color: #000;
265
+ border-radius: 12px;
266
+ overflow: hidden;
267
+ display: flex;
268
+ flex-direction: column;
269
+ align-items: center;
270
+ justify-content: center;
271
+ `;
272
+ // Create video element
273
+ const video = document.createElement("video");
274
+ video.autoplay = true;
275
+ video.playsInline = true;
276
+ video.muted = true;
277
+ video.style.cssText = `
278
+ width: 100%;
279
+ height: 100%;
280
+ object-fit: cover;
281
+ border-radius: 12px;
282
+ `;
283
+ // Create canvas for capturing
284
+ const canvas = document.createElement("canvas");
285
+ canvas.style.display = "none";
286
+ // Create capture button
287
+ const captureButton = document.createElement("button");
288
+ captureButton.innerHTML = `
289
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="white">
290
+ <circle cx="12" cy="12" r="10" stroke="white" stroke-width="2" fill="none"/>
291
+ <circle cx="12" cy="12" r="6" fill="white"/>
292
+ </svg>
293
+ `;
294
+ captureButton.style.cssText = `
295
+ position: absolute;
296
+ bottom: 20px;
297
+ left: 50%;
298
+ transform: translateX(-50%);
299
+ width: 60px;
300
+ height: 60px;
301
+ border: 3px solid white;
302
+ background-color: transparent;
303
+ border-radius: 50%;
304
+ cursor: pointer;
305
+ display: none;
306
+ align-items: center;
307
+ justify-content: center;
308
+ transition: all 0.2s ease;
309
+ `;
310
+ // Create close button
311
+ const closeButton = document.createElement("button");
312
+ closeButton.innerHTML = "×";
313
+ closeButton.style.cssText = `
314
+ position: absolute;
315
+ top: 15px;
316
+ right: 15px;
317
+ width: 40px;
318
+ height: 40px;
319
+ border: none;
320
+ background-color: rgba(0, 0, 0, 0.7);
321
+ color: white;
322
+ font-size: 24px;
323
+ font-weight: bold;
324
+ cursor: pointer;
325
+ border-radius: 50%;
326
+ display: flex;
327
+ align-items: center;
328
+ justify-content: center;
329
+ `;
330
+ // Create loading indicator
331
+ const loadingIndicator = document.createElement("div");
332
+ loadingIndicator.innerHTML = "Initializing camera...";
333
+ loadingIndicator.style.cssText = `
334
+ color: white;
335
+ font-size: 16px;
336
+ position: absolute;
337
+ top: 50%;
338
+ left: 50%;
339
+ transform: translate(-50%, -50%);
340
+ `;
341
+ // Add event listeners
342
+ closeButton.onclick = () => {
343
+ this.stopCamera(video);
344
+ modal.remove();
345
+ };
346
+ captureButton.onclick = () => {
347
+ this.captureImage(video, canvas, ww_access_token, ww_user_id, ww_product_id, modal);
348
+ };
349
+ // Assemble the camera modal
350
+ cameraContainer.appendChild(video);
351
+ cameraContainer.appendChild(canvas);
352
+ cameraContainer.appendChild(captureButton);
353
+ cameraContainer.appendChild(closeButton);
354
+ cameraContainer.appendChild(loadingIndicator);
355
+ modal.appendChild(cameraContainer);
356
+ document.body.appendChild(modal);
357
+ console.log("[WeWear VTO] Camera modal added to DOM");
358
+ // Start camera
359
+ this.startCamera(video, loadingIndicator, captureButton);
360
+ } /**
277
361
  * Initializes the virtual try-on widget on the current page
278
362
  * @returns Promise that resolves when initialization is complete
279
363
  */
@@ -310,12 +394,18 @@
310
394
  */
311
395
  async handleTryOnClick() {
312
396
  var _a;
397
+ console.log("[WeWear VTO] Button clicked, starting try-on process...");
313
398
  try {
314
399
  // Get required data
315
400
  const ww_access_token = this.getCookie("ww_access_token");
316
401
  const ww_user_id = this.getCookie("ww_user_id");
317
402
  const skuElement = document.querySelector(this.config.skuSelector);
318
403
  const ww_product_id = (_a = skuElement === null || skuElement === void 0 ? void 0 : skuElement.textContent) === null || _a === void 0 ? void 0 : _a.trim();
404
+ console.log("[WeWear VTO] Retrieved data:", {
405
+ ww_user_id,
406
+ ww_product_id,
407
+ ww_access_token: !!ww_access_token,
408
+ });
319
409
  // Validate required data
320
410
  if (!ww_user_id) {
321
411
  console.warn("[WeWear VTO] Missing required cookie: ww_user_id");
@@ -327,16 +417,8 @@
327
417
  this.showError("Product information not available");
328
418
  return;
329
419
  }
330
- // Show loading state (could be implemented)
331
- console.log("[WeWear VTO] Processing virtual try-on request...");
332
- // Make API call
333
- const result = await this.callVirtualTryOnApi(ww_access_token, ww_user_id, ww_product_id);
334
- if (result === null || result === void 0 ? void 0 : result.imageUrl) {
335
- this.showImageModal(result.imageUrl);
336
- }
337
- else {
338
- this.showError("Virtual try-on service is currently unavailable. Please try again later.");
339
- }
420
+ // Show camera capture modal
421
+ this.showCameraModal(ww_access_token, ww_user_id, ww_product_id);
340
422
  }
341
423
  catch (error) {
342
424
  console.error("[WeWear VTO] Try-on request failed:", error);
@@ -352,6 +434,316 @@
352
434
  // Simple alert for now - could be enhanced with a custom modal
353
435
  alert(`WeWear Virtual Try-On: ${message}`);
354
436
  }
437
+ /**
438
+ * Starts the camera stream
439
+ * @param video - Video element to display camera stream
440
+ * @param loadingIndicator - Loading indicator element
441
+ * @param captureButton - Capture button element
442
+ */
443
+ async startCamera(video, loadingIndicator, captureButton) {
444
+ console.log("[WeWear VTO] Starting camera...");
445
+ try {
446
+ const constraints = {
447
+ video: {
448
+ width: { ideal: 1280 },
449
+ height: { ideal: 720 },
450
+ facingMode: "user", // Front-facing camera
451
+ },
452
+ audio: false,
453
+ };
454
+ console.log("[WeWear VTO] Requesting camera access...");
455
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
456
+ video.srcObject = stream;
457
+ video.onloadedmetadata = () => {
458
+ console.log("[WeWear VTO] Camera stream loaded successfully");
459
+ loadingIndicator.style.display = "none";
460
+ captureButton.style.display = "flex";
461
+ };
462
+ }
463
+ catch (error) {
464
+ console.error("[WeWear VTO] Camera access error:", error);
465
+ loadingIndicator.innerHTML =
466
+ "Camera access denied. Please allow camera permissions.";
467
+ }
468
+ }
469
+ /**
470
+ * Stops the camera stream
471
+ * @param video - Video element with camera stream
472
+ */
473
+ stopCamera(video) {
474
+ const stream = video.srcObject;
475
+ if (stream) {
476
+ const tracks = stream.getTracks();
477
+ tracks.forEach((track) => {
478
+ track.stop();
479
+ });
480
+ video.srcObject = null;
481
+ }
482
+ }
483
+ /**
484
+ * Captures an image from the video stream
485
+ * @param video - Video element with camera stream
486
+ * @param canvas - Canvas element for capturing
487
+ * @param ww_access_token - Access token for API
488
+ * @param ww_user_id - User identifier
489
+ * @param ww_product_id - Product identifier
490
+ * @param modal - Modal element to close after processing
491
+ */
492
+ async captureImage(video, canvas, ww_access_token, ww_user_id, ww_product_id, modal) {
493
+ try {
494
+ // Set canvas dimensions to match video
495
+ canvas.width = video.videoWidth;
496
+ canvas.height = video.videoHeight;
497
+ // Draw video frame to canvas
498
+ const ctx = canvas.getContext("2d");
499
+ if (!ctx) {
500
+ throw new Error("Could not get canvas context");
501
+ }
502
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
503
+ // Convert canvas to blob
504
+ const blob = await new Promise((resolve, reject) => {
505
+ canvas.toBlob((blob) => {
506
+ if (blob) {
507
+ resolve(blob);
508
+ }
509
+ else {
510
+ reject(new Error("Failed to create blob from canvas"));
511
+ }
512
+ }, "image/jpeg", 0.8);
513
+ });
514
+ // Stop camera and close camera modal
515
+ this.stopCamera(video);
516
+ modal.remove();
517
+ // Show review modal instead of immediately processing
518
+ this.showReviewModal(blob, ww_access_token, ww_user_id, ww_product_id);
519
+ }
520
+ catch (error) {
521
+ console.error("[WeWear VTO] Image capture error:", error);
522
+ this.showError("Failed to capture image. Please try again.");
523
+ }
524
+ }
525
+ /**
526
+ * Shows the review modal for captured image
527
+ * @param imageBlob - Captured image blob
528
+ * @param ww_access_token - Access token for API
529
+ * @param ww_user_id - User identifier
530
+ * @param ww_product_id - Product identifier
531
+ */
532
+ showReviewModal(imageBlob, ww_access_token, ww_user_id, ww_product_id) {
533
+ console.log("[WeWear VTO] Opening review modal...");
534
+ // Remove any existing modals first
535
+ const existingModals = document.querySelectorAll(`.${CSS_CLASSES.MODAL}`);
536
+ existingModals.forEach((modal) => {
537
+ modal.remove();
538
+ });
539
+ // Create image URL for preview
540
+ const imageUrl = URL.createObjectURL(imageBlob);
541
+ // Create modal container (same as camera modal)
542
+ const modal = document.createElement("div");
543
+ modal.className = CSS_CLASSES.MODAL;
544
+ modal.style.cssText = `
545
+ position: fixed;
546
+ top: 0;
547
+ left: 0;
548
+ width: 100%;
549
+ height: 100%;
550
+ background-color: rgba(0, 0, 0, 0.95);
551
+ display: flex;
552
+ flex-direction: column;
553
+ align-items: center;
554
+ justify-content: center;
555
+ z-index: ${Z_INDEX.MODAL};
556
+ padding: 20px;
557
+ box-sizing: border-box;
558
+ `;
559
+ // Create review container (similar to camera container)
560
+ const reviewContainer = document.createElement("div");
561
+ reviewContainer.style.cssText = `
562
+ position: relative;
563
+ width: 100%;
564
+ max-width: 500px;
565
+ height: 70vh;
566
+ background-color: #000;
567
+ border-radius: 12px;
568
+ overflow: hidden;
569
+ display: flex;
570
+ flex-direction: column;
571
+ align-items: center;
572
+ justify-content: center;
573
+ `;
574
+ // Create image element (replaces video element)
575
+ const image = document.createElement("img");
576
+ image.src = imageUrl;
577
+ image.alt = "Review your photo";
578
+ image.style.cssText = `
579
+ width: 100%;
580
+ height: 100%;
581
+ object-fit: cover;
582
+ border-radius: 12px;
583
+ `;
584
+ // Create close button (same as camera modal)
585
+ const closeButton = document.createElement("button");
586
+ closeButton.innerHTML = "×";
587
+ closeButton.style.cssText = `
588
+ position: absolute;
589
+ top: 15px;
590
+ right: 15px;
591
+ width: 40px;
592
+ height: 40px;
593
+ border: none;
594
+ background-color: rgba(0, 0, 0, 0.7);
595
+ color: white;
596
+ font-size: 24px;
597
+ font-weight: bold;
598
+ cursor: pointer;
599
+ border-radius: 50%;
600
+ display: flex;
601
+ align-items: center;
602
+ justify-content: center;
603
+ `;
604
+ // Create button container positioned at bottom (similar to capture button position)
605
+ const buttonContainer = document.createElement("div");
606
+ buttonContainer.style.cssText = `
607
+ position: absolute;
608
+ bottom: 20px;
609
+ left: 50%;
610
+ transform: translateX(-50%);
611
+ display: flex;
612
+ gap: 15px;
613
+ width: calc(100% - 40px);
614
+ max-width: 300px;
615
+ `;
616
+ // Create retake button
617
+ const retakeButton = document.createElement("button");
618
+ retakeButton.textContent = "Retake";
619
+ retakeButton.style.cssText = `
620
+ flex: 1;
621
+ padding: 12px 24px;
622
+ background-color: transparent;
623
+ color: white;
624
+ border: 2px solid white;
625
+ border-radius: 8px;
626
+ font-size: 16px;
627
+ font-weight: 600;
628
+ cursor: pointer;
629
+ transition: all 0.2s ease;
630
+ `;
631
+ // Create use photo button
632
+ const usePhotoButton = document.createElement("button");
633
+ usePhotoButton.textContent = "Use Photo";
634
+ usePhotoButton.style.cssText = `
635
+ flex: 1;
636
+ padding: 12px 24px;
637
+ background-color: white;
638
+ color: black;
639
+ border: 2px solid white;
640
+ border-radius: 8px;
641
+ font-size: 16px;
642
+ font-weight: 600;
643
+ cursor: pointer;
644
+ transition: all 0.2s ease;
645
+ `;
646
+ // Add hover effects
647
+ retakeButton.addEventListener("mouseenter", () => {
648
+ retakeButton.style.backgroundColor = "white";
649
+ retakeButton.style.color = "black";
650
+ });
651
+ retakeButton.addEventListener("mouseleave", () => {
652
+ retakeButton.style.backgroundColor = "transparent";
653
+ retakeButton.style.color = "white";
654
+ });
655
+ usePhotoButton.addEventListener("mouseenter", () => {
656
+ usePhotoButton.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
657
+ });
658
+ usePhotoButton.addEventListener("mouseleave", () => {
659
+ usePhotoButton.style.backgroundColor = "white";
660
+ });
661
+ // Add event listeners
662
+ closeButton.onclick = () => {
663
+ URL.revokeObjectURL(imageUrl); // Clean up
664
+ modal.remove();
665
+ };
666
+ retakeButton.onclick = () => {
667
+ URL.revokeObjectURL(imageUrl); // Clean up
668
+ modal.remove();
669
+ // Reopen camera modal
670
+ this.showCameraModal(ww_access_token, ww_user_id, ww_product_id);
671
+ };
672
+ usePhotoButton.onclick = async () => {
673
+ URL.revokeObjectURL(imageUrl); // Clean up
674
+ modal.remove();
675
+ // Process the image
676
+ await this.processAcceptedImage(imageBlob, ww_access_token, ww_user_id, ww_product_id);
677
+ };
678
+ // Assemble the review modal (similar to camera modal)
679
+ buttonContainer.appendChild(retakeButton);
680
+ buttonContainer.appendChild(usePhotoButton);
681
+ reviewContainer.appendChild(image);
682
+ reviewContainer.appendChild(closeButton);
683
+ reviewContainer.appendChild(buttonContainer);
684
+ modal.appendChild(reviewContainer);
685
+ document.body.appendChild(modal);
686
+ console.log("[WeWear VTO] Review modal added to DOM");
687
+ }
688
+ /**
689
+ * Processes the accepted image by calling the API
690
+ * @param imageBlob - Accepted image blob
691
+ * @param ww_access_token - Access token for API
692
+ * @param ww_user_id - User identifier
693
+ * @param ww_product_id - Product identifier
694
+ */
695
+ async processAcceptedImage(imageBlob, ww_access_token, ww_user_id, ww_product_id) {
696
+ try {
697
+ console.log("[WeWear VTO] Processing accepted image...");
698
+ // Call the API with the accepted image
699
+ const result = await this.callVirtualTryOnApiWithImage(ww_access_token, ww_user_id, ww_product_id, imageBlob);
700
+ if (result === null || result === void 0 ? void 0 : result.imageUrl) {
701
+ this.showImageModal(result.imageUrl);
702
+ }
703
+ else {
704
+ console.error("[WeWear VTO] Invalid API response:", result);
705
+ }
706
+ }
707
+ catch (error) {
708
+ console.error("[WeWear VTO] Error processing accepted image:", error);
709
+ this.showError("Failed to process image. Please try again.");
710
+ }
711
+ }
712
+ /**
713
+ * Makes API call to virtual try-on service with captured image
714
+ * @param ww_access_token - Optional authentication token
715
+ * @param ww_user_id - User identifier
716
+ * @param ww_product_id - Product identifier
717
+ * @param imageBlob - Captured image blob
718
+ * @returns Promise with virtual try-on result or null if failed
719
+ */
720
+ async callVirtualTryOnApiWithImage(ww_access_token, ww_user_id, ww_product_id, imageBlob) {
721
+ try {
722
+ // Create form data for multipart upload
723
+ const formData = new FormData();
724
+ formData.append("ww_user_id", ww_user_id);
725
+ formData.append("ww_product_id", ww_product_id);
726
+ formData.append("image", imageBlob, "captured-image.jpg");
727
+ if (ww_access_token) {
728
+ formData.append("ww_access_token", ww_access_token);
729
+ }
730
+ const response = await fetch(`${this.config.baseUrl}/api/virtual-try-on`, {
731
+ method: "POST",
732
+ body: formData,
733
+ });
734
+ if (!response.ok) {
735
+ console.error("[WeWear VTO] API request failed:", response.status, response.statusText);
736
+ return null;
737
+ }
738
+ const result = await response.json();
739
+ console.log("[WeWear VTO] API response:", result);
740
+ return result;
741
+ }
742
+ catch (error) {
743
+ console.error("[WeWear VTO] API call failed:", error);
744
+ return null;
745
+ }
746
+ }
355
747
  /**
356
748
  * Destroys the widget and cleans up resources
357
749
  */