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