Skip to content

Aggiungere una Nuova Route

Guida step-by-step per creare nuove route in elerama-frontend con React Router 7.

Panoramica

Una route completa richiede:

  1. Route Component - Componente React per la pagina
  2. Registrazione Route - Entry in routes.ts
  3. Protezione - clientLoader per autenticazione
  4. Meta Tags - Titolo e descrizione pagina

Struttura File

app/
├── routes/
│   ├── auth/              # Route autenticazione (login, companies)
│   ├── admin/             # Route amministrative
│   │   └── {modulo}/      # Pagine modulo (es: brands/)
│   │       └── index.tsx  # Pagina principale
│   └── r.$path.tsx        # Route dinamica per moduli ERP iframe
└── routes.ts              # Registrazione route

Tipi di Route

1. Route Protetta (Autenticata)

La maggior parte delle route dell'applicazione richiede autenticazione.

typescript
// app/routes/admin/example/index.tsx
import { AuthenticatedLayout } from "@/components/layout/authenticated";
import { createProtectedLoader } from "@/lib/auth/loaders";

// Meta tags per la pagina
export function meta() {
    return [
        { title: "Esempio - Elerama" },
        { name: "description", content: "Descrizione pagina" },
    ];
}

// Protezione route: richiede autenticazione
export const clientLoader = createProtectedLoader();

export default function ExamplePage() {
    return (
        <AuthenticatedLayout>
            <div className="p-6">
                <h1>Contenuto pagina</h1>
            </div>
        </AuthenticatedLayout>
    );
}

2. Route Protetta con Settings

Per route che necessitano dei settings utente (menu, permessi, ecc.).

typescript
// app/routes/admin/brands/index.tsx
import { AuthenticatedLayout } from "@/components/layout/authenticated";
import { createProtectedLoader } from "@/lib/auth/loaders";

export function meta() {
    return [
        { title: "Gestione Brand - Elerama" },
        { name: "description", content: "Gestione anagrafica brand" },
    ];
}

// Protezione route: richiede autenticazione + settings caricati
export const clientLoader = createProtectedLoader({ requireSettings: true });

export default function BrandsPage() {
    // Settings garantiti dal loader
    return (
        <AuthenticatedLayout>
            {/* Contenuto */}
        </AuthenticatedLayout>
    );
}

3. Route Pubblica (Guest Only)

Per route accessibili solo a utenti non autenticati (login, registrazione).

typescript
// app/routes/auth/login.tsx
import { createGuestLoader } from "@/lib/auth/loaders";

export function meta() {
    return [{ title: "Login - Elerama" }];
}

// Solo utenti non autenticati (redirect se già loggati)
export const clientLoader = createGuestLoader();

export default function LoginPage() {
    return (
        <div>
            {/* Form login */}
        </div>
    );
}

Registrazione Route

File: app/routes.ts

typescript
import { type RouteConfig, index, route } from "@react-router/dev/routes";

export default [
    // Route index (login)
    index("routes/auth/login.tsx"),

    // Route post-login
    route("companies", "routes/auth/companies.tsx"),
    route("confirm-login", "routes/auth/confirm-login.tsx"),
    route("welcome", "routes/welcome.tsx"),

    // Route admin
    route("admin/brands", "routes/admin/brands/index.tsx"),
    route("admin/example", "routes/admin/example/index.tsx"),

    // Route dinamica per moduli ERP (iframe)
    route("r/*", "routes/r.$path.tsx"),

    // Catch-all 404 (deve essere l'ultima)
    route("*", "routes/$.tsx"),
] satisfies RouteConfig;

Convenzioni Path

TipoPathEsempio
Adminadmin/{modulo}admin/brands
Auth{action}login, companies
ERP Iframer/{path}r/navigator, r/products/list

Opzioni createProtectedLoader

typescript
interface ProtectedLoaderOptions {
    /**
     * Verifica che i settings siano caricati.
     * Se non presenti, li carica automaticamente.
     * Usa per route che necessitano di menu/permessi.
     */
    requireSettings?: boolean;

    /**
     * Carica i settings prima del render.
     * Blocca finché non sono disponibili.
     * Usa per pagine iniziali (welcome).
     */
    loadSettings?: boolean;

    /**
     * Se false, non redirige automaticamente.
     * @default true
     */
    redirect?: boolean;
}

Esempi d'Uso

typescript
// Solo autenticazione
export const clientLoader = createProtectedLoader();

// Autenticazione + settings (route admin)
export const clientLoader = createProtectedLoader({ requireSettings: true });

// Carica settings (welcome page)
export const clientLoader = createProtectedLoader({ loadSettings: true });

Layout Comuni

AuthenticatedLayout

Layout standard per pagine autenticate con sidebar e topbar.

typescript
import { AuthenticatedLayout } from "@/components/layout/authenticated";

export default function MyPage() {
    return (
        <AuthenticatedLayout>
            <div className="p-6">
                {/* Contenuto con padding standard */}
            </div>
        </AuthenticatedLayout>
    );
}

Card Layout Pattern

Pattern comune per pagine con contenuto centrato in card.

typescript
import { AuthenticatedLayout } from "@/components/layout/authenticated";
import { Card, CardContent, CardHeader, CardTitle } from "@elerama/ui";

export default function MyPage() {
    return (
        <AuthenticatedLayout>
            <div className="p-6">
                <Card>
                    <CardHeader>
                        <CardTitle>Titolo</CardTitle>
                    </CardHeader>
                    <CardContent>
                        {/* Contenuto */}
                    </CardContent>
                </Card>
            </div>
        </AuthenticatedLayout>
    );
}

Pattern Data Fetching

Con TanStack Query

Pattern raccomandato per tutte le route con dati server.

typescript
import { useMyDataList } from "@/hooks/api/mymodule/useMyModuleApi";

export default function MyPage() {
    const { data, isLoading, error, refetch } = useMyDataList();

    // Gestione errore
    if (error) {
        return (
            <AuthenticatedLayout>
                <Alert variant="destructive">
                    <AlertTitle>Errore</AlertTitle>
                    <AlertDescription>{error.message}</AlertDescription>
                    <Button onClick={() => refetch()}>Riprova</Button>
                </Alert>
            </AuthenticatedLayout>
        );
    }

    return (
        <AuthenticatedLayout>
            {isLoading ? (
                <Spinner />
            ) : (
                <div>{/* Render dati */}</div>
            )}
        </AuthenticatedLayout>
    );
}

Esempio Completo: Pagina Admin CRUD

typescript
// app/routes/admin/products/index.tsx
import { AuthenticatedLayout } from "@/components/layout/authenticated";
import { useProductsList, useDeleteProduct } from "@/hooks/api/products/useProductsApi";
import { createProtectedLoader } from "@/lib/auth/loaders";
import {
    Alert,
    AlertDescription,
    AlertTitle,
    Button,
    Card,
    CardContent,
    CardHeader,
    CardTitle,
    useToast,
} from "@elerama/ui";

export function meta() {
    return [
        { title: "Gestione Prodotti - Elerama" },
        { name: "description", content: "Gestione anagrafica prodotti" },
    ];
}

export const clientLoader = createProtectedLoader({ requireSettings: true });

export default function ProductsPage() {
    const { success: showSuccess, error: showError } = useToast();

    // Query lista
    const { data: products, isLoading, error, refetch } = useProductsList();

    // Mutation eliminazione
    const { mutate: deleteProduct, isPending } = useDeleteProduct({
        onSuccess: () => showSuccess("Prodotto eliminato"),
        onError: (err) => showError(err.message),
    });

    if (error) {
        return (
            <AuthenticatedLayout>
                <div className="flex flex-1 items-center justify-center p-6">
                    <Alert variant="destructive" className="max-w-md">
                        <AlertTitle>Errore nel caricamento</AlertTitle>
                        <AlertDescription>{error.message}</AlertDescription>
                        <Button onClick={() => refetch()} className="mt-4">
                            Riprova
                        </Button>
                    </Alert>
                </div>
            </AuthenticatedLayout>
        );
    }

    return (
        <AuthenticatedLayout>
            <div className="p-6">
                <Card>
                    <CardHeader>
                        <CardTitle>Gestione Prodotti</CardTitle>
                    </CardHeader>
                    <CardContent>
                        {isLoading ? (
                            <div>Caricamento...</div>
                        ) : (
                            <div>
                                {/* Lista prodotti */}
                            </div>
                        )}
                    </CardContent>
                </Card>
            </div>
        </AuthenticatedLayout>
    );
}

Checklist

  • [ ] Componente route in app/routes/
  • [ ] Funzione meta() con title e description
  • [ ] clientLoader con protezione appropriata
  • [ ] Layout corretto (AuthenticatedLayout o altro)
  • [ ] Route registrata in routes.ts
  • [ ] Gestione stati: loading, error, empty
  • [ ] Data fetching con TanStack Query hooks

Riferimenti

Documentazione Elerama Frontend