- Updated page titles to be more descriptive: * Prompts: 'AI Prompts Management' → 'Customize Your AI Writer' * AuthorProfiles: 'Author Profiles' → 'Choose Your Writing Voice' * Strategies: 'Content Strategies' → 'Your Content Strategies' * ImageTesting: 'Image Testing' → 'Test Your Image Generator' * Dashboard: 'Thinker Dashboard' → 'Your AI Configuration Dashboard' - Updated navigation tab labels across all Thinker pages: * 'Prompts' → 'AI Instructions' * 'Author Profiles' → 'Writing Voices' * 'Strategies' → 'Content Strategies' * 'Image Testing' stays the same - Updated Prompts page section titles: * 'Planner Prompts' → 'Keyword & Topic Instructions' * 'Writer Prompts' → 'Article Writing Instructions' * Updated descriptions to be more conversational Part of comprehensive UX text improvement initiative.
176 lines
6.2 KiB
TypeScript
176 lines
6.2 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import PageMeta from '../../components/common/PageMeta';
|
|
import PageHeader from '../../components/common/PageHeader';
|
|
import ModuleNavigationTabs from '../../components/navigation/ModuleNavigationTabs';
|
|
import { useToast } from '../../components/ui/toast/ToastContainer';
|
|
import { fetchAuthorProfiles, createAuthorProfile, updateAuthorProfile, deleteAuthorProfile, AuthorProfile } from '../../services/api';
|
|
import { Card } from '../../components/ui/card';
|
|
import Button from '../../components/ui/button/Button';
|
|
import FormModal, { FormField } from '../../components/common/FormModal';
|
|
import Badge from '../../components/ui/badge/Badge';
|
|
import { PlusIcon, BoltIcon, UserIcon, ShootingStarIcon, ImageIcon } from '../../icons';
|
|
|
|
export default function AuthorProfiles() {
|
|
const toast = useToast();
|
|
const [profiles, setProfiles] = useState<AuthorProfile[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const [editingProfile, setEditingProfile] = useState<AuthorProfile | null>(null);
|
|
const [formData, setFormData] = useState({
|
|
name: '',
|
|
description: '',
|
|
tone: '',
|
|
language: 'en',
|
|
is_active: true,
|
|
});
|
|
|
|
useEffect(() => {
|
|
loadProfiles();
|
|
}, []);
|
|
|
|
const loadProfiles = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const response = await fetchAuthorProfiles();
|
|
setProfiles(response.results || []);
|
|
} catch (error: any) {
|
|
toast.error(`Failed to load author profiles: ${error.message}`);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleCreate = () => {
|
|
setEditingProfile(null);
|
|
setFormData({
|
|
name: '',
|
|
description: '',
|
|
tone: '',
|
|
language: 'en',
|
|
is_active: true,
|
|
});
|
|
setIsModalOpen(true);
|
|
};
|
|
|
|
const handleEdit = (profile: AuthorProfile) => {
|
|
setEditingProfile(profile);
|
|
setFormData({
|
|
name: profile.name,
|
|
description: profile.description,
|
|
tone: profile.tone,
|
|
language: profile.language,
|
|
is_active: profile.is_active,
|
|
});
|
|
setIsModalOpen(true);
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
try {
|
|
if (editingProfile) {
|
|
await updateAuthorProfile(editingProfile.id, formData);
|
|
toast.success('Author profile updated successfully');
|
|
} else {
|
|
await createAuthorProfile(formData);
|
|
toast.success('Author profile created successfully');
|
|
}
|
|
setIsModalOpen(false);
|
|
loadProfiles();
|
|
} catch (error: any) {
|
|
toast.error(`Failed to save: ${error.message}`);
|
|
}
|
|
};
|
|
|
|
const handleDelete = async (id: number) => {
|
|
if (!confirm('Are you sure you want to delete this author profile?')) return;
|
|
try {
|
|
await deleteAuthorProfile(id);
|
|
toast.success('Author profile deleted successfully');
|
|
loadProfiles();
|
|
} catch (error: any) {
|
|
toast.error(`Failed to delete: ${error.message}`);
|
|
}
|
|
};
|
|
|
|
const formFields: FormField[] = [
|
|
{ name: 'name', label: 'Name', type: 'text', required: true },
|
|
{ name: 'description', label: 'Description', type: 'textarea', required: false },
|
|
{ name: 'tone', label: 'Tone', type: 'text', required: true },
|
|
{ name: 'language', label: 'Language', type: 'text', required: true },
|
|
{ name: 'is_active', label: 'Active', type: 'checkbox', required: false },
|
|
];
|
|
|
|
// Thinker navigation tabs
|
|
const thinkerTabs = [
|
|
{ label: 'AI Instructions', path: '/thinker/prompts', icon: <BoltIcon /> },
|
|
{ label: 'Writing Voices', path: '/thinker/author-profiles', icon: <UserIcon /> },
|
|
{ label: 'Content Strategies', path: '/thinker/strategies', icon: <ShootingStarIcon /> },
|
|
{ label: 'Image Testing', path: '/thinker/image-testing', icon: <ImageIcon /> },
|
|
];
|
|
|
|
return (
|
|
<div className="p-6">
|
|
<PageMeta title="Author Profiles" />
|
|
<PageHeader
|
|
title="Choose Your Writing Voice"
|
|
badge={{ icon: <UserIcon />, color: 'blue' }}
|
|
navigation={<ModuleNavigationTabs tabs={thinkerTabs} />}
|
|
/>
|
|
<div className="mb-6 flex justify-between items-center">
|
|
<Button onClick={handleCreate} variant="primary">
|
|
<PlusIcon className="w-4 h-4 mr-2" />
|
|
Create Profile
|
|
</Button>
|
|
</div>
|
|
|
|
{loading ? (
|
|
<div className="flex items-center justify-center h-64">
|
|
<div className="text-gray-500">Loading...</div>
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{profiles.map((profile) => (
|
|
<Card key={profile.id} className="p-6">
|
|
<div className="flex justify-between items-start mb-4">
|
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">{profile.name}</h3>
|
|
<Badge variant="light" color={profile.is_active ? 'success' : 'dark'}>
|
|
{profile.is_active ? 'Active' : 'Inactive'}
|
|
</Badge>
|
|
</div>
|
|
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">{profile.description}</p>
|
|
<div className="space-y-2 mb-4">
|
|
<div className="text-sm">
|
|
<span className="text-gray-500 dark:text-gray-400">Tone:</span>{' '}
|
|
<span className="text-gray-900 dark:text-white">{profile.tone}</span>
|
|
</div>
|
|
<div className="text-sm">
|
|
<span className="text-gray-500 dark:text-gray-400">Language:</span>{' '}
|
|
<span className="text-gray-900 dark:text-white">{profile.language}</span>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button variant="secondary" size="sm" onClick={() => handleEdit(profile)}>
|
|
Edit
|
|
</Button>
|
|
<Button variant="danger" size="sm" onClick={() => handleDelete(profile.id)}>
|
|
Delete
|
|
</Button>
|
|
</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<FormModal
|
|
isOpen={isModalOpen}
|
|
onClose={() => setIsModalOpen(false)}
|
|
onSave={handleSave}
|
|
title={editingProfile ? 'Edit Author Profile' : 'Create Author Profile'}
|
|
fields={formFields}
|
|
data={formData}
|
|
onChange={setFormData}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|