diff --git a/apps/web/src/app/api/dao/[daoId]/auctions/route.ts b/apps/web/src/app/api/dao/[daoId]/auctions/route.ts index 4be8c38..9030526 100644 --- a/apps/web/src/app/api/dao/[daoId]/auctions/route.ts +++ b/apps/web/src/app/api/dao/[daoId]/auctions/route.ts @@ -16,7 +16,7 @@ export async function GET(_request: Request, { params }: { params: Promise<{ dao try { const { daoId } = await params; const config = await getDaoNetworkConfigById(daoId); - if (!config.auctionContractId || config.auctionEnabled === false) { + if (!config.auctionContractId) { return NextResponse.json({ message: 'Auctions are disabled for this DAO.' }, { status: 404 }); } @@ -34,13 +34,14 @@ export async function GET(_request: Request, { params }: { params: Promise<{ dao const auctionTx = await client.get_auction(); const auction = jsonValue(auctionTx.result) as { token_id?: string } | null; const paused = Boolean(pausedTx.result); + const auctionEnabled = config.auctionEnabled !== false || !paused; if (!auction || typeof auction.token_id === 'undefined') { // No auction token exists yet. Determine if we've never launched or if we paused after launching. // If there's auction history, we've been paused. Otherwise, we've never launched. return NextResponse.json({ status: paused && history.length > 0 ? 'paused' : 'not-launched', auction: null, - auctionEnabled: true, + auctionEnabled, paused, config: jsonValue(configTx.result), history @@ -51,7 +52,7 @@ export async function GET(_request: Request, { params }: { params: Promise<{ dao jsonValue({ status: paused ? 'paused' : 'active', auction, - auctionEnabled: true, + auctionEnabled, config: configTx.result, paused, bids, diff --git a/apps/web/src/app/create/page.tsx b/apps/web/src/app/create/page.tsx index 29a4cbf..d0741e1 100644 --- a/apps/web/src/app/create/page.tsx +++ b/apps/web/src/app/create/page.tsx @@ -28,8 +28,8 @@ import { } from '@/lib/create-dao-schema'; import { getDeploymentConfig, isDeploymentConfigured } from '@/lib/deployment-config'; import { useDaoDeployment } from '@/lib/use-dao-deployment'; +import { useAuthSessionStore } from '@/stores/auth-session-store'; import { useCreateDaoStore } from '@/stores/create-dao-store'; -import { useDaoSessionStore } from '@/stores/dao-session-store'; type SectionStatus = 'complete' | 'active' | 'error' | 'pending'; @@ -175,7 +175,7 @@ function AccordionSection({ export default function CreateDaoPage() { const router = useRouter(); - const session = useDaoSessionStore(); + const session = useAuthSessionStore(); const sectionRefs = useRef>>({}); const basicInfo = useCreateDaoStore((state) => state.basicInfo); diff --git a/apps/web/src/app/dao/[daoId]/admin/auction/page.tsx b/apps/web/src/app/dao/[daoId]/admin/auction/page.tsx index 9d91358..43ce256 100644 --- a/apps/web/src/app/dao/[daoId]/admin/auction/page.tsx +++ b/apps/web/src/app/dao/[daoId]/admin/auction/page.tsx @@ -1,6 +1,7 @@ 'use client'; import { Client as AuctionClient } from '@builder-stellar/auction-bindings'; +import { Client as TokenClient } from '@builder-stellar/token-bindings'; import { StellarWalletsKit } from '@creit.tech/stellar-wallets-kit/sdk'; import { useState } from 'react'; import { Stack } from 'styled-system/jsx'; @@ -10,26 +11,36 @@ import { AdminPaymentTokenForm, AdminReservePriceForm } from '@/components/admin import { AdminProposalDraftDialog } from '@/components/admin/admin-proposal-draft-dialog'; import { AdminSectionNav } from '@/components/admin/admin-section-nav'; import { type AuctionAutoPauseAction, AuctionAutoPauseDialog } from '@/components/admin/auction-auto-pause-dialog'; +import { + type EnableAuctionsErrors, + EnableAuctionsForm, + type EnableAuctionsValues +} from '@/components/admin/enable-auctions-form'; import { PageSection } from '@/components/page-section'; import { Badge, Button, Callout, Card, Heading, ShortId, Skeleton, Text } from '@/components/ui'; import { useDaoContext } from '@/contexts/dao-context'; import { treasuryIsOwner } from '@/lib/admin-proposals'; import { useContractOwner } from '@/lib/admin-queries'; import { getTreasuryAssets } from '@/lib/assets-config'; -import { formatStroops } from '@/lib/auction-values'; +import { decimalToStroops, formatStroops, validateReservePrice } from '@/lib/auction-values'; import { useGoldskyMintAuthorities } from '@/lib/goldsky-queries'; import { getActionHandler } from '@/lib/proposal-actions/registry'; import { waitForConfirmation } from '@/lib/transaction-confirmation'; import { useTransactionFeedback } from '@/lib/transaction-feedback'; import { useAdminDraftStatus } from '@/lib/use-admin-draft-status'; import { useAdminProposalDraft } from '@/lib/use-admin-proposal-draft'; -import { getStellarAddressError, isValidStellarAddress } from '@/lib/validation'; -import { useDaoSessionStore } from '@/stores/dao-session-store'; +import { getStellarAddressError, isValidStellarAddress, validateDuration } from '@/lib/validation'; +import { useAuthSessionStore } from '@/stores/auth-session-store'; type AuctionStatus = { status: 'not-launched' | 'paused' | 'active'; paused: boolean; - config: { reserve_price: string; payment_token: string | null }; + config: { + duration: string | number; + reserve_price: string; + time_buffer: string | number; + payment_token: string | null; + }; }; const fetcher = async (url: string): Promise => { @@ -43,18 +54,41 @@ const fetcher = async (url: string): Promise => { }; }; +function nonZeroValue(value: string | number | null | undefined) { + if (value === null || typeof value === 'undefined' || value === '' || Number(value) <= 0) return ''; + return String(value); +} + +function nonZeroReserve(value: string | null | undefined) { + try { + return value && BigInt(value) > 0n ? formatStroops(value) : ''; + } catch { + return ''; + } +} + export default function AuctionAdminPage() { const { daoId, daoConfig: config } = useDaoContext(); - const session = useDaoSessionStore(); + const session = useAuthSessionStore(); const tx = useTransactionFeedback(config.name); const [busy, setBusy] = useState(false); const [formMessage, setFormMessage] = useState(''); const [reservePrice, setReservePrice] = useState(''); const [paymentToken, setPaymentToken] = useState(''); + const [enableValues, setEnableValues] = useState({ + duration: '', + reservePrice: '', + timeBuffer: '', + paymentToken: '' + }); + const [enableErrors, setEnableErrors] = useState({}); const [autoPauseDialogOpen, setAutoPauseDialogOpen] = useState(false); const [autoPauseAction, setAutoPauseAction] = useState(null); const proposalDraft = useAdminProposalDraft(); const draftStatus = useAdminDraftStatus(daoId, [ + 'set-mint-authority', + 'set-auction-duration', + 'set-auction-time-buffer', 'set-auction-reserve-price', 'set-auction-payment-token', 'pause-auction', @@ -71,21 +105,293 @@ export default function AuctionAdminPage() { const auctionCanMint = Boolean( mintAuthorities?.items.some((item) => item.authority === config.auctionContractId && item.enabled) ); + const auctionMintAuthorityQueued = draftStatus.actionsInDraft.some( + (action) => + action.type === 'set-mint-authority' && action.authority === config.auctionContractId && action.enabled !== false + ); + + const populatedEnableValues: EnableAuctionsValues = { + duration: enableValues.duration || nonZeroValue(data?.config.duration), + reservePrice: enableValues.reservePrice || nonZeroReserve(data?.config.reserve_price), + timeBuffer: enableValues.timeBuffer || nonZeroValue(data?.config.time_buffer), + paymentToken: enableValues.paymentToken || data?.config.payment_token || '' + }; + + function validateEnableValues() { + const nextErrors: EnableAuctionsErrors = {}; + const duration = Number(populatedEnableValues.duration); + const timeBuffer = Number(populatedEnableValues.timeBuffer); + const reservePrice = decimalToStroops(populatedEnableValues.reservePrice); + + const durationError = validateDuration(duration, 300); + if (!populatedEnableValues.duration || durationError) + nextErrors.duration = durationError || 'Auction duration is required.'; + + const reserveError = validateReservePrice(populatedEnableValues.reservePrice); + if (!populatedEnableValues.reservePrice || reserveError) + nextErrors.reservePrice = reserveError || 'Reserve price is required.'; + + const timeBufferError = validateDuration(timeBuffer, 60); + if (!populatedEnableValues.timeBuffer || timeBufferError) { + nextErrors.timeBuffer = timeBufferError || 'Time buffer is required.'; + } + + if (!populatedEnableValues.paymentToken) { + nextErrors.paymentToken = 'Payment token is required.'; + } else if (!isValidStellarAddress(populatedEnableValues.paymentToken)) { + nextErrors.paymentToken = + getStellarAddressError(populatedEnableValues.paymentToken) || 'Invalid payment token address.'; + } + + setEnableErrors(nextErrors); + if (Object.keys(nextErrors).length > 0 || reservePrice === null) return null; + return { duration, reservePrice, timeBuffer, paymentToken: populatedEnableValues.paymentToken.trim() }; + } + + function proposalContext() { + return { config, session: { address: session.address, kit: StellarWalletsKit } }; + } + + function buildEnableProposalRequests(values: { + duration: number; + reservePrice: bigint; + timeBuffer: number; + paymentToken: string; + }) { + const context = proposalContext(); + const durationAction = getActionHandler('set-auction-duration').serialize( + { value: String(values.duration) }, + context + ); + const reserveAction = getActionHandler('set-auction-reserve-price').serialize( + { reservePrice: formatStroops(values.reservePrice) }, + context + ); + const timeBufferAction = getActionHandler('set-auction-time-buffer').serialize( + { value: String(values.timeBuffer) }, + context + ); + const paymentAction = getActionHandler('set-auction-payment-token').serialize( + { paymentToken: values.paymentToken }, + context + ); + const unpauseAction = getActionHandler('unpause-auction').serialize({}, context); + const requests = [ + { + daoId, + action: durationAction, + source: 'admin/auction/set-auction-duration', + metadata: { + title: 'Set auction duration', + description: `Set each auction duration to ${values.duration} seconds.`, + url: '' + } + }, + { + daoId, + action: reserveAction, + source: 'admin/auction/set-auction-reserve-price', + metadata: { + title: 'Set auction reserve price', + description: `Set the first auction reserve price to ${formatStroops(values.reservePrice)}.`, + url: '' + } + }, + { + daoId, + action: timeBufferAction, + source: 'admin/auction/set-auction-time-buffer', + metadata: { + title: 'Set auction time buffer', + description: `Set the auction time buffer to ${values.timeBuffer} seconds.`, + url: '' + } + }, + { + daoId, + action: paymentAction, + source: 'admin/auction/set-auction-payment-token', + metadata: { + title: 'Set auction payment token', + description: `Set the auction payment token to ${values.paymentToken}.`, + url: '' + } + } + ]; + + if (!auctionCanMint && !auctionMintAuthorityQueued) { + requests.push({ + daoId, + action: getActionHandler('set-mint-authority').serialize( + { authority: config.auctionContractId, enabled: true }, + context + ), + source: 'admin/auction/set-mint-authority', + metadata: { + title: 'Enable auction mint authority', + description: `Allow ${config.auctionContractId} to mint auction tokens.`, + url: '' + } + }); + } + + requests.push({ + daoId, + action: unpauseAction, + source: 'admin/auction/unpause-auction', + metadata: { + title: 'Enable auctions', + description: 'Enable auction activity and create the first auction.', + url: '' + } + }); + return requests; + } + + async function handleEnableAuctions() { + if (!session.address) return; + const values = validateEnableValues(); + if (!values) return; + + if (!isOwner && canProposeAuction) { + proposalDraft.requestAddBatch({ + daoId, + requests: buildEnableProposalRequests(values), + onAdded: () => setFormMessage('Auction setup and enable actions added to the proposal draft.') + }); + return; + } + + setBusy(true); + setFormMessage(''); + tx.start('Configuring auctions...'); + try { + const signTransaction = async (xdr: string, opts?: { networkPassphrase?: string; address?: string }) => + StellarWalletsKit.signTransaction(xdr, { + networkPassphrase: opts?.networkPassphrase ?? config.passphrase, + address: opts?.address ?? session.address + }); + const auctionClient = new AuctionClient({ + contractId: config.auctionContractId, + rpcUrl: config.rpcUrl, + networkPassphrase: config.passphrase, + publicKey: session.address, + signTransaction + }); + const submitAuctionUpdate = async (label: string, assembled: { signAndSend: () => Promise }) => { + const sent = await assembled.signAndSend(); + const hash = sent.sendTransactionResponse?.hash ?? ''; + tx.submitted(label, hash); + await waitForConfirmation(hash, config.rpcUrl); + return hash; + }; + + await submitAuctionUpdate( + 'Auction duration update submitted', + await auctionClient.set_duration({ duration: BigInt(values.duration) }) + ); + await submitAuctionUpdate( + 'Auction reserve price update submitted', + await auctionClient.set_reserve_price({ reserve_price: values.reservePrice }) + ); + await submitAuctionUpdate( + 'Auction time buffer update submitted', + await auctionClient.set_time_buffer({ time_buffer: BigInt(values.timeBuffer) }) + ); + await submitAuctionUpdate( + 'Auction payment token update submitted', + await auctionClient.set_payment_token({ payment_token: values.paymentToken }) + ); + + if (!auctionCanMint) { + const tokenClient = new TokenClient({ + contractId: config.tokenContractId, + rpcUrl: config.rpcUrl, + networkPassphrase: config.passphrase, + publicKey: session.address, + signTransaction + }); + await submitAuctionUpdate( + 'Auction mint authority update submitted', + await tokenClient.set_mint_authority({ authority: config.auctionContractId, enabled: true }) + ); + } + + const hash = await submitAuctionUpdate( + 'Auction enable submitted', + await auctionClient.unpause({ caller: session.address }) + ); + tx.success('Auctions enabled', hash); + await mutate(); + setEnableErrors({}); + } catch (enableError) { + tx.fail(enableError, 'Auction enable failed'); + } finally { + setBusy(false); + } + } async function updatePaused(nextPaused: boolean) { if (!session.address || (!isOwner && !canProposeAuction)) return; // Determine action type and messaging based on auction status const isLaunching = data?.status === 'not-launched' && !nextPaused; + const isEnabling = config.auctionEnabled === false && !nextPaused; const actionType = nextPaused ? 'pause-auction' : 'unpause-auction'; - const actionTitle = isLaunching ? 'Launch auctions' : nextPaused ? 'Pause auctions' : 'Resume auctions'; - const actionDescription = isLaunching - ? 'Launch auctions and create the first token.' - : nextPaused - ? 'Pause auction activity.' - : 'Resume auction activity.'; + const actionTitle = isEnabling + ? 'Enable auctions' + : isLaunching + ? 'Launch auctions' + : nextPaused + ? 'Pause auctions' + : 'Resume auctions'; + const actionDescription = isEnabling + ? 'Enable auction activity and create the first token.' + : isLaunching + ? 'Launch auctions and create the first token.' + : nextPaused + ? 'Pause auction activity.' + : 'Resume auction activity.'; if (!isOwner && canProposeAuction) { + if (!nextPaused && !auctionCanMint && !auctionMintAuthorityQueued) { + const mintAuthorityHandler = getActionHandler('set-mint-authority'); + const mintAuthorityAction = mintAuthorityHandler.serialize( + { authority: config.auctionContractId, enabled: true }, + { config, session: { address: session.address, kit: StellarWalletsKit } } + ); + const unpauseHandler = getActionHandler('unpause-auction'); + const unpauseAction = unpauseHandler.serialize( + {}, + { config, session: { address: session.address, kit: StellarWalletsKit } } + ); + + proposalDraft.requestAdd({ + daoId, + action: mintAuthorityAction, + source: 'admin/auction/set-mint-authority', + metadata: { + title: 'Enable auction mint authority', + description: `Allow ${config.auctionContractId} to mint auction tokens.`, + url: '' + }, + onAdded: () => { + proposalDraft.requestAdd({ + daoId, + action: unpauseAction, + source: 'admin/auction/unpause-auction', + metadata: { + title: isEnabling ? 'Enable auctions' : 'Resume auctions', + description: actionDescription, + url: '' + }, + onAdded: () => setFormMessage(`${isEnabling ? 'Enable' : 'Resume'} auctions added to the proposal draft.`) + }); + } + }); + return; + } + const handler = getActionHandler(actionType); const action = handler.serialize({}, { config, session: { address: session.address, kit: StellarWalletsKit } }); proposalDraft.requestAdd({ @@ -102,7 +408,15 @@ export default function AuctionAdminPage() { return; } setBusy(true); - tx.start(isLaunching ? 'Launching auctions...' : nextPaused ? 'Pausing auctions...' : 'Resuming auctions...'); + tx.start( + isEnabling + ? 'Enabling auctions...' + : isLaunching + ? 'Launching auctions...' + : nextPaused + ? 'Pausing auctions...' + : 'Resuming auctions...' + ); try { const client = new AuctionClient({ contractId: config.auctionContractId, @@ -122,10 +436,18 @@ export default function AuctionAdminPage() { const hash = sent.sendTransactionResponse?.hash ?? ''; const submittedMessage = nextPaused ? 'Auction pause submitted' - : isLaunching - ? 'Auction launch submitted' - : 'Auction resume submitted'; - const successMessage = nextPaused ? 'Auctions paused' : isLaunching ? 'Auctions launched' : 'Auctions resumed'; + : isEnabling + ? 'Auction enable submitted' + : isLaunching + ? 'Auction launch submitted' + : 'Auction resume submitted'; + const successMessage = nextPaused + ? 'Auctions paused' + : isEnabling + ? 'Auctions enabled' + : isLaunching + ? 'Auctions launched' + : 'Auctions resumed'; tx.submitted(submittedMessage, hash); await waitForConfirmation(hash, config.rpcUrl); tx.success(successMessage, hash); @@ -415,11 +737,13 @@ export default function AuctionAdminPage() { ) : ( - {data?.status === 'not-launched' - ? 'Auctions have not yet been launched. Click launch to create the first auction.' - : data?.paused - ? 'Bidding and automatic settlement are paused.' - : 'Auctions are active and accepting bids.'} + {config.auctionEnabled === false + ? 'Auctions are currently disabled. Configure the auction and enable it to create the first auction.' + : data?.status === 'not-launched' + ? 'Auctions have not yet been launched. Click launch to create the first auction.' + : data?.paused + ? 'Bidding and automatic settlement are paused.' + : 'Auctions are active and accepting bids.'} )} {data ? ( @@ -436,67 +760,89 @@ export default function AuctionAdminPage() { ) : null} ) : null} -
- {data?.status === 'not-launched' ? ( - - ) : ( - <> - + ) : ( + <> + + + + )} +
+ Auction payment token + + {data?.config.payment_token + ? `${getTreasuryAssets(config.name).find((asset) => asset.contractId === data.config.payment_token)?.code ?? 'Unknown SAC'} · ${data.config.payment_token}` + : 'Not configured'} + . Changes apply after the next auction is created. If auctions are active, they will be paused + automatically. + +
+ setPaymentToken(value.paymentToken)} + network={config.name} + disabled={busy} + draftPreview={draftStatus.actionsInDraft.find((a) => a.type === 'set-auction-payment-token')} + /> + +
+ Reserve price for the next auction + + Current reserve: {data ? formatStroops(data.config.reserve_price) : '—'}{' '} + {data?.config.payment_token + ? (getTreasuryAssets(config.name).find((asset) => asset.contractId === data.config.payment_token) + ?.code ?? 'SAC') + : 'SAC'}{' '} + units. Changes apply after the next auction is created. If auctions are active, they will be paused + automatically. + +
+ setReservePrice(value.reservePrice)} + disabled={busy} + draftPreview={draftStatus.actionsInDraft.find((a) => a.type === 'set-auction-reserve-price')} + /> - - )} -
- Auction payment token - - {data?.config.payment_token - ? `${getTreasuryAssets(config.name).find((asset) => asset.contractId === data.config.payment_token)?.code ?? 'Unknown SAC'} · ${data.config.payment_token}` - : 'Not configured'} - . Changes apply after the next auction is created. If auctions are active, they will be paused - automatically. - -
- setPaymentToken(value.paymentToken)} - network={config.name} - disabled={busy} - draftPreview={draftStatus.actionsInDraft.find((a) => a.type === 'set-auction-payment-token')} - /> - -
- Reserve price for the next auction - - Current reserve: {data ? formatStroops(data.config.reserve_price) : '—'}{' '} - {data?.config.payment_token - ? (getTreasuryAssets(config.name).find((asset) => asset.contractId === data.config.payment_token) - ?.code ?? 'SAC') - : 'SAC'}{' '} - units. Changes apply after the next auction is created. If auctions are active, they will be paused - automatically. - -
- setReservePrice(value.reservePrice)} - disabled={busy} - draftPreview={draftStatus.actionsInDraft.find((a) => a.type === 'set-auction-reserve-price')} - /> - -
+ + + )} diff --git a/apps/web/src/app/dao/[daoId]/admin/governance/page.tsx b/apps/web/src/app/dao/[daoId]/admin/governance/page.tsx index ec6c499..5d1d729 100644 --- a/apps/web/src/app/dao/[daoId]/admin/governance/page.tsx +++ b/apps/web/src/app/dao/[daoId]/admin/governance/page.tsx @@ -24,7 +24,7 @@ import { waitForConfirmation } from '@/lib/transaction-confirmation'; import { useTransactionFeedback } from '@/lib/transaction-feedback'; import { useAdminDraftStatus } from '@/lib/use-admin-draft-status'; import { useAdminProposalDraft } from '@/lib/use-admin-proposal-draft'; -import { useDaoSessionStore } from '@/stores/dao-session-store'; +import { useAuthSessionStore } from '@/stores/auth-session-store'; type Drafts = Partial<{ votingDelay: number; @@ -56,7 +56,7 @@ function formatSecondsValue(value: number | null | undefined) { export default function GovernanceAdminPage() { const { daoId, daoConfig: config } = useDaoContext(); - const session = useDaoSessionStore(); + const session = useAuthSessionStore(); const [drafts, setDrafts] = useState(EMPTY_DRAFTS); const [formMessage, setFormMessage] = useState(''); const [busy, setBusy] = useState(false); diff --git a/apps/web/src/app/dao/[daoId]/admin/owner/page.tsx b/apps/web/src/app/dao/[daoId]/admin/owner/page.tsx index fa8298e..4570400 100644 --- a/apps/web/src/app/dao/[daoId]/admin/owner/page.tsx +++ b/apps/web/src/app/dao/[daoId]/admin/owner/page.tsx @@ -21,7 +21,7 @@ import { waitForConfirmation } from '@/lib/transaction-confirmation'; import { useTransactionFeedback } from '@/lib/transaction-feedback'; import { useAdminDraftStatus } from '@/lib/use-admin-draft-status'; import { useAdminProposalDraft } from '@/lib/use-admin-proposal-draft'; -import { useDaoSessionStore } from '@/stores/dao-session-store'; +import { useAuthSessionStore } from '@/stores/auth-session-store'; async function submitAuthorityUpdate( config: DaoNetworkConfig, @@ -63,7 +63,7 @@ async function submitAuthorityUpdate( export default function OwnerPage() { const { daoId, daoConfig: config } = useDaoContext(); - const session = useDaoSessionStore(); + const session = useAuthSessionStore(); const [mintAuthority, setMintAuthority] = useState(''); const [governorAuthority, setGovernorAuthority] = useState(''); const [formMessage, setFormMessage] = useState(''); diff --git a/apps/web/src/app/dao/[daoId]/admin/page.tsx b/apps/web/src/app/dao/[daoId]/admin/page.tsx index 48e76eb..889da51 100644 --- a/apps/web/src/app/dao/[daoId]/admin/page.tsx +++ b/apps/web/src/app/dao/[daoId]/admin/page.tsx @@ -12,7 +12,7 @@ import { useDaoContext } from '@/contexts/dao-context'; import { treasuryHasAuthority, treasuryIsOwner } from '@/lib/admin-proposals'; import { useContractOwner } from '@/lib/admin-queries'; import { useGoldskyGovernorAuthorities, useGoldskyMintAuthorities } from '@/lib/goldsky-queries'; -import { useDaoSessionStore } from '@/stores/dao-session-store'; +import { useAuthSessionStore } from '@/stores/auth-session-store'; function SectionCard({ title, @@ -50,7 +50,7 @@ function SectionCard({ export default function AdminPage() { const { daoId, daoConfig: config } = useDaoContext(); - const session = useDaoSessionStore(); + const session = useAuthSessionStore(); const { data: mintAuthorities } = useGoldskyMintAuthorities(config.tokenContractId); const { data: governorAuthorities } = useGoldskyGovernorAuthorities(config.tokenContractId); const { data: tokenOwner } = useContractOwner(config, 'token', session.address || undefined); diff --git a/apps/web/src/app/dao/[daoId]/admin/token/page.tsx b/apps/web/src/app/dao/[daoId]/admin/token/page.tsx index 4aea530..d822eb8 100644 --- a/apps/web/src/app/dao/[daoId]/admin/token/page.tsx +++ b/apps/web/src/app/dao/[daoId]/admin/token/page.tsx @@ -21,11 +21,11 @@ import { waitForConfirmation } from '@/lib/transaction-confirmation'; import { useTransactionFeedback } from '@/lib/transaction-feedback'; import { useAdminDraftStatus } from '@/lib/use-admin-draft-status'; import { useAdminProposalDraft } from '@/lib/use-admin-proposal-draft'; -import { useDaoSessionStore } from '@/stores/dao-session-store'; +import { useAuthSessionStore } from '@/stores/auth-session-store'; export default function TokenAdminPage() { const { daoId, daoConfig: config } = useDaoContext(); - const session = useDaoSessionStore(); + const session = useAuthSessionStore(); const [recipient, setRecipient] = useState(''); const [amount, setAmount] = useState('1'); const [formMessage, setFormMessage] = useState(''); diff --git a/apps/web/src/app/dao/[daoId]/auctions/page.tsx b/apps/web/src/app/dao/[daoId]/auctions/page.tsx index 566dfd9..fe65106 100644 --- a/apps/web/src/app/dao/[daoId]/auctions/page.tsx +++ b/apps/web/src/app/dao/[daoId]/auctions/page.tsx @@ -15,7 +15,7 @@ import { useDaoContext } from '@/contexts/dao-context'; import { getTreasuryAssets } from '@/lib/assets-config'; import { waitForConfirmation } from '@/lib/transaction-confirmation'; import { useTransactionFeedback } from '@/lib/transaction-feedback'; -import { useDaoSessionStore } from '@/stores/dao-session-store'; +import { useAuthSessionStore } from '@/stores/auth-session-store'; type AuctionData = { auction: { @@ -82,7 +82,7 @@ function formatRemaining(endTime: string, now: number) { export default function AuctionsPage() { const { daoId, daoConfig: config } = useDaoContext(); - const session = useDaoSessionStore(); + const session = useAuthSessionStore(); const tx = useTransactionFeedback(config.name); const { data, error, isLoading, mutate } = useSWR( `/api/dao/${encodeURIComponent(daoId)}/auctions`, diff --git a/apps/web/src/app/dao/[daoId]/page.tsx b/apps/web/src/app/dao/[daoId]/page.tsx index f6007f5..050232d 100644 --- a/apps/web/src/app/dao/[daoId]/page.tsx +++ b/apps/web/src/app/dao/[daoId]/page.tsx @@ -1,18 +1,20 @@ 'use client'; -import { Activity, ChevronDown, MoreHorizontal, RefreshCw } from 'lucide-react'; +import { Activity, ChevronDown, RefreshCw } from 'lucide-react'; import Image from 'next/image'; import Link from 'next/link'; import { useEffect, useState } from 'react'; import { Stack } from 'styled-system/jsx'; import useSWR from 'swr'; +import { DaoContractList } from '@/components/dao-contract-list'; import { PageSection } from '@/components/page-section'; import { ProposalStateBadge } from '@/components/proposal/proposal-state-badge'; import type { ProposalListResponse } from '@/components/proposal/types'; import { TokenCard } from '@/components/token/token-card'; -import { Button, Callout, Card, Heading, ShortId, Skeleton, Text } from '@/components/ui'; +import { Button, Callout, Card, Heading, Skeleton, Text } from '@/components/ui'; import { useDaoContext } from '@/contexts/dao-context'; +import { formatActivitySummary } from '@/lib/activity-feed'; import { useGoldskyActivityFeed, useGoldskyHealth } from '@/lib/goldsky-queries'; import { useTokenInventory } from '@/lib/token-queries'; @@ -191,68 +193,25 @@ export default function Page() { return (
- {isDashboardRefreshing ? ( - - Syncing… - - ) : null}
Contracts
Contracts -
- {config.tokenContractId ? ( - - ) : ( - Token: Missing - )} - {config.governorContractId ? ( - - ) : ( - Governor: Missing - )} - {config.treasuryContractId ? ( - - ) : ( - Treasury: Missing - )} - {config.auctionContractId ? ( - - ) : ( - Auction: Missing - )} - {config.metadataContractId ? ( - - ) : ( - Metadata: Missing - )} -
-
-
-
- - -
- Dashboard options - +
+
@@ -470,7 +429,7 @@ export default function Page() {
{item.title} - {item.summary} + {formatActivitySummary(item)}
diff --git a/apps/web/src/app/dao/[daoId]/proposals/[proposalId]/page.tsx b/apps/web/src/app/dao/[daoId]/proposals/[proposalId]/page.tsx index 838e676..5cc20ac 100644 --- a/apps/web/src/app/dao/[daoId]/proposals/[proposalId]/page.tsx +++ b/apps/web/src/app/dao/[daoId]/proposals/[proposalId]/page.tsx @@ -26,7 +26,7 @@ import { proposalActionMode } from '@/lib/proposal-state'; import { waitForConfirmation } from '@/lib/transaction-confirmation'; import { useTransactionFeedback } from '@/lib/transaction-feedback'; import { useVotingPower } from '@/lib/voting-power'; -import { useDaoSessionStore } from '@/stores/dao-session-store'; +import { useAuthSessionStore } from '@/stores/auth-session-store'; type ProposalPageData = { detail: ProposalDetail; @@ -85,7 +85,7 @@ export default function ProposalDetailPage() { const { daoId, daoConfig: config } = useDaoContext(); const params = useParams<{ proposalId: string }>(); const proposalId = params.proposalId; - const session = useDaoSessionStore(); + const session = useAuthSessionStore(); const [voteReason, setVoteReason] = useState(''); const [selectedVoteType, setSelectedVoteType] = useState(null); const [formMessage, setFormMessage] = useState(''); diff --git a/apps/web/src/app/dao/[daoId]/proposals/create/page.tsx b/apps/web/src/app/dao/[daoId]/proposals/create/page.tsx index dd96d7f..0353172 100644 --- a/apps/web/src/app/dao/[daoId]/proposals/create/page.tsx +++ b/apps/web/src/app/dao/[daoId]/proposals/create/page.tsx @@ -8,7 +8,6 @@ import { useRouter } from 'next/navigation'; import { useCallback, useRef, useState } from 'react'; import { Grid, Stack } from 'styled-system/jsx'; -import { PageSection } from '@/components/page-section'; import { ProposalActionConfirmDialog } from '@/components/proposal/proposal-action-confirm-dialog'; import { ProposalContextRail } from '@/components/proposal/proposal-context-rail'; import { Badge, Button, Callout, Card, Heading, Input, Skeleton, Text, Textarea } from '@/components/ui'; @@ -22,7 +21,7 @@ import { proposalIdToRouteId } from '@/lib/proposal-id'; import { encodeProposalMetadata, validateProposalMetadataDraft } from '@/lib/proposal-metadata'; import { waitForConfirmation } from '@/lib/transaction-confirmation'; import { useTransactionFeedback } from '@/lib/transaction-feedback'; -import { useDaoSessionStore } from '@/stores/dao-session-store'; +import { useAuthSessionStore } from '@/stores/auth-session-store'; import { selectCanProceedToStep2, selectDraft, @@ -33,7 +32,7 @@ import { export default function ProposalCreatePage() { const { daoId } = useDaoContext(); const router = useRouter(); - const session = useDaoSessionStore(); + const session = useAuthSessionStore(); const { daoConfig: config } = useDaoContext(); // Zustand store hooks @@ -150,266 +149,272 @@ export default function ProposalCreatePage() { return ( <> - +
- - {session.address && eligibility.loading ? ( -
- Checking proposal eligibility - - - - - - -
- ) : proposalCreationError && !hasDraft ? ( - - ) : proposalCreationLocked && !hasDraft ? ( - - ) : ( - <> - {proposalCreationError ? ( - - ) : null} - {proposalCreationLocked && !proposalCreationError ? ( - - ) : null} - {/* Wizard Steps */} -
- 1. Details -