Add phone and birthday fields to User model with admin editor

- Add phone/birthday columns via Prisma migration
- Admin user page shows phone and birthday with inline edit UI
- UserContactEditor client component for editing contact info
- updateUserContact server action with admin-only guard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 16:43:35 +05:00
parent c64f393a7b
commit fdb9f96382
5 changed files with 149 additions and 1 deletions
@@ -0,0 +1,120 @@
"use client";
import { useState, useTransition } from "react";
import { updateUserContact } from "@/app/admin/users/[userId]/actions";
const inputStyle = {
border: "2px solid var(--border)",
background: "var(--background)",
outline: "none",
width: "100%",
padding: "0.4rem 0.6rem",
fontSize: "0.875rem",
fontFamily: "inherit",
} as React.CSSProperties;
interface Props {
userId: string;
phone: string | null;
birthday: Date | null;
}
export function UserContactEditor({ userId, phone, birthday }: Props) {
const [editing, setEditing] = useState(false);
const [phoneVal, setPhoneVal] = useState(phone ?? "");
const [birthdayVal, setBirthdayVal] = useState(
birthday ? birthday.toISOString().slice(0, 10) : ""
);
const [pending, startTransition] = useTransition();
function handleSave() {
startTransition(async () => {
await updateUserContact(userId, { phone: phoneVal, birthday: birthdayVal });
setEditing(false);
});
}
if (!editing) {
return (
<div className="flex items-start gap-6 flex-wrap">
<div className="space-y-0.5">
<p className="text-xs uppercase tracking-widest font-bold" style={{ color: "var(--muted-foreground)" }}>
Телефон
</p>
<p className="text-sm">{phone || "—"}</p>
</div>
<div className="space-y-0.5">
<p className="text-xs uppercase tracking-widest font-bold" style={{ color: "var(--muted-foreground)" }}>
День рождения
</p>
<p className="text-sm">
{birthday
? new Date(birthday).toLocaleDateString("ru-RU", { day: "numeric", month: "long", year: "numeric" })
: "—"}
</p>
</div>
<button
type="button"
onClick={() => setEditing(true)}
className="text-xs underline self-end pb-0.5"
style={{ color: "var(--muted-foreground)" }}
>
Изменить
</button>
</div>
);
}
return (
<div className="space-y-3">
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<label className="text-xs uppercase tracking-widest font-bold" style={{ color: "var(--muted-foreground)" }}>
Телефон
</label>
<input
type="tel"
value={phoneVal}
onChange={(e) => setPhoneVal(e.target.value)}
placeholder="+7 900 000-00-00"
style={inputStyle}
onFocus={(e) => (e.currentTarget.style.borderColor = "var(--foreground)")}
onBlur={(e) => (e.currentTarget.style.borderColor = "var(--border)")}
/>
</div>
<div className="space-y-1">
<label className="text-xs uppercase tracking-widest font-bold" style={{ color: "var(--muted-foreground)" }}>
День рождения
</label>
<input
type="date"
value={birthdayVal}
onChange={(e) => setBirthdayVal(e.target.value)}
style={inputStyle}
onFocus={(e) => (e.currentTarget.style.borderColor = "var(--foreground)")}
onBlur={(e) => (e.currentTarget.style.borderColor = "var(--border)")}
/>
</div>
</div>
<div className="flex gap-2">
<button
type="button"
onClick={handleSave}
disabled={pending}
className="btn-aubade btn-aubade-accent px-4 py-1.5 text-xs"
style={{ opacity: pending ? 0.6 : 1 }}
>
{pending ? "Сохранение..." : "Сохранить"}
</button>
<button
type="button"
onClick={() => setEditing(false)}
className="text-xs underline"
style={{ color: "var(--muted-foreground)" }}
>
Отмена
</button>
</div>
</div>
);
}