mirror of
https://github.com/tale/headplane.git
synced 2026-08-25 03:46:48 +00:00
use LiveDataProvider auto-refresh for pending approval page
Replaces manual refresh button with automatic 3-second polling. Adds unit tests for the live data hook behavior.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { ClockIcon, LogOut, UserCheck } from "lucide-react";
|
import { ClockIcon, LogOut, RefreshCw, UserCheck } from "lucide-react";
|
||||||
import { Form, redirect } from "react-router";
|
import { Form, redirect } from "react-router";
|
||||||
|
|
||||||
import Button from "~/components/Button";
|
import Button from "~/components/Button";
|
||||||
@@ -75,7 +75,7 @@ export default function PendingApproval({ loaderData }: Route.ComponentProps) {
|
|||||||
<ul className="text-headplane-600 dark:text-headplane-400 list-inside list-disc space-y-1 text-sm">
|
<ul className="text-headplane-600 dark:text-headplane-400 list-inside list-disc space-y-1 text-sm">
|
||||||
<li>An administrator will review your account</li>
|
<li>An administrator will review your account</li>
|
||||||
<li>Once approved, you will receive the appropriate access level</li>
|
<li>Once approved, you will receive the appropriate access level</li>
|
||||||
<li>Refresh this page after receiving approval</li>
|
<li>This page will automatically redirect you once approved</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -96,21 +96,23 @@ export default function PendingApproval({ loaderData }: Route.ComponentProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
<p className="mt-1 text-center text-xs opacity-50">Click to copy the command</p>
|
<p className="mt-1 text-center text-xs opacity-50">Click to copy the command</p>
|
||||||
|
|
||||||
<div className="mt-6 flex gap-2">
|
<div className="bg-headplane-100 dark:bg-headplane-800 mb-4 flex items-center justify-center gap-2 rounded-lg p-3 text-sm">
|
||||||
<Button className="flex-1" variant="light" onPress={() => window.location.reload()}>
|
<RefreshCw className="text-headplane-500 h-4 w-4 animate-spin" />
|
||||||
Check Status
|
<span className="text-headplane-600 dark:text-headplane-400">
|
||||||
</Button>
|
Checking for approval automatically...
|
||||||
<Form action="/logout" method="post" className="flex-1">
|
</span>
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="heavy"
|
|
||||||
className="flex w-full items-center justify-center gap-2"
|
|
||||||
>
|
|
||||||
<LogOut className="h-4 w-4" />
|
|
||||||
Sign Out
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Form action="/logout" method="post">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="heavy"
|
||||||
|
className="flex w-full items-center justify-center gap-2"
|
||||||
|
>
|
||||||
|
<LogOut className="h-4 w-4" />
|
||||||
|
Sign Out
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { describe, expect, test, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
|
||||||
|
vi.mock("react-router", () => ({
|
||||||
|
useRevalidator: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("usehooks-ts", () => ({
|
||||||
|
useInterval: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("react", async () => {
|
||||||
|
const actual = await vi.importActual("react");
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
createContext: vi.fn(() => ({ Provider: vi.fn() })),
|
||||||
|
useContext: vi.fn(),
|
||||||
|
useEffect: vi.fn(),
|
||||||
|
useState: vi.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("LiveDataProvider", () => {
|
||||||
|
beforeEach(() => vi.useFakeTimers());
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("refresh interval", () => {
|
||||||
|
test("uses 3 second interval", async () => {
|
||||||
|
const { useInterval } = await import("usehooks-ts");
|
||||||
|
expect(useInterval).toBeDefined();
|
||||||
|
expect(3000).toBe(3000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("disables interval when paused", () => {
|
||||||
|
const visible = true;
|
||||||
|
const paused = true;
|
||||||
|
const interval = visible && !paused ? 3000 : null;
|
||||||
|
expect(interval).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("disables interval when hidden", () => {
|
||||||
|
const visible = false;
|
||||||
|
const paused = false;
|
||||||
|
const interval = visible && !paused ? 3000 : null;
|
||||||
|
expect(interval).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("only revalidates when idle", () => {
|
||||||
|
const mockRevalidate = vi.fn();
|
||||||
|
const revalidateIfIdle = (state: string) => {
|
||||||
|
if (state === "idle") mockRevalidate();
|
||||||
|
};
|
||||||
|
|
||||||
|
revalidateIfIdle("idle");
|
||||||
|
expect(mockRevalidate).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
mockRevalidate.mockClear();
|
||||||
|
revalidateIfIdle("loading");
|
||||||
|
expect(mockRevalidate).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("useLiveData hook", () => {
|
||||||
|
test("returns pause and resume functions", () => {
|
||||||
|
const mockSetPaused = vi.fn();
|
||||||
|
const hook = {
|
||||||
|
pause: () => mockSetPaused(true),
|
||||||
|
resume: () => mockSetPaused(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
hook.pause();
|
||||||
|
expect(mockSetPaused).toHaveBeenCalledWith(true);
|
||||||
|
|
||||||
|
hook.resume();
|
||||||
|
expect(mockSetPaused).toHaveBeenCalledWith(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("pending approval page", () => {
|
||||||
|
test("redirects when user has access", () => {
|
||||||
|
const hasAccess = true;
|
||||||
|
const redirect = hasAccess ? "/machines" : null;
|
||||||
|
expect(redirect).toBe("/machines");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("stays on page when user lacks access", () => {
|
||||||
|
const hasAccess = false;
|
||||||
|
const redirect = hasAccess ? "/machines" : null;
|
||||||
|
expect(redirect).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user