literate programming in the editor.jsx
This commit is contained in:
@@ -33,7 +33,7 @@
|
|||||||
* - Form state management
|
* - Form state management
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import MDEditor from '@uiw/react-md-editor';
|
import MDEditor from '@uiw/react-md-editor';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
@@ -98,187 +98,537 @@ export default function Editor() {
|
|||||||
* 3. Make state dependencies clearer in useEffect hooks
|
* 3. Make state dependencies clearer in useEffect hooks
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Content Management States
|
/**
|
||||||
const [title, setTitle] = useState(''); // Main post title
|
* Content Management State Section
|
||||||
const [content, setContent] = useState(''); // Markdown content
|
*
|
||||||
const [status, setStatus] = useState('draft'); // Publication status
|
* This section establishes the core state variables that manage our post's content
|
||||||
const [publishDate, setPublishDate] = useState(''); // Scheduled publish date
|
* using React's useState hook. Each state variable is responsible for a specific
|
||||||
|
* aspect of the post's data.
|
||||||
|
*
|
||||||
|
* Implementation Pattern:
|
||||||
|
* For each piece of state, we use array destructuring with useState:
|
||||||
|
* const [value, setValue] = useState(initialValue)
|
||||||
|
* - value: current state value
|
||||||
|
* - setValue: function to update the state
|
||||||
|
* - initialValue: starting value for the state
|
||||||
|
*/
|
||||||
|
|
||||||
// Tag Management States
|
// Title State
|
||||||
const [tags, setTags] = useState([]); // Array of current tags
|
// Controls the post's title field
|
||||||
const [tagInput, setTagInput] = useState(''); // Current tag input value
|
const [title, setTitle] = useState(''); // Initialize empty
|
||||||
|
// - title: Holds current title text
|
||||||
|
// - setTitle: Function called on title input changes
|
||||||
|
// - Empty string initialization prevents "undefined" in input
|
||||||
|
// Example: setTitle("My New Blog Post")
|
||||||
|
|
||||||
// UI State Management
|
// Content State
|
||||||
const [saving, setSaving] = useState(false); // Tracks save operation status
|
// Manages the main markdown content of the post
|
||||||
const [error, setError] = useState(''); // Holds error messages
|
const [content, setContent] = useState(''); // Initialize empty
|
||||||
|
// - content: Stores current markdown text
|
||||||
|
// - setContent: Called by MDEditor on content changes
|
||||||
|
// - Empty string prevents MDEditor warnings
|
||||||
|
// Example: setContent("# My Heading\n\nPost content...")
|
||||||
|
|
||||||
|
// Publication Status State
|
||||||
|
// Tracks whether post is a draft or published
|
||||||
|
const [status, setStatus] = useState('draft'); // Initialize as draft
|
||||||
|
// - status: Current publication state ('draft' or 'published')
|
||||||
|
// - setStatus: Updates publication status
|
||||||
|
// - 'draft' default ensures unpublished start state
|
||||||
|
// This state determines:
|
||||||
|
// 1. Which save button to display ('Publish' vs 'Update')
|
||||||
|
// 2. Whether to show publish date picker
|
||||||
|
// 3. How post is saved to backend API
|
||||||
|
// Example: setStatus('published')
|
||||||
|
|
||||||
|
// Publication Date State
|
||||||
|
// Handles scheduled publishing functionality
|
||||||
|
const [publishDate, setPublishDate] = useState(''); // Initialize empty
|
||||||
|
// - publishDate: Scheduled publication date/time
|
||||||
|
// - setPublishDate: Updates scheduled publication date
|
||||||
|
// - Empty string works with datetime-local input
|
||||||
|
// - Only used when status is 'published'
|
||||||
|
// Format: YYYY-MM-DDThh:mm (HTML5 datetime-local format)
|
||||||
|
// Example: setPublishDate('2024-12-01T15:30')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Why Individual State Variables?
|
||||||
|
*
|
||||||
|
* We use separate useState calls instead of a single state object because:
|
||||||
|
* 1. Allows granular updates without re-rendering unrelated parts
|
||||||
|
* 2. Simplifies state updates (no spread operator needed)
|
||||||
|
* 3. Makes state dependencies clearer in useEffect hooks
|
||||||
|
* 4. Enables easier testing and debugging
|
||||||
|
*
|
||||||
|
* Alternative Approach (Not Used):
|
||||||
|
* const [postData, setPostData] = useState({
|
||||||
|
* title: '',
|
||||||
|
* content: '',
|
||||||
|
* status: 'draft',
|
||||||
|
* publishDate: ''
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* The above approach would require spread operator for updates:
|
||||||
|
* setPostData(prev => ({ ...prev, title: newTitle }));
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag Management and UI State Section
|
||||||
|
*
|
||||||
|
* This section establishes two distinct groups of state variables:
|
||||||
|
* 1. Tag management - Handling post categorization
|
||||||
|
* 2. UI state management - Tracking application status
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag Management States
|
||||||
|
*
|
||||||
|
* These states work together to implement a dynamic tag input system:
|
||||||
|
* - tags: Stores the final list of tags
|
||||||
|
* - tagInput: Manages the temporary input state
|
||||||
|
*
|
||||||
|
* Implementation Example:
|
||||||
|
* 1. User types "react" in input (tagInput updates)
|
||||||
|
* 2. User presses Enter
|
||||||
|
* 3. "react" is added to tags array
|
||||||
|
* 4. tagInput is cleared
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Tags Collection State
|
||||||
|
// Maintains the list of all tags attached to the post
|
||||||
|
const [tags, setTags] = useState([]); // Initialize as empty array
|
||||||
|
// - tags: Array of strings, each representing a tag
|
||||||
|
// - setTags: Updates entire tags collection
|
||||||
|
// - Empty array initialization prevents map/filter errors
|
||||||
|
// Example: setTags(['react', 'javascript', 'web'])
|
||||||
|
// Used in:
|
||||||
|
// 1. Displaying tag list
|
||||||
|
// 2. API submission
|
||||||
|
// 3. Tag validation (preventing duplicates)
|
||||||
|
|
||||||
|
// Tag Input State
|
||||||
|
// Manages the temporary input value for new tags
|
||||||
|
const [tagInput, setTagInput] = useState(''); // Initialize empty
|
||||||
|
// - tagInput: Current value in tag input field
|
||||||
|
// - setTagInput: Updates temporary tag value
|
||||||
|
// - Cleared after tag is added to main tags array
|
||||||
|
// Example: setTagInput('react')
|
||||||
|
// Controlled component pattern:
|
||||||
|
// <input value={tagInput} onChange={e => setTagInput(e.target.value)} />
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI State Management
|
||||||
|
*
|
||||||
|
* These states handle application-level UI states:
|
||||||
|
* - saving: Loading/processing indicators
|
||||||
|
* - error: Error message display
|
||||||
|
*
|
||||||
|
* Together they manage the user feedback system during
|
||||||
|
* asynchronous operations and error conditions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Saving State
|
||||||
|
// Tracks whether a save operation is in progress
|
||||||
|
const [saving, setSaving] = useState(false); // Initialize as not saving
|
||||||
|
// - saving: Boolean indicating save operation status
|
||||||
|
// - setSaving: Toggles saving indicator
|
||||||
|
// Used to:
|
||||||
|
// 1. Disable save buttons during operation
|
||||||
|
// 2. Show loading indicators
|
||||||
|
// 3. Prevent duplicate save attempts
|
||||||
|
// Example flow:
|
||||||
|
// setSaving(true) // Start save operation
|
||||||
|
// await saveData() // Perform save
|
||||||
|
// setSaving(false) // Complete save operation
|
||||||
|
|
||||||
|
// Error State
|
||||||
|
// Manages error messages for user feedback
|
||||||
|
const [error, setError] = useState(''); // Initialize as no error
|
||||||
|
// - error: Current error message (empty string = no error)
|
||||||
|
// - setError: Updates error message
|
||||||
|
// Used for:
|
||||||
|
// 1. API error messages
|
||||||
|
// 2. Validation error messages
|
||||||
|
// 3. User feedback
|
||||||
|
// Example: setError('Failed to save post')
|
||||||
|
// Clear error: setError('')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Why These Groupings?
|
||||||
|
*
|
||||||
|
* 1. Tag Management:
|
||||||
|
* - Separates final (tags) from temporary (tagInput) state
|
||||||
|
* - Enables controlled input pattern
|
||||||
|
* - Simplifies tag addition/removal logic
|
||||||
|
*
|
||||||
|
* 2. UI State:
|
||||||
|
* - Centralizes application status management
|
||||||
|
* - Keeps user feedback mechanisms separate from data
|
||||||
|
* - Enables consistent error handling patterns
|
||||||
|
*
|
||||||
|
* Alternative Approaches Not Used:
|
||||||
|
* 1. Combined tag state:
|
||||||
|
* const [tagState, setTagState] = useState({ tags: [], input: '' });
|
||||||
|
* - Would require more complex updates
|
||||||
|
* - Would cause unnecessary re-renders
|
||||||
|
*
|
||||||
|
* 2. Context for UI state:
|
||||||
|
* - Overkill for this component's scope
|
||||||
|
* - Would add unnecessary complexity
|
||||||
|
*/
|
||||||
|
|
||||||
// Router Hooks
|
// Router Hooks
|
||||||
const navigate = useNavigate(); // Programmatic navigation
|
const navigate = useNavigate(); // Programmatic navigation
|
||||||
const { id } = useParams(); // Extract post ID from URL if editing
|
const { id } = useParams(); // Extract post ID from URL if editing
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Post Loading Effect
|
* Post Loading Effect Section
|
||||||
*
|
*
|
||||||
* This effect runs when the component mounts and whenever the ID changes.
|
* This useEffect hook and associated loadPost function handle fetching
|
||||||
* It's responsible for loading existing post data when in edit mode.
|
* and populating data for existing posts in edit mode.
|
||||||
*
|
*
|
||||||
* Key Concepts:
|
* Component Lifecycle:
|
||||||
* 1. Conditional Execution:
|
* 1. Component mounts
|
||||||
* Only runs if an ID is present (edit mode)
|
* 2. Effect checks for post ID
|
||||||
*
|
* 3. If ID exists, loadPost is called
|
||||||
* 2. Dependency Array:
|
* 4. Post data populates form fields
|
||||||
* [id] ensures the effect reruns if the URL parameter changes
|
|
||||||
*
|
|
||||||
* 3. Error Handling:
|
|
||||||
* Errors during loading are caught and displayed to the user
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Post Loading Effect
|
||||||
|
// Automatically fetches post data when component mounts in edit mode
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Only load post data if we have an ID (edit mode)
|
||||||
|
// Skip for new posts (no ID present)
|
||||||
if (id) {
|
if (id) {
|
||||||
loadPost();
|
loadPost();
|
||||||
}
|
}
|
||||||
}, [id]);
|
}, [id]); // Re-run if ID changes (rare, but possible with route changes)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Post Loading Implementation
|
* Post Loading Function
|
||||||
|
* Fetches existing post data from the API and populates the form.
|
||||||
*
|
*
|
||||||
* Fetches and populates form data for an existing post.
|
* Data Flow:
|
||||||
|
* 1. API request with authentication
|
||||||
|
* 2. Response processing
|
||||||
|
* 3. State updates for all form fields
|
||||||
|
* 4. Error handling if needed
|
||||||
*
|
*
|
||||||
* Technical Implementation Details:
|
* @async - Returns a promise that resolves when post is loaded
|
||||||
* 1. Authentication:
|
* @throws {Error} - When API request fails or data is invalid
|
||||||
* - Sends Bearer token in Authorization header
|
|
||||||
* - Token is retrieved from localStorage
|
|
||||||
*
|
|
||||||
* 2. Data Transformation:
|
|
||||||
* - Converts API response format to component state format
|
|
||||||
* - Handles potential missing data gracefully
|
|
||||||
*
|
|
||||||
* 3. Error Handling:
|
|
||||||
* - Catches and logs API errors
|
|
||||||
* - Updates UI error state for user feedback
|
|
||||||
*
|
|
||||||
* @async
|
|
||||||
* @throws {Error} When API request fails
|
|
||||||
*/
|
*/
|
||||||
const loadPost = async () => {
|
const loadPost = async () => {
|
||||||
try {
|
try {
|
||||||
// API Request Configuration
|
/**
|
||||||
|
* API Request Section
|
||||||
|
* Send GET request to fetch post data
|
||||||
|
*
|
||||||
|
* Request Details:
|
||||||
|
* - URL: Constructed using environment variable and post ID
|
||||||
|
* - Headers: Includes JWT token for authentication
|
||||||
|
* - Method: GET (read operation)
|
||||||
|
*/
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`${import.meta.env.VITE_API_URL}/api/posts/${id}`,
|
`${import.meta.env.VITE_API_URL}/api/posts/${id}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
// Authorization using JWT pattern
|
||||||
|
// Token retrieved from browser storage
|
||||||
Authorization: `Bearer ${localStorage.getItem('token')}`
|
Authorization: `Bearer ${localStorage.getItem('token')}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Destructure and Transform API Response
|
/**
|
||||||
const post = response.data;
|
* Data Processing Section
|
||||||
|
* Extract post data from response and update all form states
|
||||||
|
*/
|
||||||
|
const post = response.data; // Extract post object from response
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State Updates Section
|
||||||
|
* Update each form field with data from API
|
||||||
|
* Each setter is called separately to:
|
||||||
|
* 1. Keep updates atomic
|
||||||
|
* 2. Maintain clear data flow
|
||||||
|
* 3. Allow React to batch updates efficiently
|
||||||
|
*/
|
||||||
|
|
||||||
// Update Component State
|
// Update title state
|
||||||
// Each setter is called individually to:
|
// Direct assignment - no transformation needed
|
||||||
// 1. Maintain clear data flow
|
|
||||||
// 2. Allow React to batch updates
|
|
||||||
// 3. Keep transformations clear and separated
|
|
||||||
setTitle(post.title);
|
setTitle(post.title);
|
||||||
|
|
||||||
|
// Update markdown content
|
||||||
|
// Directly set editor content from API response
|
||||||
setContent(post.markdown_content);
|
setContent(post.markdown_content);
|
||||||
|
|
||||||
|
// Update publication status
|
||||||
|
// Directly assign status string
|
||||||
setStatus(post.status);
|
setStatus(post.status);
|
||||||
|
|
||||||
// Transform publication date
|
/**
|
||||||
// - Converts ISO string to local datetime-local input format
|
* Publication Date Processing
|
||||||
// - Handles null/undefined cases with empty string
|
* Transform ISO date string to format required by datetime-local input
|
||||||
|
*
|
||||||
|
* Steps:
|
||||||
|
* 1. Check if published_at exists
|
||||||
|
* 2. If exists: Convert to datetime-local format
|
||||||
|
* 3. If null/undefined: Use empty string
|
||||||
|
*
|
||||||
|
* Format Conversion:
|
||||||
|
* From: 2024-12-01T15:30:00Z (ISO)
|
||||||
|
* To: 2024-12-01T15:30 (datetime-local)
|
||||||
|
*/
|
||||||
setPublishDate(
|
setPublishDate(
|
||||||
post.published_at
|
post.published_at
|
||||||
? new Date(post.published_at).toISOString().slice(0, 16)
|
? new Date(post.published_at).toISOString().slice(0, 16)
|
||||||
: ''
|
: ''
|
||||||
);
|
);
|
||||||
|
|
||||||
// Transform tags from API format to component format
|
/**
|
||||||
// - Maps array of objects to array of strings
|
* Tags Processing
|
||||||
// - Handles null/undefined with empty array fallback
|
* Transform API tag format to component format
|
||||||
|
*
|
||||||
|
* Transformation:
|
||||||
|
* From: [{name: 'tag1'}, {name: 'tag2'}] (API format)
|
||||||
|
* To: ['tag1', 'tag2'] (Component format)
|
||||||
|
*
|
||||||
|
* Safety Features:
|
||||||
|
* - Optional chaining (?.) handles null/undefined tags
|
||||||
|
* - Fallback to empty array if mapping fails
|
||||||
|
*/
|
||||||
setTags(post.tags?.map(tag => tag.name) || []);
|
setTags(post.tags?.map(tag => tag.name) || []);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Error Handling
|
/**
|
||||||
console.error('Error loading post:', err);
|
* Error Handling Section
|
||||||
setError('Failed to load post');
|
* Handle any errors during post loading
|
||||||
|
*
|
||||||
|
* Error Flow:
|
||||||
|
* 1. Log full error for debugging
|
||||||
|
* 2. Set user-friendly error message
|
||||||
|
* 3. Component will display error to user
|
||||||
|
*/
|
||||||
|
console.error('Error loading post:', err); // Detailed error for debugging
|
||||||
|
setError('Failed to load post'); // User-friendly message
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Post Saving Implementation
|
* Post Saving System
|
||||||
*
|
*
|
||||||
* Handles both creating new posts and updating existing ones.
|
* A comprehensive implementation of the post saving mechanism that handles both
|
||||||
*
|
* creation and updates of blog posts within the application. This system
|
||||||
* Technical Design:
|
* implements optimistic UI updates, handles race conditions, manages state
|
||||||
* 1. State Management:
|
* transitions, and provides user feedback throughout the save process.
|
||||||
* - Sets saving flag for UI feedback
|
*
|
||||||
* - Clears previous errors before attempt
|
* Core Responsibilities:
|
||||||
* - Updates UI state after completion
|
* 1. State Synchronization:
|
||||||
*
|
* - Manages UI feedback states
|
||||||
* 2. Data Preparation:
|
* - Handles optimistic updates
|
||||||
* - Constructs API payload
|
* - Maintains data consistency
|
||||||
* - Handles publication date logic
|
*
|
||||||
* - Manages tags format
|
* 2. Data Processing:
|
||||||
*
|
* - Validates input data
|
||||||
* 3. API Integration:
|
* - Transforms data for API
|
||||||
* - Determines POST vs PUT based on ID presence
|
* - Handles date/time calculations
|
||||||
* - Handles authentication
|
*
|
||||||
* - Processes response
|
* 3. API Communication:
|
||||||
*
|
* - Manages HTTP requests
|
||||||
* @param {string} newStatus - Target status ('draft' or 'published')
|
* - Handles authentication
|
||||||
* @throws {Error} When API request fails
|
* - Processes API responses
|
||||||
*/
|
*
|
||||||
|
* 4. Error Management:
|
||||||
|
* - Provides error feedback
|
||||||
|
* - Handles edge cases
|
||||||
|
* - Maintains system stability
|
||||||
|
*
|
||||||
|
* Technical Concepts Used:
|
||||||
|
* 1. Promise-based async/await pattern
|
||||||
|
* 2. REST API conventions
|
||||||
|
* 3. JWT authentication
|
||||||
|
* 4. State management patterns
|
||||||
|
* 5. Error boundaries
|
||||||
|
*
|
||||||
|
* @param {string} newStatus - The target publication status for the post
|
||||||
|
* Must be either 'draft' or 'published'
|
||||||
|
* Defaults to current status if not provided
|
||||||
|
*
|
||||||
|
* @throws {Error} API_ERROR When network request fails
|
||||||
|
* @throws {Error} AUTH_ERROR When authentication is invalid
|
||||||
|
* @throws {Error} VALIDATION_ERROR When data validation fails
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>} Resolves when save operation completes
|
||||||
|
* Rejects if operation fails
|
||||||
|
*
|
||||||
|
* @fires navigate Triggers navigation on successful save
|
||||||
|
* @fires setSaving Updates saving state for UI feedback
|
||||||
|
* @fires setError Updates error state for user feedback
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Save as draft
|
||||||
|
* await handleSave('draft');
|
||||||
|
*
|
||||||
|
* // Publish post
|
||||||
|
* await handleSave('published');
|
||||||
|
*/
|
||||||
const handleSave = async (newStatus = status) => {
|
const handleSave = async (newStatus = status) => {
|
||||||
// Update UI State
|
/**
|
||||||
setSaving(true); // Disable save buttons
|
* Phase 1: Pre-Save State Management
|
||||||
setError(''); // Clear previous errors
|
*
|
||||||
|
* Initialize the UI state for the save operation:
|
||||||
// Prepare API Payload
|
* 1. Set saving indicator to prevent duplicate submissions
|
||||||
|
* 2. Clear any previous error messages
|
||||||
|
*
|
||||||
|
* State Management Pattern:
|
||||||
|
* - Use React setState for atomic updates
|
||||||
|
* - Order matters: saving before error clear
|
||||||
|
* - Both updates will be batched by React
|
||||||
|
*/
|
||||||
|
setSaving(true); // Activate save indicator
|
||||||
|
setError(''); // Reset error state
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 2: Data Preparation
|
||||||
|
*
|
||||||
|
* Construct the API payload with all required data transformations:
|
||||||
|
* - All fields are included for consistency
|
||||||
|
* - Dates are properly formatted
|
||||||
|
* - Status is explicitly set
|
||||||
|
*
|
||||||
|
* Data Transformation Rules:
|
||||||
|
* 1. Title & Content: Direct pass-through
|
||||||
|
* 2. Status: Use new status from parameter
|
||||||
|
* 3. PublishedAt: Complex logic based on status
|
||||||
|
* 4. Tags: Array format validation
|
||||||
|
*/
|
||||||
const data = {
|
const data = {
|
||||||
title,
|
// Core Content Fields
|
||||||
content,
|
title, // Post title (string)
|
||||||
status: newStatus,
|
content, // Markdown content (string)
|
||||||
// Publication Date Logic:
|
|
||||||
// 1. If publishing, use provided date or current time
|
|
||||||
// 2. If draft, set to null
|
|
||||||
publishedAt: newStatus === 'published'
|
|
||||||
? (publishDate || new Date().toISOString())
|
|
||||||
: null,
|
|
||||||
tags
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
// API Request Configuration
|
|
||||||
const headers = {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem('token')}`
|
|
||||||
};
|
|
||||||
|
|
||||||
// Determine API Action:
|
// Publication Status
|
||||||
// - PUT request for existing posts (id present)
|
status: newStatus, // 'draft' or 'published'
|
||||||
// - POST request for new posts (no id)
|
|
||||||
|
/**
|
||||||
|
* Publication Date Logic
|
||||||
|
*
|
||||||
|
* Complex ternary operation that:
|
||||||
|
* 1. Checks if status is 'published'
|
||||||
|
* 2. If true: Uses provided date or generates current timestamp
|
||||||
|
* 3. If false: Sets null for drafts
|
||||||
|
*
|
||||||
|
* Date Handling Rules:
|
||||||
|
* - Published posts must have a date
|
||||||
|
* - Drafts must have null date
|
||||||
|
* - Current time is fallback for published posts
|
||||||
|
*
|
||||||
|
* @type {string|null} ISO timestamp or null
|
||||||
|
*/
|
||||||
|
publishedAt: newStatus === 'published'
|
||||||
|
? (publishDate || new Date().toISOString())
|
||||||
|
: null,
|
||||||
|
|
||||||
|
// Tags Array
|
||||||
|
tags // Array<string>
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
/**
|
||||||
|
* Phase 3: API Communication
|
||||||
|
*
|
||||||
|
* Handle the actual API request with proper authentication:
|
||||||
|
* 1. Configure request headers
|
||||||
|
* 2. Determine request type (PUT vs POST)
|
||||||
|
* 3. Execute request
|
||||||
|
*
|
||||||
|
* Authentication:
|
||||||
|
* - JWT token from localStorage
|
||||||
|
* - Bearer token pattern
|
||||||
|
* - Token validation happens server-side
|
||||||
|
*/
|
||||||
|
const headers = {
|
||||||
|
Authorization: `Bearer ${localStorage.getItem('token')}`
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request Type Determination
|
||||||
|
*
|
||||||
|
* Logic:
|
||||||
|
* - PUT: Update existing post (id exists)
|
||||||
|
* - POST: Create new post (no id)
|
||||||
|
*
|
||||||
|
* RESTful Conventions:
|
||||||
|
* - PUT is idempotent
|
||||||
|
* - POST creates new resources
|
||||||
|
* - URLs follow REST patterns
|
||||||
|
*/
|
||||||
if (id) {
|
if (id) {
|
||||||
|
/**
|
||||||
|
* Update Existing Post
|
||||||
|
*
|
||||||
|
* PUT request to update existing post:
|
||||||
|
* 1. URL includes post ID
|
||||||
|
* 2. Sends complete post data
|
||||||
|
* 3. Expects 200 OK response
|
||||||
|
*/
|
||||||
await axios.put(
|
await axios.put(
|
||||||
`${import.meta.env.VITE_API_URL}/api/posts/${id}`,
|
`${import.meta.env.VITE_API_URL}/api/posts/${id}`,
|
||||||
data,
|
data,
|
||||||
{ headers }
|
{ headers }
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
/**
|
||||||
|
* Create New Post
|
||||||
|
*
|
||||||
|
* POST request to create new post:
|
||||||
|
* 1. URL is collection endpoint
|
||||||
|
* 2. Sends complete post data
|
||||||
|
* 3. Expects 201 Created response
|
||||||
|
*/
|
||||||
await axios.post(
|
await axios.post(
|
||||||
`${import.meta.env.VITE_API_URL}/api/posts`,
|
`${import.meta.env.VITE_API_URL}/api/posts`,
|
||||||
data,
|
data,
|
||||||
{ headers }
|
{ headers }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Success: Navigate to dashboard
|
/**
|
||||||
|
* Phase 4: Success Handling
|
||||||
|
*
|
||||||
|
* After successful save:
|
||||||
|
* 1. Navigate to dashboard
|
||||||
|
* 2. UI state will be reset in finally block
|
||||||
|
*/
|
||||||
navigate('/dashboard');
|
navigate('/dashboard');
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Error Handling
|
/**
|
||||||
|
* Phase 5: Error Handling
|
||||||
|
*
|
||||||
|
* Comprehensive error handling:
|
||||||
|
* 1. Log full error for debugging
|
||||||
|
* 2. Set user-friendly error message
|
||||||
|
* 3. Error state will trigger UI update
|
||||||
|
*
|
||||||
|
* Error Categories:
|
||||||
|
* - Network errors
|
||||||
|
* - Authentication errors
|
||||||
|
* - Validation errors
|
||||||
|
* - Server errors
|
||||||
|
*/
|
||||||
console.error('Error saving post:', err);
|
console.error('Error saving post:', err);
|
||||||
setError('Failed to save post');
|
setError('Failed to save post');
|
||||||
} finally {
|
} finally {
|
||||||
// Reset UI State
|
/**
|
||||||
|
* Phase 6: Cleanup
|
||||||
|
*
|
||||||
|
* Reset UI state regardless of outcome:
|
||||||
|
* 1. Clear saving indicator
|
||||||
|
* 2. Allow new save attempts
|
||||||
|
*
|
||||||
|
* Always executes to prevent UI lockup
|
||||||
|
*/
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -463,7 +813,8 @@ export default function Editor() {
|
|||||||
{/* Title Input
|
{/* Title Input
|
||||||
Full-width input for post title
|
Full-width input for post title
|
||||||
Updates state directly on change
|
Updates state directly on change
|
||||||
<input
|
*/}
|
||||||
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Post title"
|
placeholder="Post title"
|
||||||
value={title}
|
value={title}
|
||||||
|
|||||||
Reference in New Issue
Block a user