Skip to main content

Overview

Portal Ciudadano Manta is built with modern web technologies using Vue 3, TypeScript, and Supabase as the backend. The application follows a modular architecture with clear separation of concerns.

Tech Stack

Frontend Framework

Vue 3.5+ with Composition API and TypeScript

Build Tool

Vite 7.1+ for fast development and optimized builds

State Management

Pinia 3.0+ for centralized state management

Backend

Supabase (PostgreSQL, Auth, Storage, Realtime)

Project Structure

src/
├── assets/              # Static assets (images, icons)
│   ├── footer/
│   ├── logo/
│   └── sobreNosotros/
├── components/          # Reusable Vue components
│   ├── accessibility/   # Accessibility features (TextToSpeech)
│   ├── cards/          # Card components (ServiceCard)
│   ├── footer/         # Footer component
│   ├── maps/           # Map components (Leaflet integration)
│   └── nav/            # Navigation components (Navbar)
├── composables/         # Vue composables for shared logic
│   ├── useAuth.ts
│   ├── useImageUpload.ts
│   ├── useKeyboardShortcuts.ts
│   ├── useLanguage.ts
│   └── useReportes.ts
├── i18n/               # Internationalization
│   ├── index.ts        # i18n configuration
│   └── locales/        # Translation files (es, en, qu)
├── lib/                # Library configurations
│   ├── supabase.ts     # Supabase client setup
│   └── storage.ts      # Storage utilities
├── router/             # Vue Router configuration
│   └── index.ts        # Route definitions and guards
├── stores/             # Pinia stores
│   ├── auth.store.ts
│   ├── encuestas.store.ts
│   ├── noticias.store.ts
│   └── reportes.store.ts
├── types/              # TypeScript type definitions
│   └── database.types.ts
├── views/              # Page components
│   ├── Home.vue
│   ├── Login.vue
│   ├── Dashboard.vue
│   ├── AdminPanel.vue
│   └── ...
├── App.vue             # Root component
└── main.ts             # Application entry point

Application Initialization

The application follows a specific initialization sequence to ensure proper setup:
src/main.ts
import { createPinia } from "pinia";
import { createApp } from "vue";
import App from "./App.vue";
import i18n from "./i18n";
import router from "./router";
import { useAuthStore } from "./stores/auth.store";

const app = createApp(App);
const pinia = createPinia();

// Configure plugins
app.use(pinia);
app.use(i18n);
app.use(router);

// Initialize authentication before mounting
const authStore = useAuthStore();
authStore.initAuth().then(() => {
  app.mount("#app");
});
The app waits for authentication initialization before mounting to prevent race conditions and ensure consistent user state.

Build Configuration

Vite is configured with optimized code splitting and bundle management:
vite.config.ts
import tailwindcss from "@tailwindcss/vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [vue(), tailwindcss()],
  resolve: {
    alias: {
      "@": resolve(__dirname, "src"),
    },
  },
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'vue-vendor': ['vue', 'vue-router', 'pinia'],
          'i18n': ['vue-i18n'],
          'supabase': ['@supabase/supabase-js'],
          'leaflet': ['leaflet', 'vue3-leaflet'],
        },
      },
    },
    chunkSizeWarningLimit: 1000,
    minify: 'esbuild',
    target: 'es2015',
  },
});

Manual Code Splitting

The build is optimized with manual chunk definitions:
  • vue-vendor: Core Vue libraries (Vue, Router, Pinia)
  • i18n: Internationalization library
  • supabase: Backend client
  • leaflet: Map components
This ensures optimal caching and faster load times.

Routing Architecture

The application uses Vue Router with authentication guards:
Routes accessible without authentication:
  • / - Home page
  • /login - Login page
  • /register - Registration page
  • /reset-password - Password reset
  • /sobre-nosotros - About us
src/router/index.ts
router.beforeEach(async (to, from, next) => {
  const authStore = useAuthStore();

  // Wait for auth initialization
  if (authStore.loading) {
    await new Promise<void>((resolve) => {
      const timeout = setTimeout(() => resolve(), 5000);
      const stop = watch(
        () => authStore.loading,
        (loading) => {
          if (!loading) {
            clearTimeout(timeout);
            stop();
            resolve();
          }
        }
      );
    });
  }

  const requiresAuth = to.meta.requiresAuth;
  const isAuthenticated = authStore.isAuthenticated();
  const isAdmin = authStore.isAdministrador();
  const isAdminRoute = to.path.startsWith("/admin");

  // Allow direct access to reset-password
  if (to.name === "ResetPassword") {
    return next();
  }

  // Require authentication
  if (requiresAuth && !isAuthenticated) {
    return next({
      name: "Login",
      query: { redirect: to.fullPath },
    });
  }

  // Require admin role
  if (isAdminRoute && isAuthenticated && !isAdmin) {
    return next({ name: "Dashboard" });
  }

  next();
});

Supabase Integration

The Supabase client is configured with type safety and optimal settings:
src/lib/supabase.ts
import { createClient } from "@supabase/supabase-js";
import type { Database } from "../types/database.types";

export const supabase = createClient<Database>(
  import.meta.env.VITE_SUPABASE_URL,
  import.meta.env.VITE_SUPABASE_ANON_KEY,
  {
    auth: {
      persistSession: true,
      autoRefreshToken: true,
      detectSessionInUrl: true,
      flowType: "pkce", // More secure for SPAs
    },
    db: {
      schema: "public",
    },
    realtime: {
      timeout: 10000,
    },
  }
);
Always use PKCE flow (flowType: "pkce") for single-page applications to ensure secure authentication.

Design Patterns

Composition API Pattern

All components use Vue 3’s Composition API for better code organization and reusability:
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { useAuthStore } from '@/stores/auth.store';

const authStore = useAuthStore();
const isLoggedIn = computed(() => authStore.isAuthenticated());

onMounted(() => {
  // Component initialization
});
</script>

Store Pattern

Pinia stores follow a consistent pattern:
  1. State: Reactive data
  2. Getters: Computed properties (functions returning computed values)
  3. Actions: Async operations and mutations

Error Handling Pattern

All async operations follow a consistent error handling pattern:
try {
  loading.value = true;
  const { data, error } = await supabase.from('table').select();
  
  if (error) throw error;
  
  return { success: true, data };
} catch (err: any) {
  error.value = err.message;
  console.error('❌ Error:', err);
  return { success: false, error: err.message };
} finally {
  loading.value = false;
}

Performance Optimizations

Routes are lazy-loaded using dynamic imports:
{
  path: '/admin',
  component: () => import('@/views/AdminPanel.vue')
}
Vite pre-bundles frequently used dependencies:
optimizeDeps: {
  include: ['vue', 'vue-router', 'pinia', 'vue-i18n', '@supabase/supabase-js'],
}
Static assets are processed through Vite’s asset pipeline with automatic optimization and fingerprinting.

Next Steps

Database Schema

Learn about the database structure and relationships

State Management

Explore Pinia stores and state patterns

Internationalization

Understand the i18n implementation

API Reference

View the complete API documentation

Build docs developers (and LLMs) love