@akinon/pz-virtual-try-on 1.118.0-rc.7 → 2.0.0-beta.12
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/CHANGELOG.md +0 -73
- package/README.md +7 -403
- package/package.json +3 -4
- package/src/hooks/use-image-cropper.ts +8 -58
- package/src/hooks/use-virtual-try-on-async.ts +0 -1
- package/src/hooks/use-virtual-try-on.ts +4 -30
- package/src/index.ts +1 -26
- package/src/types/index.ts +0 -32
- package/src/utils/error-mapping.ts +1 -3
- package/src/utils/index.ts +0 -115
- package/src/views/basket-async-modal.tsx +2 -7
- package/src/views/main.tsx +1 -15
- package/src/views/virtual-try-on-upload-modal.tsx +44 -62
- package/src/components/barcode-scanner.tsx +0 -422
- package/src/data/barcode-endpoints.ts +0 -34
- package/src/hooks/use-barcode-search.ts +0 -172
- package/src/types/barcode.ts +0 -308
- package/src/views/barcode-scanner-button.tsx +0 -63
- package/src/views/barcode-scanner-modal.tsx +0 -632
- package/src/views/barcode-scanner-plugin.tsx +0 -232
package/CHANGELOG.md
CHANGED
|
@@ -1,78 +1,5 @@
|
|
|
1
1
|
# @akinon/pz-virtual-try-on
|
|
2
2
|
|
|
3
|
-
## 1.118.0-rc.7
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- @akinon/next@1.118.0-rc.7
|
|
8
|
-
|
|
9
|
-
## 1.118.0-rc.6
|
|
10
|
-
|
|
11
|
-
### Patch Changes
|
|
12
|
-
|
|
13
|
-
- @akinon/next@1.118.0-rc.6
|
|
14
|
-
|
|
15
|
-
## 1.118.0-rc.5
|
|
16
|
-
|
|
17
|
-
### Patch Changes
|
|
18
|
-
|
|
19
|
-
- Updated dependencies [aef81c5d]
|
|
20
|
-
- Updated dependencies [49c82e1a]
|
|
21
|
-
- @akinon/next@1.118.0-rc.5
|
|
22
|
-
|
|
23
|
-
## 1.118.0-rc.4
|
|
24
|
-
|
|
25
|
-
### Patch Changes
|
|
26
|
-
|
|
27
|
-
- Updated dependencies [0754c835]
|
|
28
|
-
- @akinon/next@1.118.0-rc.4
|
|
29
|
-
|
|
30
|
-
## 1.118.0-rc.3
|
|
31
|
-
|
|
32
|
-
### Patch Changes
|
|
33
|
-
|
|
34
|
-
- Updated dependencies [94a86fcc]
|
|
35
|
-
- @akinon/next@1.118.0-rc.3
|
|
36
|
-
|
|
37
|
-
## 1.118.0-rc.2
|
|
38
|
-
|
|
39
|
-
### Patch Changes
|
|
40
|
-
|
|
41
|
-
- Updated dependencies [8a7fd0f4]
|
|
42
|
-
- @akinon/next@1.118.0-rc.2
|
|
43
|
-
|
|
44
|
-
## 1.118.0-rc.1
|
|
45
|
-
|
|
46
|
-
### Patch Changes
|
|
47
|
-
|
|
48
|
-
- @akinon/next@1.118.0-rc.1
|
|
49
|
-
|
|
50
|
-
## 1.118.0-rc.0
|
|
51
|
-
|
|
52
|
-
### Minor Changes
|
|
53
|
-
|
|
54
|
-
- 36143125: ZERO-3987: Add barcode scanner functionality with modal and button
|
|
55
|
-
- 5fb34b5c: ZERO-3955: Add aspect ratio validation and error handling for image cropping
|
|
56
|
-
- 187208d2: ZERO-3913 :Implement closeCrop functionality and enhance modal behavior in virtual try-on feature
|
|
57
|
-
|
|
58
|
-
### Patch Changes
|
|
59
|
-
|
|
60
|
-
- Updated dependencies [d2c0e759]
|
|
61
|
-
- Updated dependencies [b55acb76]
|
|
62
|
-
- Updated dependencies [36143125]
|
|
63
|
-
- Updated dependencies [f7e0f646]
|
|
64
|
-
- Updated dependencies [143be2b9]
|
|
65
|
-
- Updated dependencies [9f8cd3bc]
|
|
66
|
-
- Updated dependencies [729fe756]
|
|
67
|
-
- Updated dependencies [d99a6a7d]
|
|
68
|
-
- Updated dependencies [d7e5178b]
|
|
69
|
-
- Updated dependencies [591e345e]
|
|
70
|
-
- Updated dependencies [4de5303c]
|
|
71
|
-
- Updated dependencies [b59fdd1c]
|
|
72
|
-
- Updated dependencies [95b139dc]
|
|
73
|
-
- Updated dependencies [3909d322]
|
|
74
|
-
- @akinon/next@1.118.0-rc.0
|
|
75
|
-
|
|
76
3
|
## 1.117.0
|
|
77
4
|
|
|
78
5
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
- [Customization Strategies](#markdown-header-customization-strategies)
|
|
18
18
|
- [Professional Examples](#markdown-header-professional-examples)
|
|
19
19
|
- [Usage Blueprints](#markdown-header-usage-blueprints)
|
|
20
|
-
- [Upload Modal Content Customization](#markdown-header-upload-modal-content-customization)
|
|
21
20
|
- [Styling Reference](#markdown-header-styling-reference)
|
|
22
21
|
- [Workflow & Caching](#markdown-header-workflow-caching)
|
|
23
22
|
- [Best Practices](#markdown-header-best-practices)
|
|
@@ -93,10 +92,8 @@
|
|
|
93
92
|
- **⚙️ Per-Component Control**: Independent customization for each modal and component
|
|
94
93
|
- **🔒 Type Safety**: Full TypeScript support for all customization options
|
|
95
94
|
- **🌍 Localization Support**: Built-in `useLocalization` hook integration in every component for multi-language UI
|
|
96
|
-
- **🖼️ Upload Modal Images**: Replace rule example images with custom branded assets via `customStyles.uploadModalImages`
|
|
97
|
-
- **📝 Upload Modal Texts**: Customize all modal text content via `customStyles.uploadModalTexts`
|
|
98
95
|
|
|
99
|
-
**Customizable Components (
|
|
96
|
+
**Customizable Components (10/10 - 100%):**
|
|
100
97
|
|
|
101
98
|
- ✅ `VirtualTryOnUploadModal` - 30+ render functions, 80+ style points, CSS variables, cropping UI
|
|
102
99
|
- ✅ `VirtualTryOnResultModal` - 15+ render functions, 50+ style points, mobile-optimized feedback
|
|
@@ -106,24 +103,9 @@
|
|
|
106
103
|
- ✅ `VirtualTryOnProductSelector` - 3 render functions, 30+ style points, max selection UI
|
|
107
104
|
- ✅ `VirtualTryOnAsyncModal` - 5 render functions, 35+ style points, async feedback system
|
|
108
105
|
- ✅ `BasketVirtualTryOn` - 2 render functions, 4 style points, eligibility filtering
|
|
109
|
-
- ✅ `BarcodeScannerPlugin` - Complete VTO flow orchestration with customization pass-through
|
|
110
|
-
- ✅ `BarcodeScannerButton` - 2 render functions, 2 style points, floating button
|
|
111
|
-
- ✅ `BarcodeScannerModal` - 30+ render functions, 50+ style points, scanner UI
|
|
112
106
|
- ✅ `BasketAsyncModal` - 3 render functions, 25+ style points, real-time polling
|
|
113
107
|
- ✅ `VirtualTryOnButton` - 4 render functions, 10+ style points, variant support
|
|
114
108
|
|
|
115
|
-
### 🆕 NEW: Barcode Scanner Integration
|
|
116
|
-
|
|
117
|
-
- **📷 Barcode Scanning**: Scan product barcodes with your mobile camera to find and try on products instantly
|
|
118
|
-
- **🔍 Universal Barcode Support**: Supports all major formats (EAN-13, EAN-8, UPC-A, UPC-E, Code-128, Code-39, QR Code, and more)
|
|
119
|
-
- **📱 Homepage Only**: Floating button visible only on mobile devices and only on homepage
|
|
120
|
-
- **⚡ Real-time Search**: Instantly searches products by barcode number
|
|
121
|
-
- **🛒 Multi-Product Scanning**: Scan multiple products before continuing to VTO
|
|
122
|
-
- **🎨 Fully Customizable**: 50+ style points, 30+ render functions, theme support, CSS variables
|
|
123
|
-
- **📳 Haptic Feedback**: Optional vibration on successful scan
|
|
124
|
-
- **🔒 Permission Handling**: Graceful camera permission requests and error handling
|
|
125
|
-
- **⏱️ Auto-dismiss Errors**: Error messages automatically dismiss after 2 seconds
|
|
126
|
-
|
|
127
109
|
### 🆕 NEW: Multiple Product Try-On (Basket Integration)
|
|
128
110
|
|
|
129
111
|
- **🛒 Basket Integration**: Try on multiple products from basket simultaneously (max 3 products)
|
|
@@ -145,240 +127,6 @@ Select `pz-virtual-try-on` when prompted. In existing monorepos run `yarn instal
|
|
|
145
127
|
|
|
146
128
|
## Quick Start
|
|
147
129
|
|
|
148
|
-
### 🆕 Barcode Scanner Integration (Mobile Homepage)
|
|
149
|
-
|
|
150
|
-
**NEW**: Scan product barcodes to instantly find and try on products!
|
|
151
|
-
|
|
152
|
-
#### Method 1: Using PluginModule (Recommended)
|
|
153
|
-
|
|
154
|
-
The barcode scanner is automatically available via the PluginModule system. Add it to your **homepage only** (not client-root, as it should only appear on the homepage):
|
|
155
|
-
|
|
156
|
-
```tsx
|
|
157
|
-
// In src/app/[commerce]/[locale]/[currency]/home-client.tsx
|
|
158
|
-
'use client';
|
|
159
|
-
|
|
160
|
-
import PluginModule, { Component } from '@akinon/next/components/plugin-module';
|
|
161
|
-
|
|
162
|
-
export function HomeClient() {
|
|
163
|
-
return (
|
|
164
|
-
<>
|
|
165
|
-
{/* Barcode Scanner Plugin - Mobile only floating button */}
|
|
166
|
-
<PluginModule component={Component.BarcodeScannerPlugin} />
|
|
167
|
-
</>
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
Then use it in your homepage:
|
|
173
|
-
|
|
174
|
-
```tsx
|
|
175
|
-
// In src/app/[commerce]/[locale]/[currency]/page.tsx
|
|
176
|
-
import { HomeClient } from './home-client';
|
|
177
|
-
|
|
178
|
-
async function Page() {
|
|
179
|
-
return (
|
|
180
|
-
<>
|
|
181
|
-
{/* Your homepage widgets */}
|
|
182
|
-
<HomeClient />
|
|
183
|
-
</>
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
#### Method 2: Direct Component Import
|
|
189
|
-
|
|
190
|
-
```tsx
|
|
191
|
-
import { BarcodeScannerPlugin } from '@akinon/pz-virtual-try-on';
|
|
192
|
-
import type {
|
|
193
|
-
BarcodeScannerSettings,
|
|
194
|
-
VirtualTryOnPluginSettings
|
|
195
|
-
} from '@akinon/pz-virtual-try-on';
|
|
196
|
-
|
|
197
|
-
// Full customization example with styles and renderers
|
|
198
|
-
const barcodeScannerSettings: BarcodeScannerSettings = {
|
|
199
|
-
maxProducts: 5,
|
|
200
|
-
vibrateOnScan: true,
|
|
201
|
-
// Theme customization
|
|
202
|
-
theme: {
|
|
203
|
-
colors: {
|
|
204
|
-
primary: '#3b82f6',
|
|
205
|
-
background: '#ffffff',
|
|
206
|
-
text: '#1f2937',
|
|
207
|
-
success: '#10b981',
|
|
208
|
-
error: '#ef4444',
|
|
209
|
-
scannerOverlay: 'rgba(0, 0, 0, 0.6)',
|
|
210
|
-
scannerFrame: '#3b82f6',
|
|
211
|
-
scannerCorner: '#3b82f6'
|
|
212
|
-
},
|
|
213
|
-
borderRadius: {
|
|
214
|
-
sm: '4px',
|
|
215
|
-
md: '8px',
|
|
216
|
-
lg: '16px'
|
|
217
|
-
}
|
|
218
|
-
},
|
|
219
|
-
// 50+ Custom style points
|
|
220
|
-
customStyles: {
|
|
221
|
-
// Floating button
|
|
222
|
-
floatingButton: 'bg-blue-600 shadow-xl hover:bg-blue-700',
|
|
223
|
-
floatingButtonIcon: 'text-white w-7 h-7',
|
|
224
|
-
// Modal
|
|
225
|
-
modalOverlay: 'bg-black/80',
|
|
226
|
-
modalContainer: 'bg-white rounded-2xl',
|
|
227
|
-
modalHeader: 'border-b border-gray-100',
|
|
228
|
-
modalCloseButton: 'hover:bg-gray-100 rounded-full',
|
|
229
|
-
// Scanner
|
|
230
|
-
scannerContainer: 'rounded-lg overflow-hidden',
|
|
231
|
-
scannerFrame: 'border-2 border-blue-500',
|
|
232
|
-
scannerHelpText: 'text-white text-sm',
|
|
233
|
-
// Products
|
|
234
|
-
productCard: 'rounded-lg shadow-sm',
|
|
235
|
-
productImage: 'object-cover',
|
|
236
|
-
productRemoveButton: 'bg-red-500 hover:bg-red-600',
|
|
237
|
-
// Actions
|
|
238
|
-
continueButton: 'bg-blue-600 hover:bg-blue-700 text-white font-semibold',
|
|
239
|
-
// Error states
|
|
240
|
-
errorContainer: 'bg-red-500/90 rounded-lg',
|
|
241
|
-
errorMessage: 'text-white',
|
|
242
|
-
// Loading
|
|
243
|
-
loadingSpinner: 'text-blue-500',
|
|
244
|
-
// Permission denied
|
|
245
|
-
permissionDeniedButton: 'bg-blue-600 text-white'
|
|
246
|
-
},
|
|
247
|
-
// 30+ Custom render functions
|
|
248
|
-
customRenderers: {
|
|
249
|
-
// Custom floating button
|
|
250
|
-
renderFloatingButton: ({ onClick }) => (
|
|
251
|
-
<button
|
|
252
|
-
onClick={onClick}
|
|
253
|
-
className="fixed bottom-20 right-4 z-50 md:hidden w-16 h-16 rounded-full bg-gradient-to-r from-blue-500 to-purple-500 text-white shadow-xl"
|
|
254
|
-
>
|
|
255
|
-
📷
|
|
256
|
-
</button>
|
|
257
|
-
),
|
|
258
|
-
// Custom modal header
|
|
259
|
-
renderModalHeader: ({ title, onClose }) => (
|
|
260
|
-
<div className="flex justify-between items-center p-4 bg-gradient-to-r from-blue-500 to-purple-500">
|
|
261
|
-
<h2 className="text-white font-bold text-lg">{title}</h2>
|
|
262
|
-
<button onClick={onClose} className="text-white">
|
|
263
|
-
✕
|
|
264
|
-
</button>
|
|
265
|
-
</div>
|
|
266
|
-
),
|
|
267
|
-
// Custom product card
|
|
268
|
-
renderProductCard: ({ product, onRemove }) => (
|
|
269
|
-
<div className="relative p-2 bg-white rounded-lg shadow">
|
|
270
|
-
<img
|
|
271
|
-
src={product.productimage_set?.[0]?.image}
|
|
272
|
-
alt={product.name}
|
|
273
|
-
className="w-16 h-16 object-cover rounded"
|
|
274
|
-
/>
|
|
275
|
-
<button
|
|
276
|
-
onClick={() => onRemove?.(product)}
|
|
277
|
-
className="absolute -top-2 -right-2 w-5 h-5 bg-red-500 text-white rounded-full text-xs"
|
|
278
|
-
>
|
|
279
|
-
×
|
|
280
|
-
</button>
|
|
281
|
-
</div>
|
|
282
|
-
),
|
|
283
|
-
// Custom continue button
|
|
284
|
-
renderContinueButton: ({ onClick, disabled, text }) => (
|
|
285
|
-
<button
|
|
286
|
-
onClick={onClick}
|
|
287
|
-
disabled={disabled}
|
|
288
|
-
className={`w-full py-3 rounded-full font-bold transition ${
|
|
289
|
-
disabled
|
|
290
|
-
? 'bg-gray-300'
|
|
291
|
-
: 'bg-gradient-to-r from-blue-500 to-purple-500 text-white'
|
|
292
|
-
}`}
|
|
293
|
-
>
|
|
294
|
-
{text}
|
|
295
|
-
</button>
|
|
296
|
-
),
|
|
297
|
-
// Custom error display
|
|
298
|
-
renderError: ({ error, onRetry }) => (
|
|
299
|
-
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
|
300
|
-
<span>{error}</span>
|
|
301
|
-
{onRetry && (
|
|
302
|
-
<button onClick={onRetry} className="ml-2 underline">
|
|
303
|
-
Retry
|
|
304
|
-
</button>
|
|
305
|
-
)}
|
|
306
|
-
</div>
|
|
307
|
-
)
|
|
308
|
-
}
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
export function HomePage() {
|
|
312
|
-
return (
|
|
313
|
-
<div>
|
|
314
|
-
{/* Your homepage content */}
|
|
315
|
-
|
|
316
|
-
{/* Barcode Scanner Button (shows on mobile only) */}
|
|
317
|
-
<BarcodeScannerPlugin
|
|
318
|
-
barcodeScannerSettings={barcodeScannerSettings}
|
|
319
|
-
onProductsScanned={(products) => {
|
|
320
|
-
// Handle scanned products
|
|
321
|
-
}}
|
|
322
|
-
onVTOComplete={(result) => {
|
|
323
|
-
// Handle VTO completion
|
|
324
|
-
}}
|
|
325
|
-
/>
|
|
326
|
-
</div>
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
#### Method 3: Configure via settings.js
|
|
332
|
-
|
|
333
|
-
You can also configure the barcode scanner via `settings.js`:
|
|
334
|
-
|
|
335
|
-
```javascript
|
|
336
|
-
// settings.js
|
|
337
|
-
module.exports = {
|
|
338
|
-
// ... other settings
|
|
339
|
-
plugins: {
|
|
340
|
-
'pz-virtual-try-on': {
|
|
341
|
-
barcodeScanner: {
|
|
342
|
-
maxProducts: 5,
|
|
343
|
-
vibrateOnScan: true,
|
|
344
|
-
customStyles: {
|
|
345
|
-
floatingButton: 'bg-black shadow-xl',
|
|
346
|
-
continueButton: 'bg-black hover:bg-gray-800'
|
|
347
|
-
}
|
|
348
|
-
},
|
|
349
|
-
vto: {
|
|
350
|
-
theme: {
|
|
351
|
-
colors: {
|
|
352
|
-
primary: '#000000'
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
**How it works:**
|
|
362
|
-
|
|
363
|
-
1. Floating button appears in bottom-right corner (mobile only)
|
|
364
|
-
2. Click opens fullscreen barcode scanner
|
|
365
|
-
3. Point camera at product barcode
|
|
366
|
-
4. Product is searched and displayed automatically
|
|
367
|
-
5. Scan more products or click "Sanal Kabine Devam Et" (Continue to Virtual Cabin)
|
|
368
|
-
6. Upload photo and see VTO results for all scanned products
|
|
369
|
-
|
|
370
|
-
**Supported Barcode Formats:**
|
|
371
|
-
|
|
372
|
-
- EAN-13 / EAN-8 (European Article Number)
|
|
373
|
-
- UPC-A / UPC-E (Universal Product Code)
|
|
374
|
-
- Code-128 / Code-39 / Code-93
|
|
375
|
-
- QR Code
|
|
376
|
-
- Data Matrix
|
|
377
|
-
- PDF-417
|
|
378
|
-
- Codabar
|
|
379
|
-
- ITF (Interleaved 2 of 5)
|
|
380
|
-
- Aztec
|
|
381
|
-
|
|
382
130
|
### 🆕 Basket Integration (Multiple Products)
|
|
383
131
|
|
|
384
132
|
**NEW**: Try on multiple products from basket page!
|
|
@@ -478,10 +226,10 @@ const virtualTryOnSettings: VirtualTryOnPluginSettings = {
|
|
|
478
226
|
{step === 'upload'
|
|
479
227
|
? ' 01'
|
|
480
228
|
: step === 'crop'
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
229
|
+
? ' 02'
|
|
230
|
+
: step === 'processing'
|
|
231
|
+
? ' 03'
|
|
232
|
+
: ' 04'}
|
|
485
233
|
</p>
|
|
486
234
|
<h2 className="text-xl font-semibold text-gray-900">{title}</h2>
|
|
487
235
|
</div>
|
|
@@ -900,17 +648,7 @@ type VirtualTryOnPluginSettings = {
|
|
|
900
648
|
buttonSize?: 'sm' | 'md' | 'lg';
|
|
901
649
|
legal_text?: string;
|
|
902
650
|
instructions?: string;
|
|
903
|
-
customStyles?:
|
|
904
|
-
// CSS class overrides (300+ slots)
|
|
905
|
-
button?: string;
|
|
906
|
-
uploadModal?: string;
|
|
907
|
-
resultModal?: string;
|
|
908
|
-
// ... and many more
|
|
909
|
-
|
|
910
|
-
// Upload Modal Content Customization
|
|
911
|
-
uploadModalImages?: VirtualTryOnModalImages;
|
|
912
|
-
uploadModalTexts?: VirtualTryOnModalTexts;
|
|
913
|
-
};
|
|
651
|
+
customStyles?: Record<string, string | undefined>;
|
|
914
652
|
customRenderers?: {
|
|
915
653
|
UploadModal?: React.ComponentType<VirtualTryOnUploadModalProps>;
|
|
916
654
|
ResultModal?: React.ComponentType<VirtualTryOnResultModalProps>;
|
|
@@ -934,7 +672,7 @@ type VirtualTryOnPluginSettings = {
|
|
|
934
672
|
};
|
|
935
673
|
```
|
|
936
674
|
|
|
937
|
-
> `customStyles` exposes 300+ slots
|
|
675
|
+
> `customStyles` exposes 300+ slots (see [Styling Reference](#markdown-header-styling-reference)); `CustomRendererProps` provides 60+ render callbacks across all 10 components with full localization support.
|
|
938
676
|
|
|
939
677
|
### Service Endpoints
|
|
940
678
|
|
|
@@ -1957,140 +1695,6 @@ Customize modal overlays, sizes, and button styles for brand consistency.
|
|
|
1957
1695
|
/>
|
|
1958
1696
|
```
|
|
1959
1697
|
|
|
1960
|
-
## Upload Modal Content Customization
|
|
1961
|
-
|
|
1962
|
-
The upload modal images and texts can be fully customized via `customStyles.uploadModalImages` and `customStyles.uploadModalTexts`. This is useful for:
|
|
1963
|
-
|
|
1964
|
-
- Replacing default rule images with custom branded examples
|
|
1965
|
-
- Translating or customizing all modal text content
|
|
1966
|
-
- Providing localized photo guidelines with custom imagery
|
|
1967
|
-
- A/B testing different upload instructions
|
|
1968
|
-
|
|
1969
|
-
### Available Image Slots
|
|
1970
|
-
|
|
1971
|
-
| Property | Description | Default |
|
|
1972
|
-
|----------|-------------|---------|
|
|
1973
|
-
| `ruleGoodExample` | Good photo example (rule 1 image) | Built-in asset |
|
|
1974
|
-
| `ruleBadExample1` | Bad example: cluttered background | Built-in asset |
|
|
1975
|
-
| `ruleBadExample2` | Bad example: poor lighting | Built-in asset |
|
|
1976
|
-
| `ruleBadExample3` | Bad example: cropped body | Built-in asset |
|
|
1977
|
-
| `ruleBadExample4` | Bad example: multiple people | Built-in asset |
|
|
1978
|
-
| `uploadIcon` | Upload area icon | Built-in SVG |
|
|
1979
|
-
|
|
1980
|
-
### Available Text Slots
|
|
1981
|
-
|
|
1982
|
-
| Property | Description | Fallback Localization Key |
|
|
1983
|
-
|----------|-------------|---------------------------|
|
|
1984
|
-
| `title` | Modal header title | `product.virtual_try_on.title` |
|
|
1985
|
-
| `editPhotoTitle` | Title when editing/cropping photo | `product.virtual_try_on.edit_photo_title` |
|
|
1986
|
-
| `uploadPrompt` | Main upload prompt text | `product.virtual_try_on.upload_prompt` |
|
|
1987
|
-
| `uploadRequirements` | File requirements text | `product.virtual_try_on.upload_requirements` |
|
|
1988
|
-
| `uploadInfo` | Additional upload info | `product.virtual_try_on.upload_info` |
|
|
1989
|
-
| `rule1` | Rule 1 description | `product.virtual_try_on.rule_1` |
|
|
1990
|
-
| `rule2` | Rule 2 description | `product.virtual_try_on.rule_2` |
|
|
1991
|
-
| `rule3` | Rule 3 description | `product.virtual_try_on.rule_3` |
|
|
1992
|
-
| `rule4` | Rule 4 description | `product.virtual_try_on.rule_4` |
|
|
1993
|
-
| `rule5` | Rule 5 description | `product.virtual_try_on.rule_5` |
|
|
1994
|
-
| `rule6` | Rule 6 description | `product.virtual_try_on.rule_6` |
|
|
1995
|
-
| `processingTitle` | Processing overlay title | `product.virtual_try_on.processing` |
|
|
1996
|
-
| `processingMessage` | Processing overlay message | `product.virtual_try_on.processing_message` |
|
|
1997
|
-
| `retryUpload` | Retry upload button text | `product.virtual_try_on.retry` |
|
|
1998
|
-
|
|
1999
|
-
### Example: Custom Images and Texts
|
|
2000
|
-
|
|
2001
|
-
```tsx
|
|
2002
|
-
import PluginModule, { Component } from '@akinon/next/components/plugin-module';
|
|
2003
|
-
|
|
2004
|
-
<PluginModule
|
|
2005
|
-
component={Component.VirtualTryOnPlugin}
|
|
2006
|
-
props={{
|
|
2007
|
-
product: data.product,
|
|
2008
|
-
settings: {
|
|
2009
|
-
customStyles: {
|
|
2010
|
-
// Custom button styling
|
|
2011
|
-
button: 'bg-purple-600 hover:bg-purple-700 text-white font-bold px-6 py-3 rounded-lg',
|
|
2012
|
-
|
|
2013
|
-
// Custom Upload Modal Images
|
|
2014
|
-
uploadModalImages: {
|
|
2015
|
-
ruleGoodExample: 'https://cdn.example.com/vto/good-example.jpg',
|
|
2016
|
-
ruleBadExample1: 'https://cdn.example.com/vto/bad-background.jpg',
|
|
2017
|
-
ruleBadExample2: 'https://cdn.example.com/vto/bad-lighting.jpg',
|
|
2018
|
-
ruleBadExample3: 'https://cdn.example.com/vto/bad-cropped.jpg',
|
|
2019
|
-
ruleBadExample4: 'https://cdn.example.com/vto/bad-multiple.jpg',
|
|
2020
|
-
uploadIcon: 'https://cdn.example.com/vto/upload-icon.svg'
|
|
2021
|
-
},
|
|
2022
|
-
|
|
2023
|
-
// Custom Upload Modal Texts
|
|
2024
|
-
uploadModalTexts: {
|
|
2025
|
-
title: '🎭 Virtual Try-On Studio',
|
|
2026
|
-
editPhotoTitle: '✂️ Adjust Your Photo',
|
|
2027
|
-
uploadPrompt: '📸 Drag & drop your photo here',
|
|
2028
|
-
uploadRequirements: 'JPEG or PNG, max 5MB',
|
|
2029
|
-
uploadInfo: 'Your photo will be processed securely',
|
|
2030
|
-
rule1: '🌟 Use a plain background',
|
|
2031
|
-
rule2: '💡 Ensure good lighting',
|
|
2032
|
-
rule3: '👤 Show your full body',
|
|
2033
|
-
rule4: '🚫 No multiple people',
|
|
2034
|
-
rule5: '📐 Face the camera directly',
|
|
2035
|
-
rule6: '👔 Wear fitted clothing',
|
|
2036
|
-
processingTitle: '✨ Creating your look...',
|
|
2037
|
-
processingMessage: 'Our AI is working its magic!',
|
|
2038
|
-
retryUpload: '🔄 Try another photo'
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
}}
|
|
2043
|
-
/>
|
|
2044
|
-
```
|
|
2045
|
-
|
|
2046
|
-
### TypeScript Interfaces
|
|
2047
|
-
|
|
2048
|
-
```ts
|
|
2049
|
-
interface VirtualTryOnModalImages {
|
|
2050
|
-
ruleGoodExample?: string;
|
|
2051
|
-
ruleBadExample1?: string;
|
|
2052
|
-
ruleBadExample2?: string;
|
|
2053
|
-
ruleBadExample3?: string;
|
|
2054
|
-
ruleBadExample4?: string;
|
|
2055
|
-
uploadIcon?: string;
|
|
2056
|
-
}
|
|
2057
|
-
|
|
2058
|
-
interface VirtualTryOnModalTexts {
|
|
2059
|
-
title?: string;
|
|
2060
|
-
editPhotoTitle?: string;
|
|
2061
|
-
uploadPrompt?: string;
|
|
2062
|
-
uploadRequirements?: string;
|
|
2063
|
-
uploadInfo?: string;
|
|
2064
|
-
rule1?: string;
|
|
2065
|
-
rule2?: string;
|
|
2066
|
-
rule3?: string;
|
|
2067
|
-
rule4?: string;
|
|
2068
|
-
rule5?: string;
|
|
2069
|
-
rule6?: string;
|
|
2070
|
-
processingTitle?: string;
|
|
2071
|
-
processingMessage?: string;
|
|
2072
|
-
retryUpload?: string;
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
// Usage in VirtualTryOnPluginSettings
|
|
2076
|
-
interface VirtualTryOnPluginSettings {
|
|
2077
|
-
customStyles?: {
|
|
2078
|
-
// ... other style slots
|
|
2079
|
-
uploadModalImages?: VirtualTryOnModalImages;
|
|
2080
|
-
uploadModalTexts?: VirtualTryOnModalTexts;
|
|
2081
|
-
};
|
|
2082
|
-
}
|
|
2083
|
-
```
|
|
2084
|
-
|
|
2085
|
-
### Fallback Behavior
|
|
2086
|
-
|
|
2087
|
-
If a custom image or text is not provided, the component automatically falls back to:
|
|
2088
|
-
|
|
2089
|
-
1. **Images**: Built-in default assets from the package
|
|
2090
|
-
2. **Texts**: Localized strings via `useLocalization()` hook with the corresponding translation key
|
|
2091
|
-
|
|
2092
|
-
This allows partial customization - you can override only the images or texts you need while keeping defaults for the rest.
|
|
2093
|
-
|
|
2094
1698
|
## Styling Reference
|
|
2095
1699
|
|
|
2096
1700
|
### Button Targets
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/pz-virtual-try-on",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-beta.12",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"peerDependencies": {
|
|
@@ -8,11 +8,10 @@
|
|
|
8
8
|
"react-dom": "^18.0.0"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@akinon/next": "
|
|
11
|
+
"@akinon/next": "2.0.0-beta.12",
|
|
12
12
|
"clsx": "^2.0.0",
|
|
13
13
|
"tailwind-merge": "^2.0.0",
|
|
14
|
-
"react-image-crop": "^11.0.5"
|
|
15
|
-
"html5-qrcode": "^2.3.8"
|
|
14
|
+
"react-image-crop": "^11.0.5"
|
|
16
15
|
},
|
|
17
16
|
"devDependencies": {
|
|
18
17
|
"@types/node": "^18.7.8",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useState, useRef, useCallback } from 'react';
|
|
2
2
|
import 'react-image-crop/dist/ReactCrop.css';
|
|
3
|
-
import { validateAspectRatioFromDimensions } from '../utils';
|
|
4
3
|
|
|
5
4
|
type CropType = {
|
|
6
5
|
unit: 'px' | '%';
|
|
@@ -10,16 +9,10 @@ type CropType = {
|
|
|
10
9
|
height: number;
|
|
11
10
|
};
|
|
12
11
|
|
|
13
|
-
export interface CropResult {
|
|
14
|
-
success: boolean;
|
|
15
|
-
error?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
12
|
export function useImageCropper(
|
|
19
13
|
setIsLoading: (loading: boolean) => void,
|
|
20
14
|
processImage: (base64: string) => void,
|
|
21
|
-
clearError?: () => void
|
|
22
|
-
onAspectRatioError?: (error: string) => void
|
|
15
|
+
clearError?: () => void
|
|
23
16
|
) {
|
|
24
17
|
const [isCropping, setIsCropping] = useState(false);
|
|
25
18
|
const [crop, setCrop] = useState<CropType>({
|
|
@@ -71,12 +64,10 @@ export function useImageCropper(
|
|
|
71
64
|
}
|
|
72
65
|
};
|
|
73
66
|
|
|
74
|
-
const processCompletedCropImmediate = async (
|
|
75
|
-
crop: any
|
|
76
|
-
): Promise<CropResult> => {
|
|
67
|
+
const processCompletedCropImmediate = async (crop: any) => {
|
|
77
68
|
if (!imageRef.current || !crop?.width || !crop?.height) {
|
|
78
69
|
setIsLoading(false);
|
|
79
|
-
return
|
|
70
|
+
return;
|
|
80
71
|
}
|
|
81
72
|
|
|
82
73
|
if (clearError) {
|
|
@@ -91,28 +82,8 @@ export function useImageCropper(
|
|
|
91
82
|
const scaleX = image.naturalWidth / image.width;
|
|
92
83
|
const scaleY = image.naturalHeight / image.height;
|
|
93
84
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// Validate aspect ratio before processing
|
|
98
|
-
const aspectRatioValidation = validateAspectRatioFromDimensions(
|
|
99
|
-
croppedWidth,
|
|
100
|
-
croppedHeight
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
if (!aspectRatioValidation.isValid) {
|
|
104
|
-
setIsLoading(false);
|
|
105
|
-
if (onAspectRatioError && aspectRatioValidation.error) {
|
|
106
|
-
onAspectRatioError(aspectRatioValidation.error);
|
|
107
|
-
}
|
|
108
|
-
return {
|
|
109
|
-
success: false,
|
|
110
|
-
error: aspectRatioValidation.error
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
canvas.width = croppedWidth;
|
|
115
|
-
canvas.height = croppedHeight;
|
|
85
|
+
canvas.width = crop.width * scaleX;
|
|
86
|
+
canvas.height = crop.height * scaleY;
|
|
116
87
|
|
|
117
88
|
const ctx = canvas.getContext('2d');
|
|
118
89
|
|
|
@@ -123,8 +94,8 @@ export function useImageCropper(
|
|
|
123
94
|
|
|
124
95
|
const sourceX = crop.x * scaleX;
|
|
125
96
|
const sourceY = crop.y * scaleY;
|
|
126
|
-
const sourceWidth =
|
|
127
|
-
const sourceHeight =
|
|
97
|
+
const sourceWidth = crop.width * scaleX;
|
|
98
|
+
const sourceHeight = crop.height * scaleY;
|
|
128
99
|
|
|
129
100
|
ctx.drawImage(
|
|
130
101
|
image,
|
|
@@ -144,11 +115,9 @@ export function useImageCropper(
|
|
|
144
115
|
setCropProcessed(true);
|
|
145
116
|
setLastSuccessfulCrop(crop);
|
|
146
117
|
setIsLoading(false);
|
|
147
|
-
return { success: true };
|
|
148
118
|
} catch (error) {
|
|
149
119
|
console.error('❌ processCompletedCrop failed:', error);
|
|
150
120
|
setIsLoading(false);
|
|
151
|
-
return { success: false, error: 'Failed to process crop' };
|
|
152
121
|
}
|
|
153
122
|
};
|
|
154
123
|
|
|
@@ -187,28 +156,10 @@ export function useImageCropper(
|
|
|
187
156
|
});
|
|
188
157
|
};
|
|
189
158
|
|
|
190
|
-
const closeCrop = () => {
|
|
191
|
-
if (debounceTimeoutRef.current) {
|
|
192
|
-
clearTimeout(debounceTimeoutRef.current);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
setIsCropping(false);
|
|
196
|
-
setCompletedCrop(null);
|
|
197
|
-
setCrop(undefined as any);
|
|
198
|
-
};
|
|
199
|
-
|
|
200
159
|
const confirmCrop = useCallback(async () => {
|
|
201
160
|
if (completedCrop) {
|
|
202
161
|
await processManualCrop(completedCrop);
|
|
203
162
|
setIsCropping(false);
|
|
204
|
-
setCompletedCrop(null);
|
|
205
|
-
setCrop({
|
|
206
|
-
unit: '%',
|
|
207
|
-
x: 25,
|
|
208
|
-
y: 25,
|
|
209
|
-
width: 50,
|
|
210
|
-
height: 50
|
|
211
|
-
});
|
|
212
163
|
}
|
|
213
164
|
}, [completedCrop, processManualCrop]);
|
|
214
165
|
|
|
@@ -224,7 +175,6 @@ export function useImageCropper(
|
|
|
224
175
|
processCompletedCrop,
|
|
225
176
|
processManualCrop,
|
|
226
177
|
confirmCrop,
|
|
227
|
-
resetCrop
|
|
228
|
-
closeCrop
|
|
178
|
+
resetCrop
|
|
229
179
|
};
|
|
230
180
|
}
|