fix documentation et landing page
This commit is contained in:
parent
0e4c0d7785
commit
e1f813bd92
@ -76,6 +76,11 @@ ONE_API_URL=https://api.one-line.com/v1
|
|||||||
ONE_USERNAME=your-one-username
|
ONE_USERNAME=your-one-username
|
||||||
ONE_PASSWORD=your-one-password
|
ONE_PASSWORD=your-one-password
|
||||||
|
|
||||||
|
# Swagger Documentation Access (HTTP Basic Auth)
|
||||||
|
# Leave empty to disable Swagger in production, or set both to protect with a password
|
||||||
|
SWAGGER_USERNAME=admin
|
||||||
|
SWAGGER_PASSWORD=change-this-strong-password
|
||||||
|
|
||||||
# Security
|
# Security
|
||||||
BCRYPT_ROUNDS=12
|
BCRYPT_ROUNDS=12
|
||||||
SESSION_TIMEOUT_MS=7200000
|
SESSION_TIMEOUT_MS=7200000
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import compression from 'compression';
|
|||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { Logger } from 'nestjs-pino';
|
import { Logger } from 'nestjs-pino';
|
||||||
import { helmetConfig, corsConfig } from './infrastructure/security/security.config';
|
import { helmetConfig, corsConfig } from './infrastructure/security/security.config';
|
||||||
|
import type { Request, Response, NextFunction } from 'express';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule, {
|
const app = await NestFactory.create(AppModule, {
|
||||||
@ -19,6 +20,7 @@ async function bootstrap() {
|
|||||||
const configService = app.get(ConfigService);
|
const configService = app.get(ConfigService);
|
||||||
const port = configService.get<number>('PORT', 4000);
|
const port = configService.get<number>('PORT', 4000);
|
||||||
const apiPrefix = configService.get<string>('API_PREFIX', 'api/v1');
|
const apiPrefix = configService.get<string>('API_PREFIX', 'api/v1');
|
||||||
|
const isProduction = configService.get<string>('NODE_ENV') === 'production';
|
||||||
|
|
||||||
// Use Pino logger
|
// Use Pino logger
|
||||||
app.useLogger(app.get(Logger));
|
app.useLogger(app.get(Logger));
|
||||||
@ -52,39 +54,76 @@ async function bootstrap() {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Swagger documentation
|
// ─── Swagger documentation ────────────────────────────────────────────────
|
||||||
const config = new DocumentBuilder()
|
const swaggerUser = configService.get<string>('SWAGGER_USERNAME');
|
||||||
.setTitle('Xpeditis API')
|
const swaggerPass = configService.get<string>('SWAGGER_PASSWORD');
|
||||||
.setDescription(
|
const swaggerEnabled = !isProduction || (Boolean(swaggerUser) && Boolean(swaggerPass));
|
||||||
'Maritime Freight Booking Platform - API for searching rates and managing bookings'
|
|
||||||
)
|
|
||||||
.setVersion('1.0')
|
|
||||||
.addBearerAuth()
|
|
||||||
.addTag('rates', 'Rate search and comparison')
|
|
||||||
.addTag('bookings', 'Booking management')
|
|
||||||
.addTag('auth', 'Authentication and authorization')
|
|
||||||
.addTag('users', 'User management')
|
|
||||||
.addTag('organizations', 'Organization management')
|
|
||||||
.build();
|
|
||||||
|
|
||||||
const document = SwaggerModule.createDocument(app, config);
|
if (swaggerEnabled) {
|
||||||
SwaggerModule.setup('api/docs', app, document, {
|
// HTTP Basic Auth guard for Swagger routes when credentials are configured
|
||||||
customSiteTitle: 'Xpeditis API Documentation',
|
if (swaggerUser && swaggerPass) {
|
||||||
customfavIcon: 'https://xpeditis.com/favicon.ico',
|
const swaggerPaths = ['/api/docs', '/api/docs-json', '/api/docs-yaml'];
|
||||||
customCss: '.swagger-ui .topbar { display: none }',
|
app.use(swaggerPaths, (req: Request, res: Response, next: NextFunction) => {
|
||||||
});
|
const authHeader = req.headers['authorization'];
|
||||||
|
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
||||||
|
res.setHeader('WWW-Authenticate', 'Basic realm="Xpeditis API Docs"');
|
||||||
|
res.status(401).send('Authentication required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const decoded = Buffer.from(authHeader.slice(6), 'base64').toString('utf-8');
|
||||||
|
const colonIndex = decoded.indexOf(':');
|
||||||
|
const user = decoded.slice(0, colonIndex);
|
||||||
|
const pass = decoded.slice(colonIndex + 1);
|
||||||
|
if (user !== swaggerUser || pass !== swaggerPass) {
|
||||||
|
res.setHeader('WWW-Authenticate', 'Basic realm="Xpeditis API Docs"');
|
||||||
|
res.status(401).send('Invalid credentials');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = new DocumentBuilder()
|
||||||
|
.setTitle('Xpeditis API')
|
||||||
|
.setDescription(
|
||||||
|
'Maritime Freight Booking Platform - API for searching rates and managing bookings'
|
||||||
|
)
|
||||||
|
.setVersion('1.0')
|
||||||
|
.addBearerAuth()
|
||||||
|
.addApiKey({ type: 'apiKey', name: 'x-api-key', in: 'header' }, 'x-api-key')
|
||||||
|
.addTag('rates', 'Rate search and comparison')
|
||||||
|
.addTag('bookings', 'Booking management')
|
||||||
|
.addTag('auth', 'Authentication and authorization')
|
||||||
|
.addTag('users', 'User management')
|
||||||
|
.addTag('organizations', 'Organization management')
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const document = SwaggerModule.createDocument(app, config);
|
||||||
|
SwaggerModule.setup('api/docs', app, document, {
|
||||||
|
customSiteTitle: 'Xpeditis API Documentation',
|
||||||
|
customfavIcon: 'https://xpeditis.com/favicon.ico',
|
||||||
|
customCss: '.swagger-ui .topbar { display: none }',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
await app.listen(port);
|
await app.listen(port);
|
||||||
|
|
||||||
|
const swaggerStatus = swaggerEnabled
|
||||||
|
? swaggerUser
|
||||||
|
? `http://localhost:${port}/api/docs (protected)`
|
||||||
|
: `http://localhost:${port}/api/docs (open — add SWAGGER_USERNAME/PASSWORD to secure)`
|
||||||
|
: 'disabled in production';
|
||||||
|
|
||||||
console.log(`
|
console.log(`
|
||||||
╔═══════════════════════════════════════╗
|
╔═══════════════════════════════════════════════╗
|
||||||
║ ║
|
║ ║
|
||||||
║ 🚢 Xpeditis API Server Running ║
|
║ 🚢 Xpeditis API Server Running ║
|
||||||
║ ║
|
║ ║
|
||||||
║ API: http://localhost:${port}/${apiPrefix} ║
|
║ API: http://localhost:${port}/${apiPrefix} ║
|
||||||
║ Docs: http://localhost:${port}/api/docs ║
|
║ Docs: ${swaggerStatus} ║
|
||||||
║ ║
|
║ ║
|
||||||
╚═══════════════════════════════════════╝
|
╚═══════════════════════════════════════════════╝
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -23,7 +23,6 @@ import {
|
|||||||
Users,
|
Users,
|
||||||
LogOut,
|
LogOut,
|
||||||
Lock,
|
Lock,
|
||||||
Code2,
|
|
||||||
Key,
|
Key,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useSubscription } from '@/lib/context/subscription-context';
|
import { useSubscription } from '@/lib/context/subscription-context';
|
||||||
@ -62,7 +61,6 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
|||||||
{ name: 'Suivi', href: '/dashboard/track-trace', icon: Search, requiredFeature: 'dashboard' },
|
{ name: 'Suivi', href: '/dashboard/track-trace', icon: Search, requiredFeature: 'dashboard' },
|
||||||
{ name: 'Wiki Maritime', href: '/dashboard/wiki', icon: BookOpen, requiredFeature: 'wiki' },
|
{ name: 'Wiki Maritime', href: '/dashboard/wiki', icon: BookOpen, requiredFeature: 'wiki' },
|
||||||
{ name: 'Organisation', href: '/dashboard/settings/organization', icon: Building2 },
|
{ name: 'Organisation', href: '/dashboard/settings/organization', icon: Building2 },
|
||||||
{ name: 'Documentation API', href: '/dashboard/docs', icon: Code2 },
|
|
||||||
{ name: 'Clés API', href: '/dashboard/settings/api-keys', icon: Key, requiredFeature: 'api_access' as PlanFeature },
|
{ name: 'Clés API', href: '/dashboard/settings/api-keys', icon: Key, requiredFeature: 'api_access' as PlanFeature },
|
||||||
// ADMIN and MANAGER only navigation items
|
// ADMIN and MANAGER only navigation items
|
||||||
...(user?.role === 'ADMIN' || user?.role === 'MANAGER' ? [
|
...(user?.role === 'ADMIN' || user?.role === 'MANAGER' ? [
|
||||||
|
|||||||
7
apps/frontend/app/docs/api/page.tsx
Normal file
7
apps/frontend/app/docs/api/page.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { DocsPageContent } from '@/components/docs/DocsPageContent';
|
||||||
|
|
||||||
|
export default function PublicDocsPage() {
|
||||||
|
return <DocsPageContent basePath="/docs/api" variant="public" />;
|
||||||
|
}
|
||||||
16
apps/frontend/app/docs/layout.tsx
Normal file
16
apps/frontend/app/docs/layout.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { LandingHeader } from '@/components/layout/LandingHeader';
|
||||||
|
import { LandingFooter } from '@/components/layout/LandingFooter';
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: 'Documentation API — Xpeditis',
|
||||||
|
description: 'Documentation de l\'API Xpeditis pour intégrer le fret maritime dans vos applications.',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DocsLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<LandingHeader />
|
||||||
|
<main className="pt-20">{children}</main>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -70,7 +70,7 @@ export default function LandingPage() {
|
|||||||
const heroRef = useRef(null);
|
const heroRef = useRef(null);
|
||||||
const featuresRef = useRef(null);
|
const featuresRef = useRef(null);
|
||||||
const statsRef = useRef(null);
|
const statsRef = useRef(null);
|
||||||
const toolsRef = useRef(null);
|
|
||||||
const pricingRef = useRef(null);
|
const pricingRef = useRef(null);
|
||||||
const testimonialsRef = useRef(null);
|
const testimonialsRef = useRef(null);
|
||||||
const ctaRef = useRef(null);
|
const ctaRef = useRef(null);
|
||||||
@ -79,7 +79,7 @@ export default function LandingPage() {
|
|||||||
const isHeroInView = useInView(heroRef, { once: true });
|
const isHeroInView = useInView(heroRef, { once: true });
|
||||||
const isFeaturesInView = useInView(featuresRef, { once: true });
|
const isFeaturesInView = useInView(featuresRef, { once: true });
|
||||||
const isStatsInView = useInView(statsRef, { once: true, amount: 0.3 });
|
const isStatsInView = useInView(statsRef, { once: true, amount: 0.3 });
|
||||||
const isToolsInView = useInView(toolsRef, { once: true });
|
|
||||||
const isPricingInView = useInView(pricingRef, { once: true });
|
const isPricingInView = useInView(pricingRef, { once: true });
|
||||||
const isTestimonialsInView = useInView(testimonialsRef, { once: true });
|
const isTestimonialsInView = useInView(testimonialsRef, { once: true });
|
||||||
const isCtaInView = useInView(ctaRef, { once: true });
|
const isCtaInView = useInView(ctaRef, { once: true });
|
||||||
@ -139,44 +139,6 @@ export default function LandingPage() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const tools = [
|
|
||||||
{
|
|
||||||
icon: LayoutDashboard,
|
|
||||||
title: 'Tableau de bord',
|
|
||||||
description: 'Vue d\'ensemble de votre activité maritime',
|
|
||||||
link: '/dashboard',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: Package,
|
|
||||||
title: 'Mes Réservations',
|
|
||||||
description: 'Gérez toutes vos réservations en un seul endroit',
|
|
||||||
link: '/dashboard/bookings',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: FileText,
|
|
||||||
title: 'Documents',
|
|
||||||
description: 'Accédez à tous vos documents maritimes',
|
|
||||||
link: '/dashboard/documents',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: Search,
|
|
||||||
title: 'Suivi des expéditions',
|
|
||||||
description: 'Suivez vos conteneurs en temps réel',
|
|
||||||
link: '/dashboard/track-trace',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: BookOpen,
|
|
||||||
title: 'Wiki Maritime',
|
|
||||||
description: 'Base de connaissances du fret maritime',
|
|
||||||
link: '/dashboard/wiki',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: Users,
|
|
||||||
title: 'Mon Profil',
|
|
||||||
description: 'Gérez vos informations personnelles',
|
|
||||||
link: '/dashboard/profile',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const stats = [
|
const stats = [
|
||||||
{ end: 50, prefix: '', suffix: '+', decimals: 0, label: 'Compagnies Maritimes', icon: Ship },
|
{ end: 50, prefix: '', suffix: '+', decimals: 0, label: 'Compagnies Maritimes', icon: Ship },
|
||||||
@ -237,7 +199,7 @@ export default function LandingPage() {
|
|||||||
{ text: 'Accès API', included: false },
|
{ text: 'Accès API', included: false },
|
||||||
{ text: 'KAM dédié', included: false },
|
{ text: 'KAM dédié', included: false },
|
||||||
],
|
],
|
||||||
cta: 'Essai gratuit 14 jours',
|
cta: 'Commencer',
|
||||||
ctaLink: '/register',
|
ctaLink: '/register',
|
||||||
highlighted: true,
|
highlighted: true,
|
||||||
accentColor: 'from-slate-400 to-slate-500',
|
accentColor: 'from-slate-400 to-slate-500',
|
||||||
@ -266,7 +228,7 @@ export default function LandingPage() {
|
|||||||
{ text: 'Accès API complet', included: true },
|
{ text: 'Accès API complet', included: true },
|
||||||
{ text: 'KAM dédié', included: false },
|
{ text: 'KAM dédié', included: false },
|
||||||
],
|
],
|
||||||
cta: 'Essai gratuit 14 jours',
|
cta: 'Commencer',
|
||||||
ctaLink: '/register',
|
ctaLink: '/register',
|
||||||
highlighted: false,
|
highlighted: false,
|
||||||
accentColor: 'from-yellow-400 to-amber-400',
|
accentColor: 'from-yellow-400 to-amber-400',
|
||||||
@ -600,67 +562,6 @@ export default function LandingPage() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Tools & Calculators Section */}
|
|
||||||
<section
|
|
||||||
ref={toolsRef}
|
|
||||||
id="tools"
|
|
||||||
className="py-20 lg:py-32 bg-gradient-to-br from-gray-50 to-white"
|
|
||||||
>
|
|
||||||
<div className="max-w-7xl mx-auto px-6 lg:px-8">
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 30 }}
|
|
||||||
animate={isToolsInView ? { opacity: 1, y: 0 } : {}}
|
|
||||||
transition={{ duration: 0.8 }}
|
|
||||||
className="text-center mb-16"
|
|
||||||
>
|
|
||||||
<h2 className="text-4xl lg:text-5xl font-bold text-brand-navy mb-4">
|
|
||||||
Outils & Calculateurs
|
|
||||||
</h2>
|
|
||||||
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
|
|
||||||
Des outils puissants pour optimiser vos opérations maritimes
|
|
||||||
</p>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<motion.div
|
|
||||||
variants={containerVariants}
|
|
||||||
initial="hidden"
|
|
||||||
animate={isToolsInView ? 'visible' : 'hidden'}
|
|
||||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
|
|
||||||
>
|
|
||||||
{tools.map((tool, index) => {
|
|
||||||
const IconComponent = tool.icon;
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
key={index}
|
|
||||||
variants={itemVariants}
|
|
||||||
whileHover={{ y: -5 }}
|
|
||||||
className="group"
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
href={tool.link}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="block bg-white p-6 rounded-xl border-2 border-gray-200 hover:border-brand-turquoise transition-all hover:shadow-lg"
|
|
||||||
>
|
|
||||||
<div className="flex items-start space-x-4">
|
|
||||||
<div className="flex-shrink-0 w-12 h-12 bg-brand-turquoise/10 rounded-lg flex items-center justify-center group-hover:bg-brand-turquoise/20 transition-colors">
|
|
||||||
<IconComponent className="w-6 h-6 text-brand-turquoise" />
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<h3 className="text-lg font-bold text-brand-navy mb-1 group-hover:text-brand-turquoise transition-colors">
|
|
||||||
{tool.title}
|
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-gray-600">{tool.description}</p>
|
|
||||||
</div>
|
|
||||||
<ArrowRight className="w-5 h-5 text-gray-400 group-hover:text-brand-turquoise group-hover:translate-x-1 transition-all" />
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Partner Logos Section */}
|
{/* Partner Logos Section */}
|
||||||
<section className="py-16 bg-white">
|
<section className="py-16 bg-white">
|
||||||
@ -928,7 +829,7 @@ export default function LandingPage() {
|
|||||||
className="mt-12 text-center space-y-2"
|
className="mt-12 text-center space-y-2"
|
||||||
>
|
>
|
||||||
<p className="text-gray-600 text-sm">
|
<p className="text-gray-600 text-sm">
|
||||||
Plans Silver et Gold : essai gratuit 14 jours inclus · Aucune carte bancaire requise
|
Sans engagement · Résiliable à tout moment
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
Des questions ?{' '}
|
Des questions ?{' '}
|
||||||
|
|||||||
@ -24,6 +24,7 @@ const prefixPublicPaths = [
|
|||||||
'/contact',
|
'/contact',
|
||||||
'/carrier',
|
'/carrier',
|
||||||
'/pricing',
|
'/pricing',
|
||||||
|
'/docs',
|
||||||
];
|
];
|
||||||
|
|
||||||
export function middleware(request: NextRequest) {
|
export function middleware(request: NextRequest) {
|
||||||
|
|||||||
1190
apps/frontend/src/components/docs/DocsPageContent.tsx
Normal file
1190
apps/frontend/src/components/docs/DocsPageContent.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@ -9,12 +9,13 @@ import {
|
|||||||
Info,
|
Info,
|
||||||
BookOpen,
|
BookOpen,
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
|
Code2,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useAuth } from '@/lib/context/auth-context';
|
import { useAuth } from '@/lib/context/auth-context';
|
||||||
|
|
||||||
interface LandingHeaderProps {
|
interface LandingHeaderProps {
|
||||||
transparentOnTop?: boolean;
|
transparentOnTop?: boolean;
|
||||||
activePage?: 'about' | 'contact' | 'careers' | 'blog' | 'press';
|
activePage?: 'about' | 'contact' | 'careers' | 'blog' | 'press' | 'docs';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LandingHeader({ transparentOnTop = false, activePage }: LandingHeaderProps) {
|
export function LandingHeader({ transparentOnTop = false, activePage }: LandingHeaderProps) {
|
||||||
@ -91,12 +92,6 @@ export function LandingHeader({ transparentOnTop = false, activePage }: LandingH
|
|||||||
>
|
>
|
||||||
Fonctionnalités
|
Fonctionnalités
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
|
||||||
href="/#tools"
|
|
||||||
className="text-white hover:text-brand-turquoise transition-colors font-medium"
|
|
||||||
>
|
|
||||||
Outils
|
|
||||||
</Link>
|
|
||||||
<Link
|
<Link
|
||||||
href="/#pricing"
|
href="/#pricing"
|
||||||
className="text-white hover:text-brand-turquoise transition-colors font-medium"
|
className="text-white hover:text-brand-turquoise transition-colors font-medium"
|
||||||
@ -104,18 +99,6 @@ export function LandingHeader({ transparentOnTop = false, activePage }: LandingH
|
|||||||
Tarifs
|
Tarifs
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Contact — lien direct dans la nav principale */}
|
|
||||||
<Link
|
|
||||||
href="/contact"
|
|
||||||
className={`transition-colors font-medium ${
|
|
||||||
activePage === 'contact'
|
|
||||||
? 'text-brand-turquoise'
|
|
||||||
: 'text-white hover:text-brand-turquoise'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Contact
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{/* Menu Entreprise */}
|
{/* Menu Entreprise */}
|
||||||
<div
|
<div
|
||||||
className="relative"
|
className="relative"
|
||||||
@ -184,6 +167,29 @@ export function LandingHeader({ transparentOnTop = false, activePage }: LandingH
|
|||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href="/contact"
|
||||||
|
className={`transition-colors font-medium ${
|
||||||
|
activePage === 'contact'
|
||||||
|
? 'text-brand-turquoise'
|
||||||
|
: 'text-white hover:text-brand-turquoise'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Contact
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href="/docs/api"
|
||||||
|
className={`flex items-center gap-1.5 transition-colors font-medium ${
|
||||||
|
activePage === 'docs'
|
||||||
|
? 'text-brand-turquoise'
|
||||||
|
: 'text-white hover:text-brand-turquoise'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Code2 className="w-4 h-4" />
|
||||||
|
Docs API
|
||||||
|
</Link>
|
||||||
|
|
||||||
{/* Affichage conditionnel: connecté vs non connecté */}
|
{/* Affichage conditionnel: connecté vs non connecté */}
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="w-8 h-8 rounded-full bg-white/20 animate-pulse" />
|
<div className="w-8 h-8 rounded-full bg-white/20 animate-pulse" />
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user