Skip to content

Adding a Page

This guide walks through adding a new page to the frontend application.

1. Create the Module

Create the module directory structure:

frontend/src/modules/<name>/
├── pages/
│   └── <name>.vue
├── components/         # (if needed)
├── composables/        # (if needed)
├── api/                # (if needed)
├── types/              # (if needed)
└── utils/              # (if needed)

2. Create the Page Component

Create frontend/src/modules/<name>/pages/<name>.vue:

vue
<template>
  <div>
    <h1 class="text-xl font-semibold text-neutral-900 mb-6">
      Naslov strani
    </h1>

    <!-- Page content -->
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
</script>

<script lang="ts">
export default {
  name: '<name>-page',
};
</script>

Remember:

  • Two <script> blocks — <script setup> for logic, named export for DevTools
  • Page name pattern: <name>-page
  • UI text in Slovenian

3. Add the Route

Edit frontend/src/router/index.ts to add the route as a child of the appropriate layout:

typescript
{
  path: '/',
  component: () => import('@/shared/layouts/default.vue'),
  children: [
    // Existing routes...
    {
      path: '<name>',
      name: '<name>',
      component: () => import('@/modules/<name>/pages/<name>.vue'),
    },
  ],
},

Rules:

  • Always use lazy-loaded imports: () => import(...)
  • Use the default layout for main app pages
  • Use the auth layout for authentication pages
  • Use the legal layout for legal/static pages

4. Add Sidebar Navigation

Edit frontend/src/shared/layouts/components/sidebar-menu.vue to add a menu item:

vue
<SidebarMenuItem
  to="/<name>"
  icon="ri-icon-name"
  label="Ime strani"
/>

Find the appropriate icon from Remix Icons.

5. Add Data Fetching (if needed)

If the page needs to fetch data from the API:

Create API functions

Create frontend/src/modules/<name>/api/<name>.api.ts:

typescript
import type { SomeType } from '../types/<name>.types';

export async function fetchItems(filters?: Record<string, string>): Promise<SomeType[]> {
  const params = new URLSearchParams();
  if (filters?.search) params.set('search', filters.search);
  const qs = params.toString();
  const url = qs ? `/api/<name>?${qs}` : '/api/<name>';

  const response = await fetch(url);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

Create a composable

Create frontend/src/modules/<name>/composables/use<Name>Query.ts:

typescript
import { computed } from 'vue';
import { useQuery } from '@tanstack/vue-query';
import { fetchItems } from '../api/<name>.api';

export function use<Name>Query(filters?: Record<string, string>) {
  const { data, isLoading, error } = useQuery({
    queryKey: ['<name>', filters],
    queryFn: () => fetchItems(filters),
  });

  return { data, isLoading, error };
}

Wire it up in the page

vue
<script setup lang="ts">
import { use<Name>Query } from '../composables/use<Name>Query';
import GisSpinner from '@uikit/spinner/spinner.vue';

const { data, isLoading, error } = use<Name>Query();
</script>

<template>
  <GisSpinner v-if="isLoading" />
  <div v-else-if="error">Napaka pri nalaganju podatkov</div>
  <div v-else>
    <!-- Render data -->
  </div>
</template>

6. Verify

  1. Run pnpm dev:frontend and navigate to your new page
  2. Check that the sidebar link works and highlights correctly
  3. Verify data loading (if applicable)
  4. Test responsive behavior on mobile