Files
Socialflow/client/src/pages/new-post.tsx
T
michaelschal 5829873f8c Add all application pages and their functionalities
Add new routes and components for AI, Calendar, Media, Pages Management, Analytics, History, and Settings.

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/vhNnBGJ
2025-10-01 13:36:26 +00:00

289 lines
11 KiB
TypeScript

import { useState } from "react";
import { useQuery, useMutation } from "@tanstack/react-query";
import { useLocation } from "wouter";
import { Send, Sparkles, Image as ImageIcon, Calendar } from "lucide-react";
import Sidebar from "@/components/sidebar";
import TopBar from "@/components/topbar";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Textarea } from "@/components/ui/textarea";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import { useToast } from "@/hooks/use-toast";
import { apiRequest, queryClient } from "@/lib/queryClient";
import type { SocialPage, Media } from "@shared/schema";
export default function NewPost() {
const [sidebarOpen, setSidebarOpen] = useState(false);
const [, navigate] = useLocation();
const { toast } = useToast();
const [productInfo, setProductInfo] = useState('');
const [selectedMedia, setSelectedMedia] = useState<string | null>(null);
const [selectedPages, setSelectedPages] = useState<string[]>([]);
const [scheduledDate, setScheduledDate] = useState('');
const { data: pages = [] } = useQuery<SocialPage[]>({
queryKey: ['/api/pages'],
});
const { data: mediaList = [] } = useQuery<Media[]>({
queryKey: ['/api/media'],
});
const generateTextMutation = useMutation({
mutationFn: (productInfo: string) =>
apiRequest('POST', '/api/ai/generate', { productInfo }),
onSuccess: (data: any) => {
toast({
title: "Texte généré",
description: `${data.variations?.length || 0} variations créées`,
});
queryClient.invalidateQueries({ queryKey: ['/api/ai/generations'] });
},
onError: () => {
toast({
title: "Erreur",
description: "Impossible de générer le texte",
variant: "destructive",
});
},
});
const createPostMutation = useMutation({
mutationFn: (data: any) =>
apiRequest('POST', '/api/posts', data),
onSuccess: () => {
toast({
title: "Publication créée",
description: "La publication a été créée avec succès",
});
navigate('/');
},
onError: () => {
toast({
title: "Erreur",
description: "Impossible de créer la publication",
variant: "destructive",
});
},
});
const handleGenerateText = () => {
if (!productInfo.trim()) {
toast({
title: "Information manquante",
description: "Veuillez saisir les informations du produit",
variant: "destructive",
});
return;
}
generateTextMutation.mutate(productInfo);
};
const handleCreatePost = () => {
if (!productInfo.trim()) {
toast({
title: "Contenu requis",
description: "Veuillez saisir le contenu de la publication",
variant: "destructive",
});
return;
}
createPostMutation.mutate({
content: productInfo,
scheduledFor: scheduledDate || undefined,
});
};
return (
<div className="flex h-screen overflow-hidden bg-background">
{sidebarOpen && (
<div
className="fixed inset-0 bg-black/50 z-40 lg:hidden"
onClick={() => setSidebarOpen(false)}
/>
)}
<div className={`
fixed lg:static inset-y-0 left-0 z-50 transform transition-transform duration-300 ease-in-out
${sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}
`}>
<Sidebar />
</div>
<main className="flex-1 overflow-y-auto">
<TopBar onMenuClick={() => setSidebarOpen(!sidebarOpen)} />
<div className="p-6 max-w-5xl mx-auto">
<div className="mb-6">
<h1 className="text-3xl font-bold text-foreground">Nouvelle publication</h1>
<p className="text-muted-foreground mt-2">
Créez et planifiez une nouvelle publication
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Contenu</CardTitle>
<CardDescription>Informations du produit ou message</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div>
<Label htmlFor="productInfo">Informations du produit</Label>
<Textarea
id="productInfo"
value={productInfo}
onChange={(e) => setProductInfo(e.target.value)}
placeholder="Décrivez votre produit : nom, caractéristiques, prix, etc."
rows={6}
data-testid="input-product-info"
/>
</div>
<Button
onClick={handleGenerateText}
disabled={generateTextMutation.isPending}
className="w-full"
data-testid="button-generate-text"
>
<Sparkles className="w-4 h-4 mr-2" />
{generateTextMutation.isPending ? 'Génération...' : 'Générer le texte IA'}
</Button>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Média</CardTitle>
<CardDescription>Sélectionnez une image ou vidéo</CardDescription>
</CardHeader>
<CardContent>
{mediaList.length === 0 ? (
<div className="text-center py-8">
<ImageIcon className="w-12 h-12 mx-auto text-muted-foreground mb-2" />
<p className="text-muted-foreground">Aucun média disponible</p>
<Button
variant="link"
onClick={() => navigate('/media')}
data-testid="link-upload-media"
>
Télécharger des médias
</Button>
</div>
) : (
<div className="grid grid-cols-3 gap-2">
{mediaList.map((media) => (
<button
key={media.id}
onClick={() => setSelectedMedia(media.id)}
className={`relative aspect-square rounded-lg overflow-hidden border-2 transition-all ${
selectedMedia === media.id
? 'border-primary ring-2 ring-primary'
: 'border-transparent hover:border-muted-foreground'
}`}
data-testid={`button-select-media-${media.id}`}
>
<img
src={media.originalUrl}
alt={media.fileName}
className="w-full h-full object-cover"
/>
</button>
))}
</div>
)}
</CardContent>
</Card>
</div>
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Pages cibles</CardTitle>
<CardDescription>Sélectionnez les pages où publier</CardDescription>
</CardHeader>
<CardContent>
{pages.length === 0 ? (
<div className="text-center py-8">
<p className="text-muted-foreground mb-2">Aucune page connectée</p>
<Button
variant="link"
onClick={() => navigate('/pages')}
data-testid="link-add-pages"
>
Ajouter des pages
</Button>
</div>
) : (
<div className="space-y-3">
{pages.map((page) => (
<div key={page.id} className="flex items-center space-x-2">
<Checkbox
id={`page-${page.id}`}
checked={selectedPages.includes(page.id)}
onCheckedChange={(checked) => {
if (checked) {
setSelectedPages([...selectedPages, page.id]);
} else {
setSelectedPages(selectedPages.filter(id => id !== page.id));
}
}}
data-testid={`checkbox-page-${page.id}`}
/>
<label
htmlFor={`page-${page.id}`}
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 flex-1"
>
{page.pageName} ({page.platform})
</label>
</div>
))}
</div>
)}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Planification</CardTitle>
<CardDescription>Programmez la publication (optionnel)</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2">
<Label htmlFor="scheduledDate">Date et heure</Label>
<Input
id="scheduledDate"
type="datetime-local"
value={scheduledDate}
onChange={(e) => setScheduledDate(e.target.value)}
data-testid="input-scheduled-date"
/>
<p className="text-xs text-muted-foreground">
Laissez vide pour publier immédiatement
</p>
</div>
</CardContent>
</Card>
<Button
onClick={handleCreatePost}
disabled={createPostMutation.isPending}
className="w-full"
size="lg"
data-testid="button-create-post"
>
<Send className="w-4 h-4 mr-2" />
{createPostMutation.isPending ? 'Création...' : 'Créer la publication'}
</Button>
</div>
</div>
</div>
</main>
</div>
);
}