@pitvox/partner-react 0.3.0 → 0.5.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:**
133
-
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
71
+ ### Styled components
148
72
 
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,84 @@ 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
168
+ The dashboard automatically includes:
169
+ - **Upcoming Events** competition rounds the driver is registered for (CDN-based, always available)
170
+ - **Notifications** only when `onFetchNotifications` is provided to the provider (see [Notifications](#notifications))
226
171
 
227
172
  ### Layer components
228
173
 
229
- For more control, use the individual components directly:
174
+ ```jsx
175
+ import { DriverProfile, StatsCards, RecordsTable, UpcomingEvents, NotificationsCard } from '@pitvox/partner-react'
176
+ ```
177
+
178
+ - **`<UpcomingEvents>`** — Upcoming competition rounds card (accepts `events` array from `useUpcomingEvents()`)
179
+ - **`<NotificationsCard>`** — Notifications list with read/unread state (accepts `notifications`, `unreadCount`, `onMarkRead`, `onMarkAllRead`)
180
+
181
+ ### Hooks
230
182
 
231
183
  ```jsx
232
- import { DriverProfile, StatsCards, RecordsTable } from '@pitvox/partner-react'
233
- import '@pitvox/partner-react/styles.css'
184
+ import { useDriverStats, useDriverRating, useUpcomingEvents } from '@pitvox/partner-react'
234
185
  ```
235
186
 
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
187
+ **`useDriverStats(steamId)`**Driver stats, records, and ranking from CDN.
188
+
189
+ **`useDriverRating(steamId)`**Driver's rating from the partner ratings file.
190
+
191
+ **`useUpcomingEvents()`** — Upcoming competition rounds the current user is registered for (CDN-based).
239
192
 
240
- ### Hooks only
193
+ ## Notifications
241
194
 
242
- For fully custom UIs, use the data hooks directly:
195
+ Notifications require a backend to proxy requests to pitvox-api (keeping the API key server-side). Provide callbacks to the provider:
243
196
 
244
197
  ```jsx
245
- import { useDriverStats, useDriverRating } from '@pitvox/partner-react'
198
+ <PitVoxPartnerProvider
199
+ partnerSlug="your-slug"
200
+ getSteamId={() => user?.steamId ?? null}
201
+ onFetchNotifications={async (params) => {
202
+ const res = await fetch(`/api/notifications?limit=${params.limit || 20}`)
203
+ return res.json() // { notifications: [...], unreadCount: number }
204
+ }}
205
+ onMarkNotificationRead={async (id) => {
206
+ await fetch(`/api/notifications/${id}/read`, { method: 'PATCH' })
207
+ }}
208
+ onMarkAllNotificationsRead={async () => {
209
+ await fetch('/api/notifications/read-all', { method: 'PATCH' })
210
+ }}
211
+ >
212
+ ```
213
+
214
+ When no callbacks are provided, notification hooks return disabled/empty state and `DriverDashboard` hides the notifications section.
215
+
216
+ ### Hooks
217
+
218
+ ```jsx
219
+ import {
220
+ useNotifications, useUnreadCount, useMarkNotificationRead,
221
+ useMarkAllNotificationsRead, useNotificationsEnabled,
222
+ } from '@pitvox/partner-react'
246
223
  ```
247
224
 
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 }`
225
+ **`useNotifications(options?)`** — Fetch notifications (polls every 30s). Returns `{ data: { notifications, unreadCount }, isLoading }`.
226
+
227
+ **`useUnreadCount()`** Unread count for navbar badges. Returns `{ count, isLoading }`.
228
+
229
+ **`useMarkNotificationRead()`** — Mutation to mark a notification as read.
230
+
231
+ **`useMarkAllNotificationsRead()`** — Mutation to mark all as read.
251
232
 
252
- **`useDriverRating(steamId)`** — Fetch driver's rating from the partner ratings file.
253
- - Returns `{ data: { rating, rank, totalDrivers, comboCount, distinctCars, combos }, isLoading, error }`
233
+ **`useNotificationsEnabled()`** — Returns `boolean` whether notification callbacks are provided.
254
234
 
255
235
  ## Registration
256
236
 
257
- The SDK supports two registration modes, determined automatically by whether you provide callbacks to the provider.
237
+ The SDK supports two registration modes, determined by whether you provide callbacks to the provider.
258
238
 
259
239
  ### Basic mode (default)
260
240
 
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.
241
+ No configuration needed. Registration components render links to pitvox.com where users register with Steam.
262
242
 
263
243
  ### Power mode
264
244
 
265
- For partners with a backend (e.g. an Amplify Lambda that proxies to pitvox-api), provide `onRegister` and `onWithdraw` callbacks to the provider:
245
+ For partners with a backend (e.g. Amplify Lambda proxying to pitvox-api), provide callbacks:
266
246
 
267
247
  ```jsx
268
248
  <PitVoxPartnerProvider
@@ -277,29 +257,21 @@ For partners with a backend (e.g. an Amplify Lambda that proxies to pitvox-api),
277
257
  >
278
258
  ```
279
259
 
280
- The `RegisterButton` will then render as an in-app button instead of an external link.
281
-
282
- ### Hooks
260
+ ### Registration hooks
283
261
 
284
262
  ```jsx
285
- import {
286
- useRegistrationStatus,
287
- useRegister,
288
- useWithdraw,
289
- useRegistrationMode,
290
- useRegistrationUrl,
291
- } from '@pitvox/partner-react'
263
+ import { useRegistrationStatus, useRegister, useWithdraw, useRegistrationMode, useRegistrationUrl } from '@pitvox/partner-react'
292
264
  ```
293
265
 
294
- **`useRegistrationStatus(competitionId)`** — Check if current user (via `getSteamId`) is registered. Returns `{ data: { isRegistered, entryList } }`.
266
+ **`useRegistrationStatus(competitionId)`** — Check if current user is registered.
295
267
 
296
- **`useRegister(competitionId)`** — Returns a mutation. Delegates to `onRegister` callback. Throws if no callback provided.
268
+ **`useRegister(competitionId)`** — Mutation delegating to `onRegister` callback.
297
269
 
298
- **`useWithdraw(competitionId)`** — Returns a mutation. Delegates to `onWithdraw` callback. Throws if no callback provided.
270
+ **`useWithdraw(competitionId)`** — Mutation delegating to `onWithdraw` callback.
299
271
 
300
- **`useRegistrationMode()`** — Returns `{ isPowerMode, isBasicMode }` based on whether callbacks are provided.
272
+ **`useRegistrationMode()`** — Returns `{ isPowerMode, isBasicMode }`.
301
273
 
302
- **`useRegistrationUrl(competitionId)`** — Returns the pitvox.com registration URL for basic mode.
274
+ **`useRegistrationUrl(competitionId)`** — Returns pitvox.com registration URL for basic mode.
303
275
 
304
276
  ## Formatting utilities
305
277
 
@@ -312,7 +284,8 @@ import {
312
284
  formatDate, // ISO string → "27 Feb 2024"
313
285
  formatRelativeTime, // ISO string → "2h ago"
314
286
  formatDelta, // 542 → "+0.542"
315
- formatTyreCompound, // "SR" → "Soft Race"
287
+ formatTyreCompound, // "SR" → "Soft Race"
288
+ formatNotificationMessage, // notification → "X beat your record on Track — Car"
316
289
  } from '@pitvox/partner-react'
317
290
  ```
318
291
 
@@ -322,36 +295,25 @@ The default stylesheet uses CSS custom properties. Override them to match your b
322
295
 
323
296
  ```css
324
297
  :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 */
298
+ --pvx-accent: #e11d48;
299
+ --pvx-bg-card: #1a1a2e;
300
+ --pvx-sector-best: #22d3ee;
301
+ --pvx-rank-gold: #fbbf24;
329
302
  }
330
303
  ```
331
304
 
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.
305
+ All classes are prefixed with `pvx-` to avoid collisions. See `styles.css` for the full list of variables.
333
306
 
334
- ## CDN data paths
307
+ ## Partner templates
335
308
 
336
- The SDK reads pre-computed JSON from the PitVox CDN. No API key is needed for read-only leaderboard and competition data.
309
+ For a complete working site using this SDK, see:
337
310
 
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` |
311
+ - **[pitvox-partner-template](https://github.com/meekysoft/pitvox-partner-template)** Basic template (no auth)
312
+ - **[pitvox-partner-template-amplify](https://github.com/meekysoft/pitvox-partner-template-amplify)** — AWS Amplify template with Steam auth and in-app registration
351
313
 
352
- ## Local development
314
+ The templates demonstrate how to compose SDK hooks and components into full pages with routing.
353
315
 
354
- To develop against a local version of the SDK:
316
+ ## Local development
355
317
 
356
318
  ```bash
357
319
  # In the SDK repo
@@ -367,7 +329,7 @@ Add `resolve.dedupe` to your Vite config to avoid duplicate React instances:
367
329
  // vite.config.js
368
330
  export default defineConfig({
369
331
  resolve: {
370
- dedupe: ['react', 'react-dom', 'react-router-dom', '@tanstack/react-query'],
332
+ dedupe: ['react', 'react-dom', '@tanstack/react-query'],
371
333
  },
372
334
  })
373
335
  ```