@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 +106 -0
- package/dist/styles.css +9 -6
- package/package.json +1 -1
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
|
|
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-
|
|
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
|
-
|
|
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
|
|
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-
|
|
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-
|
|
3166
|
+
color: var(--pvx-text-muted);
|
|
3164
3167
|
margin: 0.25rem 0 0;
|
|
3165
3168
|
}
|
|
3166
3169
|
|
package/package.json
CHANGED