@akinon/pz-virtual-try-on 2.0.34-beta.0 → 2.0.34
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 +4 -3
- package/README.md +96 -23
- package/package.json +3 -2
- package/src/data/endpoints.ts +50 -5
- package/src/hooks/use-virtual-try-on-async.ts +115 -43
- package/src/hooks/use-virtual-try-on.ts +49 -28
- package/src/index.ts +5 -0
- package/src/types/index.ts +48 -8
- package/src/utils/polling.ts +6 -3
- package/src/views/basket-async-modal.tsx +38 -0
- package/src/views/basket-virtual-try-on.tsx +107 -9
- package/src/views/main.tsx +63 -19
- package/src/views/virtual-try-on-multiple-modal.tsx +8 -2
- package/src/views/virtual-try-on-multiple-result-modal.tsx +36 -4
- package/src/views/virtual-try-on-product-selector.tsx +5 -1
- package/src/views/virtual-try-on-result-modal.tsx +40 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# @akinon/pz-virtual-try-on
|
|
2
2
|
|
|
3
|
-
## 2.0.34
|
|
3
|
+
## 2.0.34
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
|
|
7
|
+
- 4e2ea16b: ZERO-4582: enhance virtual try-on functionality with single and async mutations
|
|
8
|
+
- Updated dependencies [4e2ea16b]
|
|
9
|
+
- @akinon/next@2.0.34
|
|
9
10
|
|
|
10
11
|
## 2.0.33
|
|
11
12
|
|
package/README.md
CHANGED
|
@@ -2109,11 +2109,20 @@ This allows partial customization - you can override only the images or texts yo
|
|
|
2109
2109
|
|
|
2110
2110
|
`loadingSpinner`, `loadingOverlay`, `loadingText`, `modalEnter`, `modalExit`, `fadeIn`, `fadeOut`, `slideIn`, `slideOut`.
|
|
2111
2111
|
|
|
2112
|
+
### Usage Limit & Loader Targets
|
|
2113
|
+
|
|
2114
|
+
`usageCheckOverlay`, `usageCheckSpinner` (full-screen loader shown while the try-on usage/limit pre-check runs on button click), `resultModalUsageInfo`, `multipleResultModalUsageInfo`, `basketAsyncModalUsageInfo` (the "used/limit" info line rendered in each result surface when the backend returns `tryon_limit`).
|
|
2115
|
+
|
|
2112
2116
|
### Render Functions
|
|
2113
2117
|
|
|
2114
|
-
- **Button**: `renderButton`, `renderIcon`, `renderLoadingSpinner`, `renderButtonText`.
|
|
2118
|
+
- **Button**: `renderButton`, `renderIcon`, `renderLoadingSpinner`, `renderButtonText`, `renderUsageCheckLoader`.
|
|
2115
2119
|
- **Upload Modal**: `renderModal`, `renderHeader`, `renderCloseButton`, `renderLegalConsent`, `renderLegalConsentTitle`, `renderLegalConsentText`, `renderLegalConsentButton`, `renderUploadSection`, `renderUploadTitle`, `renderUploadInstructions`, `renderUploadArea`, `renderUploadIcon`, `renderUploadPrompt`, `renderUploadRequirements`, `renderFileInput`, `renderImagePreview`, `renderImageContainer`, `renderCropContainer`, `renderCroppingImage`, `renderPreviewImage`, `renderBackButton`, `renderBackButtonIcon`, `renderCropButton`, `renderCropButtonIcon`, `renderCropTickButton`, `renderCropTickIcon`, `renderCropResetButton`, `renderCropResetIcon`, `renderTryOnButton`, `renderProcessingState`, `renderProcessingOverlay`, `renderProcessingSpinner`, `renderProcessingMessage`, `renderSuccessState`, `renderSuccessIcon`, `renderSuccessMessage`, `renderActionButtonsContainer`, `renderViewResultButton`, `renderViewResultIcon`, `renderTryAgainButton`, `renderTryAgainIcon`, `renderErrorMessage`, `renderErrorIcon`, `renderErrorText`.
|
|
2116
|
-
- **Result Modal**: `renderModal`, `renderHeader`, `renderCloseButton`, `renderBackButton`, `renderBackButtonIcon`, `renderResultImage`, `renderImageContainer`, `renderFeedbackSection`, `renderFeedbackPrompt`, `renderFeedbackButtonsContainer`, `renderFeedbackButtons`, `renderFeedbackButton`, `renderPositiveFeedbackButton`, `renderNegativeFeedbackButton`, `renderThumbsUpIcon`, `renderThumbsDownIcon`, `renderErrorState`, `renderResultErrorIcon`, `renderResultErrorMessage`, `renderRetryButton`, `renderEmptyState`, `renderEmptyIcon`, `renderEmptyMessage`, `renderGoBackButton`, `renderDebugSection`, `renderDebugStats`, `renderClearCacheButton`.
|
|
2120
|
+
- **Result Modal**: `renderModal`, `renderHeader`, `renderCloseButton`, `renderBackButton`, `renderBackButtonIcon`, `renderResultImage`, `renderImageContainer`, `renderFeedbackSection`, `renderFeedbackPrompt`, `renderFeedbackButtonsContainer`, `renderFeedbackButtons`, `renderFeedbackButton`, `renderPositiveFeedbackButton`, `renderNegativeFeedbackButton`, `renderThumbsUpIcon`, `renderThumbsDownIcon`, `renderErrorState`, `renderResultErrorIcon`, `renderResultErrorMessage`, `renderRetryButton`, `renderEmptyState`, `renderEmptyIcon`, `renderEmptyMessage`, `renderGoBackButton`, `renderDebugSection`, `renderDebugStats`, `renderClearCacheButton`, `renderUsageInfo`.
|
|
2121
|
+
- **Multiple Result Modal**: `renderModal`, `renderBackIcon`, `renderCloseIcon`, `renderErrorIcon`, `renderUsageInfo`.
|
|
2122
|
+
- **Basket Async Modal**: `renderModal`, `renderCloseIcon`, `renderProcessingSpinner`, `renderUsageInfo`.
|
|
2123
|
+
- **Basket Virtual Try-On**: `renderButton`, `renderIcon`, `renderButtonText`, `renderUsageCheckLoader`.
|
|
2124
|
+
|
|
2125
|
+
`renderUsageCheckLoader()` replaces the full-screen loader shown during the usage/limit pre-check. `renderUsageInfo({ used, limit, text })` replaces the "used/limit" info line; `text` is the already-localized string, `used`/`limit` are the raw numbers.
|
|
2117
2126
|
|
|
2118
2127
|
## Workflow & Caching
|
|
2119
2128
|
|
|
@@ -2145,31 +2154,44 @@ Cache TTL defaults to five minutes (`5 / 60` hours) and auto-prunes expired keys
|
|
|
2145
2154
|
|
|
2146
2155
|
---
|
|
2147
2156
|
|
|
2148
|
-
## 🆕
|
|
2157
|
+
## 🆕 Async Try-On API Documentation (Single & Multiple)
|
|
2149
2158
|
|
|
2150
2159
|
### Async Workflow
|
|
2151
2160
|
|
|
2152
|
-
|
|
2161
|
+
Try-on uses an async workflow with polling. All calls go through the Next.js proxy (`/api/virtual-try-on?endpoint=...`), which forwards to the try-on service and injects identity headers server-side (`X-Session-Id` from the `osessionid` cookie, `X-User-Id` from the NextAuth session `user.pk`):
|
|
2153
2162
|
|
|
2154
2163
|
```
|
|
2155
|
-
|
|
2156
|
-
├─ Request: {
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2164
|
+
0. POST ?endpoint=usage → /api/v1/try-on-usage (limit pre-check)
|
|
2165
|
+
├─ Request: { skus: ["SKU-1", ...] }
|
|
2166
|
+
├─ Response: { tryon_limit, skus: [{ sku, used_count }] }
|
|
2167
|
+
├─ tryon_limit = 0 → unlimited, skip client-side checks
|
|
2168
|
+
└─ Guest + limit active → 400 VALIDATION → redirect to login
|
|
2169
|
+
|
|
2170
|
+
1. POST ?endpoint=single-try-on → /api/v1/virtual-try-on (PDP)
|
|
2171
|
+
POST ?endpoint=multiple-try-on → /api/v1/multiple-virtual-try-on (basket, 1-3 products)
|
|
2172
|
+
├─ Request: { reference_image, ... } (flat for single, { products: [...] } for multiple)
|
|
2173
|
+
├─ Response: { process_id, status: "pending" }
|
|
2174
|
+
└─ 429 TRY_ON_LIMIT_EXCEEDED when the per-user+SKU limit is exhausted
|
|
2175
|
+
|
|
2176
|
+
2. GET ?endpoint=job-status&process_id=... → /api/v1/process/{process_id}
|
|
2177
|
+
├─ Poll every 2 seconds (max 60 attempts single, 150 multiple)
|
|
2161
2178
|
├─ Status flow: pending → processing → completed | failed
|
|
2162
|
-
└─ On completed: {
|
|
2179
|
+
└─ On completed: { result: { job_type, single | multiple }, tryon_limit, used_count }
|
|
2180
|
+
|
|
2181
|
+
3. POST ?endpoint=event → /api/v1/try-on-event (conversion tracking, logged-in only)
|
|
2182
|
+
└─ Request: { sku, event_name } — e.g. ADD_TO_CART, PURCHASE
|
|
2163
2183
|
|
|
2164
|
-
|
|
2184
|
+
4. PUT ?endpoint=feedback → /api/v1/feedback
|
|
2165
2185
|
├─ Request: { url: generated_image_url, feedback: boolean }
|
|
2166
2186
|
└─ Submit per product independently
|
|
2167
2187
|
```
|
|
2168
2188
|
|
|
2169
|
-
|
|
2189
|
+
The limit is counted per **user + SKU** on the backend (only successful try-ons count, it never resets). The frontend never decrements anything — it only reads `used_count`/`tryon_limit` for UX (blocking toast on PDP, disabled products in the basket selector, info line in result modals).
|
|
2190
|
+
|
|
2191
|
+
### Hook Usage: `useVirtualTryOnAsync`
|
|
2170
2192
|
|
|
2171
2193
|
```tsx
|
|
2172
|
-
import {
|
|
2194
|
+
import { useVirtualTryOnAsync } from '@akinon/pz-virtual-try-on';
|
|
2173
2195
|
|
|
2174
2196
|
function MyMultipleTryOn({ products }) {
|
|
2175
2197
|
const {
|
|
@@ -2189,7 +2211,7 @@ function MyMultipleTryOn({ products }) {
|
|
|
2189
2211
|
submitFeedbackForResult,
|
|
2190
2212
|
reset,
|
|
2191
2213
|
retry
|
|
2192
|
-
} =
|
|
2214
|
+
} = useVirtualTryOnAsync(products);
|
|
2193
2215
|
|
|
2194
2216
|
return (
|
|
2195
2217
|
<div>
|
|
@@ -2210,7 +2232,7 @@ function MyMultipleTryOn({ products }) {
|
|
|
2210
2232
|
{isProcessing && (
|
|
2211
2233
|
<div>
|
|
2212
2234
|
<p>Status: {jobStatus}</p>
|
|
2213
|
-
<p>Attempt: {pollingAttempts} /
|
|
2235
|
+
<p>Attempt: {pollingAttempts} / 150</p>
|
|
2214
2236
|
</div>
|
|
2215
2237
|
)}
|
|
2216
2238
|
|
|
@@ -2267,31 +2289,45 @@ if (hasCompatibilityError(result)) {
|
|
|
2267
2289
|
// - empty: No person detected
|
|
2268
2290
|
```
|
|
2269
2291
|
|
|
2292
|
+
The current API also returns a generic `INCOMPATIBLE` code whose `detail` carries a
|
|
2293
|
+
pipe-separated, already-localized message (`"FACE_NOT_VISIBLE|Lütfen yüzünüzün net
|
|
2294
|
+
göründüğü bir fotoğraf kullanın"`). Use `parseVirtualTryOnError` to extract the
|
|
2295
|
+
user-facing part after the pipe — all built-in views already do this.
|
|
2296
|
+
|
|
2297
|
+
```tsx
|
|
2298
|
+
import { parseVirtualTryOnError } from '@akinon/pz-virtual-try-on';
|
|
2299
|
+
|
|
2300
|
+
parseVirtualTryOnError('FACE_NOT_VISIBLE|Lütfen yüzünüzün net göründüğü bir fotoğraf kullanın');
|
|
2301
|
+
// → "Lütfen yüzünüzün net göründüğü bir fotoğraf kullanın"
|
|
2302
|
+
```
|
|
2303
|
+
|
|
2270
2304
|
### Polling Control
|
|
2271
2305
|
|
|
2272
2306
|
```tsx
|
|
2273
2307
|
import { VirtualTryOnPoller } from '@akinon/pz-virtual-try-on';
|
|
2274
2308
|
|
|
2275
2309
|
const poller = new VirtualTryOnPoller({
|
|
2276
|
-
referenceUrl:
|
|
2277
|
-
maxAttempts: 60,
|
|
2278
|
-
interval:
|
|
2310
|
+
referenceUrl: processId, // process_id returned by the start endpoint
|
|
2311
|
+
maxAttempts: 150, // 60 for single, 150 for multiple (2s interval)
|
|
2312
|
+
interval: 2000, // 2 seconds
|
|
2279
2313
|
onStatusChange: (status, attempts) => {
|
|
2280
2314
|
console.log(`Status: ${status}, Attempt: ${attempts}`);
|
|
2281
2315
|
},
|
|
2282
2316
|
onComplete: (result) => {
|
|
2283
|
-
console.log('Completed!', result.
|
|
2317
|
+
console.log('Completed!', result.result);
|
|
2284
2318
|
},
|
|
2285
2319
|
onError: (error) => {
|
|
2286
2320
|
console.error('Failed:', error);
|
|
2287
2321
|
},
|
|
2288
2322
|
onTimeout: () => {
|
|
2289
|
-
console.warn('Timeout after
|
|
2323
|
+
console.warn('Timeout after max attempts');
|
|
2290
2324
|
}
|
|
2291
2325
|
});
|
|
2292
2326
|
|
|
2293
|
-
await poller.start(async (
|
|
2294
|
-
const response = await fetch(
|
|
2327
|
+
await poller.start(async (processId) => {
|
|
2328
|
+
const response = await fetch(
|
|
2329
|
+
`/api/virtual-try-on?endpoint=job-status&process_id=${processId}`
|
|
2330
|
+
);
|
|
2295
2331
|
return response.json();
|
|
2296
2332
|
});
|
|
2297
2333
|
|
|
@@ -2299,6 +2335,43 @@ await poller.start(async (url) => {
|
|
|
2299
2335
|
poller.stop();
|
|
2300
2336
|
```
|
|
2301
2337
|
|
|
2338
|
+
### Usage Limits & Event Tracking
|
|
2339
|
+
|
|
2340
|
+
The backend enforces a per-**user + SKU** try-on limit (`tryon_limit`; `0` means
|
|
2341
|
+
unlimited). Only successful try-ons increment `used_count` and the counter never
|
|
2342
|
+
resets. Built-in behavior:
|
|
2343
|
+
|
|
2344
|
+
- **PDP button click**: usage is pre-checked for everyone. Guests get redirected
|
|
2345
|
+
to `/users/auth` when the limit feature is active (400 `VALIDATION`); logged-in
|
|
2346
|
+
users whose limit is exhausted see a toast (`product.virtual_try_on.limit_exceeded`
|
|
2347
|
+
locale key) and the modal never opens.
|
|
2348
|
+
- **Basket selector**: SKUs with `used_count >= tryon_limit` render disabled
|
|
2349
|
+
(`productSelectorProductItemDisabled` style target).
|
|
2350
|
+
- **Start endpoints**: a 429 `TRY_ON_LIMIT_EXCEEDED` from the backend is surfaced
|
|
2351
|
+
through the normal error UI with the backend-localized message.
|
|
2352
|
+
|
|
2353
|
+
```tsx
|
|
2354
|
+
import {
|
|
2355
|
+
useGetTryOnUsageMutation,
|
|
2356
|
+
useSubmitTryOnEventMutation
|
|
2357
|
+
} from '@akinon/pz-virtual-try-on';
|
|
2358
|
+
|
|
2359
|
+
// Pre-check remaining rights for a set of SKUs
|
|
2360
|
+
const [getTryOnUsage] = useGetTryOnUsageMutation();
|
|
2361
|
+
const usage = await getTryOnUsage({ skus: ['SKU-1', 'SKU-2'] }).unwrap();
|
|
2362
|
+
// { tryon_limit: 2, skus: [{ sku: 'SKU-1', used_count: 1 }, ...] }
|
|
2363
|
+
|
|
2364
|
+
// Track conversion events for previously tried-on products (logged-in users only)
|
|
2365
|
+
const [submitTryOnEvent] = useSubmitTryOnEventMutation();
|
|
2366
|
+
await submitTryOnEvent({ sku: 'SKU-1', event_name: 'ADD_TO_CART' }).unwrap();
|
|
2367
|
+
// Suggested event names: ADD_TO_CART, PURCHASE, VIEW_VARIANT, ADD_TO_WISHLIST,
|
|
2368
|
+
// SHARE, REMOVE_FROM_CART
|
|
2369
|
+
```
|
|
2370
|
+
|
|
2371
|
+
Identity headers are injected by the Next.js proxy server-side — `X-Session-Id`
|
|
2372
|
+
from the `osessionid` cookie and `X-User-Id` from the NextAuth session `user.pk`.
|
|
2373
|
+
Client code never sends them manually.
|
|
2374
|
+
|
|
2302
2375
|
### Limited Categories Caching
|
|
2303
2376
|
|
|
2304
2377
|
```tsx
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/pz-virtual-try-on",
|
|
3
|
-
"version": "2.0.34
|
|
3
|
+
"version": "2.0.34",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"peerDependencies": {
|
|
7
|
+
"next-auth": "^5.0.0-beta",
|
|
7
8
|
"react": "^18.0.0 || ^19.0.0",
|
|
8
9
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
9
10
|
},
|
|
10
11
|
"dependencies": {
|
|
11
|
-
"@akinon/next": "2.0.34
|
|
12
|
+
"@akinon/next": "2.0.34",
|
|
12
13
|
"clsx": "^2.0.0",
|
|
13
14
|
"tailwind-merge": "^2.0.0",
|
|
14
15
|
"react-image-crop": "^11.0.5",
|
package/src/data/endpoints.ts
CHANGED
|
@@ -7,13 +7,27 @@ import type {
|
|
|
7
7
|
VirtualTryOnAsyncRequest,
|
|
8
8
|
VirtualTryOnAsyncProduct,
|
|
9
9
|
VirtualTryOnAsyncResponse,
|
|
10
|
-
VirtualTryOnJobStatusResponse
|
|
10
|
+
VirtualTryOnJobStatusResponse,
|
|
11
|
+
TryOnUsageResponse,
|
|
12
|
+
TryOnEventResponse
|
|
11
13
|
} from '../types';
|
|
12
14
|
|
|
13
15
|
export const virtualTryOnApi = api.injectEndpoints({
|
|
14
16
|
endpoints: (build) => ({
|
|
15
17
|
// ==================== ASYNC API (SINGLE & MULTIPLE) ====================
|
|
16
18
|
|
|
19
|
+
// Start async virtual try-on process for a single product
|
|
20
|
+
virtualTryOnSingle: build.mutation<
|
|
21
|
+
VirtualTryOnAsyncResponse,
|
|
22
|
+
VirtualTryOnRequest
|
|
23
|
+
>({
|
|
24
|
+
query: (requestData) => ({
|
|
25
|
+
url: '/api/virtual-try-on?endpoint=single-try-on',
|
|
26
|
+
method: 'POST',
|
|
27
|
+
body: requestData
|
|
28
|
+
})
|
|
29
|
+
}),
|
|
30
|
+
|
|
17
31
|
// Start async virtual try-on process
|
|
18
32
|
// Supports 1-3 products (always uses multiple endpoint)
|
|
19
33
|
virtualTryOnAsync: build.mutation<
|
|
@@ -21,7 +35,7 @@ export const virtualTryOnApi = api.injectEndpoints({
|
|
|
21
35
|
VirtualTryOnAsyncRequest
|
|
22
36
|
>({
|
|
23
37
|
query: (requestData) => ({
|
|
24
|
-
url: '/api/virtual-try-on?endpoint=
|
|
38
|
+
url: '/api/virtual-try-on?endpoint=multiple-try-on',
|
|
25
39
|
method: 'POST',
|
|
26
40
|
body: requestData
|
|
27
41
|
})
|
|
@@ -30,16 +44,41 @@ export const virtualTryOnApi = api.injectEndpoints({
|
|
|
30
44
|
// Get job status for async process (polling endpoint)
|
|
31
45
|
getVirtualTryOnJobStatus: build.query<
|
|
32
46
|
VirtualTryOnJobStatusResponse,
|
|
33
|
-
string //
|
|
47
|
+
string // process_id
|
|
34
48
|
>({
|
|
35
|
-
query: (
|
|
36
|
-
url: `/api/virtual-try-on?endpoint=job-status&
|
|
49
|
+
query: (processId) => ({
|
|
50
|
+
url: `/api/virtual-try-on?endpoint=job-status&process_id=${encodeURIComponent(
|
|
51
|
+
processId
|
|
52
|
+
)}`,
|
|
37
53
|
method: 'GET'
|
|
38
54
|
}),
|
|
39
55
|
// Don't cache - used for polling
|
|
40
56
|
keepUnusedDataFor: 0
|
|
41
57
|
}),
|
|
42
58
|
|
|
59
|
+
// ==================== USAGE & EVENTS ====================
|
|
60
|
+
|
|
61
|
+
// Get try-on usage counts for given SKUs
|
|
62
|
+
getTryOnUsage: build.mutation<TryOnUsageResponse, { skus: string[] }>({
|
|
63
|
+
query: (body) => ({
|
|
64
|
+
url: '/api/virtual-try-on?endpoint=usage',
|
|
65
|
+
method: 'POST',
|
|
66
|
+
body
|
|
67
|
+
})
|
|
68
|
+
}),
|
|
69
|
+
|
|
70
|
+
// Track try-on related events (add to cart, purchase, etc.)
|
|
71
|
+
submitTryOnEvent: build.mutation<
|
|
72
|
+
TryOnEventResponse,
|
|
73
|
+
{ sku: string; event_name: string }
|
|
74
|
+
>({
|
|
75
|
+
query: (body) => ({
|
|
76
|
+
url: '/api/virtual-try-on?endpoint=event',
|
|
77
|
+
method: 'POST',
|
|
78
|
+
body
|
|
79
|
+
})
|
|
80
|
+
}),
|
|
81
|
+
|
|
43
82
|
// ==================== FEEDBACK & CATEGORIES ====================
|
|
44
83
|
|
|
45
84
|
// Submit feedback for try-on result
|
|
@@ -76,12 +115,18 @@ export const virtualTryOnApi = api.injectEndpoints({
|
|
|
76
115
|
});
|
|
77
116
|
|
|
78
117
|
export const {
|
|
118
|
+
// Async API (single product)
|
|
119
|
+
useVirtualTryOnSingleMutation,
|
|
79
120
|
// Async API (single & multiple products, 1-3 products)
|
|
80
121
|
useVirtualTryOnAsyncMutation,
|
|
81
122
|
// Job status polling
|
|
82
123
|
useGetVirtualTryOnJobStatusQuery,
|
|
83
124
|
useLazyGetVirtualTryOnJobStatusQuery,
|
|
84
125
|
|
|
126
|
+
// Usage & events
|
|
127
|
+
useGetTryOnUsageMutation,
|
|
128
|
+
useSubmitTryOnEventMutation,
|
|
129
|
+
|
|
85
130
|
// Common endpoints
|
|
86
131
|
useGetVirtualTryOnLimitedCategoriesQuery,
|
|
87
132
|
useSubmitVirtualTryOnFeedbackMutation
|