Integrate Cloudinary for cloud-based image and video storage

Implement Cloudinary integration for uploading, storing, and transforming media files, including a new settings section for user configuration and API endpoints for media management.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: ae4037a0-2a6f-4530-9bac-79b543286bda
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/397bca8c-984f-43ff-841a-10897aeb8140/ae4037a0-2a6f-4530-9bac-79b543286bda/p0c5kxJ
This commit is contained in:
michaelschal committed 2025-10-01 14:14:59 +00:00
1 parent 66e20a83e7
commit bee6f53f23
6 files changed
+363 -35

No files matched your search

+105 -8
View File
@@ -1,5 +1,6 @@
import { useState } from "react";
import { Settings as SettingsIcon, Bell, Key, Shield } from "lucide-react";
import { useState, useEffect } from "react";
import { Settings as SettingsIcon, Bell, Key, Shield, Cloud } from "lucide-react";
import { useQuery, useMutation } from "@tanstack/react-query";
import Sidebar from "@/components/sidebar";
import TopBar from "@/components/topbar";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
@@ -7,11 +8,52 @@ import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { useToast } from "@/hooks/use-toast";
import { apiRequest, queryClient } from "@/lib/queryClient";
export default function Settings() {
const [sidebarOpen, setSidebarOpen] = useState(false);
const [notifications, setNotifications] = useState(true);
const [autoPublish, setAutoPublish] = useState(true);
const [cloudName, setCloudName] = useState("");
const [apiKey, setApiKey] = useState("");
const [apiSecret, setApiSecret] = useState("");
const { toast } = useToast();
const { data: cloudinaryConfig } = useQuery({
queryKey: ['/api/cloudinary/config'],
});
useEffect(() => {
if (cloudinaryConfig) {
setCloudName((cloudinaryConfig as any).cloudName || "");
setApiKey((cloudinaryConfig as any).apiKey || "");
}
}, [cloudinaryConfig]);
const saveCloudinaryMutation = useMutation({
mutationFn: () =>
apiRequest('POST', '/api/cloudinary/config', {
cloudName,
apiKey,
apiSecret,
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/api/cloudinary/config'] });
toast({
title: "Configuration sauvegardée",
description: "Vos paramètres Cloudinary ont été enregistrés",
});
setApiSecret(""); // Clear the secret after saving
},
onError: () => {
toast({
title: "Erreur",
description: "Impossible de sauvegarder la configuration",
variant: "destructive",
});
},
});
return (
<div className="flex h-screen overflow-hidden bg-background">
@@ -109,6 +151,67 @@ export default function Settings() {
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Cloud className="w-5 h-5" />
Configuration Cloudinary
</CardTitle>
<CardDescription>
Configurez Cloudinary pour le stockage d'images dans le cloud
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="cloudName">Cloud Name</Label>
<Input
id="cloudName"
type="text"
value={cloudName}
onChange={(e) => setCloudName(e.target.value)}
placeholder="mon-cloud-name"
data-testid="input-cloud-name"
/>
<p className="text-xs text-muted-foreground">
Trouvez-le dans votre tableau de bord Cloudinary
</p>
</div>
<div className="space-y-2">
<Label htmlFor="apiKey">API Key</Label>
<Input
id="apiKey"
type="text"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder="123456789012345"
data-testid="input-api-key"
/>
</div>
<div className="space-y-2">
<Label htmlFor="apiSecret">API Secret</Label>
<Input
id="apiSecret"
type="password"
value={apiSecret}
onChange={(e) => setApiSecret(e.target.value)}
placeholder="••••••••••••••••"
data-testid="input-api-secret"
/>
<p className="text-xs text-muted-foreground">
Votre API Secret restera sécurisé et ne sera jamais exposé
</p>
</div>
<Button
onClick={() => saveCloudinaryMutation.mutate()}
disabled={saveCloudinaryMutation.isPending || !cloudName || !apiKey || !apiSecret}
data-testid="button-save-cloudinary"
className="w-full"
>
{saveCloudinaryMutation.isPending ? "Enregistrement..." : "Enregistrer Cloudinary"}
</Button>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
@@ -125,12 +228,6 @@ export default function Settings() {
</p>
</CardContent>
</Card>
<div className="flex justify-end">
<Button data-testid="button-save-settings">
Enregistrer les modifications
</Button>
</div>
</div>
</div>
</main>