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