Conversation
Add full backend API surface for sessions and gameplay: session creation, session detail, players (list/create), rounds (start/end), shocks (list/create), transactions (list/create) and transaction confirmation, plus player role/status endpoint. Introduce lib/db (Neon connector) and move game logic to lib/game. Add global styles and RootLayout with Google fonts, update play and teacher pages to normalize numeric values, and include project configs (.gitignore, package.json, package-lock.json, tsconfig, tailwind/postcss, next-env). Also add DB schema under db/schema.sql and minor repo housekeeping (.DS_Store). These changes implement server-side game flow and UI refinements to support live market simulation.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Introduce a new client page at app/create/page.tsx for teachers to create sessions (passphrase, round duration) and persist the passphrase to sessionStorage before navigating to the teacher view. Extracted and removed the create flow from the Home page, simplifying app/page.tsx into a join-focused landing with a hero, centered join form, updated UI/spacing, and a link to the new /create route. Also update the Player interface in app/play/[sessionId]/page.tsx to include a numeric total_surplus field.
There was a problem hiding this comment.
Pull request overview
This PR scaffolds a market-simulation Next.js app by adding a Neon-backed sessions/rounds/players/transactions/shocks API plus a new global theme/UI foundation (Tailwind config + custom CSS), and introduces a market-curve chart on the teacher view.
Changes:
- Added database schema + Neon SQL helper and implemented session/round/player/transaction/shock API routes.
- Added global styling foundation (fonts, CSS variables, Tailwind/PostCSS config) and new landing page UI.
- Added a client-only
MarketChartvisualization and fixed numeric formatting issues on teacher/play pages.
Reviewed changes
Copilot reviewed 21 out of 28 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
tsconfig.json |
TypeScript config and path alias for @/*. |
tailwind.config.ts |
Tailwind theme extensions (fonts/colors) and content globs. |
postcss.config.js |
PostCSS pipeline wiring for Tailwind + autoprefixer. |
package.json |
Adds Next/React app dependencies including Neon and charting libs. |
next-env.d.ts |
Next.js TypeScript environment declarations. |
lib/game.ts |
Core game helpers (PIN generation, role distribution, surplus calc). |
lib/db.ts |
Neon serverless SQL client initialization. |
db/schema.sql |
Postgres schema for sessions, players, rounds, transactions, shocks. |
components/MarketChart.tsx |
Recharts-based demand/supply + equilibrium visualization. |
app/teacher/[sessionId]/page.tsx |
Adds client-only chart and numeric formatting fixes. |
app/play/[sessionId]/page.tsx |
Numeric formatting fixes for player surplus/penalty display. |
app/page.tsx |
New landing page with “create/join game” flows. |
app/layout.tsx |
Root layout wiring global CSS + Google fonts. |
app/globals.css |
Global dark theme variables + reusable UI classes. |
app/api/sessions/route.ts |
POST /sessions session creation. |
app/api/sessions/[id]/route.ts |
GET /sessions/:id session + round + shocks summary. |
app/api/sessions/[id]/rounds/route.ts |
POST /sessions/:id/rounds start a new round + assign roles. |
app/api/sessions/[id]/rounds/[roundId]/end/route.ts |
POST .../end end round and apply penalties/summaries. |
app/api/sessions/[id]/players/route.ts |
List/add players for a session. |
app/api/sessions/[id]/players/[playerId]/role/route.ts |
Player role/round state + pending tx info. |
app/api/sessions/[id]/transactions/route.ts |
List/create transactions for the current round. |
app/api/sessions/[id]/transactions/[transactionId]/confirm/route.ts |
Confirm/reject a pending transaction and compute surplus. |
app/api/sessions/[id]/shocks/route.ts |
List/create market shocks. |
.gitignore |
Ignore build artifacts and env files. |
.DS_Store |
Newly added OS metadata file (should not be committed). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| if (!process.env.DATABASE_URL) { | ||
| throw new Error('DATABASE_URL is not set'); | ||
| } |
There was a problem hiding this comment.
Throwing at module import time will make next build / route compilation fail in environments where DATABASE_URL isn’t set (e.g., CI build step). Prefer lazy initialization (create the client inside handlers) or throw a clearer error when a request actually executes, so builds can succeed without runtime env.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| const { passphrase, roundDuration } = await req.json(); | ||
| if (!passphrase) return NextResponse.json({ error: 'Passphrase required' }, { status: 400 }); | ||
|
|
||
| const sessionId = generatePin(); | ||
| await sql` | ||
| INSERT INTO sessions (id, passphrase, round_duration) | ||
| VALUES (${sessionId}, ${passphrase}, ${roundDuration || 300}) |
There was a problem hiding this comment.
The session passphrase is being stored in plaintext. If this is used for authorization, it should be stored as a salted hash (and compared with a constant-time check) to reduce impact of DB exposure.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| const { id, transactionId } = params; | ||
| const { playerId, confirmed } = await req.json(); | ||
|
|
||
| const txs = await sql`SELECT * FROM transactions WHERE id = ${transactionId}`; | ||
| if (txs.length === 0) return NextResponse.json({ error: 'Transaction not found' }, { status: 404 }); | ||
| const tx = txs[0]; | ||
|
|
||
| if (tx.partner_id !== playerId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); | ||
| if (tx.status !== 'pending') return NextResponse.json({ error: 'Transaction no longer pending' }, { status: 400 }); | ||
|
|
There was a problem hiding this comment.
params.id (session id) is never used to authorize/validate the transaction. As written, anyone who can guess a transactionId could hit /api/sessions/<any>/transactions/<transactionId>/confirm and confirm/reject it. Verify the transaction’s session_id matches params.id (and ideally that the round is active) before proceeding.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| const { passphrase, type, description, priceShift } = await req.json(); | ||
|
|
||
| const session = await sql`SELECT * FROM sessions WHERE id = ${id}`; | ||
| if (session.length === 0) return NextResponse.json({ error: 'Session not found' }, { status: 404 }); | ||
| if (session[0].passphrase !== passphrase) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); | ||
|
|
||
| await sql` | ||
| INSERT INTO shocks (session_id, type, description, price_shift) | ||
| VALUES (${id}, ${type}, ${description}, ${priceShift}) | ||
| `; |
There was a problem hiding this comment.
priceShift is inserted directly. If the client omits it (undefined), many drivers will send NULL, which will violate price_shift ... NOT NULL and return a 500. Default priceShift server-side (e.g. 10) and validate type/description to return a 400 on bad input.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| :root { | ||
| --bg: #0c0e14; | ||
| --surface: #14171f; | ||
| --surface-2: #1c202a; | ||
| --surface-s: #191c26; |
There was a problem hiding this comment.
Tailwind is configured in postcss.config.js/tailwind.config.ts, and pages use Tailwind utility classes, but this stylesheet doesn’t include the required @tailwind base/components/utilities directives. Without them, Tailwind utilities won’t be generated/applied.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| // Check for pending transactions | ||
| const pending = await sql` | ||
| SELECT id FROM transactions | ||
| WHERE round_id = ${round.id} AND status = 'pending' AND (initiator_id = ${initiatorId} OR partner_id = ${initiatorId}) | ||
| `; |
There was a problem hiding this comment.
The “pending transaction” check only considers initiatorId. This allows the selected partnerId to already have a pending transaction and still be targeted, which can lead to conflicting pending offers. The query should also check for pending transactions involving partnerId (and ideally both players in either role).
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| // Get current round number | ||
| const rounds = await sql`SELECT COUNT(*) as count FROM rounds WHERE session_id = ${id}`; | ||
| const nextRoundNumber = parseInt(rounds[0].count) + 1; | ||
|
|
||
| // Get pending shocks | ||
| const shocks = await sql` | ||
| SELECT * FROM shocks | ||
| WHERE session_id = ${id} AND applied_to_round IS NULL | ||
| `; | ||
| const shock = shocks[0] || null; | ||
|
|
||
| const supplyShift = shocks.filter(s => s.type === 'supply').reduce((acc, s) => acc + parseFloat(s.price_shift), 0); | ||
| const demandShift = shocks.filter(s => s.type === 'demand').reduce((acc, s) => acc + parseFloat(s.price_shift), 0); | ||
|
|
||
| // Create new round | ||
| const newRounds = await sql` | ||
| INSERT INTO rounds (session_id, round_number, shock_description) | ||
| VALUES (${id}, ${nextRoundNumber}, ${shock?.description || null}) | ||
| RETURNING id | ||
| `; |
There was a problem hiding this comment.
Starting a new round doesn’t check whether there is already an active round for this session. Calling this twice will create multiple active rounds and duplicate role assignments. Consider enforcing “at most one active round per session” (check + reject, or end the previous round first) and/or add a DB constraint to prevent it.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| // End round | ||
| await sql`UPDATE rounds SET status = 'ended', ended_at = NOW() WHERE id = ${roundId}`; | ||
|
|
There was a problem hiding this comment.
This route ends a round by id only, without verifying that the round belongs to params.id or that it’s currently active. A caller with the teacher passphrase for one session could end a round from another session if they know its UUID, and repeated calls can apply penalties multiple times. Update by both roundId and session_id, and guard/idempotently handle already-ended rounds.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| const playerRounds = await sql` | ||
| SELECT * FROM player_rounds | ||
| WHERE player_id = ${playerId} AND round_id = ${currentRound.id} | ||
| `; | ||
| const pr = playerRounds[0]; |
There was a problem hiding this comment.
This endpoint returns role/secretValue for any playerId without verifying that the player belongs to the session (players.session_id = params.id). That allows a user to query other players’ secret values by UUID. Add a check that playerId exists in players for this session (and reject otherwise).
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| import { | ||
| ComposedChart, Line, Scatter, XAxis, YAxis, CartesianGrid, | ||
| Tooltip, ResponsiveContainer, ReferenceLine, Area, | ||
| } from 'recharts'; |
There was a problem hiding this comment.
Area is imported from recharts but never used. This will typically fail next lint/TypeScript unused-import checks; remove the unused import (or add the missing Area usage).
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
Remove the inline `border: 'none'` from the teacher session button style so the intended error border (`1px solid rgba(248,113,113,0.35)`) can be displayed. This fixes a UI issue where the border was being suppressed. Change is in app/teacher/[sessionId]/page.tsx.
|
@ghostleek I've opened a new pull request, #2, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@ghostleek I've opened a new pull request, #3, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@ghostleek I've opened a new pull request, #4, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@ghostleek I've opened a new pull request, #5, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@ghostleek I've opened a new pull request, #6, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@ghostleek I've opened a new pull request, #7, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@ghostleek I've opened a new pull request, #8, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@ghostleek I've opened a new pull request, #9, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@ghostleek I've opened a new pull request, #10, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@ghostleek I've opened a new pull request, #11, to work on those changes. Once the pull request is ready, I'll request review from you. |
Create vercel.json to explicitly set the deployment framework to "nextjs" so Vercel uses Next.js-specific build and runtime settings during deployments.
This pull request introduces a comprehensive backend API for a market simulation game, along with global CSS and layout improvements for the frontend. The changes establish endpoints for session, round, player, transaction, and shock management, and provide a modern, themeable UI foundation.
Backend API Implementation:
Session and round management:
POST /sessions), retrieve session details, and manage rounds, including starting new rounds and ending rounds with summary statistics and penalty logic. (app/api/sessions/route.tsR1-R20, app/api/sessions/[id]/route.tsR1-R47, app/api/sessions/[id]/rounds/route.tsR1-R64, app/api/sessions/[id]/rounds/[roundId]/end/route.tsR1-R52)Transaction and shock handling:
Frontend and Styling Improvements:
Global styles and layout:
UI bugfix:
These changes lay the groundwork for a robust, interactive market simulation platform with a consistent and modern user interface.