2krika
Technical

Configuration

Configuration et variables d'environnement de la plateforme 2Krika

Configuration

Guide complet de configuration des applications 2Krika.

Variables d'Environnement

App Customers

apps/customers/.env
# 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/xxx

App Staffs

apps/staffs/.env
# 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/xxx

Backend API

backend/.env
# 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.5

Configuration par Environnement

Développement (Local)

.env.development
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_production

Staging

.env.staging
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_staging

Production

.env.production
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_prod

Configuration Next.js

App Customers

apps/customers/next.config.mjs
/** @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

apps/staffs/next.config.mjs
/** @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

apps/customers/tailwind.config.ts
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

tsconfig.json
{
  "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

apps/customers/src/app/layout.tsx
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

apps/customers/src/providers/QueryProvider.tsx
'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

apps/customers/src/config/api.ts
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

apps/customers/src/config/socket.ts
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)

apps/customers/src/i18n/request.ts
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,
  };
});
apps/customers/src/i18n/routing.ts
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

apps/customers/src/config/constants.ts
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.local créé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é

Prochaines Étapes

On this page