{"id":27248710,"url":"https://github.com/teeldinho/library","last_synced_at":"2026-04-06T01:34:22.146Z","repository":{"id":286796031,"uuid":"959351648","full_name":"Teeldinho/library","owner":"Teeldinho","description":"A modern job search interface built with Next.js 15 App Router, featuring real-time location autocomplete, multilingual support, and URL-driven state management – powered by a custom CSS design system.","archived":false,"fork":false,"pushed_at":"2025-04-08T11:21:32.000Z","size":1173,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-01-03T14:35:11.737Z","etag":null,"topics":["css","css-variables","cva","hybrid-ssr","nextjs","nuqs","reactjs","ssr","zod","zod-validation"],"latest_commit_sha":null,"homepage":"https://library.teeldinho.co.za","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Teeldinho.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-04-02T16:46:31.000Z","updated_at":"2025-11-18T11:10:32.000Z","dependencies_parsed_at":"2025-04-08T12:39:25.625Z","dependency_job_id":null,"html_url":"https://github.com/Teeldinho/library","commit_stats":null,"previous_names":["teeldinho/library"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Teeldinho/library","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Teeldinho%2Flibrary","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Teeldinho%2Flibrary/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Teeldinho%2Flibrary/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Teeldinho%2Flibrary/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Teeldinho","download_url":"https://codeload.github.com/Teeldinho/library/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Teeldinho%2Flibrary/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31456661,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T21:22:52.476Z","status":"ssl_error","status_checked_at":"2026-04-05T21:22:51.943Z","response_time":75,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css","css-variables","cva","hybrid-ssr","nextjs","nuqs","reactjs","ssr","zod","zod-validation"],"created_at":"2025-04-10T23:41:37.661Z","updated_at":"2026-04-06T01:34:22.128Z","avatar_url":"https://github.com/Teeldinho.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Library Job Search\n\nA modern job search interface built with Next.js 15 App Router, featuring real-time location autocomplete, multilingual support, and URL-driven state management - powered by a custom CSS design system.\n\n![Job Search Interface](public/desktop-hero-2.png)\n\n## Architectural Highlights\n\n1. **Compositional UI** - The render props pattern allows component consumers to customize presentation while the core handles complex behaviors\n2. **Type-safe interactions** - End-to-end TypeScript integration with discriminated unions ensures robust error handling\n3. **Performance optimization** - Server Components for data fetching paired with client interactivity creates an optimal user experience\n4. **Internationalization by design** - Built-in multilingual support with locale-specific routing enhances accessibility\n5. **State preservation** - URL-driven state management with NUQS enables shareable application states\n6. **Resilient data handling** - Robust promise handling with Suspense and fallback mechanisms ensures graceful degradation\n\n### Frontend\n\n- **Next.js 15 App Router**: Server components enable hybrid rendering, fetching location data and passing promises to client components, which unwrap the promises on the client using the `use` hook, optimizing initial load performance while maintaining interactivity.\n- **NUQS**: URL-based state management enables shareable search states and seamless server/client synchronization.\n- **CSS Modules and CSS Variables**: Custom design system with design tokens using CSS variables, utility classes, and component variants.\n- **next-intl**: Internationalization with EN/FR/ES support and locale-specific routing.\n- **TypeScript**: End-to-end type safety with discriminated unions for error and success states.\n\n## Key Features\n\n- **Smart Location Autocomplete**:\n\n  - **Headless Component Architecture**:\n\n    - Separation of core logic from UI presentation\n    - Accessibility-first design with ARIA attributes\n    - Keyboard navigation and focus management\n\n  - **Render Props Pattern**:\n\n    - Complete UI customization through `renderItem` prop\n    - Composable with design system components (HStack, Label)\n    - State-aware rendering with highlighted/selected indicators\n\n  - **Production-Ready Implementation**:\n    - Real-time API integration with location service\n    - Elegant loading, error, and empty states with Suspense boundaries\n    - Character validation with user feedback\n    - Consistent styling with automatic hover states\n    - Resilient error handling with graceful fallbacks\n\n- **Multilingual Interface**:\n\n  - Complete internationalization with URL-based locale switching\n  - Translation files for English, French, and Spanish\n  - Server and client component translation separation\n\n- **Responsive Design**:\n\n  - Mobile-first approach with CSS Grid and flexible layouts\n  - Custom spacing system for consistent component relationships\n  - Form elements that adapt to different viewport sizes\n\n- **Hybrid Rendering**:\n  - Server-side data fetching for optimal loading performance\n  - Progressive enhancement with client-side interactivity\n  - Streaming with Suspense for improved user experience\n\n## Architectural Decisions\n\n### Feature-Sliced Design-inspired File Structure\n\nThe project adopts a simplified version of Feature-Sliced Design (FSD) for code organization:\n\n```\nsrc/\n├── features/          # Business domains\n│   └── job-search/    # Domain-specific feature\n│       ├── api/       # API clients \u0026 queries\n│       ├── helpers/   # Helper functions, utilities (and dummy data) that maintain at least one level of abstraction -- keeping our components clean\n│       ├── models/    # Type definitions \u0026 schemas\n│       └── ui/        # Presentational components\n├── components/        # Shared UI primitives\n├── styles/            # Global styles \u0026 design system\n└── lib/               # Generic utilities\n```\n\nThis structure provides clear boundaries between different concerns, enabling:\n\n1. **Better code navigation** by grouping related files\n2. **Improved maintainability** through isolation of feature modules\n3. **Easier onboarding** for new developers through consistent patterns\n4. **Scalability** as new features can be added without affecting existing ones\n\n### UI State Components\n\nThe application implements specialized components for handling different UI states:\n\n```typescript\n// Loading state with customizable message and spinner\n\u003cLoadingState\n  message=\"Loading content...\"\n  variant=\"default\" // optional, default is \"default\"\n  size=\"sm\" // optional, default is \"sm\"\n  alignment=\"left\" // optional, default is \"center\"\n/\u003e\n\n// Loading list with customizable message and spinner\n\u003cLoadingList\n  message=\"Loading content...\"\n/\u003e\n\n// Empty state with customizable message and icon\n\u003cEmptyState\n  message=\"No results found\"\n  variant=\"muted\" // optional, default is \"default\"\n  size=\"md\" // optional, default is \"sm\"\n  hideIcon={true} // optional, default is false\n/\u003e\n\n// Empty list with customizable message\n \u003cEmptyList message={t(\"noSuggestions\")} /\u003e; // this uses the t function from next-intl to get the message from the translations\n```\n\nThese components provide:\n\n1. **Consistent user feedback** - Standardized appearance for loading and empty states\n2. **Flexible presentation** - Customizable sizing, alignment, and styling\n3. **Accessibility** - Proper ARIA attributes and semantic structure\n4. **List variants** - Specialized `LoadingList` and `EmptyList` for dropdown contexts\n\nTogether with Suspense boundaries, these components create a cohesive approach to handling asynchronous operations and empty datasets throughout the application.\n\n### Promise Handling with Suspense\n\nThe application leverages React 19's modern data fetching patterns with Suspense to create a fluid user experience:\n\n```typescript\n// Conceptual flow in AutoComplete component\n\u003cSuspense fallback={\u003cLoadingList message=\"Loading suggestions...\" /\u003e}\u003e\n  \u003cSuggestionsList suggestions={suggestionsPromise} /\u003e\n\u003c/Suspense\u003e\n```\n\nThis pattern allows for:\n\n1. **Progressive rendering** - The UI is streamed from server to client, allowing users to see and interact with parts of the page while other components are still loading\n2. **Decoupled data fetching** - Suspense boundaries isolate loading states to specific components rather than entire pages\n3. **Sequential user flows** - Loading states are shown before no-results states, creating a logical progression for users\n4. **`Use` hook integration** - React 19's `use` hook unwraps promises safely within Suspense boundaries\n5. **Fallback resiliency** - API failures trigger fallbacks to mock data after simulated delays\n\nThe component gracefully handles both static data arrays and promises that resolve to data, providing flexibility in data sourcing while maintaining consistent UI patterns.\n\n### URL-Driven State with NUQS and Hybrid Rendering\n\nThe application combines URL-based state management with Next.js 15's hybrid rendering for a powerful developer and user experience:\n\n```typescript\n// URL state definition\nexport const searchParamsObject = {\n  keywords: parseAsString.withDefault(\"\").withOptions({\n    throttleMs: 300, // Debounce user input\n    shallow: true, // No request is made to the server when the URL changes\n  }),\n  location: parseAsString.withDefault(\"\").withOptions({\n    throttleMs: 750, // Debounce user input\n    shallow: false, // A request is made to the server when the URL changes\n  }),\n};\n```\n\nThis approach creates several key advantages:\n\n1. **Stateful URLs** - When a user shares `https://library.teeldinho.co.za/en?location=Great` (click this [link](https://library.teeldinho.co.za/en?location=Great) for demo), the recipient receives a page with pre-populated state\n2. **Hydration optimization** - Next.js recognizes URL parameters and can pre-fetch data server-side before sending HTML\n3. **Reduced loading states** - When navigating to a URL with parameters, loading states may be minimized or eliminated entirely as data is already available\n4. **Seamless transitions** - Server Components can prepare data based on URL parameters before client hydration\n5. **State persistence** - Browser history navigation preserves application state without additional client-side code\n\nFor example, when a user selects \"Manchester\" from the location dropdown, `setLocation(\"Manchester\")` updates the URL to `?location=Manchester`. If this URL is shared, Next.js can use this parameter to fetch location data server-side, potentially eliminating loading indicators entirely for the recipient.\n\nThis pattern exemplifies Next.js 15's \"full-stack to the frontend\" approach, where server and client collaborate to optimize both initial and subsequent page loads.\n\n### Type-Safe API Layer\n\nThe application implements a robust pattern for API interactions:\n\n```typescript\n// Discriminated union type for success/error states\nexport type FetchResult\u003cT\u003e = { status: \"success\"; data: T } | { status: \"error\"; message: string };\n\n// Helper functions enforce the correct Success shape\nexport const handleSuccess = \u003cT\u003e(data: T): FetchResult\u003cT\u003e =\u003e ({\n  status: \"success\",\n  data,\n});\n\n// Helper functions enforce the correct Error shape\nexport const handleError = (message: string): FetchResult\u003cnever\u003e =\u003e ({\n  status: \"error\",\n  message,\n});\n```\n\nThis pattern provides several benefits:\n\n1. **Compile-time error checking** prevents invalid state combinations\n2. **Exhaustive pattern matching** with TypeScript ensures all cases are handled\n3. **Consistent error handling** across the application\n4. **Self-documenting code** clearly shows possible outcomes\n\n### Data Transformation Pipeline\n\nThe application implements a clear data transformation flow to decouple frontend from backend concerns:\n\n```typescript\n// 1. Schema definition validates API response structure (schemas.ts)\nexport const LocationsApiResponseSchema = z.array(\n  z.object({\n    label: z.string(),\n    terms: z.array(z.string()),\n    displayLocation: z.string(),\n  })\n);\n\n// 2. DTO definition - we use the schema to infer and define the DTO that will be used by the API layer (schemas.ts)\nexport type LocationApiDTO = z.infer\u003ctypeof LocationApiSchema\u003e;\n\n// 3. RTO definition - we define the RTO that will be used by the UI components (mappers.ts)\nexport interface LocationRTO {\n  label: string; // Display text\n  value: string; // Form value\n}\n\n// 4. Mapper function - we use the DTO to transform the API response data to the RTO (mappers.ts)\nexport function mapLocationDtoToRto(dto: LocationApiDTO): LocationRTO {\n  const primaryTerm = dto.terms[0]; // Extract primary term for value\n  return {\n    label: dto.label,\n    value: primaryTerm, // Extract primary term for value\n  };\n}\n\n// 5. API layer uses schemas for validation and mappers for transformation (queries.ts)\nexport async function fetchLocations(query: string): Promise\u003cFetchResult\u003cLocationRTO[]\u003e\u003e {\n  // ...fetch implementation\n  const data = await response.json();\n\n  // Validate the API response structure using our ZOD schema\n  const parsedData = LocationsApiResponseSchema.safeParse(data);\n\n  // If the data is valid, transform it to the RTO format and return it\n  return parsedData.success ? handleSuccess(parsedData.data.map(mapLocationDtoToRto)) : handleError(\"Invalid data format\");\n  // ...error handling and fallbacks\n}\n\n// 6. UI components consume the RTOs (location-search.tsx)\n\u003cAutoComplete\u003cLocationRTO\u003e\n  suggestions={suggestionsPromise}\n  onSelect={(item) =\u003e setLocation(item.value)}\n  // ...other props\n  renderItem={({ item }) =\u003e (\n    \u003cHStack\u003e\n      \u003cLabel\u003e{item.label}\u003c/Label\u003e\n      \u003cLabel\u003e{item.value}\u003c/Label\u003e\n    \u003c/HStack\u003e\n  )}\n/\u003e;\n```\n\nThis pattern creates a robust frontend architecture:\n\n1. **API contract validation** - Zod schemas ensure API responses match expected structure\n2. **Backend isolation** - Changes to API responses only require updates to mappers, not UI components\n3. **Frontend optimization** - RTOs contain only data needed by UI components\n4. **Graceful degradation** - Error handling with mock data fallbacks\n5. **Type safety** - TypeScript ensures correct data usage throughout the application\n\n### Design System with CSS Variables\n\nThe project implements a comprehensive design system using CSS custom properties instead of a UI framework like Tailwind:\n\n```css\n/* Scale-based tokens in tokens.css */\n:root {\n  --primary-300: oklch(55% 0.15 250);\n  --space-md: 0.75rem;\n  --text-lg: 1rem;\n  --form-element-height: 2.8125rem;\n}\n```\n\nThis approach provides several benefits:\n\n1. **Visual consistency** through centralized design tokens\n2. **Reduced bundle size** by avoiding external UI libraries\n3. **Improved performance** with native CSS instead of utility class processing\n4. **Better maintainability** through semantic variable naming\n\n### Headless Component Pattern\n\nThe application implements a powerful headless component pattern for the autocomplete functionality:\n\n```typescript\n// Core functionality without UI (auto-complete-headless.tsx)\nexport function AutocompleteHeadless\u003cT extends AutocompleteSuggestion\u003e({\n  onSelect,\n  inputValue,\n  onInputChange,\n  itemToString,\n  children,\n}: AutocompleteHeadlessProps\u003cT\u003e) {\n  const [isOpen, setIsOpen] = useState(false);\n  const [highlightedIndex, setHighlightedIndex] = useState\u003cnumber | null\u003e(null);\n\n  // State and behavior logic...\n\n  return children({\n    inputProps: {\n      id: inputId,\n      value: inputValue,\n      onChange: handleInputChange,\n      onKeyDown: handleKeyDown,\n      onFocus: () =\u003e setIsOpen(true),\n      // More accessibility attributes...\n    },\n    listProps: {\n      id: listId,\n      role: \"listbox\",\n    },\n    getItemProps,\n    isOpen,\n    highlightedIndex,\n  });\n}\n\n// Implementation with UI (auto-complete.tsx)\n\u003cAutocompleteHeadless suggestions={suggestions} onSelect={onSelect} inputValue={inputValue} onInputChange={onInputChange} itemToString={itemToString}\u003e\n  {({ inputProps, listProps, getItemProps, isOpen, highlightedIndex }) =\u003e (\n    \u003cdiv className={styles.autocomplete}\u003e\n      {label \u0026\u0026 \u003cLabel htmlFor={inputProps.id}\u003e{label}\u003c/Label\u003e}\n\n      \u003cInput {...inputProps} placeholder={placeholder} aria-describedby={listProps.id} /\u003e\n\n      {isOpen \u0026\u0026 hasMinChars(inputValue) \u0026\u0026 (\n        \u003cdiv className={styles.suggestionsContainer}\u003e\n          \u003cSuspense fallback={\u003cLoadingList message={t(\"loadingSuggestions\")} /\u003e}\u003e\n            \u003cSuggestionsList\n              suggestions={suggestions}\n              // Other props...\n              renderItem={renderItem}\n            /\u003e\n          \u003c/Suspense\u003e\n        \u003c/div\u003e\n      )}\n    \u003c/div\u003e\n  )}\n\u003c/AutocompleteHeadless\u003e;\n```\n\nThis pattern offers several key advantages:\n\n1. **Separation of concerns** - Core logic (keyboard navigation, state management) is isolated from the UI presentation\n2. **Accessibility by default** - ARIA attributes and keyboard interactions are handled by the headless component\n3. **Flexible UI customization** - The `renderItem` prop enables complete control over suggestion appearance\n4. **Progressive enhancement** - Suspense integration enables elegant loading and empty states\n5. **Type safety** - Generic typing ensures consistent data handling throughout the component hierarchy\n\nThe headless pattern creates a robust foundation that can be styled differently across the application while maintaining consistent behavior and accessibility.\n\n### Internationalization Strategy\n\nThe application uses next-intl for localization:\n\n```typescript\n// Locale detection and message loading\nexport default getRequestConfig(async ({ requestLocale }) =\u003e {\n  const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale;\n\n  return {\n    locale,\n    messages: (await import(`../../src/messages/${locale}.json`)).default,\n  };\n});\n```\n\nThe i18n implementation provides:\n\n1. **Server-side rendering** of translated content improves SEO\n2. **Locale-specific URLs** for better user experience\n3. **Message separation** for easier translation management\n4. **Dynamic loading** of translation files only when needed\n\n### Git Workflow Strategy\n\nThe project employs GitHub Flow, a simplified Git branching strategy that's ideal for smaller teams or solo developers:\n\n```\nmain         ○─────○─────○─────○─────○\n              \\     \\     \\     /     /\nfeature-1      ○─────○─────○───○\n                      \\         /\nfeature-2               ○───○──○\n                               \\\nhotfix-1                        ○───○\n```\n\nKey aspects of this approach:\n\n1. **Single production branch** (`main`) - Always deployable and protected\n2. **Feature branches** - Created for new features, merged via pull requests\n3. **Hotfix branches** - Created for urgent production fixes\n4. **No develop branch** - Simplifies the workflow for smaller teams\n5. **Pull request reviews** - Code quality gate before merging to main\n\nThis simplified workflow provides adequate structure while eliminating the overhead of more complex strategies like GitFlow, which is more suitable for larger teams with coordinated release cycles.\n\n## Areas for Improvement\n\n- **Advanced Search Options**: Implement additional filtering criteria for job searches\n- **Saved Searches**: Allow users to save and manage their frequent searches\n- **Automated Testing**: Implement E2E and component tests with Playwright and Vitest\n\n## Getting Started\n\nYou can access the live application [here](https://library.teeldinho.co.za/).\n\n## Screenshots\n\n![Job Search - Hero](public/desktop-hero-2.png)\n![Job Search - English](public/desktop-english.png)\n![Job Search - Spanish](public/desktop-spanish.png)\n![Job Search - French](public/desktop-french.png)\n![Job Search - Loading State](public/loading-state.png)\n![Job Search - Empty State](public/empty-state.png)\n![Job Search - English](public/mobile-english.png)\n![Job Search - French](public/mobile-french.png)\n![Job Search - Lighthouse Desktop](public/performance-lighthouse-desktop.png)\n![Job Search - Lighthouse Desktop - Metrics](public/performance-lighthouse-metrics-desktop.png)\n![Job Search - Lighthouse Mobile](public/performance-lighthouse-mobile.png)\n![Job Search - Lighthouse Mobile - Metrics](public/performance-lighthouse-metrics-mobile.png)\n\n## Conclusion\n\nThis project demonstrates a comprehensive approach to modern web application architecture by integrating several key patterns and technologies:\n\n1. **Decoupled UI architecture** - Headless components separate logic from presentation, improving flexibility and maintainability while enabling design system consistency\n\n2. **Resilient data handling** - A pipeline of schemas → DTOs → mappers → RTOs creates a robust barrier against API changes and ensures type safety\n\n3. **State synchronization** - URL-based state management with NUQS enables seamless server/client synchronization, shareable application states, and improved navigation\n\n4. **Progressive enhancement** - Server components fetch data that client components consume, striking an optimal balance between performance and interactivity\n\n5. **Developer experience** - GitHub Flow simplifies collaboration while maintaining code quality through pull requests\n\n6. **User experience** - Careful attention to loading states, error handling, and empty states ensures a polished interface regardless of network conditions\n\nThe combination of these patterns creates a foundation that balances technical excellence with practical considerations, resulting in an application that performs well for users while remaining maintainable for developers. The architecture scales naturally to accommodate more complex features while preserving the established patterns.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteeldinho%2Flibrary","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fteeldinho%2Flibrary","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteeldinho%2Flibrary/lists"}