TailwindCSS v4 changed the setup flow. AI coding agents still reach for old config files and deprecated commands, so developers need to check the official docs before accepting setup code.

Overview
Eco Garden is a mixed-use residential compound in Kandal Province, Cambodia with a growing colony of stray cats. Residents fed them inconsistently, none were sterilized, and sick cats had no reliable way to get medical attention. I built this platform to give the community one place to manage and fund their care.
This is a live platform, not a template or demo. People needed to donate, volunteers needed to sign up, the community needed to see where money went, and each cat needed a profile so residents could connect with them and sponsor their care.
Bilingual from Day One
The site runs in English and Khmer using next-intl. The community includes expats and Cambodian residents, so English-only content would exclude half the support base. I translated every string, label, content block, form placeholder, and error message. Khmer text uses Suwannaphum with adjusted line heights for the taller script.

The Eco Garden Cat Project homepage with hero section showing the colony cats
The Cat Registry
Each cat has a profile page with a photo gallery, personality description, medical history timeline, and status badges for sterilization, vaccination, and recovery. The "Meet the Colony" page lets visitors filter by sterilization status, vaccination status, health, and adoption availability. Filters update without a page reload.
Cat profiles pull status from a cat_history table that tracks vaccinations, spay/neuter surgeries, and vet visits. The current status badge comes from the latest history entry instead of a manually updated field.

Meet the Cats page with filter options for sterilization, vaccination, health, and adoption status
KHQR Donation Flow
Cambodia's payment habits differ from card-heavy markets. Most people pay with mobile banking apps that scan QR codes. I integrated KHQR, Cambodia's national QR payment standard, with the ts-khqr library. Donors can pick a tier ($5 for wet food, $15 for dry food, $25 for a vaccination, $50 for TNR surgery) or enter a custom amount, then scan a QR code with a countdown timer. After paying, they confirm the donation and it enters a pending verification queue in the admin panel.
This flow was the most technical part: generating KHQR-compliant QR codes server-side, showing an expiry countdown, and handling confirmation because KHQR does not provide real-time payment webhooks.

Donation page with tiered giving options and KHQR payment integration
Radical Transparency
Donation projects live or die on trust. The Transparency page shows every donation received and every expense incurred, with receipt links for vet bills and food purchases. Summary cards show total donations, total expenses, and current balance. I built it because donors need to verify where their money went.

Transparency ledger showing total donations, expenses, and every transaction with receipt links
Colony Statistics
The homepage pulls live stats from the database: total cats in the colony, cats helped, total donations received, and sterilization count. These numbers come from actual records. When an admin adds a cat or verifies a donation, the stats update.

Homepage stats showing cats in colony, cats helped, donations received, and sterilizations
Adoption & Fostering
Some colony cats are socialized enough for adoption or fostering. Admins can mark those cats as available, profiles show an adoption badge, and visitors can submit inquiry forms that email the team through Resend. The homepage also has a "Looking for a Home" section for adoptable cats.
Admin CMS
The entire site is managed through a protected admin panel with sections for managing cats (add/edit/delete with photo uploads to Supabase Storage), the financial ledger (log income and expenses), blog posts (Markdown-supported content editor), donation verification (approve/reject pending donations), and campaign goals (fundraising progress bars). Authentication uses Supabase Auth with row-level security so only authenticated admins can modify data.

Admin portal
Volunteer Recruitment
The Get Involved page lists volunteer roles (feeding shifts, vet transport, fostering, community outreach) with time commitments, a sign-up form, and an FAQ section. Form submissions trigger email notifications to the team so they can follow up within 48 hours.
Tech Stack
- Next.js 16: App Router with server components for SEO-friendly pages and server actions for mutations
- TypeScript: End-to-end type safety for the financial ledger and donation logic
- Tailwind CSS v4: Utility-first styling with CSS custom properties for the earthy, nature-inspired color palette
- Supabase: PostgreSQL database, Auth, Storage (cat photos), and Row Level Security policies
- next-intl: Full i18n for English and Khmer with locale-aware routing
- ts-khqr: Cambodia's national QR payment standard for mobile donations
- Resend: Transactional emails for volunteer sign-ups and adoption inquiries
What I Learned
Building for a real community changes how you treat each feature. Cats need feeding, donations need tracking, and a broken flow can stop someone from helping. The transparency ledger taught me that trust features are core product features in donation platforms. Nobody donates to a black box.
The bilingual implementation also reinforced that i18n is more than translated strings. Khmer script needs different typography rules, text length changes break some layouts, and dates need locale-aware formatting.
Related Projects
Personal Portfolio (2026)
A developer portfolio with cursor-driven motion, MDX project pages, blog comments, structured data, and a fast Next.js 15 build.
Baguette POS
A restaurant POS for Cambodia with fast order entry, table management, dual-currency payments, availability controls, and reporting.
Related Writing
A guide to asynchronously load Google Fonts in Gatsby using gatsby-plugin-web-font-loader

Hide controls when users cannot use them. Disable controls when users need to understand that an action exists but is not available yet.


