chore: initialize Next.js 15 project with Tailwind, TypeScript and tooling config
This commit is contained in:
commit
36f3826684
16 changed files with 11039 additions and 0 deletions
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
|
||||||
|
# Node / Next.js (nested app has its own .gitignore too)
|
||||||
|
node_modules/
|
||||||
|
.next/
|
||||||
|
.pnpm-store/
|
||||||
|
|
||||||
|
# Env files with real secrets
|
||||||
|
App/pelagia-portal/.env
|
||||||
|
App/pelagia-portal/.env.local
|
||||||
|
App/pelagia-portal/.env.*.local
|
||||||
|
|
||||||
|
# Build / generated
|
||||||
|
App/pelagia-portal/.vercel/
|
||||||
|
App/pelagia-portal/coverage/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
36
App/pelagia-portal/.env.example
Normal file
36
App/pelagia-portal/.env.example
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# =============================================================
|
||||||
|
# Pelagia Portal — Environment Variables
|
||||||
|
# Copy this file to .env.local and fill in your values
|
||||||
|
#
|
||||||
|
# DEVELOPMENT (NODE_ENV=development, i.e. `pnpm dev`):
|
||||||
|
# - File uploads are stored locally in .dev-uploads/ — no R2 needed
|
||||||
|
# - Emails are logged to the terminal — no Resend key needed
|
||||||
|
# - Only AUTH + DATABASE vars are required to run the app locally
|
||||||
|
#
|
||||||
|
# PRODUCTION (NODE_ENV=production, i.e. `pnpm build && pnpm start`):
|
||||||
|
# - All sections below must be filled in
|
||||||
|
# =============================================================
|
||||||
|
|
||||||
|
# ── Auth ─────────────────────────────────────────────────────
|
||||||
|
NEXTAUTH_SECRET=your-32-char-secret-here-generate-with-openssl
|
||||||
|
NEXTAUTH_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# ── Database ──────────────────────────────────────────────────
|
||||||
|
# Local PostgreSQL or Supabase
|
||||||
|
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/pelagia_portal"
|
||||||
|
# Supabase connection pooling URL (use for serverless deployments)
|
||||||
|
# DATABASE_POOL_URL=
|
||||||
|
|
||||||
|
# ── Cloudflare R2 Storage (production only) ──────────────────
|
||||||
|
# Not required in development — files are stored in .dev-uploads/
|
||||||
|
R2_ACCOUNT_ID=your-cloudflare-account-id
|
||||||
|
R2_ACCESS_KEY_ID=your-r2-access-key-id
|
||||||
|
R2_SECRET_ACCESS_KEY=your-r2-secret-access-key
|
||||||
|
R2_BUCKET_NAME=pelagia-portal
|
||||||
|
R2_PUBLIC_URL=https://your-bucket.your-account.r2.cloudflarestorage.com
|
||||||
|
|
||||||
|
# ── Email / Resend (production only) ─────────────────────────
|
||||||
|
# Not required in development — emails are printed to the terminal
|
||||||
|
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxx
|
||||||
|
EMAIL_FROM=noreply@pelagiaportal.com
|
||||||
|
EMAIL_FROM_NAME="Pelagia Portal"
|
||||||
41
App/pelagia-portal/.gitignore
vendored
Normal file
41
App/pelagia-portal/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# Next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# Production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
/coverage
|
||||||
|
/playwright-report
|
||||||
|
/test-results
|
||||||
|
/blob-report
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# Prisma
|
||||||
|
prisma/*.db
|
||||||
|
prisma/*.db-journal
|
||||||
|
|
||||||
|
# Dev local file uploads
|
||||||
|
.dev-uploads/
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.vercel
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
216
App/pelagia-portal/README.md
Normal file
216
App/pelagia-portal/README.md
Normal file
|
|
@ -0,0 +1,216 @@
|
||||||
|
# Pelagia Portal
|
||||||
|
|
||||||
|
An internal purchase order management system for a maritime vessel-operations company. Digitises the full PO lifecycle — from crew requisition through manager approval, vendor validation, accounts payment, and receipt confirmation — replacing ad-hoc email chains and spreadsheets with a single auditable workflow.
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
| Layer | Technology |
|
||||||
|
|---|---|
|
||||||
|
| Framework | Next.js 15 (App Router) |
|
||||||
|
| Language | TypeScript 5 (strict) |
|
||||||
|
| Database | PostgreSQL 16 via Prisma 5 |
|
||||||
|
| Auth | NextAuth.js v5 (credentials) |
|
||||||
|
| Styling | Tailwind CSS v4 + shadcn/ui |
|
||||||
|
| File Storage | Cloudflare R2 (production) / local filesystem (development) |
|
||||||
|
| Email | Resend (production) / console log (development) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
| Tool | Required Version |
|
||||||
|
|---|---|
|
||||||
|
| Node.js | >= 20.11.0 LTS |
|
||||||
|
| pnpm | >= 9.0.0 |
|
||||||
|
| PostgreSQL | >= 16 (local or Docker) |
|
||||||
|
|
||||||
|
Install pnpm if you don't have it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g pnpm
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development Setup
|
||||||
|
|
||||||
|
In development mode the app requires **only a database and auth secret** — Cloudflare R2 and Resend are not needed. File uploads are saved to `.dev-uploads/` on your local machine, and emails are printed to the terminal instead of being sent.
|
||||||
|
|
||||||
|
### 1. Install dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configure environment
|
||||||
|
|
||||||
|
Copy the example file and fill in the two required values:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env.local
|
||||||
|
```
|
||||||
|
|
||||||
|
Minimum `.env.local` for development:
|
||||||
|
|
||||||
|
```env
|
||||||
|
NEXTAUTH_SECRET=<generate with: openssl rand -base64 32>
|
||||||
|
NEXTAUTH_URL=http://localhost:3000
|
||||||
|
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/pelagia_portal"
|
||||||
|
```
|
||||||
|
|
||||||
|
The R2 and Resend variables are not needed in development and can be left as placeholders.
|
||||||
|
|
||||||
|
### 3. Set up the database
|
||||||
|
|
||||||
|
Create the database (if it doesn't exist yet), run migrations, and seed sample data:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm db:migrate # runs prisma migrate dev
|
||||||
|
pnpm db:seed # seeds vessels, accounts, vendors, and demo users
|
||||||
|
```
|
||||||
|
|
||||||
|
To inspect the database with a GUI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm db:studio # opens Prisma Studio at http://localhost:5555
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Start the dev server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
The app will be available at [http://localhost:3000](http://localhost:3000).
|
||||||
|
|
||||||
|
**Email behaviour in dev:** all notification emails are logged to the terminal in place of actual delivery. Look for lines starting with `📧 [DEV EMAIL]`.
|
||||||
|
|
||||||
|
**File upload behaviour in dev:** uploaded files are written to `.dev-uploads/` at the project root. This directory is git-ignored.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Serving in Production
|
||||||
|
|
||||||
|
Production requires all environment variables to be set, including Cloudflare R2 credentials and a Resend API key.
|
||||||
|
|
||||||
|
### 1. Configure environment
|
||||||
|
|
||||||
|
Set the following variables in your hosting platform (Vercel, etc.) or in `.env.local` for a self-hosted deploy:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Auth
|
||||||
|
NEXTAUTH_SECRET=<strong random secret>
|
||||||
|
NEXTAUTH_URL=https://your-domain.com
|
||||||
|
|
||||||
|
# Database
|
||||||
|
DATABASE_URL=postgresql://<user>:<password>@<host>:<port>/<db>
|
||||||
|
|
||||||
|
# Cloudflare R2
|
||||||
|
R2_ACCOUNT_ID=<your cloudflare account id>
|
||||||
|
R2_ACCESS_KEY_ID=<r2 access key>
|
||||||
|
R2_SECRET_ACCESS_KEY=<r2 secret key>
|
||||||
|
R2_BUCKET_NAME=pelagia-portal
|
||||||
|
R2_PUBLIC_URL=https://<bucket>.<account>.r2.cloudflarestorage.com
|
||||||
|
|
||||||
|
# Email
|
||||||
|
RESEND_API_KEY=re_<your key>
|
||||||
|
EMAIL_FROM=noreply@yourdomain.com
|
||||||
|
EMAIL_FROM_NAME="Pelagia Portal"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Run database migrations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm db:migrate:deploy # runs prisma migrate deploy (safe for production)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Build and start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm build
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
The app listens on port 3000 by default. Point your reverse proxy (nginx, Caddy, etc.) or hosting platform to that port.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Database Management
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `pnpm db:migrate` | Create and run a new migration (dev only) |
|
||||||
|
| `pnpm db:migrate:deploy` | Apply pending migrations without prompting (CI/production) |
|
||||||
|
| `pnpm db:push` | Push schema changes without a migration file (prototyping only) |
|
||||||
|
| `pnpm db:seed` | Seed sample data |
|
||||||
|
| `pnpm db:studio` | Open Prisma Studio GUI |
|
||||||
|
| `pnpm db:reset` | Drop and recreate the database, then re-seed (dev only) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm test # unit + integration tests (Vitest)
|
||||||
|
pnpm test:watch # watch mode
|
||||||
|
pnpm test:e2e # end-to-end tests (Playwright)
|
||||||
|
pnpm test:e2e:ui # Playwright with interactive UI
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Other Scripts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm lint # ESLint
|
||||||
|
pnpm type-check # tsc --noEmit
|
||||||
|
pnpm email:preview # live-preview email templates at http://localhost:3001
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
pelagia-portal/
|
||||||
|
├── app/ # Next.js App Router pages and API routes
|
||||||
|
│ ├── (auth)/ # Login
|
||||||
|
│ ├── (portal)/ # Authenticated shell (sidebar + header)
|
||||||
|
│ │ ├── dashboard/
|
||||||
|
│ │ ├── po/ # PO creation, detail, edit
|
||||||
|
│ │ ├── approvals/ # Manager approval queue
|
||||||
|
│ │ ├── payments/ # Accounts payment queue
|
||||||
|
│ │ ├── history/ # Audit trail
|
||||||
|
│ │ └── admin/ # User, vessel, account, vendor management
|
||||||
|
│ └── api/
|
||||||
|
│ ├── auth/ # NextAuth endpoints
|
||||||
|
│ ├── files/sign/ # Generate presigned upload URL (production)
|
||||||
|
│ ├── files/dev/ # Local file upload/download handler (dev only)
|
||||||
|
│ └── reports/export/ # CSV / PDF export
|
||||||
|
├── components/ # Shared UI components (shadcn/ui + custom)
|
||||||
|
├── lib/ # Business logic
|
||||||
|
│ ├── po-state-machine.ts # All PO state transitions enforced here
|
||||||
|
│ ├── permissions.ts # Role → allowed-action map
|
||||||
|
│ ├── notifier.ts # Email dispatch (Resend in prod, console in dev)
|
||||||
|
│ ├── storage.ts # File storage (R2 in prod, local in dev)
|
||||||
|
│ └── validations/ # Zod schemas
|
||||||
|
├── emails/ # React Email templates
|
||||||
|
├── prisma/ # Schema and migrations
|
||||||
|
└── tests/ # Vitest unit/integration + Playwright E2E
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Roles
|
||||||
|
|
||||||
|
| Role | Description |
|
||||||
|
|---|---|
|
||||||
|
| Technical | Deck/engine crew — create and submit POs, confirm receipt |
|
||||||
|
| Manning | Crew-management staff — same as Technical |
|
||||||
|
| Manager | Review, approve, reject, request edits |
|
||||||
|
| Accounts | Process payment for approved POs |
|
||||||
|
| SuperUser | Combined Technical + Manning + Manager authority |
|
||||||
|
| Auditor | Read-only access to all records and reports |
|
||||||
|
| Admin | Manage users, vessels, accounts, and vendors |
|
||||||
|
|
||||||
|
User accounts are provisioned by an Admin; there is no self-registration.
|
||||||
50
App/pelagia-portal/app/globals.css
Normal file
50
App/pelagia-portal/app/globals.css
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
/* Brand colours */
|
||||||
|
--color-primary-50: #eff6ff;
|
||||||
|
--color-primary-100: #dbeafe;
|
||||||
|
--color-primary-200: #bfdbfe;
|
||||||
|
--color-primary-500: #3b82f6;
|
||||||
|
--color-primary-600: #2563eb;
|
||||||
|
--color-primary-700: #1d4ed8;
|
||||||
|
--color-primary-800: #1e40af;
|
||||||
|
|
||||||
|
--color-success: #16a34a;
|
||||||
|
--color-success-50: #f0fdf4;
|
||||||
|
--color-success-100: #dcfce7;
|
||||||
|
--color-success-700: #15803d;
|
||||||
|
|
||||||
|
--color-warning: #d97706;
|
||||||
|
--color-warning-50: #fffbeb;
|
||||||
|
--color-warning-100: #fef3c7;
|
||||||
|
--color-warning-700: #b45309;
|
||||||
|
|
||||||
|
--color-danger: #dc2626;
|
||||||
|
--color-danger-50: #fef2f2;
|
||||||
|
--color-danger-100: #fee2e2;
|
||||||
|
--color-danger-700: #b91c1c;
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
|
||||||
|
--font-mono: "JetBrains Mono", ui-monospace, "Cascadia Code", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
background-color: #f9fafb;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
App/pelagia-portal/app/layout.tsx
Normal file
35
App/pelagia-portal/app/layout.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Inter, JetBrains_Mono } from "next/font/google";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
const inter = Inter({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-sans",
|
||||||
|
display: "swap",
|
||||||
|
});
|
||||||
|
|
||||||
|
const jetbrainsMono = JetBrains_Mono({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-mono",
|
||||||
|
display: "swap",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: {
|
||||||
|
default: "Pelagia Portal",
|
||||||
|
template: "%s | Pelagia Portal",
|
||||||
|
},
|
||||||
|
description: "Purchase Order Management System",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<html lang="en" className={`${inter.variable} ${jetbrainsMono.variable}`}>
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
5
App/pelagia-portal/app/page.tsx
Normal file
5
App/pelagia-portal/app/page.tsx
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
export default function RootPage() {
|
||||||
|
redirect("/dashboard");
|
||||||
|
}
|
||||||
73
App/pelagia-portal/lib/utils.ts
Normal file
73
App/pelagia-portal/lib/utils.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { clsx, type ClassValue } from "clsx";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
import type { POStatus } from "@prisma/client";
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatCurrency(amount: number | string, currency = "INR"): string {
|
||||||
|
return new Intl.NumberFormat("en-IN", { style: "currency", currency }).format(
|
||||||
|
Number(amount)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatDate(date: Date | string): string {
|
||||||
|
return new Intl.DateTimeFormat("en-US", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
}).format(new Date(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatDateTime(date: Date | string): string {
|
||||||
|
return new Intl.DateTimeFormat("en-US", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
}).format(new Date(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generatePoNumber(): string {
|
||||||
|
const year = new Date().getFullYear();
|
||||||
|
const seq = Math.floor(Math.random() * 100000)
|
||||||
|
.toString()
|
||||||
|
.padStart(5, "0");
|
||||||
|
return `PO-${year}-${seq}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PO_STATUS_LABELS: Record<POStatus, string> = {
|
||||||
|
DRAFT: "Draft",
|
||||||
|
SUBMITTED: "Submitted",
|
||||||
|
MGR_REVIEW: "Under Review",
|
||||||
|
VENDOR_ID_PENDING: "Vendor ID Pending",
|
||||||
|
EDITS_REQUESTED: "Edits Requested",
|
||||||
|
REJECTED: "Rejected",
|
||||||
|
MGR_APPROVED: "Approved",
|
||||||
|
SENT_FOR_PAYMENT: "Sent for Payment",
|
||||||
|
PAID_DELIVERED: "Paid",
|
||||||
|
CLOSED: "Closed",
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BadgeVariant =
|
||||||
|
| "default"
|
||||||
|
| "secondary"
|
||||||
|
| "success"
|
||||||
|
| "warning"
|
||||||
|
| "danger"
|
||||||
|
| "outline";
|
||||||
|
|
||||||
|
export const PO_STATUS_VARIANTS: Record<POStatus, BadgeVariant> = {
|
||||||
|
DRAFT: "outline",
|
||||||
|
SUBMITTED: "secondary",
|
||||||
|
MGR_REVIEW: "secondary",
|
||||||
|
VENDOR_ID_PENDING: "warning",
|
||||||
|
EDITS_REQUESTED: "warning",
|
||||||
|
REJECTED: "danger",
|
||||||
|
MGR_APPROVED: "success",
|
||||||
|
SENT_FOR_PAYMENT: "default",
|
||||||
|
PAID_DELIVERED: "success",
|
||||||
|
CLOSED: "secondary",
|
||||||
|
};
|
||||||
24
App/pelagia-portal/next.config.ts
Normal file
24
App/pelagia-portal/next.config.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV === "development";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
experimental: {
|
||||||
|
serverActions: {
|
||||||
|
bodySizeLimit: "10mb",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
images: {
|
||||||
|
remotePatterns: [
|
||||||
|
{
|
||||||
|
protocol: "https",
|
||||||
|
hostname: "*.r2.cloudflarestorage.com",
|
||||||
|
},
|
||||||
|
...(isDev
|
||||||
|
? [{ protocol: "http" as const, hostname: "localhost" }]
|
||||||
|
: []),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
79
App/pelagia-portal/package.json
Normal file
79
App/pelagia-portal/package.json
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"name": "pelagia-portal",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev --turbopack",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"test:integration": "vitest run --config vitest.integration.config.ts",
|
||||||
|
"test:integration:watch": "vitest --config vitest.integration.config.ts",
|
||||||
|
"test:all": "vitest run && vitest run --config vitest.integration.config.ts",
|
||||||
|
"test:e2e": "playwright test",
|
||||||
|
"test:e2e:ui": "playwright test --ui",
|
||||||
|
"db:generate": "prisma generate",
|
||||||
|
"db:push": "prisma db push",
|
||||||
|
"db:migrate": "prisma migrate dev",
|
||||||
|
"db:migrate:deploy": "prisma migrate deploy",
|
||||||
|
"db:seed": "tsx prisma/seed.ts",
|
||||||
|
"db:studio": "prisma studio",
|
||||||
|
"db:reset": "prisma migrate reset",
|
||||||
|
"email:preview": "email dev --dir emails --port 3001"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@auth/prisma-adapter": "^2.7.0",
|
||||||
|
"@aws-sdk/client-s3": "^3.705.0",
|
||||||
|
"@aws-sdk/s3-request-presigner": "^3.705.0",
|
||||||
|
"@prisma/client": "^5.22.0",
|
||||||
|
"@radix-ui/react-avatar": "^1.1.2",
|
||||||
|
"@radix-ui/react-dialog": "^1.1.4",
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
||||||
|
"@radix-ui/react-label": "^2.1.1",
|
||||||
|
"@radix-ui/react-select": "^2.1.4",
|
||||||
|
"@radix-ui/react-separator": "^1.1.1",
|
||||||
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
|
"@radix-ui/react-toast": "^1.2.4",
|
||||||
|
"@react-email/components": "^0.0.27",
|
||||||
|
"@react-email/render": "^1.0.1",
|
||||||
|
"bcryptjs": "^2.4.3",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
|
"lucide-react": "^0.468.0",
|
||||||
|
"next": "^15.1.0",
|
||||||
|
"next-auth": "^5.0.0-beta.25",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"recharts": "^2.13.3",
|
||||||
|
"resend": "^4.0.0",
|
||||||
|
"tailwind-merge": "^2.5.4",
|
||||||
|
"xlsx": "^0.18.5",
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.49.0",
|
||||||
|
"@tailwindcss/postcss": "^4.0.0",
|
||||||
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
|
"@testing-library/react": "^16.1.0",
|
||||||
|
"@testing-library/user-event": "^14.5.2",
|
||||||
|
"@types/bcryptjs": "^2.4.6",
|
||||||
|
"@types/jsdom": "^28.0.1",
|
||||||
|
"@types/node": "^22.10.1",
|
||||||
|
"@types/react": "^19.0.1",
|
||||||
|
"@types/react-dom": "^19.0.1",
|
||||||
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"eslint": "^9.16.0",
|
||||||
|
"eslint-config-next": "^15.1.0",
|
||||||
|
"jsdom": "^29.1.1",
|
||||||
|
"prisma": "^5.22.0",
|
||||||
|
"react-email": "^3.0.2",
|
||||||
|
"tailwindcss": "^4.0.0",
|
||||||
|
"tsx": "^4.19.2",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"vitest": "^2.1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
25
App/pelagia-portal/playwright.config.ts
Normal file
25
App/pelagia-portal/playwright.config.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: "./tests/e2e",
|
||||||
|
fullyParallel: true,
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
reporter: "html",
|
||||||
|
use: {
|
||||||
|
baseURL: "http://localhost:3000",
|
||||||
|
trace: "on-first-retry",
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: "chromium",
|
||||||
|
use: { ...devices["Desktop Chrome"] },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
webServer: {
|
||||||
|
command: "pnpm dev",
|
||||||
|
url: "http://localhost:3000",
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
},
|
||||||
|
});
|
||||||
10362
App/pelagia-portal/pnpm-lock.yaml
generated
Normal file
10362
App/pelagia-portal/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
7
App/pelagia-portal/postcss.config.mjs
Normal file
7
App/pelagia-portal/postcss.config.mjs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
"@tailwindcss/postcss": {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
23
App/pelagia-portal/tsconfig.json
Normal file
23
App/pelagia-portal/tsconfig.json
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [{ "name": "next" }],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
23
App/pelagia-portal/vitest.config.ts
Normal file
23
App/pelagia-portal/vitest.config.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import { resolve } from "path";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
test: {
|
||||||
|
environment: "jsdom",
|
||||||
|
globals: true,
|
||||||
|
setupFiles: ["./tests/setup.ts"],
|
||||||
|
include: ["tests/unit/**/*.test.ts", "tests/unit/**/*.test.tsx"],
|
||||||
|
coverage: {
|
||||||
|
provider: "v8",
|
||||||
|
include: ["lib/**", "components/**"],
|
||||||
|
exclude: ["lib/db.ts"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": resolve(__dirname, "."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
18
App/pelagia-portal/vitest.integration.config.ts
Normal file
18
App/pelagia-portal/vitest.integration.config.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
import { resolve } from "path";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: "node",
|
||||||
|
globals: true,
|
||||||
|
include: ["tests/integration/**/*.test.ts"],
|
||||||
|
testTimeout: 30000,
|
||||||
|
hookTimeout: 30000,
|
||||||
|
// Run integration tests serially to avoid DB conflicts
|
||||||
|
pool: "forks",
|
||||||
|
poolOptions: { forks: { singleFork: true } },
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: { "@": resolve(__dirname, ".") },
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Add table
Reference in a new issue