move to gitea

This commit is contained in:
Mamadou Sall
2025-08-24 22:41:31 +02:00
commit 032b1d9452
122 changed files with 28723 additions and 0 deletions

269
src_backup/lib/utils.ts Normal file
View File

@@ -0,0 +1,269 @@
// src/lib/utils.ts
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs));
}
// Formatage des prix
export function formatCurrency(
amount: number | string,
currency: string = 'MRU',
locale: string = 'fr-FR'
): string {
const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount;
if (isNaN(numAmount)) return '0 MRU';
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency === 'MRU' ? 'EUR' : currency, // Fallback pour MRU
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}).format(numAmount).replace('€', 'MRU');
}
// Formatage des prix simples
export function formatPrice(price: number | string, currency: string = 'MRU'): string {
const numPrice = typeof price === 'string' ? parseFloat(price) : price;
if (isNaN(numPrice)) return '0 MRU';
return `${numPrice.toLocaleString('fr-FR')} ${currency}`;
}
// Génération d'un slug à partir d'un texte
export function generateSlug(text: string): string {
return text
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '') // Supprimer les accents
.replace(/[^a-z0-9 -]/g, '') // Supprimer les caractères spéciaux
.replace(/\s+/g, '-') // Remplacer les espaces par des tirets
.replace(/-+/g, '-') // Supprimer les tirets multiples
.trim(); // Supprimer les tirets en début et fin
}
// Validation d'email
export function isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// Validation de téléphone mauritanien
export function isValidMauritanianPhone(phone: string): boolean {
// Format: +222 XX XX XX XX ou 222 XX XX XX XX ou XX XX XX XX
const phoneRegex = /^(\+222|222)?[0-9\s]{8,}$/;
const cleaned = phone.replace(/\s/g, '');
return phoneRegex.test(cleaned) && cleaned.length >= 8;
}
// Formatage de téléphone mauritanien
export function formatMauritanianPhone(phone: string): string {
const cleaned = phone.replace(/\D/g, '');
if (cleaned.length === 8) {
return `+222 ${cleaned.slice(0, 2)} ${cleaned.slice(2, 4)} ${cleaned.slice(4, 6)} ${cleaned.slice(6, 8)}`;
}
if (cleaned.length === 11 && cleaned.startsWith('222')) {
const withoutCountryCode = cleaned.slice(3);
return `+222 ${withoutCountryCode.slice(0, 2)} ${withoutCountryCode.slice(2, 4)} ${withoutCountryCode.slice(4, 6)} ${withoutCountryCode.slice(6, 8)}`;
}
return phone;
}
// Debounce function
export function debounce<T extends (...args: any[]) => any>(
func: T,
delay: number
): (...args: Parameters<T>) => void {
let timeoutId: NodeJS.Timeout;
return (...args: Parameters<T>) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), delay);
};
}
// Calculer la distance de Levenshtein pour la recherche floue
export function levenshteinDistance(str1: string, str2: string): number {
const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));
for (let i = 0; i <= str1.length; i++) {
matrix[0]![i] = i;
}
for (let j = 0; j <= str2.length; j++) {
matrix[j]![0] = j;
}
for (let j = 1; j <= str2.length; j++) {
for (let i = 1; i <= str1.length; i++) {
const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
matrix[j]![i] = Math.min(
matrix[j]![i - 1]! + 1, // deletion
matrix[j - 1]![i]! + 1, // insertion
matrix[j - 1]![i - 1]! + indicator // substitution
);
}
}
return matrix[str2.length]![str1.length]!;
}
// Recherche floue dans un tableau de chaînes
export function fuzzySearch(query: string, items: string[], threshold: number = 2): string[] {
const lowerQuery = query.toLowerCase();
return items.filter(item => {
const lowerItem = item.toLowerCase();
// Correspondance exacte
if (lowerItem.includes(lowerQuery)) return true;
// Correspondance floue
const distance = levenshteinDistance(lowerQuery, lowerItem);
return distance <= threshold;
});
}
// Générer un ID unique
export function generateId(prefix: string = ''): string {
const timestamp = Date.now().toString(36);
const randomPart = Math.random().toString(36).substr(2, 5);
return `${prefix}${timestamp}${randomPart}`;
}
// Copier dans le presse-papiers
export async function copyToClipboard(text: string): Promise<boolean> {
try {
await navigator.clipboard.writeText(text);
return true;
} catch (err) {
// Fallback pour les navigateurs plus anciens
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
document.body.removeChild(textArea);
return successful;
} catch (err) {
document.body.removeChild(textArea);
return false;
}
}
}
// Formater une date en français
export function formatDate(
date: Date | string,
options: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: 'long',
day: 'numeric'
}
): string {
const dateObj = typeof date === 'string' ? new Date(date) : date;
return dateObj.toLocaleDateString('fr-FR', options);
}
// Calculer le temps relatif (il y a X jours)
export function getRelativeTime(date: Date | string): string {
const dateObj = typeof date === 'string' ? new Date(date) : date;
const now = new Date();
const diffInSeconds = Math.floor((now.getTime() - dateObj.getTime()) / 1000);
if (diffInSeconds < 60) return 'À l\'instant';
if (diffInSeconds < 3600) return `Il y a ${Math.floor(diffInSeconds / 60)} min`;
if (diffInSeconds < 86400) return `Il y a ${Math.floor(diffInSeconds / 3600)} h`;
if (diffInSeconds < 2592000) return `Il y a ${Math.floor(diffInSeconds / 86400)} j`;
if (diffInSeconds < 31536000) return `Il y a ${Math.floor(diffInSeconds / 2592000)} mois`;
return `Il y a ${Math.floor(diffInSeconds / 31536000)} an${Math.floor(diffInSeconds / 31536000) > 1 ? 's' : ''}`;
}
// Truncate text avec ellipsis
export function truncateText(text: string, maxLength: number): string {
if (text.length <= maxLength) return text;
return text.slice(0, maxLength).trim() + '...';
}
// Capitaliser la première lettre
export function capitalize(text: string): string {
return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
}
// Calculer la note moyenne
export function calculateAverageRating(ratings: number[]): number {
if (ratings.length === 0) return 0;
const sum = ratings.reduce((acc, rating) => acc + rating, 0);
return Number((sum / ratings.length).toFixed(1));
}
// Vérifier si on est côté client
export function isClient(): boolean {
return typeof window !== 'undefined';
}
// Attendre un délai
export function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Mélanger un tableau (shuffle)
export function shuffleArray<T>(array: T[]): T[] {
const shuffled = [...array];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j]!, shuffled[i]!];
}
return shuffled;
}
// Grouper un tableau par une clé
export function groupBy<T, K extends keyof any>(
array: T[],
key: (item: T) => K
): Record<K, T[]> {
return array.reduce((groups, item) => {
const group = key(item);
if (!groups[group]) {
groups[group] = [];
}
groups[group]!.push(item);
return groups;
}, {} as Record<K, T[]>);
}
// Supprimer les doublons d'un tableau
export function removeDuplicates<T>(array: T[], key?: (item: T) => any): T[] {
if (!key) {
return [...new Set(array)];
}
const seen = new Set();
return array.filter(item => {
const keyValue = key(item);
if (seen.has(keyValue)) {
return false;
}
seen.add(keyValue);
return true;
});
}
// Vérifier si un objet est vide
export function isEmpty(obj: any): boolean {
if (obj == null) return true;
if (Array.isArray(obj) || typeof obj === 'string') return obj.length === 0;
if (obj instanceof Map || obj instanceof Set) return obj.size === 0;
return Object.keys(obj).length === 0;
}

View File

@@ -0,0 +1,181 @@
// src/lib/woocommerce.ts
// @ts-ignore
import WooCommerceRestApi from "@woocommerce/woocommerce-rest-api";
import { WooCommerceProduct, ApiResponse } from "@/types/woocommerce";
// Configuration de l'API WooCommerce
export const api = new WooCommerceRestApi({
url: process.env.NEXT_PUBLIC_WC_API_URL || "",
consumerKey: process.env.NEXT_PUBLIC_WC_CONSUMER_KEY || "",
consumerSecret: process.env.NEXT_PUBLIC_WC_CONSUMER_SECRET || "",
version: "wc/v3",
queryStringAuth: true,
});
// Fonctions utilitaires pour l'API
export class WooCommerceService {
// Récupérer tous les produits
static async getProducts(params?: {
page?: number;
per_page?: number;
category?: string;
search?: string;
orderby?: string;
order?: 'asc' | 'desc';
on_sale?: boolean;
featured?: boolean;
}): Promise<ApiResponse<WooCommerceProduct[]>> {
try {
const defaultParams = {
page: 1,
per_page: 20,
status: 'publish',
...params
};
const response = await api.get("products", defaultParams);
return {
data: response.data as WooCommerceProduct[],
success: true,
};
} catch (error) {
console.error("Erreur lors de la récupération des produits:", error);
return {
data: [],
success: false,
message: "Erreur lors de la récupération des produits"
};
}
}
// Récupérer un produit par son slug
static async getProductBySlug(slug: string): Promise<ApiResponse<WooCommerceProduct | null>> {
try {
const response = await api.get("products", { slug, status: 'publish' });
const products = response.data as WooCommerceProduct[];
return {
data: products.length > 0 ? products[0] || null : null,
success: true,
};
} catch (error) {
console.error("Erreur lors de la récupération du produit:", error);
return {
data: null,
success: false,
message: "Produit non trouvé"
};
}
}
// Récupérer un produit par son ID
static async getProductById(id: number): Promise<ApiResponse<WooCommerceProduct | null>> {
try {
const response = await api.get(`products/${id}`);
return {
data: response.data as WooCommerceProduct,
success: true,
};
} catch (error) {
console.error("Erreur lors de la récupération du produit:", error);
return {
data: null,
success: false,
message: "Produit non trouvé"
};
}
}
// Récupérer les produits en vedette
static async getFeaturedProducts(limit: number = 6): Promise<ApiResponse<WooCommerceProduct[]>> {
return this.getProducts({
featured: true,
per_page: limit,
orderby: 'date',
order: 'desc'
});
}
// Récupérer les produits en promotion
static async getSaleProducts(limit: number = 6): Promise<ApiResponse<WooCommerceProduct[]>> {
return this.getProducts({
on_sale: true,
per_page: limit,
orderby: 'date',
order: 'desc'
});
}
// Récupérer les nouvelles arrivées
static async getNewArrivals(limit: number = 8): Promise<ApiResponse<WooCommerceProduct[]>> {
return this.getProducts({
per_page: limit,
orderby: 'date',
order: 'desc'
});
}
// Récupérer les catégories
static async getCategories(): Promise<ApiResponse<any[]>> {
try {
const response = await api.get("products/categories", {
per_page: 100,
hide_empty: true
});
return {
data: response.data,
success: true,
};
} catch (error) {
console.error("Erreur lors de la récupération des catégories:", error);
return {
data: [],
success: false,
message: "Erreur lors de la récupération des catégories"
};
}
}
// Rechercher des produits
static async searchProducts(query: string, limit: number = 20): Promise<ApiResponse<WooCommerceProduct[]>> {
return this.getProducts({
search: query,
per_page: limit
});
}
}
// Fonctions utilitaires pour les prix
export const formatPrice = (price: string | number, currency: string = 'MRU'): string => {
const numPrice = typeof price === 'string' ? parseFloat(price) : price;
return `${numPrice.toLocaleString('fr-FR')} ${currency}`;
};
// Vérifier si un produit est en promotion
export const isOnSale = (product: WooCommerceProduct): boolean => {
return product.on_sale && product.sale_price !== '';
};
// Calculer le pourcentage de réduction
export const getDiscountPercentage = (product: WooCommerceProduct): number => {
if (!isOnSale(product)) return 0;
const regularPrice = parseFloat(product.regular_price);
const salePrice = parseFloat(product.sale_price);
return Math.round(((regularPrice - salePrice) / regularPrice) * 100);
};
// Obtenir la première image d'un produit
export const getProductImage = (product: WooCommerceProduct): string => {
return product.images && product.images.length > 0 ? product.images[0]!.src : '/placeholder-product.jpg';
};
// Obtenir toutes les images d'un produit
export const getProductImages = (product: WooCommerceProduct): string[] => {
return product.images ? product.images.map(image => image.src) : [];
};