Files
classroomio/apps/dashboard/ROUTE_PAGE_SETUP.md
T
Rotimi Best c7854efae3 feat: redo settings and all org pages. (#601)
* feat: new components, deploy storybook

* refactor: simplify component field definitions and enhance button loading state

- Removed unnecessary type imports and simplified the FIELDS array definitions across multiple components in Storybook.
- Updated button component to use a loading state instead of isLoading, improving clarity and consistency in props.
- Adjusted button stories to reflect the new loading prop and enhanced the button's loading visual feedback.
- Minor CSS adjustments for better layout and styling consistency.

* chore: add field, card, password components

* feat: improve page component & update AuthUI path

* chore: update bits-ui to version 2.14.4 and all org related UI's

- Updated bits-ui dependency in pnpm-lock.yaml and package.json to version 2.14.4.
- Refactored imports for RoleBasedSecurity and UpgradeBanner components to use the new ui feature directory.
- Removed unused Course components and related files to clean up the codebase.
- Enhanced various components to improve consistency and maintainability.

* chore: move all setting buttons to the top and update auth ui styles

* chore: remove svelte-loading-spinners and replace with @cio/ui/base/spinner

- Removed all instances of svelte-loading-spinners from the project.
- Updated components to use the new Spinner from @cio/ui/base/spinner.
- Cleaned up pnpm-lock.yaml and package.json to reflect the removal of svelte-loading-spinners.
- Added new CSS animations for rocket launch effects in app.css.
- Enhanced email change flow with updated verification messages in translations.

* chore: remove supabase file upload from dashboard and move to r2

* chore: update organization API and UI components

- Added new API methods for organization plan operations and team management.
- Introduced middleware for API key authentication and organization admin checks.
- Refactored organization-related components to utilize the new API structure.
- Updated various UI components to improve consistency and maintainability.
- Enhanced error handling and validation for organization-related actions.
- Removed deprecated services and streamlined the codebase.
2025-12-02 09:54:38 +01:00

8.7 KiB

Route Page Setup Documentation

This document outlines the pattern for setting up page routes in the dashboard application. Follow this structure to maintain consistency across the codebase.

Overview

The application follows a feature-based architecture where:

  • Route files (src/routes/org/[slug]/[feature]/+page.svelte) are minimal wrappers that set up the Page structure
  • Feature pages (src/lib/features/[feature]/pages/[feature].svelte) contain all business logic, state management, and UI components
  • Components are organized in src/lib/features/[feature]/components/ and exported via index.ts
  • Pages are exported from src/lib/features/[feature]/pages/index.ts

Directory Structure

apps/dashboard/src/
├── routes/
│   └── org/
│       └── [slug]/
│           └── [feature]/
│               └── +page.svelte          # Minimal route wrapper
└── lib/
    └── features/
        └── [feature]/
            ├── components/
            │   ├── [component].svelte
            │   └── index.ts               # Export all components
            ├── pages/
            │   ├── [feature].svelte      # Main feature page with all logic
            │   └── index.ts               # Export page component
            ├── utils/                     # Feature-specific utilities
            └── api/                       # Feature-specific API calls

Route File Pattern (+page.svelte)

The route file should be minimal and only contain:

  1. Page structure using @cio/ui/base/page components
  2. Import and render the feature page component
  3. Page metadata (title, etc.)

Example Route File

<script>
  import { FeaturePage } from '$lib/features/[feature]/pages';
  import { FeatureActionButton } from '$lib/features/[feature]/components';
  import { t } from '$lib/utils/functions/translations';
  import * as Page from '@cio/ui/base/page';
</script>

<svelte:head>
  <title>Feature Name - ClassroomIO</title>
</svelte:head>

<Page.Root class="w-full">
  <Page.Header>
    <Page.HeaderContent>
      <Page.Title>{$t('feature.title')}</Page.Title>
    </Page.HeaderContent>
    <Page.Action>
      <FeatureActionButton />
    </Page.Action>
  </Page.Header>
  <Page.Body>
    {#snippet child()}
      <FeaturePage />
    {/snippet}
  </Page.Body>
</Page.Root>

Route File Rules

  • ✅ DO: Keep it minimal - only Page structure
  • ✅ DO: Import feature page from $lib/features/[feature]/pages
  • ✅ DO: Import action buttons/components from $lib/features/[feature]/components
  • ✅ DO: Use Page.Root, Page.Header, Page.HeaderContent, Page.Title, Page.Action, Page.Body
  • ❌ DON'T: Put business logic in route files
  • ❌ DON'T: Put state management in route files
  • ❌ DON'T: Put data fetching in route files
  • ❌ DON'T: Put filters/search in route files (use Page.BodyHeader in feature page)

Feature Page Pattern (pages/[feature].svelte)

The feature page contains all the business logic, state management, and UI components.

Feature Page Structure

<script lang="ts">
  // 1. UI Component Imports
  import { Empty } from '@cio/ui/custom/empty';
  import { Search } from '@cio/ui/custom/search';
  import * as Page from '@cio/ui/base/page';
  import * as Select from '@cio/ui/base/select';
  import { SomeIcon } from '@lucide/svelte/icons/...';

  // 2. Feature Component Imports
  import { FeatureCard, FeatureLoader, FeatureModal } from '../components';

  // 3. Utility Imports
  import { t } from '$lib/utils/functions/translations';
  import { someStore } from '../utils/store';
  import { fetchFeatureData } from '../api';

  // 4. State Management
  let isLoading = $state(false);
  let searchValue = $state('');
  let selectedId = $state('');

</script>

<FeatureModal />

<Page.BodyHeader align="right">
  <Search placeholder="Search..." bind:value={searchValue} />
  <Select.Root bind:value={selectedId}>
    <!-- Select options -->
  </Select.Root>
</Page.BodyHeader>

<!-- Main Content with States -->
<div class="w-full">
  {#if isLoading}
    <!-- Loading State -->
    <FeatureLoader />
    <FeatureLoader />
    <FeatureLoader />
  {:else if !data.length}
    <!-- Empty State -->
    <Empty
      title={$t('feature.empty_title')}
      description={$t('feature.empty_description')}
      icon={SomeIcon}
      variant="page"
    >
      <FeatureActionButton />
    </Empty>
  {:else}
    <!-- Content -->
    {#each data as item}
      <FeatureCard {item} />
    {/each}
  {/if}
</div>

Feature Page Rules

  • ✅ DO: Use Page.BodyHeader for filters, search, and view toggles
  • ✅ DO: Use Empty component from @cio/ui/custom/empty for empty states
  • ✅ DO: Use Search component from @cio/ui/custom/search for search inputs
  • ✅ DO: Handle loading states with skeleton/loader components
  • ✅ DO: Export the page component from pages/index.ts as [Feature]Page
  • ✅ DO: Keep all business logic in the feature page
  • ❌ DON'T: Use Page.Header or Page.Action in feature pages (these are in route files)
  • ❌ DON'T: Put route-specific logic in feature pages

Component Organization

Components Directory

Create reusable components specific to the feature:

components/
├── feature-card.svelte
├── feature-loader.svelte
├── feature-modal.svelte
└── index.ts

Components Index (components/index.ts)

export { default as FeatureCard } from './feature-card.svelte';
export { default as FeatureLoader } from './feature-loader.svelte';
export { default as FeatureModal } from './feature-modal.svelte';
export { default as FeatureActionButton } from './feature-action-button.svelte';

Pages Index (pages/index.ts)

export { default as FeaturePage } from './feature.svelte';

Complete Example

Route File: routes/org/[slug]/community/+page.svelte

<script>
  import { CommunityPage } from '$lib/features/community/pages';
  import { AskCommunityButton } from '$lib/features/community/components';
  import { t } from '$lib/utils/functions/translations';
  import * as Page from '@cio/ui/base/page';
</script>

<svelte:head>
  <title>Community - ClassroomIO</title>
</svelte:head>

<Page.Root class="w-full">
  <Page.Header>
    <Page.HeaderContent>
      <Page.Title>{$t('community.title')}</Page.Title>
    </Page.HeaderContent>
    <Page.Action>
      <AskCommunityButton />
    </Page.Action>
  </Page.Header>
  <Page.Body>
    {#snippet child()}
      <CommunityPage />
    {/snippet}
  </Page.Body>
</Page.Root>

Feature Page: lib/features/community/pages/community.svelte

<script lang="ts">
  import { Search } from '@cio/ui/custom/search';
  import { Empty } from '@cio/ui/custom/empty';
  import * as Page from '@cio/ui/base/page';
  import * as Select from '@cio/ui/base/select';
  import MessageSquareMoreIcon from '@lucide/svelte/icons/message-square-more';
  import { CommunityListLoader, AskCommunityButton } from '../components';
  // ... other imports

  let isLoading = $state(false);
  let searchValue = $state('');
  let discussions = $state([]);

  // ... logic
</script>

<Page.BodyHeader>
  <Search placeholder="Search..." bind:value={searchValue} />
  <Select.Root>
    <!-- Select options -->
  </Select.Root>
</Page.BodyHeader>

{#if isLoading}
  <CommunityListLoader />
{:else}
  {#each discussions as discussion}
    <!-- Discussion content -->
  {:else}
    <Empty
      title={$t('community.no_question')}
      description={$t('community.ask_a_question')}
      icon={MessageSquareMoreIcon}
    >
      <AskCommunityButton />
    </Empty>
  {/each}
{/if}

Key Patterns

  1. Separation of Concerns: Route files handle routing structure, feature pages handle business logic
  2. Reusability: Feature pages can be imported and used in different contexts
  3. Consistency: All pages follow the same structure for maintainability
  4. Design System: Use Page.BodyHeader for filters, Empty for empty states, Search for search inputs
  5. State Management: Keep state local to feature pages or use feature-specific stores

Migration Checklist

When refactoring an existing route:

  • Create feature directory structure (lib/features/[feature]/)
  • Move business logic from route file to feature page
  • Extract components to components/ directory
  • Create components/index.ts with exports
  • Create pages/index.ts with page export
  • Update route file to be minimal wrapper
  • Add Page.BodyHeader for filters/search if needed
  • Replace custom empty states with Empty component
  • Replace custom search inputs with Search component
  • Test all functionality works correctly