diff --git a/.gitignore b/.gitignore
index 6f82a38..653e780 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ next-env.d.ts
.env.local
.vscode/
tsconfig.tsbuildinfo
+playwright-report/.last-run.json
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..cfa5d91
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,116 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+BSL Platform is a Next.js 15 web application for Big Strategy Labs, an invite-only startup accelerator. It manages startup applications, events, team leadership profiles, and provides an admin dashboard for reviewing applications.
+
+## Tech Stack
+
+- **Framework:** Next.js 15 (App Router)
+- **Database:** MongoDB with Prisma ORM
+- **Authentication:** NextAuth v5 (Google OAuth, JWT sessions)
+- **Styling:** Tailwind CSS + Shadcn/Radix UI components
+- **Validation:** Zod
+
+## Commands
+
+```bash
+npm run dev # Start development server (port 3000)
+npm run build # Production build
+npm run lint # Run ESLint
+npm run lint-fix # Fix lint issues and format with Prettier
+npm run lint-check # Check lint and formatting without fixing
+npm run format # Format all files with Prettier
+npm run test # Run Playwright E2E tests
+npm run seed # Seed user roles (edit prisma/seed.ts first)
+npx prisma generate # Generate Prisma client after schema changes
+npx prisma db push # Push schema changes to MongoDB
+```
+
+## Architecture
+
+### App Router Structure
+
+```
+app/
+├── api/
+│ ├── auth/[...nextauth]/ # NextAuth handler
+│ ├── applications/startup/ # POST startup applications
+│ ├── events/ # GET public events
+│ └── admin/ # Admin API (applications, events CRUD)
+├── apply/ # Application forms (startup, org, team)
+├── admin/ # Admin dashboard and application review
+├── leaders/ # Team leadership profiles
+├── events/ # Public events page
+└── about/ # About/mission page
+```
+
+### Key Directories
+
+- **`components/ui/`** - Shadcn/Radix primitives (Button, Input, Card, etc.)
+- **`components/layout/`** - PublicLayout wrapper with navbar/footer
+- **`components/admin/`** - AdminLayout with sidebar
+- **`lib/`** - Prisma client singleton, Mongoose connection, utilities
+- **`services/`** - Data access layer (demo products, mock applications)
+
+### Database (Prisma/MongoDB)
+
+Three main models in `prisma/schema.prisma`:
+- **User** - Auth with RBAC roles (USER, REVIEWER, AMBASSADOR, SUPER_ADMIN)
+- **Application** - Form submissions with JSON payload, status tracking
+- **Event** - Admin-managed events with creator relationship
+
+### Authentication Flow
+
+- NextAuth configured in `auth.ts` with Google provider
+- Session provider wraps app in root `layout.tsx`
+- Auth button component at `components/auth/AuthButton.tsx`
+- RBAC enforced in API routes via requireRole() in lib/auth-helpers.ts
+
+### Seeding User Roles
+
+Edit `prisma/seed.ts` to assign roles for testing:
+```ts
+const SEED_USERS = [
+ { email: "your-email@gmail.com", role: "SUPER_ADMIN", name: "Your Name" },
+];
+```
+Then run `npm run seed`. Safe to re-run (uses upsert).
+
+### Path Aliases
+
+Use `@/*` to import from project root (configured in tsconfig.json).
+
+```typescript
+import { prisma } from "@/lib/prisma";
+import { Button } from "@/components/ui/button";
+```
+
+## Environment Variables
+
+Required in `.env` (see `.env.example`):
+```
+DATABASE_URL=mongodb://localhost:27017/test-db
+AUTH_SECRET= # openssl rand -base64 32
+AUTH_GOOGLE_ID= # Google Cloud Console
+AUTH_GOOGLE_SECRET=
+```
+
+## Code Patterns
+
+- Client components use `"use client"` directive
+- API routes return `NextResponse.json()` with appropriate status codes
+- Prisma client is singleton-cached in `lib/prisma.ts` to prevent hot-reload leaks
+- Tailwind classes merged with `cn()` utility from `lib/utils.ts`
+- Forms use client-side state with POST to API routes
+
+## Testing
+
+Playwright E2E tests in `tests/e2e/`. The config auto-starts dev server on port 3000.
+
+```bash
+npx playwright test # Run all tests
+npx playwright test tests/e2e/demo.spec.ts # Run specific test
+```
diff --git a/app/leaders/page.tsx b/app/leaders/page.tsx
index a9a039b..60a99f9 100644
--- a/app/leaders/page.tsx
+++ b/app/leaders/page.tsx
@@ -9,49 +9,49 @@ const team = [
name: "Sarah Johnson",
title: "Founder & CEO",
bio: "Former co-founder of TechVentures. Early investor at Google and Amazon.",
- image: "woman.jpg",
+ image: "/woman.jpg",
},
{
name: "Michael Chen",
title: "Investment Director",
bio: "Lead investor at Sequoia Capital. Former partner at Y Combinator.",
- image: "men.jpg",
+ image: "/men.jpg",
},
{
name: "Emily Rodriguez",
title: "Head of Programs",
bio: "Built accelerator programs at Techstars. Former founder of EdTech startup.",
- image: "woman.jpg",
+ image: "/woman.jpg",
},
{
name: "David Park",
title: "Technical Advisor",
bio: "Former CTO at Stripe. Led engineering at Facebook and Airbnb.",
- image: "men.jpg",
+ image: "/men.jpg",
},
{
name: "Alex Thompson",
title: "Operations Manager",
bio: "Scaled operations at Uber. Former consultant at McKinsey.",
- image: "men.jpg",
+ image: "/men.jpg",
},
{
name: "Maria Garcia",
title: "Venture Partner",
bio: "15 years in venture capital. Board member at multiple unicorns.",
- image: "woman.jpg",
+ image: "/woman.jpg",
},
{
name: "Ryan Mitchell",
title: "Community Lead",
bio: "Built communities at Reddit. Former developer relations at GitHub.",
- image: "men.jpg",
+ image: "/men.jpg",
},
{
name: "Sharad Aggarwal",
title: "VC",
bio: "Global head of AI Strategy at Google Cloud.",
- image: "men.jpg",
+ image: "/men.jpg",
},
];
diff --git a/components/ui/LeaderCard2.tsx b/components/ui/LeaderCard2.tsx
index 16473e7..cafcb3a 100644
--- a/components/ui/LeaderCard2.tsx
+++ b/components/ui/LeaderCard2.tsx
@@ -9,32 +9,23 @@ interface LeaderCardProps {
reverse?: boolean;
}
-export default function LeaderCard2({ name, title, bio, image, reverse = false }: LeaderCardProps) {
+export default function LeaderCard2({ name, title, bio, image }: LeaderCardProps) {
return (
- {/* Image — overflows card top/bottom */}
+ {/* Image */}
{/* Text content */}
-
-
+
+
{name}
-
+
{title}
-
+
{bio}
diff --git a/package-lock.json b/package-lock.json
index a27b9a8..77232b4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -29,7 +29,7 @@
"@types/react-dom": "^18",
"@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0",
- "dotenv": "^17.2.3",
+ "dotenv": "^17.3.1",
"eslint": "^8",
"eslint-config-next": "^15.0.3",
"eslint-config-prettier": "^9.1.0",
@@ -37,7 +37,7 @@
"husky": "^9.1.7",
"postcss": "^8",
"prettier": "^3.3.3",
- "prisma": "^6.19.0",
+ "prisma": "^6.19.2",
"tailwindcss": "^3.4.1",
"tailwindcss-animate": "^1.0.7",
"tsx": "^4.21.0",
@@ -1484,6 +1484,7 @@
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
"integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"playwright": "1.58.2"
},
@@ -1665,6 +1666,7 @@
"integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==",
"devOptional": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -1731,6 +1733,7 @@
"integrity": "sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.15.0",
"@typescript-eslint/types": "8.15.0",
@@ -1901,278 +1904,13 @@
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
- "node_modules/acorn": {
- "version": "8.14.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
- "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@unrs/resolver-binding-android-arm64": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz",
- "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@unrs/resolver-binding-darwin-arm64": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz",
- "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@unrs/resolver-binding-darwin-x64": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz",
- "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@unrs/resolver-binding-freebsd-x64": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz",
- "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz",
- "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz",
- "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz",
- "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz",
- "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz",
- "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz",
- "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz",
- "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz",
- "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz",
- "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-x64-musl": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz",
- "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-wasm32-wasi": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz",
- "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==",
- "cpu": [
- "wasm32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@napi-rs/wasm-runtime": "^0.2.11"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz",
- "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz",
- "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz",
- "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
"node_modules/acorn": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2998,9 +2736,9 @@
}
},
"node_modules/dotenv": {
- "version": "17.2.3",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
- "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
+ "version": "17.4.2",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
+ "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@@ -3276,6 +3014,7 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -3452,6 +3191,7 @@
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.8",
@@ -5738,6 +5478,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.1.1",
@@ -5894,45 +5635,6 @@
"preact": ">=10"
}
},
- "node_modules/preact": {
- "version": "10.24.3",
- "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
- "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
- "license": "MIT",
- "peer": true,
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/preact"
- }
- },
- "node_modules/preact-render-to-string": {
- "version": "6.5.11",
- "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz",
- "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==",
- "license": "MIT",
- "peerDependencies": {
- "preact": ">=10"
- }
- },
- "node_modules/preact": {
- "version": "10.24.3",
- "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
- "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/preact"
- }
- },
- "node_modules/preact-render-to-string": {
- "version": "6.5.11",
- "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz",
- "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==",
- "license": "MIT",
- "peerDependencies": {
- "preact": ">=10"
- }
- },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -5964,6 +5666,7 @@
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"@prisma/config": "6.19.2",
"@prisma/engines": "6.19.2"
@@ -6055,6 +5758,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
"integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -6064,6 +5768,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz",
"integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -7013,6 +6718,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
diff --git a/tests/e2e/admin.spec.ts b/tests/e2e/admin.spec.ts
new file mode 100644
index 0000000..1255605
--- /dev/null
+++ b/tests/e2e/admin.spec.ts
@@ -0,0 +1,25 @@
+import { expect, test } from "@playwright/test";
+
+test("redirects unauthenticated users from admin dashboard", async ({
+ page,
+}) => {
+ await page.goto("/admin");
+
+ // Should redirect to login or access-denied page (depends on auth state)
+ await expect(page).toHaveURL(/\/admin\/(login|access-denied)/);
+});
+
+test("admin login page shows Google sign-in button", async ({ page }) => {
+ await page.goto("/admin/login");
+
+ await expect(
+ page.getByRole("button", { name: /sign in with google/i })
+ ).toBeVisible();
+});
+
+test("admin login page displays correct heading", async ({ page }) => {
+ await page.goto("/admin/login");
+
+ await expect(page.getByText("Admin Login")).toBeVisible();
+ await expect(page.getByText("Sign in to access the dashboard")).toBeVisible();
+});
diff --git a/tests/e2e/apply.spec.ts b/tests/e2e/apply.spec.ts
new file mode 100644
index 0000000..67a98b6
--- /dev/null
+++ b/tests/e2e/apply.spec.ts
@@ -0,0 +1,158 @@
+import { expect, test } from "@playwright/test";
+
+test("startup application form loads correctly", async ({ page }) => {
+ await page.goto("/apply/startup");
+
+ // Verify form elements are present
+ await expect(
+ page.getByRole("heading", { name: "Startup Application" })
+ ).toBeVisible();
+ await expect(page.locator("#name")).toBeVisible();
+ await expect(page.locator("#description")).toBeVisible();
+ await expect(page.locator("#deckUrl")).toBeVisible();
+ await expect(page.locator("#fundingGoal")).toBeVisible();
+ await expect(page.locator("#fundingSiteUrl")).toBeVisible();
+ await expect(page.locator("#contact")).toBeVisible();
+ await expect(page.getByRole("button", { name: "Apply" })).toBeVisible();
+});
+
+test("startup application form can be filled", async ({ page }) => {
+ await page.goto("/apply/startup");
+
+ // Fill form fields
+ await page.fill("#name", "Test Startup Inc");
+ await page.fill("#description", "A test startup for E2E testing");
+ await page.fill("#deckUrl", "https://example.com/deck.pdf");
+ await page.fill("#fundingGoal", "500000");
+ await page.fill("#fundingSiteUrl", "https://example.com/funding");
+ await page.fill("#contact", "test@example.com");
+
+ // Verify values were entered
+ await expect(page.locator("#name")).toHaveValue("Test Startup Inc");
+ await expect(page.locator("#contact")).toHaveValue("test@example.com");
+});
+
+test("startup application submit button is enabled when only required fields are filled", async ({
+ page,
+}) => {
+ await page.goto("/apply/startup");
+
+ // Fill required form fields
+ await page.fill("#name", "Test Startup Inc");
+ await page.fill("#description", "A test startup for E2E testing");
+ await page.fill("#deckUrl", "https://example.com/deck.pdf");
+ await page.fill("#fundingGoal", "500000");
+ await page.fill("#contact", "test@example.com");
+
+ // Verify submit button is enabled and clickable
+ const submitButton = page.getByRole("button", { name: "Apply" });
+ await expect(submitButton).toBeEnabled();
+});
+
+// Team Application Tests
+test("team application form loads correctly", async ({ page }) => {
+ await page.goto("/apply/team");
+
+ // Verify form elements are present
+ await expect(
+ page.getByRole("heading", { name: "Team Application" })
+ ).toBeVisible();
+ await expect(page.locator("#submitterName")).toBeVisible();
+ await expect(page.locator("#submitterEmail")).toBeVisible();
+ await expect(page.locator("#teamName")).toBeVisible();
+ await expect(page.locator("#teamSize")).toBeVisible();
+ await expect(page.locator("#skills")).toBeVisible();
+ await expect(page.locator("#projectPreferences")).toBeVisible();
+ await expect(page.locator("#description")).toBeVisible();
+ await expect(page.getByRole("button", { name: "Apply" })).toBeVisible();
+});
+
+test("team application form can be filled", async ({ page }) => {
+ await page.goto("/apply/team");
+
+ // Fill form fields
+ await page.fill("#submitterName", "John Doe");
+ await page.fill("#submitterEmail", "john@example.com");
+ await page.fill("#teamName", "Test Team");
+ await page.fill("#teamSize", "4");
+ await page.fill("#skills", "React, Node.js, Python");
+ await page.fill("#projectPreferences", "Web apps, AI/ML");
+ await page.fill("#description", "A talented team for E2E testing");
+
+ // Verify values were entered
+ await expect(page.locator("#teamName")).toHaveValue("Test Team");
+ await expect(page.locator("#submitterEmail")).toHaveValue("john@example.com");
+});
+
+test("team application submit button is enabled when required fields are filled", async ({
+ page,
+}) => {
+ await page.goto("/apply/team");
+
+ // Fill required form fields
+ await page.fill("#submitterName", "John Doe");
+ await page.fill("#submitterEmail", "john@example.com");
+ await page.fill("#teamName", "Test Team");
+ await page.fill("#teamSize", "4");
+ await page.fill("#skills", "React, Node.js");
+ await page.fill("#projectPreferences", "Web apps");
+ await page.fill("#description", "A talented team for E2E testing");
+
+ // Verify submit button is enabled
+ const submitButton = page.getByRole("button", { name: "Apply" });
+ await expect(submitButton).toBeEnabled();
+});
+
+// Organization Application Tests
+test("organization application form loads correctly", async ({ page }) => {
+ await page.goto("/apply/organization");
+
+ // Verify form elements are present
+ await expect(
+ page.getByRole("heading", { name: "New Company Project" })
+ ).toBeVisible();
+ await expect(page.locator("#submitterName")).toBeVisible();
+ await expect(page.locator("#submitterEmail")).toBeVisible();
+ await expect(page.locator("#companyName")).toBeVisible();
+ await expect(page.locator("#projectTitle")).toBeVisible();
+ await expect(page.locator("#budget")).toBeVisible();
+ await expect(page.locator("#skillsNeeded")).toBeVisible();
+ await expect(page.locator("#description")).toBeVisible();
+ await expect(page.getByRole("button", { name: "Apply" })).toBeVisible();
+});
+
+test("organization application form can be filled", async ({ page }) => {
+ await page.goto("/apply/organization");
+
+ // Fill form fields
+ await page.fill("#submitterName", "Jane Smith");
+ await page.fill("#submitterEmail", "jane@company.com");
+ await page.fill("#companyName", "Test Corp");
+ await page.fill("#projectTitle", "E2E Test Project");
+ await page.fill("#budget", "$50,000");
+ await page.fill("#skillsNeeded", "React, UI/UX Design");
+ await page.fill("#description", "A test project for E2E testing");
+
+ // Verify values were entered
+ await expect(page.locator("#companyName")).toHaveValue("Test Corp");
+ await expect(page.locator("#submitterEmail")).toHaveValue("jane@company.com");
+});
+
+test("organization application submit button is enabled when required fields are filled", async ({
+ page,
+}) => {
+ await page.goto("/apply/organization");
+
+ // Fill required form fields
+ await page.fill("#submitterName", "Jane Smith");
+ await page.fill("#submitterEmail", "jane@company.com");
+ await page.fill("#companyName", "Test Corp");
+ await page.fill("#projectTitle", "E2E Test Project");
+ await page.fill("#budget", "$50,000");
+ await page.fill("#skillsNeeded", "React, UI/UX Design");
+ await page.fill("#description", "A test project for E2E testing");
+
+ // Verify submit button is enabled
+ const submitButton = page.getByRole("button", { name: "Apply" });
+ await expect(submitButton).toBeEnabled();
+});
diff --git a/tests/e2e/public-nav.spec.ts b/tests/e2e/public-nav.spec.ts
new file mode 100644
index 0000000..6292cbd
--- /dev/null
+++ b/tests/e2e/public-nav.spec.ts
@@ -0,0 +1,37 @@
+import { expect, test } from "@playwright/test";
+
+test.describe("public pages navigation", () => {
+ test("home page loads", async ({ page }) => {
+ await page.goto("/");
+ await expect(
+ page.getByRole("heading", { name: /BIG STRATEGY LABS/i, level: 1 })
+ ).toBeVisible();
+ });
+
+ test("about page loads", async ({ page }) => {
+ await page.goto("/about");
+ await expect(
+ page.getByRole("heading", { name: /Our Mission/i })
+ ).toBeVisible();
+ });
+
+ test("leaders page loads", async ({ page }) => {
+ await page.goto("/leaders");
+ // Check for the main heading text that spans multiple lines
+ await expect(
+ page.getByRole("heading", { name: /We are the people/i })
+ ).toBeVisible();
+ });
+
+ test("events page loads", async ({ page }) => {
+ await page.goto("/events");
+ await expect(page.getByRole("heading", { name: /Events/i })).toBeVisible();
+ });
+
+ test("apply page loads", async ({ page }) => {
+ await page.goto("/apply");
+ await expect(
+ page.getByRole("heading", { name: /Apply to Join/i })
+ ).toBeVisible();
+ });
+});