mirror of
https://github.com/SamyRai/tercul-frontend.git
synced 2025-12-27 04:51:34 +00:00
Enhance blog creation with new editor and allow management of article tags
Integrate RichTextEditor into BlogEditor and implement TagManager component using React Hook Form. Replit-Commit-Author: Agent Replit-Commit-Session-Id: bddfbb2b-6d6b-457b-b18c-05792cdaa035 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/39b5c689-6e8a-4d5a-9792-69cc81a56534/049c4c65-a4ba-4585-925d-806602cc4568.jpg
This commit is contained in:
parent
276dab9e35
commit
ddb691f371
@ -1 +1,59 @@
|
|||||||
null
|
import { FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from "@/components/ui/form";
|
||||||
|
import { RichTextEditor } from "@/components/ui/rich-text-editor";
|
||||||
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
|
interface BlogEditorProps {
|
||||||
|
name: string;
|
||||||
|
label?: string;
|
||||||
|
description?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blog editor component that uses the RichTextEditor
|
||||||
|
*
|
||||||
|
* This component wraps the RichTextEditor with a form field and adds
|
||||||
|
* write/preview tabs functionality specific for blog content.
|
||||||
|
*/
|
||||||
|
export function BlogEditor({
|
||||||
|
name,
|
||||||
|
label = "Content",
|
||||||
|
description = "You can use markdown formatting. The article will be attributed to your account.",
|
||||||
|
placeholder = "Write your article content here..."
|
||||||
|
}: BlogEditorProps) {
|
||||||
|
const form = useFormContext();
|
||||||
|
const [content, setContent] = useState(form.getValues(name) || "");
|
||||||
|
|
||||||
|
// Update form field when content changes
|
||||||
|
const handleContentChange = (value: string) => {
|
||||||
|
setContent(value);
|
||||||
|
form.setValue(name, value, { shouldValidate: true, shouldDirty: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name={name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{label}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<RichTextEditor
|
||||||
|
value={field.value || ""}
|
||||||
|
onChange={handleContentChange}
|
||||||
|
placeholder={placeholder}
|
||||||
|
height="min-h-96"
|
||||||
|
className="font-serif leading-relaxed"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{description}
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1 +1,129 @@
|
|||||||
null
|
import { useState, useEffect } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select";
|
||||||
|
import { X, Plus } from "lucide-react";
|
||||||
|
import { FormLabel } from "@/components/ui/form";
|
||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
import { Tag } from "@shared/schema";
|
||||||
|
|
||||||
|
interface TagManagerProps {
|
||||||
|
name: string;
|
||||||
|
tags: Tag[] | undefined;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag manager component for selecting and managing tags
|
||||||
|
*/
|
||||||
|
export function TagManager({
|
||||||
|
name,
|
||||||
|
tags,
|
||||||
|
label = "Tags"
|
||||||
|
}: TagManagerProps) {
|
||||||
|
const form = useFormContext();
|
||||||
|
const [selectedTags, setSelectedTags] = useState<Tag[]>([]);
|
||||||
|
const [selectedTagId, setSelectedTagId] = useState<string>("");
|
||||||
|
|
||||||
|
// Initialize selected tags from form values
|
||||||
|
useEffect(() => {
|
||||||
|
const formTags = form.getValues(name) || [];
|
||||||
|
if (tags && formTags.length > 0) {
|
||||||
|
const initialTags = formTags.map(id => tags.find(t => t.id === id)).filter(Boolean) as Tag[];
|
||||||
|
setSelectedTags(initialTags);
|
||||||
|
}
|
||||||
|
}, [tags, form, name]);
|
||||||
|
|
||||||
|
// Handle tag selection
|
||||||
|
const handleAddTag = () => {
|
||||||
|
if (!selectedTagId) return;
|
||||||
|
|
||||||
|
const tagId = parseInt(selectedTagId, 10);
|
||||||
|
const tag = tags?.find(t => t.id === tagId);
|
||||||
|
|
||||||
|
if (tag && !selectedTags.some(t => t.id === tag.id)) {
|
||||||
|
setSelectedTags([...selectedTags, tag]);
|
||||||
|
|
||||||
|
// Update form values
|
||||||
|
const currentTags = form.getValues(name) || [];
|
||||||
|
form.setValue(name, [...currentTags, tag.id], { shouldValidate: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedTagId("");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle tag removal
|
||||||
|
const handleRemoveTag = (tagId: number) => {
|
||||||
|
setSelectedTags(selectedTags.filter(tag => tag.id !== tagId));
|
||||||
|
|
||||||
|
// Update form values
|
||||||
|
const currentTags = form.getValues(name) || [];
|
||||||
|
form.setValue(name, currentTags.filter(id => id !== tagId), { shouldValidate: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormLabel>{label}</FormLabel>
|
||||||
|
<div className="flex flex-wrap gap-2 mb-2">
|
||||||
|
{selectedTags.map(tag => (
|
||||||
|
<Badge
|
||||||
|
key={tag.id}
|
||||||
|
variant="secondary"
|
||||||
|
className="px-2 py-1 flex items-center gap-1"
|
||||||
|
>
|
||||||
|
{tag.name}
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-4 w-4 p-0 text-navy/60 dark:text-cream/60 hover:text-navy hover:dark:text-cream hover:bg-transparent"
|
||||||
|
onClick={() => handleRemoveTag(tag.id)}
|
||||||
|
>
|
||||||
|
<X className="h-3 w-3" />
|
||||||
|
<span className="sr-only">Remove {tag.name}</span>
|
||||||
|
</Button>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{selectedTags.length === 0 && (
|
||||||
|
<p className="text-sm text-navy/60 dark:text-cream/60">
|
||||||
|
No tags selected
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Select
|
||||||
|
value={selectedTagId}
|
||||||
|
onValueChange={setSelectedTagId}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[200px]">
|
||||||
|
<SelectValue placeholder="Select a tag" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{tags?.map(tag => (
|
||||||
|
<SelectItem
|
||||||
|
key={tag.id}
|
||||||
|
value={tag.id.toString()}
|
||||||
|
disabled={selectedTags.some(t => t.id === tag.id)}
|
||||||
|
>
|
||||||
|
{tag.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleAddTag}
|
||||||
|
disabled={!selectedTagId}
|
||||||
|
className="flex items-center gap-1"
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
<span>Add Tag</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user