@pitvox/partner-react 0.3.0 → 0.4.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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # @pitvox/partner-react
2
2
 
3
- React SDK for PitVox partner websites. Provides hooks, components, and complete leaderboard + competition experiences for sim racing communities affiliated with [PitVox](https://pitvox.com).
3
+ React SDK for PitVox partner websites. Provides hooks, styled components, and a driver dashboard for sim racing communities affiliated with [PitVox](https://pitvox.com).
4
+
5
+ **Hooks-first**: Use the data hooks with any UI framework (Tailwind, DaisyUI, Shadcn, Preline, etc.). The styled `pvx-*` components are optional building blocks for quick starts.
4
6
 
5
7
  ## Installation
6
8
 
@@ -10,10 +12,8 @@ npm install @pitvox/partner-react
10
12
 
11
13
  ### Peer dependencies
12
14
 
13
- The SDK requires these in your project:
14
-
15
15
  ```bash
16
- npm install react react-dom @tanstack/react-query react-router-dom
16
+ npm install react react-dom @tanstack/react-query
17
17
  ```
18
18
 
19
19
  ## Quick start
@@ -25,14 +25,12 @@ import { PitVoxPartnerProvider } from '@pitvox/partner-react'
25
25
 
26
26
  function App() {
27
27
  return (
28
- <BrowserRouter>
29
- <PitVoxPartnerProvider
30
- partnerSlug="your-slug"
31
- getSteamId={() => currentUser?.steamId ?? null}
32
- >
33
- {/* your app */}
34
- </PitVoxPartnerProvider>
35
- </BrowserRouter>
28
+ <PitVoxPartnerProvider
29
+ partnerSlug="your-slug"
30
+ getSteamId={() => currentUser?.steamId ?? null}
31
+ >
32
+ {/* your app */}
33
+ </PitVoxPartnerProvider>
36
34
  )
37
35
  }
38
36
  ```
@@ -41,48 +39,7 @@ function App() {
41
39
 
42
40
  ## Leaderboards
43
41
 
44
- ### Drop-in (recommended)
45
-
46
- One component gives you a complete 4-layer leaderboard with game tabs, version selector, tag filtering, sortable columns, sector highlighting, and drill-down navigation:
47
-
48
- ```jsx
49
- import { LeaderboardExplorer } from '@pitvox/partner-react'
50
- import '@pitvox/partner-react/styles.css'
51
-
52
- function LeaderboardsPage() {
53
- return <LeaderboardExplorer />
54
- }
55
- ```
56
-
57
- The `LeaderboardExplorer` manages all state via URL search parameters (`?game=evo&track=...&car=...&driver=...`), so deep-linking and browser back/forward work out of the box.
58
-
59
- **Props:**
60
-
61
- | Prop | Type | Default | Description |
62
- |------|------|---------|-------------|
63
- | `className` | `string` | — | Additional class on root container |
64
- | `defaultGame` | `'evo' \| 'acc'` | `'evo'` | Default game tab |
65
- | `title` | `string` | `'Leaderboards'` | Page heading |
66
-
67
- ### Layer components
68
-
69
- For more control, use the individual layer components directly. Each handles its own sorting, filtering, and highlighting logic:
70
-
71
- ```jsx
72
- import { TracksTable, CarsTable, DriversTable, LapHistoryTable } from '@pitvox/partner-react'
73
- import '@pitvox/partner-react/styles.css'
74
- ```
75
-
76
- - **`<TracksTable>`** — Layer 1: all tracks with record holders, tag filtering
77
- - **`<CarsTable>`** — Layer 2: cars for a selected track, tag filtering
78
- - **`<DriversTable>`** — Layer 3: drivers for a car with sectors (S1/S2/S3), tyre, fuel
79
- - **`<LapHistoryTable>`** — Layer 4: driver's lap history with validity and personal best highlighting
80
-
81
- You're responsible for wiring up the navigation between layers and managing URL state.
82
-
83
- ### Hooks only
84
-
85
- For fully custom UIs, use the data hooks directly:
42
+ ### Hooks
86
43
 
87
44
  ```jsx
88
45
  import {
@@ -96,12 +53,11 @@ import {
96
53
 
97
54
  **`useLeaderboardIndex(options?)`** — Fetch all tracks with record holders.
98
55
  - `options.game` — Filter by game (`'evo'` | `'acc'`)
99
- - `options.gameVersion` Filter by EVO version
100
- - Returns `{ data: Track[], isLoading, partner, generatedAt, totalLaps, totalUsers, versions }`
56
+ - Returns `{ data: Track[], isLoading, generatedAt, totalLaps, totalUsers, versions }`
101
57
 
102
58
  **`useTrackLeaderboard(trackId, layout?, options?)`** — Fetch track entries.
103
- - Without `options.carId`: returns best lap per car (Layer 2)
104
- - With `options.carId`: returns all drivers for that car (Layer 3)
59
+ - Without `options.carId`: returns best lap per car (car-level)
60
+ - With `options.carId`: returns all drivers for that car (driver-level)
105
61
  - Returns `{ data: Entry[], isLoading, error }`
106
62
 
107
63
  **`useDriverLaps(userId, trackId, layout, carId, options?)`** — Fetch a driver's lap history.
@@ -112,58 +68,25 @@ import {
112
68
 
113
69
  **`useCarMetadata()`** — Returns `{ tags: string[], cars: Record<string, { tags }> }` for tag filtering.
114
70
 
115
- ## Competitions
116
-
117
- ### Drop-in (recommended)
118
-
119
- One component gives you a complete competition experience with card grid, championship standings, round-by-round results with session tabs, and entry lists:
120
-
121
- ```jsx
122
- import { CompetitionExplorer } from '@pitvox/partner-react'
123
- import '@pitvox/partner-react/styles.css'
124
-
125
- function CompetitionsPage() {
126
- return <CompetitionExplorer />
127
- }
128
- ```
129
-
130
- The `CompetitionExplorer` manages all state via URL search parameters (`?competition=abc123&tab=standings&round=2`), so deep-linking and browser back/forward work out of the box.
131
-
132
- **Props:**
71
+ ### Styled components
133
72
 
134
- | Prop | Type | Default | Description |
135
- |------|------|---------|-------------|
136
- | `className` | `string` | — | Additional class on root container |
137
- | `title` | `string` | `'Competitions'` | Page heading |
138
-
139
- **Features:**
140
- - Competition card grid with poster images, type badges, format/car pills, schedule summary, registration status
141
- - Championship standings with position rank badges, nation flags, wins/podiums columns, per-round cells with dropped round styling
142
- - Round results with session tabs (Practice/Qualifying/Race), best lap with sector hover tooltip, podium highlighting
143
- - Entry list with driver avatars
144
- - Horizontal schedule strip with next-round highlighting
145
- - Breadcrumb navigation between list and detail views
146
-
147
- ### Layer components
148
-
149
- For more control, use the individual layer components directly:
73
+ For building leaderboard pages with the SDK's `pvx-*` styles:
150
74
 
151
75
  ```jsx
152
- import { CompetitionCards, StandingsTable, RoundResults, EntryList } from '@pitvox/partner-react'
76
+ import { TracksTable, CarsTable, DriversTable, LapHistoryTable } from '@pitvox/partner-react'
153
77
  import '@pitvox/partner-react/styles.css'
154
78
  ```
155
79
 
156
- - **`<CompetitionCards>`** — Competition card grid with posters, badges, schedule, and registration status
157
- - **`<StandingsTable>`** — Championship standings with per-round breakdowns, podium highlights, dropped rounds
158
- - **`<RoundResults>`** — Round results with session tab bar, driver/car detail, best sector highlighting
159
- - **`<EntryList>`** — Registered drivers grid with avatars
160
- - **`<RegisterButton>`** — Register/withdraw toggle (render prop or default button)
80
+ - **`<TracksTable>`** — All tracks with record holders, tag filtering, sorting
81
+ - **`<CarsTable>`** — Cars for a selected track with tag filtering
82
+ - **`<DriversTable>`** — Drivers for a car with sectors (S1/S2/S3), tyre, fuel
83
+ - **`<LapHistoryTable>`** — Driver's lap history with validity and personal best highlighting
161
84
 
162
- You're responsible for wiring up navigation between views and managing state.
85
+ You compose these into your own page layout and wire up navigation between layers. See the [partner templates](https://github.com/meekysoft/pitvox-partner-template) for a complete example using React Router.
163
86
 
164
- ### Hooks only
87
+ ## Competitions
165
88
 
166
- For fully custom UIs, use the data hooks directly:
89
+ ### Hooks
167
90
 
168
91
  ```jsx
169
92
  import {
@@ -187,13 +110,38 @@ import {
187
110
  **`useCompetitionAllRounds(competitionId, roundNumbers)`** — Fetch multiple round results in parallel.
188
111
 
189
112
  **`useCompetitionEntryList(competitionId)`** — Registered drivers.
113
+
114
+ ### Styled components
115
+
116
+ ```jsx
117
+ import {
118
+ CompetitionCards, StandingsTable, RoundResults, RoundSessionResults,
119
+ EntryList, RegisterButton, RegistrationPanel,
120
+ } from '@pitvox/partner-react'
121
+ import '@pitvox/partner-react/styles.css'
122
+ ```
123
+
124
+ - **`<CompetitionCards>`** — Card grid with posters, type badges, schedule, registration status
125
+ - **`<StandingsTable>`** — Championship standings with per-round breakdowns, podium highlights
126
+ - **`<RoundResults>`** — Standalone round results (fetches data, renders header + sessions)
127
+ - **`<RoundSessionResults>`** — Session tabs + results table (data-prop driven, no fetch)
128
+ - **`<EntryList>`** — Registered drivers grid with avatars
129
+ - **`<RegisterButton>`** — Register/withdraw toggle (render prop or default button)
130
+ - **`<RegistrationPanel>`** — Registration form + entry list with unregister
131
+
132
+ ### Shared utilities
133
+
134
+ Useful when composing competition pages:
135
+
136
+ ```jsx
137
+ import { TypeBadge, InfoPill, PODIUM_MEDALS, CompLoadingState, CompEmptyState } from '@pitvox/partner-react'
190
138
  ```
191
139
 
192
140
  ## Driver Dashboard
193
141
 
194
- ### Drop-in (recommended)
142
+ ### Drop-in composite
195
143
 
196
- One component gives you a complete driver dashboard with profile, stats cards, and current records:
144
+ The `DriverDashboard` is a self-contained component with no routing dependency:
197
145
 
198
146
  ```jsx
199
147
  import { DriverDashboard } from '@pitvox/partner-react'
@@ -210,8 +158,6 @@ function DashboardPage() {
210
158
  }
211
159
  ```
212
160
 
213
- **Props:**
214
-
215
161
  | Prop | Type | Default | Description |
216
162
  |------|------|---------|-------------|
217
163
  | `steamId` | `string` | — | Driver's Steam ID (required) |
@@ -219,50 +165,33 @@ function DashboardPage() {
219
165
  | `memberSince` | `string` | — | ISO date for "Racing since" display |
220
166
  | `className` | `string` | — | Additional class on root container |
221
167
 
222
- **Displays:**
223
- - Driver profile card (avatar, name, racing since)
224
- - Stats cards: total laps (with track breakdown tooltip), cars used (with car breakdown tooltip), driver rating
225
- - Current records list sorted by most recent, with track, car, lap time, and game badge
226
-
227
168
  ### Layer components
228
169
 
229
- For more control, use the individual components directly:
230
-
231
170
  ```jsx
232
171
  import { DriverProfile, StatsCards, RecordsTable } from '@pitvox/partner-react'
233
- import '@pitvox/partner-react/styles.css'
234
172
  ```
235
173
 
236
- - **`<DriverProfile>`** — Avatar, driver name, and "Racing since" date
237
- - **`<StatsCards>`** — Stats cards with icons and hover breakdown tooltips (accepts `stats` from `useDriverStats` and `rating` from `useDriverRating`)
238
- - **`<RecordsTable>`** — Compact records list sorted by most recent, with expand/collapse
239
-
240
- ### Hooks only
241
-
242
- For fully custom UIs, use the data hooks directly:
174
+ ### Hooks
243
175
 
244
176
  ```jsx
245
177
  import { useDriverStats, useDriverRating } from '@pitvox/partner-react'
246
178
  ```
247
179
 
248
- **`useDriverStats(steamId)`** — Fetch driver stats, records, and ranking from CDN.
249
- - Shares the same query cache as `useDriverLaps` (same CDN file)
250
- - Returns `{ data: { driverName, lapCount, trackBreakdown, carBreakdown, recordsHeld, currentRecords, bestRanking, bestRankingTrackId, bestRankingLayout, bestRankingCarId }, isLoading, error }`
180
+ **`useDriverStats(steamId)`** — Driver stats, records, and ranking from CDN.
251
181
 
252
- **`useDriverRating(steamId)`** — Fetch driver's rating from the partner ratings file.
253
- - Returns `{ data: { rating, rank, totalDrivers, comboCount, distinctCars, combos }, isLoading, error }`
182
+ **`useDriverRating(steamId)`** — Driver's rating from the partner ratings file.
254
183
 
255
184
  ## Registration
256
185
 
257
- The SDK supports two registration modes, determined automatically by whether you provide callbacks to the provider.
186
+ The SDK supports two registration modes, determined by whether you provide callbacks to the provider.
258
187
 
259
188
  ### Basic mode (default)
260
189
 
261
- No configuration needed. The `RegisterButton` and `CompetitionExplorer` render a link to the pitvox.com registration page, where users can sign in with Steam and register.
190
+ No configuration needed. Registration components render links to pitvox.com where users register with Steam.
262
191
 
263
192
  ### Power mode
264
193
 
265
- For partners with a backend (e.g. an Amplify Lambda that proxies to pitvox-api), provide `onRegister` and `onWithdraw` callbacks to the provider:
194
+ For partners with a backend (e.g. Amplify Lambda proxying to pitvox-api), provide callbacks:
266
195
 
267
196
  ```jsx
268
197
  <PitVoxPartnerProvider
@@ -277,29 +206,21 @@ For partners with a backend (e.g. an Amplify Lambda that proxies to pitvox-api),
277
206
  >
278
207
  ```
279
208
 
280
- The `RegisterButton` will then render as an in-app button instead of an external link.
281
-
282
- ### Hooks
209
+ ### Registration hooks
283
210
 
284
211
  ```jsx
285
- import {
286
- useRegistrationStatus,
287
- useRegister,
288
- useWithdraw,
289
- useRegistrationMode,
290
- useRegistrationUrl,
291
- } from '@pitvox/partner-react'
212
+ import { useRegistrationStatus, useRegister, useWithdraw, useRegistrationMode, useRegistrationUrl } from '@pitvox/partner-react'
292
213
  ```
293
214
 
294
- **`useRegistrationStatus(competitionId)`** — Check if current user (via `getSteamId`) is registered. Returns `{ data: { isRegistered, entryList } }`.
215
+ **`useRegistrationStatus(competitionId)`** — Check if current user is registered.
295
216
 
296
- **`useRegister(competitionId)`** — Returns a mutation. Delegates to `onRegister` callback. Throws if no callback provided.
217
+ **`useRegister(competitionId)`** — Mutation delegating to `onRegister` callback.
297
218
 
298
- **`useWithdraw(competitionId)`** — Returns a mutation. Delegates to `onWithdraw` callback. Throws if no callback provided.
219
+ **`useWithdraw(competitionId)`** — Mutation delegating to `onWithdraw` callback.
299
220
 
300
- **`useRegistrationMode()`** — Returns `{ isPowerMode, isBasicMode }` based on whether callbacks are provided.
221
+ **`useRegistrationMode()`** — Returns `{ isPowerMode, isBasicMode }`.
301
222
 
302
- **`useRegistrationUrl(competitionId)`** — Returns the pitvox.com registration URL for basic mode.
223
+ **`useRegistrationUrl(competitionId)`** — Returns pitvox.com registration URL for basic mode.
303
224
 
304
225
  ## Formatting utilities
305
226
 
@@ -322,36 +243,25 @@ The default stylesheet uses CSS custom properties. Override them to match your b
322
243
 
323
244
  ```css
324
245
  :root {
325
- --pvx-accent: #e11d48; /* your brand colour */
326
- --pvx-bg-card: #1a1a2e; /* card background */
327
- --pvx-sector-best: #22d3ee; /* best sector highlight */
328
- --pvx-rank-gold: #fbbf24; /* podium colours */
246
+ --pvx-accent: #e11d48;
247
+ --pvx-bg-card: #1a1a2e;
248
+ --pvx-sector-best: #22d3ee;
249
+ --pvx-rank-gold: #fbbf24;
329
250
  }
330
251
  ```
331
252
 
332
- All classes are prefixed with `pvx-` to avoid collisions with your existing styles. See `styles.css` for the full list of variables and classes.
253
+ All classes are prefixed with `pvx-` to avoid collisions. See `styles.css` for the full list of variables.
333
254
 
334
- ## CDN data paths
255
+ ## Partner templates
335
256
 
336
- The SDK reads pre-computed JSON from the PitVox CDN. No API key is needed for read-only leaderboard and competition data.
257
+ For a complete working site using this SDK, see:
337
258
 
338
- | Data | Path |
339
- |------|------|
340
- | Leaderboard index | `leaderboards/partners/{slug}/index.json` |
341
- | Track entries | `leaderboards/partners/{slug}/tracks/{trackId}/{layout}.json` |
342
- | Driver laps | `laps/partners/{slug}/{userId}.json` |
343
- | User display names | `users/index.json` |
344
- | Car metadata | `cars/index.json` |
345
- | Competition index | `competitions/index.json` |
346
- | Competition config | `competitions/{slug}/{id}/config.json` |
347
- | Standings | `competitions/{slug}/{id}/standings.json` |
348
- | Round results | `competitions/{slug}/{id}/rounds/{n}.json` |
349
- | Entry list | `competitions/{slug}/{id}/entrylist.json` |
350
- | Driver ratings | `leaderboards/partners/{slug}/ratings.json` |
259
+ - **[pitvox-partner-template](https://github.com/meekysoft/pitvox-partner-template)** Basic template (no auth)
260
+ - **[pitvox-partner-template-amplify](https://github.com/meekysoft/pitvox-partner-template-amplify)** — AWS Amplify template with Steam auth and in-app registration
351
261
 
352
- ## Local development
262
+ The templates demonstrate how to compose SDK hooks and components into full pages with routing.
353
263
 
354
- To develop against a local version of the SDK:
264
+ ## Local development
355
265
 
356
266
  ```bash
357
267
  # In the SDK repo
@@ -367,7 +277,7 @@ Add `resolve.dedupe` to your Vite config to avoid duplicate React instances:
367
277
  // vite.config.js
368
278
  export default defineConfig({
369
279
  resolve: {
370
- dedupe: ['react', 'react-dom', 'react-router-dom', '@tanstack/react-query'],
280
+ dedupe: ['react', 'react-dom', '@tanstack/react-query'],
371
281
  },
372
282
  })
373
283
  ```