dcf99f011a
Nouvelle page /admin/categories/new, jumeau de l'écran d'édition :
- Form complet : name, slug (auto), description, color_hex, icon,
dimension, sort_order
- Preview live identique à l'éditeur (top border + folder tinté + badge)
- Slug auto-généré depuis le nom (normalisation NFD + lowercase + dashes)
tant que l'utilisateur n'a pas touché manuellement au champ. Flag
slugDirty mis à true sur premier @input du slug -> on arrête l'auto.
- Validation color_hex /^#RRGGBB$/ côté client + normalisation upper-case
- Body construit en omettant les champs vides -> l'API applique ses
défauts (color_hex='#888888', dimension='transversal', sort_order=0)
- Sur 201 : redirige vers /admin/categories/:slug/edit pour que l'admin
voie tout de suite sa nouvelle catégorie et puisse ajuster
- 422/409/401 gérés comme sur les autres écrans admin
UI
- AdminCategoriesPage : CTA "Nouvelle catégorie" en haut à droite
- Router : route nommée admin-category-new avec guard admin
Backend
- Aucune nouvelle route : POST /api/categories existait déjà
- Le contrôleur valide name (required, max 128), dimension enum, color
hex format, slug auto-unique
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
60 lines
3.4 KiB
TypeScript
60 lines
3.4 KiB
TypeScript
/**
|
|
* Routes de l'app. AppLayout englobe toutes les pages "public" (sidebar visible).
|
|
* /admin/login a son propre layout vide (pas de sidebar tant qu'on n'est pas loggé).
|
|
*/
|
|
|
|
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
|
|
import AppLayout from '@/layouts/AppLayout.vue'
|
|
import { getAdminToken } from '@/api/client'
|
|
|
|
/**
|
|
* Guard pour les routes /admin/* (hors /admin/login). Si pas de token Bearer
|
|
* en localStorage, redirige vers /admin/login (en mémorisant la destination
|
|
* via query.next pour qu'on y retourne après auth).
|
|
*/
|
|
function requireAdmin(to: { fullPath: string }) {
|
|
if (!getAdminToken()) {
|
|
return { name: 'admin-login', query: { next: to.fullPath } }
|
|
}
|
|
return true
|
|
}
|
|
|
|
const routes: RouteRecordRaw[] = [
|
|
{
|
|
path: '/',
|
|
component: AppLayout,
|
|
children: [
|
|
{ path: '', name: 'home', component: () => import('@/pages/HomePage.vue') },
|
|
{ path: 'dashboard', name: 'dashboard', component: () => import('@/pages/DashboardPage.vue') },
|
|
{ path: 'donut', name: 'donut', component: () => import('@/pages/DonutPage.vue') },
|
|
{ path: 'projects', name: 'projects', component: () => import('@/pages/ProjectsPage.vue') },
|
|
{ path: 'projects/new', name: 'project-new', component: () => import('@/pages/ProjectNewPage.vue') },
|
|
{ path: 'projects/:slug', name: 'project-detail', component: () => import('@/pages/ProjectDetailPage.vue'), props: true },
|
|
{ path: 'archives', name: 'archives', component: () => import('@/pages/ArchivesPage.vue') },
|
|
{ path: 'categories', name: 'categories', component: () => import('@/pages/CategoriesPage.vue') },
|
|
|
|
// --- Admin dashboard (token Bearer requis pour toutes les sous-pages sauf /admin/login) ---
|
|
{ path: 'admin', name: 'admin-overview', component: () => import('@/pages/admin/AdminOverviewPage.vue'), beforeEnter: requireAdmin },
|
|
{ path: 'admin/projects', name: 'admin-projects', component: () => import('@/pages/admin/AdminProjectsPage.vue'), beforeEnter: requireAdmin },
|
|
{ path: 'admin/projects/:slug/edit', name: 'admin-project-edit', component: () => import('@/pages/admin/AdminProjectEditPage.vue'), props: true, beforeEnter: requireAdmin },
|
|
{ path: 'admin/members', name: 'admin-members', component: () => import('@/pages/admin/AdminMembersPage.vue'), beforeEnter: requireAdmin },
|
|
{ path: 'admin/members/:slug/edit', name: 'admin-member-edit', component: () => import('@/pages/admin/AdminMemberEditPage.vue'), props: true, beforeEnter: requireAdmin },
|
|
{ path: 'admin/categories', name: 'admin-categories', component: () => import('@/pages/admin/AdminCategoriesPage.vue'), beforeEnter: requireAdmin },
|
|
{ path: 'admin/categories/new', name: 'admin-category-new', component: () => import('@/pages/admin/AdminCategoryNewPage.vue'), beforeEnter: requireAdmin },
|
|
{ path: 'admin/categories/:slug/edit', name: 'admin-category-edit', component: () => import('@/pages/admin/AdminCategoryEditPage.vue'), props: true, beforeEnter: requireAdmin },
|
|
|
|
{ path: 'admin/login', name: 'admin-login', component: () => import('@/pages/AdminLoginPage.vue'), meta: { hideForAuthed: true } },
|
|
{ path: ':pathMatch(.*)*', name: 'not-found', component: () => import('@/pages/NotFoundPage.vue') },
|
|
],
|
|
},
|
|
]
|
|
|
|
export const router = createRouter({
|
|
history: createWebHistory(),
|
|
routes,
|
|
scrollBehavior() {
|
|
// Scroll en haut à chaque navigation (UX par défaut pour une SPA de contenu).
|
|
return { top: 0 }
|
|
},
|
|
})
|