This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
sedekah.je is a directory of QR codes for mosques, suraus, and other religious institutions in Malaysia. It's a community-driven platform built with Next.js 14, TypeScript, PostgreSQL (Drizzle ORM), and Better Auth.
# Development
bun dev # Start development server
bun build # Production build
bun start # Production server
# Code Quality
bun run check # Run Biome lint + format
bun run lint # Lint only
bun run format # Format code
bun run type-check # TypeScript checking
# Database
bun run db:seed # Seed database
bun run db:truncate # Clear database
# Utilities
bun run clean # Remove node_modules, .next, locks
bun run import:walter-qrs # Bulk QR import from scripts/data (see script header)
bun run match:walter-institutions # Read-only Walter vs existing similarity audit (see script header)
bun run review:walter-medium # GPT-4o-mini review of nameMatchesMedium (OPENAI_API_KEY; see script header)Important: This project uses Bun as the package manager and runtime. Always use bun instead of npm or yarn.
/app/(admin)/- Admin dashboard with role-based access/app/(user)/- User-facing features/app/api/- API routes and server actions/app/[institution]/[slug]/- Dynamic institution pages
/components/- React components (uses shadcn/ui)/lib/- Utilities, database queries, server actions/db/- Database schema and migrations (Drizzle ORM)/hooks/- Custom React hooks
Core entities:
- users - Authentication and profiles
- institutions - QR codes, locations, approval status
- auth sessions/accounts - Better Auth tables
Workflow: Institutions go through Pending → Approved/Rejected states.
- Framework: Next.js 14 with App Router
- Database: PostgreSQL with Drizzle ORM
- Auth: Better Auth with Google OAuth
- Styling: Tailwind CSS + shadcn/ui components
- Code Quality: Biome (replaces ESLint + Prettier)
- State: TanStack React Query + URL state (nuqs)
- Storage: Cloudflare R2 for images
- Security: Cloudflare Turnstile
- Use Biome for all formatting and linting (tabs, 80 chars, configured in
biome.json) - TypeScript strict mode - NO
anytypes, proper inference - Run
bun run checkbefore committing - Follow Malaysian context for UI/UX (Bahasa Malaysia, payment systems)
Feature-Based Structure (avoid file type grouping):
app/(user)/feature/
├── _lib/
│ ├── queries.ts # Server queries (cached with unstable_cache)
│ ├── actions.ts # Server actions (mutations)
│ ├── validations.ts # Zod schemas
│ └── types.ts # TypeScript types
├── _components/ # Private components
└── page.tsx # Route page
- Server Actions First - Use server actions instead of API routes
- Avoid pg enums - Use object constants for easier production management
- No useEffect for data fetching - Fetch at server component, pass as props
- Server Components by Default - Only use "use client" when needed
- Layout-based route protection:
/app/(user)/layout.tsxand/app/(admin)/layout.tsx - Server-side:
const session = await auth.api.getSession({ headers: headers() }) - Client-side:
useAuth()hook for user state - Roles: "user" (contributors) and "admin" (approval rights)
- Performance: Wrap queries with
unstable_cachefor caching - Batch Operations: Use
inArray()instead of Promise.all loops - Counting: Use Drizzle
count()function, not.length - Cache Invalidation: Use
revalidateTag()for targeted invalidation - Schema: Export types with
$inferSelectand$inferInsert
- Zod schema in
_lib/validations.ts - Server action in
_lib/actions.tswith proper validation - React Hook Form + useFormState for client component
- Always validate on server, handle authentication
- Use
revalidatePath()after mutations
- UI Streaming: Break complex pages into async components with
<Suspense> - Caching: Use
unstable_cachewith appropriate TTL (300s for dynamic, 900s for stable) - Server Components: Fetch data at server level, minimize client-side requests
No formal testing framework is currently set up. Consider this when making changes that require testing.
mosque(masjid) - Blue theme colorsurau- Green theme colorothers(lain-lain) - Violet theme color
duitnow- Malaysian instant payment systemtng- Touch 'n Go eWalletboost- Boost eWallet
pending- Awaiting admin approval (default for new submissions)approved- Approved by admin and visible to publicrejected- Rejected by admin with reason
- Static Data: Historical institutions in
app/data/institutions.ts - Dynamic Data: User-contributed institutions in PostgreSQL
- Combined Display: Both sources shown together on maps and listings
- QR Code Processing: Automatic extraction and validation of payment QR codes
- Geolocation: Institution mapping with Malaysian state-based filtering
- PWA: Progressive Web App configuration
- Telegram Integration: Logging for new user registrations and institution submissions
- Workflow Management: Institution approval system with admin controls
- Malaysian States: All 16 states and federal territories with flag support
- Update schema in
/db/schema/institutions.ts - Create migration with Drizzle
- Update form components in
/components/ - Update validation schemas
- Create route in
/app/api/ - Use server actions for mutations
- Add queries in
/lib/queries/ - Update React Query hooks
- Connection configured through
DATABASE_URL - Optional
DIRECT_URLfor connection pooling - Schema changes require migrations via Drizzle
Remember: This is a community project focused on Malaysian religious institutions. Maintain respect for the cultural context and ensure all QR codes are legitimate donation channels.