@pitvox/partner-react 0.7.24 → 0.7.25

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 CHANGED
@@ -175,6 +175,112 @@ import {
175
175
 
176
176
  **`getCompetitionPodium(standings, topN?)`** — Extract the top N drivers from a standings payload.
177
177
 
178
+ ## Promotions
179
+
180
+ Partner-run community promotions. The first (and currently only) type is `giveaway` — a one-click prize draw: drivers sign in with Steam, hit Enter, and the partner picks winners manually and publishes them. The `type` field is designed to grow (e.g. discount codes, free trials) without new hooks or components.
181
+
182
+ Promotions are managed by the partner on pitvox.com; the SDK is the read + entry surface for partner sites. Public data (config, entrants, winners) comes from the CDN; entry/withdraw go through the same backend-proxy pattern as competition registration.
183
+
184
+ ### Drop-in composite (recommended)
185
+
186
+ ```jsx
187
+ import { PromotionExplorer } from '@pitvox/partner-react'
188
+ import '@pitvox/partner-react/styles.css'
189
+
190
+ function GiveawaysPage() {
191
+ return <PromotionExplorer title="Giveaways" />
192
+ }
193
+ ```
194
+
195
+ `PromotionExplorer` is the whole experience in one component: a card grid that drills into a detail view (poster, markdown description, one-click entry with a public-display disclosure, live entrants grid, and the winners panel once announced). Selection lives in the `?promotion=` URL search param via the History API — links are shareable and the browser back button works — so it needs no router.
196
+
197
+ | Prop | Type | Default | Description |
198
+ |------|------|---------|-------------|
199
+ | `title` | `string` | `'Promotions'` | Page heading (pass `''` to hide it) |
200
+ | `driverData` | `object` | — | Extra fields sent to the enter callback (e.g. `displayName`, `avatarUrl`); usually unnecessary since the backend derives identity from the Steam token |
201
+ | `className` | `string` | — | Additional class on root container |
202
+
203
+ ### Hooks
204
+
205
+ ```jsx
206
+ import {
207
+ usePromotions,
208
+ usePromotionConfig,
209
+ usePromotionEntryList,
210
+ usePromotionEntryStatus,
211
+ } from '@pitvox/partner-react'
212
+ ```
213
+
214
+ **`usePromotions()`** — All active promotions for this partner (or all promotions in global mode). The CDN index carries active promotions only, so an empty result means nothing is running — handy for conditionally showing a nav link.
215
+
216
+ **`usePromotionConfig(promotionId, options?)`** — Single promotion config (title, prize, markdown description, entry window, winners).
217
+
218
+ **`usePromotionEntryList(promotionId, options?)`** — Entrants (Steam ID, display name, avatar, entered-at). `null` until the first entry.
219
+
220
+ **`usePromotionEntryStatus(promotionId)`** — Whether the current user (via `getSteamId`) has entered, plus the entry list. Lightweight CDN check.
221
+
222
+ All detail hooks accept `options.partnerSlug` to override the provider's slug (useful in global mode).
223
+
224
+ ### Styled components
225
+
226
+ ```jsx
227
+ import {
228
+ PromotionExplorer, PromotionCards, PromotionCard,
229
+ PromotionDetail, EnterButton,
230
+ } from '@pitvox/partner-react'
231
+ import '@pitvox/partner-react/styles.css'
232
+ ```
233
+
234
+ - **`<PromotionExplorer>`** — The drop-in composite above.
235
+ - **`<PromotionCards>`** — Card grid with posters, status/type badges, prize, entry window, and entry count. Adapts the layout to the card count (a lone promotion renders as one wide centred card). Props: `promotions`, `isLoading`, `onSelect`, `className`.
236
+ - **`<PromotionCard>`** — Individual card, for when you want to control the grid yourself. Props: `promo`, `onSelect`.
237
+ - **`<PromotionDetail>`** — Full detail view: poster, markdown body, entrants grid, the entry action with public-display disclosure, and the winners panel. Self-contained — fetches via hooks. Props: `promotionId`, `driverData`, `onBack` (omit to hide the back link), `className`.
238
+ - **`<EnterButton>`** — Enter/withdraw toggle. Render-prop or default button, with the same basic/power-mode behaviour as `RegisterButton` (see below). Props: `promotionId`, `driverData`, `className`, `children` (render prop).
239
+
240
+ ### Entry modes
241
+
242
+ Like registration, entry has two modes determined by whether you provide callbacks to the provider.
243
+
244
+ **Basic mode (default)** — no configuration; `EnterButton` and the detail view render a link to pitvox.com where the user enters with Steam.
245
+
246
+ **Power mode** — for partners with a backend proxying to pitvox-api (keeping the partner API key server-side), provide the callbacks. The Steam ID is derived server-side from the user's token, not from client input:
247
+
248
+ ```jsx
249
+ <PitVoxPartnerProvider
250
+ partnerSlug="your-slug"
251
+ getSteamId={() => user?.steamId ?? null}
252
+ onEnterPromotion={async (promotionId, driverData) => {
253
+ // Call your backend (e.g. AppSync mutation) → pitvox-api
254
+ await client.graphql({ query: ENTER_PROMOTION, variables: { promotionId, ...driverData } })
255
+ }}
256
+ onWithdrawPromotionEntry={async (promotionId, steamId) => {
257
+ await client.graphql({ query: WITHDRAW_PROMOTION_ENTRY, variables: { promotionId } })
258
+ }}
259
+ >
260
+ ```
261
+
262
+ Entry/withdraw hooks for fully custom UIs:
263
+
264
+ ```jsx
265
+ import { useEnterPromotion, useWithdrawPromotionEntry, usePromotionMode, usePromotionUrl } from '@pitvox/partner-react'
266
+ ```
267
+
268
+ **`useEnterPromotion(promotionId)`** — Mutation delegating to `onEnterPromotion`, with optimistic entry-list updates.
269
+
270
+ **`useWithdrawPromotionEntry(promotionId)`** — Mutation delegating to `onWithdrawPromotionEntry`. Withdrawals are only accepted while entries are open.
271
+
272
+ **`usePromotionMode()`** — Returns `{ isPowerMode, isBasicMode }`.
273
+
274
+ **`usePromotionUrl(promotionId)`** — pitvox.com promotion URL for basic mode.
275
+
276
+ ### Status helper
277
+
278
+ ```jsx
279
+ import { getPromotionStatus, PROMOTION_STATUS_LABELS } from '@pitvox/partner-react'
280
+ ```
281
+
282
+ **`getPromotionStatus(promotion, now?)`** — Returns `'upcoming'` | `'open'` | `'closed'` | `'winners'`, derived from the clock against `opensAt`/`closesAt` (the CDN never stores a computed open/closed flag, so files can't go stale between syncs). `PROMOTION_STATUS_LABELS` maps each to a display string.
283
+
178
284
  ## Driver Dashboard
179
285
 
180
286
  ### Drop-in composite
package/dist/styles.css CHANGED
@@ -3163,7 +3163,7 @@
3163
3163
 
3164
3164
  .pvx-promo-disclosure {
3165
3165
  font-size: 0.75rem;
3166
- color: var(--pvx-text-dimmed);
3166
+ color: var(--pvx-text-muted);
3167
3167
  margin: 0.25rem 0 0;
3168
3168
  }
3169
3169
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pitvox/partner-react",
3
- "version": "0.7.24",
3
+ "version": "0.7.25",
4
4
  "description": "React hooks and styled components for PitVox partner websites — leaderboards, competitions, driver dashboards",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",