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:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user