# Careers Module Documentation

**Location:** `lib/careers/`  
**Purpose:** Manage job listings for the careers page  
**Last Updated:** November 17, 2025

---

## Architecture Overview

The careers module is organized into separate files for maintainability:

```
lib/careers/
├── index.ts          # Public API (main exports)
├── types.ts          # TypeScript interfaces and types
├── constants.ts      # Configuration and defaults
├── transformers.ts   # Data transformation utilities
├── queries.ts        # Supabase database queries
├── service.ts        # Business logic layer
└── README.md         # This file
```

### Why This Structure?

**Before (single file):**

- ❌ 171 lines in one file
- ❌ Mixed concerns (types, logic, queries, transformations)
- ❌ Hard to test individual pieces
- ❌ Difficult to modify without breaking things

**After (modular):**

- ✅ Clear separation of concerns
- ✅ Easy to test each layer independently
- ✅ Simple to modify configuration without touching logic
- ✅ Well-documented with examples
- ✅ Type-safe with comprehensive interfaces

---

## File Breakdown

### `types.ts` - Type Definitions

**What it contains:**

- `Lang` - Supported languages ('ar' | 'en')
- `JobListing` - Frontend job listing interface
- `JobRecord` - Database record interface
- `StringTranslations` - Bilingual text type
- `StringArrayTranslations` - Bilingual array type
- All enums: `JobStatus`, `ContractType`, `ExperienceLevel`, `Department`

**When to modify:**

- Adding new language support
- Changing job listing fields
- Adding new job statuses or types

**Example:**

```typescript
import type { JobListing, Lang } from "@/lib/careers/types";

const job: JobListing = {
  title: { en: "Engineer", ar: "مهندس" },
  // ... other fields
};
```

### `constants.ts` - Configuration

**What it contains:**

- `DEFAULT_TRANSLATIONS` - Fallback text for missing translations
- `JOBS_TABLE_NAME` - Supabase table name
- `QUERY_CONFIG` - Query limits, sorting, caching
- `JOB_SELECT_COLUMNS` - Database columns to fetch
- `ERROR_MESSAGES` - Standardized error messages
- `LOG_PREFIX` - Logging prefixes

**When to modify:**

- Changing default salary ranges or locations
- Adjusting query limits or caching
- Updating error messages
- Renaming database table

**Example:**

```typescript
import { QUERY_CONFIG } from "@/lib/careers/constants";

console.log(`Max jobs per query: ${QUERY_CONFIG.maxJobsPerQuery}`);
// Output: Max jobs per query: 100
```

### `transformers.ts` - Data Transformation

**What it contains:**

- `parseStringTranslations()` - Transform JSONB to StringTranslations
- `parseArrayTranslations()` - Transform JSONB to StringArrayTranslations
- `transformJobRecord()` - Transform database record to JobListing
- `transformJobRecords()` - Transform multiple records

**When to modify:**

- Changing how database data is parsed
- Adding new transformation logic
- Modifying fallback behavior

**Example:**

```typescript
import { transformJobRecord } from "@/lib/careers/transformers";

const dbRecord = {
  /* database format */
};
const jobListing = transformJobRecord(dbRecord);
// Now jobListing.title.en is type-safe and properly formatted
```

### `queries.ts` - Database Queries

**What it contains:**

- `fetchOpenJobsFromDB()` - Get all open jobs
- `fetchJobBySlug()` - Get single job by slug
- `countOpenJobs()` - Count open positions

**When to modify:**

- Changing query filters
- Adding new query functions
- Modifying sort order

**Example:**

```typescript
import { fetchOpenJobsFromDB } from "@/lib/careers/queries";

const { jobs, error } = await fetchOpenJobsFromDB();
if (error) {
  console.error("Database error:", error);
}
```

### `service.ts` - Business Logic

**What it contains:**

- `fetchOpenJobs()` - Main service function (combines query + transform)
- `fetchJobBySlugService()` - Get and transform single job
- `getOpenJobsCount()` - Get count wrapper

**When to modify:**

- Adding business rules
- Implementing caching
- Adding analytics tracking

**Example:**

```typescript
import { fetchOpenJobs } from '@/lib/careers';

// In a React Server Component
export default async function CareersPage() {
  const jobs = await fetchOpenJobs();
  return <JobList jobs={jobs} />;
}
```

### `index.ts` - Public API

**What it contains:**

- Exports from all other files
- Public API surface
- What consumers should import

**When to modify:**

- Adding new public functions
- Hiding internal implementation details

---

## Usage Examples

### Basic: Fetch and Display Jobs

```typescript
import { fetchOpenJobs } from '@/lib/careers';
import type { JobListing } from '@/lib/careers';

export default async function CareersPage({ params }: { params: { lang: 'ar' | 'en' } }) {
  const jobs = await fetchOpenJobs();

  return (
    <div>
      <h1>Open Positions</h1>
      {jobs.length > 0 ? (
        jobs.map(job => (
          <div key={job.id}>
            <h2>{job.title[params.lang]}</h2>
            <p>{job.salaryRange[params.lang]}</p>
          </div>
        ))
      ) : (
        <p>No open positions</p>
      )}
    </div>
  );
}
```

### Advanced: Custom Query

```typescript
import { fetchOpenJobsFromDB } from "@/lib/careers/queries";
import { transformJobRecords } from "@/lib/careers/transformers";

// Custom filtering example
const { jobs: dbJobs, error } = await fetchOpenJobsFromDB();
if (!error) {
  const engineeringJobs = dbJobs.filter((j) => j.department === "engineering");
  const transformed = transformJobRecords(engineeringJobs);
  console.log(`Found ${transformed.length} engineering positions`);
}
```

### Type-Safe Access

```typescript
import type { JobListing, StringTranslations } from "@/lib/careers";

function displayJobTitle(job: JobListing, lang: "ar" | "en"): string {
  // TypeScript knows job.title is StringTranslations
  // and will error if you try to access an invalid language
  return job.title[lang]; // ✅ Type-safe
  // return job.title['fr']; // ❌ TypeScript error
}
```

---

## Modification Guide

### Adding a New Language

1. Update `types.ts`:

```typescript
export type Lang = "ar" | "en" | "fr"; // Add 'fr'
export const SUPPORTED_LANGS: readonly Lang[] = ["en", "ar", "fr"];
```

2. Update `constants.ts` defaults:

```typescript
export const DEFAULT_TRANSLATIONS = {
  location: {
    en: "Remote",
    ar: "عن بُعد",
    fr: "À distance", // Add French
  },
  // ... other defaults
};
```

3. Update database JSONB fields to include 'fr' keys

### Adding a New Job Field

1. Update database migration to add column
2. Add to `JobRecord` interface in `types.ts`
3. Add to `JobListing` interface in `types.ts`
4. Add to `JOB_SELECT_COLUMNS` in `constants.ts`
5. Add transformation in `transformers.ts`
6. Update relevant tests

### Changing Query Logic

Modify `queries.ts` → `fetchOpenJobsFromDB()`:

```typescript
// Example: Add department filter
export async function fetchEngineeringJobs() {
  const { data, error } = await supabase
    .from(JOBS_TABLE_NAME)
    .select(JOB_SELECT_COLUMNS)
    .eq("status", "open")
    .eq("department", "engineering") // New filter
    .order("opening_at", { ascending: false });

  // ... rest of logic
}
```

### Changing Transformation Logic

Modify `transformers.ts` → `parseStringTranslations()` or `transformJobRecord()`

### Changing Configuration

Modify `constants.ts`:

```typescript
export const QUERY_CONFIG = {
  maxJobsPerQuery: 50, // Changed from 100
  defaultSortField: "created_at", // Changed from 'opening_at'
  revalidateSeconds: 1800, // Changed to 30 minutes
};
```

---

## Error Handling

### Graceful Degradation

All functions handle errors gracefully:

```typescript
// If Supabase not configured
fetchOpenJobs() → returns []

// If jobs table missing
fetchOpenJobsFromDB() → returns { jobs: [], error: {...} }

// If network error
fetchOpenJobs() → returns [] (error logged)
```

### Error Logging

Errors are logged with prefixes for easy filtering:

```
[Careers][Error] Failed to fetch jobs from Supabase: {...}
[Careers][Warning] Supabase client is not configured
```

### Testing Error Scenarios

```typescript
// Mock missing table error
const mockError = { code: "PGRST205", message: "Table not found" };
// Test should verify function returns [] instead of throwing
```

---

## Testing Strategy

### Unit Tests

**Target:** `transformers.ts`, `service.ts`  
**Approach:** Mock Supabase, test logic

### Integration Tests

**Target:** `queries.ts`  
**Approach:** Test against real Supabase database

### E2E Tests

**Target:** Complete flow  
**Approach:** Test browser rendering of transformed data

---

## Performance Considerations

### Database Queries

- **Indexes:** `status`, `opening_at`, `slug` (see migration file)
- **Limit:** Max 100 jobs per query (configurable in `constants.ts`)
- **Caching:** ISR with 1-hour revalidation

### Data Transformation

- **Lazy:** Only transforms when data is fetched
- **Efficient:** Single pass through JSONB objects
- **Type-safe:** No runtime type checking needed

### Frontend

- **Server-side:** fetchOpenJobs() runs on server (zero client JS)
- **Static:** ISR caches results for 1 hour
- **Minimal:** Only necessary data fetched

---

## Migration from Old Structure

The old single-file structure (`lib/careers.ts`) has been refactored but remains **fully backward compatible**.

**No changes needed in:**

- ✅ `app/[lang]/careers/page.tsx` - Same imports work
- ✅ `__tests__/lib/careers.test.ts` - Tests still pass
- ✅ Any other files importing from `@/lib/careers`

**What changed:**

- ✅ Internal organization (modular files)
- ✅ Better documentation
- ✅ Easier to maintain

**Import paths remain the same:**

```typescript
// Still works exactly as before
import { fetchOpenJobs, type JobListing } from "@/lib/careers";
```

---

## Quick Reference

### Common Tasks

```typescript
// Fetch all open jobs
import { fetchOpenJobs } from "@/lib/careers";
const jobs = await fetchOpenJobs();

// Get job count
import { getOpenJobsCount } from "@/lib/careers";
const count = await getOpenJobsCount();

// Fetch specific job
import { fetchJobBySlug } from "@/lib/careers";
const job = await fetchJobBySlug("senior-backend-engineer");

// Access types
import type { JobListing, Lang } from "@/lib/careers";

// Access constants (if needed)
import { DEFAULT_TRANSLATIONS, QUERY_CONFIG } from "@/lib/careers";
```

---

## Related Files

- **Page:** `app/[lang]/careers/page.tsx`
- **Client Component:** `app/[lang]/careers/CareersClient.tsx`
- **Migration:** `supabase/migrations/20251117_create_jobs_table.sql`
- **Tests:** `__tests__/lib/careers.test.ts`, `tests/careers-page.spec.ts`
- **Validation:** `scripts/validate-careers-setup.mjs`

---

## Troubleshooting

### Jobs not displaying?

1. Check if table exists: `npm run validate:careers`
2. Check if jobs in DB: `npm run seed:jobs`
3. Check console for errors: Look for PGRST205

### TypeScript errors after changes?

1. Rebuild: `rm -rf .next && npm run dev`
2. Check type imports match interfaces
3. Verify JSONB structure in database

### Tests failing after refactor?

1. Run: `npm run test:careers:unit`
2. Check if imports are correct
3. Verify mock structure matches new architecture

---

**Maintainer Notes:**

- Keep this README updated when making structural changes
- Document all public API changes
- Run full test suite after modifications: `npm run test:careers:full`
