feat: add canonical currency, alt currencies and accepted resolvers to create listing form
This commit is contained in:
@@ -7,34 +7,112 @@ import { isSome } from '@solana/kit'
|
|||||||
import { useTx } from '@/hooks/useTx'
|
import { useTx } from '@/hooks/useTx'
|
||||||
import { useListing } from '@/hooks/useListing'
|
import { useListing } from '@/hooks/useListing'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { useToast } from '@/components/ui/Toast'
|
|
||||||
import {
|
import {
|
||||||
getCreateListingInstructionAsync,
|
getCreateListingInstructionAsync,
|
||||||
getUpdateListingInstruction,
|
getUpdateListingInstruction,
|
||||||
} from '@solisting/sdk'
|
} from '@solisting/sdk'
|
||||||
import type { Address } from '@solisting/sdk'
|
import type { Address, SolistingStateAltCurrencyConfigArgs } from '@solisting/sdk'
|
||||||
|
|
||||||
|
interface AltEntry {
|
||||||
|
currency: string
|
||||||
|
oracle: string
|
||||||
|
decimals: string
|
||||||
|
}
|
||||||
|
|
||||||
interface FormState {
|
interface FormState {
|
||||||
|
canonicalKind: 'Sol' | 'Spl'
|
||||||
|
mint: string
|
||||||
|
decimals: string
|
||||||
price: string
|
price: string
|
||||||
quantity: string
|
quantity: string
|
||||||
oracle: string
|
oracle: string
|
||||||
metadataUri: string
|
metadataUri: string
|
||||||
|
alts: AltEntry[]
|
||||||
|
resolvers: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
function blank(): FormState {
|
function blank(): FormState {
|
||||||
return { price: '', quantity: '', oracle: '', metadataUri: '' }
|
return {
|
||||||
|
canonicalKind: 'Sol',
|
||||||
|
mint: '',
|
||||||
|
decimals: '6',
|
||||||
|
price: '',
|
||||||
|
quantity: '',
|
||||||
|
oracle: '',
|
||||||
|
metadataUri: '',
|
||||||
|
alts: [],
|
||||||
|
resolvers: [],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
editPk?: Address
|
editPk?: Address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LABEL: React.CSSProperties = {
|
||||||
|
display: 'block',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: 'var(--mut)',
|
||||||
|
marginBottom: 8,
|
||||||
|
letterSpacing: '.04em',
|
||||||
|
}
|
||||||
|
const INPUT: React.CSSProperties = {
|
||||||
|
width: '100%',
|
||||||
|
height: 42,
|
||||||
|
padding: '0 13px',
|
||||||
|
background: 'var(--bg3)',
|
||||||
|
border: '1px solid var(--bd)',
|
||||||
|
borderRadius: 10,
|
||||||
|
fontSize: 13,
|
||||||
|
outline: 'none',
|
||||||
|
color: 'inherit',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
}
|
||||||
|
const INPUT_MONO: React.CSSProperties = { ...INPUT, fontFamily: 'var(--font-mono)' }
|
||||||
|
const SMALL_INPUT: React.CSSProperties = {
|
||||||
|
...INPUT,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 9,
|
||||||
|
fontSize: 12.5,
|
||||||
|
}
|
||||||
|
const SMALL_MONO: React.CSSProperties = { ...SMALL_INPUT, fontFamily: 'var(--font-mono)' }
|
||||||
|
|
||||||
|
function CurrencyToggle({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
value: 'Sol' | 'Spl'
|
||||||
|
onChange: (v: 'Sol' | 'Spl') => void
|
||||||
|
}) {
|
||||||
|
const active = (on: boolean): React.CSSProperties => ({
|
||||||
|
flex: 1,
|
||||||
|
padding: 11,
|
||||||
|
borderRadius: 10,
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 600,
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: on ? '#0b0613' : 'var(--mut)',
|
||||||
|
background: on ? 'linear-gradient(135deg,var(--acc),var(--acc2))' : 'var(--bg3)',
|
||||||
|
border: `1px solid ${on ? 'transparent' : 'var(--bd)'}`,
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', gap: 8 }}>
|
||||||
|
<button type="button" onClick={() => onChange('Sol')} style={active(value === 'Sol')}>
|
||||||
|
SOL
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => onChange('Spl')} style={active(value === 'Spl')}>
|
||||||
|
SPL Token
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function CreateListingForm({ editPk }: Props) {
|
export function CreateListingForm({ editPk }: Props) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { signer } = useKitTransactionSigner()
|
const { signer } = useKitTransactionSigner()
|
||||||
const { account } = useWallet()
|
const { account } = useWallet()
|
||||||
const sendTx = useTx()
|
const sendTx = useTx()
|
||||||
const toast = useToast()
|
|
||||||
|
|
||||||
const { data: existing } = useListing(editPk ?? ('' as Address))
|
const { data: existing } = useListing(editPk ?? ('' as Address))
|
||||||
const [form, setForm] = useState<FormState>(blank())
|
const [form, setForm] = useState<FormState>(blank())
|
||||||
@@ -44,22 +122,119 @@ export function CreateListingForm({ editPk }: Props) {
|
|||||||
|
|
||||||
const isUpdate = !!editPk
|
const isUpdate = !!editPk
|
||||||
|
|
||||||
|
// Pre-fill from on-chain data on first load
|
||||||
if (!initialized && existing && isUpdate) {
|
if (!initialized && existing && isUpdate) {
|
||||||
setInitialized(true)
|
setInitialized(true)
|
||||||
const d = existing.data
|
const d = existing.data
|
||||||
|
const cur = d.canonicalCurrency
|
||||||
|
const isSpl = cur.__kind === 'Spl'
|
||||||
setForm({
|
setForm({
|
||||||
price: (Number(d.price) / 1e9).toString(),
|
canonicalKind: cur.__kind,
|
||||||
|
mint: isSpl ? (cur as { __kind: 'Spl'; mint: Address; decimals: number }).mint : '',
|
||||||
|
decimals: isSpl
|
||||||
|
? String((cur as { __kind: 'Spl'; mint: Address; decimals: number }).decimals)
|
||||||
|
: '6',
|
||||||
|
price: isSpl
|
||||||
|
? String(
|
||||||
|
Number(d.price) /
|
||||||
|
Math.pow(
|
||||||
|
10,
|
||||||
|
(cur as { __kind: 'Spl'; mint: Address; decimals: number }).decimals,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: (Number(d.price) / 1e9).toString(),
|
||||||
quantity: String(d.quantity),
|
quantity: String(d.quantity),
|
||||||
oracle: isSome(d.canonicalOracle) ? d.canonicalOracle.value : '',
|
oracle: isSome(d.canonicalOracle) ? d.canonicalOracle.value : '',
|
||||||
metadataUri: d.metadataUri ?? '',
|
metadataUri: d.metadataUri ?? '',
|
||||||
|
alts: d.altCurrencies.map((a) => ({
|
||||||
|
currency:
|
||||||
|
a.currency.__kind === 'Sol'
|
||||||
|
? 'SOL'
|
||||||
|
: (a.currency as { __kind: 'Spl'; mint: Address; decimals: number }).mint,
|
||||||
|
oracle: isSome(a.usdOracle) ? a.usdOracle.value : '',
|
||||||
|
decimals:
|
||||||
|
a.currency.__kind === 'Spl'
|
||||||
|
? String(
|
||||||
|
(a.currency as { __kind: 'Spl'; mint: Address; decimals: number }).decimals,
|
||||||
|
)
|
||||||
|
: '6',
|
||||||
|
})),
|
||||||
|
resolvers: [...d.acceptedResolvers],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function set(key: keyof FormState) {
|
function set<K extends keyof FormState>(key: K) {
|
||||||
return (e: React.ChangeEvent<HTMLInputElement>) =>
|
return (e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
setForm((f) => ({ ...f, [key]: e.target.value }))
|
setForm((f) => ({ ...f, [key]: e.target.value }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alt currency helpers
|
||||||
|
function addAlt() {
|
||||||
|
if (form.alts.length >= 3) return
|
||||||
|
setForm((f) => ({ ...f, alts: [...f.alts, { currency: '', oracle: '', decimals: '6' }] }))
|
||||||
|
}
|
||||||
|
function removeAlt(i: number) {
|
||||||
|
setForm((f) => ({ ...f, alts: f.alts.filter((_, idx) => idx !== i) }))
|
||||||
|
}
|
||||||
|
function setAlt(i: number, key: keyof AltEntry, val: string) {
|
||||||
|
setForm((f) => {
|
||||||
|
const alts = f.alts.map((a, idx) => (idx === i ? { ...a, [key]: val } : a))
|
||||||
|
return { ...f, alts }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolver helpers
|
||||||
|
function addResolver() {
|
||||||
|
if (form.resolvers.length >= 4) return
|
||||||
|
setForm((f) => ({ ...f, resolvers: [...f.resolvers, ''] }))
|
||||||
|
}
|
||||||
|
function removeResolver(i: number) {
|
||||||
|
setForm((f) => ({ ...f, resolvers: f.resolvers.filter((_, idx) => idx !== i) }))
|
||||||
|
}
|
||||||
|
function setResolver(i: number, val: string) {
|
||||||
|
setForm((f) => {
|
||||||
|
const resolvers = f.resolvers.map((r, idx) => (idx === i ? val : r))
|
||||||
|
return { ...f, resolvers }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCurrency() {
|
||||||
|
if (form.canonicalKind === 'Sol') return { __kind: 'Sol' as const }
|
||||||
|
const dec = parseInt(form.decimals, 10)
|
||||||
|
return { __kind: 'Spl' as const, mint: form.mint as Address, decimals: isNaN(dec) ? 6 : dec }
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPrice() {
|
||||||
|
const n = parseFloat(form.price)
|
||||||
|
if (isNaN(n) || n <= 0) throw new Error('Invalid price')
|
||||||
|
if (form.canonicalKind === 'Sol') return BigInt(Math.round(n * 1e9))
|
||||||
|
const dec = parseInt(form.decimals, 10)
|
||||||
|
return BigInt(Math.round(n * Math.pow(10, isNaN(dec) ? 6 : dec)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAlts(): SolistingStateAltCurrencyConfigArgs[] {
|
||||||
|
return form.alts
|
||||||
|
.filter((a) => a.currency.trim())
|
||||||
|
.map((a) => {
|
||||||
|
const isSol = a.currency.trim().toUpperCase() === 'SOL'
|
||||||
|
const dec = parseInt(a.decimals, 10)
|
||||||
|
return {
|
||||||
|
currency: isSol
|
||||||
|
? { __kind: 'Sol' as const }
|
||||||
|
: {
|
||||||
|
__kind: 'Spl' as const,
|
||||||
|
mint: a.currency.trim() as Address,
|
||||||
|
decimals: isNaN(dec) ? 6 : dec,
|
||||||
|
},
|
||||||
|
usdOracle: a.oracle.trim() ? (a.oracle.trim() as Address) : null,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildResolvers(): Address[] {
|
||||||
|
return form.resolvers.filter((r) => r.trim().length > 30).map((r) => r.trim() as Address)
|
||||||
|
}
|
||||||
|
|
||||||
async function submit(e: React.FormEvent) {
|
async function submit(e: React.FormEvent) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!signer || !account) {
|
if (!signer || !account) {
|
||||||
@@ -69,41 +244,36 @@ export function CreateListingForm({ editPk }: Props) {
|
|||||||
setError(null)
|
setError(null)
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const priceN = parseFloat(form.price)
|
const price = buildPrice()
|
||||||
if (isNaN(priceN) || priceN <= 0) throw new Error('Invalid price')
|
|
||||||
const priceLamports = BigInt(Math.round(priceN * 1e9))
|
|
||||||
const quantityN = parseInt(form.quantity, 10)
|
const quantityN = parseInt(form.quantity, 10)
|
||||||
if (isNaN(quantityN) || quantityN <= 0) throw new Error('Invalid quantity')
|
if (isNaN(quantityN) || quantityN <= 0) throw new Error('Invalid quantity')
|
||||||
const canonicalOracle = form.oracle ? (form.oracle as Address) : null
|
const canonicalOracle = form.oracle.trim() ? (form.oracle.trim() as Address) : null
|
||||||
|
const canonicalCurrency = buildCurrency()
|
||||||
|
const altCurrencies = buildAlts()
|
||||||
|
const acceptedResolvers = buildResolvers()
|
||||||
|
|
||||||
if (isUpdate && editPk) {
|
if (isUpdate && editPk) {
|
||||||
const existingData = existing?.data
|
|
||||||
const ix = getUpdateListingInstruction({
|
const ix = getUpdateListingInstruction({
|
||||||
seller: signer,
|
seller: signer,
|
||||||
listingAccount: editPk,
|
listingAccount: editPk,
|
||||||
canonicalCurrency: existingData?.canonicalCurrency ?? { __kind: 'Sol' },
|
canonicalCurrency,
|
||||||
price: priceLamports,
|
price,
|
||||||
canonicalOracle,
|
canonicalOracle,
|
||||||
altCurrencies: existingData?.altCurrencies ?? [],
|
altCurrencies,
|
||||||
acceptedResolvers: existingData?.acceptedResolvers ?? [],
|
acceptedResolvers,
|
||||||
quantity: quantityN,
|
quantity: quantityN,
|
||||||
metadataUri: form.metadataUri,
|
metadataUri: form.metadataUri,
|
||||||
})
|
})
|
||||||
await sendTx(
|
await sendTx([ix as never], [['listings'], ['listing', editPk]], 'update_listing')
|
||||||
[ix as never],
|
|
||||||
[['listings'], ['listing', editPk]],
|
|
||||||
'update_listing',
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
const listingId = BigInt(Date.now())
|
|
||||||
const ix = await getCreateListingInstructionAsync({
|
const ix = await getCreateListingInstructionAsync({
|
||||||
seller: signer,
|
seller: signer,
|
||||||
listingId,
|
listingId: BigInt(Date.now()),
|
||||||
canonicalCurrency: { __kind: 'Sol' },
|
canonicalCurrency,
|
||||||
price: priceLamports,
|
price,
|
||||||
canonicalOracle,
|
canonicalOracle,
|
||||||
altCurrencies: [],
|
altCurrencies,
|
||||||
acceptedResolvers: [],
|
acceptedResolvers,
|
||||||
quantity: quantityN,
|
quantity: quantityN,
|
||||||
metadataUri: form.metadataUri,
|
metadataUri: form.metadataUri,
|
||||||
})
|
})
|
||||||
@@ -117,26 +287,12 @@ export function CreateListingForm({ editPk }: Props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const LABEL_STYLE = {
|
const priceUnit =
|
||||||
display: 'block',
|
form.canonicalKind === 'Sol'
|
||||||
fontSize: 12,
|
? 'SOL'
|
||||||
fontWeight: 600,
|
: form.mint
|
||||||
color: 'var(--mut)',
|
? form.mint.slice(0, 6) + '…'
|
||||||
marginBottom: 8,
|
: 'SPL'
|
||||||
letterSpacing: '.04em',
|
|
||||||
} as const
|
|
||||||
const INPUT_STYLE = {
|
|
||||||
width: '100%',
|
|
||||||
height: 42,
|
|
||||||
padding: '0 13px',
|
|
||||||
background: 'var(--bg3)',
|
|
||||||
border: '1px solid var(--bd)',
|
|
||||||
borderRadius: 10,
|
|
||||||
fontSize: 13,
|
|
||||||
outline: 'none',
|
|
||||||
color: 'inherit',
|
|
||||||
boxSizing: 'border-box' as const,
|
|
||||||
} as const
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ maxWidth: 640, margin: '0 auto' }}>
|
<div style={{ maxWidth: 640, margin: '0 auto' }}>
|
||||||
@@ -160,10 +316,10 @@ export function CreateListingForm({ editPk }: Props) {
|
|||||||
<h1 style={{ margin: 0, fontSize: 26, fontWeight: 700, letterSpacing: '-.02em' }}>
|
<h1 style={{ margin: 0, fontSize: 26, fontWeight: 700, letterSpacing: '-.02em' }}>
|
||||||
{isUpdate ? 'Update Listing' : 'Create Listing'}
|
{isUpdate ? 'Update Listing' : 'Create Listing'}
|
||||||
</h1>
|
</h1>
|
||||||
<p style={{ margin: '6px 0 26px', fontSize: 13, color: 'var(--mut)' }}>
|
<p style={{ margin: '6px 0 26px', fontSize: 13, fontFamily: 'var(--font-mono)', color: 'var(--mut)' }}>
|
||||||
{isUpdate
|
{isUpdate
|
||||||
? `Editing listing ${editPk?.slice(0, 8)}…`
|
? `Editing listing ${editPk?.slice(0, 8)}…`
|
||||||
: 'New SOL-priced listing on the solisting program'}
|
: 'New listing on the solisting program'}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
@@ -178,19 +334,51 @@ export function CreateListingForm({ editPk }: Props) {
|
|||||||
gap: 22,
|
gap: 22,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* CANONICAL CURRENCY */}
|
||||||
|
<div>
|
||||||
|
<label style={LABEL}>CANONICAL CURRENCY</label>
|
||||||
|
<CurrencyToggle
|
||||||
|
value={form.canonicalKind}
|
||||||
|
onChange={(v) => setForm((f) => ({ ...f, canonicalKind: v }))}
|
||||||
|
/>
|
||||||
|
{form.canonicalKind === 'Spl' && (
|
||||||
|
<div style={{ display: 'flex', gap: 10, marginTop: 12 }}>
|
||||||
|
<input
|
||||||
|
value={form.mint}
|
||||||
|
onChange={set('mint')}
|
||||||
|
placeholder="Token mint address"
|
||||||
|
style={{ ...INPUT_MONO, flex: 2 }}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
value={form.decimals}
|
||||||
|
onChange={set('decimals')}
|
||||||
|
placeholder="decimals"
|
||||||
|
style={{ ...INPUT, flex: 1 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* PRICE + QUANTITY */}
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
|
||||||
<div>
|
<div>
|
||||||
<label style={LABEL_STYLE}>PRICE (SOL)</label>
|
<label style={LABEL}>PRICE ({priceUnit})</label>
|
||||||
<input value={form.price} onChange={set('price')} placeholder="0.00" style={INPUT_STYLE} />
|
<input value={form.price} onChange={set('price')} placeholder="0.00" style={INPUT} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={LABEL_STYLE}>QUANTITY</label>
|
<label style={LABEL}>QUANTITY</label>
|
||||||
<input value={form.quantity} onChange={set('quantity')} placeholder="0" style={INPUT_STYLE} />
|
<input
|
||||||
|
value={form.quantity}
|
||||||
|
onChange={set('quantity')}
|
||||||
|
placeholder="0"
|
||||||
|
style={INPUT}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* CANONICAL ORACLE */}
|
||||||
<div>
|
<div>
|
||||||
<label style={LABEL_STYLE}>
|
<label style={LABEL}>
|
||||||
CANONICAL ORACLE{' '}
|
CANONICAL ORACLE{' '}
|
||||||
<span style={{ fontWeight: 400, textTransform: 'none' }}>(optional · Pyth V2 feed)</span>
|
<span style={{ fontWeight: 400, textTransform: 'none' }}>(optional · Pyth V2 feed)</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -198,12 +386,119 @@ export function CreateListingForm({ editPk }: Props) {
|
|||||||
value={form.oracle}
|
value={form.oracle}
|
||||||
onChange={set('oracle')}
|
onChange={set('oracle')}
|
||||||
placeholder="Pyth price feed pubkey — leave empty for $1.00 stablecoin"
|
placeholder="Pyth price feed pubkey — leave empty for $1.00 stablecoin"
|
||||||
style={INPUT_STYLE}
|
style={INPUT_MONO}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* ALT CURRENCIES */}
|
||||||
<div>
|
<div>
|
||||||
<label style={LABEL_STYLE}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
|
||||||
|
<label style={{ ...LABEL, marginBottom: 0 }}>
|
||||||
|
ALT CURRENCIES{' '}
|
||||||
|
<span style={{ fontWeight: 400, textTransform: 'none' }}>(max 3)</span>
|
||||||
|
</label>
|
||||||
|
{form.alts.length < 3 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={addAlt}
|
||||||
|
style={{ fontSize: 12, fontWeight: 600, color: 'var(--acc2light)', background: 'none', border: 'none', cursor: 'pointer' }}
|
||||||
|
>
|
||||||
|
+ Add
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{form.alts.map((alt, i) => (
|
||||||
|
<div key={i} style={{ display: 'flex', gap: 8, marginBottom: 8, alignItems: 'center' }}>
|
||||||
|
<input
|
||||||
|
value={alt.currency}
|
||||||
|
onChange={(e) => setAlt(i, 'currency', e.target.value)}
|
||||||
|
placeholder="SOL or mint"
|
||||||
|
style={{ ...SMALL_MONO, flex: 1 }}
|
||||||
|
/>
|
||||||
|
{alt.currency.trim() && alt.currency.trim().toUpperCase() !== 'SOL' && (
|
||||||
|
<input
|
||||||
|
value={alt.decimals}
|
||||||
|
onChange={(e) => setAlt(i, 'decimals', e.target.value)}
|
||||||
|
placeholder="dec"
|
||||||
|
style={{ ...SMALL_INPUT, width: 58, flex: 'none' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<input
|
||||||
|
value={alt.oracle}
|
||||||
|
onChange={(e) => setAlt(i, 'oracle', e.target.value)}
|
||||||
|
placeholder="USD oracle (optional)"
|
||||||
|
style={{ ...SMALL_MONO, flex: 2 }}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => removeAlt(i)}
|
||||||
|
style={{
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 9,
|
||||||
|
background: 'var(--bg3)',
|
||||||
|
border: '1px solid var(--bd)',
|
||||||
|
color: 'var(--danger)',
|
||||||
|
fontSize: 18,
|
||||||
|
flexShrink: 0,
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ACCEPTED RESOLVERS */}
|
||||||
|
<div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
|
||||||
|
<label style={{ ...LABEL, marginBottom: 0 }}>
|
||||||
|
ACCEPTED RESOLVERS{' '}
|
||||||
|
<span style={{ fontWeight: 400, textTransform: 'none' }}>(max 4 · empty = any)</span>
|
||||||
|
</label>
|
||||||
|
{form.resolvers.length < 4 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={addResolver}
|
||||||
|
style={{ fontSize: 12, fontWeight: 600, color: 'var(--acc2light)', background: 'none', border: 'none', cursor: 'pointer' }}
|
||||||
|
>
|
||||||
|
+ Add
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{form.resolvers.map((rv, i) => (
|
||||||
|
<div key={i} style={{ display: 'flex', gap: 8, marginBottom: 8, alignItems: 'center' }}>
|
||||||
|
<input
|
||||||
|
value={rv}
|
||||||
|
onChange={(e) => setResolver(i, e.target.value)}
|
||||||
|
placeholder="Resolver pubkey"
|
||||||
|
style={{ ...SMALL_MONO, flex: 1 }}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => removeResolver(i)}
|
||||||
|
style={{
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 9,
|
||||||
|
background: 'var(--bg3)',
|
||||||
|
border: '1px solid var(--bd)',
|
||||||
|
color: 'var(--danger)',
|
||||||
|
fontSize: 18,
|
||||||
|
flexShrink: 0,
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* METADATA URI */}
|
||||||
|
<div>
|
||||||
|
<label style={LABEL}>
|
||||||
METADATA URI{' '}
|
METADATA URI{' '}
|
||||||
<span style={{ fontWeight: 400, textTransform: 'none' }}>(max 256 chars)</span>
|
<span style={{ fontWeight: 400, textTransform: 'none' }}>(max 256 chars)</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -211,7 +506,7 @@ export function CreateListingForm({ editPk }: Props) {
|
|||||||
value={form.metadataUri}
|
value={form.metadataUri}
|
||||||
onChange={set('metadataUri')}
|
onChange={set('metadataUri')}
|
||||||
placeholder="ipfs://… or https://…"
|
placeholder="ipfs://… or https://…"
|
||||||
style={INPUT_STYLE}
|
style={INPUT_MONO}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -229,14 +524,7 @@ export function CreateListingForm({ editPk }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div style={{ display: 'flex', gap: 10, borderTop: '1px solid var(--bdSoft)', paddingTop: 20 }}>
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
gap: 10,
|
|
||||||
borderTop: '1px solid var(--bdSoft)',
|
|
||||||
paddingTop: 20,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button type="submit" disabled={loading} style={{ flex: 1, padding: 13, fontSize: 14 }}>
|
<Button type="submit" disabled={loading} style={{ flex: 1, padding: 13, fontSize: 14 }}>
|
||||||
{loading ? 'Sending…' : isUpdate ? 'Update Listing' : 'Create Listing'}
|
{loading ? 'Sending…' : isUpdate ? 'Update Listing' : 'Create Listing'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user