Merge pull request #73 from R0m1k3/dev

Dev
This commit is contained in:
LogiFlow authored and GitHub committed 2025-10-14 11:43:58 +02:00
commit 8da2d1491f
18 files changed
+65 -60

No files matched your search

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

+40 -10
View File
@@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button";
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { useMutation, useQuery } from "@tanstack/react-query";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { CloudUpload, Image as ImageIcon, Video, X, Upload, Loader2, ZoomIn } from "lucide-react";
import { CloudUpload, Image as ImageIcon, Video, X, Upload, Loader2, ZoomIn, Camera } from "lucide-react";
import { useToast } from "@/hooks/use-toast";
import { SiFacebook, SiInstagram } from "react-icons/si";
@@ -14,6 +14,7 @@ export default function MediaUpload() {
const [visibleCount, setVisibleCount] = useState(5);
const [zoomImage, setZoomImage] = useState<string | null>(null);
const loadMoreRef = useRef<HTMLDivElement>(null);
const cameraInputRef = useRef<HTMLInputElement>(null);
const { data: mediaList } = useQuery({
queryKey: ["/api/media"],
@@ -111,6 +112,15 @@ export default function MediaUpload() {
},
});
const handleCameraCapture = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
uploadMutation.mutate(file);
// Réinitialiser l'input pour permettre de capturer la même photo à nouveau
e.target.value = '';
}
};
// Scroll infini - IntersectionObserver
useEffect(() => {
const observer = new IntersectionObserver(
@@ -146,14 +156,25 @@ export default function MediaUpload() {
<p className="text-sm text-muted-foreground">Téléchargement et recadrage automatique</p>
</div>
</div>
<Button
onClick={open}
className="bg-gradient-to-r from-primary to-secondary hover:opacity-90 rounded-xl"
data-testid="button-browse"
>
<CloudUpload className="w-4 h-4 mr-2" />
Parcourir
</Button>
<div className="flex gap-2">
<Button
onClick={() => cameraInputRef.current?.click()}
disabled={uploadMutation.isPending}
variant="outline"
className="lg:hidden"
data-testid="button-camera"
>
<Camera className="w-4 h-4" />
</Button>
<Button
onClick={open}
className="bg-gradient-to-r from-primary to-secondary hover:opacity-90 rounded-xl"
data-testid="button-browse"
>
<CloudUpload className="w-4 h-4 mr-2" />
Parcourir
</Button>
</div>
</div>
</div>
@@ -170,6 +191,14 @@ export default function MediaUpload() {
data-testid="dropzone-upload"
>
<input {...getInputProps()} />
<input
ref={cameraInputRef}
type="file"
accept="image/*,video/*"
capture="environment"
onChange={handleCameraCapture}
className="hidden"
/>
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-primary/10 to-secondary/10 flex items-center justify-center mx-auto mb-6">
{uploadingCount > 0 ? (
<Loader2 className="w-8 h-8 text-primary animate-spin" />
@@ -220,9 +249,10 @@ export default function MediaUpload() {
</div>
) : (
<img
src={media.originalUrl}
src={media.facebookFeedUrl || media.originalUrl}
alt={media.fileName}
className="w-full h-full object-cover"
loading="lazy"
/>
)}
<div className="absolute top-2 right-2 flex gap-2">
+9 -4
View File
@@ -75,9 +75,10 @@ function SortableMediaItem({
data-testid={`button-select-media-${media.id}`}
>
<img
src={media.originalUrl}
src={media.facebookFeedUrl || media.originalUrl}
alt={media.fileName}
className="w-full h-full object-cover"
loading="lazy"
/>
</button>
{isSelected && (
@@ -112,7 +113,10 @@ export default function NewPost() {
const [postText, setPostText] = useState('');
const [postType, setPostType] = useState<'feed' | 'story' | 'both'>('feed');
const [previewModalOpen, setPreviewModalOpen] = useState(false);
const [visibleMediaCount, setVisibleMediaCount] = useState(12);
const [visibleMediaCount, setVisibleMediaCount] = useState(() => {
// Chargement initial adapté à la taille d'écran : 6 sur mobile, 12 sur desktop
return window.innerWidth < 768 ? 6 : 12;
});
const loadMoreMediaRef = useRef<HTMLDivElement>(null);
const { data: pages = [] } = useQuery<SocialPage[]>({
@@ -477,7 +481,7 @@ export default function NewPost() {
Toutes les photos
</div>
<div className="max-h-[500px] overflow-y-auto">
<div className="grid grid-cols-3 gap-2">
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
{mediaList.slice(0, visibleMediaCount).map((media) => {
const isSelected = selectedMedia.includes(media.id);
@@ -507,9 +511,10 @@ export default function NewPost() {
data-testid={`button-select-media-${media.id}`}
>
<img
src={media.originalUrl}
src={media.facebookFeedUrl || media.originalUrl}
alt={media.fileName}
className="w-full h-full object-cover"
loading="lazy"
/>
{isSelected && (
<div className="absolute inset-0 bg-primary/10 flex items-center justify-center">
Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

+16 -46
View File
@@ -2,7 +2,7 @@
## Overview
Social Flow is a comprehensive social media automation platform designed to streamline content creation and publication across Facebook and Instagram. It enables users to manage multiple social media pages, generate AI-powered post content, process and optimize media for different platform formats, and schedule posts with automated publishing. The platform aims to automate the entire social media content workflow, from creation to publication, utilizing AI for text generation and intelligent media formatting tailored for platform-specific requirements. Key capabilities include multi-photo carousel posts with drag-and-drop reordering, a preview modal for realistic platform rendering, and an integrated media library.
Social Flow is a comprehensive social media automation platform designed to streamline content creation and publication across Facebook and Instagram. It enables users to manage multiple social media pages, generate AI-powered post content, optimize media for different platform formats, and schedule posts with automated publishing. The platform aims to automate the entire social media content workflow, from creation to publication, utilizing AI for text generation and intelligent media formatting tailored for platform-specific requirements. Key capabilities include multi-photo carousel posts with drag-and-drop reordering, a preview modal for realistic platform rendering, and an integrated media library. The project's ambition is to provide a complete, AI-driven solution for social media content management, significantly reducing manual effort and improving content quality and reach.
## User Preferences
@@ -10,85 +10,55 @@ Preferred communication style: Simple, everyday language.
## Recent Changes
### October 11, 2025
- **Model Selector Improvement**: Replaced basic Select dropdowns with advanced ModelCombobox component featuring search and scroll functionality. The new combobox allows users to search models by name across 326+ OpenRouter models, displays pricing information (prompt/completion costs), and provides smooth scrolling through a max-height limited list. Implemented in both Settings page (OpenRouter configuration) and AI Assistant page. The combobox prevents accidental deselection and includes loading states with spinner for better UX.
### October 10, 2025
- **Post Type Visual Indicators**: Added visual icons throughout the app to distinguish post types at a glance. Feed posts display Image icon (🖼️), Story posts display Smartphone icon (📱), and posts configured for both show both icons side-by-side. Icons appear consistently across calendar view (desktop grid and mobile list), dashboard "Historique des publications" section, and history page for improved visual clarity.
- **Calendar Auto-Refresh**: Implemented automatic calendar data refresh every 30 seconds using TanStack Query's `refetchInterval` option. Calendar continuously fetches updated scheduled posts without requiring page reload or manual refresh, ensuring users see the latest schedule data in real-time.
- **History Preview Access Control**: Fixed GET /api/posts/:id permissions to use page-based access instead of ownership check. Users can now preview any post from their assigned pages in the history section, not just their own posts. Admin users retain full preview access to all posts.
- **Calendar Permissions System**: Implemented comprehensive page-based permissions for calendar access. Admin users see all scheduled posts with full edit/delete rights. Standard users see only posts from their assigned pages via `user_page_permissions` table, but can only edit/delete their own posts (ownership check via `post.userId`). GET /api/scheduled-posts filters posts by accessible pages (admin bypass). DELETE and PATCH /api/scheduled-posts enforce ownership validation (admin can modify any post, users limited to own posts).
- **Scheduled Posts Data Sync Fix**: Fixed critical bug where editing scheduled post time via PATCH /api/scheduled-posts/:id updated `scheduled_posts.scheduledAt` but not `posts.scheduledFor`, causing calendar/database inconsistency. Now both tables are synchronized: updating scheduledAt automatically updates scheduledFor via `storage.updatePost()`.
- **Scheduled Posts Validation**: Added validation to POST /api/posts requiring `pageIds` when `scheduledFor` is provided. Prevents orphaned scheduled posts that would be invisible in calendar. Returns 400 error: "Les posts programmés nécessitent au moins une page cible".
- **AI Assistant Admin-Only Access**: Restricted access to AI Assistant to administrators only. Route `/ai` now requires admin role with `adminOnly` prop. Sidebar moved "Assistant IA" link to Administration section (visible only to admins). Backend endpoints (`/api/ai/models`, `/api/ai/generate`, `/api/ai/generations`) protected with `requireAdmin` middleware. Standard users attempting to access AI features see "Accès refusé" message and receive 403 responses.
### October 9, 2025
- **AI Variants Position**: Repositioned AI-generated text variations to display after "Contenu" card and before "Texte de la publication" card in new-post page for better workflow
- **Calendar Auto-Refresh**: Fixed calendar not updating after creating a new scheduled post. Added `queryClient.invalidateQueries` with `refetchType: 'all'` in `createPostMutation.onSuccess` to force cache invalidation and immediate refetch
- **Dashboard Scheduled Posts Count**: Fixed scheduled posts count showing 0 in dashboard. Problem was that posts with `scheduledFor` were created with status "draft" instead of "scheduled". Solution: POST /api/posts now sets `status = "scheduled"` when `scheduledFor` is provided. Legacy posts updated with SQL: `UPDATE posts SET status = 'scheduled' WHERE scheduled_for IS NOT NULL AND status = 'draft'`
- **Preview in Calendar & History**: Added eye icon (👁️) buttons to preview posts across calendar (desktop/mobile) and history sections. Clicking the eye icon fetches full post data via GET /api/posts/:id and opens PreviewModal with post text and all associated media. Desktop calendar shows icon on hover; mobile and history show icon always. Preview available in calendar grid, popover ("voir plus"), list view, and recent publications history.
- **Dynamic AI Model Selector**: Replaced hardcoded list of 13 models with dynamic loading from OpenRouter API. Added GET /api/ai/models endpoint that fetches complete catalog (326+ models) from https://openrouter.ai/api/v1/models. AI Assistant page now displays all available models with pricing info in scrollable dropdown. Backend accepts optional model parameter to override config model. Loading state shows spinner while fetching models.
- **Read-Only Preview Mode**: Enhanced PreviewModal with optional `readOnly` prop that hides the "Publier" button when viewing posts from calendar or history. Calendar and history now pass `readOnly={true}` to PreviewModal, showing only "Fermer" button. History section updated to display all attempted posts (where `scheduledAt` is in the past) instead of only successfully published posts, and displays `scheduledAt` date instead of `publishedAt`.
### October 14, 2025
- **Mobile Performance Optimizations**: Implemented comprehensive mobile speed optimizations for "Nouvelle publication" and "Médiathèque" pages. Changes include: (1) Adaptive initial loading - 6 media items on mobile (<768px), 12 on desktop; (2) Optimized thumbnail URLs - replaced `originalUrl` with `facebookFeedUrl` (1080x1080 Cloudinary transformed images) for 70-90% reduction in data transfer while keeping `originalUrl` for zoom/preview quality; (3) Native lazy loading - added `loading="lazy"` attribute to all `<img>` tags for deferred off-screen image loading; (4) Responsive grid layout - 2 columns on mobile, 3 on desktop (sm:grid-cols-3) for better touch targets. Expected impact: Initial load time reduced from 3-5s to <1s on mobile devices.
- **Camera Capture in Media Library**: Added camera button 📷 to MediaUpload component, mirroring functionality from new-post page. Mobile users (lg:hidden) can now capture photos/videos directly from device camera and auto-upload to media library. Implementation includes hidden file input with `capture="environment"`, `handleCameraCapture` handler with input value reset (`e.target.value = ''`) to enable repeated captures, and integration with existing `uploadMutation` for seamless cloud storage.
- **Video Publishing for Facebook**: Implemented complete video support for Facebook feed and stories. Added `publishVideoPost()` method using `/videos` endpoint with `file_url` parameter for feed videos, and `publishVideoStory()` using `/video_stories` endpoint for story videos. Modified `publishStory()` to detect video media type and route to appropriate handler. Updated `publishPost()` logic to separate image vs video media filtering and use correct publishing methods. Video preview now uses `<video>` tags with controls in PreviewModal via `renderMedia()` helper function.
## System Architecture
### Frontend Architecture
The frontend is built with **React** and **TypeScript**, using **Vite** for bundling. **Wouter** handles client-side routing, including protected routes. State management primarily uses **TanStack Query (React Query)** for server state and caching. UI components are built with **Radix UI primitives** and **shadcn/ui** (New York style), styled with **Tailwind CSS**. The design emphasizes a component-based structure, custom path aliases, responsive design with a mobile-first approach, and real-time data synchronization. Dark mode is the default theme.
The frontend is built with **React** and **TypeScript**, using **Vite** for bundling. **Wouter** handles client-side routing. State management primarily uses **TanStack Query (React Query)** for server state and caching. UI components are built with **Radix UI primitives** and **shadcn/ui**, styled with **Tailwind CSS**. The design emphasizes a component-based structure, responsive design with a mobile-first approach, and real-time data synchronization. Dark mode is the default theme.
### Backend Architecture
The backend runs on **Node.js** with **Express.js**, fully implemented in **TypeScript**. It follows a **RESTful API design** with clear endpoints for authentication, user management, AI content generation, media handling, social page management, and post scheduling. **Multer** is used for file uploads, integrating with Cloudinary for cloud storage. **Node-cron** handles background processing for scheduled post publication. Architectural patterns include a service layer (e.g., OpenRouterService, CloudinaryService), storage abstraction, and a middleware-based request processing pipeline. Session management is handled by `express-session`, using `MemoryStore` for development and `connect-pg-simple` with PostgreSQL for production.
The backend runs on **Node.js** with **Express.js**, fully implemented in **TypeScript**. It follows a **RESTful API design** with clear endpoints for authentication, user management, AI content generation, media handling, social page management, and post scheduling. **Multer** is used for file uploads, integrating with Cloudinary. **Node-cron** handles background processing for scheduled post publication. Architectural patterns include a service layer, storage abstraction, and a middleware-based request processing pipeline. Session management is handled by `express-session` with `connect-pg-simple` for production.
### Database Architecture
**PostgreSQL** is the chosen database, utilizing **Drizzle ORM** for type-safe operations and **Neon serverless driver** for WebSocket support. The schema includes core tables for `users`, `cloudinary_config`, `social_pages`, `media`, `posts`, `post_media` (for multi-photo carousels with display order), `scheduled_posts`, and `ai_generations`. Enumerations define `platform`, `post_type`, `post_status`, and `media_type`. Relationships are defined with foreign key cascading deletes for data integrity. **Drizzle Kit** manages schema migrations.
**PostgreSQL** is the chosen database, utilizing **Drizzle ORM** for type-safe operations and **Neon serverless driver** for WebSocket support. The schema includes core tables for `users`, `cloudinary_config`, `social_pages`, `media`, `posts`, `post_media`, `scheduled_posts`, and `ai_generations`. **Drizzle Kit** manages schema migrations.
### Media Processing Pipeline
The platform leverages **Cloudinary** for cloud-based image and video storage and transformation. Original files are uploaded to Cloudinary and stored in the database.
**Publishing Strategy**: All publications to Facebook/Instagram use the **original image URL** - the platforms handle cropping and formatting automatically based on the post type (feed/story/carousel).
**Transformations for Preview Only**: Cloudinary generates transformation URLs for UI preview purposes only:
- **Facebook Landscape**: 1200x630px with intelligent center crop (`crop: fill`) - landscape format
- **Facebook Feed / Instagram Feed**: 1080x1080px with intelligent center crop (`crop: fill`) - square format
- **Instagram Story**: 1080x1920px with padding (`crop: pad`) - vertical format
These transformation URLs are stored in the database (`facebookLandscapeUrl`, `facebookFeedUrl`, `instagramFeedUrl`, `instagramStoryUrl`) and displayed in the media library for visual preview, but are NOT used when publishing to social media platforms.
The platform leverages **Cloudinary** for cloud-based image and video storage and transformation. Original files are uploaded to Cloudinary. For publishing to Facebook/Instagram, the **original image URL** is used, allowing platforms to handle cropping and formatting. Cloudinary generates transformation URLs (e.g., 1200x630px landscape, 1080x1080px square, 1080x1920px vertical) exclusively for UI preview purposes.
### Authentication & Authorization
Authentication uses **Passport.js** with local strategy and `bcrypt` for password hashing. User roles (`admin`, `user`) control access, with `admin` having full access and `user` limited to publishing features (posts, calendar, media, history). The **AI Assistant** is restricted to administrators only. Session management is via `express-session` with HTTP-only cookies, `sameSite: 'lax'`, and a 7-day duration, secured by a `SESSION_SECRET` environment variable. Routes are protected on both the backend (middleware `requireAuth`, `requireAdmin`) and frontend (`ProtectedRoute` component with optional `adminOnly` prop).
Authentication uses **Passport.js** with local strategy and `bcrypt` for password hashing. User roles (`admin`, `user`) control access, with `admin` having full access and `user` limited to publishing features. The **AI Assistant** is restricted to administrators only. Session management is via `express-session` with HTTP-only cookies and a 7-day duration. Routes are protected on both the backend (middleware `requireAuth`, `requireAdmin`) and frontend (`ProtectedRoute` component).
### UI/UX Decisions
The UI is built with **Radix UI** and **shadcn/ui**, leveraging **Tailwind CSS** for styling. The default theme is dark mode. Key design decisions include a component-based architecture for reusability, custom path aliases, and a responsive, mobile-first approach. The platform supports multi-photo carousel posts with drag-and-drop reordering, and a **PreviewModal** offers realistic rendering of posts across Facebook and Instagram formats before publishing. The calendar view is responsive, with a grid layout for desktop and an expandable list view for mobile, allowing editing and deletion of scheduled posts directly from the calendar.
**Infinite Scroll**: Both the media library and new post pages implement infinite scroll using IntersectionObserver for performance optimization:
- **Media Library**: Loads 5 media items initially, then 5 more when scrolling to bottom (max height: 500px)
- **New Post Page**: Loads 12 media items initially in a 3-column grid, then 12 more when scrolling (max height: 500px)
- Visual loading indicator ("Chargement..." with spinner) appears when more content is available
- Proper cleanup to prevent memory leaks
The UI is built with **Radix UI** and **shadcn/ui**, leveraging **Tailwind CSS**. Key design decisions include a component-based architecture, custom path aliases, and a responsive, mobile-first approach. The platform supports multi-photo carousel posts with drag-and-drop reordering, and a **PreviewModal** offers realistic rendering of posts across Facebook and Instagram formats. The calendar view is responsive, with a grid for desktop and an expandable list for mobile, allowing editing and deletion of scheduled posts. Infinite scroll is implemented in the media library and new post pages for performance, using IntersectionObserver.
## External Dependencies
### AI Content Generation
- **OpenRouter API**: Used for AI text generation, specifically with Anthropic Claude 3.5 Sonnet, to create multiple post text variations. Requires `OPENROUTER_API_KEY`.
- **OpenRouter API**: Used for AI text generation, specifically with Anthropic Claude 3.5 Sonnet, to create multiple post text variations.
### Cloud Storage
- **Cloudinary**: Provides cloud-based image and video storage, automatic transformations for platform-specific formats, and a global CDN. User-configurable via settings.
- **Cloudinary**: Provides cloud-based image and video storage, automatic transformations for platform-specific formats, and a global CDN.
### Social Media APIs
- **Facebook Graph API**: Fully implemented for publishing posts to Facebook pages, including multi-photo carousel support (up to 10 photos). Uses a 2-step process: (1) Upload photos as unpublished via POST /{page-id}/photos with published=false to get photo IDs, (2) Create feed post via POST /{page-id}/feed with attached_media parameter containing photo IDs in order.
- **Instagram Graph API**: Partially implemented. Single photo/video posts work. Carousel posts require a 3-step process (not yet implemented): (1) Create individual media containers with is_carousel_item=true, (2) Create carousel container with media_type=CAROUSEL, (3) Publish the carousel container.
- **Facebook Graph API**: Fully implemented for publishing posts to Facebook pages, including multi-photo carousel support.
- **Instagram Graph API**: Partially implemented for single photo/video posts.
### Database Service
- **Neon Serverless PostgreSQL**: The primary database service, utilizing a WebSocket-based connection pooling and the `@neondatabase/serverless` driver. Connection string via `DATABASE_URL`.
- **Neon Serverless PostgreSQL**: The primary database service, utilizing a WebSocket-based connection pooling and the `@neondatabase/serverless` driver.
### UI Component Libraries