@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 CHANGED
@@ -1,11 +1,12 @@
1
1
  # @akinon/pz-virtual-try-on
2
2
 
3
- ## 2.0.34-beta.0
3
+ ## 2.0.34
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - Updated dependencies [cbbbfd75]
8
- - @akinon/next@2.0.34-beta.0
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
- ## 🆕 Multiple Product API Documentation
2157
+ ## 🆕 Async Try-On API Documentation (Single & Multiple)
2149
2158
 
2150
2159
  ### Async Workflow
2151
2160
 
2152
- The new multiple product try-on uses an async workflow with polling:
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
- 1. POST /api/v1/async/multiple_virtual_try_on
2156
- ├─ Request: { sku, images, category_ids, reference_image, ... }
2157
- └─ Response: { reference_url, status: "pending" }
2158
-
2159
- 2. GET /api/v1/async/job_status?reference_url=...
2160
- ├─ Poll every 1+ second (max 60 attempts)
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: { status: "completed", multiple: [...] }
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
- 3. POST /api/v1/feedback
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
- ### Hook Usage: `useVirtualTryOnMultiple`
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 { useVirtualTryOnMultiple } from '@akinon/pz-virtual-try-on';
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
- } = useVirtualTryOnMultiple(products);
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} / 60</p>
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: 'https://...',
2277
- maxAttempts: 60,
2278
- interval: 1000, // 1 second
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.multiple);
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 60 attempts');
2323
+ console.warn('Timeout after max attempts');
2290
2324
  }
2291
2325
  });
2292
2326
 
2293
- await poller.start(async (url) => {
2294
- const response = await fetch(`/api/v1/async/job_status?reference_url=${url}`);
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-beta.0",
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-beta.0",
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",
@@ -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=async-multiple-try-on',
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 // reference_url
47
+ string // process_id
34
48
  >({
35
- query: (referenceUrl) => ({
36
- url: `/api/virtual-try-on?endpoint=job-status&reference_url=${encodeURIComponent(referenceUrl)}`,
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