From a9fc4f9b5909fddb476bf48fa4278fb4d67bb9a6 Mon Sep 17 00:00:00 2001 From: aowheel Date: Tue, 24 Dec 2024 08:51:46 +0900 Subject: [PATCH 1/4] =?UTF-8?q?/{treeId}=E3=80=81/{treeId}/member=E3=81=AE?= =?UTF-8?q?=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkgs/frontend/app/components/BasicRole.tsx | 57 +++++++++-- pkgs/frontend/app/routes/$treeId.tsx | 77 +++++++++++---- pkgs/frontend/app/routes/$treeId_.member.tsx | 99 ++++++++++++++++++++ pkgs/frontend/hooks/useHats.ts | 3 +- 4 files changed, 213 insertions(+), 23 deletions(-) create mode 100644 pkgs/frontend/app/routes/$treeId_.member.tsx diff --git a/pkgs/frontend/app/components/BasicRole.tsx b/pkgs/frontend/app/components/BasicRole.tsx index 7dfd4c9..dcd51ce 100644 --- a/pkgs/frontend/app/components/BasicRole.tsx +++ b/pkgs/frontend/app/components/BasicRole.tsx @@ -1,19 +1,64 @@ -import { Box, Image, Text } from "@chakra-ui/react"; +import { HStack, Image, Text, VStack } from "@chakra-ui/react"; +import { useNavigate } from "@remix-run/react"; import { FC } from "react"; import { HatsDetailSchama } from "types/hats"; +import { BasicButton } from "./BasicButton"; interface BasicRoleProps { detail?: HatsDetailSchama; imageUri?: string; } -export const BasicRole: FC = (params?) => { +export const VerticalRole: FC = (params?) => { const { detail, imageUri } = params!; return ( - - {detail?.data.name} - - + + + {detail?.data?.name} + + ); +}; + +export const HorizontalRole: FC = (params?) => { + const { detail, imageUri } = params!; + + return ( + + + {detail?.data?.name} + + ); +}; + +interface RoleActionsProps { + detail?: HatsDetailSchama; + imageUri?: string; + address?: `0x${string}`; + treeId?: string; + hatId?: `0x${string}`; +} + +export const RoleActions: FC = (params) => { + const { address, detail, imageUri, treeId, hatId } = params; + + const navigate = useNavigate(); + + const navigateToDetail = () => navigate(`/${treeId}/${hatId}/${address}`); + + const navigateToAssistCredit = () => + navigate(`/${treeId}/${hatId}/${address}/assistcredit/send`); + + return ( + + + + {detail?.data.name} + See Detail + + Transfer Assist Credit + + + ); }; diff --git a/pkgs/frontend/app/routes/$treeId.tsx b/pkgs/frontend/app/routes/$treeId.tsx index 5fbfdbe..ccd6731 100644 --- a/pkgs/frontend/app/routes/$treeId.tsx +++ b/pkgs/frontend/app/routes/$treeId.tsx @@ -1,28 +1,73 @@ -import { Box } from "@chakra-ui/react"; -import { useParams } from "@remix-run/react"; +import { + AspectRatio, + Heading, + SimpleGrid, + Text, + VStack, +} from "@chakra-ui/react"; +import { useNavigate, useParams } from "@remix-run/react"; import { useTreeInfo } from "hooks/useHats"; +import { useActiveWallet } from "hooks/useWallet"; import { FC } from "react"; -import { BasicRole } from "~/components/BasicRole"; +import { FaPlus } from "react-icons/fa6"; +import { VerticalRole, RoleActions } from "~/components/BasicRole"; +import { CommonButton } from "~/components/common/CommonButton"; import { HatsListItemParser } from "~/components/common/HatsListItemParser"; const WorkspaceTop: FC = () => { - const { treeId } = useParams(); + const { wallet } = useActiveWallet(); + const address = wallet?.account?.address; + const { treeId } = useParams(); const tree = useTreeInfo(Number(treeId)); + const topHatId = tree?.hats?.find((h) => h.levelAtLocalTree === 0)?.id; + + const navigate = useNavigate(); + const navigateToNewRole = () => navigate(`/workspaces/${topHatId}/roles/new`); return ( - - {tree?.hats - ?.filter((h) => Number(h.levelAtLocalTree) >= 2) - .map((h) => ( - ) as any} - /> - ))} - + <> + {/* My roles */} + My Roles + + {tree?.hats + ?.filter((h) => Number(h.levelAtLocalTree) >= 2) + .filter((h) => h.wearers?.map((w) => w.id === address)) + .map((h) => ( + + + + ))} + + + {/* All roles */} + All Roles + + {tree?.hats + ?.filter((h) => Number(h.levelAtLocalTree) >= 2) + .map((h) => ( + + + + ))} + + + + + + + Add role + + + ); }; diff --git a/pkgs/frontend/app/routes/$treeId_.member.tsx b/pkgs/frontend/app/routes/$treeId_.member.tsx new file mode 100644 index 0000000..6591aa9 --- /dev/null +++ b/pkgs/frontend/app/routes/$treeId_.member.tsx @@ -0,0 +1,99 @@ +import { Heading, HStack, Text, VStack } from "@chakra-ui/react"; +import { useParams } from "@remix-run/react"; +import { useNamesByAddresses } from "hooks/useENS"; +import { useTreeInfo } from "hooks/useHats"; +import { TextRecords } from "namestone-sdk"; +import { FC, useMemo } from "react"; +import { ipfs2https } from "utils/ipfs"; +import { HorizontalRole } from "~/components/BasicRole"; +import { HatsListItemParser } from "~/components/common/HatsListItemParser"; +import { UserIcon } from "~/components/icon/UserIcon"; + +const WorkspaceMember: FC = () => { + const { treeId } = useParams(); + const tree = useTreeInfo(Number(treeId)); + + // 重複のないようにwearerを取得し、持っているhatを取得 + const wearers = useMemo(() => { + if (!tree || !tree.hats) return []; + return tree.hats + .filter((h) => h.levelAtLocalTree && h.levelAtLocalTree >= 2) + .flatMap((h) => h.wearers) + .filter((w) => !!w) + .filter((w, i, self) => self.findIndex((s) => s.id === w.id) === i) + .map((w) => ({ + ...w, + hats: tree.hats?.filter( + (h) => + h.levelAtLocalTree && + h.levelAtLocalTree >= 2 && + h.wearers?.some(({ id }) => id === w.id) + ), + })); + }, [tree]); + + // namesのaddressは大文字も含むため比較の場合は小文字に + const { names } = useNamesByAddresses(wearers.map((w) => w.id)); + + const members = useMemo(() => { + const unresolvedMembers = wearers.filter( + ({ id }) => !names.flat().find((n) => n.address.toLowerCase() === id) + ); + + return [ + ...names.flat().map((n) => ({ + ...n, + wearer: wearers.find((w) => w.id === n.address.toLowerCase()), + })), + ...unresolvedMembers.map((m) => ({ + wearer: m, + name: "", + domain: "", + text_records: { + avatar: "", + } as TextRecords, + })), + ]; + }, [wearers, names]); + + return ( + <> + {/* Members */} + Members + + {members.map((m, i) => ( + + + + + {m.name + ? `${m.name} (${m.wearer?.id.slice(0, 6)}...${m.wearer?.id.slice(-4)})` + : m.wearer?.id} + + + {m.wearer?.hats?.map((h) => ( + + + + ))} + + + + ))} + + + {/* Contribution */} + Contribution + {/* 何らかの形でコントリビューションのランクを出したい */} + + ); +}; + +export default WorkspaceMember; diff --git a/pkgs/frontend/hooks/useHats.ts b/pkgs/frontend/hooks/useHats.ts index a409a32..710a8cb 100644 --- a/pkgs/frontend/hooks/useHats.ts +++ b/pkgs/frontend/hooks/useHats.ts @@ -39,6 +39,7 @@ export const useTreeInfo = (treeId: number) => { useEffect(() => { const fetch = async () => { if (!treeId) return; + const tree = await getTreeInfo({ treeId: treeId, }); @@ -49,7 +50,7 @@ export const useTreeInfo = (treeId: number) => { }; fetch(); - }, [treeId]); + }, [treeId, getTreeInfo]); return treeInfo; }; From d8f30c0740b3ef8c4c18c2125570cc66a7c4baac Mon Sep 17 00:00:00 2001 From: aowheel Date: Thu, 26 Dec 2024 13:17:40 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fraction=20token=E3=81=AE=E6=89=80=E6=8C=81?= =?UTF-8?q?=E8=80=85=E3=81=AE=E8=A1=A8=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkgs/frontend/app/routes/$treeId.tsx | 81 ++++---- pkgs/frontend/app/routes/$treeId_.member.tsx | 183 +++++++++++++++---- pkgs/frontend/hooks/useENS.ts | 14 +- pkgs/frontend/hooks/useFractionToken.ts | 161 ++++++++++++---- 4 files changed, 322 insertions(+), 117 deletions(-) diff --git a/pkgs/frontend/app/routes/$treeId.tsx b/pkgs/frontend/app/routes/$treeId.tsx index ccd6731..226e38e 100644 --- a/pkgs/frontend/app/routes/$treeId.tsx +++ b/pkgs/frontend/app/routes/$treeId.tsx @@ -1,5 +1,6 @@ import { AspectRatio, + Box, Heading, SimpleGrid, Text, @@ -16,7 +17,7 @@ import { HatsListItemParser } from "~/components/common/HatsListItemParser"; const WorkspaceTop: FC = () => { const { wallet } = useActiveWallet(); - const address = wallet?.account?.address; + const me = wallet?.account?.address; const { treeId } = useParams(); const tree = useTreeInfo(Number(treeId)); @@ -28,45 +29,49 @@ const WorkspaceTop: FC = () => { return ( <> {/* My roles */} - My Roles - - {tree?.hats - ?.filter((h) => Number(h.levelAtLocalTree) >= 2) - .filter((h) => h.wearers?.map((w) => w.id === address)) - .map((h) => ( - - - - ))} - - - {/* All roles */} - All Roles - - {tree?.hats - ?.filter((h) => Number(h.levelAtLocalTree) >= 2) - .map((h) => ( - - - - ))} + + My Roles - - - - - - Add role + {tree?.hats + ?.filter((h) => Number(h.levelAtLocalTree) >= 2) + .filter((h) => h.wearers?.some((w) => w.id === me?.toLowerCase())) + .map((h) => ( + + + + ))} - + + + {/* All roles */} + + All Roles + + {tree?.hats + ?.filter((h) => Number(h.levelAtLocalTree) >= 2) + .map((h) => ( + + + + ))} + + + + + + + Add role + + + ); }; diff --git a/pkgs/frontend/app/routes/$treeId_.member.tsx b/pkgs/frontend/app/routes/$treeId_.member.tsx index 6591aa9..d1c8ab5 100644 --- a/pkgs/frontend/app/routes/$treeId_.member.tsx +++ b/pkgs/frontend/app/routes/$treeId_.member.tsx @@ -1,6 +1,7 @@ -import { Heading, HStack, Text, VStack } from "@chakra-ui/react"; +import { Box, Heading, HStack, Text, VStack } from "@chakra-ui/react"; import { useParams } from "@remix-run/react"; import { useNamesByAddresses } from "hooks/useENS"; +import { useTokenRecipients } from "hooks/useFractionToken"; import { useTreeInfo } from "hooks/useHats"; import { TextRecords } from "namestone-sdk"; import { FC, useMemo } from "react"; @@ -13,7 +14,7 @@ const WorkspaceMember: FC = () => { const { treeId } = useParams(); const tree = useTreeInfo(Number(treeId)); - // 重複のないようにwearerを取得し、持っているhatを取得 + // 重複のないwearersを取得し、wearerの持っているhatの情報を付与 const wearers = useMemo(() => { if (!tree || !tree.hats) return []; return tree.hats @@ -22,8 +23,8 @@ const WorkspaceMember: FC = () => { .filter((w) => !!w) .filter((w, i, self) => self.findIndex((s) => s.id === w.id) === i) .map((w) => ({ - ...w, - hats: tree.hats?.filter( + id: w.id, + hats: tree.hats!.filter( (h) => h.levelAtLocalTree && h.levelAtLocalTree >= 2 && @@ -32,66 +33,168 @@ const WorkspaceMember: FC = () => { })); }, [tree]); - // namesのaddressは大文字も含むため比較の場合は小文字に - const { names } = useNamesByAddresses(wearers.map((w) => w.id)); + // wearersのidをメモ化 + const wearersIds = useMemo(() => wearers.map(({ id }) => id), [wearers]); + // namestone + const { names: wearersNames } = useNamesByAddresses(wearersIds); + // Members const members = useMemo(() => { const unresolvedMembers = wearers.filter( - ({ id }) => !names.flat().find((n) => n.address.toLowerCase() === id) + ({ id }) => + !wearersNames.flat().find((n) => n.address.toLowerCase() === id) ); return [ - ...names.flat().map((n) => ({ + ...wearersNames.flat().map((n) => ({ ...n, wearer: wearers.find((w) => w.id === n.address.toLowerCase()), })), ...unresolvedMembers.map((m) => ({ wearer: m, name: "", + address: m.id, domain: "", text_records: { avatar: "", } as TextRecords, })), ]; - }, [wearers, names]); + }, [wearers, wearersNames]); + + // hatIdとwearerのペアを取得 + const params = useMemo(() => { + if (!tree || !tree.hats) return []; + return tree.hats + .filter((h) => h.levelAtLocalTree && h.levelAtLocalTree >= 2) + .flatMap( + ({ id, wearers }) => + wearers?.map((w) => ({ hatId: id, wearer: w.id })) || [] + ); + }, [tree]); + + const recipients = useTokenRecipients(params); + + const assistants = useMemo(() => { + if (!tree || !tree.hats) return []; + return recipients.map(({ assistant, hatIds }) => ({ + id: assistant, + hats: tree.hats!.filter( + (h) => + h.levelAtLocalTree && h.levelAtLocalTree >= 2 && hatIds.includes(h.id) + ), + })); + }, [tree, recipients]); + + // assistantsのidをメモ化 + const assistantsIds = useMemo( + () => assistants.map(({ id }) => id), + [assistants] + ); + // namestone + const { names: assistantsNames } = useNamesByAddresses(assistantsIds); + + // AssistantMembers + const assistantMembers = useMemo(() => { + const unresolvedMembers = assistants.filter( + ({ id }) => + !assistantsNames + .flat() + .find((n) => n.address.toLowerCase() === id.toLowerCase()) + ); + + return [ + ...assistantsNames.flat().map((n) => ({ + ...n, + assistant: assistants.find( + (a) => a.id.toLowerCase() === n.address.toLowerCase() + ), + })), + ...unresolvedMembers.map((m) => ({ + assistant: m, + name: "", + address: m.id, + domain: "", + text_records: { + avatar: "", + } as TextRecords, + })), + ]; + }, [assistants, assistantsNames]); return ( <> {/* Members */} - Members - - {members.map((m, i) => ( - - - - - {m.name - ? `${m.name} (${m.wearer?.id.slice(0, 6)}...${m.wearer?.id.slice(-4)})` - : m.wearer?.id} - - - {m.wearer?.hats?.map((h) => ( - - - - ))} - - - - ))} - + + Members + + {members.map((m, i) => ( + + + + + {m.name + ? `${m.name} (${m.address.slice(0, 6)}...${m.address.slice(-4)})` + : m.address} + + + {m.wearer?.hats?.map((h) => ( + + + + ))} + + + + ))} + + + + {/* AssistantMembers */} + + Assistant Members + + {assistantMembers.map((m, i) => ( + + + + + {m.name + ? `${m.name} (${m.address.slice(0, 6)}...${m.address.slice(-4)})` + : m.address} + + + {m.assistant?.hats?.map((h) => ( + + + + ))} + + + + ))} + + {/* Contribution */} - Contribution - {/* 何らかの形でコントリビューションのランクを出したい */} + + Contribution + {/* 何らかの形でコントリビューションのランクを出したい */} + ); }; diff --git a/pkgs/frontend/hooks/useENS.ts b/pkgs/frontend/hooks/useENS.ts index 7497294..f4122a7 100644 --- a/pkgs/frontend/hooks/useENS.ts +++ b/pkgs/frontend/hooks/useENS.ts @@ -22,12 +22,7 @@ export const useActiveWalletIdentity = () => { export const useNamesByAddresses = (addresses?: string[]) => { const [names, setNames] = useState([]); - useEffect(() => { - if (!addresses) return; - fetchNames(addresses); - }, [addresses]); - - const fetchNames = async (addresses: string[]) => { + const fetchNames = useCallback(async (addresses: string[]) => { try { const { data } = await axios.get("/api/namestone/resolve-names", { params: { addresses: addresses.join(",") }, @@ -37,7 +32,12 @@ export const useNamesByAddresses = (addresses?: string[]) => { } catch (error) { console.error(error); } - }; + }, []); + + useEffect(() => { + if (!addresses) return; + fetchNames(addresses); + }, [addresses, fetchNames]); return { names, fetchNames }; }; diff --git a/pkgs/frontend/hooks/useFractionToken.ts b/pkgs/frontend/hooks/useFractionToken.ts index 6b9e7ca..b018cb3 100644 --- a/pkgs/frontend/hooks/useFractionToken.ts +++ b/pkgs/frontend/hooks/useFractionToken.ts @@ -8,6 +8,98 @@ import { import { useActiveWallet } from "./useWallet"; import { publicClient } from "./useViem"; +export const useTokenRecipients = ( + params: { + wearer: Address; + hatId: Address; + }[] +) => { + const [recipients, setRecipients] = useState< + { + assistant: Address; + hatIds: Address[]; + }[] + >([]); + + const { getTokenId, getTokenRecipients } = useFractionToken(); + + useEffect(() => { + const fetch = async () => { + try { + const fetchedRecipients = await Promise.all( + params.map(async ({ hatId, wearer }) => { + const tokenId = await getTokenId({ + hatId: BigInt(hatId), + account: wearer, + }); + if (!tokenId) return; + return { + hatId, + assistants: (await getTokenRecipients({ tokenId })) || [], + }; + }) + ); + + const formattedRecipients = fetchedRecipients + .filter((r) => !!r) + .reduce( + (acc, r) => { + r.assistants.forEach((a) => { + const existing = acc.find((item) => item.assistant === a); + if (existing) { + existing.hatIds.push(r.hatId); + } else { + acc.push({ assistant: a, hatIds: [r.hatId] }); + } + }); + return acc; + }, + [] as { + assistant: Address; + hatIds: Address[]; + }[] + ); + + setRecipients(formattedRecipients); + } catch (error) { + console.error("error occured when fetching tokenRecipients:", error); + } + }; + + fetch(); + }, [params, getTokenId, getTokenRecipients]); + + return recipients; +}; + +export const useBalanceOfFractionToken = ( + holder: Address, + address: Address, + hatId: bigint +) => { + const [balance, setBalance] = useState(); + + useEffect(() => { + const fetch = async () => { + if (!holder || !address || !hatId) return; + try { + const balance = await publicClient.readContract({ + ...fractionTokenBaseConfig, + functionName: "balanceOf", + args: [holder, address, hatId], + }); + setBalance(balance); + } catch (error) { + setBalance(0n); + } + }; + + fetch(); + }, [holder, hatId, address]); + + return balance; +}; + /** * FractionToken 向けの React Hooks * @returns @@ -17,10 +109,37 @@ export const useFractionToken = () => { const [isLoading, setIsLoading] = useState(false); + /** + * tokenRecipientsを取得するコールバック関数 + * @param tokenId + */ + const getTokenRecipients = useCallback( + async (params: { tokenId: bigint }) => { + if (!wallet) return; + + setIsLoading(true); + + try { + const res = await publicClient.readContract({ + ...fractionTokenBaseConfig, + functionName: "getTokenRecipients", + args: [params.tokenId], + }); + + return res; + } catch (error) { + console.error("error occured when fetching tokenRecipients:", error); + } finally { + setIsLoading(false); + } + }, + [wallet] + ); + /** * tokenIdを取得するコールバック関数 * @param hatId - * @pamra account address + * @param account address */ const getTokenId = useCallback( async (params: { hatId: bigint; account: Address }) => { @@ -48,7 +167,7 @@ export const useFractionToken = () => { /** * FractionTokenを発行するコールバック関数 * @param hatId - * @pamra account address + * @param account address */ const mintFractionToken = useCallback( async (params: { hatId: bigint; account: Address }) => { @@ -99,7 +218,7 @@ export const useFractionToken = () => { /** * FractionTokenを送信するコールバック関数 * @param hatId - * @pamra account address + * @param account address * @param to recipient address * @param amount amount of token */ @@ -162,33 +281,11 @@ export const useFractionToken = () => { [wallet] ); - return { isLoading, getTokenId, mintFractionToken, sendFractionToken }; -}; - -export const useBalanceOfFractionToken = ( - holder: Address, - address: Address, - hatId: bigint -) => { - const [balance, setBalance] = useState(); - - useEffect(() => { - const fetch = async () => { - if (!holder || !address || !hatId) return; - try { - const balance = await publicClient.readContract({ - ...fractionTokenBaseConfig, - functionName: "balanceOf", - args: [holder, address, hatId], - }); - setBalance(balance); - } catch (error) { - setBalance(0n); - } - }; - - fetch(); - }, [holder, hatId, address]); - - return balance; + return { + isLoading, + getTokenRecipients, + getTokenId, + mintFractionToken, + sendFractionToken, + }; }; From 09a2f603aa02e97c8c1706ae5615309f145c6427 Mon Sep 17 00:00:00 2001 From: aowheel Date: Thu, 26 Dec 2024 14:04:45 +0900 Subject: [PATCH 3/4] =?UTF-8?q?role=E8=A9=B3=E7=B4=B0=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E3=81=B8=E3=81=AE=E5=B0=8E=E7=B7=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkgs/frontend/app/components/BasicRole.tsx | 15 ++++++++------- pkgs/frontend/app/routes/$treeId.tsx | 18 +++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/pkgs/frontend/app/components/BasicRole.tsx b/pkgs/frontend/app/components/BasicRole.tsx index dcd51ce..c77ea58 100644 --- a/pkgs/frontend/app/components/BasicRole.tsx +++ b/pkgs/frontend/app/components/BasicRole.tsx @@ -44,18 +44,19 @@ export const RoleActions: FC = (params) => { const navigate = useNavigate(); - const navigateToDetail = () => navigate(`/${treeId}/${hatId}/${address}`); - - const navigateToAssistCredit = () => - navigate(`/${treeId}/${hatId}/${address}/assistcredit/send`); - return ( {detail?.data.name} - See Detail - + navigate(`/${treeId}/${hatId}/${address}`)}> + See Detail + + + navigate(`/${treeId}/${hatId}/${address}/assistcredit/send`) + } + > Transfer Assist Credit diff --git a/pkgs/frontend/app/routes/$treeId.tsx b/pkgs/frontend/app/routes/$treeId.tsx index 226e38e..928ea32 100644 --- a/pkgs/frontend/app/routes/$treeId.tsx +++ b/pkgs/frontend/app/routes/$treeId.tsx @@ -24,7 +24,6 @@ const WorkspaceTop: FC = () => { const topHatId = tree?.hats?.find((h) => h.levelAtLocalTree === 0)?.id; const navigate = useNavigate(); - const navigateToNewRole = () => navigate(`/workspaces/${topHatId}/roles/new`); return ( <> @@ -54,17 +53,18 @@ const WorkspaceTop: FC = () => { {tree?.hats ?.filter((h) => Number(h.levelAtLocalTree) >= 2) .map((h) => ( - - - + navigate(`/${treeId}/${h.id}`)}> + + + + ))} - + navigate(`/workspaces/${topHatId}/roles/new`)} + > From 3bf5971091e03494bb7fdc7be419247dc8f27839 Mon Sep 17 00:00:00 2001 From: yu23ki14 Date: Mon, 30 Dec 2024 22:24:47 +0900 Subject: [PATCH 4/4] modify components --- pkgs/frontend/app/components/BasicRole.tsx | 65 ------------------- .../frontend/app/components/icon/RoleIcon.tsx | 14 ++-- pkgs/frontend/app/components/roles/MyRole.tsx | 41 ++++++++++++ .../frontend/app/components/roles/RoleTag.tsx | 25 +++++++ pkgs/frontend/app/components/roles/VRole.tsx | 23 +++++++ pkgs/frontend/app/routes/$treeId.tsx | 27 ++++---- pkgs/frontend/app/routes/$treeId_.member.tsx | 26 ++++---- 7 files changed, 127 insertions(+), 94 deletions(-) delete mode 100644 pkgs/frontend/app/components/BasicRole.tsx create mode 100644 pkgs/frontend/app/components/roles/MyRole.tsx create mode 100644 pkgs/frontend/app/components/roles/RoleTag.tsx create mode 100644 pkgs/frontend/app/components/roles/VRole.tsx diff --git a/pkgs/frontend/app/components/BasicRole.tsx b/pkgs/frontend/app/components/BasicRole.tsx deleted file mode 100644 index c77ea58..0000000 --- a/pkgs/frontend/app/components/BasicRole.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { HStack, Image, Text, VStack } from "@chakra-ui/react"; -import { useNavigate } from "@remix-run/react"; -import { FC } from "react"; -import { HatsDetailSchama } from "types/hats"; -import { BasicButton } from "./BasicButton"; - -interface BasicRoleProps { - detail?: HatsDetailSchama; - imageUri?: string; -} - -export const VerticalRole: FC = (params?) => { - const { detail, imageUri } = params!; - - return ( - - - {detail?.data?.name} - - ); -}; - -export const HorizontalRole: FC = (params?) => { - const { detail, imageUri } = params!; - - return ( - - - {detail?.data?.name} - - ); -}; - -interface RoleActionsProps { - detail?: HatsDetailSchama; - imageUri?: string; - address?: `0x${string}`; - treeId?: string; - hatId?: `0x${string}`; -} - -export const RoleActions: FC = (params) => { - const { address, detail, imageUri, treeId, hatId } = params; - - const navigate = useNavigate(); - - return ( - - - - {detail?.data.name} - navigate(`/${treeId}/${hatId}/${address}`)}> - See Detail - - - navigate(`/${treeId}/${hatId}/${address}/assistcredit/send`) - } - > - Transfer Assist Credit - - - - ); -}; diff --git a/pkgs/frontend/app/components/icon/RoleIcon.tsx b/pkgs/frontend/app/components/icon/RoleIcon.tsx index 802e918..d31fa0c 100644 --- a/pkgs/frontend/app/components/icon/RoleIcon.tsx +++ b/pkgs/frontend/app/components/icon/RoleIcon.tsx @@ -1,14 +1,19 @@ -import { FaCircle, FaPeopleGroup } from "react-icons/fa6"; +import { MdOutlineBadge } from "react-icons/md"; import { CommonIcon } from "../common/CommonIcon"; -import { useEffect, useState } from "react"; +import { FC, useEffect, useState } from "react"; import { ipfs2https } from "utils/ipfs"; interface RoleIconProps { roleImageUrl?: string; size?: number | `${number}px` | "full"; + borderRadius?: string; } -export const RoleIcon = ({ roleImageUrl, size = "full" }: RoleIconProps) => { +export const RoleIcon: FC = ({ + roleImageUrl, + size = "full", + borderRadius = "xl", +}) => { const [imageUrl, setImageUrl] = useState(); useEffect(() => { @@ -23,8 +28,9 @@ export const RoleIcon = ({ roleImageUrl, size = "full" }: RoleIconProps) => { = (params) => { + const { address, detail, imageUri, treeId, hatId } = params; + + const navigate = useNavigate(); + + return ( + + + + {detail?.data.name} + navigate(`/${treeId}/${hatId}/${address}`)} + > + See Detail + + + navigate(`/${treeId}/${hatId}/${address}/assistcredit/send`) + } + > + Transfer Assist Credit + + + + ); +}; diff --git a/pkgs/frontend/app/components/roles/RoleTag.tsx b/pkgs/frontend/app/components/roles/RoleTag.tsx new file mode 100644 index 0000000..b740db4 --- /dev/null +++ b/pkgs/frontend/app/components/roles/RoleTag.tsx @@ -0,0 +1,25 @@ +import { HStack, Text, VStack } from "@chakra-ui/react"; +import { FC } from "react"; +import { HatsDetailSchama } from "types/hats"; +import { RoleIcon } from "../icon/RoleIcon"; + +interface BasicRoleProps { + detail?: HatsDetailSchama; + imageUri?: string; + bgColor?: string; +} + +export const RoleTag: FC = ({ + detail, + imageUri, + bgColor = "yellow.400", +}) => { + return ( + + + + {detail?.data?.name} + + + ); +}; diff --git a/pkgs/frontend/app/components/roles/VRole.tsx b/pkgs/frontend/app/components/roles/VRole.tsx new file mode 100644 index 0000000..1d97b14 --- /dev/null +++ b/pkgs/frontend/app/components/roles/VRole.tsx @@ -0,0 +1,23 @@ +import { Text, VStack } from "@chakra-ui/react"; +import { FC } from "react"; +import { HatsDetailSchama } from "types/hats"; +import { RoleIcon } from "../icon/RoleIcon"; + +interface BasicRoleProps { + detail?: HatsDetailSchama; + imageUri?: string; + iconSize?: number | `${number}px` | "full"; +} + +export const VRole: FC = ({ + detail, + imageUri, + iconSize = "130px", +}) => { + return ( + + + {detail?.data?.name} + + ); +}; diff --git a/pkgs/frontend/app/routes/$treeId.tsx b/pkgs/frontend/app/routes/$treeId.tsx index 928ea32..ac0becd 100644 --- a/pkgs/frontend/app/routes/$treeId.tsx +++ b/pkgs/frontend/app/routes/$treeId.tsx @@ -6,14 +6,15 @@ import { Text, VStack, } from "@chakra-ui/react"; -import { useNavigate, useParams } from "@remix-run/react"; +import { Link, useNavigate, useParams } from "@remix-run/react"; import { useTreeInfo } from "hooks/useHats"; import { useActiveWallet } from "hooks/useWallet"; import { FC } from "react"; import { FaPlus } from "react-icons/fa6"; -import { VerticalRole, RoleActions } from "~/components/BasicRole"; +import { MyRole } from "~/components/roles/MyRole"; import { CommonButton } from "~/components/common/CommonButton"; import { HatsListItemParser } from "~/components/common/HatsListItemParser"; +import { VRole } from "~/components/roles/VRole"; const WorkspaceTop: FC = () => { const { wallet } = useActiveWallet(); @@ -21,7 +22,6 @@ const WorkspaceTop: FC = () => { const { treeId } = useParams(); const tree = useTreeInfo(Number(treeId)); - const topHatId = tree?.hats?.find((h) => h.levelAtLocalTree === 0)?.id; const navigate = useNavigate(); @@ -29,7 +29,7 @@ const WorkspaceTop: FC = () => { <> {/* My roles */} - My Roles + My Roles {tree?.hats ?.filter((h) => Number(h.levelAtLocalTree) >= 2) @@ -40,7 +40,7 @@ const WorkspaceTop: FC = () => { imageUri={h.imageUri} detailUri={h.details} > - + ))} @@ -48,22 +48,23 @@ const WorkspaceTop: FC = () => { {/* All roles */} - All Roles - + All Roles + {tree?.hats ?.filter((h) => Number(h.levelAtLocalTree) >= 2) .map((h) => ( - navigate(`/${treeId}/${h.id}`)}> + - + - + ))} - + navigate(`/workspaces/${topHatId}/roles/new`)} + rounded="xl" + onClick={() => navigate(`/${treeId}/roles/new`)} + bgColor="gray.300" > diff --git a/pkgs/frontend/app/routes/$treeId_.member.tsx b/pkgs/frontend/app/routes/$treeId_.member.tsx index 885a49c..66bb6a6 100644 --- a/pkgs/frontend/app/routes/$treeId_.member.tsx +++ b/pkgs/frontend/app/routes/$treeId_.member.tsx @@ -1,13 +1,13 @@ import { Box, Heading, HStack, Text, VStack } from "@chakra-ui/react"; -import { useParams } from "@remix-run/react"; +import { Link, useParams } from "@remix-run/react"; import { useNamesByAddresses } from "hooks/useENS"; import { useTokenRecipients } from "hooks/useFractionToken"; import { useTreeInfo } from "hooks/useHats"; import { FC, useMemo } from "react"; import { ipfs2https } from "utils/ipfs"; -import { HorizontalRole } from "~/components/BasicRole"; import { HatsListItemParser } from "~/components/common/HatsListItemParser"; import { UserIcon } from "~/components/icon/UserIcon"; +import { RoleTag } from "~/components/roles/RoleTag"; const WorkspaceMember: FC = () => { const { treeId } = useParams(); @@ -95,7 +95,7 @@ const WorkspaceMember: FC = () => { <> {/* Members */} - Members + Role Members {members.map((m, i) => ( @@ -111,13 +111,15 @@ const WorkspaceMember: FC = () => { {m.wearer?.hats?.map((h) => ( - - - + + + + + ))} @@ -128,7 +130,7 @@ const WorkspaceMember: FC = () => { {/* AssistantMembers */} - Assistant Members + All Contributors {assistantMembers.map((m, i) => ( @@ -149,7 +151,7 @@ const WorkspaceMember: FC = () => { imageUri={h.imageUri} detailUri={h.details} > - + ))}