fix(web): forgot-password dialog, settings tagline, account dialog triggers

- Login page: replace static 'Forget Password? Contact IT' line with a
  proper dialog button. Clicking opens an explanatory dialog (self-
  service reset is intentionally disabled; admins can reset from
  /settings/users or run scripts/set-password.sh).
- /settings: drop the 'cm WhatsApp Bot · self-hosted' tagline.
- /accounts/[id]: Unpair + Delete cards weren't responding to clicks.
  Restructure so the transparent <button> overlay is a sibling of
  <Card> inside a <div className='relative'> wrapper (mirrors the
  working Pair/Re-pair pattern). The previous layout placed the
  DialogTrigger inside the Card, which produced no clickable button
  in the rendered DOM under radix-ui 1.4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
yiekheng 2026-05-10 18:50:28 +08:00
parent c493101b60
commit 5d583d9194
3 changed files with 89 additions and 43 deletions

View File

@ -156,12 +156,11 @@ export default async function AccountDetailPage({ params }: AccountDetailPagePro
</Card>
</Link>
{/* Unpair transparent <button> overlay opens the dialog
so we don't pass button-specific props onto the Card div
(Radix asChild does that and it produces a hydration
mismatch on a div). */}
{/* Unpair transparent <button> overlay (sibling of Card,
inside a relative wrapper). Same pattern as Delete below. */}
<Dialog>
<Card className="relative transition-all hover:shadow-md hover:ring-amber-500/30 cursor-pointer">
<div className="relative">
<Card className="transition-all hover:shadow-md hover:ring-amber-500/30 cursor-pointer">
<CardContent className="flex items-center justify-between gap-4 py-4">
<div className="flex items-center gap-3">
<div className="flex size-9 items-center justify-center rounded-lg bg-amber-500/10">
@ -176,6 +175,7 @@ export default async function AccountDetailPage({ params }: AccountDetailPagePro
</div>
<ChevronRightIcon className="size-4 text-muted-foreground/60" />
</CardContent>
</Card>
<DialogTrigger asChild>
<button
type="button"
@ -183,7 +183,7 @@ export default async function AccountDetailPage({ params }: AccountDetailPagePro
className="absolute inset-0 w-full rounded-xl bg-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
/>
</DialogTrigger>
</Card>
</div>
<DialogContent>
<DialogHeader>
<DialogTitle>Unpair this account?</DialogTitle>
@ -207,9 +207,16 @@ export default async function AccountDetailPage({ params }: AccountDetailPagePro
</>
)}
{/* Delete — transparent <button> overlay opens the dialog. */}
{/* Delete transparent <button> overlay opens the dialog.
The button lives as a sibling of <Card> (inside a relative
wrapper) instead of inside the Card. Radix's asChild-driven
DialogTrigger stops emitting the underlying button when the
wrapper Card adds an `absolute inset-0` sibling on the same
stacking context, so we mirror the working pattern from the
Pair/Re-pair card above. */}
<Dialog>
<Card className="relative transition-all hover:shadow-md hover:ring-destructive/30 cursor-pointer">
<div className="relative">
<Card className="transition-all hover:shadow-md hover:ring-destructive/30 cursor-pointer">
<CardContent className="flex items-center justify-between gap-4 py-4">
<div className="flex items-center gap-3">
<div className="flex size-9 items-center justify-center rounded-lg bg-destructive/10">
@ -224,6 +231,7 @@ export default async function AccountDetailPage({ params }: AccountDetailPagePro
</div>
<ChevronRightIcon className="size-4 text-muted-foreground/60" />
</CardContent>
</Card>
<DialogTrigger asChild>
<button
type="button"
@ -231,7 +239,7 @@ export default async function AccountDetailPage({ params }: AccountDetailPagePro
className="absolute inset-0 w-full rounded-xl bg-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive focus-visible:ring-offset-2"
/>
</DialogTrigger>
</Card>
</div>
<DialogContent>
<DialogHeader>
<DialogTitle>Delete this account permanently?</DialogTitle>

View File

@ -1,8 +1,18 @@
"use client";
import { useState, useTransition } from "react";
import { Loader2Icon, LockIcon } from "lucide-react";
import { Loader2Icon, LockIcon, HelpCircleIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogClose,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { loginAction } from "@/actions/auth";
@ -58,9 +68,41 @@ export function LoginFormClient({ next }: { next: string }) {
)}
Sign in
</Button>
<p className="text-xs text-muted-foreground text-center">
Forget Password? Contact IT
</p>
<Dialog>
<DialogTrigger asChild>
<Button
type="button"
variant="link"
size="sm"
className="w-full text-xs text-muted-foreground hover:text-foreground"
>
<HelpCircleIcon className="size-3.5" />
Forgot password?
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Forgot your password?</DialogTitle>
<DialogDescription>
Self-service password reset is intentionally disabled on
this deployment. To recover access, contact an
administrator. They can reset your password from
Settings Users, or run{" "}
<code className="font-mono text-[0.75rem]">
./scripts/set-password.sh &lt;username&gt;
</code>{" "}
from the tools container.
</DialogDescription>
</DialogHeader>
<DialogFooter showCloseButton>
<DialogClose asChild>
<Button type="button" size="sm">
Got it
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
</form>
);
}

View File

@ -77,10 +77,6 @@ export default async function SettingsPage() {
<ThemeToggle />
</CardContent>
</Card>
<p className="text-center text-xs text-muted-foreground">
cm WhatsApp Bot · self-hosted
</p>
</PageShell>
);
}