checkpoint
need polishing
This commit is contained in:
1 parent
0351726eeb
commit
5a457fa99f
11 files changed
+1610
-90
No files matched your search
@@ -0,0 +1,192 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user