Technical
Configuration
Configuration et variables d'environnement de la plateforme 2Krika
Configuration
Guide complet de configuration des applications 2Krika.
Variables d'Environnement
App Customers
# API Configuration
NEXT_PUBLIC_API_URL=https://api.2krikaservices.cloud
# Socket Configuration
NEXT_PUBLIC_SOCKET_URL=wss://socket.2krikaservices.cloud
# App Configuration
NEXT_PUBLIC_APP_NAME="2Krika"
NEXT_PUBLIC_APP_URL=https://www.2krika.com
# Features Flags (optionnel)
NEXT_PUBLIC_ENABLE_CHAT=true
NEXT_PUBLIC_ENABLE_NOTIFICATIONS=true
# Analytics (optionnel)
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
NEXT_PUBLIC_SENTRY_DSN=https://xxx@sentry.io/xxxApp Staffs
# API Configuration
NEXT_PUBLIC_API_URL=https://api.2krikaservices.cloud
# Admin Panel
NEXT_PUBLIC_APP_URL=https://staff.2krika.com
# Analytics
NEXT_PUBLIC_SENTRY_DSN=https://xxx@sentry.io/xxxBackend API
# Node Environment
NODE_ENV=production
PORT=8000
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/krika_db
DB_HOST=localhost
DB_PORT=5432
DB_NAME=krika_db
DB_USER=krika_user
DB_PASSWORD=secure_password
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
# JWT
JWT_SECRET=your_super_secret_jwt_key_change_this
JWT_EXPIRES_IN=15m
JWT_REFRESH_SECRET=your_refresh_secret_key_change_this
JWT_REFRESH_EXPIRES_IN=7d
# AWS S3
AWS_ACCESS_KEY_ID=your_aws_access_key
AWS_SECRET_ACCESS_KEY=your_aws_secret_key
AWS_REGION=us-east-1
AWS_BUCKET_NAME=2krika-uploads
# Email (Nodemailer)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=noreply@2krika.com
SMTP_PASSWORD=your_smtp_password
EMAIL_FROM="2Krika <noreply@2krika.com>"
# Payment Gateway (exemple Stripe)
STRIPE_SECRET_KEY=sk_live_xxx
STRIPE_PUBLISHABLE_KEY=pk_live_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
# Application URLs
FRONTEND_URL=https://www.2krika.com
ADMIN_URL=https://staff.2krika.com
# Features
ENABLE_KYC_VERIFICATION=true
ENABLE_EMAIL_NOTIFICATIONS=true
ENABLE_WEBHOOKS=false
# Rate Limiting
RATE_LIMIT_WINDOW_MS=60000
RATE_LIMIT_MAX_REQUESTS=100
# File Upload
MAX_FILE_SIZE=10485760
ALLOWED_FILE_TYPES=image/jpeg,image/png,image/gif,application/pdf
# Socket.io
SOCKET_PORT=3001
SOCKET_CORS_ORIGIN=https://www.2krika.com
# Monitoring
SENTRY_DSN=https://xxx@sentry.io/xxx
LOG_LEVEL=info
# Commission Rates (%)
DEFAULT_COMMISSION_RATE=10
PAYMENT_PROCESSING_FEE=2.5Configuration par Environnement
Développement (Local)
NODE_ENV=development
NEXT_PUBLIC_API_URL=http://localhost:8000
NEXT_PUBLIC_SOCKET_URL=ws://localhost:3001
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/krika_dev
JWT_SECRET=dev_secret_not_for_productionStaging
NODE_ENV=staging
NEXT_PUBLIC_API_URL=https://api-staging.2krikaservices.cloud
NEXT_PUBLIC_SOCKET_URL=wss://socket-staging.2krikaservices.cloud
DATABASE_URL=postgresql://user:pass@db-staging.xxx/krika_stagingProduction
NODE_ENV=production
NEXT_PUBLIC_API_URL=https://api.2krikaservices.cloud
NEXT_PUBLIC_SOCKET_URL=wss://socket.2krikaservices.cloud
DATABASE_URL=postgresql://user:pass@db-prod.xxx/krika_prodConfiguration Next.js
App Customers
/** @type {import('next').NextConfig} */
const nextConfig = {
// Image optimization
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'api.2krikaservices.cloud',
pathname: '/uploads/**',
},
{
protocol: 'https',
hostname: '2krika-uploads.s3.amazonaws.com',
pathname: '/**',
},
],
formats: ['image/avif', 'image/webp'],
},
// Internationalization
i18n: {
locales: ['fr', 'en'],
defaultLocale: 'fr',
},
// Webpack configuration
webpack: (config, { dev }) => {
if (dev) {
config.cache = false;
}
return config;
},
// Headers
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin',
},
],
},
];
},
// Redirects
async redirects() {
return [
{
source: '/home',
destination: '/',
permanent: true,
},
];
},
// Experimental features
experimental: {
optimizeCss: true,
optimizePackageImports: ['@mantine/core', '@mantine/hooks'],
},
// Output
output: 'standalone',
};
export default nextConfig;App Staffs
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'api.2krikaservices.cloud',
},
],
},
// Pas d'i18n pour l'admin
output: 'standalone',
};
export default nextConfig;Configuration Tailwind
import type { Config } from 'tailwindcss';
const config: Config = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
950: '#082f49',
},
secondary: {
50: '#fdf4ff',
100: '#fae8ff',
200: '#f5d0fe',
300: '#f0abfc',
400: '#e879f9',
500: '#d946ef',
600: '#c026d3',
700: '#a21caf',
800: '#86198f',
900: '#701a75',
950: '#4a044e',
},
},
fontFamily: {
sans: ['var(--font-inter)', 'system-ui', 'sans-serif'],
heading: ['var(--font-poppins)', 'system-ui', 'sans-serif'],
},
spacing: {
'128': '32rem',
'144': '36rem',
},
borderRadius: {
'4xl': '2rem',
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
require('@tailwindcss/aspect-ratio'),
],
};
export default config;Configuration TypeScript
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "preserve",
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"allowJs": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"],
"@/services/*": ["./src/services/*"],
"@/models/*": ["./src/models/*"],
"@/utils/*": ["./src/utils/*"],
"@/hooks/*": ["./src/hooks/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}Configuration Mantine
import '@mantine/core/styles.css';
import { MantineProvider, createTheme } from '@mantine/core';
const theme = createTheme({
primaryColor: 'blue',
defaultRadius: 'md',
fontFamily: 'Inter, sans-serif',
headings: {
fontFamily: 'Poppins, sans-serif',
},
colors: {
brand: [
'#f0f9ff',
'#e0f2fe',
'#bae6fd',
'#7dd3fc',
'#38bdf8',
'#0ea5e9',
'#0284c7',
'#0369a1',
'#075985',
'#0c4a6e',
],
},
});
export default function RootLayout({ children }) {
return (
<html lang="fr">
<body>
<MantineProvider theme={theme}>{children}</MantineProvider>
</body>
</html>
);
}Configuration React Query
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { useState } from 'react';
export function QueryProvider({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false,
retry: 1,
},
mutations: {
retry: 1,
},
},
})
);
return (
<QueryClientProvider client={queryClient}>
{children}
{process.env.NODE_ENV === 'development' && (
<ReactQueryDevtools initialIsOpen={false} />
)}
</QueryClientProvider>
);
}Configuration Axios
import axios from 'axios';
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// Token expired
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = localStorage.getItem('refreshToken');
const { data } = await axios.post(
`${process.env.NEXT_PUBLIC_API_URL}/auth/refresh`,
{ refreshToken }
);
localStorage.setItem('accessToken', data.accessToken);
originalRequest.headers.Authorization = `Bearer ${data.accessToken}`;
return api(originalRequest);
} catch (refreshError) {
// Redirect to login
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
export default api;Configuration Socket.io
import { io, Socket } from 'socket.io-client';
let socket: Socket | null = null;
export const getSocket = (): Socket => {
if (!socket) {
socket = io(process.env.NEXT_PUBLIC_SOCKET_URL!, {
autoConnect: false,
auth: {
token: localStorage.getItem('accessToken'),
},
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000,
});
socket.on('connect', () => {
console.log('Socket connected:', socket?.id);
});
socket.on('disconnect', (reason) => {
console.log('Socket disconnected:', reason);
});
socket.on('connect_error', (error) => {
console.error('Socket connection error:', error);
});
}
return socket;
};
export const connectSocket = () => {
const socket = getSocket();
if (!socket.connected) {
socket.connect();
}
return socket;
};
export const disconnectSocket = () => {
if (socket?.connected) {
socket.disconnect();
}
};Configuration i18n (next-intl)
import { getRequestConfig } from 'next-intl/server';
import { notFound } from 'next/navigation';
const locales = ['fr', 'en'];
export default getRequestConfig(async ({ locale }) => {
if (!locales.includes(locale as any)) notFound();
return {
messages: (await import(`../locales/${locale}.json`)).default,
};
});import { defineRouting } from 'next-intl/routing';
import { createSharedPathnamesNavigation } from 'next-intl/navigation';
export const routing = defineRouting({
locales: ['fr', 'en'],
defaultLocale: 'fr',
});
export const { Link, redirect, usePathname, useRouter } =
createSharedPathnamesNavigation(routing);Constantes de Configuration
export const APP_CONFIG = {
name: '2Krika',
description: 'Plateforme de services freelance',
url: process.env.NEXT_PUBLIC_APP_URL || 'https://www.2krika.com',
api: {
url: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000',
timeout: 30000,
},
socket: {
url: process.env.NEXT_PUBLIC_SOCKET_URL || 'ws://localhost:3001',
},
};
export const LIMITS = {
maxFileSize: 10 * 1024 * 1024, // 10MB
maxImagesPerService: 5,
minServicePrice: 5,
maxServicePrice: 10000,
maxServiceTitleLength: 100,
maxServiceDescriptionLength: 5000,
};
export const ROUTES = {
home: '/',
services: '/services',
service: (id: string) => `/service/${id}`,
profile: (id: string) => `/profile/${id}`,
sales: '/sales',
purchases: '/purchases',
settings: '/settings',
login: '/login',
register: '/register',
} as const;
export const ORDER_STATUSES = {
PENDING: 'pending',
IN_PROGRESS: 'in_progress',
DELIVERED: 'delivered',
COMPLETED: 'completed',
CANCELLED: 'cancelled',
DISPUTED: 'disputed',
} as const;
export const SERVICE_STATUSES = {
DRAFT: 'draft',
PENDING: 'pending',
ACTIVE: 'active',
PAUSED: 'paused',
REJECTED: 'rejected',
} as const;Checklist de Configuration
Développement
- Variables d'environnement
.env.localcréées - API backend fonctionnelle
- Base de données accessible
- Redis configuré
Staging
- Variables d'environnement configurées sur Vercel/serveur
- Domaines de staging configurés
- SSL activé
- Database staging créée
Production
- Toutes les variables d'environnement en production
- Domaines de production configurés
- SSL activé
- Monitoring activé
- Backups configurés
- Logs centralisés
- Rate limiting activé