Add PocketBase login, first-sign-in password changes, account settings, and user activation, reactivation and deletion with self-removal guards. Protect application pages and APIs and keep private pages out of PWA caches. Resolve plant locations from site assignments and improve map controls, profit chart views, navigation and mobile table layouts. Validation: production build and TypeScript passed; 28 unit tests passed. Backend access integration tests and mobile verification are documented.
34 lines
2 KiB
TypeScript
34 lines
2 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useTransition } from "react";
|
|
import { LogOut, LoaderCircle } from "lucide-react";
|
|
|
|
export function LogoutButton({ iconOnly = false, fullWidth = false }: { iconOnly?: boolean; fullWidth?: boolean }) {
|
|
const [pending, startTransition] = useTransition();
|
|
const [failed, setFailed] = useState(false);
|
|
return <div className="relative">
|
|
<button type="button" aria-label="Sign out" title="Sign out" disabled={pending}
|
|
className={`flex min-h-11 min-w-11 items-center gap-3 rounded-lg text-sm font-medium text-gray-500 transition hover:bg-gray-100 focus-visible:outline-2 focus-visible:outline-[#30638E] dark:text-gray-400 dark:hover:bg-white/5 ${fullWidth ? "w-full justify-start px-3" : iconOnly ? "h-11 w-11 justify-center" : "justify-center px-4"}`}
|
|
onClick={() => startTransition(async () => {
|
|
setFailed(false);
|
|
try {
|
|
const response = await fetch("/api/auth/logout", { method: "POST" });
|
|
if (!response.ok) throw new Error("Could not sign out.");
|
|
// Old workers may have cached dashboards. Clear them even after upgrading.
|
|
try {
|
|
if ("caches" in window) await Promise.all((await caches.keys()).map((key) => caches.delete(key)));
|
|
} catch { /* Cookie deletion still signs the user out if cache storage is unavailable. */ }
|
|
if ("BroadcastChannel" in window) {
|
|
const channel = new BroadcastChannel("mega-auth");
|
|
channel.postMessage("logout");
|
|
channel.close();
|
|
}
|
|
window.location.replace("/login");
|
|
} catch { setFailed(true); }
|
|
})}>
|
|
{pending ? <LoaderCircle className="h-4 w-4 animate-spin" /> : <LogOut className="h-4 w-4" />}
|
|
{!iconOnly && (pending ? "Signing out…" : "Sign out")}
|
|
</button>
|
|
{failed && <p role="alert" className="absolute right-0 top-full z-50 mt-2 w-56 rounded-xl border border-[#D1495B]/20 bg-white p-3 text-sm text-[#D1495B] shadow-lg dark:bg-surface">Couldn't sign out. Please try again.</p>}
|
|
</div>;
|
|
}
|