From 99c8346188d3044b2255c4bd5b4ed40dbeeb04fe Mon Sep 17 00:00:00 2001 From: Ramida J Date: Tue, 17 Jan 2023 16:27:27 +0700 Subject: [PATCH 1/7] feat(components): filter code by instantiate permission --- CHANGELOG.md | 1 + src/lib/components/InputWithIcon.tsx | 4 +- .../components/forms/FilterByPermission.tsx | 59 +++++++++++++++++++ src/lib/components/forms/SelectInput.tsx | 54 +++++++++++++---- src/lib/pages/all-codes/data.ts | 35 +++++++++-- src/lib/pages/all-codes/index.tsx | 48 +++++++++++---- 6 files changed, 168 insertions(+), 33 deletions(-) create mode 100644 src/lib/components/forms/FilterByPermission.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 84c3af937..357568c6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- [#93](https://github.com/alleslabs/celatone-frontend/pull/93) Add filter code by instantiate permission in all codes - [#75](https://github.com/alleslabs/celatone-frontend/pull/75) Add code-related contracts table to the code detail page - [#81](https://github.com/alleslabs/celatone-frontend/pull/81) Can scroll on side bar with fix deploy new contract button - [#86](https://github.com/alleslabs/celatone-frontend/pull/86) Add transactions table in contract details page diff --git a/src/lib/components/InputWithIcon.tsx b/src/lib/components/InputWithIcon.tsx index 8d049aa9f..5fce94bb9 100644 --- a/src/lib/components/InputWithIcon.tsx +++ b/src/lib/components/InputWithIcon.tsx @@ -24,8 +24,8 @@ const InputWithIcon = ({ onChange={onChange} size={size} /> - - + + ); diff --git a/src/lib/components/forms/FilterByPermission.tsx b/src/lib/components/forms/FilterByPermission.tsx new file mode 100644 index 000000000..a7d6085c2 --- /dev/null +++ b/src/lib/components/forms/FilterByPermission.tsx @@ -0,0 +1,59 @@ +import { Grid } from "@chakra-ui/react"; +import type { IconType } from "react-icons"; +import { MdCheck, MdHowToVote, MdPerson } from "react-icons/md"; + +import { SelectInput } from "./SelectInput"; + +interface PermissionOption { + label: string; + value: string; + disabled: boolean; + icon?: IconType; + iconColor: string; +} + +interface FilterByPermissionProps { + setPermissionValue: (newVal: string) => void; + initialSelected: string; +} + +const options: PermissionOption[] = [ + { + label: "All", + value: "all", + disabled: false, + icon: MdCheck, + iconColor: "gray.600", + }, + { + label: "Can Instantiate without proposal", + value: "without-proposal", + disabled: false, + icon: MdPerson, + iconColor: "primary.main", + }, + { + label: "Instantiate through proposal only", + value: "with-proposal", + disabled: false, + icon: MdHowToVote, + iconColor: "text.dark", + }, +]; + +export const FilterByPermission = ({ + setPermissionValue, + initialSelected, +}: FilterByPermissionProps) => { + return ( + + + + ); +}; diff --git a/src/lib/components/forms/SelectInput.tsx b/src/lib/components/forms/SelectInput.tsx index 938ffa51e..ff880993b 100644 --- a/src/lib/components/forms/SelectInput.tsx +++ b/src/lib/components/forms/SelectInput.tsx @@ -6,22 +6,31 @@ import { Popover, PopoverTrigger, PopoverContent, - Box, useDisclosure, useOutsideClick, + Flex, + InputLeftElement, } from "@chakra-ui/react"; import type { MutableRefObject, ReactNode } from "react"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; +import type { IconType } from "react-icons/lib"; import { MdArrowDropDown } from "react-icons/md"; -const ITEM_HEIGHT = 57; +const ITEM_HEIGHT = 56; interface SelectInputProps { formLabel?: string; - options: { label: string; value: string; disabled: boolean }[]; + options: { + label: string; + value: string; + disabled: boolean; + icon?: IconType; + iconColor?: string; + }[]; onChange: (newVal: string) => void; placeholder?: string; initialSelected: string; + hasDivider?: boolean; } interface SelectItemProps { @@ -32,18 +41,19 @@ interface SelectItemProps { const SelectItem = ({ children, onSelect, disabled }: SelectItemProps) => { return ( - {children} - + ); }; @@ -53,19 +63,27 @@ export const SelectInput = ({ onChange, placeholder = "", initialSelected, + hasDivider = false, }: SelectInputProps) => { const optionRef = useRef() as MutableRefObject; const { isOpen, onClose, onOpen } = useDisclosure(); - + const inputRef = useRef() as MutableRefObject; const [selected, setSelected] = useState( () => options.find((asset) => asset.value === initialSelected)?.label ?? "" ); - + const [inputRefWidth, setInputRefWidth] = useState(); useOutsideClick({ ref: optionRef, handler: () => isOpen && onClose(), }); + const selectedOption = + options.find((item) => item.label === selected) || undefined; + useEffect(() => { + if (inputRef.current) { + setInputRefWidth(inputRef.current.clientWidth); + } + }, [inputRef]); return ( @@ -92,6 +110,15 @@ export const SelectInput = ({ }} >
{formLabel}
+ {selectedOption?.icon && ( + + + + )} @@ -109,7 +138,7 @@ export const SelectInput = ({ ref={optionRef} border="unset" bg="gray.900" - w="200px" + w={inputRefWidth} maxH={`${ITEM_HEIGHT * 4}px`} overflow="scroll" borderRadius="4px" @@ -118,12 +147,12 @@ export const SelectInput = ({ }} sx={{ "> div:not(:last-of-type)": { - borderBottom: "1px solid", - borderBottomColor: "divider.main", + borderBottom: hasDivider && "1px solid", + borderBottomColor: hasDivider && "divider.main", }, }} > - {options.map(({ label, value, disabled }) => ( + {options.map(({ label, value, disabled, icon, iconColor }) => ( { @@ -133,6 +162,7 @@ export const SelectInput = ({ }} disabled={disabled} > + {icon && } {label} ))} diff --git a/src/lib/pages/all-codes/data.ts b/src/lib/pages/all-codes/data.ts index b2f60dfe5..e0d3dc29f 100644 --- a/src/lib/pages/all-codes/data.ts +++ b/src/lib/pages/all-codes/data.ts @@ -1,15 +1,19 @@ +import { useWallet } from "@cosmos-kit/react"; import { useMemo } from "react"; import { useCodeStore } from "lib/hooks"; import { useCodeListQuery } from "lib/services/codeService"; -import type { CodeInfo } from "lib/types"; +import type { CodeInfo, HumanAddr } from "lib/types"; interface AllCodesData { allCodes: CodeInfo[]; isLoading: boolean; } -export const useAllCodesData = (keyword?: string): AllCodesData => { +export const useAllCodesData = ( + keyword: string, + permissionValue: string +): AllCodesData => { const { getCodeLocalInfo, isCodeIdSaved } = useCodeStore(); const { data: rawAllCodes = [], isLoading } = useCodeListQuery(); @@ -18,11 +22,27 @@ export const useAllCodesData = (keyword?: string): AllCodesData => { description: getCodeLocalInfo(code.id)?.description, isSaved: isCodeIdSaved(code.id), })); + // console.log("permissionValue", permissionValue); + const { address } = useWallet(); return useMemo(() => { - const filterFn = (code: CodeInfo) => { - if (keyword === undefined) return true; + const permissionFilter = (code: CodeInfo) => { + const isEveryBody = code.instantiatePermission === "Everybody"; + const isNobody = code.instantiatePermission === "Nobody"; + const isInclude = code.permissionAddresses.includes(address as HumanAddr); + + switch (permissionValue) { + case "with-proposal": + return (!isEveryBody && !isInclude) || isNobody; + case "without-proposal": + return isInclude || isEveryBody; + case "all": + default: + return true; + } + }; + const searchFilter = (code: CodeInfo) => { const computedKeyword = keyword.trim(); if (computedKeyword.length === 0) return true; @@ -32,6 +52,9 @@ export const useAllCodesData = (keyword?: string): AllCodesData => { ); }; - return { allCodes: allCodes.filter(filterFn), isLoading }; - }, [keyword, allCodes, isLoading]); + return { + allCodes: allCodes.filter(permissionFilter).filter(searchFilter), + isLoading, + }; + }, [keyword, allCodes, isLoading, permissionValue, address]); }; diff --git a/src/lib/pages/all-codes/index.tsx b/src/lib/pages/all-codes/index.tsx index f4b55963b..c68587580 100644 --- a/src/lib/pages/all-codes/index.tsx +++ b/src/lib/pages/all-codes/index.tsx @@ -1,21 +1,36 @@ -import { Heading, Box } from "@chakra-ui/react"; +import { Heading, Box, Flex } from "@chakra-ui/react"; import { observer } from "mobx-react-lite"; import type { ChangeEvent } from "react"; -import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { FilterByPermission } from "lib/components/forms/FilterByPermission"; import InputWithIcon from "lib/components/InputWithIcon"; import { Loading } from "lib/components/Loading"; import CodesTable from "lib/pages/codes/components/CodesTable"; import { useAllCodesData } from "./data"; +interface AllCodeState { + keyword: string; + permissionValue: string; +} + const AllCodes = observer(() => { - const [keyword, setKeyword] = useState(""); - const { allCodes, isLoading } = useAllCodesData(keyword); + const { watch, setValue } = useForm({ + defaultValues: { + permissionValue: "all", + keyword: "", + }, + }); + const states = watch(); + const { allCodes, isLoading } = useAllCodesData( + states.keyword, + states.permissionValue + ); const handleFilterChange = (e: ChangeEvent) => { const inputValue = e.target.value; - setKeyword(inputValue); + setValue("keyword", inputValue); }; return ( @@ -24,13 +39,20 @@ const AllCodes = observer(() => { All Codes - - + + + + setValue("permissionValue", newVal) + } + /> + {isLoading ? ( @@ -39,7 +61,7 @@ const AllCodes = observer(() => { type="all" tableName="All Codes" codes={allCodes} - isSearching={!!keyword} + isSearching={!!states.keyword} /> )} From 9c7c5e31c51733575ec9ca3497f304c585bbb13a Mon Sep 17 00:00:00 2001 From: Ramida J Date: Tue, 17 Jan 2023 16:30:17 +0700 Subject: [PATCH 2/7] feat(components): remove comment --- src/lib/pages/all-codes/data.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/pages/all-codes/data.ts b/src/lib/pages/all-codes/data.ts index e0d3dc29f..9b51f2810 100644 --- a/src/lib/pages/all-codes/data.ts +++ b/src/lib/pages/all-codes/data.ts @@ -22,7 +22,6 @@ export const useAllCodesData = ( description: getCodeLocalInfo(code.id)?.description, isSaved: isCodeIdSaved(code.id), })); - // console.log("permissionValue", permissionValue); const { address } = useWallet(); return useMemo(() => { From 9b4c012356d98cfd709530e085766a318e7cefe6 Mon Sep 17 00:00:00 2001 From: Ramida J Date: Thu, 19 Jan 2023 13:56:05 +0700 Subject: [PATCH 3/7] feat(components): add permission filter for codes page --- src/lib/pages/codes/data.ts | 33 +++++++++++++++++++---- src/lib/pages/codes/index.tsx | 50 ++++++++++++++++++++++++----------- 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/src/lib/pages/codes/data.ts b/src/lib/pages/codes/data.ts index ab0149722..985bf52c7 100644 --- a/src/lib/pages/codes/data.ts +++ b/src/lib/pages/codes/data.ts @@ -6,7 +6,7 @@ import { useCodeListByIDsQuery, useCodeListByUserQuery, } from "lib/services/codeService"; -import type { CodeInfo } from "lib/types"; +import type { CodeInfo, HumanAddr } from "lib/types"; import { InstantiatePermission } from "lib/types"; interface CodeListData { @@ -17,7 +17,10 @@ interface CodeListData { allCodesCount: number; } -export const useCodeListData = (keyword?: string): CodeListData => { +export const useCodeListData = ( + keyword?: string, + permissionValue?: string +): CodeListData => { const { address } = useWallet(); const { getCodeLocalInfo, lastSavedCodes, lastSavedCodeIds, isCodeIdSaved } = useCodeStore(); @@ -60,7 +63,24 @@ export const useCodeListData = (keyword?: string): CodeListData => { const storedCodesCount = storedCodes.length; const [filteredSavedCodes, filteredStoredCodes] = useMemo(() => { - const filterFn = (code: CodeInfo) => { + const permissionFilter = (code: CodeInfo) => { + const isEveryBody = + code.instantiatePermission === InstantiatePermission.EVERYBODY; + const isNobody = + code.instantiatePermission === InstantiatePermission.NOBODY; + const isInclude = code.permissionAddresses.includes(address as HumanAddr); + + switch (permissionValue) { + case "with-proposal": + return (!isEveryBody && !isInclude) || isNobody; + case "without-proposal": + return isInclude || isEveryBody; + case "all": + default: + return true; + } + }; + const searchFilter = (code: CodeInfo) => { if (keyword === undefined) return true; const computedKeyword = keyword.trim(); @@ -72,8 +92,11 @@ export const useCodeListData = (keyword?: string): CodeListData => { ); }; - return [savedCodes.filter(filterFn), storedCodes.filter(filterFn)]; - }, [keyword, savedCodes, storedCodes]); + return [ + savedCodes.filter(permissionFilter).filter(searchFilter), + storedCodes.filter(permissionFilter).filter(searchFilter), + ]; + }, [savedCodes, storedCodes, address, permissionValue, keyword]); return { savedCodes: filteredSavedCodes, diff --git a/src/lib/pages/codes/index.tsx b/src/lib/pages/codes/index.tsx index bcd96bfec..9d12a3a83 100644 --- a/src/lib/pages/codes/index.tsx +++ b/src/lib/pages/codes/index.tsx @@ -5,12 +5,14 @@ import { TabPanels, TabPanel, Box, + Flex, } from "@chakra-ui/react"; import { observer } from "mobx-react-lite"; import type { ChangeEvent } from "react"; -import { useState } from "react"; +import { useForm } from "react-hook-form"; import { CustomTab } from "lib/components/CustomTab"; +import { FilterByPermission } from "lib/components/forms/FilterByPermission"; import InputWithIcon from "lib/components/InputWithIcon"; import CodesTable from "lib/pages/codes/components/CodesTable"; @@ -18,20 +20,28 @@ import SaveCodeButton from "./components/SaveCodeButton"; import UploadButton from "./components/UploadButton"; import { useCodeListData } from "./data"; +interface AllCodeState { + keyword: string; + permissionValue: string; +} const Codes = observer(() => { - const [keyword, setKeyword] = useState(""); + const { watch, setValue } = useForm({ + defaultValues: { + permissionValue: "all", + keyword: "", + }, + }); + const states = watch(); const { storedCodesCount, storedCodes: stored, savedCodesCount, savedCodes: saved, allCodesCount, - } = useCodeListData(keyword); - + } = useCodeListData(states.keyword, states.permissionValue); const handleFilterChange = (e: ChangeEvent) => { const inputValue = e.target.value; - - setKeyword(inputValue); + setValue("keyword", inputValue); }; return ( @@ -48,12 +58,20 @@ const Codes = observer(() => { My Stored Codes My Saved Codes - + + + + setValue("permissionValue", newVal) + } + /> + @@ -62,7 +80,7 @@ const Codes = observer(() => { tableName="My Stored Codes" codes={stored} action={} - isSearching={!!keyword} + isSearching={!!states.keyword} /> { codes={saved} action={} isRemovable - isSearching={!!keyword} + isSearching={!!states.keyword} /> @@ -79,7 +97,7 @@ const Codes = observer(() => { tableName="My Stored Codes" codes={stored} action={} - isSearching={!!keyword} + isSearching={!!states.keyword} /> @@ -88,7 +106,7 @@ const Codes = observer(() => { tableName="My Saved Codes" codes={saved} action={} - isSearching={!!keyword} + isSearching={!!states.keyword} isRemovable /> From 86319d3ad52770b34456fe42dffba6f9b6225819 Mon Sep 17 00:00:00 2001 From: Ramida J Date: Thu, 19 Jan 2023 17:32:54 +0700 Subject: [PATCH 4/7] fix(components): fix as comment --- src/lib/components/forms/SelectInput.tsx | 3 +-- src/lib/pages/all-codes/index.tsx | 11 ++++------- src/lib/pages/codes/index.tsx | 14 +++++++------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/lib/components/forms/SelectInput.tsx b/src/lib/components/forms/SelectInput.tsx index 0f2135ff2..f7dccb02c 100644 --- a/src/lib/components/forms/SelectInput.tsx +++ b/src/lib/components/forms/SelectInput.tsx @@ -79,8 +79,7 @@ export const SelectInput = ({ ref: optionRef, handler: () => isOpen && onClose(), }); - const selectedOption = - options.find((item) => item.label === selected) || undefined; + const selectedOption = options.find((item) => item.label === selected); useEffect(() => { if (inputRef.current) { diff --git a/src/lib/pages/all-codes/index.tsx b/src/lib/pages/all-codes/index.tsx index c68587580..e6766be17 100644 --- a/src/lib/pages/all-codes/index.tsx +++ b/src/lib/pages/all-codes/index.tsx @@ -22,11 +22,8 @@ const AllCodes = observer(() => { keyword: "", }, }); - const states = watch(); - const { allCodes, isLoading } = useAllCodesData( - states.keyword, - states.permissionValue - ); + const { keyword, permissionValue } = watch(); + const { allCodes, isLoading } = useAllCodesData(keyword, permissionValue); const handleFilterChange = (e: ChangeEvent) => { const inputValue = e.target.value; @@ -42,7 +39,7 @@ const AllCodes = observer(() => { @@ -61,7 +58,7 @@ const AllCodes = observer(() => { type="all" tableName="All Codes" codes={allCodes} - isSearching={!!states.keyword} + isSearching={!!keyword} /> )} diff --git a/src/lib/pages/codes/index.tsx b/src/lib/pages/codes/index.tsx index 9d12a3a83..2b80a5694 100644 --- a/src/lib/pages/codes/index.tsx +++ b/src/lib/pages/codes/index.tsx @@ -31,14 +31,14 @@ const Codes = observer(() => { keyword: "", }, }); - const states = watch(); + const { keyword, permissionValue } = watch(); const { storedCodesCount, storedCodes: stored, savedCodesCount, savedCodes: saved, allCodesCount, - } = useCodeListData(states.keyword, states.permissionValue); + } = useCodeListData(keyword, permissionValue); const handleFilterChange = (e: ChangeEvent) => { const inputValue = e.target.value; setValue("keyword", inputValue); @@ -61,7 +61,7 @@ const Codes = observer(() => { @@ -80,7 +80,7 @@ const Codes = observer(() => { tableName="My Stored Codes" codes={stored} action={} - isSearching={!!states.keyword} + isSearching={!!keyword} /> { codes={saved} action={} isRemovable - isSearching={!!states.keyword} + isSearching={!!keyword} /> @@ -97,7 +97,7 @@ const Codes = observer(() => { tableName="My Stored Codes" codes={stored} action={} - isSearching={!!states.keyword} + isSearching={!!keyword} /> @@ -106,7 +106,7 @@ const Codes = observer(() => { tableName="My Saved Codes" codes={saved} action={} - isSearching={!!states.keyword} + isSearching={!!keyword} isRemovable /> From aeea5d44e6a3b291c566dac8e93a708af28abe57 Mon Sep 17 00:00:00 2001 From: Ramida J Date: Thu, 26 Jan 2023 15:46:06 +0700 Subject: [PATCH 5/7] fix(components): combine permission filter and search filter into app-fns --- src/lib/app-fns/filter/index.tsx | 45 ++++++++++++++++++++++++++++++++ src/lib/pages/all-codes/data.ts | 43 +++++------------------------- src/lib/pages/codes/data.ts | 36 ++++--------------------- 3 files changed, 57 insertions(+), 67 deletions(-) create mode 100644 src/lib/app-fns/filter/index.tsx diff --git a/src/lib/app-fns/filter/index.tsx b/src/lib/app-fns/filter/index.tsx new file mode 100644 index 000000000..52e6d8308 --- /dev/null +++ b/src/lib/app-fns/filter/index.tsx @@ -0,0 +1,45 @@ +import { useWallet } from "@cosmos-kit/react"; +import { useCallback } from "react"; + +import type { CodeInfo, HumanAddr } from "lib/types"; +import { InstantiatePermission } from "lib/types"; + +export const usePermissionFilter = (permissionValue?: string) => { + const { address } = useWallet(); + return useCallback( + (code: CodeInfo) => { + const isEveryBody = + code.instantiatePermission === InstantiatePermission.EVERYBODY; + const isNobody = + code.instantiatePermission === InstantiatePermission.NOBODY; + const isInclude = code.permissionAddresses.includes(address as HumanAddr); + + switch (permissionValue) { + case "with-proposal": + return (!isEveryBody && !isInclude) || isNobody; + case "without-proposal": + return isInclude || isEveryBody; + case "all": + default: + return true; + } + }, + [address, permissionValue] + ); +}; + +export const useSearchFilter = (keyword?: string) => { + return useCallback( + (code: CodeInfo) => { + if (keyword === undefined) return true; + const computedKeyword = keyword.trim(); + if (computedKeyword.length === 0) return true; + + return ( + code.id.toString().startsWith(computedKeyword) || + code.description?.toLowerCase().includes(computedKeyword.toLowerCase()) + ); + }, + [keyword] + ); +}; diff --git a/src/lib/pages/all-codes/data.ts b/src/lib/pages/all-codes/data.ts index d26f658cd..574dc86ea 100644 --- a/src/lib/pages/all-codes/data.ts +++ b/src/lib/pages/all-codes/data.ts @@ -1,10 +1,9 @@ -import { useWallet } from "@cosmos-kit/react"; import { useMemo } from "react"; +import { usePermissionFilter, useSearchFilter } from "lib/app-fns/filter"; import { useCodeStore } from "lib/hooks"; import { useCodeListQuery } from "lib/services/codeService"; -import type { CodeInfo, HumanAddr } from "lib/types"; -import { InstantiatePermission } from "lib/types"; +import type { CodeInfo } from "lib/types"; interface AllCodesData { allCodes: CodeInfo[]; @@ -12,8 +11,8 @@ interface AllCodesData { } export const useAllCodesData = ( - keyword: string, - permissionValue: string + permissionValue: string, + keyword?: string ): AllCodesData => { const { getCodeLocalInfo, isCodeIdSaved } = useCodeStore(); const { data: rawAllCodes = [], isLoading } = useCodeListQuery(); @@ -23,40 +22,12 @@ export const useAllCodesData = ( description: getCodeLocalInfo(code.id)?.description, isSaved: isCodeIdSaved(code.id), })); - const { address } = useWallet(); - + const permissionFilter = usePermissionFilter(permissionValue); + const searchFilter = useSearchFilter(keyword); return useMemo(() => { - const permissionFilter = (code: CodeInfo) => { - const isEveryBody = - code.instantiatePermission === InstantiatePermission.EVERYBODY; - const isNobody = - code.instantiatePermission === InstantiatePermission.NOBODY; - const isInclude = code.permissionAddresses.includes(address as HumanAddr); - - switch (permissionValue) { - case "with-proposal": - return (!isEveryBody && !isInclude) || isNobody; - case "without-proposal": - return isInclude || isEveryBody; - case "all": - default: - return true; - } - }; - - const searchFilter = (code: CodeInfo) => { - const computedKeyword = keyword.trim(); - if (computedKeyword.length === 0) return true; - - return ( - code.id.toString().startsWith(computedKeyword) || - code.description?.toLowerCase().includes(computedKeyword.toLowerCase()) - ); - }; - return { allCodes: allCodes.filter(permissionFilter).filter(searchFilter), isLoading, }; - }, [keyword, allCodes, isLoading, permissionValue, address]); + }, [allCodes, permissionFilter, searchFilter, isLoading]); }; diff --git a/src/lib/pages/codes/data.ts b/src/lib/pages/codes/data.ts index 985bf52c7..385612171 100644 --- a/src/lib/pages/codes/data.ts +++ b/src/lib/pages/codes/data.ts @@ -1,12 +1,13 @@ import { useWallet } from "@cosmos-kit/react"; import { useMemo } from "react"; +import { usePermissionFilter, useSearchFilter } from "lib/app-fns/filter"; import { useUserKey, useCodeStore } from "lib/hooks"; import { useCodeListByIDsQuery, useCodeListByUserQuery, } from "lib/services/codeService"; -import type { CodeInfo, HumanAddr } from "lib/types"; +import type { CodeInfo } from "lib/types"; import { InstantiatePermission } from "lib/types"; interface CodeListData { @@ -62,41 +63,14 @@ export const useCodeListData = ( const storedCodesCount = storedCodes.length; + const permissionFilter = usePermissionFilter(permissionValue); + const searchFilter = useSearchFilter(keyword); const [filteredSavedCodes, filteredStoredCodes] = useMemo(() => { - const permissionFilter = (code: CodeInfo) => { - const isEveryBody = - code.instantiatePermission === InstantiatePermission.EVERYBODY; - const isNobody = - code.instantiatePermission === InstantiatePermission.NOBODY; - const isInclude = code.permissionAddresses.includes(address as HumanAddr); - - switch (permissionValue) { - case "with-proposal": - return (!isEveryBody && !isInclude) || isNobody; - case "without-proposal": - return isInclude || isEveryBody; - case "all": - default: - return true; - } - }; - const searchFilter = (code: CodeInfo) => { - if (keyword === undefined) return true; - - const computedKeyword = keyword.trim(); - if (computedKeyword.length === 0) return true; - - return ( - code.id.toString().startsWith(computedKeyword) || - code.description?.toLowerCase().includes(computedKeyword.toLowerCase()) - ); - }; - return [ savedCodes.filter(permissionFilter).filter(searchFilter), storedCodes.filter(permissionFilter).filter(searchFilter), ]; - }, [savedCodes, storedCodes, address, permissionValue, keyword]); + }, [savedCodes, permissionFilter, searchFilter, storedCodes]); return { savedCodes: filteredSavedCodes, From ef5837f75331875b2684f8a656e4b645dff9f7d0 Mon Sep 17 00:00:00 2001 From: Ramida J Date: Fri, 27 Jan 2023 12:07:02 +0700 Subject: [PATCH 6/7] fix(components): fix as comment --- src/lib/pages/all-codes/index.tsx | 9 +++------ src/lib/pages/codes/index.tsx | 8 +++----- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/lib/pages/all-codes/index.tsx b/src/lib/pages/all-codes/index.tsx index e6766be17..a225d249d 100644 --- a/src/lib/pages/all-codes/index.tsx +++ b/src/lib/pages/all-codes/index.tsx @@ -25,11 +25,6 @@ const AllCodes = observer(() => { const { keyword, permissionValue } = watch(); const { allCodes, isLoading } = useAllCodesData(keyword, permissionValue); - const handleFilterChange = (e: ChangeEvent) => { - const inputValue = e.target.value; - setValue("keyword", inputValue); - }; - return ( @@ -40,7 +35,9 @@ const AllCodes = observer(() => { ) => + setValue("keyword", e.target.value) + } size="lg" /> { savedCodes: saved, allCodesCount, } = useCodeListData(keyword, permissionValue); - const handleFilterChange = (e: ChangeEvent) => { - const inputValue = e.target.value; - setValue("keyword", inputValue); - }; return ( @@ -62,7 +58,9 @@ const Codes = observer(() => { ) => + setValue("keyword", e.target.value) + } size="lg" /> Date: Thu, 2 Feb 2023 14:27:11 +0700 Subject: [PATCH 7/7] refactor: improve logic and typing --- src/lib/app-fns/filter/index.tsx | 45 ------------------ .../components/forms/FilterByPermission.tsx | 8 ++-- src/lib/components/forms/SelectInput.tsx | 12 ++--- src/lib/hooks/index.ts | 1 + src/lib/hooks/useCodeFilter.ts | 46 +++++++++++++++++++ src/lib/pages/all-codes/data.ts | 17 +++---- src/lib/pages/all-codes/index.tsx | 10 ++-- src/lib/pages/codes/data.ts | 21 +++++---- src/lib/pages/codes/index.tsx | 11 +++-- 9 files changed, 93 insertions(+), 78 deletions(-) delete mode 100644 src/lib/app-fns/filter/index.tsx create mode 100644 src/lib/hooks/useCodeFilter.ts diff --git a/src/lib/app-fns/filter/index.tsx b/src/lib/app-fns/filter/index.tsx deleted file mode 100644 index 52e6d8308..000000000 --- a/src/lib/app-fns/filter/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { useWallet } from "@cosmos-kit/react"; -import { useCallback } from "react"; - -import type { CodeInfo, HumanAddr } from "lib/types"; -import { InstantiatePermission } from "lib/types"; - -export const usePermissionFilter = (permissionValue?: string) => { - const { address } = useWallet(); - return useCallback( - (code: CodeInfo) => { - const isEveryBody = - code.instantiatePermission === InstantiatePermission.EVERYBODY; - const isNobody = - code.instantiatePermission === InstantiatePermission.NOBODY; - const isInclude = code.permissionAddresses.includes(address as HumanAddr); - - switch (permissionValue) { - case "with-proposal": - return (!isEveryBody && !isInclude) || isNobody; - case "without-proposal": - return isInclude || isEveryBody; - case "all": - default: - return true; - } - }, - [address, permissionValue] - ); -}; - -export const useSearchFilter = (keyword?: string) => { - return useCallback( - (code: CodeInfo) => { - if (keyword === undefined) return true; - const computedKeyword = keyword.trim(); - if (computedKeyword.length === 0) return true; - - return ( - code.id.toString().startsWith(computedKeyword) || - code.description?.toLowerCase().includes(computedKeyword.toLowerCase()) - ); - }, - [keyword] - ); -}; diff --git a/src/lib/components/forms/FilterByPermission.tsx b/src/lib/components/forms/FilterByPermission.tsx index a7d6085c2..77dc0c0b2 100644 --- a/src/lib/components/forms/FilterByPermission.tsx +++ b/src/lib/components/forms/FilterByPermission.tsx @@ -2,18 +2,20 @@ import { Grid } from "@chakra-ui/react"; import type { IconType } from "react-icons"; import { MdCheck, MdHowToVote, MdPerson } from "react-icons/md"; +import type { PermissionFilterValue } from "lib/hooks"; + import { SelectInput } from "./SelectInput"; interface PermissionOption { label: string; - value: string; + value: PermissionFilterValue; disabled: boolean; icon?: IconType; iconColor: string; } interface FilterByPermissionProps { - setPermissionValue: (newVal: string) => void; + setPermissionValue: (newVal: PermissionFilterValue) => void; initialSelected: string; } @@ -47,7 +49,7 @@ export const FilterByPermission = ({ }: FilterByPermissionProps) => { return ( - formLabel="Filter by Instantiate Permission" options={options} onChange={setPermissionValue} diff --git a/src/lib/components/forms/SelectInput.tsx b/src/lib/components/forms/SelectInput.tsx index f7dccb02c..d3d44697f 100644 --- a/src/lib/components/forms/SelectInput.tsx +++ b/src/lib/components/forms/SelectInput.tsx @@ -20,16 +20,16 @@ import type { Option } from "lib/types"; const ITEM_HEIGHT = 56; -interface SelectInputProps { +interface SelectInputProps { formLabel?: string; options: { label: string; - value: string; + value: T; disabled: boolean; icon?: IconType; iconColor?: string; }[]; - onChange: (newVal: string) => void; + onChange: (newVal: T) => void; placeholder?: string; initialSelected: string; hasDivider?: boolean; @@ -60,19 +60,19 @@ const SelectItem = ({ children, onSelect, disabled }: SelectItemProps) => { ); }; -export const SelectInput = ({ +export const SelectInput = ({ formLabel, options, onChange, placeholder = "", initialSelected, hasDivider = false, -}: SelectInputProps) => { +}: SelectInputProps) => { const optionRef = useRef() as MutableRefObject; const { isOpen, onClose, onOpen } = useDisclosure(); const inputRef = useRef() as MutableRefObject; const [selected, setSelected] = useState( - () => options.find((asset) => asset.value === initialSelected)?.label ?? "" + () => options.find((item) => item.value === initialSelected)?.label ?? "" ); const [inputRefWidth, setInputRefWidth] = useState>(); useOutsideClick({ diff --git a/src/lib/hooks/index.ts b/src/lib/hooks/index.ts index 8810afd42..bc3f6ae0e 100644 --- a/src/lib/hooks/index.ts +++ b/src/lib/hooks/index.ts @@ -5,3 +5,4 @@ export * from "./useLCDEndpoint"; export * from "./useUserKey"; export * from "./useDummyWallet"; export * from "./useAddress"; +export * from "./useCodeFilter"; diff --git a/src/lib/hooks/useCodeFilter.ts b/src/lib/hooks/useCodeFilter.ts new file mode 100644 index 000000000..1714c3f35 --- /dev/null +++ b/src/lib/hooks/useCodeFilter.ts @@ -0,0 +1,46 @@ +import { useWallet } from "@cosmos-kit/react"; +import { useCallback } from "react"; + +import type { CodeInfo, HumanAddr } from "lib/types"; +import { InstantiatePermission } from "lib/types"; + +export type PermissionFilterValue = + | "all" + | "without-proposal" + | "with-proposal"; + +export const usePermissionFilter = (filterValue?: PermissionFilterValue) => { + const { address } = useWallet(); + return useCallback( + ({ instantiatePermission, permissionAddresses }: CodeInfo) => { + const isAllowed = + permissionAddresses.includes(address as HumanAddr) || + instantiatePermission === InstantiatePermission.EVERYBODY; + + switch (filterValue) { + case "with-proposal": + return !isAllowed; + case "without-proposal": + return isAllowed; + case "all": + default: + return true; + } + }, + [address, filterValue] + ); +}; + +export const useSearchFilter = (keyword = "") => { + return useCallback( + (code: CodeInfo) => { + const computedKeyword = keyword.trim(); + if (!computedKeyword.length) return true; + return ( + code.id.toString().startsWith(computedKeyword) || + code.description?.toLowerCase().includes(computedKeyword.toLowerCase()) + ); + }, + [keyword] + ); +}; diff --git a/src/lib/pages/all-codes/data.ts b/src/lib/pages/all-codes/data.ts index 574dc86ea..95e918430 100644 --- a/src/lib/pages/all-codes/data.ts +++ b/src/lib/pages/all-codes/data.ts @@ -1,7 +1,7 @@ import { useMemo } from "react"; -import { usePermissionFilter, useSearchFilter } from "lib/app-fns/filter"; -import { useCodeStore } from "lib/hooks"; +import type { PermissionFilterValue } from "lib/hooks"; +import { useCodeStore, usePermissionFilter, useSearchFilter } from "lib/hooks"; import { useCodeListQuery } from "lib/services/codeService"; import type { CodeInfo } from "lib/types"; @@ -11,23 +11,24 @@ interface AllCodesData { } export const useAllCodesData = ( - permissionValue: string, - keyword?: string + keyword: string, + permissionValue: PermissionFilterValue ): AllCodesData => { const { getCodeLocalInfo, isCodeIdSaved } = useCodeStore(); const { data: rawAllCodes = [], isLoading } = useCodeListQuery(); + const permissionFilterFn = usePermissionFilter(permissionValue); + const searchFilterFn = useSearchFilter(keyword); const allCodes = rawAllCodes.map((code) => ({ ...code, description: getCodeLocalInfo(code.id)?.description, isSaved: isCodeIdSaved(code.id), })); - const permissionFilter = usePermissionFilter(permissionValue); - const searchFilter = useSearchFilter(keyword); + return useMemo(() => { return { - allCodes: allCodes.filter(permissionFilter).filter(searchFilter), + allCodes: allCodes.filter(permissionFilterFn).filter(searchFilterFn), isLoading, }; - }, [allCodes, permissionFilter, searchFilter, isLoading]); + }, [allCodes, isLoading, permissionFilterFn, searchFilterFn]); }; diff --git a/src/lib/pages/all-codes/index.tsx b/src/lib/pages/all-codes/index.tsx index a225d249d..c254d7c86 100644 --- a/src/lib/pages/all-codes/index.tsx +++ b/src/lib/pages/all-codes/index.tsx @@ -6,13 +6,14 @@ import { useForm } from "react-hook-form"; import { FilterByPermission } from "lib/components/forms/FilterByPermission"; import InputWithIcon from "lib/components/InputWithIcon"; import { Loading } from "lib/components/Loading"; +import type { PermissionFilterValue } from "lib/hooks"; import CodesTable from "lib/pages/codes/components/CodesTable"; import { useAllCodesData } from "./data"; interface AllCodeState { keyword: string; - permissionValue: string; + permissionValue: PermissionFilterValue; } const AllCodes = observer(() => { @@ -42,9 +43,10 @@ const AllCodes = observer(() => { /> - setValue("permissionValue", newVal) - } + setPermissionValue={(newVal: PermissionFilterValue) => { + if (newVal === permissionValue) return; + setValue("permissionValue", newVal); + }} /> diff --git a/src/lib/pages/codes/data.ts b/src/lib/pages/codes/data.ts index 385612171..22cfded43 100644 --- a/src/lib/pages/codes/data.ts +++ b/src/lib/pages/codes/data.ts @@ -1,8 +1,13 @@ import { useWallet } from "@cosmos-kit/react"; import { useMemo } from "react"; -import { usePermissionFilter, useSearchFilter } from "lib/app-fns/filter"; -import { useUserKey, useCodeStore } from "lib/hooks"; +import type { PermissionFilterValue } from "lib/hooks"; +import { + useUserKey, + useCodeStore, + usePermissionFilter, + useSearchFilter, +} from "lib/hooks"; import { useCodeListByIDsQuery, useCodeListByUserQuery, @@ -20,7 +25,7 @@ interface CodeListData { export const useCodeListData = ( keyword?: string, - permissionValue?: string + permissionValue?: PermissionFilterValue ): CodeListData => { const { address } = useWallet(); const { getCodeLocalInfo, lastSavedCodes, lastSavedCodeIds, isCodeIdSaved } = @@ -29,6 +34,8 @@ export const useCodeListData = ( const { data: rawStoredCodes = [] } = useCodeListByUserQuery(address); const userKey = useUserKey(); + const permissionFilterFn = usePermissionFilter(permissionValue); + const searchFilterFn = useSearchFilter(keyword); const savedCodeIds = lastSavedCodeIds(userKey); const { data: querySavedCodeInfos = [] } = @@ -63,14 +70,12 @@ export const useCodeListData = ( const storedCodesCount = storedCodes.length; - const permissionFilter = usePermissionFilter(permissionValue); - const searchFilter = useSearchFilter(keyword); const [filteredSavedCodes, filteredStoredCodes] = useMemo(() => { return [ - savedCodes.filter(permissionFilter).filter(searchFilter), - storedCodes.filter(permissionFilter).filter(searchFilter), + savedCodes.filter(permissionFilterFn).filter(searchFilterFn), + storedCodes.filter(permissionFilterFn).filter(searchFilterFn), ]; - }, [savedCodes, permissionFilter, searchFilter, storedCodes]); + }, [savedCodes, storedCodes, permissionFilterFn, searchFilterFn]); return { savedCodes: filteredSavedCodes, diff --git a/src/lib/pages/codes/index.tsx b/src/lib/pages/codes/index.tsx index 0c35bedb7..5f66a9bd0 100644 --- a/src/lib/pages/codes/index.tsx +++ b/src/lib/pages/codes/index.tsx @@ -14,6 +14,7 @@ import { useForm } from "react-hook-form"; import { CustomTab } from "lib/components/CustomTab"; import { FilterByPermission } from "lib/components/forms/FilterByPermission"; import InputWithIcon from "lib/components/InputWithIcon"; +import type { PermissionFilterValue } from "lib/hooks"; import CodesTable from "lib/pages/codes/components/CodesTable"; import SaveCodeButton from "./components/SaveCodeButton"; @@ -22,8 +23,9 @@ import { useCodeListData } from "./data"; interface AllCodeState { keyword: string; - permissionValue: string; + permissionValue: PermissionFilterValue; } + const Codes = observer(() => { const { watch, setValue } = useForm({ defaultValues: { @@ -65,9 +67,10 @@ const Codes = observer(() => { /> - setValue("permissionValue", newVal) - } + setPermissionValue={(newVal: PermissionFilterValue) => { + if (newVal === permissionValue) return; + setValue("permissionValue", newVal); + }} />