accounts/dashboard/app/components/mail/ComposeForm.tsx

140 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { FormEvent, useCallback, useMemo, useState } from 'react'
import { Loader2, Send } from 'lucide-react'
import { useRouter, useSearchParams } from 'next/navigation'
import { sendMessage } from '@lib/mail/apiClient'
import type { ComposePayload } from '@lib/mail/types'
interface ComposeFormProps {
tenantId: string
}
function parseList(value: string): string[] {
return value
.split(',')
.map((item) => item.trim())
.filter((item) => item.length > 0)
}
export default function ComposeForm({ tenantId }: ComposeFormProps) {
const router = useRouter()
const params = useSearchParams()
const replySubject = params.get('subject') ?? ''
const replyTo = params.get('to') ?? ''
const [submitting, setSubmitting] = useState(false)
const [form, setForm] = useState<ComposePayload>({
to: replyTo ? parseList(replyTo) : [],
cc: [],
bcc: [],
subject: replySubject,
text: '',
html: '',
attachments: [],
})
const [status, setStatus] = useState<string | null>(null)
const handleSubmit = useCallback(
async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
if (form.to.length === 0) {
setStatus('至少填写一个收件人')
return
}
try {
setSubmitting(true)
await sendMessage(tenantId, form)
setStatus('已成功发送!')
setTimeout(() => {
router.push(`/panel/mail?tenantId=${tenantId}`)
}, 600)
} catch (error) {
setStatus(error instanceof Error ? error.message : '发送失败')
} finally {
setSubmitting(false)
}
},
[form, router, tenantId],
)
const handleFieldChange = useCallback(
(field: keyof ComposePayload, value: string | string[]) => {
setForm((prev) => ({
...prev,
[field]: Array.isArray(value) ? value : value,
}))
},
[],
)
const recipientsPreview = useMemo(() => form.to.join(', '), [form.to])
return (
<form onSubmit={handleSubmit} className="space-y-6 rounded-2xl border border-[color:var(--color-surface-border)] bg-[var(--color-surface-elevated)] p-6 shadow-[var(--shadow-md)]">
<div className="flex flex-col gap-1">
<h1 className="text-2xl font-semibold text-[var(--color-heading)]"></h1>
<p className="text-sm text-[var(--color-text-subtle)]"> TLS-only 使 S3 </p>
</div>
<div className="grid gap-4 md:grid-cols-2">
<label className="flex flex-col gap-1 text-sm">
<span className="text-[var(--color-text-subtle)]"></span>
<input
className="rounded-lg border border-[color:var(--color-surface-border)] bg-[var(--color-surface-muted)] px-3 py-2 text-sm text-[var(--color-text)] focus:border-[color:var(--color-primary)] focus:outline-none"
value={recipientsPreview}
placeholder="user@example.com, second@example.com"
onChange={(event) => handleFieldChange('to', parseList(event.target.value))}
/>
</label>
<label className="flex flex-col gap-1 text-sm">
<span className="text-[var(--color-text-subtle)]"></span>
<input
className="rounded-lg border border-[color:var(--color-surface-border)] bg-[var(--color-surface-muted)] px-3 py-2 text-sm text-[var(--color-text)] focus:border-[color:var(--color-primary)] focus:outline-none"
placeholder="可选"
onChange={(event) => handleFieldChange('cc', parseList(event.target.value))}
/>
</label>
<label className="flex flex-col gap-1 text-sm">
<span className="text-[var(--color-text-subtle)]"></span>
<input
className="rounded-lg border border-[color:var(--color-surface-border)] bg-[var(--color-surface-muted)] px-3 py-2 text-sm text-[var(--color-text)] focus:border-[color:var(--color-primary)] focus:outline-none"
placeholder="可选"
onChange={(event) => handleFieldChange('bcc', parseList(event.target.value))}
/>
</label>
<label className="flex flex-col gap-1 text-sm">
<span className="text-[var(--color-text-subtle)]"></span>
<input
className="rounded-lg border border-[color:var(--color-surface-border)] bg-[var(--color-surface-muted)] px-3 py-2 text-sm text-[var(--color-text)] focus:border-[color:var(--color-primary)] focus:outline-none"
value={form.subject}
onChange={(event) => handleFieldChange('subject', event.target.value)}
/>
</label>
</div>
<label className="flex flex-col gap-1 text-sm">
<span className="text-[var(--color-text-subtle)]"></span>
<textarea
className="min-h-[200px] rounded-xl border border-[color:var(--color-surface-border)] bg-[var(--color-surface-muted)] px-3 py-2 text-sm text-[var(--color-text)] focus:border-[color:var(--color-primary)] focus:outline-none"
value={form.text}
onChange={(event) => handleFieldChange('text', event.target.value)}
placeholder="请在此输入邮件内容,可使用 Markdown。"
/>
</label>
<div className="flex flex-col gap-2 rounded-xl border border-dashed border-[color:var(--color-surface-border)] bg-[var(--color-surface-muted)] px-4 py-6 text-center text-sm text-[var(--color-text-subtle)]">
<p className="font-medium text-[var(--color-heading)]"></p>
<p> S3</p>
</div>
{status ? <p className="text-sm text-[var(--color-text-subtle)]">{status}</p> : null}
<div className="flex justify-end">
<button
type="submit"
disabled={submitting}
className="inline-flex items-center gap-2 rounded-full bg-[var(--color-primary)] px-6 py-2 text-sm font-semibold text-[var(--color-primary-foreground)] shadow-[var(--shadow-md)] transition hover:bg-[var(--color-primary-hover)] disabled:cursor-not-allowed disabled:opacity-70"
>
{submitting ? <Loader2 className="h-4 w-4 animate-spin" /> : <Send className="h-4 w-4" />}
</button>
</div>
</form>
)
}