feat: email provider, admin password reset, portal new-patient, chat pill

Chat: history pill now shows a History icon + a Start-new-chat (SquarePen)
button; removed the duplicate chat-history list from the sidebar.

Email: deployment-wide email provider config (Resend/Postmark/SendGrid/SMTP) in
Settings → Developers, with encrypted API key and a Send-test action. sendEmail
dispatches via the chosen provider (REST via fetch; SMTP via nodemailer).

Forgot password with no provider: alert the clinic admin(s) via a "System"
message card in Messages + a bell notification (seeded system user + per-clinic
System conversation); clicking deep-links to /settings?tab=careTeam&member=<id>.
Admins can set a member's password directly from the employee dialog
(PATCH /api/staff/:id/password via Better Auth's internal context — no admin
plugin needed).

Patient Portal: "New patient" booking path registers a demographics-only patient
then books; bookings reject double-booked slots (409).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalid Abdi
2026-06-20 19:52:55 +03:00
parent 516de6ad60
commit 90e6ec4cc0
29 changed files with 5200 additions and 142 deletions
+29 -1
View File
@@ -4,6 +4,7 @@ import {
CalendarClock,
Download,
FileText,
KeyRound,
Mail,
Paperclip,
Plus,
@@ -93,7 +94,32 @@ const GROUP_WINDOW_MS = 5 * 60 * 1000;
// shared-appointment card. Alignment (left/right) comes from the parent column.
function SentAttachment({ att }: { att: MessageAttachment }) {
const { t } = useTranslation();
const router = useRouter();
const [apptOpen, setApptOpen] = useState(false);
if (att.kind === "passwordReset") {
return (
<button
className="max-w-[75%] rounded-2xl border border-warning/40 bg-warning/5 p-3 text-left text-sm transition-colors hover:bg-warning/10"
onClick={() =>
router.push(
`/settings?tab=careTeam&member=${encodeURIComponent(att.userId)}`,
)
}
type="button"
>
<div className="flex items-center gap-1.5 text-warning text-xs">
<KeyRound className="size-3.5" />
{t("messages.system.label")}
</div>
<p className="mt-1 font-medium text-foreground">
{t("messages.system.passwordResetTitle")}
</p>
<p className="text-muted-foreground text-xs">
{t("messages.system.passwordResetBody", { name: att.userName })}
</p>
</button>
);
}
if (att.kind === "file") {
return (
<button
@@ -668,7 +694,9 @@ export function MessagesView() {
<span className="max-w-40 truncate">
{att.kind === "file"
? att.fileName
: att.appointment.name}
: att.kind === "appointment"
? att.appointment.name
: ""}
</span>
<button
aria-label={t("messages.attach.remove")}