UGA Index is a demo Next.js application for the Ukrainian Grain Association. It publishes a bilingual public spot export price index for Ukrainian grain and oilseed commodities and includes internal demo workflows for respondent submissions, admin review, index calculation and publication.
The current development deployment is prepared for index-uga.cr0pto.com, but the app does not hardcode production domains. Public URLs, embed URLs and frame allowlists are configured through environment variables.
Public site:
- Ukrainian and English locale routes:
/uk,/en - locale detection from cookie and country headers on
/ - public homepage with current index values, FX display conversion and UGA branding
- About, Methodology and Analytics pages
- Privacy Policy, Terms of Use and Risk Disclosure pages
- embeddable cards and chart widgets for the UGA website
- public API routes for latest and historical index data
Internal demo:
- allowlist-based demo login with email/password only
- admin and respondent roles inferred from the demo allowlist
- admin daily input matrix with Spike Brokers indicative comparison
- respondent daily survey tied to one respondent company
- calculation and publish workflow with outlier handling
- demo audit and publication behavior using database-backed data when available, with mock fallback where implemented
- Next.js App Router
- React
- TypeScript
- Tailwind CSS
- Prisma
- PostgreSQL-ready data model
- Vitest
- ESLint
Install dependencies:
npm installCopy environment variables:
cp .env.example .env.localStart the development server:
npm run devOpen http://localhost:3000.
Required variables are listed in .env.example:
DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/uga_index?schema=public"
NEXT_PUBLIC_SITE_URL="https://index-uga.cr0pto.com"
ALLOWED_EMBED_ORIGINS="https://uga.ua https://www.uga.ua https://index-uga.cr0pto.com http://localhost:* http://127.0.0.1:*"
DEMO_AUTH_SECRET="replace-with-a-long-random-secret"Notes:
DATABASE_URLis required for Prisma-backed persistence and seeding.NEXT_PUBLIC_SITE_URLis used for public absolute URLs and embed snippets.ALLOWED_EMBED_ORIGINScontrols the frame allowlist for/embed/*.DEMO_AUTH_SECRETsigns the simple demo session cookie.
The demo uses an allowlist in src/lib/demo-allowlist.ts. Users do not select their role in the login form.
Primary credentials:
- Admin:
admin@uga.ua/admin - Respondent:
bunge@uga-index.demo/respondent
Presentation shortcuts:
admin/adminrespondent/respondent
Post-login routing:
- Admin users go to
/admin/daily-inputs. - Respondent users go to
/respondent.
Production auth is documented in docs/auth.md.
Public:
//uk,/en/uk/about,/en/about/uk/methodology,/en/methodology/uk/analytics,/en/analytics/uk/privacy,/en/privacy/uk/terms,/en/terms/uk/risk-disclosure,/en/risk-disclosure
Internal:
/login/logout/admin/admin/daily-inputs/admin/calculate/respondent
Embeds:
/embed/cards/embed/chart/embed/uga-index.js
Public API:
GET /api/healthGET /api/public/latestGET /api/public/historyGET /api/public/fx-rates
Generate Prisma client:
npm run db:generateFor a local PostgreSQL database:
npx prisma db push
npm run db:seedFor production-style migration flow after migrations are committed:
npx prisma migrate deploy
npm run db:seedThe seed creates commodities, FOB Black Sea delivery basis, respondents, demo users, 14 days of mock respondent submissions, Spike indicatives and published demo indices.
More detail is in docs/database.md.
The calculation engine is in src/lib/index-calculation.ts.
Rules:
- collect respondent prices by date, commodity and delivery basis
- calculate the median
- exclude prices deviating more than +/-2% from the median
- calculate the arithmetic average of the cleaned sample
- require at least 5 included respondent prices for
publishable - keep official published values in USD/t
- support future weighted baskets while demo uses a single basket
Unit tests are in src/lib/index-calculation.test.ts.
Use NEXT_PUBLIC_SITE_URL when preparing embed snippets.
Cards iframe example:
<iframe
src="https://index-uga.cr0pto.com/embed/cards?locale=en&theme=light&layout=cards"
title="UGA Index"
loading="lazy"
style="width: 100%; height: 420px; border: 0; display: block;"
></iframe>JavaScript loader example:
<div id="uga-index-widget"></div>
<script
src="https://index-uga.cr0pto.com/embed/uga-index.js"
data-target="#uga-index-widget"
data-locale="en"
data-theme="light"
data-layout="cards"
></script>More detail is in docs/embed.md.
The project is prepared for Vercel deployment.
Checklist:
- Connect the GitHub repository to Vercel.
- Configure environment variables from
.env.example. - Configure the development domain, currently
index-uga.cr0pto.com. - Run Prisma setup against the target PostgreSQL database.
- Run validation before deployment.
Validation commands:
npm run lint
npm test
npm run buildHealth check:
curl https://YOUR_DOMAIN/api/healthMore detail is in docs/deployment.md.
Project docs:
docs/product-brief.mddocs/implementation-plan.mddocs/database.mddocs/auth.mddocs/deployment.mddocs/embed.mddocs/demo-script.mddocs/known-limitations.mddocs/legal.mddocs/source-analysis.mddocs/variant-design-analysis.md
Source reference materials are stored under docs/source/.
Run before committing:
npm run lint
npm test
npm run build- Replace demo auth with production allowlist auth, password setup emails and hashed passwords.
- Integrate real Spike Brokers indicative ingestion.
- Replace any remaining mock fallback paths with durable database workflows where required.
- Finalize production legal text with legal counsel.
- Add paid analytics, UGA member access and API subscriber entitlement handling.
- Add production observability, backup and operational runbooks.