192 lines
6.2 KiB
TypeScript
192 lines
6.2 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { useCreateLiveBlog } from '@/queries/live-blogs';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
import { useNavigate } from '@tanstack/react-router';
|
|
import { Plus } from 'lucide-react';
|
|
|
|
interface CreateLiveBlogProps {
|
|
onSuccess?: () => void;
|
|
className?: string;
|
|
}
|
|
|
|
export function CreateLiveBlog({ onSuccess, className }: CreateLiveBlogProps) {
|
|
const navigate = useNavigate();
|
|
const [formData, setFormData] = useState({
|
|
title: '',
|
|
slug: '',
|
|
description: '',
|
|
status: 'draft' as 'draft' | 'live' | 'ended' | 'archived',
|
|
});
|
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
|
|
const createMutation = useCreateLiveBlog();
|
|
|
|
const generateSlug = (title: string) => {
|
|
return title
|
|
.toLowerCase()
|
|
.replace(/[^\w\s-]/g, '')
|
|
.replace(/\s+/g, '-')
|
|
.replace(/--+/g, '-')
|
|
.trim();
|
|
};
|
|
|
|
const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const title = e.target.value;
|
|
setFormData({
|
|
...formData,
|
|
title,
|
|
slug: formData.slug || generateSlug(title),
|
|
});
|
|
if (errors.title) setErrors({ ...errors, title: '' });
|
|
};
|
|
|
|
const handleSlugChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
setFormData({ ...formData, slug: e.target.value });
|
|
if (errors.slug) setErrors({ ...errors, slug: '' });
|
|
};
|
|
|
|
const validateForm = () => {
|
|
const newErrors: Record<string, string> = {};
|
|
|
|
if (!formData.title.trim()) {
|
|
newErrors.title = 'Title is required';
|
|
}
|
|
|
|
if (!formData.slug.trim()) {
|
|
newErrors.slug = 'Slug is required';
|
|
} else if (!/^[a-z0-9-]+$/.test(formData.slug)) {
|
|
newErrors.slug = 'Slug can only contain lowercase letters, numbers, and hyphens';
|
|
}
|
|
|
|
setErrors(newErrors);
|
|
return Object.keys(newErrors).length === 0;
|
|
};
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
if (!validateForm()) return;
|
|
|
|
try {
|
|
const result = await createMutation.mutateAsync(formData);
|
|
|
|
// Reset form
|
|
setFormData({
|
|
title: '',
|
|
slug: '',
|
|
description: '',
|
|
status: 'draft',
|
|
});
|
|
setErrors({});
|
|
|
|
// Navigate to the new live blog
|
|
navigate({ to: `/admin/live-blogs/${result.slug}` });
|
|
|
|
if (onSuccess) onSuccess();
|
|
} catch (error) {
|
|
console.error('Failed to create live blog:', error);
|
|
setErrors({ submit: 'Failed to create live blog. Please try again.' });
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Card className={className}>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Plus className="w-5 h-5" />
|
|
Create New Live Blog
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="title">Title *</Label>
|
|
<Input
|
|
id="title"
|
|
value={formData.title}
|
|
onChange={handleTitleChange}
|
|
placeholder="Enter live blog title"
|
|
disabled={createMutation.isPending}
|
|
/>
|
|
{errors.title && (
|
|
<p className="text-sm text-destructive">{errors.title}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="slug">Slug *</Label>
|
|
<Input
|
|
id="slug"
|
|
value={formData.slug}
|
|
onChange={handleSlugChange}
|
|
placeholder="URL-friendly identifier"
|
|
disabled={createMutation.isPending}
|
|
/>
|
|
{errors.slug && (
|
|
<p className="text-sm text-destructive">{errors.slug}</p>
|
|
)}
|
|
<p className="text-xs text-muted-foreground">
|
|
This will be used in the URL: /live-blogs/your-slug-here
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="description">Description</Label>
|
|
<Textarea
|
|
id="description"
|
|
value={formData.description}
|
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
|
placeholder="Optional description for this live blog"
|
|
rows={3}
|
|
disabled={createMutation.isPending}
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="status">Initial Status</Label>
|
|
<Select
|
|
value={formData.status}
|
|
onValueChange={(value: 'draft' | 'live' | 'ended' | 'archived') =>
|
|
setFormData({ ...formData, status: value })
|
|
}
|
|
disabled={createMutation.isPending}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select status" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="draft">Draft (Not visible to public)</SelectItem>
|
|
<SelectItem value="live">Live (Visible and accepting updates)</SelectItem>
|
|
<SelectItem value="ended">Ended (Visible but not accepting updates)</SelectItem>
|
|
<SelectItem value="archived">Archived (Hidden from public)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<p className="text-xs text-muted-foreground">
|
|
You can change this later in the settings
|
|
</p>
|
|
</div>
|
|
|
|
{errors.submit && (
|
|
<div className="p-3 bg-destructive/10 border border-destructive/20 rounded-lg">
|
|
<p className="text-sm text-destructive">{errors.submit}</p>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex justify-end gap-2 pt-4">
|
|
<Button
|
|
type="submit"
|
|
disabled={createMutation.isPending}
|
|
>
|
|
{createMutation.isPending ? 'Creating...' : 'Create Live Blog'}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
} |