Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate AllDomains #49

Merged
merged 12 commits into from
Dec 16, 2024
77 changes: 60 additions & 17 deletions components/Members/AddMemberForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import AddMemberIcon from '@components/AddMemberIcon'
import {
ArrowCircleDownIcon,
ArrowCircleUpIcon,
RefreshIcon,
} from '@heroicons/react/outline'
import useCreateProposal from '@hooks/useCreateProposal'
import { AssetAccount } from '@utils/uiTypes/assets'
Expand All @@ -33,6 +34,8 @@ import { useRealmQuery } from '@hooks/queries/realm'
import { DEFAULT_GOVERNANCE_PROGRAM_VERSION } from '@components/instructions/tools'
import useLegacyConnectionContext from '@hooks/useLegacyConnectionContext'
import {useVoteByCouncilToggle} from "@hooks/useVoteByCouncilToggle";
import { resolveDomain } from '@utils/domains'
import debounce from 'lodash/debounce'

interface AddMemberForm extends Omit<MintForm, 'mintAccount'> {
description: string
Expand Down Expand Up @@ -95,6 +98,44 @@ const AddMemberForm: FC<{ close: () => void; mintAccount: AssetAccount }> = ({
// note the lack of space is not a typo
const proposalTitle = `Add ${govpop}member ${abbrevAddress}`

const [isResolvingDomain, setIsResolvingDomain] = useState(false)

const resolveDomainDebounced = useMemo(
() =>
debounce(async (domain: string) => {
try {
console.log('Attempting to resolve domain:', domain)
const resolved = await resolveDomain(connection.current, domain)
console.log('Domain resolved to:', resolved?.toBase58() || 'null')

if (resolved) {
handleSetForm({
value: resolved.toBase58(),
propertyName: 'destinationAccount',
})
}
} catch (error) {
console.error('Error resolving domain:', error)
} finally {
setIsResolvingDomain(false)
}
}, 500),
[connection]
)

const handleDestinationAccountChange = async (event) => {
const value = event.target.value
handleSetForm({
value,
propertyName: 'destinationAccount',
})

if (value.includes('.')) {
setIsResolvingDomain(true)
resolveDomainDebounced(value)
}
}

const setAmount = (event) => {
const value = event.target.value

Expand Down Expand Up @@ -264,23 +305,25 @@ const AddMemberForm: FC<{ close: () => void; mintAccount: AssetAccount }> = ({
<h2 className="text-xl">Add new member to {realmInfo?.displayName}</h2>
</div>

<Input
useDefaultStyle={false}
className="p-4 w-full bg-bkg-3 border border-bkg-3 default-transition text-sm text-fgd-1 rounded-md focus:border-bkg-3 focus:outline-none"
wrapperClassName="my-6"
label="Member's wallet"
placeholder="Member's wallet"
value={form.destinationAccount}
type="text"
onChange={(event) =>
handleSetForm({
value: event.target.value,
propertyName: 'destinationAccount',
})
}
noMaxWidth
error={formErrors['destinationAccount']}
/>
<div className="relative">
<Input
useDefaultStyle={false}
className="p-4 w-full bg-bkg-3 border border-bkg-3 default-transition text-sm text-fgd-1 rounded-md focus:border-bkg-3 focus:outline-none"
wrapperClassName="my-6"
label="Member's wallet"
placeholder="Member's wallet or domain name (e.g. domain.sol)"
thearyanag marked this conversation as resolved.
Show resolved Hide resolved
value={form.destinationAccount}
type="text"
onChange={handleDestinationAccountChange}
noMaxWidth
error={formErrors['destinationAccount']}
/>
{isResolvingDomain && (
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
<RefreshIcon className="h-4 w-4 animate-spin text-primary-light" />
</div>
)}
</div>

<div
className={'flex items-center hover:cursor-pointer w-24 my-3'}
Expand Down
72 changes: 67 additions & 5 deletions components/NewRealmWizard/components/steps/InviteMembersForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import { updateUserInput, validateSolAddress } from '@utils/formValidation'
import { FORM_NAME as MULTISIG_FORM } from 'pages/realms/new/multisig'
import { textToAddressList } from '@utils/textToAddressList'
import useWalletOnePointOh from '@hooks/useWalletOnePointOh'
import { resolveDomain } from '@utils/domains'
import { Connection } from '@solana/web3.js'
// import { DEFAULT_RPC_ENDPOINT } from '@constants/endpoints'
import { useConnection } from '@solana/wallet-adapter-react'

/**
* Convert a list of addresses into a list of uniques and duplicates
Expand Down Expand Up @@ -131,6 +135,58 @@ export interface InviteMembers {
memberAddresses: string[]
}

/**
* Convert a list of addresses/domains into a list of resolved addresses
*/
async function resolveAddressList(connection: Connection, textBlock: string) {
const items = textBlock.split(/[\n,]+/).map((item) => item.trim()).filter(Boolean)
console.log('Resolving items:', items)

const results = await Promise.all(
items.map(async (item) => {
try {
// If it's already a valid address, return it
if (validateSolAddress(item)) {
console.log(`${item} is already a valid address`)
return { input: item, resolved: item }
}

// Try to resolve as domain
console.log(`Attempting to resolve domain: ${item}`)
const resolved = await resolveDomain(connection, item)
console.log('Resolved:', resolved)
console.log(`Domain ${item} resolved to:`, resolved?.toBase58() || 'null')
return {
input: item,
resolved: resolved?.toBase58()
}
} catch (error) {
console.error(`Error resolving ${item}:`, error)
return { input: item, resolved: undefined }
}
})
)

const valid: string[] = []
const invalid: string[] = []

results.forEach(({ input, resolved }) => {
if (resolved && validateSolAddress(resolved)) {
thearyanag marked this conversation as resolved.
Show resolved Hide resolved
valid.push(resolved)
} else {
invalid.push(input)
}
})

console.log('Final resolution results:', {
valid,
invalid,
results
})

return { valid, invalid }
}

export default function InviteMembersForm({
visible,
type,
Expand All @@ -146,6 +202,7 @@ export default function InviteMembersForm({
const [inviteList, setInviteList] = useState<string[]>([])
const [invalidAddresses, setInvalidAddresses] = useState<string[]>([])
const [lacksMintAuthority, setLackMintAuthority] = useState(false)
const { connection } = useConnection()

const schema = yup.object(InviteMembersSchema)
const {
Expand Down Expand Up @@ -211,19 +268,25 @@ export default function InviteMembersForm({
onSubmit({ step: currentStep, data: values })
}

function addToAddressList(textBlock: string) {
/**
* Update the addToAddressList function to be async
*/
async function addToAddressList(textBlock: string) {
if (lacksMintAuthority) {
return
}

const { valid, invalid } = textToAddressList(textBlock)
const { valid, invalid } = await resolveAddressList(connection, textBlock)
const { unique, duplicate } = splitUniques(inviteList.concat(valid))
setInviteList(unique)
setInvalidAddresses((currentList) =>
currentList.concat(invalid).concat(duplicate)
)
}

/**
* Update the event handlers to handle async addToAddressList
*/
function handleBlur(ev) {
addToAddressList(ev.currentTarget.value)
ev.currentTarget.value = ''
Expand All @@ -232,13 +295,12 @@ export default function InviteMembersForm({
function handlePaste(ev: React.ClipboardEvent<HTMLInputElement>) {
addToAddressList(ev.clipboardData.getData('text'))
ev.clipboardData.clearData()
// Don't allow the paste event to populate the input field
ev.preventDefault()
}

function handleKeyDown(ev) {
if (ev.defaultPrevented) {
return // Do nothing if the event was already processed
return
}

if (ev.key === 'Enter') {
Expand Down Expand Up @@ -323,7 +385,7 @@ export default function InviteMembersForm({
<Input
type="text"
name="memberAddresses"
placeholder="e.g. CWvWQWt5mTv7Zx..."
placeholder="e.g. CWvWQWt5mTv7Zx... or domain.sol"
thearyanag marked this conversation as resolved.
Show resolved Hide resolved
data-testid="dao-member-list-input"
disabled={lacksMintAuthority}
ref={inputElement}
Expand Down
12 changes: 11 additions & 1 deletion components/Profile/ProfileName.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { useProfile } from '@components/Profile/useProfile';
import { PublicKey } from '@solana/web3.js';
import ContentLoader from "react-content-loader";
import {ShortAddress} from "@components/Profile/ShortAddress";
import { fetchDomainsByPubkey } from '@utils/domains'
import { useConnection } from '@solana/wallet-adapter-react'
import { useEffect, useState } from 'react'

type Props = { publicKey?: PublicKey, height?: string;
width?: string;
Expand All @@ -13,6 +16,13 @@ export const ProfileName: FC<Props> = ({ publicKey, height = "13",
dark = false,
style, }) => {
const { profile, loading } = useProfile(publicKey)
const { connection } = useConnection()
const [domains, setDomains] = useState<any[]>([])
useEffect(() => {
if (publicKey) {
fetchDomainsByPubkey(connection, publicKey).then(setDomains)
}
}, [publicKey, connection])


if (!publicKey) return <></>;
Expand All @@ -34,7 +44,7 @@ export const ProfileName: FC<Props> = ({ publicKey, height = "13",
</div>
) : (
<div style={{ display: "flex", gap: "5px", ...style }}>
{profile?.name?.value || <ShortAddress address={publicKey} />}
{domains.length > 0 ? domains[0].domainName : profile?.name?.value || <ShortAddress address={publicKey} />}
</div>
);
}
29 changes: 19 additions & 10 deletions hooks/useTreasuryInfo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import useRealm from '@hooks/useRealm'

import { assembleWallets } from './assembleWallets'
import { calculateTokenCountAndValue } from './calculateTokenCountAndValue'
import { getDomains } from './getDomains'
import { fetchDomainsByPubkey } from '@utils/domains'
import { useRealmQuery } from '@hooks/queries/realm'
import { useRealmConfigQuery } from '@hooks/queries/realmConfig'
import {
Expand Down Expand Up @@ -67,18 +67,27 @@ export default function useTreasuryInfo(
if (!loadingGovernedAccounts && accounts.length && getNftsAndDomains) {
setDomainsLoading(true)
setBuildingWallets(true)
getDomains(
accounts.filter((acc) => acc.isSol),
connection.current
).then((domainNames) => {
setDomains(domainNames)
setDomainsLoading(false)
})

Promise.all(
accounts
.filter((acc) => acc.isSol)
.map((account) =>
fetchDomainsByPubkey(connection.current, account.pubkey)
)
).then((domainResults) => {
const allDomains = domainResults.flat().map(domain => ({
name: domain.domainName?.replace('.sol', ''),
address: domain.domainAddress,
owner: accounts[0].pubkey.toBase58(),
type: domain.type
}));

setDomains(allDomains);
setDomainsLoading(false);
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- TODO please fix, it can cause difficult bugs. You might wanna check out https://bobbyhadz.com/blog/react-hooks-exhaustive-deps for info. -@asktree
}, [
loadingGovernedAccounts,
// eslint-disable-next-line react-hooks/exhaustive-deps -- TODO please fix, it can cause difficult bugs. You might wanna check out https://bobbyhadz.com/blog/react-hooks-exhaustive-deps for info. -@asktree
accounts.map((account) => account.pubkey.toBase58()).join('-'),
])

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@next/bundle-analyzer": "12.1.5",
"@nivo/bar": "0.79.1",
"@nivo/core": "0.79.0",
"@onsol/tldparser": "0.6.6",
"@parcl-oss/staking": "0.0.2",
"@project-serum/common": "0.0.1-beta.3",
"@project-serum/serum": "0.13.65",
Expand Down
Loading