From c176b48b4184211df2236b8eca46f1a27fe1d2b8 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 22 Nov 2023 17:20:05 +0200 Subject: [PATCH] feedback: settings page direction --- src/devhub/components/molecule/ListEditor.jsx | 87 ++++++ src/devhub/entity/team/Configurator.jsx | 20 +- src/devhub/entity/team/LabelRow.jsx | 177 ++++++------ src/devhub/entity/team/TeamInfo.jsx | 259 ------------------ src/devhub/page/admin.jsx | 241 ++++++++++------ 5 files changed, 332 insertions(+), 452 deletions(-) create mode 100644 src/devhub/components/molecule/ListEditor.jsx delete mode 100644 src/devhub/entity/team/TeamInfo.jsx diff --git a/src/devhub/components/molecule/ListEditor.jsx b/src/devhub/components/molecule/ListEditor.jsx new file mode 100644 index 000000000..6aa92cd3c --- /dev/null +++ b/src/devhub/components/molecule/ListEditor.jsx @@ -0,0 +1,87 @@ +const { Tile } = + VM.require("${REPL_DEVHUB}/widget/devhub.components.molecule.Tile") || + (() => <>); + +const { data, setList, validate, invalidate } = props; + +const [newItem, setNewItem] = useState(""); + +const handleAddItem = () => { + if (validate(newItem)) { + setList([...data.list, newItem]); + setNewItem(""); + } else { + return invalidate(); + } +}; + +const handleDeleteItem = (index) => { + const updatedData = [...data.list]; + updatedData.splice(index, 1); + setList(updatedData); +}; + +const Item = styled.div` + padding: 10px; + margin: 5px; + display: flex; + align-items: center; + flex-direction: row; + gap: 10px; +`; + +return ( + <> + {data.list.map((item, index) => ( + +
+ +
+ +
+ ))} + {data.list.length < data.maxLength && ( + +
+ setNewItem(e.target.value), + value: newItem, + placeholder: data.placeholder, + inputProps: { + prefix: data.prefix, + }, + }} + /> +
+ +
+ )} + +); diff --git a/src/devhub/entity/team/Configurator.jsx b/src/devhub/entity/team/Configurator.jsx index 591c69bed..ab703bd3b 100644 --- a/src/devhub/entity/team/Configurator.jsx +++ b/src/devhub/entity/team/Configurator.jsx @@ -1,8 +1,3 @@ -/** - * In the context of the contract, a group is essentially a member identified - * by the prefix 'team:'; therefore, on the front end, we also employ 'team,' - * with the user interface displaying 'group' for clarity. - */ const { Tile } = VM.require("${REPL_DEVHUB}/widget/devhub.components.molecule.Tile") || (() => <>); @@ -40,7 +35,6 @@ const [newItem, setNewItem] = useState(""); const [teamName, setTeamName] = useState( backwardsCompatibleTeam(data.teamName) || "" ); -const [description, setDescription] = useState(data.description || ""); const [label, setLabel] = useState(data.label || ""); const [labelType, setLabelType] = useState( (data.label || "").startsWith("starts-with:") ? "starts-with:" : "" @@ -86,7 +80,6 @@ const handleSubmit = () => { onSubmit({ teamName, - description, label: labelType + backwardsCompatibleLabel(label), editPost, useLabels, @@ -97,7 +90,7 @@ const handleSubmit = () => { return ( -

{data.teamName ? "Edit label" : "Create group"}

+

{data.teamName == "" ? "Edit label" : "Create label"}

-
- Group description - -
+
Would you like this group to limit their restrictions to a single diff --git a/src/devhub/entity/team/LabelRow.jsx b/src/devhub/entity/team/LabelRow.jsx index 3cbbcd6eb..f3501c55d 100644 --- a/src/devhub/entity/team/LabelRow.jsx +++ b/src/devhub/entity/team/LabelRow.jsx @@ -158,97 +158,94 @@ const backwardsCompatibleLabel = (oldLabel) => { const backwardsCompatibleTeam = (oldTeam) => oldTeam.startsWith("team:") ? oldTeam.slice(5) : oldTeam; -return editMode ? ( +return ( <> - setEditMode(false), - onSubmit: (params) => editTeam(params), - }} - /> - setAlertMessage(""), - message: alertMessage, - }} - /> - -) : ( - /** - * label name - Type - Members - Only allow members to use label - Allow members to edit any post with label - Actions - */ - - - - - - {(label || "").startsWith("starts-with:") - ? "Multiple labels with common prefix" - : "Single label"} - - - {metadata.children && ( -
- {metadata.children.length ? ( - metadata.children.map((child) =>

{child}

) - ) : ( -
No members in this group
- )} -
- )} - - -
- setUseLabels(!useLabels)} - disabled={disabled} + + + -
- - -
- setEditPost(!editPost)} - disabled={disabled} + + + {(label || "").startsWith("starts-with:") + ? "Multiple labels with common prefix" + : "Single label"} + + + {metadata.children && ( +
+ {metadata.children.length ? ( + metadata.children.map((child) =>

{child}

) + ) : ( +
No members in this group
+ )} +
+ )} + + +
+ setUseLabels(!useLabels)} + disabled={disabled} + /> +
+ + +
+ setEditPost(!editPost)} + disabled={disabled} + /> +
+ + + setEditMode(true), + }} /> -
- - - setEditMode(true), - }} - /> - - + + + {editMode && ( + + + setEditMode(false), + onSubmit: (params) => editTeam(params), + }} + /> + setAlertMessage(""), + message: alertMessage, + }} + /> + + + )} + ); diff --git a/src/devhub/entity/team/TeamInfo.jsx b/src/devhub/entity/team/TeamInfo.jsx deleted file mode 100644 index 302e2285a..000000000 --- a/src/devhub/entity/team/TeamInfo.jsx +++ /dev/null @@ -1,259 +0,0 @@ -/** - * In the context of the contract, a group is essentially a member identified - * by the prefix 'team:'; therefore, on the front end, we also employ 'team,' - * with the user interface displaying 'group' for clarity. - */ -const { Tile } = - VM.require("${REPL_DEVHUB}/widget/devhub.components.molecule.Tile") || - (() => <>); - -const { getAccessControlInfo, getRootMembers } = VM.require( - "${REPL_DEVHUB}/widget/core.adapter.devhub-contract" -); - -if (!getAccessControlInfo || !getRootMembers) { - return

Loading modules...

; -} - -const accessControlInfo = getAccessControlInfo(); -const rootMembers = getRootMembers(); -const allTeamNames = Object.keys(rootMembers || {}); - -if (!accessControlInfo || !rootMembers) { - return

Loading access control info...

; -} - -const { teamName } = props; -const teamModerators = teamName == "team:moderators"; -const label = Object.keys(rootMembers[teamName].permissions)[0] || ""; -const metadata = accessControlInfo.members_list[teamName]; -const editPost = rootMembers[teamName].permissions[label].includes("edit-post"); -const useLabels = - rootMembers[teamName].permissions[label].includes("use-labels"); -const members = rootMembers[teamName].children || []; - -const configuratorData = { - teamName: teamName, - description: metadata.description, - label: label, - members, - editPost, - useLabels, -}; - -const [editMode, setEditMode] = useState(false); -const [alertMessage, setAlertMessage] = useState(""); - -function arrayEq(arr1, arr2) { - if (arr1.length !== arr2.length) { - return false; - } - const sortedArr1 = arr1.slice().sort(); - const sortedArr2 = arr2.slice().sort(); - for (let i = 0; i < sortedArr1.length; i++) { - if (sortedArr1[i] !== sortedArr2[i]) { - return false; - } - } - - return true; -} - -function editTeam({ - teamName: tmnm, - description: dscrptn, - label: lbl, - editPost: edtpst, - useLabels: uslbls, - members: mmbrs, -}) { - let txn = []; - let numberOfChanges = 0; - - if (backwardsCompatibleTeam(teamName) !== tmnm) { - numberOfChanges++; - if (allTeamNames.includes(`team:${tmnm}`)) { - return setAlertMessage("This team name already exists"); - } - } - - console.log(label, lbl); - if (label !== lbl) { - const allLabels = Object.keys(accessControlInfo.rules_list); - if (allLabels.includes(lbl)) { - return setAlertMessage( - "This label is already restricted by another team" - ); - } - } - - if (description !== dscrptn || editPost !== edtpst || useLabels !== uslbls) { - numberOfChanges++; - } - - if (!arrayEq(members, mmbrs)) { - numberOfChanges++; - let membersAndTeams = Object.keys(accessControlInfo.members_list); - - mmbrs.forEach((member) => { - if (!membersAndTeams.includes(member)) { - // Contract panic member does not exist in the members_list yet. - txn.push({ - contractName: "${REPL_DEVHUB_CONTRACT}", - methodName: "add_member", - args: { - member: member, - metadata: { - member_metadata_version: "V0", - description: "", - permissions: {}, - children: [], - parents: [], - }, - }, - deposit: Big(0).pow(21), - gas: Big(10).pow(12).mul(100), - }); - } - }); - } - - if (numberOfChanges < 1) { - return setAlertMessage("No changes found."); - } - - Near.call([ - ...txn, - { - contractName: "${REPL_DEVHUB_CONTRACT}", - methodName: "edit_member", - args: { - member: `team:${tmnm}`, - metadata: { - member_metadata_version: "V0", - description: dscrptn, - permissions: { - [lbl]: [ - ...(edtpst ? ["edit-post"] : []), - ...(uslbls ? ["use-labels"] : []), - ], - }, - children: mmbrs, - parents: [], - }, - }, - deposit: Big(0).pow(21), - gas: Big(10).pow(12).mul(100), - }, - ]); -} -// Teams are saved in contract by their prefix 'team:' -// This function makes the teamName display friendly. -const backwardsCompatibleTeam = (oldTeam) => - oldTeam.startsWith("team:") ? oldTeam.slice(5) : oldTeam; - -return editMode ? ( - <> - setEditMode(false), - onSubmit: (params) => editTeam(params), - }} - /> - setAlertMessage(""), - message: alertMessage, - }} - /> - -) : ( -
-
-
-

{backwardsCompatibleTeam(teamName)}

- setEditMode(true), - }} - /> -
-
- -
- { -

- {!teamModerators ? ( - - ) : ( - "The moderator group has permissions to edit any posts and apply all labels, including restricted ones." - )} -

- } - {/* Hide this case of the moderators */} - {!teamModerators && ( - <> - - Restricted label: - - - -
Permissions associated with that label
- - - - )} - -
Members
- - {metadata.children && ( -
- {metadata.children.length ? ( - metadata.children.map((child) => ( - - - - )) - ) : ( -
No members in this group
- )} -
- )} -
-
-); diff --git a/src/devhub/page/admin.jsx b/src/devhub/page/admin.jsx index 2aeb313de..067735168 100644 --- a/src/devhub/page/admin.jsx +++ b/src/devhub/page/admin.jsx @@ -33,6 +33,11 @@ if (!fc) { const featuredCommunityList = fc || []; const allMetadata = getAllCommunitiesMetadata(); const accessControlInfo = getAccessControlInfo(); + +if (!accessControlInfo.members_list) { + return

Loading members list...

; +} + const rootMembers = getRootMembers(); const teamNames = Object.keys(rootMembers || {}); @@ -46,9 +51,11 @@ const [createTeam, setCreateTeam] = useState(false); const [communityHandles, setCommunityHandles] = useState( featuredCommunityList.map(({ handle }) => handle) ); -const [newItem, setNewItem] = useState(""); -const [editMode, setEditMode] = useState(false); const [previewConnect, setPreviewConnect] = useState(false); +const [editModerators, setEditModerators] = useState(false); +const [moderators, setModerators] = useState( + accessControlInfo.members_list["team:moderators"].children || [] +); const handleResetItems = () => { setCommunityHandles(featuredCommunityList.map(({ handle }) => handle)); @@ -95,21 +102,25 @@ function handleSubmit() { setFeaturedCommunities({ handles: communityHandles }); } -function createNewTeam({ +function createEditTeam({ teamName, description, label, editPost, useLabels, members, + contractCall, // TODO typescript edit_member || add_member }) { let txn = []; - if (rootMembers.includes(`team:${teamName}`)) { + if ( + rootMembers.includes(`team:${teamName}`) && + contractCall === "add_member" + ) { return setAlertMessage("This team name already exists"); } const allLabels = Object.keys(accessControlInfo.rules_list); - if (allLabels.includes(label)) { + if (allLabels.includes(label) && contractCall === "add_member") { return setAlertMessage("This label is already restricted by another team"); } @@ -142,7 +153,7 @@ function createNewTeam({ ...txn, { contractName: "${REPL_DEVHUB_CONTRACT}", - methodName: "add_member", + methodName: contractCall, // add_member || edit_member args: { member: `team:${teamName}`, metadata: { @@ -164,6 +175,19 @@ function createNewTeam({ ]); } +const handleEditModerators = () => { + createEditTeam({ + teamName: "team:moderators", + description: + "The moderator group has permissions to edit any posts and apply all labels, including restricted ones.", + label: "any", + editPost: true, + useLabels: true, + members: moderators, + contractCall: "edit_member", + }); +}; + const Item = styled.div` padding: 10px; margin: 5px; @@ -256,59 +280,27 @@ return ( message: communityMessage, }} /> - +

Manage featured communities

- {communityHandles.map((item, index) => ( - -
- -
- -
- ))} - {communityHandles.length < 5 && ( - -
- setNewItem(e.target.value), - value: newItem, - placeholder: "zero-knowledge", - inputProps: { - prefix: "Community handle", - }, - }} - /> -
- -
- )} + + !allMetadata.map(({ handle }) => handle).includes(newItem), + invalidate: () => + setCommunityMessage( + "This community handle does not exist, make sure you use an existing handle." + ), + }} + />
{ - setEditMode(false); handleResetItems(); }, }} @@ -352,13 +343,14 @@ return ( testId: "preview-homepage", }} /> - - {previewConnect && ( - - )} +
+ {previewConnect && ( + + )} +

Moderators

- {teamNames.includes("team:moderators") && ( - <> - - setAlertMessage(""), - message: alertMessage, - }} - /> - - )} +
+
+ The moderator group has permissions to edit any posts and apply + all labels, including restricted ones. +
+ setEditModerators(!editModerators), + testId: "edit-members", + }} + /> +
+ + {editModerators ? ( + <> + true, + invalidate: () => null, // TODO check if id exists on near + }} + /> +
+ { + handleResetItems(); + }, + }} + /> + +
+ + ) : ( + <> +
Members
+ + {moderators && ( +
+ {moderators.length ? ( + moderators.map((child) => ( + + + + )) + ) : ( +
No moderators
+ )} +
+ )} + + )} +
setCreateTeam(false), - onSubmit: (params) => createNewTeam(params), + onSubmit: (params) => + createEditTeam({ ...params, contractCall: "add_member" }), }} /> )} -
+