@pitvox/partner-react 0.7.23 → 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
@@ -2974,7 +2974,7 @@
2974
2974
 
2975
2975
  .pvx-promo-card-prize {
2976
2976
  font-size: 0.875rem;
2977
- color: var(--pvx-text-muted);
2977
+ color: var(--pvx-text);
2978
2978
  margin: 0;
2979
2979
  display: -webkit-box;
2980
2980
  -webkit-line-clamp: 2;
@@ -2988,7 +2988,7 @@
2988
2988
  gap: 0.25rem;
2989
2989
  margin-top: auto;
2990
2990
  font-size: 0.75rem;
2991
- color: var(--pvx-text-dimmed);
2991
+ color: var(--pvx-text-muted);
2992
2992
  }
2993
2993
 
2994
2994
  /* ─── Badges ─── */
@@ -3061,11 +3061,14 @@
3061
3061
  border-radius: var(--pvx-radius);
3062
3062
  overflow: hidden;
3063
3063
  background: #0d1117;
3064
+ /* Match the card's 16:9 crop so the framing is identical between the
3065
+ grid and the opened detail view. */
3066
+ aspect-ratio: 16 / 9;
3064
3067
  }
3065
3068
 
3066
3069
  .pvx-promo-detail-poster img {
3067
3070
  width: 100%;
3068
- max-height: 24rem;
3071
+ height: 100%;
3069
3072
  object-fit: cover;
3070
3073
  display: block;
3071
3074
  }
@@ -3085,7 +3088,7 @@
3085
3088
 
3086
3089
  .pvx-promo-detail-prize {
3087
3090
  font-size: 1rem;
3088
- color: var(--pvx-text-muted);
3091
+ color: var(--pvx-text);
3089
3092
  margin: 0;
3090
3093
  }
3091
3094
 
@@ -3094,7 +3097,7 @@
3094
3097
  gap: 1rem;
3095
3098
  flex-wrap: wrap;
3096
3099
  font-size: 0.8125rem;
3097
- color: var(--pvx-text-dimmed);
3100
+ color: var(--pvx-text-muted);
3098
3101
  }
3099
3102
 
3100
3103
  .pvx-promo-section-title {
@@ -3160,7 +3163,7 @@
3160
3163
 
3161
3164
  .pvx-promo-disclosure {
3162
3165
  font-size: 0.75rem;
3163
- color: var(--pvx-text-dimmed);
3166
+ color: var(--pvx-text-muted);
3164
3167
  margin: 0.25rem 0 0;
3165
3168
  }
3166
3169
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pitvox/partner-react",
3
- "version": "0.7.23",
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",