Compare commits
9 Commits
67df2d95e9
...
mac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4db8271ecf | ||
|
|
e6d6770c26 | ||
| 371ed17867 | |||
| f6e3c1bb8c | |||
| 6acd5ebe2e | |||
| 6a42c52b58 | |||
| 72c24e0cc6 | |||
| bac36efb94 | |||
| 75ae9c5f3d |
2
.env
2
.env
@@ -5,4 +5,4 @@ DATABASE_URL="postgresql://admin:adminpassword@192.168.1.25:5432/afrohub_dev?sch
|
|||||||
# Exemple avec Supabase ou Neon (si base distante)
|
# Exemple avec Supabase ou Neon (si base distante)
|
||||||
# DATABASE_URL="postgresql://postgres:[MOT_DE_PASSE]@db.[ID_PROJET].supabase.co:5432/postgres"
|
# DATABASE_URL="postgresql://postgres:[MOT_DE_PASSE]@db.[ID_PROJET].supabase.co:5432/postgres"
|
||||||
# Environnement : "development" = accents rouges, "production" = accents normaux
|
# Environnement : "development" = accents rouges, "production" = accents normaux
|
||||||
NEXT_PUBLIC_APP_ENV=production
|
NEXT_PUBLIC_APP_ENV=development
|
||||||
10
admin/package-lock.json
generated
10
admin/package-lock.json
generated
@@ -12,7 +12,6 @@
|
|||||||
"@prisma/client": "6.19.3",
|
"@prisma/client": "6.19.3",
|
||||||
"@prisma/config": "6.19.3",
|
"@prisma/config": "6.19.3",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"afrohub": "file:..",
|
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"dotenv": "^17.4.1",
|
"dotenv": "^17.4.1",
|
||||||
"lucide-react": "^1.8.0",
|
"lucide-react": "^1.8.0",
|
||||||
@@ -39,6 +38,7 @@
|
|||||||
"..": {
|
"..": {
|
||||||
"name": "afrohub",
|
"name": "afrohub",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
|
"extraneous": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google/genai": "^1.30.0",
|
"@google/genai": "^1.30.0",
|
||||||
"@prisma/adapter-pg": "6.19.3",
|
"@prisma/adapter-pg": "6.19.3",
|
||||||
@@ -47,13 +47,12 @@
|
|||||||
"@tailwindcss/postcss": "^4.2.2",
|
"@tailwindcss/postcss": "^4.2.2",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/dompurify": "^3.0.5",
|
|
||||||
"@types/node": "^22.14.0",
|
"@types/node": "^22.14.0",
|
||||||
"@types/pg": "^8.16.0",
|
"@types/pg": "^8.16.0",
|
||||||
|
"@types/sanitize-html": "^2.16.1",
|
||||||
"autoprefixer": "^10.4.27",
|
"autoprefixer": "^10.4.27",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"dotenv": "^17.3.1",
|
"dotenv": "^17.3.1",
|
||||||
"isomorphic-dompurify": "^3.11.0",
|
|
||||||
"jiti": "^2.6.1",
|
"jiti": "^2.6.1",
|
||||||
"lucide-react": "^0.554.0",
|
"lucide-react": "^0.554.0",
|
||||||
"next": "^16.1.6",
|
"next": "^16.1.6",
|
||||||
@@ -65,6 +64,7 @@
|
|||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-hot-toast": "^2.6.0",
|
"react-hot-toast": "^2.6.0",
|
||||||
"recharts": "^3.8.1",
|
"recharts": "^3.8.1",
|
||||||
|
"sanitize-html": "^2.17.3",
|
||||||
"tailwindcss": "^4.2.2",
|
"tailwindcss": "^4.2.2",
|
||||||
"tsx": "^4.21.0",
|
"tsx": "^4.21.0",
|
||||||
"typescript": "~5.8.2"
|
"typescript": "~5.8.2"
|
||||||
@@ -2339,10 +2339,6 @@
|
|||||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/afrohub": {
|
|
||||||
"resolved": "..",
|
|
||||||
"link": true
|
|
||||||
},
|
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.14.0",
|
"version": "6.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
"@prisma/client": "6.19.3",
|
"@prisma/client": "6.19.3",
|
||||||
"@prisma/config": "6.19.3",
|
"@prisma/config": "6.19.3",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"afrohub": "file:..",
|
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"dotenv": "^17.4.1",
|
"dotenv": "^17.4.1",
|
||||||
"lucide-react": "^1.8.0",
|
"lucide-react": "^1.8.0",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const HomeClient = ({ initialFeatured, initialPosts, initialCategories }: Props)
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { user, settings } = useUser();
|
const { user, settings } = useUser();
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [locationTerm, setLocationTerm] = useState('');
|
||||||
const [featuredBusinesses, setFeaturedBusinesses] = useState<Business[]>(initialFeatured);
|
const [featuredBusinesses, setFeaturedBusinesses] = useState<Business[]>(initialFeatured);
|
||||||
const [posts, setPosts] = useState<BlogPost[]>(initialPosts);
|
const [posts, setPosts] = useState<BlogPost[]>(initialPosts);
|
||||||
const [categories, setCategories] = useState<any[]>(initialCategories);
|
const [categories, setCategories] = useState<any[]>(initialCategories);
|
||||||
@@ -27,7 +28,10 @@ const HomeClient = ({ initialFeatured, initialPosts, initialCategories }: Props)
|
|||||||
|
|
||||||
const handleSearch = (e: React.FormEvent) => {
|
const handleSearch = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
router.push(`/annuaire?q=${searchTerm}`);
|
const params = new URLSearchParams();
|
||||||
|
if (searchTerm) params.append('q', searchTerm);
|
||||||
|
if (locationTerm) params.append('location', locationTerm);
|
||||||
|
router.push(`/annuaire?${params.toString()}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -59,7 +63,13 @@ const HomeClient = ({ initialFeatured, initialPosts, initialCategories }: Props)
|
|||||||
</div>
|
</div>
|
||||||
<div className="md:w-1/3 relative border-t md:border-t-0 md:border-l border-gray-200">
|
<div className="md:w-1/3 relative border-t md:border-t-0 md:border-l border-gray-200">
|
||||||
<MapPin className="absolute left-3 top-3 text-gray-400 w-5 h-5" />
|
<MapPin className="absolute left-3 top-3 text-gray-400 w-5 h-5" />
|
||||||
<input type="text" placeholder="Localisation (ex: Abidjan)" className="w-full pl-10 pr-4 py-3 rounded-md focus:outline-none text-gray-900" />
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Localisation (ex: Abidjan)"
|
||||||
|
className="w-full pl-10 pr-4 py-3 rounded-md focus:outline-none text-gray-900"
|
||||||
|
value={locationTerm}
|
||||||
|
onChange={(e) => setLocationTerm(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" className="bg-brand-600 text-white px-8 py-3 rounded-md font-semibold hover:bg-brand-700 transition-colors">
|
<button type="submit" className="bg-brand-600 text-white px-8 py-3 rounded-md font-semibold hover:bg-brand-700 transition-colors">
|
||||||
Rechercher
|
Rechercher
|
||||||
|
|||||||
28
app/actions/upload.ts
Normal file
28
app/actions/upload.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
export async function uploadImage(formData: FormData) {
|
||||||
|
try {
|
||||||
|
const file = formData.get('file') as File;
|
||||||
|
if (!file) {
|
||||||
|
return { success: false, error: 'Aucun fichier fourni' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert file to buffer
|
||||||
|
const bytes = await file.arrayBuffer();
|
||||||
|
const buffer = Buffer.from(bytes);
|
||||||
|
|
||||||
|
// Convert to Base64
|
||||||
|
const base64 = buffer.toString('base64');
|
||||||
|
const mimeType = file.type || 'image/jpeg';
|
||||||
|
const dataUrl = `data:${mimeType};base64,${base64}`;
|
||||||
|
|
||||||
|
// Return the data URL directly (it will be saved in the database)
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
url: dataUrl
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during image conversion:', error);
|
||||||
|
return { success: false, error: 'Erreur lors de la conversion de l\'image' };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -439,9 +439,206 @@ const BusinessDetailClient = ({ initialBusiness }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Avis et Commentaires */}
|
||||||
|
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6 md:p-8">
|
||||||
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4">
|
||||||
|
<h2 className="text-xl font-bold font-serif text-gray-900 flex items-center">
|
||||||
|
<Star className="w-5 h-5 mr-2 text-yellow-400 fill-current" /> Avis et Commentaires
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{!isOwner && (
|
||||||
|
<div className="flex flex-col items-end gap-2">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{[1, 2, 3, 4, 5].map((star) => (
|
||||||
|
<button
|
||||||
|
key={star}
|
||||||
|
onMouseEnter={() => setHoverRating(star)}
|
||||||
|
onMouseLeave={() => setHoverRating(null)}
|
||||||
|
onClick={() => handleRate(star)}
|
||||||
|
className="transition-transform active:scale-125 focus:outline-none"
|
||||||
|
>
|
||||||
|
<Star
|
||||||
|
className={`w-8 h-8 ${star <= (hoverRating || userRating || 0) ? 'text-yellow-400 fill-current' : 'text-gray-200'}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-gray-500 font-medium italic">
|
||||||
|
{userRating ? "Merci pour votre note !" : "Cliquez sur une étoile pour voter"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!isOwner && (
|
||||||
|
<div className="mb-10 bg-gray-50 rounded-xl p-5 border border-gray-100">
|
||||||
|
<p className="text-sm font-bold text-gray-700 mb-3">Laissez un commentaire</p>
|
||||||
|
<div className="relative">
|
||||||
|
<textarea
|
||||||
|
value={reviewComment}
|
||||||
|
onChange={(e) => setReviewComment(e.target.value)}
|
||||||
|
placeholder="Que pensez-vous de cette entreprise ? (Qualité, service...)"
|
||||||
|
className="w-full bg-white border border-gray-200 rounded-lg p-4 text-sm focus:ring-2 focus:ring-brand-500 outline-none h-24 resize-none shadow-sm"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => handleRate(userRating || 5)}
|
||||||
|
disabled={isRating}
|
||||||
|
className="absolute bottom-3 right-3 bg-brand-600 text-white px-4 py-1.5 rounded-md text-xs font-bold hover:bg-brand-700 transition-all flex items-center gap-2 shadow-md"
|
||||||
|
>
|
||||||
|
{isRating ? <Loader2 className="w-3 h-3 animate-spin" /> : "Publier"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
{loadingRatings ? (
|
||||||
|
<div className="flex justify-center py-10">
|
||||||
|
<Loader2 className="w-8 h-8 text-brand-600 animate-spin" />
|
||||||
|
</div>
|
||||||
|
) : ratings.length > 0 ? (
|
||||||
|
ratings.map((rating) => (
|
||||||
|
<div key={rating.id} className="border-b border-gray-50 last:border-0 pb-6">
|
||||||
|
<div className="flex justify-between items-start mb-3">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-brand-50 flex items-center justify-center text-brand-700 font-bold text-sm">
|
||||||
|
{(rating as any).user?.name?.charAt(0) || 'U'}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="font-bold text-gray-900 text-sm">{(rating as any).user?.name}</p>
|
||||||
|
<div className="flex items-center">
|
||||||
|
{[1, 2, 3, 4, 5].map((star) => (
|
||||||
|
<Star
|
||||||
|
key={star}
|
||||||
|
className={`w-3 h-3 ${star <= rating.value ? 'text-yellow-400 fill-current' : 'text-gray-200'}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<span className="text-[10px] text-gray-400 ml-2 font-medium">
|
||||||
|
{new Date(rating.createdAt).toLocaleDateString('fr-FR')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-600 text-sm leading-relaxed italic ml-13">
|
||||||
|
<Quote className="w-3 h-3 text-brand-200 inline mr-1" />
|
||||||
|
{rating.comment || "Pas de commentaire"}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Reply section for owner */}
|
||||||
|
{isOwner && !rating.reply && (
|
||||||
|
<div className="mt-4 ml-13">
|
||||||
|
{replyingTo === rating.id ? (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<textarea
|
||||||
|
value={replyText}
|
||||||
|
onChange={(e) => setReplyText(e.target.value)}
|
||||||
|
placeholder="Votre réponse..."
|
||||||
|
className="w-full bg-gray-50 border border-gray-200 rounded-lg p-3 text-sm outline-none focus:ring-1 focus:ring-brand-500 h-20"
|
||||||
|
/>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button onClick={() => handleReply(rating.id)} disabled={isSubmittingReply} className="bg-brand-600 text-white px-3 py-1.5 rounded text-xs font-bold">Répondre</button>
|
||||||
|
<button onClick={() => setReplyingTo(null)} className="text-gray-500 text-xs">Annuler</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<button onClick={() => setReplyingTo(rating.id)} className="text-brand-600 text-xs font-bold hover:underline">Répondre au client</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{rating.reply && (
|
||||||
|
<div className="mt-4 ml-13 bg-brand-50/50 p-4 rounded-lg border-l-2 border-brand-300">
|
||||||
|
<div className="flex items-center gap-2 mb-1">
|
||||||
|
<MessageCircle className="w-3 h-3 text-brand-600" />
|
||||||
|
<span className="text-[10px] font-bold text-brand-700 uppercase">Réponse de {business.name}</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-700 text-sm italic">{rating.reply}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-10 bg-gray-50 rounded-xl border border-dashed border-gray-200">
|
||||||
|
<Star className="w-10 h-10 text-gray-200 mx-auto mb-2" />
|
||||||
|
<p className="text-gray-500 text-sm">Soyez le premier à donner votre avis !</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
|
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6 md:p-8">
|
||||||
|
<h2 className="text-xl font-bold font-serif text-gray-900 mb-6">Coordonnées</h2>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{business.websiteUrl && (
|
||||||
|
<a href={business.websiteUrl.startsWith('http') ? business.websiteUrl : `https://${business.websiteUrl}`} target="_blank" rel="noopener noreferrer" className="flex items-center text-gray-600 hover:text-brand-600 transition-colors group">
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-gray-50 flex items-center justify-center mr-3 group-hover:bg-brand-50">
|
||||||
|
<Globe className="w-5 h-5 text-brand-600" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="text-xs text-gray-400 uppercase font-bold tracking-wider">Site Web</p>
|
||||||
|
<p className="text-sm font-medium truncate">{business.websiteUrl.replace(/^https?:\/\//, '')}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{business.showEmail && business.contactEmail && (
|
||||||
|
<a href={`mailto:${business.contactEmail}`} className="flex items-center text-gray-600 hover:text-brand-600 transition-colors group">
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-gray-50 flex items-center justify-center mr-3 group-hover:bg-brand-50">
|
||||||
|
<Mail className="w-5 h-5 text-brand-600" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="text-xs text-gray-400 uppercase font-bold tracking-wider">Email</p>
|
||||||
|
<p className="text-sm font-medium truncate">{business.contactEmail}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{business.showPhone && business.contactPhone && (
|
||||||
|
<a href={`tel:${business.contactPhone}`} className="flex items-center text-gray-600 hover:text-brand-600 transition-colors group">
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-gray-50 flex items-center justify-center mr-3 group-hover:bg-brand-50">
|
||||||
|
<Phone className="w-5 h-5 text-brand-600" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="text-xs text-gray-400 uppercase font-bold tracking-wider">Téléphone</p>
|
||||||
|
<p className="text-sm font-medium truncate">{business.contactPhone}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{business.showSocials && business.socialLinks && (
|
||||||
|
<div className="pt-4 border-t border-gray-50">
|
||||||
|
<p className="text-xs text-gray-400 uppercase font-bold tracking-wider mb-3">Réseaux Sociaux</p>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{business.socialLinks.facebook && (
|
||||||
|
<a href={business.socialLinks.facebook} target="_blank" rel="noopener noreferrer" className="w-10 h-10 rounded-lg bg-gray-50 flex items-center justify-center text-gray-400 hover:text-blue-600 hover:bg-blue-50 transition-all">
|
||||||
|
<Facebook className="w-5 h-5" />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{business.socialLinks.linkedin && (
|
||||||
|
<a href={business.socialLinks.linkedin} target="_blank" rel="noopener noreferrer" className="w-10 h-10 rounded-lg bg-gray-50 flex items-center justify-center text-gray-400 hover:text-blue-700 hover:bg-blue-50 transition-all">
|
||||||
|
<Linkedin className="w-5 h-5" />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{business.socialLinks.instagram && (
|
||||||
|
<a href={business.socialLinks.instagram} target="_blank" rel="noopener noreferrer" className="w-10 h-10 rounded-lg bg-gray-50 flex items-center justify-center text-gray-400 hover:text-pink-600 hover:bg-pink-50 transition-all">
|
||||||
|
<Instagram className="w-5 h-5" />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{business.socialLinks.twitter && (
|
||||||
|
<a href={business.socialLinks.twitter} target="_blank" rel="noopener noreferrer" className="w-10 h-10 rounded-lg bg-gray-50 flex items-center justify-center text-gray-400 hover:text-black hover:bg-gray-100 transition-all">
|
||||||
|
<Twitter className="w-5 h-5" />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="contact-form" ref={contactRef} className="bg-white rounded-xl shadow-sm border border-gray-100 p-6 md:p-8">
|
<div id="contact-form" ref={contactRef} className="bg-white rounded-xl shadow-sm border border-gray-100 p-6 md:p-8">
|
||||||
<h2 className="text-xl font-bold font-serif text-gray-900 mb-6">Contacter l'entreprise</h2>
|
<h2 className="text-xl font-bold font-serif text-gray-900 mb-6">Contacter l'entreprise</h2>
|
||||||
<form onSubmit={handleContactSubmit} className="space-y-4">
|
<form onSubmit={handleContactSubmit} className="space-y-4">
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const AnnuairePageContent = () => {
|
|||||||
const [filterCategory, setFilterCategory] = useState('All');
|
const [filterCategory, setFilterCategory] = useState('All');
|
||||||
const [filterCountry, setFilterCountry] = useState('All');
|
const [filterCountry, setFilterCountry] = useState('All');
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [locationQuery, setLocationQuery] = useState('');
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
// 1. Fetch Metadata (Countries & Categories)
|
// 1. Fetch Metadata (Countries & Categories)
|
||||||
@@ -69,6 +70,7 @@ const AnnuairePageContent = () => {
|
|||||||
if (filterCategory !== 'All') params.append('category', filterCategory);
|
if (filterCategory !== 'All') params.append('category', filterCategory);
|
||||||
if (filterCountry !== 'All') params.append('countryId', filterCountry);
|
if (filterCountry !== 'All') params.append('countryId', filterCountry);
|
||||||
if (searchQuery) params.append('q', searchQuery);
|
if (searchQuery) params.append('q', searchQuery);
|
||||||
|
if (locationQuery) params.append('location', locationQuery);
|
||||||
|
|
||||||
const res = await fetch(`/api/businesses?${params.toString()}`, {
|
const res = await fetch(`/api/businesses?${params.toString()}`, {
|
||||||
headers: user ? { 'x-user-id': user.id } : {}
|
headers: user ? { 'x-user-id': user.id } : {}
|
||||||
@@ -86,12 +88,15 @@ const AnnuairePageContent = () => {
|
|||||||
|
|
||||||
const timeoutId = setTimeout(fetchBusinesses, 300); // Debounce
|
const timeoutId = setTimeout(fetchBusinesses, 300); // Debounce
|
||||||
return () => clearTimeout(timeoutId);
|
return () => clearTimeout(timeoutId);
|
||||||
}, [filterCategory, searchQuery, filterCountry]);
|
}, [filterCategory, searchQuery, filterCountry, locationQuery]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const q = searchParams.get('q');
|
const q = searchParams.get('q');
|
||||||
if (q) setSearchQuery(q);
|
if (q) setSearchQuery(q);
|
||||||
|
|
||||||
|
const loc = searchParams.get('location');
|
||||||
|
if (loc) setLocationQuery(loc);
|
||||||
|
|
||||||
const cat = searchParams.get('category');
|
const cat = searchParams.get('category');
|
||||||
if (cat) setFilterCategory(cat);
|
if (cat) setFilterCategory(cat);
|
||||||
|
|
||||||
@@ -124,6 +129,17 @@ const AnnuairePageContent = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-6">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Ville / Localisation</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={locationQuery}
|
||||||
|
onChange={(e) => setLocationQuery(e.target.value)}
|
||||||
|
className="w-full border-gray-300 rounded-md shadow-sm focus:ring-brand-500 focus:border-brand-500 sm:text-sm p-2 border"
|
||||||
|
placeholder="Ex: Abidjan, Dakar..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">Pays</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">Pays</label>
|
||||||
<select
|
<select
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
const category = searchParams.get('category')
|
const category = searchParams.get('category')
|
||||||
const q = searchParams.get('q')?.toLowerCase()
|
const q = searchParams.get('q')?.toLowerCase()
|
||||||
|
const location = searchParams.get('location')?.toLowerCase()
|
||||||
const featured = searchParams.get('featured')
|
const featured = searchParams.get('featured')
|
||||||
const countryId = searchParams.get('countryId')
|
const countryId = searchParams.get('countryId')
|
||||||
|
|
||||||
@@ -19,11 +20,12 @@ export async function GET(request: NextRequest) {
|
|||||||
isSuspended: false
|
isSuspended: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (category && category !== 'All') {
|
|
||||||
// Robust check: IDs are usually alphanumeric and long, without spaces or special chars
|
|
||||||
// Names like "Agriculture & Agrobusiness" have spaces or special chars.
|
|
||||||
const isId = /^[a-z0-9-]+$/.test(category) && category.length > 20;
|
|
||||||
|
|
||||||
|
// Build the conditions array
|
||||||
|
const conditions: any[] = [];
|
||||||
|
|
||||||
|
if (category && category !== 'All') {
|
||||||
|
const isId = /^[a-z0-9-]+$/.test(category) && category.length > 20;
|
||||||
if (isId) {
|
if (isId) {
|
||||||
where.categoryId = category
|
where.categoryId = category
|
||||||
} else {
|
} else {
|
||||||
@@ -32,13 +34,30 @@ export async function GET(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
if (featured === 'true') where.isFeatured = true
|
if (featured === 'true') where.isFeatured = true
|
||||||
if (countryId) where.countryId = countryId
|
if (countryId) where.countryId = countryId
|
||||||
|
|
||||||
if (q) {
|
if (q) {
|
||||||
where.OR = [
|
conditions.push({
|
||||||
|
OR: [
|
||||||
{ name: { contains: q, mode: 'insensitive' } },
|
{ name: { contains: q, mode: 'insensitive' } },
|
||||||
{ description: { contains: q, mode: 'insensitive' } },
|
{ description: { contains: q, mode: 'insensitive' } },
|
||||||
// Simplified tags check
|
{ city: { contains: q, mode: 'insensitive' } },
|
||||||
|
{ location: { contains: q, mode: 'insensitive' } },
|
||||||
{ tags: { has: q } }
|
{ tags: { has: q } }
|
||||||
]
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (location) {
|
||||||
|
conditions.push({
|
||||||
|
OR: [
|
||||||
|
{ city: { contains: location, mode: 'insensitive' } },
|
||||||
|
{ location: { contains: location, mode: 'insensitive' } }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conditions.length > 0) {
|
||||||
|
where.AND = conditions;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbBusinesses = await prisma.business.findMany({
|
const dbBusinesses = await prisma.business.findMany({
|
||||||
|
|||||||
@@ -279,10 +279,12 @@ const DashboardContent = () => {
|
|||||||
{currentView === 'subscription' && 'Mon Abonnement'}
|
{currentView === 'subscription' && 'Mon Abonnement'}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<Link href={displayBusiness.id === 'new' ? '/annuaire' : `/annuaire/${displayBusiness.slug || displayBusiness.id}`} className="hidden sm:inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none">
|
{business?.isActive && (
|
||||||
|
<Link href={`/annuaire/${business.slug || business.id}`} className="hidden sm:inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none transition-all hover:scale-105 active:scale-95">
|
||||||
<Eye className="-ml-1 mr-2 h-4 w-4 text-gray-500" />
|
<Eye className="-ml-1 mr-2 h-4 w-4 text-gray-500" />
|
||||||
Voir ma fiche
|
Voir ma fiche
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic';
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
export default function GlobalError({
|
|
||||||
error,
|
|
||||||
reset,
|
|
||||||
}: {
|
|
||||||
error: Error & { digest?: string };
|
|
||||||
reset: () => void;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<html lang="fr">
|
|
||||||
<head>
|
|
||||||
<meta charSet="utf-8" />
|
|
||||||
<title>Erreur Système - Afrohub</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div style={{ textAlign: 'center', padding: '50px', fontFamily: 'sans-serif' }}>
|
|
||||||
<h2>Erreur critique du serveur</h2>
|
|
||||||
<p style={{ color: '#666', marginBottom: '20px' }}>Une erreur inattendue s'est produite.</p>
|
|
||||||
<button
|
|
||||||
onClick={() => reset()}
|
|
||||||
style={{ padding: '10px 20px', cursor: 'pointer', backgroundColor: '#ef4444', color: 'white', border: 'none', borderRadius: '8px', fontWeight: 'bold' }}
|
|
||||||
>
|
|
||||||
Recharger l'application
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -27,9 +27,10 @@ body {
|
|||||||
|
|
||||||
.prose {
|
.prose {
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
hyphens: auto;
|
hyphens: none;
|
||||||
word-break: normal;
|
word-break: normal;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: normal;
|
||||||
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose p {
|
.prose p {
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { DynamicProviders } from '../components/DynamicProviders';
|
import Script from 'next/script';
|
||||||
import Navbar from '../components/Navbar';
|
import Navbar from '../components/Navbar';
|
||||||
import Footer from '../components/Footer';
|
import Footer from '../components/Footer';
|
||||||
import { getSiteSettings } from '../lib/settings';
|
import { UserProvider } from '../components/UserProvider';
|
||||||
|
import AnalyticsTracker from '../components/AnalyticsTracker';
|
||||||
|
import CookieBanner from '../components/CookieBanner';
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
|
import { Toaster } from 'react-hot-toast';
|
||||||
|
import { getSiteSettings } from '../lib/settings';
|
||||||
import { Inter, Playfair_Display } from 'next/font/google';
|
import { Inter, Playfair_Display } from 'next/font/google';
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
|
|
||||||
// 2. LA MAGIE EST ICI : On utilise un wrapper Client pour le no-SSR
|
|
||||||
|
|
||||||
|
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
subsets: ['latin'],
|
subsets: ['latin'],
|
||||||
variable: '--font-inter',
|
variable: '--font-inter',
|
||||||
@@ -22,7 +23,7 @@ const playfair = Playfair_Display({
|
|||||||
display: 'swap',
|
display: 'swap',
|
||||||
});
|
});
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'; // Restauration du nom standard
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
const settings = await getSiteSettings();
|
const settings = await getSiteSettings();
|
||||||
@@ -33,36 +34,42 @@ export async function generateMetadata(): Promise<Metadata> {
|
|||||||
alternates: {
|
alternates: {
|
||||||
canonical: '/',
|
canonical: '/',
|
||||||
},
|
},
|
||||||
icons: {
|
|
||||||
icon: '/favicon.png',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDev = process.env.NEXT_PUBLIC_APP_ENV === 'development';
|
|
||||||
const devBrandOverride: React.CSSProperties | undefined = isDev ? {
|
|
||||||
'--color-brand-50': '#fef2f2',
|
|
||||||
'--color-brand-100': '#fee2e2',
|
|
||||||
'--color-brand-200': '#fecaca',
|
|
||||||
'--color-brand-500': '#ef4444',
|
|
||||||
'--color-brand-600': '#dc2626',
|
|
||||||
'--color-brand-700': '#b91c1c',
|
|
||||||
'--color-brand-900': '#7f1d1d',
|
|
||||||
} as React.CSSProperties : undefined;
|
|
||||||
|
|
||||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
const settings = await getSiteSettings();
|
const settings = await getSiteSettings();
|
||||||
|
const isDev = process.env.NEXT_PUBLIC_APP_ENV === 'development';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="fr" className={`${inter.variable} ${playfair.variable}`} style={devBrandOverride} suppressHydrationWarning>
|
<html lang="fr" className={`${inter.variable} ${playfair.variable}`} suppressHydrationWarning>
|
||||||
|
<head>
|
||||||
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||||
|
{isDev && (
|
||||||
|
<style dangerouslySetInnerHTML={{ __html: `
|
||||||
|
:root {
|
||||||
|
--color-brand-50: #fef2f2 !important;
|
||||||
|
--color-brand-100: #fee2e2 !important;
|
||||||
|
--color-brand-200: #fecaca !important;
|
||||||
|
--color-brand-500: #ef4444 !important;
|
||||||
|
--color-brand-600: #dc2626 !important;
|
||||||
|
--color-brand-700: #b91c1c !important;
|
||||||
|
--color-brand-900: #7f1d1d !important;
|
||||||
|
}
|
||||||
|
`}} />
|
||||||
|
)}
|
||||||
|
</head>
|
||||||
<body className="bg-gray-50 text-gray-900 antialiased min-h-screen flex flex-col font-sans" suppressHydrationWarning>
|
<body className="bg-gray-50 text-gray-900 antialiased min-h-screen flex flex-col font-sans" suppressHydrationWarning>
|
||||||
<DynamicProviders>
|
<UserProvider>
|
||||||
|
<Toaster position="top-right" />
|
||||||
|
<AnalyticsTracker />
|
||||||
<Navbar settings={settings} />
|
<Navbar settings={settings} />
|
||||||
<main className="flex-grow">
|
<main className="flex-grow">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
<Footer settings={settings} />
|
<Footer settings={settings} />
|
||||||
</DynamicProviders>
|
<CookieBanner />
|
||||||
|
</UserProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ const Footer = ({ settings }: FooterProps) => {
|
|||||||
<li><Link href="/annuaire" className="hover:text-brand-500">Rechercher une entreprise</Link></li>
|
<li><Link href="/annuaire" className="hover:text-brand-500">Rechercher une entreprise</Link></li>
|
||||||
<li><Link href="/login" className="hover:text-brand-500">Inscrire mon entreprise</Link></li>
|
<li><Link href="/login" className="hover:text-brand-500">Inscrire mon entreprise</Link></li>
|
||||||
<li><Link href="/blog" className="hover:text-brand-500">Actualités & Conseils</Link></li>
|
<li><Link href="/blog" className="hover:text-brand-500">Actualités & Conseils</Link></li>
|
||||||
|
<li><Link href="/subscription" className="hover:text-brand-500">Tarifs</Link></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ interface Props {
|
|||||||
label?: string;
|
label?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
suggestions?: string[];
|
suggestions?: string[];
|
||||||
|
onlySuggestions?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TagInput({ value, onChange, label, placeholder, suggestions = [] }: Props) {
|
export default function TagInput({ value, onChange, label, placeholder, suggestions = [], onlySuggestions = false }: Props) {
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
const [filteredSuggestions, setFilteredSuggestions] = useState<string[]>([]);
|
const [filteredSuggestions, setFilteredSuggestions] = useState<string[]>([]);
|
||||||
const [showSuggestions, setShowSuggestions] = useState(false);
|
const [showSuggestions, setShowSuggestions] = useState(false);
|
||||||
@@ -52,7 +53,15 @@ export default function TagInput({ value, onChange, label, placeholder, suggesti
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const addTag = (tagToAdd?: string) => {
|
const addTag = (tagToAdd?: string) => {
|
||||||
|
if (onlySuggestions && !tagToAdd) return;
|
||||||
|
|
||||||
const tag = (tagToAdd || inputValue).trim().toLowerCase();
|
const tag = (tagToAdd || inputValue).trim().toLowerCase();
|
||||||
|
|
||||||
|
// If restricted, check if tag exists in suggestions
|
||||||
|
if (onlySuggestions && tagToAdd && !suggestions.some(s => s.toLowerCase() === tag)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (tag && !value.includes(tag)) {
|
if (tag && !value.includes(tag)) {
|
||||||
onChange([...value, tag]);
|
onChange([...value, tag]);
|
||||||
}
|
}
|
||||||
@@ -69,10 +78,10 @@ export default function TagInput({ value, onChange, label, placeholder, suggesti
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (selectedIndex >= 0 && filteredSuggestions[selectedIndex]) {
|
if (selectedIndex >= 0 && filteredSuggestions[selectedIndex]) {
|
||||||
addTag(filteredSuggestions[selectedIndex]);
|
addTag(filteredSuggestions[selectedIndex]);
|
||||||
} else {
|
} else if (!onlySuggestions) {
|
||||||
addTag();
|
addTag();
|
||||||
}
|
}
|
||||||
} else if (e.key === ',' || e.key === ';') {
|
} else if ((e.key === ',' || e.key === ';') && !onlySuggestions) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
addTag();
|
addTag();
|
||||||
} else if (e.key === 'ArrowDown') {
|
} else if (e.key === 'ArrowDown') {
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Image as ImageIcon, Sparkles, Youtube, X, Globe, Facebook, Linkedin, Instagram, CheckCircle, AlertCircle, Hash } from 'lucide-react';
|
import { Image as ImageIcon, Sparkles, Youtube, X, Globe, Facebook, Linkedin, Instagram, Twitter, CheckCircle, AlertCircle, Hash } from 'lucide-react';
|
||||||
import { Business, Country } from '../../types';
|
import { Business, Country } from '../../types';
|
||||||
import TagInput from '../TagInput';
|
import TagInput from '../TagInput';
|
||||||
import { generateBusinessDescription } from '../../lib/geminiService';
|
import { generateBusinessDescription } from '../../lib/geminiService';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import { useUser } from '../UserProvider';
|
import { useUser } from '../UserProvider';
|
||||||
|
import { uploadImage } from '../../app/actions/upload';
|
||||||
|
import { Loader2, Upload, Link as LinkIcon } from 'lucide-react';
|
||||||
|
|
||||||
// Helper to extract youtube ID
|
// Helper to extract youtube ID
|
||||||
const getYouTubeId = (url: string) => {
|
const getYouTubeId = (url: string) => {
|
||||||
@@ -40,6 +42,7 @@ const normalizeBusinessData = (b: Business): Business => ({
|
|||||||
facebook: b.socialLinks?.facebook || '',
|
facebook: b.socialLinks?.facebook || '',
|
||||||
linkedin: b.socialLinks?.linkedin || '',
|
linkedin: b.socialLinks?.linkedin || '',
|
||||||
instagram: b.socialLinks?.instagram || '',
|
instagram: b.socialLinks?.instagram || '',
|
||||||
|
twitter: b.socialLinks?.twitter || '',
|
||||||
website: b.socialLinks?.website || '',
|
website: b.socialLinks?.website || '',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -54,6 +57,12 @@ const DashboardProfile = ({ business, setBusiness }: { business: Business, setBu
|
|||||||
const [isGenerating, setIsGenerating] = useState(false);
|
const [isGenerating, setIsGenerating] = useState(false);
|
||||||
const [isMounted, setIsMounted] = useState(false);
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
const [allTags, setAllTags] = useState<string[]>([]);
|
const [allTags, setAllTags] = useState<string[]>([]);
|
||||||
|
const [isUploadingLogo, setIsUploadingLogo] = useState(false);
|
||||||
|
const [isUploadingCover, setIsUploadingCover] = useState(false);
|
||||||
|
const [logoInputMode, setLogoInputMode] = useState<'url' | 'upload'>('upload');
|
||||||
|
const [coverInputMode, setCoverInputMode] = useState<'url' | 'upload'>('upload');
|
||||||
|
const logoFileInputRef = React.useRef<HTMLInputElement>(null);
|
||||||
|
const coverFileInputRef = React.useRef<HTMLInputElement>(null);
|
||||||
// Fetch countries, categories and tags
|
// Fetch countries, categories and tags
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
@@ -112,6 +121,74 @@ const DashboardProfile = ({ business, setBusiness }: { business: Business, setBu
|
|||||||
setIsGenerating(false);
|
setIsGenerating(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLogoUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
if (!file.type.startsWith('image/')) {
|
||||||
|
toast.error("Le fichier doit être une image");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.size > 5 * 1024 * 1024) {
|
||||||
|
toast.error("L'image ne doit pas dépasser 5 Mo");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsUploadingLogo(true);
|
||||||
|
const uploadFormData = new FormData();
|
||||||
|
uploadFormData.append('file', file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await uploadImage(uploadFormData);
|
||||||
|
if (result.success && result.url) {
|
||||||
|
setFormData({ ...formData, logoUrl: result.url });
|
||||||
|
toast.success("Logo mis à jour !");
|
||||||
|
} else {
|
||||||
|
toast.error(result.error || "Erreur lors de l'upload");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Erreur réseau lors de l'upload");
|
||||||
|
} finally {
|
||||||
|
setIsUploadingLogo(false);
|
||||||
|
if (logoFileInputRef.current) logoFileInputRef.current.value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCoverUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
if (!file.type.startsWith('image/')) {
|
||||||
|
toast.error("Le fichier doit être une image");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.size > 5 * 1024 * 1024) {
|
||||||
|
toast.error("L'image ne doit pas dépasser 5 Mo");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsUploadingCover(true);
|
||||||
|
const uploadFormData = new FormData();
|
||||||
|
uploadFormData.append('file', file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await uploadImage(uploadFormData);
|
||||||
|
if (result.success && result.url) {
|
||||||
|
setFormData({ ...formData, coverUrl: result.url });
|
||||||
|
toast.success("Bannière mise à jour !");
|
||||||
|
} else {
|
||||||
|
toast.error(result.error || "Erreur lors de l'upload");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Erreur réseau lors de l'upload");
|
||||||
|
} finally {
|
||||||
|
setIsUploadingCover(false);
|
||||||
|
if (coverFileInputRef.current) coverFileInputRef.current.value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/businesses', {
|
const res = await fetch('/api/businesses', {
|
||||||
@@ -233,19 +310,94 @@ const DashboardProfile = ({ business, setBusiness }: { business: Business, setBu
|
|||||||
{/* A. Bloc Identité Visuelle */}
|
{/* A. Bloc Identité Visuelle */}
|
||||||
<div className="bg-white shadow rounded-lg p-6">
|
<div className="bg-white shadow rounded-lg p-6">
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-4 flex items-center"><ImageIcon className="w-5 h-5 mr-2 text-brand-600" /> Identité Visuelle</h3>
|
<h3 className="text-lg font-medium text-gray-900 mb-4 flex items-center"><ImageIcon className="w-5 h-5 mr-2 text-brand-600" /> Identité Visuelle</h3>
|
||||||
<div className="flex items-center space-x-6">
|
<div className="flex flex-col md:flex-row md:items-center gap-6">
|
||||||
<div className="shrink-0 flex flex-col items-center gap-2">
|
<div className="shrink-0 flex flex-col items-center gap-2">
|
||||||
<img className="h-24 w-24 object-cover rounded-full border-2 border-gray-200" src={formData.logoUrl} alt="Logo actuel" />
|
<img
|
||||||
<span className="text-[10px] font-bold text-gray-400 uppercase">Logo</span>
|
className="h-24 w-24 object-cover rounded-full border-2 border-brand-200 shadow-sm"
|
||||||
|
src={formData.logoUrl || "https://picsum.photos/200/200?random=logo"}
|
||||||
|
alt="Logo actuel"
|
||||||
|
/>
|
||||||
|
<span className="text-[10px] font-bold text-gray-400 uppercase tracking-widest">Aperçu Logo</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 border-2 border-dashed border-gray-300 rounded-md p-6 flex flex-col items-center justify-center hover:border-brand-400 transition-colors cursor-pointer bg-gray-50">
|
|
||||||
<ImageIcon className="h-8 w-8 text-gray-400" />
|
<div className="flex-1 space-y-4">
|
||||||
<p className="mt-1 text-xs text-gray-500">Modifier le logo</p>
|
<div className="flex bg-gray-100 p-1 rounded-lg w-fit border border-gray-200">
|
||||||
|
<button
|
||||||
|
onClick={() => setLogoInputMode('upload')}
|
||||||
|
className={`px-3 py-1.5 rounded-md text-xs font-bold transition-all flex items-center gap-2 ${logoInputMode === 'upload' ? 'bg-white text-brand-600 shadow-sm' : 'text-gray-500 hover:text-gray-700'}`}
|
||||||
|
>
|
||||||
|
<Upload className="w-3.5 h-3.5" /> Upload
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setLogoInputMode('url')}
|
||||||
|
className={`px-3 py-1.5 rounded-md text-xs font-bold transition-all flex items-center gap-2 ${logoInputMode === 'url' ? 'bg-white text-brand-600 shadow-sm' : 'text-gray-500 hover:text-gray-700'}`}
|
||||||
|
>
|
||||||
|
<LinkIcon className="w-3.5 h-3.5" /> Lien URL
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{logoInputMode === 'upload' ? (
|
||||||
|
<div
|
||||||
|
onClick={() => logoFileInputRef.current?.click()}
|
||||||
|
className="group border-2 border-dashed border-gray-300 rounded-xl p-6 flex flex-col items-center justify-center hover:border-brand-400 hover:bg-brand-50 transition-all cursor-pointer relative overflow-hidden"
|
||||||
|
>
|
||||||
|
{isUploadingLogo ? (
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<Loader2 className="w-8 h-8 text-brand-600 animate-spin" />
|
||||||
|
<span className="text-xs font-bold text-brand-600">Upload en cours...</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center group-hover:bg-brand-100 group-hover:text-brand-600 transition-colors mb-2">
|
||||||
|
<Upload className="w-5 h-5 text-gray-400 group-hover:text-brand-600" />
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-bold text-gray-700">Cliquez pour changer le logo</p>
|
||||||
|
<p className="text-[10px] text-gray-400 mt-1">PNG, JPG ou WEBP (Max 5Mo)</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
ref={logoFileInputRef}
|
||||||
|
onChange={handleLogoUpload}
|
||||||
|
accept="image/*"
|
||||||
|
className="hidden"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="relative">
|
||||||
|
<LinkIcon className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="logoUrl"
|
||||||
|
value={formData.logoUrl || ''}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder="URL de votre logo (ex: https://...)"
|
||||||
|
className="block w-full border border-gray-300 rounded-lg py-2.5 pl-10 pr-3 focus:ring-brand-500 focus:border-brand-500 sm:text-sm shadow-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">Photo de Couverture (Bannière)</label>
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<label className="block text-sm font-medium text-gray-700">Photo de Couverture (Bannière)</label>
|
||||||
|
<div className="flex bg-gray-100 p-1 rounded-lg border border-gray-200">
|
||||||
|
<button
|
||||||
|
onClick={() => setCoverInputMode('upload')}
|
||||||
|
className={`px-3 py-1 rounded-md text-[10px] font-bold transition-all flex items-center gap-1.5 ${coverInputMode === 'upload' ? 'bg-white text-brand-600 shadow-sm' : 'text-gray-500 hover:text-gray-700'}`}
|
||||||
|
>
|
||||||
|
<Upload className="w-3 h-3" /> Upload
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setCoverInputMode('url')}
|
||||||
|
className={`px-3 py-1 rounded-md text-[10px] font-bold transition-all flex items-center gap-1.5 ${coverInputMode === 'url' ? 'bg-white text-brand-600 shadow-sm' : 'text-gray-500 hover:text-gray-700'}`}
|
||||||
|
>
|
||||||
|
<LinkIcon className="w-3 h-3" /> URL
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="relative h-48 w-full rounded-lg overflow-hidden bg-gray-100 border-2 border-dashed border-gray-300 group transition-all">
|
<div className="relative h-48 w-full rounded-lg overflow-hidden bg-gray-100 border-2 border-dashed border-gray-300 group transition-all">
|
||||||
{formData.coverUrl ? (
|
{formData.coverUrl ? (
|
||||||
<img
|
<img
|
||||||
@@ -263,12 +415,33 @@ const DashboardProfile = ({ business, setBusiness }: { business: Business, setBu
|
|||||||
<span className="text-xs">Aucune bannière personnalisée</span>
|
<span className="text-xs">Aucune bannière personnalisée</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
|
|
||||||
<button className="bg-white text-gray-900 px-4 py-1.5 rounded-full text-xs font-bold shadow-lg">Changer la photo</button>
|
{isUploadingCover && (
|
||||||
|
<div className="absolute inset-0 bg-white/60 backdrop-blur-sm flex flex-col items-center justify-center gap-2 z-20">
|
||||||
|
<Loader2 className="w-8 h-8 text-brand-600 animate-spin" />
|
||||||
|
<span className="text-xs font-bold text-brand-600">Upload en cours...</span>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center z-10">
|
||||||
|
<button
|
||||||
|
onClick={() => coverFileInputRef.current?.click()}
|
||||||
|
className="bg-white text-gray-900 px-4 py-1.5 rounded-full text-xs font-bold shadow-lg flex items-center gap-2 hover:scale-105 transition-transform"
|
||||||
|
>
|
||||||
|
<Upload className="w-3.5 h-3.5" /> Changer la photo
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
ref={coverFileInputRef}
|
||||||
|
onChange={handleCoverUpload}
|
||||||
|
accept="image/*"
|
||||||
|
className="hidden"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4 bg-gray-50 p-4 rounded-lg border border-gray-200">
|
<div className="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4 bg-gray-50 p-4 rounded-lg border border-gray-200">
|
||||||
|
{/* ... (Zoom/Position controls stay same) */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-gray-500 uppercase mb-2">Zoom : {(formData.coverZoom || 1).toFixed(1)}x</label>
|
<label className="block text-xs font-bold text-gray-500 uppercase mb-2">Zoom : {(formData.coverZoom || 1).toFixed(1)}x</label>
|
||||||
<input
|
<input
|
||||||
@@ -307,14 +480,16 @@ const DashboardProfile = ({ business, setBusiness }: { business: Business, setBu
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{coverInputMode === 'url' && (
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="coverUrl"
|
name="coverUrl"
|
||||||
value={formData.coverUrl || ''}
|
value={formData.coverUrl || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
placeholder="URL de votre image de couverture (ex: https://...)"
|
placeholder="URL de votre image de couverture (ex: https://...)"
|
||||||
className="mt-4 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:ring-brand-500 focus:border-brand-500 sm:text-sm"
|
className="mt-4 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:ring-brand-500 focus:border-brand-500 sm:text-sm animate-in fade-in slide-in-from-top-1"
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
<p className="mt-1 text-[10px] text-gray-500 italic">Ajustez le zoom et la position pour un rendu optimal sur votre fiche.</p>
|
<p className="mt-1 text-[10px] text-gray-500 italic">Ajustez le zoom et la position pour un rendu optimal sur votre fiche.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6 mt-6">
|
<div className="grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6 mt-6">
|
||||||
@@ -389,8 +564,9 @@ const DashboardProfile = ({ business, setBusiness }: { business: Business, setBu
|
|||||||
value={formData.tags || []}
|
value={formData.tags || []}
|
||||||
onChange={(tags) => setFormData({ ...formData, tags })}
|
onChange={(tags) => setFormData({ ...formData, tags })}
|
||||||
suggestions={allTags}
|
suggestions={allTags}
|
||||||
|
onlySuggestions={true}
|
||||||
label="Mots-clés & Tags"
|
label="Mots-clés & Tags"
|
||||||
placeholder="innovation, tech, artisanat..."
|
placeholder="Choisir dans la liste..."
|
||||||
/>
|
/>
|
||||||
<p className="mt-1 text-[10px] text-gray-500 italic">Ces tags aideront les clients à trouver votre boutique via la recherche.</p>
|
<p className="mt-1 text-[10px] text-gray-500 italic">Ces tags aideront les clients à trouver votre boutique via la recherche.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -546,6 +722,12 @@ const DashboardProfile = ({ business, setBusiness }: { business: Business, setBu
|
|||||||
</span>
|
</span>
|
||||||
<input type="text" placeholder="Lien Instagram" value={formData.socialLinks?.instagram || ''} onChange={(e) => handleSocialChange('instagram', e.target.value)} className="flex-1 min-w-0 block w-full px-3 py-2 rounded-none rounded-r-md focus:ring-brand-500 focus:border-brand-500 sm:text-sm border-gray-300" />
|
<input type="text" placeholder="Lien Instagram" value={formData.socialLinks?.instagram || ''} onChange={(e) => handleSocialChange('instagram', e.target.value)} className="flex-1 min-w-0 block w-full px-3 py-2 rounded-none rounded-r-md focus:ring-brand-500 focus:border-brand-500 sm:text-sm border-gray-300" />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex rounded-md shadow-sm">
|
||||||
|
<span className="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500">
|
||||||
|
<Twitter className="w-4 h-4" />
|
||||||
|
</span>
|
||||||
|
<input type="text" placeholder="Lien Twitter (X)" value={formData.socialLinks?.twitter || ''} onChange={(e) => handleSocialChange('twitter', e.target.value)} className="flex-1 min-w-0 block w-full px-3 py-2 rounded-none rounded-r-md focus:ring-brand-500 focus:border-brand-500 sm:text-sm border-gray-300" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
import DOMPurify from 'isomorphic-dompurify';
|
import sanitizeHtml from 'sanitize-html';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitizes HTML content to prevent XSS attacks.
|
* Sanitizes HTML content to prevent XSS attacks.
|
||||||
* Use this before passing content to dangerouslySetInnerHTML.
|
* Use this before passing content to dangerouslySetInnerHTML.
|
||||||
*/
|
*/
|
||||||
export function sanitizeHTML(html: string): string {
|
export function sanitizeHTML(html: string): string {
|
||||||
return DOMPurify.sanitize(html, {
|
if (!html) return '';
|
||||||
USE_PROFILES: { html: true },
|
|
||||||
ALLOWED_TAGS: [
|
return sanitizeHtml(html, {
|
||||||
|
allowedTags: [
|
||||||
'p', 'br', 'b', 'i', 'em', 'strong', 'a', 'ul', 'ol', 'li',
|
'p', 'br', 'b', 'i', 'em', 'strong', 'a', 'ul', 'ol', 'li',
|
||||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'span', 'div',
|
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'span', 'div',
|
||||||
'img', 'table', 'thead', 'tbody', 'tr', 'th', 'td'
|
'img', 'table', 'thead', 'tbody', 'tr', 'th', 'td'
|
||||||
],
|
],
|
||||||
ALLOWED_ATTR: ['href', 'target', 'rel', 'src', 'alt', 'class', 'style']
|
allowedAttributes: {
|
||||||
|
'a': ['href', 'target', 'rel'],
|
||||||
|
'img': ['src', 'alt'],
|
||||||
|
'*': ['class', 'style']
|
||||||
|
},
|
||||||
|
selfClosing: ['img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta'],
|
||||||
|
// Standard URLs only
|
||||||
|
allowedSchemes: ['http', 'https', 'ftp', 'mailto', 'tel'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ const nextConfig = {
|
|||||||
turbopack: {
|
turbopack: {
|
||||||
root: __dirname,
|
root: __dirname,
|
||||||
},
|
},
|
||||||
|
experimental: {
|
||||||
|
serverActions: {
|
||||||
|
bodySizeLimit: '10mb',
|
||||||
|
},
|
||||||
|
},
|
||||||
async headers() {
|
async headers() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
658
package-lock.json
generated
658
package-lock.json
generated
@@ -15,13 +15,12 @@
|
|||||||
"@tailwindcss/postcss": "^4.2.2",
|
"@tailwindcss/postcss": "^4.2.2",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/dompurify": "^3.0.5",
|
|
||||||
"@types/node": "^22.14.0",
|
"@types/node": "^22.14.0",
|
||||||
"@types/pg": "^8.16.0",
|
"@types/pg": "^8.16.0",
|
||||||
|
"@types/sanitize-html": "^2.16.1",
|
||||||
"autoprefixer": "^10.4.27",
|
"autoprefixer": "^10.4.27",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"dotenv": "^17.3.1",
|
"dotenv": "^17.3.1",
|
||||||
"isomorphic-dompurify": "^3.11.0",
|
|
||||||
"jiti": "^2.6.1",
|
"jiti": "^2.6.1",
|
||||||
"lucide-react": "^0.554.0",
|
"lucide-react": "^0.554.0",
|
||||||
"next": "^16.1.6",
|
"next": "^16.1.6",
|
||||||
@@ -33,6 +32,7 @@
|
|||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-hot-toast": "^2.6.0",
|
"react-hot-toast": "^2.6.0",
|
||||||
"recharts": "^3.8.1",
|
"recharts": "^3.8.1",
|
||||||
|
"sanitize-html": "^2.17.3",
|
||||||
"tailwindcss": "^4.2.2",
|
"tailwindcss": "^4.2.2",
|
||||||
"tsx": "^4.21.0",
|
"tsx": "^4.21.0",
|
||||||
"typescript": "~5.8.2"
|
"typescript": "~5.8.2"
|
||||||
@@ -57,199 +57,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@asamuzakjp/css-color": {
|
|
||||||
"version": "5.1.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz",
|
|
||||||
"integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@asamuzakjp/generational-cache": "^1.0.1",
|
|
||||||
"@csstools/css-calc": "^3.2.0",
|
|
||||||
"@csstools/css-color-parser": "^4.1.0",
|
|
||||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
|
||||||
"@csstools/css-tokenizer": "^4.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@asamuzakjp/dom-selector": {
|
|
||||||
"version": "7.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz",
|
|
||||||
"integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@asamuzakjp/generational-cache": "^1.0.1",
|
|
||||||
"@asamuzakjp/nwsapi": "^2.3.9",
|
|
||||||
"bidi-js": "^1.0.3",
|
|
||||||
"css-tree": "^3.2.1",
|
|
||||||
"is-potential-custom-element-name": "^1.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@asamuzakjp/generational-cache": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@asamuzakjp/nwsapi": {
|
|
||||||
"version": "2.3.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
|
|
||||||
"integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@bramus/specificity": {
|
|
||||||
"version": "2.4.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
|
|
||||||
"integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"css-tree": "^3.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"specificity": "bin/cli.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@csstools/color-helpers": {
|
|
||||||
"version": "6.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",
|
|
||||||
"integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/csstools"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/csstools"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT-0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.19.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@csstools/css-calc": {
|
|
||||||
"version": "3.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz",
|
|
||||||
"integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/csstools"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/csstools"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.19.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
|
||||||
"@csstools/css-tokenizer": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@csstools/css-color-parser": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/csstools"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/csstools"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@csstools/color-helpers": "^6.0.2",
|
|
||||||
"@csstools/css-calc": "^3.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.19.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
|
||||||
"@csstools/css-tokenizer": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@csstools/css-parser-algorithms": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/csstools"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/csstools"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.19.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@csstools/css-tokenizer": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@csstools/css-syntax-patches-for-csstree": {
|
|
||||||
"version": "1.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz",
|
|
||||||
"integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/csstools"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/csstools"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT-0",
|
|
||||||
"peerDependencies": {
|
|
||||||
"css-tree": "^3.2.1"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"css-tree": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@csstools/css-tokenizer": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/csstools"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/csstools"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.19.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@emnapi/runtime": {
|
"node_modules/@emnapi/runtime": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||||
@@ -676,23 +483,6 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@exodus/bytes": {
|
|
||||||
"version": "1.15.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz",
|
|
||||||
"integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@noble/hashes": "^1.8.0 || ^2.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@noble/hashes": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@google/genai": {
|
"node_modules/@google/genai": {
|
||||||
"version": "1.50.1",
|
"version": "1.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.50.1.tgz",
|
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.50.1.tgz",
|
||||||
@@ -1918,15 +1708,6 @@
|
|||||||
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
|
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/dompurify": {
|
|
||||||
"version": "3.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz",
|
|
||||||
"integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/trusted-types": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.19.17",
|
"version": "22.19.17",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
|
||||||
@@ -1974,11 +1755,14 @@
|
|||||||
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
|
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/trusted-types": {
|
"node_modules/@types/sanitize-html": {
|
||||||
"version": "2.0.7",
|
"version": "2.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.16.1.tgz",
|
||||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
"integrity": "sha512-n9wjs8bCOTyN/ynwD8s/nTcTreIHB1vf31vhLMGqUPNHaweKC4/fAl4Dj+hUlCTKYgm4P3k83fmiFfzkZ6sgMA==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"htmlparser2": "^10.1"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/use-sync-external-store": {
|
"node_modules/@types/use-sync-external-store": {
|
||||||
"version": "0.0.6",
|
"version": "0.0.6",
|
||||||
@@ -2098,15 +1882,6 @@
|
|||||||
"bcrypt": "bin/bcrypt"
|
"bcrypt": "bin/bcrypt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bidi-js": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"require-from-string": "^2.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/bignumber.js": {
|
"node_modules/bignumber.js": {
|
||||||
"version": "9.3.1",
|
"version": "9.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
|
||||||
@@ -2359,19 +2134,6 @@
|
|||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/css-tree": {
|
|
||||||
"version": "3.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
|
|
||||||
"integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"mdn-data": "2.27.1",
|
|
||||||
"source-map-js": "^1.2.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cssesc": {
|
"node_modules/cssesc": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
@@ -2520,19 +2282,6 @@
|
|||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/data-urls": {
|
|
||||||
"version": "7.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
|
|
||||||
"integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"whatwg-mimetype": "^5.0.0",
|
|
||||||
"whatwg-url": "^16.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
@@ -2550,18 +2299,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/decimal.js": {
|
|
||||||
"version": "10.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
|
|
||||||
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/decimal.js-light": {
|
"node_modules/decimal.js-light": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||||
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
|
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/deepmerge": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/deepmerge-ts": {
|
"node_modules/deepmerge-ts": {
|
||||||
"version": "7.1.5",
|
"version": "7.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
|
||||||
@@ -2592,13 +2344,71 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dompurify": {
|
"node_modules/dom-serializer": {
|
||||||
"version": "3.4.2",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||||
"integrity": "sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==",
|
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
"license": "MIT",
|
||||||
"optionalDependencies": {
|
"dependencies": {
|
||||||
"@types/trusted-types": "^2.0.7"
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"entities": "^4.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dom-serializer/node_modules/entities": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domelementtype": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/domhandler": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domutils": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
@@ -2667,18 +2477,6 @@
|
|||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/entities": {
|
|
||||||
"version": "8.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
|
|
||||||
"integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
|
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.19.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/es-toolkit": {
|
"node_modules/es-toolkit": {
|
||||||
"version": "1.46.0",
|
"version": "1.46.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.0.tgz",
|
||||||
@@ -2739,6 +2537,18 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/escape-string-regexp": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eventemitter3": {
|
"node_modules/eventemitter3": {
|
||||||
"version": "5.0.4",
|
"version": "5.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
|
||||||
@@ -2959,16 +2769,35 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/html-encoding-sniffer": {
|
"node_modules/htmlparser2": {
|
||||||
"version": "6.0.0",
|
"version": "10.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
|
||||||
"integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
|
"integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@exodus/bytes": "^1.6.0"
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"domutils": "^3.2.2",
|
||||||
|
"entities": "^7.0.1"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/htmlparser2/node_modules/entities": {
|
||||||
|
"version": "7.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
||||||
|
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/https-proxy-agent": {
|
"node_modules/https-proxy-agent": {
|
||||||
@@ -3013,23 +2842,13 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-potential-custom-element-name": {
|
"node_modules/is-plain-object": {
|
||||||
"version": "1.0.1",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||||
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
|
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/isomorphic-dompurify": {
|
|
||||||
"version": "3.11.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-3.11.0.tgz",
|
|
||||||
"integrity": "sha512-il0sNhLnfawc6vKoMqnZX1JXzEzw0pP3DZWg7mM7VJNJ8cq6DCxeAjEcfjTazyBF8dkUn+rBsBPS5NQs4ZaI3g==",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"dompurify": "^3.4.1",
|
|
||||||
"jsdom": "^29.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20.19.0 || ^22.13.0 || >=24.0.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jiti": {
|
"node_modules/jiti": {
|
||||||
@@ -3041,46 +2860,6 @@
|
|||||||
"jiti": "lib/jiti-cli.mjs"
|
"jiti": "lib/jiti-cli.mjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jsdom": {
|
|
||||||
"version": "29.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz",
|
|
||||||
"integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@asamuzakjp/css-color": "^5.1.11",
|
|
||||||
"@asamuzakjp/dom-selector": "^7.1.1",
|
|
||||||
"@bramus/specificity": "^2.4.2",
|
|
||||||
"@csstools/css-syntax-patches-for-csstree": "^1.1.3",
|
|
||||||
"@exodus/bytes": "^1.15.0",
|
|
||||||
"css-tree": "^3.2.1",
|
|
||||||
"data-urls": "^7.0.0",
|
|
||||||
"decimal.js": "^10.6.0",
|
|
||||||
"html-encoding-sniffer": "^6.0.0",
|
|
||||||
"is-potential-custom-element-name": "^1.0.1",
|
|
||||||
"lru-cache": "^11.3.5",
|
|
||||||
"parse5": "^8.0.1",
|
|
||||||
"saxes": "^6.0.0",
|
|
||||||
"symbol-tree": "^3.2.4",
|
|
||||||
"tough-cookie": "^6.0.1",
|
|
||||||
"undici": "^7.25.0",
|
|
||||||
"w3c-xmlserializer": "^5.0.0",
|
|
||||||
"webidl-conversions": "^8.0.1",
|
|
||||||
"whatwg-mimetype": "^5.0.0",
|
|
||||||
"whatwg-url": "^16.0.1",
|
|
||||||
"xml-name-validator": "^5.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^20.19.0 || ^22.13.0 || >=24.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"canvas": "^3.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"canvas": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/json-bigint": {
|
"node_modules/json-bigint": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
|
||||||
@@ -3366,15 +3145,6 @@
|
|||||||
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/lru-cache": {
|
|
||||||
"version": "11.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz",
|
|
||||||
"integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==",
|
|
||||||
"license": "BlueOak-1.0.0",
|
|
||||||
"engines": {
|
|
||||||
"node": "20 || >=22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lucide-react": {
|
"node_modules/lucide-react": {
|
||||||
"version": "0.554.0",
|
"version": "0.554.0",
|
||||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.554.0.tgz",
|
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.554.0.tgz",
|
||||||
@@ -3393,12 +3163,6 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mdn-data": {
|
|
||||||
"version": "2.27.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
|
|
||||||
"integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
|
|
||||||
"license": "CC0-1.0"
|
|
||||||
},
|
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -3577,17 +3341,11 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/parse5": {
|
"node_modules/parse-srcset": {
|
||||||
"version": "8.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
|
||||||
"integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==",
|
"integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"dependencies": {
|
|
||||||
"entities": "^8.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/pathe": {
|
"node_modules/pathe": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
@@ -3851,15 +3609,6 @@
|
|||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/punycode": {
|
|
||||||
"version": "2.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
|
||||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pure-rand": {
|
"node_modules/pure-rand": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
|
||||||
@@ -4022,15 +3771,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/require-from-string": {
|
|
||||||
"version": "2.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
|
||||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/reselect": {
|
"node_modules/reselect": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||||
@@ -4085,16 +3825,18 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/saxes": {
|
"node_modules/sanitize-html": {
|
||||||
"version": "6.0.0",
|
"version": "2.17.3",
|
||||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.3.tgz",
|
||||||
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
|
"integrity": "sha512-Kn4srCAo2+wZyvCNKCSyB2g8RQ8IkX/gQs2uqoSRNu5t9I2qvUyAVvRDiFUVAiX3N3PNuwStY0eNr+ooBHVWEg==",
|
||||||
"license": "ISC",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"xmlchars": "^2.2.0"
|
"deepmerge": "^4.2.2",
|
||||||
},
|
"escape-string-regexp": "^4.0.0",
|
||||||
"engines": {
|
"htmlparser2": "^10.1.0",
|
||||||
"node": ">=v12.22.7"
|
"is-plain-object": "^5.0.0",
|
||||||
|
"parse-srcset": "^1.0.2",
|
||||||
|
"postcss": "^8.3.11"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/scheduler": {
|
"node_modules/scheduler": {
|
||||||
@@ -4259,12 +4001,6 @@
|
|||||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/symbol-tree": {
|
|
||||||
"version": "3.2.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
|
||||||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "4.2.4",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz",
|
||||||
@@ -4299,48 +4035,6 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tldts": {
|
|
||||||
"version": "7.0.29",
|
|
||||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.29.tgz",
|
|
||||||
"integrity": "sha512-JIXCerhudr/N6OWLwLF1HVsTTUo7ry6qHa5eWZEkiMuxsIiAACL55tGLfqfHfoH7QaMQUW8fngD7u7TxWexYQg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"tldts-core": "^7.0.29"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"tldts": "bin/cli.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tldts-core": {
|
|
||||||
"version": "7.0.29",
|
|
||||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.29.tgz",
|
|
||||||
"integrity": "sha512-W99NuU7b1DcG3uJ3v9k9VztCH3WialNbBkBft5wCs8V8mexu0XQqaZEYb9l9RNNzK8+3EJ9PKWB0/RUtTQ/o+Q==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/tough-cookie": {
|
|
||||||
"version": "6.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz",
|
|
||||||
"integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==",
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"dependencies": {
|
|
||||||
"tldts": "^7.0.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tr46": {
|
|
||||||
"version": "6.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
|
|
||||||
"integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"punycode": "^2.3.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tree-kill": {
|
"node_modules/tree-kill": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||||
@@ -4389,15 +4083,6 @@
|
|||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
|
||||||
"version": "7.25.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz",
|
|
||||||
"integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.18.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.21.0",
|
"version": "6.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
@@ -4471,18 +4156,6 @@
|
|||||||
"d3-timer": "^3.0.1"
|
"d3-timer": "^3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/w3c-xmlserializer": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"xml-name-validator": "^5.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/web-streams-polyfill": {
|
"node_modules/web-streams-polyfill": {
|
||||||
"version": "3.3.3",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||||
@@ -4492,38 +4165,6 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webidl-conversions": {
|
|
||||||
"version": "8.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
|
|
||||||
"integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
|
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/whatwg-mimetype": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/whatwg-url": {
|
|
||||||
"version": "16.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
|
|
||||||
"integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@exodus/bytes": "^1.11.0",
|
|
||||||
"tr46": "^6.0.0",
|
|
||||||
"webidl-conversions": "^8.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/wrap-ansi": {
|
"node_modules/wrap-ansi": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
@@ -4563,21 +4204,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/xml-name-validator": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/xmlchars": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
|
||||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/xtend": {
|
"node_modules/xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"dev": "next dev -p 3000",
|
"dev": "next dev -p 3000",
|
||||||
"dev:admin": "npm run dev --prefix admin -- -p 3001",
|
"dev:admin": "npm run dev --prefix admin -- -p 3001",
|
||||||
"all": "concurrently \"npm run dev\" \"npm run dev:admin\"",
|
"all": "concurrently \"npm run dev\" \"npm run dev:admin\"",
|
||||||
"build": "prisma generate && next build",
|
"build": "prisma generate && next build && npm run build:admin",
|
||||||
"build:admin": "npm install --prefix admin && prisma generate && npm run build --prefix admin",
|
"build:admin": "npm install --prefix admin && prisma generate && npm run build --prefix admin",
|
||||||
"start": "npx prisma migrate deploy && next start",
|
"start": "npx prisma migrate deploy && next start",
|
||||||
"start:admin": "npm run start --prefix admin",
|
"start:admin": "npm run start --prefix admin",
|
||||||
@@ -28,13 +28,12 @@
|
|||||||
"@tailwindcss/postcss": "^4.2.2",
|
"@tailwindcss/postcss": "^4.2.2",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/dompurify": "^3.0.5",
|
|
||||||
"@types/node": "^22.14.0",
|
"@types/node": "^22.14.0",
|
||||||
"@types/pg": "^8.16.0",
|
"@types/pg": "^8.16.0",
|
||||||
|
"@types/sanitize-html": "^2.16.1",
|
||||||
"autoprefixer": "^10.4.27",
|
"autoprefixer": "^10.4.27",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"dotenv": "^17.3.1",
|
"dotenv": "^17.3.1",
|
||||||
"isomorphic-dompurify": "^3.11.0",
|
|
||||||
"jiti": "^2.6.1",
|
"jiti": "^2.6.1",
|
||||||
"lucide-react": "^0.554.0",
|
"lucide-react": "^0.554.0",
|
||||||
"next": "^16.1.6",
|
"next": "^16.1.6",
|
||||||
@@ -46,6 +45,7 @@
|
|||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-hot-toast": "^2.6.0",
|
"react-hot-toast": "^2.6.0",
|
||||||
"recharts": "^3.8.1",
|
"recharts": "^3.8.1",
|
||||||
|
"sanitize-html": "^2.17.3",
|
||||||
"tailwindcss": "^4.2.2",
|
"tailwindcss": "^4.2.2",
|
||||||
"tsx": "^4.21.0",
|
"tsx": "^4.21.0",
|
||||||
"typescript": "~5.8.2"
|
"typescript": "~5.8.2"
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 34 KiB |
Reference in New Issue
Block a user