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,26 +156,26 @@ export default async function AccountDetailPage({ params }: AccountDetailPagePro
</Card> </Card>
</Link> </Link>
{/* Unpair transparent <button> overlay opens the dialog {/* Unpair transparent <button> overlay (sibling of Card,
so we don't pass button-specific props onto the Card div inside a relative wrapper). Same pattern as Delete below. */}
(Radix asChild does that and it produces a hydration
mismatch on a div). */}
<Dialog> <Dialog>
<Card className="relative transition-all hover:shadow-md hover:ring-amber-500/30 cursor-pointer"> <div className="relative">
<CardContent className="flex items-center justify-between gap-4 py-4"> <Card className="transition-all hover:shadow-md hover:ring-amber-500/30 cursor-pointer">
<div className="flex items-center gap-3"> <CardContent className="flex items-center justify-between gap-4 py-4">
<div className="flex size-9 items-center justify-center rounded-lg bg-amber-500/10"> <div className="flex items-center gap-3">
<PowerOffIcon className="size-4 text-amber-600 dark:text-amber-400" /> <div className="flex size-9 items-center justify-center rounded-lg bg-amber-500/10">
<PowerOffIcon className="size-4 text-amber-600 dark:text-amber-400" />
</div>
<div>
<p className="text-sm font-medium">Unpair</p>
<p className="text-xs text-muted-foreground">
Disconnect from WhatsApp; keep the account so you can re-pair later
</p>
</div>
</div> </div>
<div> <ChevronRightIcon className="size-4 text-muted-foreground/60" />
<p className="text-sm font-medium">Unpair</p> </CardContent>
<p className="text-xs text-muted-foreground"> </Card>
Disconnect from WhatsApp; keep the account so you can re-pair later
</p>
</div>
</div>
<ChevronRightIcon className="size-4 text-muted-foreground/60" />
</CardContent>
<DialogTrigger asChild> <DialogTrigger asChild>
<button <button
type="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" 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> </DialogTrigger>
</Card> </div>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Unpair this account?</DialogTitle> <DialogTitle>Unpair this account?</DialogTitle>
@ -207,23 +207,31 @@ 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> <Dialog>
<Card className="relative transition-all hover:shadow-md hover:ring-destructive/30 cursor-pointer"> <div className="relative">
<CardContent className="flex items-center justify-between gap-4 py-4"> <Card className="transition-all hover:shadow-md hover:ring-destructive/30 cursor-pointer">
<div className="flex items-center gap-3"> <CardContent className="flex items-center justify-between gap-4 py-4">
<div className="flex size-9 items-center justify-center rounded-lg bg-destructive/10"> <div className="flex items-center gap-3">
<Trash2Icon className="size-4 text-destructive" /> <div className="flex size-9 items-center justify-center rounded-lg bg-destructive/10">
<Trash2Icon className="size-4 text-destructive" />
</div>
<div>
<p className="text-sm font-medium text-destructive">Delete Account</p>
<p className="text-xs text-muted-foreground">
Remove the account and all its reminders, groups, and history
</p>
</div>
</div> </div>
<div> <ChevronRightIcon className="size-4 text-muted-foreground/60" />
<p className="text-sm font-medium text-destructive">Delete Account</p> </CardContent>
<p className="text-xs text-muted-foreground"> </Card>
Remove the account and all its reminders, groups, and history
</p>
</div>
</div>
<ChevronRightIcon className="size-4 text-muted-foreground/60" />
</CardContent>
<DialogTrigger asChild> <DialogTrigger asChild>
<button <button
type="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" 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> </DialogTrigger>
</Card> </div>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Delete this account permanently?</DialogTitle> <DialogTitle>Delete this account permanently?</DialogTitle>

View File

@ -1,8 +1,18 @@
"use client"; "use client";
import { useState, useTransition } from "react"; 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 { 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 { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { loginAction } from "@/actions/auth"; import { loginAction } from "@/actions/auth";
@ -58,9 +68,41 @@ export function LoginFormClient({ next }: { next: string }) {
)} )}
Sign in Sign in
</Button> </Button>
<p className="text-xs text-muted-foreground text-center"> <Dialog>
Forget Password? Contact IT <DialogTrigger asChild>
</p> <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> </form>
); );
} }

View File

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