Skip to content

Add sessions API, DB lib, layout & UI fixes#1

Merged
ghostleek merged 4 commits intomainfrom
fix/prod
Mar 10, 2026
Merged

Add sessions API, DB lib, layout & UI fixes#1
ghostleek merged 4 commits intomainfrom
fix/prod

Conversation

@ghostleek
Copy link
Copy Markdown
Contributor

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:

Transaction and shock handling:

Frontend and Styling Improvements:

Global styles and layout:

  • Introduced a global CSS file with a dark theme, custom color variables, and reusable component classes for cards, buttons, and input fields.
  • Updated the root layout to load custom fonts and apply new global styles.

UI bugfix:

These changes lay the groundwork for a robust, interactive market simulation platform with a consistent and modern user interface.

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.
@ghostleek ghostleek requested a review from Copilot March 10, 2026 05:17
@ghostleek ghostleek self-assigned this Mar 10, 2026
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
live-game Canceled Canceled Mar 10, 2026 5:38am

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.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 MarketChart visualization 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.

Comment on lines +3 to +5
if (!process.env.DATABASE_URL) {
throw new Error('DATABASE_URL is not set');
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +7 to +13
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})
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +7 to +16
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 });

Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +17 to +26
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})
`;
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +1 to +5
:root {
--bg: #0c0e14;
--surface: #14171f;
--surface-2: #1c202a;
--surface-s: #191c26;
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +62 to +66
// 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})
`;
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +17 to +36
// 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
`;
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +13 to +15
// End round
await sql`UPDATE rounds SET status = 'ended', ended_at = NOW() WHERE id = ${roundId}`;

Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +29 to +33
const playerRounds = await sql`
SELECT * FROM player_rounds
WHERE player_id = ${playerId} AND round_id = ${currentRound.id}
`;
const pr = playerRounds[0];
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +3 to +6
import {
ComposedChart, Line, Scatter, XAxis, YAxis, CartesianGrid,
Tooltip, ResponsiveContainer, ReferenceLine, Area,
} from 'recharts';
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.
Copy link
Copy Markdown

Copilot AI commented Mar 10, 2026

@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.

Copy link
Copy Markdown

Copilot AI commented Mar 10, 2026

@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.

Copy link
Copy Markdown

Copilot AI commented Mar 10, 2026

@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.

Copy link
Copy Markdown

Copilot AI commented Mar 10, 2026

@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.

Copy link
Copy Markdown

Copilot AI commented Mar 10, 2026

@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.

Copy link
Copy Markdown

Copilot AI commented Mar 10, 2026

@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.

Copy link
Copy Markdown

Copilot AI commented Mar 10, 2026

@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.

Copy link
Copy Markdown

Copilot AI commented Mar 10, 2026

@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.

Copy link
Copy Markdown

Copilot AI commented Mar 10, 2026

@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.

Copy link
Copy Markdown

Copilot AI commented Mar 10, 2026

@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.
@ghostleek ghostleek merged commit 8019cc4 into main Mar 10, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants