diff --git a/CODE_REFACTORING_SUMMARY.md b/CODE_REFACTORING_SUMMARY.md new file mode 100644 index 0000000..fd8728b --- /dev/null +++ b/CODE_REFACTORING_SUMMARY.md @@ -0,0 +1,154 @@ +# Code Quality Improvements Summary + +This document outlines the major refactoring improvements made to enhance code organization, maintainability, and scalability. + +## Major Improvements + +### 1. Create Post Form Component Refactoring + +**Before:** +- Single massive file: `src/components/create-post-form.tsx` (911 lines) +- Multiple responsibilities mixed in one component +- Hard to maintain and test + +**After:** +- Modular component structure with focused responsibilities: + - `src/components/create-post-form/types.ts` - Shared types and interfaces + - `src/components/create-post-form/hooks/use-post-form-state.ts` - Form state management + - `src/components/create-post-form/hooks/use-ai-generation.ts` - AI generation logic + - `src/components/create-post-form/ai-generation-panel.tsx` - AI generation UI + - `src/components/create-post-form/destination-selector.tsx` - Destination selection UI + - `src/components/create-post-form/post-actions.tsx` - Action buttons (publish/schedule) + - `src/components/create-post-form/create-post-form.tsx` - Main orchestrating component + - `src/components/create-post-form/index.ts` - Re-exports + +**Benefits:** +- ✅ Single Responsibility Principle +- ✅ Better testability +- ✅ Easier maintenance +- ✅ Reusable components and hooks +- ✅ Type safety maintained +- ✅ Clear separation of concerns + +### 2. Posts Functions Refactoring + +**Before:** +- Single large file: `src/functions/posts.ts` (329 lines) +- Multiple unrelated functions in one file + +**After:** +- Modular function structure: + - `src/functions/posts/create-post.ts` - Post creation logic + - `src/functions/posts/destinations.ts` - Destination-related functions + - `src/functions/posts/post-management.ts` - CRUD operations + - `src/functions/posts/recent-posts.ts` - Recent posts fetching + - `src/functions/posts/index.ts` - Re-exports + +**Benefits:** +- ✅ Related functions grouped together +- ✅ Easier to locate specific functionality +- ✅ Better maintainability +- ✅ Reduced file size for focused changes + +### 3. Integrations Functions Refactoring + +**Before:** +- Single file: `src/functions/integrations.ts` (140 lines) +- Mixed responsibilities + +**After:** +- Focused modules: + - `src/functions/integrations/integration-management.ts` - Basic CRUD operations + - `src/functions/integrations/platform-credentials.ts` - Credential management + - `src/functions/integrations/index.ts` - Re-exports + +**Benefits:** +- ✅ Clear separation between integration management and credentials +- ✅ Easier to maintain credential-related logic +- ✅ Better organization + +## Key Principles Applied + +### 1. **Single Responsibility Principle** +- Each module/component has one clear purpose +- Functions and components are focused on specific tasks + +### 2. **Separation of Concerns** +- UI components separated from business logic +- State management extracted to custom hooks +- Server functions organized by domain + +### 3. **Type Safety** +- All types consolidated in dedicated type files +- No redundant type declarations +- Maintained strict TypeScript compliance + +### 4. **Modularity** +- Code split into logical, reusable modules +- Clear import/export structure +- Easy to extend and modify + +### 5. **Maintainability** +- Smaller, focused files are easier to understand +- Clear naming conventions +- Consistent patterns throughout + +## File Structure Before vs After + +### Before +``` +src/ +├── components/ +│ └── create-post-form.tsx (911 lines - TOO LARGE) +├── functions/ +│ ├── posts.ts (329 lines - TOO LARGE) +│ └── integrations.ts (140 lines - MIXED CONCERNS) +``` + +### After +``` +src/ +├── components/ +│ ├── create-post-form.tsx (re-exports) +│ └── create-post-form/ +│ ├── types.ts +│ ├── hooks/ +│ │ ├── use-post-form-state.ts +│ │ └── use-ai-generation.ts +│ ├── ai-generation-panel.tsx +│ ├── destination-selector.tsx +│ ├── post-actions.tsx +│ ├── create-post-form.tsx +│ └── index.ts +├── functions/ +│ ├── posts.ts (re-exports) +│ ├── posts/ +│ │ ├── create-post.ts +│ │ ├── destinations.ts +│ │ ├── post-management.ts +│ │ ├── recent-posts.ts +│ │ └── index.ts +│ ├── integrations.ts (re-exports) +│ └── integrations/ +│ ├── integration-management.ts +│ ├── platform-credentials.ts +│ └── index.ts +``` + +## Benefits Achieved + +1. **Reduced Complexity**: Large files split into manageable, focused modules +2. **Improved Maintainability**: Easier to locate and modify specific functionality +3. **Better Testability**: Smaller, focused units are easier to test +4. **Enhanced Reusability**: Components and hooks can be reused across the application +5. **Clearer Architecture**: Obvious separation between UI, state management, and business logic +6. **Type Safety**: All types properly organized and no redundancy +7. **Future-Proof**: Easy to extend with new features + +## Migration Impact + +- **Zero Breaking Changes**: All existing imports continue to work through re-exports +- **Backward Compatibility**: Existing code doesn't need modification +- **Gradual Adoption**: New code can immediately benefit from the improved structure + +This refactoring significantly improves code quality while maintaining the lean, type-safe approach requested. \ No newline at end of file diff --git a/src/components/create-post-form.tsx b/src/components/create-post-form.tsx index d73312b..168c3f6 100644 --- a/src/components/create-post-form.tsx +++ b/src/components/create-post-form.tsx @@ -1,910 +1,3 @@ -import { Button } from "@/components/ui/button" -import { Checkbox } from "@/components/ui/checkbox" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { Textarea } from "@/components/ui/textarea" -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" -import type { Platform } from "@/database/schema" -import { checkAiAccess, generateAiContent } from "@/functions/ai" -import { createDestinationFromInput, getRecentDestinations } from "@/functions/posts" -import type { PlatformInfo, PostDestination } from "@/lib/server/social-platforms/base-platform" -import { useMutation, useQuery } from "@tanstack/react-query" -import { CalendarClock, ChevronDown, ChevronUp, HelpCircle, Loader2, Lock, MapPin, Rocket, RotateCcw, Sparkles, X } from "lucide-react" -import { useState } from "react" -import { toast } from "sonner" - -interface CreatePostFormProps { - selectedIntegrationId: string | undefined - currentIntegrationName?: string - currentPlatform?: Platform - platformInfo?: PlatformInfo - isPending: boolean - onCreatePost: (data: { - integrationId: string - content: string - scheduledAt?: Date - destination?: PostDestination - additionalFields?: Record - }) => void - onClear: () => void - onValidationError: (message: string) => void -} - -interface GenerationHistory { - id: string - content: string - prompt: string - parameters: AiTuningParameters - timestamp: Date -} - -interface AiTuningParameters { - temperature?: number - maxTokens?: number - styleOverride?: string - toneOverride?: string - lengthOverride?: string - useEmojisOverride?: boolean - useHashtagsOverride?: boolean - customInstructionsOverride?: string -} - -export function CreatePostForm({ - selectedIntegrationId, - currentIntegrationName, - currentPlatform, - platformInfo, - isPending, - onCreatePost, - onClear, - onValidationError -}: CreatePostFormProps) { - const [content, setContent] = useState("") - const [scheduledDateTime, setScheduledDateTime] = useState("") - const [isPublishPopoverOpen, setIsPublishPopoverOpen] = useState(false) - const [isSchedulePopoverOpen, setIsSchedulePopoverOpen] = useState(false) - const [aiPrompt, setAiPrompt] = useState("") - const [showAiInput, setShowAiInput] = useState(false) - const [showAdvancedSettings, setShowAdvancedSettings] = useState(false) - const [showGenerationHistory, setShowGenerationHistory] = useState(false) - const [selectedDestination, setSelectedDestination] = useState(undefined) - const [customDestination, setCustomDestination] = useState("") - const [showCustomDestination, setShowCustomDestination] = useState(false) - const [additionalFields, setAdditionalFields] = useState>({}) - - // AI tuning parameters - const [aiParameters, setAiParameters] = useState({ - temperature: 0.7, - maxTokens: 150 - }) - - // Generation history - const [generationHistory, setGenerationHistory] = useState([]) - - // Query for recent destinations - const { data: recentDestinations, isLoading: isLoadingDestinations } = useQuery({ - queryKey: ["recent-destinations", currentPlatform], - queryFn: () => getRecentDestinations({ data: { platform: currentPlatform!, limit: 5 } }), - enabled: !!currentPlatform && !!platformInfo?.supportsDestinations && !!selectedIntegrationId, - staleTime: 5 * 60 * 1000 // 5 minutes - }) - - // Always check AI access (for displaying appropriate messages) - const { data: aiAccess, isLoading: isCheckingAiAccess } = useQuery({ - queryKey: ["ai-access"], - queryFn: checkAiAccess, - retry: false, - staleTime: 5 * 60 * 1000 // 5 minutes - }) - - // AI content generation mutation - const { mutate: generateContent, isPending: isGenerating } = useMutation({ - mutationFn: generateAiContent, - onSuccess: (result) => { - if (result.content) { - // Add to history - const historyEntry: GenerationHistory = { - id: Date.now().toString(), - content: result.content, - prompt: aiPrompt, - parameters: { ...aiParameters }, - timestamp: new Date() - } - setGenerationHistory((prev) => [historyEntry, ...prev.slice(0, 9)]) // Keep last 10 - - setContent(result.content) - toast.success("Content generated successfully!") - } - }, - onError: (error: Error) => { - toast.error(error.message) - } - }) - - const handleGenerateAiContent = (iterationInstruction?: string, previousContent?: string) => { - if (!isAiAvailable) { - onValidationError(aiUnavailableReason || "AI features are not available") - return - } - - if (!selectedIntegrationId) { - onValidationError("Please select a platform first.") - return - } - if (!aiPrompt.trim() && !iterationInstruction) { - onValidationError("Please enter a prompt for AI generation.") - return - } - - generateContent({ - data: { - prompt: iterationInstruction || aiPrompt, - integrationId: selectedIntegrationId, - temperature: aiParameters.temperature, - maxTokens: aiParameters.maxTokens, - styleOverride: aiParameters.styleOverride as "casual" | "formal" | "humorous" | "professional" | "conversational" | undefined, - toneOverride: aiParameters.toneOverride as - | "friendly" - | "professional" - | "authoritative" - | "inspirational" - | "educational" - | undefined, - lengthOverride: aiParameters.lengthOverride as "short" | "medium" | "long" | undefined, - useEmojisOverride: aiParameters.useEmojisOverride, - useHashtagsOverride: aiParameters.useHashtagsOverride, - customInstructionsOverride: aiParameters.customInstructionsOverride, - previousContent, - iterationInstruction: iterationInstruction || undefined - } - }) - } - - const handleQuickAdjustment = (adjustment: string) => { - const adjustmentPrompts: Record = { - shorter: "Make this shorter and more concise", - longer: "Expand this with more details and examples", - formal: "Make this more formal and professional", - casual: "Make this more casual and conversational", - humor: "Add some humor and personality to this", - engaging: "Make this more engaging and attention-grabbing" - } - - if (content && adjustmentPrompts[adjustment]) { - handleGenerateAiContent(adjustmentPrompts[adjustment], content) - } - } - - const handleUseHistoryItem = (historyItem: GenerationHistory) => { - setContent(historyItem.content) - setAiPrompt(historyItem.prompt) - setAiParameters(historyItem.parameters) - setShowGenerationHistory(false) - } - - const handlePublishNow = () => { - if (!selectedIntegrationId) { - onValidationError("Please select a platform.") - return - } - if (!content) { - onValidationError("Please enter some content.") - return - } - if (platformInfo?.destinationRequired && !selectedDestination) { - onValidationError("Please select a destination.") - return - } - - // Validate required fields - if (platformInfo?.requiredFields) { - for (const field of platformInfo.requiredFields) { - if (field.required && !additionalFields[field.key]) { - onValidationError(`${field.label} is required.`) - return - } - } - } - - onCreatePost({ - integrationId: selectedIntegrationId, - content, - scheduledAt: undefined, - destination: selectedDestination, - additionalFields: Object.keys(additionalFields).length > 0 ? additionalFields : undefined - }) - setIsPublishPopoverOpen(false) - } - - const handleSchedulePost = () => { - if (!selectedIntegrationId) { - onValidationError("Please select a platform.") - return - } - if (!content) { - onValidationError("Please enter some content.") - return - } - if (platformInfo?.destinationRequired && !selectedDestination) { - onValidationError("Please select a destination.") - return - } - if (!scheduledDateTime) { - onValidationError("Please select a date and time for scheduling.") - return - } - const scheduledDate = new Date(scheduledDateTime) - if (scheduledDate <= new Date()) { - onValidationError("Scheduled time must be in the future.") - return - } - - // Validate required fields - if (platformInfo?.requiredFields) { - for (const field of platformInfo.requiredFields) { - if (field.required && !additionalFields[field.key]) { - onValidationError(`${field.label} is required.`) - return - } - } - } - - onCreatePost({ - integrationId: selectedIntegrationId, - content, - scheduledAt: scheduledDate, - destination: selectedDestination, - additionalFields: Object.keys(additionalFields).length > 0 ? additionalFields : undefined - }) - setIsSchedulePopoverOpen(false) - } - - const handleClear = () => { - setContent("") - setAiPrompt("") - setScheduledDateTime("") - setSelectedDestination(undefined) - setCustomDestination("") - setShowCustomDestination(false) - setAdditionalFields({}) - onClear() - } - - const handleCustomDestination = () => { - if (!customDestination.trim()) { - onValidationError("Please enter a destination.") - return - } - if (!currentPlatform) { - onValidationError("Please select a platform first.") - return - } - if (!selectedIntegrationId) { - onValidationError("Please select an integration first.") - return - } - - // Use the backend function to create destination with platform-specific logic - createDestinationFromInput({ - data: { - platform: currentPlatform, - input: customDestination, - integrationId: selectedIntegrationId - } - }) - .then((destination) => { - setSelectedDestination(destination) - setShowCustomDestination(false) - setCustomDestination("") - }) - .catch((error) => { - onValidationError(error.message || "Failed to create destination") - }) - } - - const handleDestinationSelect = (destination: PostDestination) => { - setSelectedDestination(destination) - setShowCustomDestination(false) - setCustomDestination("") - } - - const handleAdditionalFieldChange = (key: string, value: string) => { - setAdditionalFields((prev) => ({ ...prev, [key]: value })) - } - - const getMinDateTime = () => { - const now = new Date() - now.setMinutes(now.getMinutes() + 5) - return now.toISOString().slice(0, 16) - } - - const renderAdditionalFields = () => { - if (!platformInfo?.requiredFields || platformInfo.requiredFields.length === 0) { - return null - } - - return ( -
- {platformInfo.requiredFields.map((field) => ( -
- - {field.type === "textarea" ? ( -