From a97aaada8464d11920a0edd4148d77ce53740d9b Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:24:49 +0100 Subject: [PATCH 1/4] fix(UI/UX): add a validation step before leaving scope --- .../@[scope]/(_islands)/ScopeInviteForm.tsx | 4 +- .../@[scope]/(_islands)/ScopeMemberLeave.tsx | 85 +++++ frontend/routes/@[scope]/~/members.tsx | 299 ------------------ 3 files changed, 88 insertions(+), 300 deletions(-) create mode 100644 frontend/routes/@[scope]/(_islands)/ScopeMemberLeave.tsx diff --git a/frontend/routes/@[scope]/(_islands)/ScopeInviteForm.tsx b/frontend/routes/@[scope]/(_islands)/ScopeInviteForm.tsx index ed5c07c5..d29c49dd 100644 --- a/frontend/routes/@[scope]/(_islands)/ScopeInviteForm.tsx +++ b/frontend/routes/@[scope]/(_islands)/ScopeInviteForm.tsx @@ -4,6 +4,7 @@ import { useCallback, useRef } from "preact/hooks"; import { JSX } from "preact/jsx-runtime"; import { ScopeInvite } from "../../../utils/api_types.ts"; import { api, path } from "../../../utils/api.ts"; +import { TbUsersPlus } from "tb-icons"; interface ScopeInviteFormProps { scope: string; @@ -53,7 +54,7 @@ export function ScopeInviteForm(props: ScopeInviteFormProps) { class="contents" onSubmit={onSubmit} > -
{error}
} diff --git a/frontend/routes/@[scope]/(_islands)/ScopeMemberLeave.tsx b/frontend/routes/@[scope]/(_islands)/ScopeMemberLeave.tsx new file mode 100644 index 00000000..923ba1a6 --- /dev/null +++ b/frontend/routes/@[scope]/(_islands)/ScopeMemberLeave.tsx @@ -0,0 +1,85 @@ +// Copyright 2024 the JSR authors. All rights reserved. MIT license. +import { useSignal } from "@preact/signals"; +import { useEffect } from "preact/hooks"; +import { TbArrowRightFromArc } from "tb-icons"; + +export function ScopeMemberLeave({ + userId, + isAdmin, + isLastAdmin, + scopeName = "", +}: { + userId: string; + isAdmin: boolean; + isLastAdmin: boolean; + scopeName?: string; +}) { + const scopeInput = useSignal(""); + const isEmptyInput = useSignal(false); + const isInvalidInput = useSignal(false); + + useEffect(() => { + const handler = setTimeout(() => { + validate(); + }, 300); + + return () => clearTimeout(handler); + }, [scopeInput.value]); + + const validate = () => { + isEmptyInput.value = scopeInput.value.length === 0; + isInvalidInput.value = scopeInput.value !== scopeName && + scopeInput.value.length > 0; + }; + + return ( + + ); +} diff --git a/frontend/routes/@[scope]/~/members.tsx b/frontend/routes/@[scope]/~/members.tsx index 62baaba7..9ba9d416 100644 --- a/frontend/routes/@[scope]/~/members.tsx +++ b/frontend/routes/@[scope]/~/members.tsx @@ -1,200 +1,3 @@ -// Copyright 2024 the JSR authors. All rights reserved. MIT license. -import { HttpError } from "fresh"; -import { define } from "../../../util.ts"; -import { ScopeHeader } from "../(_components)/ScopeHeader.tsx"; -import { ScopeNav } from "../(_components)/ScopeNav.tsx"; -import { ScopePendingInvite } from "../(_components)/ScopePendingInvite.tsx"; -import { ScopeInviteForm } from "../(_islands)/ScopeInviteForm.tsx"; -import { ScopeMemberRole } from "../(_islands)/ScopeMemberRole.tsx"; -import { Table, TableData, TableRow } from "../../../components/Table.tsx"; -import { CopyButton } from "../../../islands/CopyButton.tsx"; -import { path } from "../../../utils/api.ts"; -import { - FullUser, - ScopeInvite, - ScopeMember, -} from "../../../utils/api_types.ts"; -import { scopeData } from "../../../utils/data.ts"; -import TbTrash from "tb-icons/TbTrash"; -import { scopeIAM } from "../../../utils/iam.ts"; -import { ScopeIAM } from "../../../utils/iam.ts"; - -export default define.page- Inviting users to this scope grants them access to publish all packages - in this scope and create new packages. They will not be able to manage - members unless they are granted admin status. -
-- {isLastAdmin && - "You are the last admin in this scope. You must promote another member to admin before leaving."} - {isInvalidInput.value && - "The scope name you entered does not match the scope name."} -
-+ {isLastAdmin && + "You are the last admin in this scope. You must promote another member to admin before leaving."} + {isInvalidInput.value && + "The scope name you entered does not match the scope name."} +
+
{isLastAdmin &&
diff --git a/frontend/routes/@[scope]/~/members.tsx b/frontend/routes/@[scope]/~/members.tsx
index 9ba9d416..dc98ad46 100644
--- a/frontend/routes/@[scope]/~/members.tsx
+++ b/frontend/routes/@[scope]/~/members.tsx
@@ -1,36 +1,300 @@
-function MemberLeave(
- props: { userId: string; isAdmin: boolean; isLastAdmin: boolean },
+// Copyright 2024 the JSR authors. All rights reserved. MIT license.
+import { HttpError } from "fresh";
+import { define } from "../../../util.ts";
+import { ScopeHeader } from "../(_components)/ScopeHeader.tsx";
+import { ScopeNav } from "../(_components)/ScopeNav.tsx";
+import { ScopePendingInvite } from "../(_components)/ScopePendingInvite.tsx";
+import { ScopeInviteForm } from "../(_islands)/ScopeInviteForm.tsx";
+import { ScopeMemberRole } from "../(_islands)/ScopeMemberRole.tsx";
+import { Table, TableData, TableRow } from "../../../components/Table.tsx";
+import { CopyButton } from "../../../islands/CopyButton.tsx";
+import { path } from "../../../utils/api.ts";
+import {
+ FullUser,
+ ScopeInvite,
+ ScopeMember,
+} from "../../../utils/api_types.ts";
+import { scopeData } from "../../../utils/data.ts";
+import TbTrash from "tb-icons/TbTrash";
+import { scopeIAM } from "../../../utils/iam.ts";
+import { ScopeIAM } from "../../../utils/iam.ts";
+import { ScopeMemberLeave } from "../(_islands)/ScopeMemberLeave.tsx";
+
+export default define.page