feat(admission): add group VPN exceptions
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { UserGroupSelect } from "./user-group-select";
|
||||
|
||||
describe("UserGroupSelect", () => {
|
||||
it("renders the effective group and preserves the active search", () => {
|
||||
const markup = renderToStaticMarkup(<UserGroupSelect
|
||||
action={async () => undefined}
|
||||
effectiveGroupId="group-ops"
|
||||
groups={[{ id: "group-everyone", name: "everyone" }, { id: "group-ops", name: "Ops" }]}
|
||||
search="alex smith"
|
||||
userId="user-one"
|
||||
userLabel="Alex"
|
||||
/>);
|
||||
|
||||
expect(markup).toContain('aria-label="Group for Alex"');
|
||||
expect(markup).toContain('<option value="group-ops" selected="">Ops</option>');
|
||||
expect(markup).toContain('<input type="hidden" name="search" value="alex smith"/>');
|
||||
expect(markup).toContain("Apply group");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
"use client";
|
||||
|
||||
import { useFormStatus } from "react-dom";
|
||||
|
||||
export function UserGroupSelect({
|
||||
action,
|
||||
effectiveGroupId,
|
||||
groups,
|
||||
search,
|
||||
userId,
|
||||
userLabel,
|
||||
}: {
|
||||
action: (formData: FormData) => Promise<void>;
|
||||
effectiveGroupId: string;
|
||||
groups: Array<{ id: string; name: string }>;
|
||||
search: string;
|
||||
userId: string;
|
||||
userLabel: string;
|
||||
}) {
|
||||
const helpId = `group-help-${userId}`;
|
||||
return (
|
||||
<form action={action} className="flex items-center gap-2">
|
||||
<input name="userId" type="hidden" value={userId} />
|
||||
<input name="search" type="hidden" value={search} />
|
||||
<span className="sr-only" id={helpId}>Changing this selection applies the group immediately.</span>
|
||||
<GroupSelectControl effectiveGroupId={effectiveGroupId} groups={groups} helpId={helpId} userLabel={userLabel} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function GroupSelectControl({
|
||||
effectiveGroupId,
|
||||
groups,
|
||||
helpId,
|
||||
userLabel,
|
||||
}: {
|
||||
effectiveGroupId: string;
|
||||
groups: Array<{ id: string; name: string }>;
|
||||
helpId: string;
|
||||
userLabel: string;
|
||||
}) {
|
||||
const { pending } = useFormStatus();
|
||||
return (
|
||||
<>
|
||||
<select
|
||||
aria-describedby={helpId}
|
||||
aria-label={`Group for ${userLabel}`}
|
||||
className="max-w-44 border border-line bg-canvas px-3 py-2 font-mono text-xs outline-none focus:border-accent disabled:cursor-wait disabled:opacity-60"
|
||||
defaultValue={effectiveGroupId}
|
||||
disabled={pending}
|
||||
name="groupId"
|
||||
onChange={(event) => event.currentTarget.form?.requestSubmit()}
|
||||
>
|
||||
{groups.map((group) => <option key={group.id} value={group.id}>{group.name}</option>)}
|
||||
</select>
|
||||
<span aria-live="polite" className="sr-only">{pending ? "Updating group." : ""}</span>
|
||||
<button className="sr-only focus:not-sr-only" disabled={pending} type="submit">Apply group</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user