Instant Sale Create Flow
This commit is contained in:
parent
0b8feaed9a
commit
f684a0490c
|
@ -2,11 +2,6 @@ import {
|
|||
findProgramAddress,
|
||||
getAuctionExtended,
|
||||
programIds,
|
||||
VAULT_PREFIX,
|
||||
} from '@oyster/common';
|
||||
import {
|
||||
findProgramAddress,
|
||||
programIds,
|
||||
StringPublicKey,
|
||||
toPublicKey,
|
||||
VAULT_PREFIX,
|
||||
|
|
|
@ -36,7 +36,6 @@ import {
|
|||
import { Connection, LAMPORTS_PER_SOL } from '@solana/web3.js';
|
||||
import { MintLayout } from '@solana/spl-token';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { capitalize } from 'lodash';
|
||||
import { WinningConfigType, AmountRange } from '../../models/metaplex';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
|
@ -57,6 +56,7 @@ const { Step } = Steps;
|
|||
const { ZERO } = constants;
|
||||
|
||||
export enum AuctionCategory {
|
||||
InstantSale,
|
||||
Limited,
|
||||
Single,
|
||||
Open,
|
||||
|
@ -98,7 +98,6 @@ export interface AuctionState {
|
|||
|
||||
//////////////////
|
||||
category: AuctionCategory;
|
||||
saleType?: 'auction' | 'sale';
|
||||
|
||||
price?: number;
|
||||
priceFloor?: number;
|
||||
|
@ -133,7 +132,8 @@ export const AuctionCreateView = () => {
|
|||
|
||||
const [step, setStep] = useState<number>(0);
|
||||
const [stepsVisible, setStepsVisible] = useState<boolean>(true);
|
||||
const [auctionObj, setAuctionObj] = useState<
|
||||
const [auctionObj, setAuctionObj] =
|
||||
useState<
|
||||
| {
|
||||
vault: StringPublicKey;
|
||||
auction: StringPublicKey;
|
||||
|
@ -145,7 +145,6 @@ export const AuctionCreateView = () => {
|
|||
reservationPrice: 0,
|
||||
items: [],
|
||||
category: AuctionCategory.Open,
|
||||
saleType: 'auction',
|
||||
auctionDurationType: 'minutes',
|
||||
gapTimeType: 'minutes',
|
||||
winnersCount: 1,
|
||||
|
@ -445,6 +444,14 @@ export const AuctionCreateView = () => {
|
|||
/>
|
||||
);
|
||||
|
||||
const instantSaleStep = (
|
||||
<InstantSaleStep
|
||||
attributes={attributes}
|
||||
setAttributes={setAttributes}
|
||||
confirm={() => gotoNextStep()}
|
||||
/>
|
||||
);
|
||||
|
||||
const copiesStep = (
|
||||
<CopiesStep
|
||||
attributes={attributes}
|
||||
|
@ -461,16 +468,8 @@ export const AuctionCreateView = () => {
|
|||
/>
|
||||
);
|
||||
|
||||
const typeStep = (
|
||||
<SaleTypeStep
|
||||
attributes={attributes}
|
||||
setAttributes={setAttributes}
|
||||
confirm={() => gotoNextStep()}
|
||||
/>
|
||||
);
|
||||
|
||||
const priceStep = (
|
||||
<PriceStep
|
||||
const priceAuction = (
|
||||
<PriceAuction
|
||||
attributes={attributes}
|
||||
setAttributes={setAttributes}
|
||||
confirm={() => gotoNextStep()}
|
||||
|
@ -486,7 +485,7 @@ export const AuctionCreateView = () => {
|
|||
);
|
||||
|
||||
const endingStep = (
|
||||
<EndingPhaseStep
|
||||
<EndingPhaseAuction
|
||||
attributes={attributes}
|
||||
setAttributes={setAttributes}
|
||||
confirm={() => gotoNextStep()}
|
||||
|
@ -529,11 +528,17 @@ export const AuctionCreateView = () => {
|
|||
const congratsStep = <Congrats auction={auctionObj} />;
|
||||
|
||||
const stepsByCategory = {
|
||||
[AuctionCategory.InstantSale]: [
|
||||
['Category', categoryStep],
|
||||
['Instant Sale', instantSaleStep],
|
||||
['Review', reviewStep], // Update for clarity
|
||||
['Publish', waitStep], // Just the loading screen
|
||||
[undefined, congratsStep],
|
||||
],
|
||||
[AuctionCategory.Limited]: [
|
||||
['Category', categoryStep],
|
||||
['Copies', copiesStep],
|
||||
['Sale Type', typeStep],
|
||||
['Price', priceStep],
|
||||
['Price', priceAuction],
|
||||
['Initial Phase', initialStep],
|
||||
['Ending Phase', endingStep],
|
||||
['Participation NFT', participationStep],
|
||||
|
@ -544,7 +549,7 @@ export const AuctionCreateView = () => {
|
|||
[AuctionCategory.Single]: [
|
||||
['Category', categoryStep],
|
||||
['Copies', copiesStep],
|
||||
['Price', priceStep],
|
||||
['Price', priceAuction],
|
||||
['Initial Phase', initialStep],
|
||||
['Ending Phase', endingStep],
|
||||
['Participation NFT', participationStep],
|
||||
|
@ -555,7 +560,7 @@ export const AuctionCreateView = () => {
|
|||
[AuctionCategory.Open]: [
|
||||
['Category', categoryStep],
|
||||
['Copies', copiesStep],
|
||||
['Price', priceStep],
|
||||
['Price', priceAuction],
|
||||
['Initial Phase', initialStep],
|
||||
['Ending Phase', endingStep],
|
||||
['Review', reviewStep],
|
||||
|
@ -566,7 +571,7 @@ export const AuctionCreateView = () => {
|
|||
['Category', categoryStep],
|
||||
['Winners', winnersStep],
|
||||
['Tiers', tierTableStep],
|
||||
['Price', priceStep],
|
||||
['Price', priceAuction],
|
||||
['Initial Phase', initialStep],
|
||||
['Ending Phase', endingStep],
|
||||
['Participation NFT', participationStep],
|
||||
|
@ -627,6 +632,20 @@ const CategoryStep = (props: {
|
|||
</Row>
|
||||
<Row justify={width < 768 ? 'center' : 'start'}>
|
||||
<Col>
|
||||
<Row>
|
||||
<Button
|
||||
className="type-btn"
|
||||
size="large"
|
||||
onClick={() => props.confirm(AuctionCategory.InstantSale)}
|
||||
>
|
||||
<div>
|
||||
<div>Instant Sale</div>
|
||||
<div className="type-btn-description">
|
||||
At a fixed price, sell a single Master NFT or copies of it
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</Row>
|
||||
<Row>
|
||||
<Button
|
||||
className="type-btn"
|
||||
|
@ -691,6 +710,145 @@ const CategoryStep = (props: {
|
|||
);
|
||||
};
|
||||
|
||||
const InstantSaleStep = (props: {
|
||||
attributes: AuctionState;
|
||||
setAttributes: (attr: AuctionState) => void;
|
||||
confirm: () => void;
|
||||
}) => {
|
||||
const [copiesChecked, setCopiesChecked] = useState(false);
|
||||
const copiesEnabled = React.useMemo(
|
||||
() => !!props.attributes?.items?.[0]?.masterEdition?.info?.maxSupply,
|
||||
[props.attributes?.items?.[0]],
|
||||
);
|
||||
|
||||
let artistFilter = (i: SafetyDepositDraft) =>
|
||||
!(i.metadata.info.data.creators || []).find((c: Creator) => !c.verified);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row className="call-to-action" style={{ marginBottom: 0 }}>
|
||||
<h2>Select which item to sell:</h2>
|
||||
</Row>
|
||||
|
||||
<Row className="content-action">
|
||||
<Col xl={24}>
|
||||
<ArtSelector
|
||||
filter={artistFilter}
|
||||
selected={props.attributes.items}
|
||||
setSelected={items => {
|
||||
props.setAttributes({ ...props.attributes, items });
|
||||
}}
|
||||
allowMultiple={false}
|
||||
>
|
||||
Select NFT
|
||||
</ArtSelector>
|
||||
|
||||
<label className="action-field">
|
||||
<Checkbox
|
||||
defaultChecked={false}
|
||||
checked={copiesChecked}
|
||||
disabled={!copiesEnabled}
|
||||
onChange={e => setCopiesChecked(e.target.checked)}
|
||||
>
|
||||
<span className="field-title">
|
||||
Create copies of a Master Edition NFT?
|
||||
</span>
|
||||
</Checkbox>
|
||||
{copiesChecked && copiesEnabled && (
|
||||
<>
|
||||
<span className="field-info">
|
||||
Each copy will be given unique edition number e.g. 1 of 30
|
||||
</span>
|
||||
<Input
|
||||
autoFocus
|
||||
className="input"
|
||||
placeholder="Enter number of copies sold"
|
||||
allowClear
|
||||
onChange={info =>
|
||||
props.setAttributes({
|
||||
...props.attributes,
|
||||
editions: parseInt(info.target.value),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</label>
|
||||
|
||||
<label className="action-field">
|
||||
<span className="field-title">Price</span>
|
||||
<span className="field-info">
|
||||
This is the instant sale price for your item.
|
||||
</span>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
autoFocus
|
||||
className="input"
|
||||
placeholder="Price"
|
||||
prefix="◎"
|
||||
suffix="SOL"
|
||||
onChange={info =>
|
||||
props.setAttributes({
|
||||
...props.attributes,
|
||||
priceFloor: parseFloat(info.target.value),
|
||||
instantSalePrice: parseFloat(info.target.value),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="action-field">
|
||||
<span className="field-title">Sale Duration</span>
|
||||
<span className="field-info">
|
||||
This is how long the sale will last for.
|
||||
</span>
|
||||
<Input
|
||||
addonAfter={
|
||||
<Select
|
||||
defaultValue={props.attributes.auctionDurationType}
|
||||
onChange={value =>
|
||||
props.setAttributes({
|
||||
...props.attributes,
|
||||
auctionDurationType: value,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Option value="minutes">Minutes</Option>
|
||||
<Option value="hours">Hours</Option>
|
||||
<Option value="days">Days</Option>
|
||||
</Select>
|
||||
}
|
||||
autoFocus
|
||||
type="number"
|
||||
className="input"
|
||||
placeholder="Set the sale duration"
|
||||
onChange={info =>
|
||||
props.setAttributes({
|
||||
...props.attributes,
|
||||
auctionDuration: parseInt(info.target.value),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={() => {
|
||||
props.confirm();
|
||||
}}
|
||||
className="action-btn"
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const CopiesStep = (props: {
|
||||
attributes: AuctionState;
|
||||
setAttributes: (attr: AuctionState) => void;
|
||||
|
@ -823,137 +981,6 @@ const NumberOfWinnersStep = (props: {
|
|||
);
|
||||
};
|
||||
|
||||
const SaleTypeStep = (props: {
|
||||
attributes: AuctionState;
|
||||
setAttributes: (attr: AuctionState) => void;
|
||||
confirm: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<Row className="call-to-action">
|
||||
<h2>Sale Type</h2>
|
||||
<p>Sell a limited copy or copies of a single Master NFT.</p>
|
||||
</Row>
|
||||
<Row className="content-action">
|
||||
<Col className="section" xl={24}>
|
||||
<label className="action-field">
|
||||
<span className="field-title">
|
||||
How do you want to sell your NFT(s)?
|
||||
</span>
|
||||
<Radio.Group
|
||||
defaultValue={props.attributes.saleType}
|
||||
onChange={info =>
|
||||
props.setAttributes({
|
||||
...props.attributes,
|
||||
saleType: info.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Radio className="radio-field" value="auction">
|
||||
Auction
|
||||
</Radio>
|
||||
<div className="radio-subtitle">
|
||||
Allow bidding on your NFT(s).
|
||||
</div>
|
||||
</Radio.Group>
|
||||
<Radio.Group
|
||||
defaultValue={props.attributes.saleType}
|
||||
onChange={info =>
|
||||
props.setAttributes({
|
||||
...props.attributes,
|
||||
saleType: info.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Radio className="radio-field" value="auction">
|
||||
Instant Sale
|
||||
</Radio>
|
||||
<div className="radio-subtitle">
|
||||
Instant purchase and redemption of your NFT.
|
||||
</div>
|
||||
</Radio.Group>
|
||||
</label>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={props.confirm}
|
||||
className="action-btn"
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const PriceStep = (props: {
|
||||
attributes: AuctionState;
|
||||
setAttributes: (attr: AuctionState) => void;
|
||||
confirm: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{props.attributes.saleType === 'auction' ? (
|
||||
<PriceAuction {...props} />
|
||||
) : (
|
||||
<PriceSale {...props} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const PriceSale = (props: {
|
||||
attributes: AuctionState;
|
||||
setAttributes: (attr: AuctionState) => void;
|
||||
confirm: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<Row className="call-to-action">
|
||||
<h2>Price</h2>
|
||||
<p>Set the fixed price for your instant sale.</p>
|
||||
</Row>
|
||||
<Row className="content-action">
|
||||
<label className="action-field">
|
||||
<span className="field-title">Sale price</span>
|
||||
<span className="field-info">
|
||||
This is the price of purchasing the item(s).
|
||||
</span>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
autoFocus
|
||||
className="input"
|
||||
placeholder="Price"
|
||||
prefix="◎"
|
||||
suffix="SOL"
|
||||
onChange={info =>
|
||||
props.setAttributes({
|
||||
...props.attributes,
|
||||
priceFloor: parseFloat(info.target.value),
|
||||
instantSalePrice: parseFloat(info.target.value),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
</Row>
|
||||
<Row>
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={props.confirm}
|
||||
className="action-btn"
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const PriceAuction = (props: {
|
||||
attributes: AuctionState;
|
||||
setAttributes: (attr: AuctionState) => void;
|
||||
|
@ -1103,13 +1130,13 @@ const InitialPhaseStep = (props: {
|
|||
<>
|
||||
<Row className="call-to-action">
|
||||
<h2>Initial Phase</h2>
|
||||
<p>Set the terms for your {props.attributes.saleType}.</p>
|
||||
<p>Set the terms for your auction.</p>
|
||||
</Row>
|
||||
<Row className="content-action">
|
||||
<Col className="section" xl={24}>
|
||||
<label className="action-field">
|
||||
<span className="field-title">
|
||||
When do you want the {props.attributes.saleType} to begin?
|
||||
When do you want the auction to begin?
|
||||
</span>
|
||||
<Radio.Group
|
||||
defaultValue="now"
|
||||
|
@ -1134,9 +1161,7 @@ const InitialPhaseStep = (props: {
|
|||
{!startNow && (
|
||||
<>
|
||||
<label className="action-field">
|
||||
<span className="field-title">
|
||||
{capitalize(props.attributes.saleType)} Start Date
|
||||
</span>
|
||||
<span className="field-title">Auction Start Date</span>
|
||||
{saleMoment && (
|
||||
<DateTimePicker
|
||||
momentObj={saleMoment}
|
||||
|
@ -1215,22 +1240,6 @@ const InitialPhaseStep = (props: {
|
|||
);
|
||||
};
|
||||
|
||||
const EndingPhaseStep = (props: {
|
||||
attributes: AuctionState;
|
||||
setAttributes: (attr: AuctionState) => void;
|
||||
confirm: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{props.attributes.saleType === 'auction' ? (
|
||||
<EndingPhaseAuction {...props} />
|
||||
) : (
|
||||
<EndingPhaseSale {...props} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const EndingPhaseAuction = (props: {
|
||||
attributes: AuctionState;
|
||||
setAttributes: (attr: AuctionState) => void;
|
||||
|
@ -1245,10 +1254,7 @@ const EndingPhaseAuction = (props: {
|
|||
<Row className="content-action">
|
||||
<Col className="section" xl={24}>
|
||||
<div className="action-field">
|
||||
<span className="field-title">
|
||||
{props.attributes.saleType == 'auction' ? 'Auction' : 'Sale'}{' '}
|
||||
Duration
|
||||
</span>
|
||||
<span className="field-title">Auction Duration</span>
|
||||
<span className="field-info">
|
||||
This is how long the auction will last for.
|
||||
</span>
|
||||
|
@ -1281,14 +1287,12 @@ const EndingPhaseAuction = (props: {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{props.attributes.saleType == 'auction' && (
|
||||
<>
|
||||
<div className="action-field">
|
||||
<span className="field-title">Gap Time</span>
|
||||
<span className="field-info">
|
||||
The final phase of the auction will begin when there is this
|
||||
much time left on the countdown. Any bids placed during the
|
||||
final phase will extend the end time by this same duration.
|
||||
The final phase of the auction will begin when there is this much
|
||||
time left on the countdown. Any bids placed during the final phase
|
||||
will extend the end time by this same duration.
|
||||
</span>
|
||||
<Input
|
||||
addonAfter={
|
||||
|
@ -1321,9 +1325,9 @@ const EndingPhaseAuction = (props: {
|
|||
<label className="action-field">
|
||||
<span className="field-title">Tick Size for Ending Phase</span>
|
||||
<span className="field-info">
|
||||
In order for winners to move up in the auction, they must
|
||||
place a bid that’s at least this percentage higher than the
|
||||
next highest bid.
|
||||
In order for winners to move up in the auction, they must place a
|
||||
bid that’s at least this percentage higher than the next highest
|
||||
bid.
|
||||
</span>
|
||||
<Input
|
||||
type="number"
|
||||
|
@ -1338,96 +1342,6 @@ const EndingPhaseAuction = (props: {
|
|||
}
|
||||
/>
|
||||
</label>
|
||||
</>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={props.confirm}
|
||||
className="action-btn"
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const EndingPhaseSale = (props: {
|
||||
attributes: AuctionState;
|
||||
setAttributes: (attr: AuctionState) => void;
|
||||
confirm: () => void;
|
||||
}) => {
|
||||
const startMoment = props.attributes.startSaleTS
|
||||
? moment.unix(props.attributes.startSaleTS)
|
||||
: moment();
|
||||
const [untilSold, setUntilSold] = useState<boolean>(true);
|
||||
const [endMoment, setEndMoment] = useState<moment.Moment | undefined>(
|
||||
props.attributes.endTS ? moment.unix(props.attributes.endTS) : undefined,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
props.setAttributes({
|
||||
...props.attributes,
|
||||
endTS: endMoment && endMoment.unix(),
|
||||
});
|
||||
}, [endMoment]);
|
||||
|
||||
useEffect(() => {
|
||||
if (untilSold) setEndMoment(undefined);
|
||||
else setEndMoment(startMoment);
|
||||
}, [untilSold]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row className="call-to-action">
|
||||
<h2>Ending Phase</h2>
|
||||
<p>Set the terms for your sale.</p>
|
||||
</Row>
|
||||
<Row className="content-action">
|
||||
<Col className="section" xl={24}>
|
||||
<label className="action-field">
|
||||
<span className="field-title">
|
||||
When do you want the sale to end?
|
||||
</span>
|
||||
<Radio.Group
|
||||
defaultValue="now"
|
||||
onChange={info => setUntilSold(info.target.value === 'now')}
|
||||
>
|
||||
<Radio className="radio-field" value="now">
|
||||
Until sold
|
||||
</Radio>
|
||||
<div className="radio-subtitle">
|
||||
The sale will end once the supply goes to zero.
|
||||
</div>
|
||||
<Radio className="radio-field" value="later">
|
||||
At a specified date
|
||||
</Radio>
|
||||
<div className="radio-subtitle">
|
||||
The sale will end at this date, regardless if there is remaining
|
||||
supply.
|
||||
</div>
|
||||
</Radio.Group>
|
||||
</label>
|
||||
|
||||
{!untilSold && (
|
||||
<label className="action-field">
|
||||
<span className="field-title">End Date</span>
|
||||
{endMoment && (
|
||||
<DateTimePicker
|
||||
momentObj={endMoment}
|
||||
setMomentObj={setEndMoment}
|
||||
datePickerProps={{
|
||||
disabledDate: (current: moment.Moment) =>
|
||||
current && current < startMoment,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
|
@ -1892,6 +1806,7 @@ const ReviewStep = (props: {
|
|||
startListTS: props.attributes.startListTS || moment().unix(),
|
||||
startSaleTS: props.attributes.startSaleTS || moment().unix(),
|
||||
});
|
||||
console.log('attributes', props.attributes);
|
||||
props.confirm();
|
||||
}}
|
||||
className="action-btn"
|
||||
|
|
Loading…
Reference in New Issue