ENS Resolving (#942)
* Refactor BaseNode to be an interface INode * Initial contract commit * Remove redundant fallback ABI function * First working iteration of Contract generator to be used in ENS branch * Hide abi to clean up logging output * Strip 0x prefix from output decode * Handle unnamed output params * Implement ability to supply output mappings to ABI functions * Fix null case in outputMapping * Add flow typing * Add .call method to functions * Partial commit for type refactor * Temp contract type fix -- waiting for NPM modularization * Remove empty files * Cleanup contract * Add call request to node interface * Fix output mapping types * Revert destructuring overboard * Add sendCallRequest to rpcNode class and add typing * Use enum for selecting ABI methods * Add transaction capability to contracts * Cleanup privaite/public members * Remove broadcasting step from a contract transaction * Cleanup uneeded types * Refactor ens-base to typescript and add typings for ENS smart contracts * Migrate ens-name-search to TS * Add IResolveDomainRequest * Fix rest of TSC errors * Add definition file for bn.js * Remove types-bn * Fix some typings * make isBN a static property * progress commit -- swap out bignumber.js for bn.js * Swap out bignumber for bn in vendor * Change modn to number return * Start to strip out units lib for a string manipulation based lib * Convert codebase to only base units * Get rid of useless component * Handle only wei in values * Use unit conversion in sidebar * Automatically strip hex prefix, and handle decimal edge case * Handle base 16 wei in transactions * Make a render callback component for dealing with unit conversion * Switch contracts to use bn.js, and get transaction values from signedTx instead of state * Get send transaction working with bn.js * Remove redundant hex stripping, return base value of tokens * Cleanup unit file * Re-implement toFixed for strings * Use formatNumber in codebase * Cleanup code * Undo package test changes * Update snapshot and remove console logs * Use TokenValue / Wei more consistently where applicable * Add typing to deterministicWallets, fix confirmation modal, make UnitDisplay more flexible * Split different ENS modes into their own components * Fix Abi typedef * Remove redundant moment type package * Add Aux helper component * Split out resolve components * Make 'to' parameter optional * Change import type * Change typing to be base domain request * Split handling of resolving into object handler * Fix countdown component * Adjust element spacing * Implement reveal search functionality * Add unit display for highest bidder * Fill out forbidden/NYA modes * ENS wallet component skeleton * Clean up prop handling in UnitDisplay * Change instanceof to typeof check, change boolean of displayBalance * Add ENS wallet component * Cleanup spacing * Convert ConfModal for bidding in ENS * Make ui component for placing bids * Fix destructure in placeBid * Pass through entire wallet * Remove text center * Display inline notification ENS isValid & add some ENS tests * Add export of Aux * Reformat with prettier * progress... * Add ENSUnlockLayout * Add RevealBid component * organize NameResolve components * Merge ENS with transaction-refactor changes * Fix address resolution * Update styles * convert ens name to lowercase before checking * Add overflow-y:scroll to table * update ens snapshots & tests * cast 'undefined' state argument as any for testing * clean up components * Connect unitconverter to redux state * remove unnecessary type assertion * fix spinner size * remove old bidmodal * validate bidmask before opening modal * progress... * Update styles * Add saga / actions for placing a bid * Update types & clean up dead code * Delete old test * Dispatch PlaceBidRequested acitons * Progress commit -- get ENS bidding ready for tx generation via sagas * Seperate ENS action creators and types * Add reducer & actions for ENS fields * Add preliminary sagas for bid mask and bid value * Fix ts errors * Get bidding fields connected with some validation * Clean up generate bid * Hook up generate bid to redux state * Get bid data generation working * Add support for bidding on already open auctions * Move bid generation states to redux, improve default field values * Remove generate bid component * Throttle bid generation * Progress commit -- Bid Modal * Hook bidmodal component up to bidding component * Update template modal to handle custom confirm behavior * Remove old redux bidding actions, add new one for downloaded bids * Save downloaded bids to local storage * Finish bidding modal * Fix gas estimation bug * Fix typing * Remove bidding related functionality * Get passing unit tests * Make previous test more comprehensive * Fix ts errors * Remove commented code * Fix invalid return * Remove implementation of revealing bid * Update snapshot * Fix tests * Delegate bidding to V3
This commit is contained in:
parent
8f3669cbc6
commit
2a4ad180d3
|
@ -1,5 +1,4 @@
|
||||||
import { ResolveDomainAction } from './resolveDomain';
|
import { ResolveDomainAction } from './resolveDomain';
|
||||||
|
|
||||||
export * from './resolveDomain';
|
export * from './resolveDomain';
|
||||||
|
|
||||||
export type EnsAction = ResolveDomainAction;
|
export type EnsAction = ResolveDomainAction;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export enum TypeKeys {
|
export enum TypeKeys {
|
||||||
ENS_RESOLVE_DOMAIN_REQUESTED = 'ENS_RESOLVE_DOMAIN_REQUESTED',
|
ENS_RESOLVE_DOMAIN_REQUESTED = 'ENS_RESOLVE_DOMAIN_REQUESTED',
|
||||||
ENS_RESOLVE_DOMAIN_SUCCEEDED = 'ENS_RESOLVE_DOMAIN_SUCCEEDED',
|
ENS_RESOLVE_DOMAIN_SUCCEEDED = 'ENS_RESOLVE_DOMAIN_SUCCEEDED',
|
||||||
ENS_RESOLVE_DOMAIN_CACHED = 'ENS_RESOLVE_DOMAIN_CACHED',
|
ENS_RESOLVE_DOMAIN_FAILED = 'ENS_RESOLVE_DOMAIN_FAILED',
|
||||||
ENS_RESOLVE_DOMAIN_FAILED = 'ENS_RESOLVE_DOMAIN_FAILED'
|
ENS_RESOLVE_DOMAIN_CACHED = 'ENS_RESOLVE_DOMAIN_CACHED'
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,12 +69,12 @@ class DeployClass extends Component<DispatchProps> {
|
||||||
</div>
|
</div>
|
||||||
<SigningStatus />
|
<SigningStatus />
|
||||||
<SendButtonFactory
|
<SendButtonFactory
|
||||||
|
Modal={ConfirmationModal}
|
||||||
withProps={({ onClick }) => (
|
withProps={({ onClick }) => (
|
||||||
<button className="Deploy-submit btn btn-primary" onClick={onClick}>
|
<button className="Deploy-submit btn btn-primary" onClick={onClick}>
|
||||||
{translate('NAV_DeployContract')}
|
{translate('NAV_DeployContract')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
Modal={ConfirmationModal}
|
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React from 'react';
|
||||||
|
import NameInputHoc from './NameInputHOC';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isValidDomain: boolean;
|
||||||
|
domainToCheck: string;
|
||||||
|
onClick(ev: React.FormEvent<HTMLButtonElement>): void;
|
||||||
|
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
||||||
|
}
|
||||||
|
class ENSNameInput extends React.Component<Props, {}> {
|
||||||
|
public render() {
|
||||||
|
const { onChange, onClick, isValidDomain, domainToCheck } = this.props;
|
||||||
|
return (
|
||||||
|
<article className="row">
|
||||||
|
<section className="col-xs-12 col-sm-6 col-sm-offset-3 text-center">
|
||||||
|
<div className="input-group">
|
||||||
|
<input
|
||||||
|
className={`form-control ${
|
||||||
|
domainToCheck === '' ? '' : isValidDomain ? 'is-valid' : 'is-invalid'
|
||||||
|
}`}
|
||||||
|
type="text"
|
||||||
|
placeholder="myetherwallet"
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
<div className="input-group-btn">
|
||||||
|
<a className="btn btn-default">.eth</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isValidDomain ? null : <p>Use at least 7 characters</p>}
|
||||||
|
<button className="btn btn-primary " onClick={onClick}>
|
||||||
|
Check ENS Name
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NameInputHoc(ENSNameInput);
|
|
@ -0,0 +1,42 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import { isValidENSName } from 'libs/validators';
|
||||||
|
interface State {
|
||||||
|
domainToCheck: string;
|
||||||
|
isValidDomain: boolean;
|
||||||
|
}
|
||||||
|
interface Props {
|
||||||
|
resolveDomainRequested(domain: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NameInputHoc = PassedComponent =>
|
||||||
|
class HOC extends Component<Props, State> {
|
||||||
|
public state = {
|
||||||
|
isValidDomain: false,
|
||||||
|
domainToCheck: ''
|
||||||
|
};
|
||||||
|
//add delay to namehash computation / getting the availability
|
||||||
|
public onChange = (event: React.FormEvent<HTMLButtonElement>) => {
|
||||||
|
const domainToCheck: string = event.currentTarget.value.toLowerCase();
|
||||||
|
this.setState({ domainToCheck });
|
||||||
|
const isValidName: boolean = isValidENSName(domainToCheck);
|
||||||
|
this.setState({ isValidDomain: isValidName });
|
||||||
|
};
|
||||||
|
public onClick = () => {
|
||||||
|
const { isValidDomain, domainToCheck } = this.state;
|
||||||
|
const { resolveDomainRequested } = this.props;
|
||||||
|
return isValidDomain && resolveDomainRequested(domainToCheck);
|
||||||
|
};
|
||||||
|
public render() {
|
||||||
|
const { onChange, onClick } = this;
|
||||||
|
const { isValidDomain, domainToCheck } = this.state;
|
||||||
|
const props = {
|
||||||
|
onChange,
|
||||||
|
onClick,
|
||||||
|
isValidDomain,
|
||||||
|
domainToCheck
|
||||||
|
};
|
||||||
|
return <PassedComponent {...props} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export default NameInputHoc;
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as NameInput } from './components/NameInput';
|
|
@ -0,0 +1,40 @@
|
||||||
|
.auction-info {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ens-title {
|
||||||
|
margin: 2rem 0;
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ens-panel-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
.ens-panel {
|
||||||
|
flex-grow: 1;
|
||||||
|
color: white;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: #185475;
|
||||||
|
|
||||||
|
p,
|
||||||
|
h4 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-light {
|
||||||
|
background-color: #1e92ba;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrapper {
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 820px) {
|
||||||
|
.ens-panel {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { IBaseDomainRequest } from 'libs/ens';
|
||||||
|
import ENSTime from './components/ENSTime';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { NewTabLink } from 'components/ui';
|
||||||
|
|
||||||
|
const getDeadlines = (registrationDate: string) => {
|
||||||
|
// Get the time to reveal bids, and the time when the action closes
|
||||||
|
const time = moment(+registrationDate * 1000);
|
||||||
|
const auctionCloseTime = +time;
|
||||||
|
const revealBidTime = +time.subtract(2, 'days');
|
||||||
|
return { auctionCloseTime, revealBidTime };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NameAuction: React.SFC<IBaseDomainRequest> = props => {
|
||||||
|
const { registrationDate, name } = props;
|
||||||
|
const { auctionCloseTime, revealBidTime } = getDeadlines(registrationDate);
|
||||||
|
return (
|
||||||
|
<section className="row">
|
||||||
|
<div className="auction-info text-center">
|
||||||
|
<div className="ens-title">
|
||||||
|
<h1>
|
||||||
|
An auction has started for <strong>{name}.eth</strong>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ens-panel-wrapper">
|
||||||
|
<section className="ens-panel">
|
||||||
|
<ENSTime text="Reveal Bids On" time={revealBidTime} />
|
||||||
|
</section>
|
||||||
|
<section className="ens-panel ens-panel-light">
|
||||||
|
<ENSTime text="Auction Closes On" time={auctionCloseTime} />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<NewTabLink
|
||||||
|
content={`Do you want to place a bid on ${name}.eth? You'll need to bid on MyCrypto V3 by clicking here: `}
|
||||||
|
href="https://mycrypto.com/#ens"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,6 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { IBaseDomainRequest } from 'libs/ens';
|
||||||
|
|
||||||
|
export const NameForbidden: React.SFC<IBaseDomainRequest> = props => (
|
||||||
|
<h1>{props.name}.eth is forbidden</h1>
|
||||||
|
);
|
|
@ -0,0 +1,6 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { IBaseDomainRequest } from 'libs/ens';
|
||||||
|
|
||||||
|
export const NameNotYetAvailable: React.SFC<IBaseDomainRequest> = props => (
|
||||||
|
<h1>{props.name}.eth is not yet available</h1>
|
||||||
|
);
|
|
@ -0,0 +1,23 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { IBaseDomainRequest } from 'libs/ens';
|
||||||
|
import { NewTabLink } from 'components/ui';
|
||||||
|
|
||||||
|
export const NameOpen: React.SFC<IBaseDomainRequest> = props => (
|
||||||
|
<section className="row">
|
||||||
|
<section className="auction-info text-center">
|
||||||
|
<div className="ens-title">
|
||||||
|
<h1>
|
||||||
|
<strong>{props.name}.eth</strong> is available!
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<NewTabLink
|
||||||
|
className="text-center"
|
||||||
|
content={`Do you want ${
|
||||||
|
props.name
|
||||||
|
}.eth? You'll need open an auction on MyCrypto V3 by clicking here`}
|
||||||
|
href="https://mycrypto.com/#ens"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
);
|
|
@ -0,0 +1,59 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { IOwnedDomainRequest } from 'libs/ens';
|
||||||
|
import { NewTabLink } from 'components/ui';
|
||||||
|
const lookupLink = name => `https://etherscan.io/enslookup?q=${name}`;
|
||||||
|
|
||||||
|
type ChildrenProps = any;
|
||||||
|
|
||||||
|
const MonoTd = ({ children }: ChildrenProps) => <td className="mono">{children}</td>;
|
||||||
|
|
||||||
|
export const NameOwned: React.SFC<IOwnedDomainRequest> = ({
|
||||||
|
highestBid,
|
||||||
|
labelHash,
|
||||||
|
nameHash,
|
||||||
|
resolvedAddress,
|
||||||
|
ownerAddress,
|
||||||
|
name
|
||||||
|
}) => (
|
||||||
|
<section>
|
||||||
|
<div className="ens-title">
|
||||||
|
<h1 className="text-center">
|
||||||
|
<strong>{name}.eth</strong> is already owned
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div className="table-wrapper">
|
||||||
|
<table className="table table-striped">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Name: </td>
|
||||||
|
<MonoTd>
|
||||||
|
<NewTabLink content={`${name}.eth`} href={lookupLink(`${name}.eth`)} />
|
||||||
|
</MonoTd>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Labelhash ({name}): </td>
|
||||||
|
<MonoTd>{labelHash}</MonoTd>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Namehash ({name}.eth): </td>
|
||||||
|
<MonoTd>{nameHash}</MonoTd>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Owner:</td>
|
||||||
|
<MonoTd>{ownerAddress}</MonoTd>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Highest Bidder (Deed Owner): </td>
|
||||||
|
<MonoTd>
|
||||||
|
<span>{highestBid}</span>
|
||||||
|
</MonoTd>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Resolved Address: </td>
|
||||||
|
<MonoTd>{resolvedAddress}</MonoTd>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { IRevealDomainRequest } from 'libs/ens';
|
||||||
|
import ENSTime from './components/ENSTime';
|
||||||
|
import { UnitDisplay, NewTabLink } from 'components/ui';
|
||||||
|
import { Wei } from 'libs/units';
|
||||||
|
|
||||||
|
export const NameReveal: React.SFC<IRevealDomainRequest> = props => (
|
||||||
|
<section className="row text-center">
|
||||||
|
<div className="auction-info text-center">
|
||||||
|
<div className="ens-title">
|
||||||
|
<h2>
|
||||||
|
<p>
|
||||||
|
It's time to reveal the bids for <strong>{props.name}.eth.</strong>{' '}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Current Highest bid is{' '}
|
||||||
|
<strong>
|
||||||
|
<UnitDisplay
|
||||||
|
value={Wei(props.highestBid)}
|
||||||
|
unit="ether"
|
||||||
|
symbol="ETH"
|
||||||
|
displayShortBalance={false}
|
||||||
|
checkOffline={false}
|
||||||
|
/>
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ens-panel-wrapper">
|
||||||
|
<section className="ens-panel">
|
||||||
|
<ENSTime text="Auction closes on" time={+props.registrationDate * 1000} />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NewTabLink
|
||||||
|
content={`Did you you bid on ${
|
||||||
|
props.name
|
||||||
|
}.eth? You must reveal your bid now. You'll need reveal your bid on MyCrypto V3 by clicking here`}
|
||||||
|
href="https://mycrypto.com/#ens"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
);
|
|
@ -0,0 +1,72 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
initialTime: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
currentTime: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CountDown extends Component<Props, State> {
|
||||||
|
public state = { currentTime: 0 };
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.startCountDown();
|
||||||
|
this.state = { currentTime: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { currentTime } = this.state;
|
||||||
|
return <p>{this.humanizeTime(currentTime)}</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
private humanizeTime = (time: number) => {
|
||||||
|
let timeRemaining = time;
|
||||||
|
const floorTime = unit => Math.floor(timeRemaining / unit);
|
||||||
|
const pad = (num: number) => num.toString().padStart(2, '0');
|
||||||
|
const second = 1000;
|
||||||
|
const minute = second * 60;
|
||||||
|
const hour = minute * 60;
|
||||||
|
const day = hour * 24;
|
||||||
|
|
||||||
|
const days = floorTime(day);
|
||||||
|
timeRemaining -= days * day;
|
||||||
|
const hours = floorTime(hour);
|
||||||
|
timeRemaining -= hours * hour;
|
||||||
|
const minutes = floorTime(minute);
|
||||||
|
timeRemaining -= minutes * minute;
|
||||||
|
const seconds = floorTime(second);
|
||||||
|
|
||||||
|
return `${pad(days)} Days ${pad(hours)} Hours ${pad(minutes)} Minutes ${pad(seconds)} Seconds `;
|
||||||
|
};
|
||||||
|
|
||||||
|
private startCountDown = () => {
|
||||||
|
const intervalId = window.setInterval(() => {
|
||||||
|
const nextTime = +moment(this.props.initialTime).diff(+moment(), 'ms');
|
||||||
|
|
||||||
|
if (nextTime < 0) {
|
||||||
|
return clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ currentTime: nextTime });
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ITime {
|
||||||
|
text: string;
|
||||||
|
time: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ENSTime: React.SFC<ITime> = ({ text, time }) => (
|
||||||
|
<section className="sm-6 col-xs-12 order-info">
|
||||||
|
<p>{text}</p>
|
||||||
|
<h4>{moment(time).toString()}</h4>
|
||||||
|
<CountDown initialTime={time} />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ENSTime;
|
|
@ -0,0 +1,6 @@
|
||||||
|
export { NameAuction } from './NameAuction';
|
||||||
|
export { NameForbidden } from './NameForbidden';
|
||||||
|
export { NameNotYetAvailable } from './NameNotYetAvailable';
|
||||||
|
export { NameOpen } from './NameOpen';
|
||||||
|
export { NameOwned } from './NameOwned';
|
||||||
|
export { NameReveal } from './NameReveal';
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { AppState } from 'reducers';
|
||||||
|
import { NameState } from 'libs/ens';
|
||||||
|
import {
|
||||||
|
NameOwned,
|
||||||
|
NameAuction,
|
||||||
|
NameForbidden,
|
||||||
|
NameNotYetAvailable,
|
||||||
|
NameOpen,
|
||||||
|
NameReveal
|
||||||
|
} from './components';
|
||||||
|
import './NameResolve.scss';
|
||||||
|
import { Spinner } from 'components/ui';
|
||||||
|
|
||||||
|
type Props = AppState['ens'];
|
||||||
|
|
||||||
|
const modeResult = {
|
||||||
|
[NameState.Auction]: NameAuction,
|
||||||
|
[NameState.Forbidden]: NameForbidden,
|
||||||
|
[NameState.NotYetAvailable]: NameNotYetAvailable,
|
||||||
|
[NameState.Open]: NameOpen,
|
||||||
|
[NameState.Owned]: NameOwned,
|
||||||
|
[NameState.Reveal]: NameReveal
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NameResolve: React.SFC<Props> = props => {
|
||||||
|
const { domainRequests, domainSelector } = props;
|
||||||
|
const { currentDomain } = domainSelector;
|
||||||
|
|
||||||
|
if (!currentDomain || !domainRequests[currentDomain] || domainRequests[currentDomain].error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const domainData = domainRequests[currentDomain].data! || false;
|
||||||
|
const Component = domainData ? modeResult[domainData.mode] : Spinner;
|
||||||
|
|
||||||
|
return <Component {...domainData} />;
|
||||||
|
};
|
|
@ -1,47 +0,0 @@
|
||||||
@import "common/sass/variables";
|
|
||||||
|
|
||||||
.UnfinishedBanner {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: $brand-warning;
|
|
||||||
overflow: auto;
|
|
||||||
z-index: 10000;
|
|
||||||
|
|
||||||
&-content {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 20%;
|
|
||||||
@media screen and (min-width: 2150px) {
|
|
||||||
margin-top: 15%;
|
|
||||||
}
|
|
||||||
color: #fff;
|
|
||||||
text-shadow: 1px 1px 1px rgba(#000, 0.12);
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 52px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 30px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fade out
|
|
||||||
&.is-fading {
|
|
||||||
pointer-events: none;
|
|
||||||
opacity: 0;
|
|
||||||
background: #fff;
|
|
||||||
transition: all 500ms ease 400ms;
|
|
||||||
|
|
||||||
.UnfinishedBanner-content {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(15px);
|
|
||||||
transition: all 500ms ease;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './index.scss';
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
isFading: boolean;
|
|
||||||
hasAcknowledged: boolean;
|
|
||||||
}
|
|
||||||
export default class UnfinishedBanner extends React.Component<{}, State> {
|
|
||||||
public state = {
|
|
||||||
isFading: false,
|
|
||||||
hasAcknowledged: false
|
|
||||||
};
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
if (this.state.hasAcknowledged) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isFading = this.state.isFading ? 'is-fading' : '';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`UnfinishedBanner ${isFading}`} onClick={this.continue}>
|
|
||||||
<div className="UnfinishedBanner-content">
|
|
||||||
<h2>Under Contruction</h2>
|
|
||||||
<p>The ENS section is still under contruction</p>
|
|
||||||
<p>Expect unfinished components</p>
|
|
||||||
<h3>Click to continue</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private continue = () => {
|
|
||||||
this.setState({ isFading: true });
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.setState({ hasAcknowledged: true });
|
|
||||||
}, 1000);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { GeneralInfoPanel } from './GeneralInfoPanel';
|
||||||
|
export { NameInput } from './NameInput';
|
||||||
|
export { NameResolve } from './NameResolve';
|
|
@ -1,11 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { GeneralInfoPanel } from './components/GeneralInfoPanel';
|
import { GeneralInfoPanel, NameInput, NameResolve } from './components';
|
||||||
import UnfinishedBanner from './components/UnfinishedBanner';
|
|
||||||
import TabSection from 'containers/TabSection';
|
import TabSection from 'containers/TabSection';
|
||||||
import { Route, Switch, RouteComponentProps } from 'react-router';
|
import { Route, Switch, RouteComponentProps } from 'react-router';
|
||||||
import { RouteNotFound } from 'components/RouteNotFound';
|
import { RouteNotFound } from 'components/RouteNotFound';
|
||||||
import { NewTabLink } from 'components/ui';
|
import { NewTabLink } from 'components/ui';
|
||||||
import translate from 'translations';
|
import translate from 'translations';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { resolveDomainRequested, TResolveDomainRequested } from 'actions/ens';
|
||||||
|
import { AppState } from 'reducers';
|
||||||
|
|
||||||
const ENSDocsLink = () => (
|
const ENSDocsLink = () => (
|
||||||
<NewTabLink
|
<NewTabLink
|
||||||
|
@ -26,7 +28,17 @@ const ENSTitle = () => (
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
|
|
||||||
class ENSClass extends React.Component<RouteComponentProps<{}>> {
|
interface StateProps {
|
||||||
|
ens: AppState['ens'];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DispatchProps {
|
||||||
|
resolveDomainRequested: TResolveDomainRequested;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = StateProps & DispatchProps;
|
||||||
|
|
||||||
|
class ENSClass extends React.Component<RouteComponentProps<any> & Props> {
|
||||||
public render() {
|
public render() {
|
||||||
const { match } = this.props;
|
const { match } = this.props;
|
||||||
const currentPath = match.url;
|
const currentPath = match.url;
|
||||||
|
@ -39,8 +51,9 @@ class ENSClass extends React.Component<RouteComponentProps<{}>> {
|
||||||
path={currentPath}
|
path={currentPath}
|
||||||
render={() => (
|
render={() => (
|
||||||
<section role="main" className="row">
|
<section role="main" className="row">
|
||||||
<UnfinishedBanner />
|
|
||||||
<ENSTitle />
|
<ENSTitle />
|
||||||
|
<NameInput resolveDomainRequested={this.props.resolveDomainRequested} />
|
||||||
|
<NameResolve {...this.props.ens} />
|
||||||
<GeneralInfoPanel />
|
<GeneralInfoPanel />
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
@ -53,4 +66,7 @@ class ENSClass extends React.Component<RouteComponentProps<{}>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ENSClass;
|
const mapStateToProps = (state: AppState): StateProps => ({ ens: state.ens });
|
||||||
|
const mapDispatchToProps: DispatchProps = { resolveDomainRequested };
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ENSClass);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import {
|
import {
|
||||||
EnsAction,
|
|
||||||
ResolveDomainRequested,
|
ResolveDomainRequested,
|
||||||
ResolveDomainFailed,
|
ResolveDomainFailed,
|
||||||
ResolveDomainSucceeded,
|
ResolveDomainSucceeded,
|
||||||
ResolveDomainCached
|
ResolveDomainCached,
|
||||||
|
ResolveDomainAction
|
||||||
} from 'actions/ens';
|
} from 'actions/ens';
|
||||||
import { DomainRequest } from 'libs/ens';
|
import { DomainRequest } from 'libs/ens';
|
||||||
import { TypeKeys } from 'actions/ens/constants';
|
import { TypeKeys } from 'actions/ens/constants';
|
||||||
|
@ -66,7 +66,7 @@ const resolveDomainFailed = (state: State, action: ResolveDomainFailed): State =
|
||||||
return { ...state, [domain]: nextDomain };
|
return { ...state, [domain]: nextDomain };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (state: State = INITIAL_STATE, action: EnsAction): State => {
|
export default (state: State = INITIAL_STATE, action: ResolveDomainAction): State => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case TypeKeys.ENS_RESOLVE_DOMAIN_REQUESTED:
|
case TypeKeys.ENS_RESOLVE_DOMAIN_REQUESTED:
|
||||||
return resolveDomainRequested(state, action);
|
return resolveDomainRequested(state, action);
|
||||||
|
|
|
@ -8,4 +8,4 @@ export interface State {
|
||||||
domainRequests: DRState;
|
domainRequests: DRState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ens = combineReducers({ domainSelector, domainRequests });
|
export const ens = combineReducers<State>({ domainSelector, domainRequests });
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { call, put, select, all, actionChannel, take, fork, race } from 'redux-s
|
||||||
import { showNotification } from 'actions/notifications';
|
import { showNotification } from 'actions/notifications';
|
||||||
import { resolveDomainRequest } from './modeMap';
|
import { resolveDomainRequest } from './modeMap';
|
||||||
import { getCurrentDomainName, getCurrentDomainData } from 'selectors/ens';
|
import { getCurrentDomainName, getCurrentDomainData } from 'selectors/ens';
|
||||||
|
import { IBaseDomainRequest } from 'libs/ens';
|
||||||
|
|
||||||
function* shouldResolveDomain(domain: string) {
|
function* shouldResolveDomain(domain: string) {
|
||||||
const currentDomainName = yield select(getCurrentDomainName);
|
const currentDomainName = yield select(getCurrentDomainName);
|
||||||
|
@ -43,18 +44,19 @@ function* resolveDomain(): SagaIterator {
|
||||||
}
|
}
|
||||||
|
|
||||||
const node: INode = yield select(getNodeLib);
|
const node: INode = yield select(getNodeLib);
|
||||||
const result = yield race({
|
|
||||||
|
const result: { domainData: IBaseDomainRequest; error } = yield race({
|
||||||
domainData: call(resolveDomainRequest, domain, node),
|
domainData: call(resolveDomainRequest, domain, node),
|
||||||
err: call(delay, 4000)
|
err: call(delay, 4000)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { domainData } = result;
|
const { domainData } = result;
|
||||||
|
|
||||||
if (!domainData) {
|
if (!domainData) {
|
||||||
throw Error();
|
throw Error();
|
||||||
}
|
}
|
||||||
const domainSuccessAction = resolveDomainSucceeded(domain, domainData);
|
const domainSuccessAction = resolveDomainSucceeded(domain, domainData);
|
||||||
yield put(domainSuccessAction);
|
yield put(domainSuccessAction);
|
||||||
yield;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const domainFailAction = resolveDomainFailed(domain, e);
|
const domainFailAction = resolveDomainFailed(domain, e);
|
||||||
yield put(domainFailAction);
|
yield put(domainFailAction);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { IDomainData, NameState, getNameHash } from 'libs/ens';
|
import { IDomainData, NameState, getNameHash, IBaseDomainRequest } from 'libs/ens';
|
||||||
import ENS from 'libs/ens/contracts';
|
import ENS from 'libs/ens/contracts';
|
||||||
import { SagaIterator } from 'redux-saga';
|
import { SagaIterator } from 'redux-saga';
|
||||||
import { call } from 'redux-saga/effects';
|
import { call } from 'redux-saga/effects';
|
||||||
|
@ -48,7 +48,7 @@ function* nameStateReveal({ deedAddress }: IDomainData<NameState.Reveal>): SagaI
|
||||||
data: ENS.deed.owner.encodeInput(),
|
data: ENS.deed.owner.encodeInput(),
|
||||||
decoder: ENS.deed.owner.decodeOutput
|
decoder: ENS.deed.owner.decodeOutput
|
||||||
});
|
});
|
||||||
return ownerAddress;
|
return { ownerAddress };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IModeMap {
|
interface IModeMap {
|
||||||
|
@ -83,11 +83,12 @@ export function* resolveDomainRequest(name: string): SagaIterator {
|
||||||
const nameStateHandler = modeMap[domainData.mode];
|
const nameStateHandler = modeMap[domainData.mode];
|
||||||
const result = yield call(nameStateHandler, domainData, nameHash);
|
const result = yield call(nameStateHandler, domainData, nameHash);
|
||||||
|
|
||||||
return {
|
const returnValue: IBaseDomainRequest = {
|
||||||
name,
|
name,
|
||||||
...domainData,
|
...domainData,
|
||||||
...result,
|
...result,
|
||||||
labelHash: hash.toString('hex'),
|
labelHash: hash.toString('hex'),
|
||||||
nameHash
|
nameHash
|
||||||
};
|
};
|
||||||
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { composeWithDevTools } from 'redux-devtools-extension';
|
||||||
import { createLogger } from 'redux-logger';
|
import { createLogger } from 'redux-logger';
|
||||||
import createSagaMiddleware from 'redux-saga';
|
import createSagaMiddleware from 'redux-saga';
|
||||||
import { loadStatePropertyOrEmptyObject, saveState } from 'utils/localStorage';
|
import { loadStatePropertyOrEmptyObject, saveState } from 'utils/localStorage';
|
||||||
import RootReducer from './reducers';
|
import RootReducer, { AppState } from './reducers';
|
||||||
import promiseMiddleware from 'redux-promise-middleware';
|
import promiseMiddleware from 'redux-promise-middleware';
|
||||||
import { getNodeConfigFromId } from 'utils/node';
|
import { getNodeConfigFromId } from 'utils/node';
|
||||||
import { getNetworkConfigFromId } from 'utils/network';
|
import { getNetworkConfigFromId } from 'utils/network';
|
||||||
|
@ -111,7 +111,6 @@ const configureStore = () => {
|
||||||
// ONLY LOAD SWAP STATE FROM LOCAL STORAGE IF STEP WAS 3
|
// ONLY LOAD SWAP STATE FROM LOCAL STORAGE IF STEP WAS 3
|
||||||
swap: swapState
|
swap: swapState
|
||||||
};
|
};
|
||||||
|
|
||||||
// if 'web3' has persisted as node selection, reset to app default
|
// if 'web3' has persisted as node selection, reset to app default
|
||||||
// necessary because web3 is only initialized as a node upon MetaMask / Mist unlock
|
// necessary because web3 is only initialized as a node upon MetaMask / Mist unlock
|
||||||
if (persistedInitialState.config.nodeSelection === 'web3') {
|
if (persistedInitialState.config.nodeSelection === 'web3') {
|
||||||
|
@ -127,14 +126,13 @@ const configureStore = () => {
|
||||||
|
|
||||||
store.subscribe(
|
store.subscribe(
|
||||||
throttle(() => {
|
throttle(() => {
|
||||||
const state = store.getState();
|
const state: AppState = store.getState();
|
||||||
saveState({
|
saveState({
|
||||||
config: {
|
config: {
|
||||||
nodeSelection: state.config.nodeSelection,
|
nodeSelection: state.config.nodeSelection,
|
||||||
languageSelection: state.config.languageSelection,
|
languageSelection: state.config.languageSelection,
|
||||||
customNodes: state.config.customNodes,
|
customNodes: state.config.customNodes,
|
||||||
customNetworks: state.config.customNetworks,
|
customNetworks: state.config.customNetworks
|
||||||
setGasLimit: state.config.setGasLimit
|
|
||||||
},
|
},
|
||||||
transaction: {
|
transaction: {
|
||||||
fields: {
|
fields: {
|
||||||
|
|
|
@ -1,20 +1,45 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`snapshot test ENS component 1`] = `
|
exports[`snapshot test ENS component 1`] = `
|
||||||
<Connect(TabSection)
|
<ENSClass
|
||||||
isUnavailableOffline={true}
|
history={
|
||||||
>
|
Object {
|
||||||
<section
|
"action": "PUSH",
|
||||||
className="container"
|
"block": [Function],
|
||||||
>
|
"createHref": [Function],
|
||||||
<Switch>
|
"go": [Function],
|
||||||
<Route
|
"goBack": [Function],
|
||||||
exact={true}
|
"goForward": [Function],
|
||||||
path="/ens"
|
"length": 2,
|
||||||
render={[Function]}
|
"listen": [Function],
|
||||||
/>
|
"location": Object {
|
||||||
<Component />
|
"hash": "",
|
||||||
</Switch>
|
"key": "e08jz7",
|
||||||
</section>
|
"pathname": "/ens",
|
||||||
</Connect(TabSection)>
|
"search": "",
|
||||||
|
"state": Object {},
|
||||||
|
},
|
||||||
|
"push": [Function],
|
||||||
|
"replace": [Function],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
location={
|
||||||
|
Object {
|
||||||
|
"hash": "",
|
||||||
|
"key": "e08jz7",
|
||||||
|
"pathname": "/ens",
|
||||||
|
"search": "",
|
||||||
|
"state": Object {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match={
|
||||||
|
Object {
|
||||||
|
"isExact": false,
|
||||||
|
"params": Object {},
|
||||||
|
"path": "/ens",
|
||||||
|
"url": "/ens",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolveDomainRequested={[Function]}
|
||||||
|
/>
|
||||||
`;
|
`;
|
||||||
|
|
Loading…
Reference in New Issue