vetir-style-this-piece 1.0.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/README.md ADDED
@@ -0,0 +1,568 @@
1
+ # vetir-style-this-piece
2
+
3
+ A React component that adds **AI outfit suggestions** to any product page. The shopper sees a "Style this Piece" button, clicks it, and gets a carousel of complete outfits built around that product.
4
+
5
+ Works with **Vite**, **Create React App**, **Next.js**, or any React 16.8+ app.
6
+
7
+ ## Preview
8
+
9
+ ![Style This Piece demo — click to stream outfits and browse results](https://unpkg.com/vetir-style-this-piece/docs/style-this-piece-demo-flow.gif)
10
+
11
+ _Live demo showing multiple banner configurations (custom CTA, brand colours, compact button) and AI outfit results._
12
+
13
+ ---
14
+
15
+ ## What it does
16
+
17
+ 1. You place the component on a product page.
18
+ 2. The shopper clicks the banner button.
19
+ 3. The library calls your AI stylist API and **streams outfit results** in real time.
20
+ 4. Outfits appear as cards below the banner (or on a page you build yourself).
21
+
22
+ You do **not** need to write the API call, loading states, or carousel — the default component handles all of that.
23
+
24
+ ---
25
+
26
+ ## Before you start
27
+
28
+ Make sure you have these ready:
29
+
30
+ | You need | What it is |
31
+ | --------------------- | ------------------------------------------------------------------------ |
32
+ | **Partner API token** | A key from your API provider. The library sends this with every request. |
33
+ | **API base URL** | Your stylist API root URL (e.g. `https://api.example.com/s1/`). |
34
+ | **Product ID** | The ID of the product on the page (from your product data). |
35
+ | **User ID** | A user ID or guest ID string. Required for the API to work. |
36
+ | **React app** | React 16.8 or newer (`react` and `react-dom` installed in your project). |
37
+
38
+ > Pass `productId`, `userId`, `token`, and `apiBaseUrl`. If any are missing, the banner shows a clear error message. The library does not ship a default API URL.
39
+
40
+ ---
41
+
42
+ ## Step-by-step setup
43
+
44
+ ### Step 1 — Install the package
45
+
46
+ ```bash
47
+ npm install vetir-style-this-piece
48
+ ```
49
+
50
+ Run this in your project folder (the same place as your `package.json`). After installing, you should see it inside `node_modules/vetir-style-this-piece/`.
51
+
52
+ ### Step 2 — Import the CSS (once)
53
+
54
+ Add this **one line** to your app entry file. If you skip this, the banner and cards will look unstyled.
55
+
56
+ **Vite / Create React App** — `src/main.jsx` or `src/index.js`:
57
+
58
+ ```js
59
+ import "vetir-style-this-piece/style.css";
60
+ ```
61
+
62
+ **Next.js** — `pages/_app.js` or `app/layout.js`:
63
+
64
+ ```js
65
+ import "vetir-style-this-piece/style.css";
66
+ ```
67
+
68
+ ### Step 3 — Add the component to your product page
69
+
70
+ ```jsx
71
+ import StyleThisPiece from "vetir-style-this-piece";
72
+
73
+ function ProductPage({ product }) {
74
+ const userId = getLoggedInUserId() || getGuestUserId();
75
+ const apiToken = import.meta.env.VITE_STP_API_KEY;
76
+ const apiBaseUrl = import.meta.env.VITE_STP_API_BASE_URL;
77
+
78
+ return (
79
+ <div>
80
+ {/* your existing product details */}
81
+ <h1>{product.name}</h1>
82
+
83
+ <StyleThisPiece
84
+ productId={product.id}
85
+ userId={userId}
86
+ token={apiToken}
87
+ apiBaseUrl={apiBaseUrl}
88
+ />
89
+ </div>
90
+ );
91
+ }
92
+ ```
93
+
94
+ That is the **minimum** working setup. The banner appears, outfits stream in below it when clicked.
95
+
96
+ ### Step 4 — Add a "Shop the look" button (optional)
97
+
98
+ By default, outfit cards have no button. Pass `onCardCta` to handle clicks:
99
+
100
+ ```jsx
101
+ <StyleThisPiece
102
+ productId={product.id}
103
+ userId={userId}
104
+ token={apiToken}
105
+ apiBaseUrl={apiBaseUrl}
106
+ cardCtaText="Shop The Look"
107
+ onCardCta={(outfit) => {
108
+ // outfit is an array of products (top, bottom, shoes, bag, etc.)
109
+ openOutfitModal(outfit);
110
+ }}
111
+ />
112
+ ```
113
+
114
+ ### Step 5 — Match your brand (optional)
115
+
116
+ Override text and colours with props — no CSS file edits needed:
117
+
118
+ ```jsx
119
+ <StyleThisPiece
120
+ productId={product.id}
121
+ userId={userId}
122
+ token={apiToken}
123
+ apiBaseUrl={apiBaseUrl}
124
+ bannerText="See how to wear this piece."
125
+ ctaText="Get Outfit Ideas"
126
+ ctaStyle={{ background: "#000", color: "#fff" }}
127
+ />
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Understanding the required props
133
+
134
+ | Prop | In plain English |
135
+ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
136
+ | `productId` | Which product to build outfits around. Use the same ID your catalogue uses. |
137
+ | `userId` | Who is asking for outfits. Use a real user ID or a guest ID. If empty, the banner shows **"User ID is required"**. |
138
+ | `token` | Your partner API key. If empty, the banner shows **"API token is required"**. |
139
+ | `apiBaseUrl` | Your stylist API base URL. If empty, the banner shows **"API base URL is required"**. Not hardcoded in the library — you must pass it. |
140
+
141
+ **Guest users:** Most apps pass `authUserId || guestUserId`. As long as `userId` is a non-empty string, the API is called.
142
+
143
+ **Token security:** Load the token from your server or a build-time env var (e.g. `import.meta.env.VITE_STP_TOKEN`). The token is sent from the browser to the stylist API.
144
+
145
+ ---
146
+
147
+ ## What happens when the user clicks
148
+
149
+ ```
150
+ User clicks "Style this Piece"
151
+
152
+ Is productId set? ──no──→ Banner shows "Product ID is required"
153
+
154
+ yes
155
+
156
+ Is userId set? ──no──→ Banner shows "User ID is required"
157
+
158
+ yes
159
+
160
+ Is token set? ──no──→ Banner shows "API token is required"
161
+
162
+ yes
163
+
164
+ Is apiBaseUrl set? ──no──→ Banner shows "API base URL is required"
165
+
166
+ yes
167
+
168
+ Library calls the streaming API with productId + userId + token + apiBaseUrl
169
+
170
+ Outfits arrive one by one (loading spinner while waiting)
171
+
172
+ Cards appear in a horizontal carousel below the banner
173
+
174
+ User clicks a card CTA (if you provided onCardCta)
175
+
176
+ Your onCardCta(outfit) runs with the full outfit array
177
+ ```
178
+
179
+ ---
180
+
181
+ ## Choose your integration style
182
+
183
+ | Approach | Best for | What you use |
184
+ | ------------------------ | ---------------------------------------------------- | ------------------------------------------------------------------ |
185
+ | **All-in-one** (easiest) | Most product pages | `<StyleThisPiece />` — banner, API, and results included |
186
+ | **Hook + banner** | Custom results UI (new page, modal, your own layout) | `useStyleThisPieceOutfits` + `StyleThisPieceBanner` |
187
+ | **Full control** | Completely custom flow | Hook + `StyleThisPieceBanner` + `StyleThisPieceResults` separately |
188
+
189
+ **Start with the all-in-one component.** Only switch to the hook if you need to render results somewhere else.
190
+
191
+ ---
192
+
193
+ ## Examples
194
+
195
+ ### Example 1 — Custom results page (hook + banner)
196
+
197
+ **When to use:** You want outfits on a separate page or in your own design — not the built-in carousel.
198
+
199
+ The hook fetches outfits. The banner is just the trigger button. You decide where results go.
200
+
201
+ **Trigger (product page):**
202
+
203
+ ```jsx
204
+ import {
205
+ StyleThisPieceBanner,
206
+ useStyleThisPieceOutfits,
207
+ } from "vetir-style-this-piece";
208
+
209
+ function StyleTrigger({ productId, userId, token, apiBaseUrl, onSeeResults }) {
210
+ const { outfits, isLoading, error, generateOutfits } =
211
+ useStyleThisPieceOutfits({
212
+ productId,
213
+ userId,
214
+ token,
215
+ baseUrl: apiBaseUrl,
216
+ });
217
+
218
+ const hasResults = outfits.length > 0;
219
+
220
+ return (
221
+ <>
222
+ <StyleThisPieceBanner
223
+ isLoading={isLoading}
224
+ error={hasResults ? null : error}
225
+ onGenerate={generateOutfits}
226
+ showCtaIcon={false}
227
+ />
228
+ {hasResults && (
229
+ <button type="button" onClick={() => onSeeResults(outfits)}>
230
+ See Results
231
+ </button>
232
+ )}
233
+ </>
234
+ );
235
+ }
236
+ ```
237
+
238
+ **Results page (your own UI):**
239
+
240
+ ```jsx
241
+ function OutfitResultsPage({ outfits }) {
242
+ return (
243
+ <ul>
244
+ {outfits.map((outfit, i) => (
245
+ <li key={i}>
246
+ <h2>Look {i + 1}</h2>
247
+ <ul>
248
+ {outfit.map((product) => (
249
+ <li key={product.id}>
250
+ <img src={product.imageUrls?.[0]} alt={product.productName} />
251
+ <span>
252
+ {product.brandName} — {product.productName}
253
+ </span>
254
+ <span>${product.productPrice?.toLocaleString()}</span>
255
+ {product.productButtonLink && (
256
+ <a
257
+ href={product.productButtonLink}
258
+ target="_blank"
259
+ rel="noreferrer"
260
+ >
261
+ View
262
+ </a>
263
+ )}
264
+ </li>
265
+ ))}
266
+ </ul>
267
+ </li>
268
+ ))}
269
+ </ul>
270
+ );
271
+ }
272
+ ```
273
+
274
+ **Wire with your router:**
275
+
276
+ ```jsx
277
+ <StyleTrigger
278
+ productId={productId}
279
+ userId={userId}
280
+ token={token}
281
+ apiBaseUrl={apiBaseUrl}
282
+ onSeeResults={(outfits) => navigate("/outfits", { state: { outfits } })}
283
+ />
284
+ ```
285
+
286
+ ---
287
+
288
+ ### Example 2 — Custom card CTA text
289
+
290
+ Change the button label on each outfit card:
291
+
292
+ ```jsx
293
+ <StyleThisPiece
294
+ productId={productId}
295
+ userId={userId}
296
+ token={token}
297
+ apiBaseUrl={apiBaseUrl}
298
+ cardCtaText="Explore This Look"
299
+ onCardCta={(outfit) => openOutfitModal(outfit)}
300
+ />
301
+ ```
302
+
303
+ ---
304
+
305
+ ### Example 3 — Brand colours and copy override
306
+
307
+ Change all visible text and colours without touching CSS files:
308
+
309
+ ```jsx
310
+ <StyleThisPiece
311
+ productId={productId}
312
+ userId={userId}
313
+ token={token}
314
+ apiBaseUrl={apiBaseUrl}
315
+ bannerText="Discover complete looks featuring this item."
316
+ ctaText="Get Styled"
317
+ resultsTitle="Complete The Look"
318
+ resultsDescription="AI-curated outfits built around your selection."
319
+ bannerStyle={{ background: "white", borderColor: "#444" }}
320
+ ctaStyle={{ background: "#d4af37", color: "#000" }}
321
+ />
322
+ ```
323
+
324
+ ---
325
+
326
+ ### Example 4 — Compact round CTA + card extras
327
+
328
+ A small round icon button and extra info under each card (item count, total price):
329
+
330
+ ```jsx
331
+ <StyleThisPiece
332
+ productId={productId}
333
+ userId={userId}
334
+ token={token}
335
+ apiBaseUrl={apiBaseUrl}
336
+ bannerText=""
337
+ ctaText=""
338
+ bannerStyle={{ borderRadius: 60, padding: "8px", width: 120, height: 120 }}
339
+ ctaStyle={{
340
+ width: 60,
341
+ padding: 0,
342
+ justifyContent: "center",
343
+ height: 60,
344
+ borderRadius: 60,
345
+ }}
346
+ renderCardExtra={(outfit) => (
347
+ <div style={{ padding: "8px 12px", fontSize: 12, color: "#555" }}>
348
+ {outfit.length} items · Est. $
349
+ {outfit.reduce((s, p) => s + (p.productPrice || 0), 0).toLocaleString()}
350
+ </div>
351
+ )}
352
+ />
353
+ ```
354
+
355
+ ---
356
+
357
+ ### Example 5 — Custom label above the carousel
358
+
359
+ Add a branded strip above the outfit cards:
360
+
361
+ ```jsx
362
+ <StyleThisPiece
363
+ productId={productId}
364
+ userId={userId}
365
+ token={token}
366
+ apiBaseUrl={apiBaseUrl}
367
+ renderResultsHeader={() => (
368
+ <div
369
+ style={{
370
+ padding: "8px 16px",
371
+ background: "#222",
372
+ color: "#fff",
373
+ fontSize: 12,
374
+ letterSpacing: 2,
375
+ textTransform: "uppercase",
376
+ textAlign: "center",
377
+ }}
378
+ >
379
+ ✦ Styled by AI · Personalised for you
380
+ </div>
381
+ )}
382
+ />
383
+ ```
384
+
385
+ ---
386
+
387
+ ### Full production integration
388
+
389
+ Everything together: custom API URL, results in a specific page section, card CTA, header, and styling:
390
+
391
+ ```jsx
392
+ const resultsRef = useRef(null);
393
+
394
+ <StyleThisPiece
395
+ productId={productId}
396
+ userId={userId}
397
+ token={token}
398
+ apiBaseUrl={apiBaseUrl}
399
+ gender="female"
400
+ resultsContainerRef={resultsRef}
401
+ cardCtaText="Shop The Look"
402
+ onCardCta={(outfit) => openOutfitModal(outfit)}
403
+ renderResultsHeader={() => (
404
+ <div
405
+ style={{
406
+ padding: "8px 16px",
407
+ background: "#222",
408
+ color: "#fff",
409
+ fontSize: 12,
410
+ }}
411
+ >
412
+ ✦ AI Stylist Picks
413
+ </div>
414
+ )}
415
+ renderCardExtra={(outfit) => (
416
+ <div style={{ padding: "8px 12px", fontSize: 12, color: "#555" }}>
417
+ {outfit.length} items
418
+ </div>
419
+ )}
420
+ resultsTitle="Styled For You"
421
+ resultsDescription="AI-curated outfits built around this piece."
422
+ bannerStyle={{ borderColor: "#333" }}
423
+ ctaStyle={{ background: "#c9a96e" }}
424
+ />;
425
+
426
+ {
427
+ /* Results render inside this div instead of below the banner */
428
+ }
429
+ <div ref={resultsRef} />;
430
+ ```
431
+
432
+ `resultsContainerRef` lets you control **where** on the page the carousel appears — useful if your layout has a sidebar or a dedicated "stylist" section.
433
+
434
+ ---
435
+
436
+ ## Props reference
437
+
438
+ ### `StyleThisPiece` (default export — recommended)
439
+
440
+ | Prop | Required | Description |
441
+ | --------------------------------- | -------- | ---------------------------------------------------------------------- |
442
+ | `productId` | ✅ | Product to style |
443
+ | `userId` | ✅ | User or guest ID. Empty → banner shows "User ID is required" |
444
+ | `token` | ✅ | Partner API key |
445
+ | `apiBaseUrl` | ✅ | Stylist API base URL. Empty → banner shows "API base URL is required" |
446
+ | `gender` | — | `female` (default) or `male` |
447
+ | `country` | — | Region for pricing. Default: from cookie or `United States of America` |
448
+ | `hideBannerOnResults` | — | Hide banner once results show. Default `false` |
449
+ | `resultsContainerRef` | — | Render results inside this DOM element instead of inline |
450
+ | `onCardCta` | — | Called when card button clicked. Button only shows if this is set |
451
+ | `cardCtaText` | — | Card button label (e.g. `"Shop The Look"`) |
452
+ | `renderResultsHeader` | — | Custom JSX above the carousel |
453
+ | `renderCardExtra` | — | Custom JSX inside each outfit card |
454
+ | `bannerText` | — | Text next to the CTA button |
455
+ | `ctaText` | — | CTA button label |
456
+ | `showCtaIcon` | — | Show sparkle icon. Default `true` |
457
+ | `ctaIcon` | — | Replace sparkle with your own image URL |
458
+ | `resultsTitle` | — | Heading above the carousel |
459
+ | `resultsDescription` | — | Subheading above the carousel |
460
+ | `bannerStyle` / `ctaStyle` / etc. | — | Inline styles to match your brand |
461
+
462
+ ### Named exports (advanced)
463
+
464
+ ```jsx
465
+ import {
466
+ StyleThisPieceBanner,
467
+ StyleThisPieceResults,
468
+ StyleThisPieceOutfitCard,
469
+ useStyleThisPieceOutfits,
470
+ } from "vetir-style-this-piece";
471
+ ```
472
+
473
+ #### `useStyleThisPieceOutfits`
474
+
475
+ ```ts
476
+ const {
477
+ outfits, // array of outfits; each outfit is an array of products
478
+ isLoading, // true until the first outfit arrives
479
+ isStreaming, // true while more outfits are still coming in
480
+ hasStarted, // true after the user clicked generate
481
+ error, // error message string, or null
482
+ generateOutfits, // call this to start (wired to banner button)
483
+ reset, // go back to the initial banner state
484
+ } = useStyleThisPieceOutfits({
485
+ productId,
486
+ userId,
487
+ token,
488
+ baseUrl, // required — same as apiBaseUrl on the main component
489
+ gender?,
490
+ country?,
491
+ });
492
+ ```
493
+
494
+ ---
495
+
496
+ ## What you get back from the API
497
+
498
+ Each **outfit** is an array of **products** (typically top, bottom, shoes, bag, jewelry):
499
+
500
+ ```ts
501
+ interface OutfitProduct {
502
+ id: string;
503
+ productId: string;
504
+ brandName: string;
505
+ productName: string;
506
+ productPrice: number;
507
+ imageUrls: string[]; // use imageUrls[0] for the card image
508
+ productColor: string;
509
+ categoryName: string;
510
+ aiCategoryName: "top" | "bottom" | "footwear" | "bag" | "jewelry";
511
+ productSizes: string[];
512
+ productButtonLink: string; // link to buy this item
513
+ returnObject: { url: string; text: string };
514
+ gender: string;
515
+ country: string[];
516
+ }
517
+ ```
518
+
519
+ When `onCardCta` or `renderCardExtra` fires, you receive one full outfit array — loop over it to show each item.
520
+
521
+ ---
522
+
523
+ ## Troubleshooting
524
+
525
+ | Problem | Likely cause | Fix |
526
+ | ------------------------------------------------------- | ------------------------ | ------------------------------------------------------------ |
527
+ | `Failed to resolve import "vetir-style-this-piece"` | Package not installed | Run `npm install` in your project |
528
+ | `Failed to resolve import "vetir-style-this-piece/style.css"` | Same as above | Run `npm install`, then restart dev server |
529
+ | Banner looks broken / no styles | CSS not imported | Add `import 'vetir-style-this-piece/style.css'` to your entry file |
530
+ | Banner says "Product ID is required" | `productId` is empty | Pass the product ID from your product data |
531
+ | Banner says "User ID is required" | `userId` is empty | Pass `authUserId \|\| guestUserId` |
532
+ | Banner says "API token is required" | `token` prop missing | Pass your partner API key |
533
+ | Banner says "API base URL is required" | `apiBaseUrl` missing | Pass your stylist API base URL |
534
+ | Stream fails / Retry shown | Bad token or API error | Check token value and API base URL |
535
+ | Card has no button | `onCardCta` not provided | Add `onCardCta` and `cardCtaText` together |
536
+ | Results in wrong place | Layout issue | Use `resultsContainerRef` to target a specific `<div>` |
537
+
538
+ ---
539
+
540
+ ## Peer dependencies
541
+
542
+ Your app must already have React installed:
543
+
544
+ ```json
545
+ "react": ">=16.8.0",
546
+ "react-dom": ">=16.8.0"
547
+ ```
548
+
549
+ The library does not bundle React — it uses yours.
550
+
551
+ ---
552
+
553
+ ## Local development (yalc)
554
+
555
+ For testing unreleased changes in another project:
556
+
557
+ ```bash
558
+ # In this repo — after every change:
559
+ npm run push
560
+
561
+ # In consumer — one-time setup:
562
+ yalc add vetir-style-this-piece
563
+ yarn install
564
+ ```
565
+
566
+ ---
567
+
568
+ More examples: [USAGE.md](./USAGE.md)