211 lines
7.4 KiB
TypeScript
211 lines
7.4 KiB
TypeScript
"use client";
|
||
|
||
import Link from "next/link";
|
||
import { useRouter } from "next/navigation";
|
||
import { startTransition, useState } from "react";
|
||
|
||
export function BindDeviceForm() {
|
||
const router = useRouter();
|
||
const [rawCode, setRawCode] = useState("");
|
||
const [submitting, setSubmitting] = useState(false);
|
||
const [notice, setNotice] = useState<string | null>(null);
|
||
|
||
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||
event.preventDefault();
|
||
|
||
if (!rawCode.trim()) {
|
||
setNotice("先把老人二维码里的设备码贴进来。\n");
|
||
return;
|
||
}
|
||
|
||
setSubmitting(true);
|
||
setNotice("正在建立连接,请稍候……");
|
||
|
||
try {
|
||
const response = await fetch("/api/family/bind", {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({ deviceUuid: rawCode }),
|
||
});
|
||
|
||
const payload = (await response.json().catch(() => null)) as
|
||
| { error?: string }
|
||
| null;
|
||
|
||
if (!response.ok) {
|
||
throw new Error(payload?.error || "连接失败,请稍后再试。");
|
||
}
|
||
|
||
setNotice("已经连上了,这位老人的留言和陪伴记录会出现在下方。\n");
|
||
setRawCode("");
|
||
startTransition(() => {
|
||
router.refresh();
|
||
});
|
||
} catch (error) {
|
||
setNotice(
|
||
error instanceof Error ? error.message : "连接失败,请稍后再试。",
|
||
);
|
||
} finally {
|
||
setSubmitting(false);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="rounded-[28px] border border-white/70 bg-white/80 p-5 shadow-[0_24px_60px_rgba(125,80,46,0.12)] backdrop-blur">
|
||
<div className="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
|
||
<div className="max-w-xl">
|
||
<p className="text-sm font-medium tracking-[0.18em] text-[var(--copper)] uppercase">
|
||
家人连接
|
||
</p>
|
||
<h3 className="mt-2 font-[family-name:var(--font-display)] text-2xl text-[var(--ink)]">
|
||
扫二维码,或者直接贴入设备码
|
||
</h3>
|
||
<p className="mt-2 text-sm leading-7 text-[var(--muted)]">
|
||
如果老人已经把二维码发给您,直接扫码最快;如果您拿到的是一段链接或设备码,也可以贴到下面。
|
||
</p>
|
||
</div>
|
||
<Link
|
||
href="/scan"
|
||
className="inline-flex h-12 items-center justify-center rounded-full bg-[var(--copper)] px-5 text-sm font-semibold text-white transition hover:bg-[var(--copper-deep)]"
|
||
>
|
||
打开扫码页
|
||
</Link>
|
||
</div>
|
||
|
||
<form className="mt-5 flex flex-col gap-3 lg:flex-row" onSubmit={handleSubmit}>
|
||
<input
|
||
value={rawCode}
|
||
onChange={(event) => setRawCode(event.target.value)}
|
||
placeholder="粘贴二维码链接,或者直接贴设备 UUID"
|
||
className="h-13 flex-1 rounded-full border border-[var(--line)] bg-[var(--paper-soft)] px-5 text-sm text-[var(--ink)] outline-none transition focus:border-[var(--copper)] focus:bg-white"
|
||
/>
|
||
<button
|
||
type="submit"
|
||
disabled={submitting}
|
||
className="inline-flex h-13 min-w-36 items-center justify-center rounded-full bg-[var(--olive)] px-5 text-sm font-semibold text-white transition hover:bg-[var(--olive-deep)] disabled:cursor-not-allowed disabled:opacity-60"
|
||
>
|
||
{submitting ? "正在连接" : "立即绑定"}
|
||
</button>
|
||
</form>
|
||
|
||
{notice ? (
|
||
<p className="mt-3 whitespace-pre-line text-sm leading-7 text-[var(--muted)]">
|
||
{notice}
|
||
</p>
|
||
) : null}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
type SendMessageFormProps = {
|
||
deviceUuid: string;
|
||
};
|
||
|
||
export function SendMessageForm({ deviceUuid }: SendMessageFormProps) {
|
||
const router = useRouter();
|
||
const [content, setContent] = useState("");
|
||
const [importance, setImportance] = useState("NORMAL");
|
||
const [submitting, setSubmitting] = useState(false);
|
||
const [notice, setNotice] = useState<string | null>(null);
|
||
|
||
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||
event.preventDefault();
|
||
|
||
if (!content.trim()) {
|
||
setNotice("先写下想对老人说的话。\n");
|
||
return;
|
||
}
|
||
|
||
setSubmitting(true);
|
||
setNotice("正在送达这条留言……");
|
||
|
||
try {
|
||
const response = await fetch("/api/family/messages", {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({
|
||
deviceUuid,
|
||
content,
|
||
importance,
|
||
}),
|
||
});
|
||
|
||
const payload = (await response.json().catch(() => null)) as
|
||
| { error?: string }
|
||
| null;
|
||
|
||
if (!response.ok) {
|
||
throw new Error(payload?.error || "留言发送失败,请稍后再试。");
|
||
}
|
||
|
||
setNotice("留言已经写进家人的收件盒,老人下次打开或询问时就能看到。\n");
|
||
setContent("");
|
||
setImportance("NORMAL");
|
||
startTransition(() => {
|
||
router.refresh();
|
||
});
|
||
} catch (error) {
|
||
setNotice(
|
||
error instanceof Error ? error.message : "留言发送失败,请稍后再试。",
|
||
);
|
||
} finally {
|
||
setSubmitting(false);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<form className="rounded-[28px] border border-white/70 bg-white/88 p-5" onSubmit={handleSubmit}>
|
||
<div className="flex items-center justify-between gap-3">
|
||
<div>
|
||
<p className="text-sm font-medium tracking-[0.18em] text-[var(--copper)] uppercase">
|
||
给老人留句话
|
||
</p>
|
||
<h4 className="mt-2 font-[family-name:var(--font-display)] text-xl text-[var(--ink)]">
|
||
写一句今天就能被看到的问候
|
||
</h4>
|
||
</div>
|
||
<select
|
||
value={importance}
|
||
onChange={(event) => setImportance(event.target.value)}
|
||
className="h-11 rounded-full border border-[var(--line)] bg-[var(--paper-soft)] px-4 text-sm text-[var(--ink)] outline-none"
|
||
>
|
||
<option value="LOW">温和提醒</option>
|
||
<option value="NORMAL">日常问候</option>
|
||
<option value="HIGH">希望尽快看到</option>
|
||
<option value="URGENT">需要尽快联系</option>
|
||
</select>
|
||
</div>
|
||
|
||
<textarea
|
||
value={content}
|
||
onChange={(event) => setContent(event.target.value)}
|
||
rows={4}
|
||
placeholder="例如:妈,晚上天气凉,记得添件衣服。晚饭后我给您打电话。"
|
||
className="mt-4 w-full rounded-[24px] border border-[var(--line)] bg-[var(--paper-soft)] px-4 py-4 text-sm leading-7 text-[var(--ink)] outline-none transition focus:border-[var(--copper)] focus:bg-white"
|
||
/>
|
||
|
||
<div className="mt-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||
<p className="text-sm leading-7 text-[var(--muted)]">
|
||
这条话会进入老人的留言列表,也会被 function calling 工具读取到。
|
||
</p>
|
||
<button
|
||
type="submit"
|
||
disabled={submitting}
|
||
className="inline-flex h-12 min-w-32 items-center justify-center rounded-full bg-[var(--ink)] px-5 text-sm font-semibold text-white transition hover:bg-[var(--ink-soft)] disabled:cursor-not-allowed disabled:opacity-60"
|
||
>
|
||
{submitting ? "正在送达" : "写好并送出"}
|
||
</button>
|
||
</div>
|
||
|
||
{notice ? (
|
||
<p className="mt-3 whitespace-pre-line text-sm leading-7 text-[var(--muted)]">
|
||
{notice}
|
||
</p>
|
||
) : null}
|
||
</form>
|
||
);
|
||
} |