From baf5981847a1ac8d3aa6d9e30a418b2a67c8d662 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 10 Feb 2026 17:16:35 +0100 Subject: [PATCH] fix improve --- CLAUDE.md | 182 ++++++++++++------ .../src/application/dto/csv-booking.dto.ts | 6 + .../services/csv-booking.service.ts | 2 + .../src/domain/entities/csv-booking.entity.ts | 7 +- .../src/domain/ports/out/email.port.ts | 1 + .../src/infrastructure/email/email.adapter.ts | 1 + .../email/templates/email-templates.ts | 9 + .../typeorm/mappers/csv-booking.mapper.ts | 3 +- .../app/carrier/documents/[token]/page.tsx | 44 +++-- .../app/dashboard/booking/new/page.tsx | 10 +- apps/frontend/app/dashboard/bookings/page.tsx | 10 +- apps/frontend/app/dashboard/layout.tsx | 19 +- .../components/organization/LicensesTab.tsx | 14 +- 13 files changed, 201 insertions(+), 107 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7444d4d..d210ccc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,21 +4,27 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -**Xpeditis** is a B2B SaaS maritime freight booking platform. Freight forwarders search and compare real-time shipping rates, book containers, and manage shipments. Built as a monorepo with NestJS backend (Hexagonal Architecture) and Next.js 14 frontend. +**Xpeditis** is a B2B SaaS maritime freight booking platform. Freight forwarders search and compare real-time shipping rates, book containers, and manage shipments. Monorepo with NestJS 10 backend (Hexagonal Architecture) and Next.js 14 frontend. ## Development Commands +All commands run from repo root unless noted otherwise. + ```bash -# Start infrastructure (PostgreSQL + Redis + MinIO) +# Infrastructure (PostgreSQL 15 + Redis 7 + MinIO) docker-compose up -d -# Install dependencies -npm install && cd apps/backend && npm install && cd ../frontend && npm install +# Install all dependencies +npm run install:all -# Run database migrations +# Environment setup (required on first run) +cp apps/backend/.env.example apps/backend/.env +cp apps/frontend/.env.example apps/frontend/.env + +# Database migrations (from apps/backend/) cd apps/backend && npm run migration:run -# Start development servers +# Development servers npm run backend:dev # http://localhost:4000, Swagger: /api/docs npm run frontend:dev # http://localhost:3000 ``` @@ -27,15 +33,32 @@ npm run frontend:dev # http://localhost:3000 ```bash # Backend (from apps/backend/) -npm test # Unit tests +npm test # Unit tests (Jest) npm test -- booking.entity.spec.ts # Single file npm run test:cov # With coverage -npm run test:integration # Integration tests (needs DB/Redis) +npm run test:integration # Integration tests (needs DB/Redis, 30s timeout) npm run test:e2e # E2E tests # Frontend (from apps/frontend/) npm test -npx playwright test # E2E tests +npm run test:e2e # Playwright (chromium, firefox, webkit + mobile) + +# From root +npm run backend:test +npm run frontend:test +``` + +Backend test config is in `apps/backend/package.json` (Jest). Integration test config: `apps/backend/jest-integration.json` (covers infrastructure layer, setup in `test/setup-integration.ts`). Frontend E2E config: `apps/frontend/playwright.config.ts`. + +### Linting, Formatting & Type Checking + +```bash +npm run backend:lint # ESLint backend +npm run frontend:lint # ESLint frontend +npm run format # Prettier (all files) +npm run format:check # Check formatting +# From apps/frontend/ +npm run type-check # TypeScript checking (frontend only) ``` ### Database Migrations @@ -50,10 +73,17 @@ npm run migration:revert ### Build ```bash -npm run backend:build # Compiles TS with path alias resolution (tsc-alias) -npm run frontend:build # Next.js production build +npm run backend:build # NestJS build with tsc-alias for path resolution +npm run frontend:build # Next.js production build (standalone output) ``` +## Local Infrastructure + +Docker-compose defaults (no `.env` changes needed for local dev): +- **PostgreSQL**: `xpeditis:xpeditis_dev_password@localhost:5432/xpeditis_dev` +- **Redis**: password `xpeditis_redis_password`, port 6379 +- **MinIO** (S3-compatible storage): `minioadmin:minioadmin`, API port 9000, console port 9001 + ## Architecture ### Hexagonal Architecture (Backend) @@ -61,74 +91,108 @@ npm run frontend:build # Next.js production build ``` apps/backend/src/ ├── domain/ # CORE - Pure TypeScript, NO framework imports -│ ├── entities/ # Booking, RateQuote, User (with private props, static create()) -│ ├── value-objects/# Money, Email, BookingNumber (immutable, validated) -│ ├── services/ # Domain services +│ ├── entities/ # Booking, RateQuote, User, Carrier, Port, Container, CsvBooking, etc. +│ ├── value-objects/ # Money, Email, BookingNumber, BookingStatus, PortCode, ContainerType, Volume, LicenseStatus, SubscriptionPlan, etc. +│ ├── services/ # Pure domain services (rate-search, csv-rate-price-calculator, booking, port-search, etc.) │ ├── ports/ -│ │ ├── in/ # Use case interfaces -│ │ └── out/ # Repository interfaces (SPIs) -│ └── exceptions/ # Domain exceptions -├── application/ # Controllers, DTOs, Guards (depends ONLY on domain) -└── infrastructure/ # TypeORM, Redis, Carrier APIs (depends ONLY on domain) +│ │ ├── in/ # Use case interfaces with execute() method +│ │ └── out/ # Repository/SPI interfaces (token constants like BOOKING_REPOSITORY = 'BookingRepository') +│ └── exceptions/ # Domain-specific exceptions +├── application/ # Controllers, DTOs (class-validator), Guards, Decorators, Mappers +└── infrastructure/ # TypeORM entities/repos/mappers, Redis cache, carrier APIs, MinIO/S3, email (MJML+Nodemailer), Sentry ``` -**Critical Rules**: -- Domain layer: Zero imports from NestJS, TypeORM, Redis -- Dependencies flow inward: Infrastructure → Application → Domain -- Use path aliases: `@domain/*`, `@application/*`, `@infrastructure/*` +**Critical dependency rules**: +- Domain layer: zero imports from NestJS, TypeORM, Redis, or any framework +- Dependencies flow inward only: Infrastructure → Application → Domain +- Path aliases: `@domain/*`, `@application/*`, `@infrastructure/*` (defined in `apps/backend/tsconfig.json`) - Domain tests run without NestJS TestingModule +- Backend has strict TypeScript: `strict: true`, `strictNullChecks: true` (but `strictPropertyInitialization: false`) + +### NestJS Modules (app.module.ts) + +Global guards: JwtAuthGuard (all routes protected by default), CustomThrottlerGuard. + +Feature modules: Auth, Rates, Ports, Bookings, CsvBookings, Organizations, Users, Dashboard, Audit, Notifications, Webhooks, GDPR, Admin, Subscriptions. + +Infrastructure modules: CacheModule, CarrierModule, SecurityModule, CsvRateModule. + +Swagger plugin enabled in `nest-cli.json` — DTOs auto-documented. ### Frontend (Next.js 14 App Router) ``` apps/frontend/ ├── app/ # App Router pages -│ ├── dashboard/ # Protected routes -│ │ ├── bookings/ # Booking management -│ │ ├── admin/ # Admin features (ADMIN role) -│ │ └── settings/ # User/org settings +│ ├── dashboard/ # Protected routes (bookings, admin, settings) │ └── carrier/ # Carrier portal (magic link auth) └── src/ - ├── components/ # React components (shadcn/ui in ui/) - ├── hooks/ # useBookings, useNotifications - ├── lib/api/ # API client modules - └── types/ # TypeScript definitions + ├── components/ # React components (shadcn/ui in ui/, layout/, bookings/, admin/) + ├── hooks/ # useBookings, useNotifications, useCsvRateSearch, useCompanies, useFilterOptions + ├── lib/ + │ ├── api/ # Fetch-based API client with auto token refresh (client.ts + per-module files) + │ ├── context/ # Auth context + │ └── fonts.ts # Manrope (headings) + Montserrat (body) + ├── types/ # TypeScript type definitions + └── utils/ # Export utilities (Excel, PDF) ``` -Path aliases: `@/*` → `./src/*`, `@/components/*`, `@/lib/*`, `@/hooks/*` +Path aliases: `@/*` → `./src/*`, `@/components/*`, `@/lib/*`, `@/app/*` → `./app/*`, `@/types/*`, `@/hooks/*`, `@/utils/*` + +**Note**: Frontend tsconfig has `strict: false`, `noImplicitAny: false`, `strictNullChecks: false` (unlike backend which is strict). + +### Brand Design + +Colors: Navy `#10183A` (primary), Turquoise `#34CCCD` (accent), Green `#067224` (success), Gray `#F2F2F2`. +Fonts: Manrope (headings), Montserrat (body). +Landing page is in French. ## Key Patterns ### Entity Pattern (Domain) +Private constructor + static `create()` factory. Immutable — mutation methods return new instances. Some entities also have `fromPersistence()` for reconstitution and `toObject()` for serialization. ```typescript export class Booking { private readonly props: BookingProps; - static create(props: Omit): Booking { ... } + static create(props: Omit): Booking { ... } updateStatus(newStatus: BookingStatus): Booking { // Returns new instance return new Booking({ ...this.props, status: newStatus }); } } ``` -### Repository Pattern -- Interface in `domain/ports/out/booking.repository.ts` -- Implementation in `infrastructure/persistence/typeorm/repositories/typeorm-booking.repository.ts` -- Separate mappers for Domain ↔ ORM entity conversions +### Value Object Pattern +Immutable, self-validating via static `create()`. E.g. `Money` supports USD, EUR, GBP, CNY, JPY with arithmetic and formatting methods. -### Circuit Breaker (External APIs) -- Library: `opossum`, Timeout: 5s -- Used for carrier API calls (Maersk, MSC, CMA CGM) +### Repository Pattern +- Interface in `domain/ports/out/` with token constant (e.g. `BOOKING_REPOSITORY = 'BookingRepository'`) +- Implementation in `infrastructure/persistence/typeorm/repositories/` +- ORM entities: `infrastructure/persistence/typeorm/entities/*.orm-entity.ts` +- Separate mapper classes (`infrastructure/persistence/typeorm/mappers/`) with static `toOrm()`, `toDomain()`, `toDomainMany()` methods + +### Frontend API Client +Custom Fetch wrapper in `src/lib/api/client.ts` — exports `get()`, `post()`, `patch()`, `del()`, `upload()`, `download()`. Auto-refreshes JWT on 401. Tokens stored in localStorage. Per-module files (auth.ts, bookings.ts, rates.ts, etc.) import from client. + +### Application Decorators +- `@Public()` — skip JWT auth +- `@Roles()` — role-based access control +- `@CurrentUser()` — inject authenticated user + +### Carrier Connectors +Five carrier connectors (Maersk, MSC, CMA CGM, Hapag-Lloyd, ONE) extending `base-carrier.connector.ts`, each with request/response mappers. Circuit breaker via `opossum` (5s timeout). ### Caching -- Redis with 15-min TTL for rate quotes -- Cache key format: `rate:{origin}:{destination}:{containerType}` +Redis with 15-min TTL for rate quotes. Key format: `rate:{origin}:{destination}:{containerType}`. ## Business Rules - Booking number format: `WCM-YYYY-XXXXXX` +- Booking status flow: draft → confirmed → shipped → delivered - Rate quotes expire after 15 minutes -- Multi-currency: USD, EUR +- Multi-currency: USD, EUR, GBP, CNY, JPY - RBAC Roles: ADMIN, MANAGER, USER, VIEWER, CARRIER +- JWT: access token 15min, refresh token 7d +- Password hashing: Argon2 ### Carrier Portal Workflow 1. Admin creates CSV booking → assigns carrier @@ -136,35 +200,33 @@ export class Booking { 3. Carrier auto-login → accept/reject booking 4. Activity logged in `carrier_activities` table -## Tech Stack - -**Backend**: NestJS 10, TypeORM 0.3, PostgreSQL 15, Redis 7, Argon2, Pino, Sentry -**Frontend**: Next.js 14, React 18, TanStack Query/Table, React Hook Form + Zod, Tailwind + shadcn/ui, Socket.IO - ## Common Pitfalls - Never import NestJS/TypeORM in domain layer -- Never use `any` type (strict mode enabled) -- Never use `DATABASE_SYNC=true` in production -- Never modify applied migrations - create new ones -- Always validate DTOs with `class-validator` -- Always create mappers for Domain ↔ ORM conversions +- Never use `any` type in backend (strict mode enabled) +- Never modify applied migrations — create new ones +- Always validate DTOs with `class-validator` decorators +- Always create separate mappers for Domain ↔ ORM conversions +- ORM entity files must match pattern `*.orm-entity.{ts,js}` (auto-discovered by data-source) +- Migration files must be in `infrastructure/persistence/typeorm/migrations/` +- Database synchronize is hard-coded to `false` — always use migrations ## Adding a New Feature 1. **Domain Entity** → `domain/entities/*.entity.ts` (pure TS, unit tests) 2. **Value Objects** → `domain/value-objects/*.vo.ts` (immutable) -3. **Port Interface** → `domain/ports/out/*.repository.ts` +3. **Port Interface** → `domain/ports/out/*.repository.ts` (with token constant) 4. **ORM Entity** → `infrastructure/persistence/typeorm/entities/*.orm-entity.ts` -5. **Generate Migration** → `npm run migration:generate -- ...` +5. **Migration** → `npm run migration:generate -- src/infrastructure/persistence/typeorm/migrations/MigrationName` 6. **Repository Impl** → `infrastructure/persistence/typeorm/repositories/` -7. **DTOs** → `application/dto/` (with class-validator decorators) -8. **Controller** → `application/controllers/` (with Swagger decorators) -9. **Module** → Register and import in `app.module.ts` +7. **Mapper** → `infrastructure/persistence/typeorm/mappers/` (static toOrm/toDomain/toDomainMany) +8. **DTOs** → `application/dto/` (with class-validator decorators) +9. **Controller** → `application/controllers/` (with Swagger decorators) +10. **Module** → Register and import in `app.module.ts` ## Documentation -- API Docs: http://localhost:4000/api/docs (Swagger) -- Architecture: `docs/architecture.md` +- API Docs: http://localhost:4000/api/docs (Swagger, when running) +- Setup guide: `docs/installation/START-HERE.md` - Carrier Portal API: `apps/backend/docs/CARRIER_PORTAL_API.md` - Full docs index: `docs/README.md` diff --git a/apps/backend/src/application/dto/csv-booking.dto.ts b/apps/backend/src/application/dto/csv-booking.dto.ts index 5a69d81..d2425f3 100644 --- a/apps/backend/src/application/dto/csv-booking.dto.ts +++ b/apps/backend/src/application/dto/csv-booking.dto.ts @@ -201,6 +201,12 @@ export class CsvBookingResponseDto { }) id: string; + @ApiPropertyOptional({ + description: 'Booking number (e.g. XPD-2026-W75VPT)', + example: 'XPD-2026-W75VPT', + }) + bookingNumber?: string; + @ApiProperty({ description: 'User ID who created the booking', example: '987fcdeb-51a2-43e8-9c6d-8b9a1c2d3e4f', diff --git a/apps/backend/src/application/services/csv-booking.service.ts b/apps/backend/src/application/services/csv-booking.service.ts index 677067b..7ddf3da 100644 --- a/apps/backend/src/application/services/csv-booking.service.ts +++ b/apps/backend/src/application/services/csv-booking.service.ts @@ -176,6 +176,7 @@ export class CsvBookingService { fileName: doc.fileName, })), confirmationToken, + notes: dto.notes, }); this.logger.log(`Email sent to carrier: ${dto.carrierEmail}`); } catch (error: any) { @@ -921,6 +922,7 @@ export class CsvBookingService { return { id: booking.id, + bookingNumber: booking.bookingNumber, userId: booking.userId, organizationId: booking.organizationId, carrierName: booking.carrierName, diff --git a/apps/backend/src/domain/entities/csv-booking.entity.ts b/apps/backend/src/domain/entities/csv-booking.entity.ts index 30f3842..1361e0d 100644 --- a/apps/backend/src/domain/entities/csv-booking.entity.ts +++ b/apps/backend/src/domain/entities/csv-booking.entity.ts @@ -79,7 +79,8 @@ export class CsvBooking { public readonly requestedAt: Date, public respondedAt?: Date, public notes?: string, - public rejectionReason?: string + public rejectionReason?: string, + public readonly bookingNumber?: string ) { this.validate(); } @@ -361,7 +362,8 @@ export class CsvBooking { requestedAt: Date, respondedAt?: Date, notes?: string, - rejectionReason?: string + rejectionReason?: string, + bookingNumber?: string ): CsvBooking { // Create instance without calling constructor validation const booking = Object.create(CsvBooking.prototype); @@ -389,6 +391,7 @@ export class CsvBooking { booking.respondedAt = respondedAt; booking.notes = notes; booking.rejectionReason = rejectionReason; + booking.bookingNumber = bookingNumber; return booking; } diff --git a/apps/backend/src/domain/ports/out/email.port.ts b/apps/backend/src/domain/ports/out/email.port.ts index ab7321c..75c1375 100644 --- a/apps/backend/src/domain/ports/out/email.port.ts +++ b/apps/backend/src/domain/ports/out/email.port.ts @@ -102,6 +102,7 @@ export interface EmailPort { fileName: string; }>; confirmationToken: string; + notes?: string; } ): Promise; diff --git a/apps/backend/src/infrastructure/email/email.adapter.ts b/apps/backend/src/infrastructure/email/email.adapter.ts index e0ff9f3..363c713 100644 --- a/apps/backend/src/infrastructure/email/email.adapter.ts +++ b/apps/backend/src/infrastructure/email/email.adapter.ts @@ -256,6 +256,7 @@ export class EmailAdapter implements EmailPort { fileName: string; }>; confirmationToken: string; + notes?: string; } ): Promise { // Use APP_URL (frontend) for accept/reject links diff --git a/apps/backend/src/infrastructure/email/templates/email-templates.ts b/apps/backend/src/infrastructure/email/templates/email-templates.ts index 7348082..7fe0d94 100644 --- a/apps/backend/src/infrastructure/email/templates/email-templates.ts +++ b/apps/backend/src/infrastructure/email/templates/email-templates.ts @@ -277,6 +277,7 @@ export class EmailTemplates { type: string; fileName: string; }>; + notes?: string; acceptUrl: string; rejectUrl: string; }): Promise { @@ -557,6 +558,14 @@ export class EmailTemplates { + {{#if notes}} + +
+

📝 Notes du client

+

{{notes}}

+
+ {{/if}} +

Veuillez confirmer votre décision :

diff --git a/apps/backend/src/infrastructure/persistence/typeorm/mappers/csv-booking.mapper.ts b/apps/backend/src/infrastructure/persistence/typeorm/mappers/csv-booking.mapper.ts index 66a8912..4fee923 100644 --- a/apps/backend/src/infrastructure/persistence/typeorm/mappers/csv-booking.mapper.ts +++ b/apps/backend/src/infrastructure/persistence/typeorm/mappers/csv-booking.mapper.ts @@ -41,7 +41,8 @@ export class CsvBookingMapper { ormEntity.requestedAt, ormEntity.respondedAt, ormEntity.notes, - ormEntity.rejectionReason + ormEntity.rejectionReason, + ormEntity.bookingNumber ?? undefined ); } diff --git a/apps/frontend/app/carrier/documents/[token]/page.tsx b/apps/frontend/app/carrier/documents/[token]/page.tsx index e670ba4..92cba27 100644 --- a/apps/frontend/app/carrier/documents/[token]/page.tsx +++ b/apps/frontend/app/carrier/documents/[token]/page.tsx @@ -2,6 +2,7 @@ import { useEffect, useState, useRef } from 'react'; import { useParams } from 'next/navigation'; +import Image from 'next/image'; import { FileText, Download, @@ -296,9 +297,9 @@ export default function CarrierDocumentsPage() { // Loading state if (loading) { return ( -
+
- +

Chargement...

Veuillez patienter

@@ -309,14 +310,14 @@ export default function CarrierDocumentsPage() { // Error state if (error) { return ( -
+

Erreur

{error}

@@ -328,11 +329,11 @@ export default function CarrierDocumentsPage() { // Password form state if (requirements?.requiresPassword && !data) { return ( -
+
-
- +
+

Accès sécurisé

@@ -358,7 +359,7 @@ export default function CarrierDocumentsPage() { value={password} onChange={e => setPassword(e.target.value.toUpperCase())} placeholder="Ex: A3B7K9" - className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-sky-500 focus:border-sky-500 text-center text-xl tracking-widest font-mono uppercase" + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-brand-turquoise focus:border-brand-turquoise text-center text-xl tracking-widest font-mono uppercase" autoComplete="off" autoFocus /> @@ -381,7 +382,7 @@ export default function CarrierDocumentsPage() { @@ -435,14 +441,14 @@ export default function CarrierDocumentsPage() {

{/* Booking Summary Card */}
-
+
{booking.origin} {booking.destination}
{booking.bookingNumber && ( -

+

N° {booking.bookingNumber}

)} @@ -491,7 +497,7 @@ export default function CarrierDocumentsPage() {

- + Documents ({documents.length})

@@ -516,7 +522,7 @@ export default function CarrierDocumentsPage() {

{doc.fileName}

- + {documentTypeLabels[doc.type] || doc.type} {formatFileSize(doc.size)} @@ -527,7 +533,7 @@ export default function CarrierDocumentsPage() {
{/* Footer */} -
+

© {new Date().getFullYear()} Xpeditis - Plateforme de fret maritime

diff --git a/apps/frontend/app/dashboard/booking/new/page.tsx b/apps/frontend/app/dashboard/booking/new/page.tsx index c590cd6..ebff420 100644 --- a/apps/frontend/app/dashboard/booking/new/page.tsx +++ b/apps/frontend/app/dashboard/booking/new/page.tsx @@ -50,6 +50,7 @@ function NewBookingPageContent() { const searchParams = useSearchParams(); const [currentStep, setCurrentStep] = useState(1); const [isSubmitting, setIsSubmitting] = useState(false); + const [termsAccepted, setTermsAccepted] = useState(false); const [error, setError] = useState(null); const [formData, setFormData] = useState({ @@ -188,7 +189,7 @@ function NewBookingPageContent() { const canProceedToStep2 = formData.carrierName && formData.origin && formData.destination; const canProceedToStep3 = formData.documents.length >= 1; - const canSubmit = canProceedToStep3; + const canSubmit = canProceedToStep3 && termsAccepted; const formatPrice = (price: number, currency: string) => { return new Intl.NumberFormat('fr-FR', { @@ -218,10 +219,10 @@ function NewBookingPageContent() {
{/* Progress Steps */} -
+
{[1, 2, 3].map(step => ( -
+
setTermsAccepted(e.target.checked)} className="mt-1 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" - required /> Je confirme que les informations fournies sont exactes et que j'accepte les{' '} diff --git a/apps/frontend/app/dashboard/bookings/page.tsx b/apps/frontend/app/dashboard/bookings/page.tsx index 19043cd..89d04bf 100644 --- a/apps/frontend/app/dashboard/bookings/page.tsx +++ b/apps/frontend/app/dashboard/bookings/page.tsx @@ -288,9 +288,12 @@ export default function BookingsListPage() { Date - + N° Devis + + N° Booking + @@ -352,11 +355,14 @@ export default function BookingsListPage() { }) : 'N/A'} - + {booking.type === 'csv' ? `#${booking.bookingId || booking.id.slice(0, 8).toUpperCase()}` : booking.bookingNumber || `#${booking.id.slice(0, 8).toUpperCase()}`} + + {booking.bookingNumber || '-'} + ))} diff --git a/apps/frontend/app/dashboard/layout.tsx b/apps/frontend/app/dashboard/layout.tsx index 919a31e..b57a3c3 100644 --- a/apps/frontend/app/dashboard/layout.tsx +++ b/apps/frontend/app/dashboard/layout.tsx @@ -19,7 +19,6 @@ import { FileText, Search, BookOpen, - User, Building2, Users, LogOut, @@ -36,7 +35,6 @@ export default function DashboardLayout({ children }: { children: React.ReactNod { name: 'Documents', href: '/dashboard/documents', icon: FileText }, { name: 'Suivi', href: '/dashboard/track-trace', icon: Search }, { name: 'Wiki Maritime', href: '/dashboard/wiki', icon: BookOpen }, - { name: 'Mon Profil', href: '/dashboard/profile', icon: User }, { name: 'Organisation', href: '/dashboard/settings/organization', icon: Building2 }, // ADMIN and MANAGER only navigation items ...(user?.role === 'ADMIN' || user?.role === 'MANAGER' ? [ @@ -171,19 +169,10 @@ export default function DashboardLayout({ children }: { children: React.ReactNod {/* Notifications */} - {/* User Role Badge */} - {user?.role === 'ADMIN' ? ( - - {user.role} - - ) : ( - - {user?.role} - - )} + {/* User Initials */} + + {user?.firstName?.[0]}{user?.lastName?.[0]} +
diff --git a/apps/frontend/src/components/organization/LicensesTab.tsx b/apps/frontend/src/components/organization/LicensesTab.tsx index 6f93a71..694524c 100644 --- a/apps/frontend/src/components/organization/LicensesTab.tsx +++ b/apps/frontend/src/components/organization/LicensesTab.tsx @@ -9,6 +9,8 @@ import { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { getSubscriptionOverview } from '@/lib/api/subscriptions'; +import Link from 'next/link'; +import { UserPlus } from 'lucide-react'; export default function LicensesTab() { const [error, setError] = useState(''); @@ -110,10 +112,17 @@ export default function LicensesTab() { {/* Active Licenses */}
-
+

Licences actives ({activeLicenses.length})

+ + + Inviter un utilisateur +
{activeLicenses.length === 0 ? (
@@ -335,9 +344,6 @@ export default function LicensesTab() {
  • Chaque utilisateur actif de votre organisation consomme une licence
  • -
  • - Les administrateurs (ADMIN) ont des licences illimitées et ne sont pas comptés dans le quota -
  • Les licences sont automatiquement assignées lors de l'ajout d'un utilisateur