move to gitea
This commit is contained in:
269
src_backup/lib/utils.ts
Normal file
269
src_backup/lib/utils.ts
Normal 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;
|
||||
}
|
||||
181
src_backup/lib/woocommerce.ts
Normal file
181
src_backup/lib/woocommerce.ts
Normal 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) : [];
|
||||
};
|
||||
Reference in New Issue
Block a user