231 lines
8.9 KiB
TypeScript
231 lines
8.9 KiB
TypeScript
'use client';
|
||
|
||
import { Locale } from '@/lib/i18n';
|
||
import { getSEOData, SEOData } from '@/lib/seo';
|
||
import { useEffect, useState } from 'react';
|
||
|
||
interface TDKConfigManagerProps {
|
||
locale: Locale;
|
||
page: string;
|
||
onSave?: (data: SEOData) => void;
|
||
className?: string;
|
||
}
|
||
|
||
export default function TDKConfigManager({
|
||
locale,
|
||
page,
|
||
onSave,
|
||
className = '',
|
||
}: TDKConfigManagerProps) {
|
||
const [seoData, setSeoData] = useState<SEOData>({
|
||
title: '',
|
||
description: '',
|
||
keywords: [],
|
||
});
|
||
const [loading, setLoading] = useState(true);
|
||
const [saving, setSaving] = useState(false);
|
||
const [newKeyword, setNewKeyword] = useState('');
|
||
|
||
useEffect(() => {
|
||
const loadSEOData = async () => {
|
||
try {
|
||
setLoading(true);
|
||
const data = await getSEOData(locale, page);
|
||
setSeoData(data);
|
||
} catch (error) {
|
||
console.error('Failed to load SEO data:', error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
loadSEOData();
|
||
}, [locale, page]);
|
||
|
||
const handleSave = async () => {
|
||
if (onSave) {
|
||
setSaving(true);
|
||
try {
|
||
await onSave(seoData);
|
||
alert(
|
||
locale === 'zh-CN'
|
||
? '保存成功!'
|
||
: locale === 'zh-TW'
|
||
? '儲存成功!'
|
||
: 'Saved successfully!',
|
||
);
|
||
} catch (error) {
|
||
alert(
|
||
locale === 'zh-CN'
|
||
? '保存失败!'
|
||
: locale === 'zh-TW'
|
||
? '儲存失敗!'
|
||
: 'Save failed!',
|
||
);
|
||
} finally {
|
||
setSaving(false);
|
||
}
|
||
}
|
||
};
|
||
|
||
const addKeyword = () => {
|
||
if (newKeyword.trim() && !seoData.keywords.includes(newKeyword.trim())) {
|
||
setSeoData({
|
||
...seoData,
|
||
keywords: [...seoData.keywords, newKeyword.trim()],
|
||
});
|
||
setNewKeyword('');
|
||
}
|
||
};
|
||
|
||
const removeKeyword = (index: number) => {
|
||
setSeoData({
|
||
...seoData,
|
||
keywords: seoData.keywords.filter((_, i) => i !== index),
|
||
});
|
||
};
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className={`animate-pulse ${className}`}>
|
||
<div className="space-y-4">
|
||
<div className="h-4 bg-gray-200 rounded"></div>
|
||
<div className="h-20 bg-gray-200 rounded"></div>
|
||
<div className="h-4 bg-gray-200 rounded"></div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className={`tdk-config-manager ${className}`}>
|
||
<h3 className="text-lg font-semibold mb-4">
|
||
{locale === 'zh-CN'
|
||
? 'TDK配置管理'
|
||
: locale === 'zh-TW'
|
||
? 'TDK配置管理'
|
||
: 'TDK Configuration'}
|
||
</h3>
|
||
|
||
<div className="space-y-4">
|
||
{/* Title */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
{locale === 'zh-CN' ? '标题' : locale === 'zh-TW' ? '標題' : 'Title'}
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={seoData.title}
|
||
onChange={(e) => setSeoData({ ...seoData, title: e.target.value })}
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
placeholder={
|
||
locale === 'zh-CN'
|
||
? '输入页面标题'
|
||
: locale === 'zh-TW'
|
||
? '輸入頁面標題'
|
||
: 'Enter page title'
|
||
}
|
||
/>
|
||
|
||
<div className="text-xs text-gray-500 mt-1">
|
||
{seoData.title.length}/60{' '}
|
||
{locale === 'zh-CN' ? '字符' : locale === 'zh-TW' ? '字元' : 'characters'}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Description */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
{locale === 'zh-CN' ? '描述' : locale === 'zh-TW' ? '描述' : 'Description'}
|
||
</label>
|
||
<textarea
|
||
value={seoData.description}
|
||
onChange={(e) => setSeoData({ ...seoData, description: e.target.value })}
|
||
rows={3}
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
placeholder={
|
||
locale === 'zh-CN'
|
||
? '输入页面描述'
|
||
: locale === 'zh-TW'
|
||
? '輸入頁面描述'
|
||
: 'Enter page description'
|
||
}
|
||
/>
|
||
|
||
<div className="text-xs text-gray-500 mt-1">
|
||
{seoData.description.length}/160{' '}
|
||
{locale === 'zh-CN' ? '字符' : locale === 'zh-TW' ? '字元' : 'characters'}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Keywords */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
{locale === 'zh-CN' ? '关键词' : locale === 'zh-TW' ? '關鍵詞' : 'Keywords'}
|
||
</label>
|
||
<div className="flex gap-2 mb-2">
|
||
<input
|
||
type="text"
|
||
value={newKeyword}
|
||
onChange={(e) => setNewKeyword(e.target.value)}
|
||
onKeyPress={(e) => e.key === 'Enter' && addKeyword()}
|
||
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
placeholder={
|
||
locale === 'zh-CN'
|
||
? '添加关键词'
|
||
: locale === 'zh-TW'
|
||
? '新增關鍵詞'
|
||
: 'Add keyword'
|
||
}
|
||
/>
|
||
|
||
<button
|
||
onClick={addKeyword}
|
||
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
>
|
||
{locale === 'zh-CN' ? '添加' : locale === 'zh-TW' ? '新增' : 'Add'}
|
||
</button>
|
||
</div>
|
||
<div className="flex flex-wrap gap-2">
|
||
{seoData.keywords.map((keyword, index) => (
|
||
<span
|
||
key={index}
|
||
className="inline-flex items-center gap-1 bg-blue-100 text-blue-800 text-sm px-2 py-1 rounded"
|
||
>
|
||
{keyword}
|
||
<button
|
||
onClick={() => removeKeyword(index)}
|
||
className="text-blue-600 hover:text-blue-800"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Save Button */}
|
||
{onSave && (
|
||
<button
|
||
onClick={handleSave}
|
||
disabled={saving}
|
||
className="w-full px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 disabled:opacity-50"
|
||
>
|
||
{saving
|
||
? locale === 'zh-CN'
|
||
? '保存中...'
|
||
: locale === 'zh-TW'
|
||
? '儲存中...'
|
||
: 'Saving...'
|
||
: locale === 'zh-CN'
|
||
? '保存配置'
|
||
: locale === 'zh-TW'
|
||
? '儲存配置'
|
||
: 'Save Configuration'}
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|