diff --git a/components/groups/components/myGroups.tsx b/components/groups/components/myGroups.tsx index 6e584ef..d63783a 100644 --- a/components/groups/components/myGroups.tsx +++ b/components/groups/components/myGroups.tsx @@ -54,9 +54,16 @@ export function YourGroups({ const router = useRouter(); const { address } = useChain('manifest'); - const filteredGroups = groups.groups.filter(group => - (group.ipfsMetadata?.title || 'Untitled Group').toLowerCase().includes(searchTerm.toLowerCase()) - ); + const filteredGroups = groups.groups.filter(group => { + try { + const metadata = group.metadata ? JSON.parse(group.metadata) : null; + const groupTitle = metadata?.title || 'Untitled Group'; + return groupTitle.toLowerCase().includes(searchTerm.toLowerCase()); + } catch (e) { + console.warn('Failed to parse group metadata:', e); + return 'Untitled Group'.toLowerCase().includes(searchTerm.toLowerCase()); + } + }); const totalPages = Math.ceil(filteredGroups.length / pageSize); const paginatedGroups = filteredGroups.slice( @@ -72,9 +79,18 @@ export function YourGroups({ g => g.policies && g.policies.length > 0 && g.policies[0]?.address === policyAddress ); if (group) { + let groupName = 'Untitled Group'; + try { + const metadata = group.metadata ? JSON.parse(group.metadata) : null; + groupName = metadata?.title ?? 'Untitled Group'; + } catch (e) { + // If JSON parsing fails, fall back to default name + console.warn('Failed to parse group metadata:', e); + } + setSelectedGroup({ policyAddress, - name: group.ipfsMetadata?.title ?? 'Untitled Group', + name: groupName, threshold: (group.policies[0]?.decision_policy as ThresholdDecisionPolicySDKType)?.threshold ?? '0', @@ -451,8 +467,13 @@ function GroupRow({ setActiveInfoModalId: (id: string | null) => void; }) { const policyAddress = (group.policies && group.policies[0]?.address) || ''; - const groupName = group.ipfsMetadata?.title || 'Untitled Group'; - + let groupName = 'Untitled Group'; + try { + const metadata = group.metadata ? JSON.parse(group.metadata) : null; + groupName = metadata?.title || 'Untitled Group'; + } catch (e) { + console.warn('Failed to parse group metadata:', e); + } const filterActiveProposals = (proposals: ProposalSDKType[]) => { return proposals?.filter( proposal => diff --git a/components/groups/forms/groups/ConfirmationForm.tsx b/components/groups/forms/groups/ConfirmationForm.tsx index b329e95..ba4dafe 100644 --- a/components/groups/forms/groups/ConfirmationForm.tsx +++ b/components/groups/forms/groups/ConfirmationForm.tsx @@ -1,8 +1,6 @@ -import { useEffect, useState } from 'react'; import { TruncatedAddressWithCopy } from '@/components/react/addressCopy'; import { FormData } from '@/helpers/formReducer'; import { useFeeEstimation } from '@/hooks/useFeeEstimation'; -import { uploadJsonToIPFS } from '@/hooks/useIpfs'; import { useTx } from '@/hooks/useTx'; import { cosmos } from '@liftedinit/manifestjs'; import { ThresholdDecisionPolicy } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/types'; @@ -35,11 +33,6 @@ export default function ConfirmationForm({ const { tx, isSigning, setIsSigning } = useTx('manifest'); const { estimateFee } = useFeeEstimation('manifest'); - const uploadMetaDataToIPFS = async () => { - const CID = await uploadJsonToIPFS(jsonString); - return CID; - }; - const minExecutionPeriod: Duration = { seconds: BigInt(0), nanos: 0, @@ -62,7 +55,6 @@ export default function ConfirmationForm({ const handleConfirm = async () => { setIsSigning(true); try { - const CID = await uploadMetaDataToIPFS(); const msg = createGroupWithPolicy({ admin: address ?? '', members: formData.members.map(member => ({ @@ -71,7 +63,7 @@ export default function ConfirmationForm({ metadata: member.name, added_at: new Date(), })), - groupMetadata: CID, + groupMetadata: jsonString, groupPolicyMetadata: '', groupPolicyAsAdmin: true, decisionPolicy: { diff --git a/components/groups/forms/groups/MemberInfoForm.tsx b/components/groups/forms/groups/MemberInfoForm.tsx index 5b9be03..629055c 100644 --- a/components/groups/forms/groups/MemberInfoForm.tsx +++ b/components/groups/forms/groups/MemberInfoForm.tsx @@ -247,6 +247,7 @@ export default function MemberInfoForm({ isOpen={isContactsOpen} setOpen={setIsContactsOpen} showContacts={true} + currentAddress={address} onSelect={(selectedAddress: string) => { if (activeMemberIndex !== null) { setFieldValue(`members.${activeMemberIndex}.address`, selectedAddress); diff --git a/components/groups/modals/groupDetailsModal.tsx b/components/groups/modals/groupDetailsModal.tsx index 21bbf23..83358cf 100644 --- a/components/groups/modals/groupDetailsModal.tsx +++ b/components/groups/modals/groupDetailsModal.tsx @@ -1,15 +1,17 @@ import { TruncatedAddressWithCopy } from '@/components/react/addressCopy'; -import { IPFSMetadata } from '@/hooks/useQueries'; -import ProfileAvatar from '@/utils/identicon'; +import { + GroupMemberSDKType, + GroupPolicyInfoSDKType, + ThresholdDecisionPolicySDKType, +} from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/types'; import { PiXCircleLight } from 'react-icons/pi'; interface Group { group: { admin: string; metadata: string; - ipfsMetadata: IPFSMetadata | null; - members: any[]; - policies: any[]; + members: GroupMemberSDKType[]; + policies: GroupPolicyInfoSDKType[]; }; } @@ -24,6 +26,13 @@ export function GroupDetailsModal({ group, modalId }: Group & { modalId: string return adminAddresses.includes(address); }; + const metadata = group.metadata ? JSON.parse(group.metadata) : null; + + const authors = metadata?.authors || 'No authors available'; + const summary = metadata?.summary || 'No summary available'; + const details = metadata?.details || 'No details available'; + const proposalForumURL = metadata?.proposalForumURL || 'No forum URL available'; + return (
@@ -37,29 +46,25 @@ export function GroupDetailsModal({ group, modalId }: Group & { modalId: string

AUTHORS

-

{group?.ipfsMetadata?.authors ?? 'No authors available'}

+

{authors}

SUMMARY

-

{group?.ipfsMetadata?.summary ?? 'No summary available'}

+

{summary}

DETAILS

-

- {group?.ipfsMetadata?.details ?? 'No details available'} -

+

{details}

FORUM

-

- {group?.ipfsMetadata?.proposalForumURL ?? 'No forum URL available'} -

+

{proposalForumURL}

@@ -82,10 +87,14 @@ export function GroupDetailsModal({ group, modalId }: Group & { modalId: string

VOTING WINDOW

- {policy?.decision_policy?.windows?.voting_period + {(policy?.decision_policy as ThresholdDecisionPolicySDKType)?.windows + ?.voting_period ? Math.floor( - parseInt(policy.decision_policy.windows.voting_period.slice(0, -1)) / - 86400 + parseInt( + ( + policy?.decision_policy as ThresholdDecisionPolicySDKType + )?.windows?.voting_period.seconds.toString() ?? '0' + ) / 86400 ) : 'No voting period available'}{' '} days @@ -96,8 +105,9 @@ export function GroupDetailsModal({ group, modalId }: Group & { modalId: string

THRESHOLD

- {policy?.decision_policy?.threshold ?? 'No threshold available'} / - {group?.members.length ?? 'No members available'} + {(policy?.decision_policy as ThresholdDecisionPolicySDKType)?.threshold ?? + 'No threshold available'}{' '} + / {group?.members.length ?? 'No members available'}

@@ -152,12 +162,12 @@ export function GroupDetailsModal({ group, modalId }: Group & { modalId: string /> - {isPolicyAdmin(member?.member?.address) && - isAdmin(member?.member?.address) ? ( + {isPolicyAdmin(member?.member?.address ?? '') && + isAdmin(member?.member?.address ?? '') ? ( 'Super Admin' - ) : isPolicyAdmin(member?.member?.address) ? ( + ) : isPolicyAdmin(member?.member?.address ?? '') ? ( 'Policy' - ) : isAdmin(member?.member?.address) ? ( + ) : isAdmin(member?.member?.address ?? '') ? ( 'Group' ) : ( diff --git a/components/groups/modals/groupInfo.tsx b/components/groups/modals/groupInfo.tsx index a228d9e..6833789 100644 --- a/components/groups/modals/groupInfo.tsx +++ b/components/groups/modals/groupInfo.tsx @@ -84,39 +84,53 @@ export function GroupInfo({ 'No threshold available'; function renderAuthors() { - const authors = group?.ipfsMetadata?.authors; + let metadata = null; + let authors: string | string[] = 'No authors available'; + + try { + metadata = group?.metadata ? JSON.parse(group.metadata) : null; + authors = metadata?.authors || 'No authors available'; + } catch (e) { + console.warn('Failed to parse group metadata for authors:', e); + } if (!authors) { return ; } - const formatAddress = (author: string, index: number) => ( - - ); + try { + const formatAddress = (author: string, index: number) => ( + + ); - const formatAuthor = (author: string, index: number) => ( - - ); + const formatAuthor = (author: string, index: number) => ( + + ); - if (typeof authors === 'string') { - if (authors.startsWith('manifest1')) { - return
{formatAddress(authors, 0)}
; + if (typeof authors === 'string') { + if (authors.startsWith('manifest1')) { + return
{formatAddress(authors, 0)}
; + } + return formatAuthor(authors, 0); } - return formatAuthor(authors, 0); - } - - if (Array.isArray(authors)) { - const manifestAddresses = authors.filter(author => author.startsWith('manifest1')); - if (manifestAddresses.length > 0) { - return ( -
- {manifestAddresses.map((author, index) => formatAddress(author, index))} -
+ if (Array.isArray(authors)) { + const manifestAddresses = authors.filter( + (author): author is string => typeof author === 'string' && author.startsWith('manifest1') ); - } - return
{authors.map((author, index) => formatAuthor(author, index))}
; + if (manifestAddresses.length > 0) { + return ( +
+ {manifestAddresses.map((author, index) => formatAddress(author, index))} +
+ ); + } + + return
{authors.map((author, index) => formatAuthor(String(author), index))}
; + } + } catch (e) { + console.warn('Failed to process authors data:', e); } return ; @@ -147,6 +161,17 @@ export function GroupInfo({ } }; + let title = 'Untitled Group'; + let details = 'No description'; + + try { + const metadata = group.metadata ? JSON.parse(group.metadata) : null; + title = metadata?.title || 'Untitled Group'; + details = metadata?.details || 'No description'; + } catch (e) { + console.warn('Failed to parse group metadata:', e); + } + const modalContent = (
-

{group.ipfsMetadata?.title ?? 'No title'}

+

{title}

diff --git a/components/wallet.tsx b/components/wallet.tsx index dad1247..d3f84e6 100644 --- a/components/wallet.tsx +++ b/components/wallet.tsx @@ -213,12 +213,8 @@ export const IconWallet: React.FC = ({ chainName }) => { return (
diff --git a/hooks/index.tsx b/hooks/index.tsx index 6aa415f..5504a11 100644 --- a/hooks/index.tsx +++ b/hooks/index.tsx @@ -1,5 +1,4 @@ export * from './useFeeEstimation'; -export * from './useIpfs'; export * from './useLcdQueryClient'; export * from './usePoaLcdQueryClient'; export * from './useQueries'; diff --git a/hooks/useIpfs.ts b/hooks/useIpfs.ts deleted file mode 100644 index 2e6e3f3..0000000 --- a/hooks/useIpfs.ts +++ /dev/null @@ -1,57 +0,0 @@ -// hooks/useIPFSFetch.ts -import { useState, useEffect } from 'react'; - -const useIPFSFetch = (cid: string) => { - const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - if (!cid) { - setError(new Error('CID is required')); - setLoading(false); - return; - } - - const fetchData = async () => { - setLoading(true); - setError(null); - try { - const response = await fetch(`https://nodes.chandrastation.com/ipfs/gateway/${cid}`); - if (!response.ok) { - throw new Error(`Failed to fetch data: ${response.status} ${response.statusText}`); - } - const content = await response.text(); - setData(content); - } catch (err) { - setError(err as Error); - } finally { - setLoading(false); - } - }; - - fetchData(); - }, [cid]); - - return { data, loading, error }; -}; - -export default useIPFSFetch; - -export const uploadJsonToIPFS = async (jsonString: BlobPart) => { - const blob = new Blob([jsonString], { type: 'application/json' }); - const formData = new FormData(); - formData.append('file', blob, 'groupMetadata.json'); - - const response = await fetch('https://nodes.chandrastation.com/ipfs/api/add', { - method: 'POST', - body: formData, - }); - - if (!response.ok) { - throw new Error(`Failed to upload to IPFS: ${response.status} ${response.statusText}`); - } - - const data = await response.json(); - return data.Hash; -}; diff --git a/hooks/useQueries.ts b/hooks/useQueries.ts index 361759e..c214e66 100644 --- a/hooks/useQueries.ts +++ b/hooks/useQueries.ts @@ -17,17 +17,8 @@ import { TransactionGroup } from '@/components'; import { Octokit } from 'octokit'; import { useOsmosisRpcQueryClient } from '@/hooks/useOsmosisRpcQueryClient'; -export interface IPFSMetadata { - title: string; - authors: string | string[]; - summary: string; - details: string; - proposalForumURL: string; - voteOptionContext: string; -} export type ExtendedGroupType = QueryGroupsByMemberResponseSDKType['groups'][0] & { - ipfsMetadata: IPFSMetadata | null; policies: GroupPolicyInfoSDKType[]; members: GroupMemberSDKType[]; }; @@ -136,34 +127,15 @@ export const useGroupsByMember = (address: string) => { const memberPromises = groupQuery.data.groups.map(group => lcdQueryClient?.cosmos.group.v1.groupMembers({ groupId: group.id }) ); - const ipfsPromises = groupQuery.data.groups.map(group => { - if (isValidIPFSCID(group.metadata)) { - return fetch(`https://nodes.chandrastation.com/ipfs/gateway/${group.metadata}`) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - return response.json() as Promise; - }) - .catch(err => { - console.error(`Invalid IPFS CID for group #${group?.id}`, err); - return null; - }); - } else { - console.warn(`Invalid IPFS CID for group #${group?.id}`); - return Promise.resolve(null); - } - }); - const [policiesResults, membersResults, ipfsResults] = await Promise.all([ + const [policiesResults, membersResults] = await Promise.all([ Promise.all(policyPromises), Promise.all(memberPromises), - Promise.all(ipfsPromises), ]); const groupsWithAllData = groupQuery.data.groups.map((group, index) => ({ ...group, - ipfsMetadata: ipfsResults[index], + policies: policiesResults[index]?.group_policies || [], members: membersResults[index]?.members || [], })); @@ -216,29 +188,14 @@ export const useGroupsByAdmin = (admin: string) => { const memberPromises = groupQuery.data.groups.map(group => lcdQueryClient?.cosmos.group.v1.groupMembers({ groupId: group.id }) ); - const ipfsPromises = groupQuery.data.groups.map(group => - fetch(`https://nodes.chandrastation.com/ipfs/gateway/${group.metadata}`) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - return response.json() as Promise; - }) - .catch(err => { - console.warn(`Failed to fetch IPFS metadata for group #${group.id}:`, err); - return null; - }) - ); - const [policiesResults, membersResults, ipfsResults] = await Promise.all([ + const [policiesResults, membersResults] = await Promise.all([ Promise.all(policyPromises), Promise.all(memberPromises), - Promise.all(ipfsPromises), ]); const groupsWithAllData = groupQuery.data.groups.map((group, index) => ({ ...group, - ipfsMetadata: ipfsResults[index] || null, policies: policiesResults[index]?.group_policies || [], members: membersResults[index]?.members || [], }));