From b9d1db4d5c2304705b7a324c78a270a150047ca3 Mon Sep 17 00:00:00 2001 From: juliopavila Date: Thu, 9 May 2024 18:39:38 -0300 Subject: [PATCH] feat: refactor permissions --- packages/app/src/App.tsx | 24 +- .../src/components/commons/PermissionItem.tsx | 67 ++--- .../src/components/commons/WalletBadge.tsx | 34 ++- .../views/publication/PermissionView.tsx | 247 ++++-------------- .../components/PermissionSection.tsx | 28 +- packages/app/src/schemas/permission.schema.ts | 27 ++ .../contexts/publication.context.tsx | 2 +- .../contexts/publication.types.ts | 2 +- .../publications/hooks/usePublications.ts | 9 + .../publications/utils/publication-methods.ts | 16 ++ 10 files changed, 169 insertions(+), 287 deletions(-) create mode 100644 packages/app/src/schemas/permission.schema.ts diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index d477cdd1..6a5a1775 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -60,18 +60,18 @@ const App: React.FC = () => { } /> } /> {/* } /> - } /> - } /> - } /> - } /> - } /> - - } /> - } /> - } /> - } /> - } /> - */} + } /> + } /> + } /> + } /> + } /> */} + + } /> + {/* } /> + } /> + } /> + } /> */} + diff --git a/packages/app/src/components/commons/PermissionItem.tsx b/packages/app/src/components/commons/PermissionItem.tsx index a52f2de2..5a83c1da 100644 --- a/packages/app/src/components/commons/PermissionItem.tsx +++ b/packages/app/src/components/commons/PermissionItem.tsx @@ -1,15 +1,15 @@ import React, { useEffect, useState } from "react" -import { Box, Grid, Stack } from "@mui/material" +import { Box, CircularProgress, Grid, Stack } from "@mui/material" import { styled } from "@mui/styles" -import { palette } from "../../theme" -import { Permission, Publication } from "../../models/publication" +import { palette } from "@/theme" +import { Permission, Publication } from "@/models/publication" import EditIcon from "@mui/icons-material/Edit" import CloseIcon from "@mui/icons-material/Close" import { UserBadge } from "./UserBadge" -import usePublication from "../../services/publications/hooks/usePublication" -import { PermissionFormType } from "../views/publication/PermissionView" -import usePoster from "../../services/poster/hooks/usePoster" -import { useNavigate, useParams } from "react-router-dom" +import { useNavigate } from "react-router-dom" +import usePublications from "@/services/publications/hooks/usePublications" +import { TransactionStatus } from "@/hooks/useContract" +import { usePublicationContext } from "@/services/publications/contexts" const PermissionItemContainer = styled(Box)({ alignItems: "center", @@ -40,60 +40,31 @@ type PermissionItemProps = { onClick: (id: string) => void } const PermissionItem: React.FC = ({ publication, permission, canEdit, showRemove, onClick }) => { - const { publicationSlug } = useParams<{ publicationSlug: string }>() const { address, id } = permission const navigate = useNavigate() const [deleteLoading, setDeleteLoading] = useState(false) - const { - indexing: deleteIndexing, - setExecutePollInterval: deleteInterval, - transactionCompleted: deleteTransaction, - setCurrentUserPermission, - } = usePublication(publicationSlug || "") - const { givePermission } = usePoster() - const handlePermission = async (data: PermissionFormType) => { - if (publication) { - await givePermission({ - action: "publication/permissions", - id: publication.id, - account: permission?.address || "", - permissions: { - "article/create": data.articleCreate, - "article/update": data.articleUpdate, - "article/delete": data.articleDelete, - "publication/delete": data.publicationDelete, - "publication/update": data.publicationUpdate, - "publication/permissions": data.publicationPermissions, - }, - }).then((res) => { - if (res && res.error) { - setDeleteLoading(false) - } else { - deleteInterval(true) - } - }) - } - } + const { givePermission, status, txLoading } = usePublications() + const { savePermission } = usePublicationContext() useEffect(() => { - if (deleteTransaction) { + if (status === TransactionStatus.Success) { + savePermission(undefined) navigate(-1) } - }, [deleteTransaction, navigate]) + }, [navigate, savePermission, status]) - const handleDeletePermission = () => { - if (publication && publication.permissions) { + const handleDeletePermission = async () => { + if (publication?.id && permission) { setDeleteLoading(true) - setCurrentUserPermission(publication.permissions) - handlePermission({ + await givePermission(publication.id, { articleCreate: false, articleDelete: false, articleUpdate: false, publicationDelete: false, publicationPermissions: false, publicationUpdate: false, - account: "", + account: permission?.address, }) } } @@ -105,7 +76,7 @@ const PermissionItem: React.FC = ({ publication, permission {canEdit && ( { - if (!deleteLoading || !deleteIndexing) { + if (!deleteLoading) { canEdit && onClick(id ? id : "") } }} @@ -129,11 +100,13 @@ const PermissionItem: React.FC = ({ publication, permission }, }} onClick={() => { - if (!deleteLoading || !deleteIndexing) { + if (!deleteLoading) { handleDeletePermission() } }} > + {" "} + {txLoading.update || (deleteLoading && )} diff --git a/packages/app/src/components/commons/WalletBadge.tsx b/packages/app/src/components/commons/WalletBadge.tsx index 4d2f459c..7b635855 100644 --- a/packages/app/src/components/commons/WalletBadge.tsx +++ b/packages/app/src/components/commons/WalletBadge.tsx @@ -1,7 +1,9 @@ import React from "react" -import { Avatar } from "@mui/material" +import { Avatar, Box, Typography } from "@mui/material" import * as blockies from "blockies-ts" import { useNotification } from "@/hooks/useNotification" +import { typography } from "@/theme" +import { shortAddress } from "@/utils/string-handler" type WalletBadgeProps = { copyable?: boolean @@ -25,17 +27,23 @@ export const WalletBadge: React.FC = ({ address, copyable }) = } } return ( - + + + + {shortAddress(address).toLowerCase()} + + ) } diff --git a/packages/app/src/components/views/publication/PermissionView.tsx b/packages/app/src/components/views/publication/PermissionView.tsx index 34bdb742..b692b29b 100644 --- a/packages/app/src/components/views/publication/PermissionView.tsx +++ b/packages/app/src/components/views/publication/PermissionView.tsx @@ -1,27 +1,17 @@ import React, { useEffect, useState } from "react" -import { - Grid, - Box, - Typography, - styled, - TextField, - Divider, - Button, - FormHelperText, - CircularProgress, - InputLabel, -} from "@mui/material" +import { Grid, Box, Typography, styled, TextField, Divider, Button, CircularProgress, InputLabel } from "@mui/material" import RemoveCircleOutlineIcon from "@mui/icons-material/RemoveCircleOutline" -import { palette, typography } from "../../../theme" +import { palette, typography } from "@/theme" import CloseIcon from "@mui/icons-material/Close" import { useNavigate, useParams } from "react-router-dom" -import { CustomCheckbox } from "../../commons/Checkbox" +import { CustomCheckbox } from "@/components/commons/Checkbox" import { Controller, useForm } from "react-hook-form" -import { ethers } from "ethers" -import usePoster from "../../../services/poster/hooks/usePoster" -import { usePublicationContext } from "../../../services/publications/contexts" -import usePublication from "../../../services/publications/hooks/usePublication" -import { WalletBadge } from "../../commons/WalletBadge" +import { usePublicationContext } from "@/services/publications/contexts" +import { WalletBadge } from "@/components/commons/WalletBadge" +import { yupResolver } from "@hookform/resolvers/yup" +import { PermissionFormSchema, permissionsSchema } from "@/schemas/permission.schema" +import usePublications from "@/services/publications/hooks/usePublications" +import { TransactionStatus } from "@/hooks/useContract" type OptionsType = { label: string @@ -34,16 +24,6 @@ type OptionsType = { | "publicationPermissions" } -export type PermissionFormType = { - articleCreate: boolean - articleDelete: boolean - articleUpdate: boolean - publicationDelete: boolean - publicationPermissions: boolean - publicationUpdate: boolean - account: string -} - const PermissionContainer = styled(Grid)({ justifyContent: "center", alignItems: "center", @@ -91,162 +71,60 @@ const PUBLICATIONS_OPTIONS: OptionsType[] = [ }, ] -const INITIAL_VALUE = { - articleCreate: false, - articleDelete: false, - articleUpdate: false, - publicationDelete: false, - publicationPermissions: false, - publicationUpdate: false, - account: "", -} - export const PermissionView: React.FC = () => { const navigate = useNavigate() - const { givePermission } = usePoster() - const { type, publicationSlug } = useParams<{ type: "edit" | "new"; publicationSlug: string }>() - const { publication, permission } = usePublicationContext() - const { - indexing: deleteIndexing, - setExecutePollInterval: deleteInterval, - transactionCompleted: deleteTransaction, - setCurrentUserPermission, - } = usePublication(publicationSlug || "") - const { - indexing: newPermissionIndexing, - setExecutePollInterval: newPermissionInterval, - transactionCompleted: permissionTransaction, - setAccountPermission, - } = usePublication(publicationSlug || "") - const { - indexing: updateIndexing, - setExecutePollInterval: updateInterval, - transactionCompleted: updateTransaction, - setCurrentUserPermission: updateCurrentUser, - } = usePublication(publicationSlug || "") - const [loading, setLoading] = useState(false) - const [deleteLoading, setDeleteLoading] = useState(false) + // const { givePermission } = usePoster() + const { type } = useParams<{ type: "edit" | "new"; publicationSlug: string }>() + const { publication, permission, savePermission } = usePublicationContext() + const { txLoading, givePermission, status } = usePublications() + const [deleteLoading, setDeleteloading] = useState(false) const { control, handleSubmit, - watch, - clearErrors, - setError, - setValue, formState: { errors }, } = useForm({ - defaultValues: INITIAL_VALUE, + defaultValues: { + articleCreate: permission?.articleCreate ?? false, + articleDelete: permission?.articleDelete ?? false, + articleUpdate: permission?.articleUpdate ?? false, + publicationDelete: permission?.publicationDelete ?? false, + publicationPermissions: permission?.publicationPermissions ?? false, + publicationUpdate: permission?.publicationUpdate ?? false, + account: permission?.address ?? "", + }, + resolver: yupResolver(permissionsSchema), }) - const account = watch("account") useEffect(() => { - if (type === "edit" && permission) { - const { - articleCreate, - articleDelete, - articleUpdate, - publicationDelete, - publicationPermissions, - publicationUpdate, - } = permission - setValue("articleCreate", articleCreate) - setValue("articleDelete", articleDelete) - setValue("articleUpdate", articleUpdate) - setValue("publicationDelete", publicationDelete) - setValue("publicationPermissions", publicationPermissions) - setValue("publicationUpdate", publicationUpdate) - } - }, [type, setValue, permission]) - - useEffect(() => { - if (account) { - const isValid = ethers.utils.isAddress(account) - if (!isValid) { - setError("account", { - type: "manual", - message: "Please provide a valid address", - }) - } else { - clearErrors("account") - } - } - }, [account, setError, clearErrors]) - - useEffect(() => { - if (deleteTransaction || updateTransaction || permissionTransaction) { + if (status === TransactionStatus.Success) { + setDeleteloading(false) + savePermission(undefined) navigate(-1) } - }, [deleteTransaction, navigate, permissionTransaction, updateTransaction]) + }, [navigate, savePermission, status]) - const onSubmitHandler = (data: PermissionFormType) => { - if (type === "new") { - setAccountPermission(data.account) - handlePermission(data, "new") - } - if (type === "edit") { - handlePermission(data, "edit") + const onSubmitHandler = async (data: PermissionFormSchema) => { + if (publication?.id) { + await givePermission(publication.id, data) } } - const handlePermission = async (data: PermissionFormType, type: "new" | "edit" | "delete") => { - if (publication) { - if (["new", "edit"].includes(type)) { - setLoading(true) - } - await givePermission({ - action: "publication/permissions", - id: publication.id, - account: type === "new" ? data.account : permission?.address || "", - permissions: { - "article/create": data.articleCreate, - "article/update": data.articleUpdate, - "article/delete": data.articleDelete, - "publication/delete": data.publicationDelete, - "publication/update": data.publicationUpdate, - "publication/permissions": data.publicationPermissions, - }, - }).then((res) => { - if (res && res.error) { - setDeleteLoading(false) - setLoading(false) - } else { - if (type === "delete") { - deleteInterval(true) - } - if (type === "new") { - newPermissionInterval(true) - } - if (type === "edit") { - updateInterval(true) - if (publication && publication.permissions) { - updateCurrentUser(publication.permissions) - } - } - } + const handleDeletePermission = async () => { + if (publication?.id && permission) { + setDeleteloading(true) + await givePermission(publication.id, { + articleCreate: false, + articleDelete: false, + articleUpdate: false, + publicationDelete: false, + publicationPermissions: false, + publicationUpdate: false, + account: permission?.address, }) } } - const handleDeletePermission = () => { - if (publication && publication.permissions) { - setDeleteLoading(true) - setCurrentUserPermission(publication.permissions) - handlePermission( - { - articleCreate: false, - articleDelete: false, - articleUpdate: false, - publicationDelete: false, - publicationPermissions: false, - publicationUpdate: false, - account: "", - }, - "delete", - ) - } - } - return ( @@ -259,7 +137,7 @@ export const PermissionView: React.FC = () => { -
onSubmitHandler(data as PermissionFormType))}> + onSubmitHandler(data as PermissionFormSchema))}> {type === "new" && ( @@ -269,15 +147,16 @@ export const PermissionView: React.FC = () => { render={({ field }) => ( <> Address - + )} /> - {errors && errors.account && ( - - {errors.account.message} - - )} )} {type === "edit" && permission && ( @@ -294,12 +173,12 @@ export const PermissionView: React.FC = () => { variant="contained" size="small" onClick={handleDeletePermission} - disabled={loading || deleteLoading || deleteIndexing || updateIndexing || newPermissionIndexing} + disabled={txLoading.update || deleteLoading} startIcon={} sx={{ whiteSpace: "nowrap" }} > {deleteLoading && } - {deleteIndexing ? "Indexing..." : "Remove User"} + Remove User )} @@ -336,28 +215,18 @@ export const PermissionView: React.FC = () => { {type === "new" && ( - )} {type === "edit" && ( - )} diff --git a/packages/app/src/components/views/publication/components/PermissionSection.tsx b/packages/app/src/components/views/publication/components/PermissionSection.tsx index 734f39bd..8c032069 100644 --- a/packages/app/src/components/views/publication/components/PermissionSection.tsx +++ b/packages/app/src/components/views/publication/components/PermissionSection.tsx @@ -1,11 +1,11 @@ import React from "react" import { Box, Grid, Container, Typography, Stack } from "@mui/material" -import { palette } from "../../../../theme" +import { palette } from "@/theme" import AddIcon from "@mui/icons-material/Add" -import { usePublicationContext } from "../../../../services/publications/contexts" -import PermissionItem from "../../../commons/PermissionItem" +import { usePublicationContext } from "@/services/publications/contexts" +import PermissionItem from "@/components/commons/PermissionItem" import { useNavigate } from "react-router-dom" -import { haveActionPermission, usersWithPermissions } from "../../../../utils/permission" +import { haveActionPermission, usersWithPermissions } from "@/utils/permission" import { useWeb3ModalAccount } from "@web3modal/ethers5/react" export const PermissionSection: React.FC = () => { @@ -17,26 +17,6 @@ export const PermissionSection: React.FC = () => { const havePermissionToEdit = haveActionPermission(permissions, "publicationPermissions", address || "") return ( - {/* - - - Permissions - - - {havePermissionToEdit && ( - - - - )} - */} {usersPermissions.length > 0 && usersPermissions.map((permission) => ( diff --git a/packages/app/src/schemas/permission.schema.ts b/packages/app/src/schemas/permission.schema.ts new file mode 100644 index 00000000..33f23e47 --- /dev/null +++ b/packages/app/src/schemas/permission.schema.ts @@ -0,0 +1,27 @@ +import { ethers } from "ethers" +import * as yup from "yup" + +export interface PermissionFormSchema { + articleCreate: boolean + articleDelete: boolean + articleUpdate: boolean + publicationDelete: boolean + publicationPermissions: boolean + publicationUpdate: boolean + account: string +} + +export const permissionsSchema = yup.object({ + articleCreate: yup.boolean().default(false), + articleDelete: yup.boolean().default(false), + articleUpdate: yup.boolean().default(false), + publicationDelete: yup.boolean().default(false), + publicationPermissions: yup.boolean().default(false), + publicationUpdate: yup.boolean().default(false), + account: yup + .string() + .required("Account is required") + .test("is-eth-address", "Please provide a valid address", (value) => + value ? ethers.utils.isAddress(value) : false, + ), +}) diff --git a/packages/app/src/services/publications/contexts/publication.context.tsx b/packages/app/src/services/publications/contexts/publication.context.tsx index d6348aab..d4f46579 100644 --- a/packages/app/src/services/publications/contexts/publication.context.tsx +++ b/packages/app/src/services/publications/contexts/publication.context.tsx @@ -53,7 +53,7 @@ const PublicationProvider = ({ children }: PublicationProviderProps) => { const savePublication = (publication: Publication | undefined) => setPublication(publication) const savePublications = (publications: Publication[] | undefined) => setPublications(publications) - const savePermission = (permission: Permission) => setPermission(permission) + const savePermission = (permission: Permission | undefined) => setPermission(permission) const saveIsEditingPublication = (isEditing: boolean) => setEditingPublication(isEditing) const saveDraftPublicationImage = (file: File | undefined) => setDraftPublicationImage(file) diff --git a/packages/app/src/services/publications/contexts/publication.types.ts b/packages/app/src/services/publications/contexts/publication.types.ts index d6b15e95..4baacf83 100644 --- a/packages/app/src/services/publications/contexts/publication.types.ts +++ b/packages/app/src/services/publications/contexts/publication.types.ts @@ -21,7 +21,7 @@ export type PublicationContextType = { saveIsEditingPublication: (isEditing: boolean) => void saveDraftPublicationImage: (file: File | undefined) => void setCurrentPath: (path: string | undefined) => void - savePermission: (permission: Permission) => void + savePermission: (permission: Permission | undefined) => void savePublication: (publication: Publication | undefined) => void savePublications: (publications: Publication[] | undefined) => void } diff --git a/packages/app/src/services/publications/hooks/usePublications.ts b/packages/app/src/services/publications/hooks/usePublications.ts index 4a50a7ea..d5fbaaf8 100644 --- a/packages/app/src/services/publications/hooks/usePublications.ts +++ b/packages/app/src/services/publications/hooks/usePublications.ts @@ -13,10 +13,12 @@ import { useNavigate } from "react-router-dom" import { useIPFSContext } from "@/services/ipfs/context" import { deletePublicationBody, + generatePermissionBody, generatePublicationBody, generateUpdatePublicationBody, } from "@/services/publications/utils/publication-methods" import { usePublicationContext } from "@/services/publications/contexts" +import { PermissionFormSchema } from "@/schemas/permission.schema" interface TransactionBody extends Object { image?: string @@ -161,6 +163,12 @@ const usePublications = () => { setPublicationIdToDelete(publicationIdToDelete) }) } + const givePermission = async (publicationId: string, form: PermissionFormSchema) => { + const publicationBody = await generatePermissionBody(publicationId, form) + handleTransaction(publicationBody, "update", () => { + setPublicationIdToUpdate(publicationId) + }) + } return { loading: fetching, @@ -171,6 +179,7 @@ const usePublications = () => { createNewPublication, deletePublication, updatePublication, + givePermission, refetch, executeQuery, } diff --git a/packages/app/src/services/publications/utils/publication-methods.ts b/packages/app/src/services/publications/utils/publication-methods.ts index 39f121f8..c964fcdd 100644 --- a/packages/app/src/services/publications/utils/publication-methods.ts +++ b/packages/app/src/services/publications/utils/publication-methods.ts @@ -1,4 +1,5 @@ import { PublicationAction } from "@/models/publication" +import { PermissionFormSchema } from "@/schemas/permission.schema" import { PublicationFormSchema, UpdatePublicationFormSchema } from "@/schemas/publication.schema" const processPublicationBody = async ( @@ -55,3 +56,18 @@ export const deletePublicationBody = async (id: string) => { id, } } +export const generatePermissionBody = async (publicationId: string, permissionFields: PermissionFormSchema) => { + return { + action: "publication/permissions", + id: publicationId, + account: permissionFields.account, + permissions: { + "article/create": permissionFields.articleCreate, + "article/update": permissionFields.articleUpdate, + "article/delete": permissionFields.articleDelete, + "publication/delete": permissionFields.publicationDelete, + "publication/update": permissionFields.publicationUpdate, + "publication/permissions": permissionFields.publicationPermissions, + }, + } +}