Multi-fix batch from a rapid feedback round: - Password policy mirrors Facebook's documented rule (≥6 chars + mix of letters with numbers/symbols). Centralised in apps/web/src/lib/password-policy.ts; createUserAction, resetUserPasswordAction, the AddUser form, and the row Reset-password flow all use it. CLI scripts/set-password.ts inlines the same check so the bootstrap path stays consistent. - App shell adds a Sign-out button in both the desktop sidebar footer and the mobile drawer footer, with the signed-in username next to it. Layout passes username down alongside role. Theme toggle was removed from the shell per request — operators don't need it in the chrome. - Dashboard stats: getDashboardStats was running findMany on reminders with NO operator filter, so a brand-new user saw global counts from every tenant. Switched to an INNER JOIN on whatsapp_accounts so the card on / only counts this user's reminders. (Counts had been showing '1 / 1 / 3 / 5' to a fresh user — the cross-tenant leak the user flagged.) - /activity drops the All tab and the Clear-history button. Default filter is now Success when no ?filter= is set; Partial keeps fanning into Paused + Failed; Skipped still merges into Archived. - /settings drops the Display name row entirely and only shows the Role row to admins. Layout receives username so the shell can also surface it next to the Sign-out button. - Tests: password-policy.test.ts (11 cases), updated users.test.ts to use policy-compliant passwords + cover letters-only / digits-only rejection, sidebar-footer assertion swapped from theme-toggle to the new Sign-out + username markup. 453 tests green; typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
96 lines
3.2 KiB
TypeScript
96 lines
3.2 KiB
TypeScript
import Link from "next/link";
|
|
import { ShieldCheckIcon, ChevronRightIcon } from "lucide-react";
|
|
import { getSeededOperator } from "@/lib/operator";
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
|
import { Separator } from "@/components/ui/separator";
|
|
import { ThemeToggle } from "@/components/theme-toggle";
|
|
import { NotificationsToggle } from "@/components/notifications-toggle";
|
|
import { PageShell } from "@/components/page-shell";
|
|
|
|
export default async function SettingsPage() {
|
|
const op = await getSeededOperator();
|
|
const isAdmin = op.role === "admin";
|
|
return (
|
|
<PageShell title="Settings" narrow>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Operator</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3 text-sm">
|
|
<Row label="Username" value={op.username} mono />
|
|
<Separator />
|
|
<Row label="Default timezone" value={op.defaultTimezone} mono />
|
|
{isAdmin && (
|
|
<>
|
|
<Separator />
|
|
<Row label="Role" value={op.role} mono />
|
|
</>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{isAdmin && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<ShieldCheckIcon className="size-4" />
|
|
Admin
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Manage which usernames can sign in and what role each
|
|
one has. Visible to admins only.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="p-0">
|
|
<Link
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
href={"/settings/users" as any}
|
|
className="flex items-center justify-between gap-3 px-6 py-3 text-sm font-medium hover:bg-muted focus-visible:bg-muted rounded-b-xl"
|
|
>
|
|
<span>Users</span>
|
|
<ChevronRightIcon className="size-4 text-muted-foreground" />
|
|
</Link>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Notifications</CardTitle>
|
|
<CardDescription>
|
|
Browser notifications when a reminder fires successfully or a
|
|
test message is sent. Uses the in-tab Notification API — works
|
|
while the app is open. Background push is on the roadmap.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<NotificationsToggle />
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Appearance</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="flex items-center justify-between">
|
|
<div className="text-sm text-muted-foreground">Theme</div>
|
|
<ThemeToggle />
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<p className="text-center text-xs text-muted-foreground">
|
|
cm WhatsApp Bot · self-hosted
|
|
</p>
|
|
</PageShell>
|
|
);
|
|
}
|
|
|
|
function Row({ label, value, mono }: { label: string; value: string; mono?: boolean }) {
|
|
return (
|
|
<div className="flex items-center justify-between gap-3">
|
|
<dt className="text-muted-foreground">{label}</dt>
|
|
<dd className={mono ? "font-mono text-xs" : ""}>{value}</dd>
|
|
</div>
|
|
);
|
|
}
|