Add support for publishing posts to both feed and stories simultaneously

Introduce a new 'both' post type allowing simultaneous publishing to Facebook and Instagram feeds and stories. This includes UI updates for selecting post types, backend logic for splitting 'both' posts into separate feed and story entries, and the ability to delete scheduled posts. The changes also include a new 'Pages gérées' link in the sidebar and necessary database schema updates.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: ae4037a0-2a6f-4530-9bac-79b543286bda
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/397bca8c-984f-43ff-841a-10897aeb8140/ae4037a0-2a6f-4530-9bac-79b543286bda/WuFXYmm
This commit is contained in:
michaelschal committed 2025-10-08 13:16:55 +00:00
1 parent 02794fecc2
commit 3971c6f912
9 files changed
+268 -24

No files matched your search

+52 -9
View File
@@ -1,16 +1,45 @@
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { ChevronLeft, ChevronRight, Calendar as CalendarIcon } from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import { ChevronLeft, ChevronRight, Calendar as CalendarIcon, Trash2 } from "lucide-react";
import { useQuery, useMutation } from "@tanstack/react-query";
import { useToast } from "@/hooks/use-toast";
import { apiRequest, queryClient } from "@/lib/queryClient";
import type { ScheduledPost } from "@shared/schema";
export default function CalendarView() {
const [currentDate, setCurrentDate] = useState(new Date());
const { toast } = useToast();
const { data: scheduledPosts = [] } = useQuery<ScheduledPost[]>({
queryKey: ["/api/scheduled-posts"],
});
const deletePostMutation = useMutation({
mutationFn: (postId: string) =>
apiRequest('DELETE', `/api/scheduled-posts/${postId}`, {}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/api/scheduled-posts'] });
toast({
title: "Publication supprimée",
description: "La publication programmée a été supprimée avec succès",
});
},
onError: () => {
toast({
title: "Erreur",
description: "Impossible de supprimer la publication",
variant: "destructive",
});
},
});
const handleDeletePost = (postId: string, e: React.MouseEvent) => {
e.stopPropagation();
if (confirm("Êtes-vous sûr de vouloir supprimer cette publication programmée ?")) {
deletePostMutation.mutate(postId);
}
};
const getPostsForDate = (date: Date) => {
return (scheduledPosts || []).filter(post => {
if (!post.scheduledAt) return false;
@@ -161,14 +190,28 @@ export default function CalendarView() {
<div
key={idx}
className={`
px-3 py-2 rounded-lg text-xs cursor-pointer transition-all font-medium shadow-sm
${isPending ? 'bg-warning/20 text-warning hover:bg-warning/30' : ''}
${isPublished ? 'bg-success/20 text-success hover:bg-success/30' : ''}
px-3 py-2 rounded-lg text-xs transition-all font-medium shadow-sm group relative
${isPending ? 'bg-blue-500/20 text-blue-600 hover:bg-blue-500/30' : ''}
${isPublished ? 'bg-green-500/20 text-green-600 hover:bg-green-500/30' : ''}
`}
data-testid={`calendar-post-${post.id}`}
>
<div className="font-semibold">{time}</div>
<div className="truncate opacity-90">{post.postType || 'Post'}</div>
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<div className="font-semibold">{time}</div>
<div className="truncate opacity-90">{post.postType || 'Post'}</div>
</div>
{isPending && (
<button
onClick={(e) => handleDeletePost(post.id, e)}
className="opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-red-500/20 rounded"
data-testid={`button-delete-post-${post.id}`}
title="Supprimer"
>
<Trash2 className="w-3 h-3 text-red-500" />
</button>
)}
</div>
</div>
);
})}
@@ -186,11 +229,11 @@ export default function CalendarView() {
<div className="mt-8 flex items-center justify-center gap-8 flex-wrap">
<div className="flex items-center gap-3">
<div className="w-4 h-4 rounded-md bg-warning/20 border-2 border-warning"></div>
<div className="w-4 h-4 rounded-md bg-blue-500/20 border-2 border-blue-500"></div>
<span className="text-sm text-muted-foreground font-medium">Programmé</span>
</div>
<div className="flex items-center gap-3">
<div className="w-4 h-4 rounded-md bg-success/20 border-2 border-success"></div>
<div className="w-4 h-4 rounded-md bg-green-500/20 border-2 border-green-500"></div>
<span className="text-sm text-muted-foreground font-medium">Publié</span>
</div>
</div>
+17 -1
View File
@@ -38,7 +38,6 @@ export default function Sidebar() {
{ icon: PlusCircle, label: "Nouvelle publication", href: "/new", badge: null },
{ icon: Calendar, label: "Calendrier", href: "/calendar", badge: null },
{ icon: Images, label: "Médiathèque", href: "/media", badge: null },
{ icon: Users, label: "Pages gérées", href: "/pages", badge: null },
{ icon: Bot, label: "Assistant IA", href: "/ai", badge: null },
];
@@ -141,6 +140,23 @@ export default function Sidebar() {
<div className="mb-2">
<p className="text-xs font-semibold text-muted-foreground mb-2 px-4 uppercase tracking-wider">Administration</p>
</div>
<Link
href="/pages"
className={`
flex items-center gap-3 px-4 py-3 rounded-xl transition-all relative
${location === "/pages"
? 'bg-gradient-to-r from-primary/10 to-secondary/10 text-primary shadow-sm'
: 'text-muted-foreground hover:bg-sidebar-accent hover:text-foreground'
}
`}
data-testid="link-pages-gérées"
>
{location === "/pages" && (
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-8 gradient-primary rounded-r-full" />
)}
<Users className="w-5 h-5" />
<span>Pages gérées</span>
</Link>
<Link
href="/users"
className={`