Documentation is currently in beta. Report issues →
ContributingCode Style

Code Style

Coding conventions and patterns used in Y3NKO.

TypeScript

Strict Mode

Use TypeScript strictly — no any types:

// Bad
function process(data: any) { }
 
// Good
interface UserData {
  id: string
  name: string
}
function process(data: UserData) { }

Type Definitions

Place shared types in types/index.ts:

// types/index.ts
export interface ListingFilters {
  region?: string
  city?: string
  minPrice?: number
  maxPrice?: number
  type?: ListingType
}
 
export type BookingStatus = 'PENDING' | 'CONFIRMED' | 'CANCELLED' | 'COMPLETED'

Prisma Types

Use Prisma’s generated types:

import { Listing, Booking } from '@prisma/client'
import { Prisma } from '@prisma/client'
 
// With relations
type ListingWithImages = Prisma.ListingGetPayload<{
  include: { images: true }
}>

React Components

Server Components (Default)

// app/accommodations/page.tsx
import { getListings } from '@/lib/actions/listings'
 
export default async function AccommodationsPage() {
  const listings = await getListings()
 
  return (
    <main>
      <ListingGrid listings={listings} />
    </main>
  )
}

Client Components

Only use when needed for interactivity:

// components/features/search/search-bar.tsx
"use client"
 
import { useState } from 'react'
 
export function SearchBar({ onSearch }: SearchBarProps) {
  const [query, setQuery] = useState('')
 
  return (
    <input
      value={query}
      onChange={(e) => setQuery(e.target.value)}
    />
  )
}

Component Props

Define interfaces for props:

interface ListingCardProps {
  listing: Listing & { images: ListingImage[] }
  showFavorite?: boolean
  className?: string
}
 
export function ListingCard({ listing, showFavorite = true, className }: ListingCardProps) {
  // ...
}

File Naming

TypeConventionExample
ComponentsPascalCaseListingCard.tsx
UtilitiescamelCaseformatCurrency.ts
Pagespage.tsxapp/accommodations/page.tsx
Layoutslayout.tsxapp/layout.tsx
API Routesroute.tsapp/api/bookings/route.ts
Server ActionscamelCaselib/actions/bookings.ts

Imports

Use path aliases:

// Good
import { Button } from '@/components/ui/button'
import { prisma } from '@/lib/db'
 
// Avoid relative imports going up multiple levels
import { Button } from '../../../components/ui/button'

Order imports:

  1. React/Next.js
  2. Third-party libraries
  3. Local modules (@/)
import { Suspense } from 'react'
import Link from 'next/link'
import { format } from 'date-fns'
import { prisma } from '@/lib/db'
import { Button } from '@/components/ui/button'

Error Handling

Server Actions

"use server"
 
export async function createBooking(data: BookingInput) {
  const user = await getCurrentUser()
  if (!user) {
    throw new Error('Please log in to book')
  }
 
  try {
    // Operation
    return { success: true, data: booking }
  } catch (error) {
    console.error('Booking error:', error)
    throw new Error('Failed to create booking')
  }
}

API Routes

export async function POST(request: Request) {
  try {
    // Operation
    return NextResponse.json({ data: result })
  } catch (error) {
    console.error('API error:', error)
    return NextResponse.json(
      { error: { code: 'INTERNAL_ERROR', message: 'Something went wrong' } },
      { status: 500 }
    )
  }
}

Validation

Use Zod for all input validation:

import { z } from 'zod'
 
export const bookingSchema = z.object({
  listingId: z.string().cuid(),
  checkIn: z.string().datetime(),
  checkOut: z.string().datetime(),
  adults: z.number().int().min(1).max(20),
  guestEmail: z.string().email(),
  guestPhone: z.string().regex(/^\+233[0-9]{9}$/),
}).refine(
  data => new Date(data.checkOut) > new Date(data.checkIn),
  { message: "Check-out must be after check-in" }
)

CSS / Styling

Tailwind Classes

Order classes logically:

  1. Layout (display, position)
  2. Sizing (width, height)
  3. Spacing (margin, padding)
  4. Typography
  5. Colors/Background
  6. Effects
  7. Responsive
<div className="flex items-center justify-between w-full p-4 text-lg font-medium text-gray-900 bg-white rounded-lg shadow-md hover:bg-gray-50 md:p-6">

Custom CSS Variables

Use CSS variables for brand colors:

// Use CSS variables
<div className="text-[var(--ghana-red)]">
 
// Or direct hex for clarity
<div className="text-[#CE1126]">

Comments

Comment why, not what:

// Bad - describes what
// Loop through users
for (const user of users) { }
 
// Good - explains why
// Process users in batches to avoid memory issues
for (const batch of chunks(users, 100)) { }

Testing

Unit Tests

// lib/__tests__/pricing.test.ts
import { calculatePricing } from '../pricing'
 
describe('calculatePricing', () => {
  it('calculates total correctly', () => {
    const result = calculatePricing({
      pricePerNight: 100,
      nights: 3,
      currency: 'GHS'
    })
 
    expect(result.subtotal).toBe(300)
    expect(result.total).toBeGreaterThan(300) // Includes fees
  })
})

Accessibility

Always include:

  • aria-label for icon-only buttons
  • alt text for images
  • Proper heading hierarchy
  • Focus states
<button aria-label="Add to favorites">
  <Heart className="w-5 h-5" />
</button>
 
<Image src={url} alt="Villa exterior with pool" />