260 lines
7.3 KiB
TypeScript
260 lines
7.3 KiB
TypeScript
'use client'
|
||
|
||
import { useState } from 'react'
|
||
import { useEmojis } from '@/hooks/useEmojis'
|
||
import {
|
||
AVATAR_EMOJI_RESULTS,
|
||
AVATAR_EMOJI_TYPES,
|
||
supportsAvatarEmojiResult,
|
||
type AvatarEmojiResult,
|
||
type AvatarEmojiType,
|
||
} from '@/lib/protocol'
|
||
import { EmojiMedia } from './EmojiMedia'
|
||
|
||
interface EmojiFormProps {
|
||
avatarId: string
|
||
onSuccess: () => void
|
||
onCancel: () => void
|
||
emojiId?: string
|
||
initialLabel?: string
|
||
initialUrl?: string
|
||
initialDescription?: string
|
||
initialTriggerType?: AvatarEmojiType
|
||
initialTriggerResult?: AvatarEmojiResult | ''
|
||
}
|
||
|
||
export function EmojiForm({
|
||
avatarId,
|
||
onSuccess,
|
||
onCancel,
|
||
emojiId,
|
||
initialLabel = '',
|
||
initialUrl = '',
|
||
initialDescription = '',
|
||
initialTriggerType = 'message',
|
||
initialTriggerResult = '',
|
||
}: EmojiFormProps) {
|
||
const { createEmoji, updateEmoji } = useEmojis()
|
||
const [label, setLabel] = useState(initialLabel)
|
||
const [url, setUrl] = useState(initialUrl)
|
||
const [description, setDescription] = useState(initialDescription)
|
||
const [triggerType, setTriggerType] = useState<AvatarEmojiType>(initialTriggerType)
|
||
const [triggerResult, setTriggerResult] = useState<AvatarEmojiResult | ''>(initialTriggerResult)
|
||
const [submitting, setSubmitting] = useState(false)
|
||
const [error, setError] = useState<string | null>(null)
|
||
|
||
const resultEnabled = supportsAvatarEmojiResult(triggerType)
|
||
|
||
const handleSubmit = async (e: React.FormEvent) => {
|
||
e.preventDefault()
|
||
|
||
if (!label.trim()) {
|
||
setError('请输入表情标签')
|
||
return
|
||
}
|
||
|
||
if (!url.trim()) {
|
||
setError('请输入表情URL')
|
||
return
|
||
}
|
||
|
||
// 简单的URL验证
|
||
try {
|
||
new URL(url)
|
||
} catch {
|
||
setError('请输入有效的URL')
|
||
return
|
||
}
|
||
|
||
try {
|
||
setSubmitting(true)
|
||
setError(null)
|
||
|
||
if (emojiId) {
|
||
await updateEmoji(emojiId, {
|
||
label: label.trim(),
|
||
url: url.trim(),
|
||
description: description.trim() || undefined,
|
||
trigger_type: triggerType,
|
||
trigger_result: resultEnabled
|
||
? (triggerResult || null)
|
||
: null,
|
||
})
|
||
} else {
|
||
await createEmoji(
|
||
avatarId,
|
||
label.trim(),
|
||
url.trim(),
|
||
description.trim() || undefined,
|
||
undefined,
|
||
triggerType,
|
||
resultEnabled ? (triggerResult || undefined) : undefined,
|
||
)
|
||
}
|
||
|
||
onSuccess()
|
||
} catch (err) {
|
||
setError(err instanceof Error ? err.message : '添加失败')
|
||
} finally {
|
||
setSubmitting(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<form onSubmit={handleSubmit} className="space-y-4">
|
||
{error && (
|
||
<div className="rounded-lg bg-red-50 p-3 text-sm text-red-700">
|
||
{error}
|
||
</div>
|
||
)}
|
||
|
||
<div>
|
||
<label
|
||
htmlFor="label"
|
||
className="block text-sm font-medium text-slate-700"
|
||
>
|
||
表情标签 *
|
||
</label>
|
||
<input
|
||
id="label"
|
||
type="text"
|
||
value={label}
|
||
onChange={e => setLabel(e.target.value)}
|
||
placeholder="例如:开心、难过、生气"
|
||
className="mt-1 w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
|
||
disabled={submitting}
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label
|
||
htmlFor="url"
|
||
className="block text-sm font-medium text-slate-700"
|
||
>
|
||
图片URL *
|
||
</label>
|
||
<input
|
||
id="url"
|
||
type="url"
|
||
value={url}
|
||
onChange={e => setUrl(e.target.value)}
|
||
placeholder="https://example.com/emoji.png"
|
||
className="mt-1 w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
|
||
disabled={submitting}
|
||
/>
|
||
<p className="mt-1 text-xs text-slate-500">
|
||
请提供图片的完整URL地址
|
||
</p>
|
||
</div>
|
||
|
||
<div>
|
||
<label
|
||
htmlFor="triggerType"
|
||
className="block text-sm font-medium text-slate-700"
|
||
>
|
||
触发类型 *
|
||
</label>
|
||
<select
|
||
id="triggerType"
|
||
value={triggerType}
|
||
onChange={e => {
|
||
const nextType = e.target.value as AvatarEmojiType
|
||
setTriggerType(nextType)
|
||
if (!supportsAvatarEmojiResult(nextType)) {
|
||
setTriggerResult('')
|
||
}
|
||
}}
|
||
className="mt-1 w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
|
||
disabled={submitting}
|
||
>
|
||
{AVATAR_EMOJI_TYPES.map(type => (
|
||
<option key={type} value={type}>
|
||
{type}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
|
||
{resultEnabled && (
|
||
<div>
|
||
<label
|
||
htmlFor="triggerResult"
|
||
className="block text-sm font-medium text-slate-700"
|
||
>
|
||
结果类型
|
||
</label>
|
||
<select
|
||
id="triggerResult"
|
||
value={triggerResult}
|
||
onChange={e => setTriggerResult(e.target.value as AvatarEmojiResult | '')}
|
||
className="mt-1 w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
|
||
disabled={submitting}
|
||
>
|
||
<option value="">通用</option>
|
||
{AVATAR_EMOJI_RESULTS.map(result => (
|
||
<option key={result} value={result}>
|
||
{result}
|
||
</option>
|
||
))}
|
||
</select>
|
||
<p className="mt-1 text-xs text-slate-500">
|
||
适用于需要区分成功和失败的事件
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
<div>
|
||
<label
|
||
htmlFor="description"
|
||
className="block text-sm font-medium text-slate-700"
|
||
>
|
||
描述(可选)
|
||
</label>
|
||
<textarea
|
||
id="description"
|
||
value={description}
|
||
onChange={e => setDescription(e.target.value)}
|
||
placeholder="简单描述这个表情..."
|
||
rows={2}
|
||
className="mt-1 w-full resize-none rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
|
||
disabled={submitting}
|
||
/>
|
||
</div>
|
||
|
||
{url && (
|
||
<div>
|
||
<label className="block text-sm font-medium text-slate-700">
|
||
预览
|
||
</label>
|
||
<div className="mt-2 inline-block rounded-lg border border-slate-200 bg-slate-50 p-4">
|
||
<EmojiMedia
|
||
url={url}
|
||
alt="预览"
|
||
className="h-24 w-24 object-contain"
|
||
fallbackClassName="h-24 w-24 object-contain"
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<div className="flex gap-2">
|
||
<button
|
||
type="submit"
|
||
disabled={submitting}
|
||
className="flex-1 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50"
|
||
>
|
||
{submitting ? (emojiId ? '保存中...' : '添加中...') : (emojiId ? '保存' : '添加')}
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={onCancel}
|
||
disabled={submitting}
|
||
className="rounded-lg border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50 disabled:opacity-50"
|
||
>
|
||
取消
|
||
</button>
|
||
</div>
|
||
</form>
|
||
)
|
||
}
|