@@ -1,198 +1,178 @@
1−import React from 'react';
2−import { Just, Nothing } from 'folktale/maybe';
1+import React, { useCallback, useEffect, useState } from 'react';
2+import { Nothing, Just } from 'folktale/maybe';
3+import cn from 'classnames';
4+import { Grid, Text, Input } from 'indigo-react';
35import * as azimuth from 'azimuth-js';
4−import * as ob from 'urbit-ob';
5−import * as need from '../lib/need';
6−
7−import { Button } from '../components/old/Base';
8−import { H1, P, Anchor } from '../components/old/Base';
9−import {
10− InnerLabel,
11− GalaxyInput,
12− AddressInput,
13− ValidatedSigil,
14− ShowBlockie,
15−} from '../components/old/Base';
16−import StatelessTransaction from '../components/old/StatelessTransaction';
17−
18−import { NETWORK_TYPES } from '../lib/network';
19−import { canDecodePatp } from '../lib/txn';
20−
21−import { isValidAddress, isZeroAddress } from '../lib/wallet';
22−
23−import { isValidGalaxy, compose } from '../lib/lib';
24−import { withNetwork } from '../store/network';
25−import { withWallet } from '../store/wallet';
26−import View from 'components/View';
27−
28−const buttonTriState = status => {
29− if (status === null) return 'blue';
30− if (status === false) return 'yellow';
31− if (status === true) return 'green';
32−};
33−
34−const buttonTriStateText = status => {
35− if (status === null) return 'Confirm Galaxy Availablility';
36− if (status === false) return 'Galaxy is Not Available';
37− if (status === true) return 'Galaxy is Available';
38−};
39−
40−class CreateGalaxy extends React.Component {
41− constructor(props) {
42− super(props);
43−
44− const galaxyOwner = need.addressFromWallet(props.wallet);
45−
46− this.state = {
47− galaxyOwner: galaxyOwner,
48− galaxyName: '',
49− isAvailable: null,
50− };
51−
52− this.handleAddressInput = this.handleAddressInput.bind(this);
53− this.handleGalaxyNameInput = this.handleGalaxyNameInput.bind(this);
54− this.confirmAvailability = this.confirmAvailability.bind(this);
55− this.createUnsignedTxn = this.createUnsignedTxn.bind(this);
56− this.statelessRef = React.createRef();
57− }
58−
59− handleGalaxyNameInput = galaxyName => {
60− this.setState({ galaxyName, isAvailable: null });
61− this.statelessRef.current.clearTxn();
62− };
63−
64− handleAddressInput = galaxyOwner => {
65− this.setState({ galaxyOwner });
66− this.statelessRef.current.clearTxn();
67− };
686
69− createUnsignedTxn = () => {
70− const { state, props } = this;
71− if (isValidAddress(state.galaxyOwner) === false) return Nothing();
72− if (state.isAvailable === false) return Nothing();
73− if (canDecodePatp(state.galaxyName) === false) return Nothing();
74− if (isValidGalaxy(state.galaxyName) === false) return Nothing();
7+import { useNetwork } from 'store/network';
8+import { useHistory } from 'store/history';
9+import { usePointCache } from 'store/pointCache';
7510
76− const validContracts = need.contracts(props.contracts);
11+import * as need from 'lib/need';
12+import { useAddressInput, useGalaxyInput } from 'lib/useInputs';
13+import useEthereumTransaction from 'lib/useEthereumTransaction';
14+import { GAS_LIMITS } from 'lib/constants';
15+import patp2dec from 'lib/patp2dec';
7716
78− const galaxyDec = ob.patp2dec(state.galaxyName);
79−
80− const txn = azimuth.ecliptic.createGalaxy(
81− validContracts,
82− galaxyDec,
83− state.galaxyOwner
84− );
85−
86− return Just(txn);
17+import ViewHeader from 'components/ViewHeader';
18+import MiniBackButton from 'components/MiniBackButton';
19+import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
20+import View from 'components/View';
21+import { isZeroAddress } from 'lib/wallet';
22+
23+function useCreateGalaxy() {
24+ const { contracts } = useNetwork();
25+ const { syncKnownPoint } = usePointCache();
26+
27+ const _contracts = need.contracts(contracts);
28+
29+ const [galaxy, setGalaxy] = useState();
30+
31+ const {
32+ construct: _construct,
33+ unconstruct,
34+ confirmed,
35+ inputsLocked,
36+ bind,
37+ } = useEthereumTransaction(GAS_LIMITS.DEFAULT);
38+
39+ const construct = useCallback(
40+ (galaxy, owner) => {
41+ setGalaxy(galaxy);
42+ _construct(azimuth.ecliptic.createGalaxy(_contracts, galaxy, owner));
43+ },
44+ [_construct, _contracts]
45+ );
46+
47+ useEffect(() => {
48+ if (confirmed) {
49+ syncKnownPoint(galaxy);
50+ }
51+ }, [confirmed, galaxy, syncKnownPoint]);
52+
53+ return {
54+ construct,
55+ unconstruct,
56+ confirmed,
57+ inputsLocked,
58+ bind,
8759 };
60+}
8861
89− confirmAvailability = async () => {
90− const { state, props } = this;
62+export default function CreateGalaxy() {
63+ const { pop } = useHistory();
64+ const { contracts } = useNetwork();
65+ const _contracts = need.contracts(contracts);
66+
67+ const [error, setError] = useState();
68+ const [isAvailable, setIsAvailable] = useState(Nothing());
69+
70+ const {
71+ construct,
72+ unconstruct,
73+ confirmed,
74+ inputsLocked,
75+ bind,
76+ } = useCreateGalaxy();
77+
78+ const [
79+ galaxyNameInput,
80+ { pass: validGalaxyName, syncPass: syncValidGalaxyName, value: galaxyName },
81+ // ^ we use value: here so our effect runs onChange
82+ ] = useGalaxyInput({
83+ disabled: inputsLocked,
84+ autoFocus: true,
85+ error:
86+ error ||
87+ isAvailable.matchWith({
88+ Nothing: () => 'Loading availabiliy...', // TODO: make async loading?
89+ Just: p => (p.value ? undefined : 'This galaxy is already owned'),
90+ }),
91+ });
92+
93+ const [ownerInput, { pass: validOwner, data: owner }] = useAddressInput({
94+ name: 'owner',
95+ label: `Ethereum Address`,
96+ disabled: inputsLocked,
97+ });
98+
99+ useEffect(() => {
100+ if (validGalaxyName && validOwner) {
101+ construct(patp2dec(galaxyName), owner);
102+ } else {
103+ unconstruct();
104+ }
105+ }, [owner, construct, unconstruct, validGalaxyName, validOwner, galaxyName]);
91106
92− if (canDecodePatp(state.galaxyName) === false) {
93− this.setState({ isAvailable: false });
107+ useEffect(() => {
108+ if (!syncValidGalaxyName || inputsLocked) {
94109 return;
95110 }
96111
97− const validContracts = need.contracts(props.contracts);
98−
99− const galaxyDec = ob.patp2dec(state.galaxyName);
100−
101− const currentOwner = await azimuth.azimuth.getOwner(
102− validContracts,
103− galaxyDec
104− );
105−
106− const available = isZeroAddress(currentOwner);
107−
108− this.setState({ isAvailable: available });
109− };
110−
111− render() {
112− const { props, state } = this;
113−
114− const validAddress = isValidAddress(state.galaxyOwner);
115− const validGalaxy = isValidGalaxy(state.galaxyName);
116−
117− const canGenerate =
118− validAddress === true &&
119− validGalaxy === true &&
120− state.isAvailable === true;
121−
122− const esvisible =
123− props.networkType === NETWORK_TYPES.ROPSTEN ||
124− props.networkType === NETWORK_TYPES.MAINNET;
125−
126− const esdomain =
127− props.networkType === NETWORK_TYPES.ROPSTEN
128− ? 'ropsten.etherscan.io'
129− : 'etherscan.io';
130−
131− return (
132− <View>
133− <H1> {'Create a Galaxy'} </H1>
134−
135− <P>
136− {'Enter the galaxy to create and the address that will own ' +
137− 'it (defaulting to this account, if not provided).'}
138− </P>
139−
140− <GalaxyInput
141− className="mono"
142− prop-size="lg"
143− prop-format="innerLabel"
144− autoFocus
145− placeholder="e.g. ~zod"
146− value={state.galaxyName}
147− onChange={v => this.handleGalaxyNameInput(v)}>
148− <InnerLabel>{'Galaxy Name'}</InnerLabel>
149− <ValidatedSigil
150− className="tr-0 mt-05 mr-0 abs"
151− patp={state.galaxyName}
152− size={68}
153− margin={8}
154− />
155− </GalaxyInput>
156−
157− <AddressInput
158− className="mono mt-8"
159− prop-size="lg"
160− prop-format="innerLabel"
161− value={state.galaxyOwner}
162− onChange={v => this.handleAddressInput(v)}>
163− <InnerLabel>{'Address that will own this galaxy'}</InnerLabel>
164− <ShowBlockie className={'mt-1'} address={state.galaxyOwner} />
165− </AddressInput>
166−
167− <Anchor
168− className={'mt-1'}
169− prop-size={'sm'}
170− prop-disabled={!validAddress || !esvisible}
171− target={'_blank'}
172− href={`https://${esdomain}/address/${state.galaxyOwner}`}>
173− {'View on Etherscan ↗'}
174− </Anchor>
175−
176− <Button
177− prop-size="lg wide"
178− className="mt-8"
179− prop-color={buttonTriState(state.isAvailable)}
180− disabled={!validGalaxy}
181− onClick={() => this.confirmAvailability()}>
182− {buttonTriStateText(state.isAvailable)}
183− </Button>
184−
185− <StatelessTransaction
186− ref={this.statelessRef}
187− canGenerate={canGenerate}
188− createUnsignedTxn={this.createUnsignedTxn}
112+ setIsAvailable(Nothing());
113+
114+ let cancelled = false;
115+
116+ (async () => {
117+ try {
118+ const currentOwner = await azimuth.azimuth.getOwner(
119+ _contracts,
120+ patp2dec(galaxyName)
121+ );
122+
123+ const isAvailable = isZeroAddress(currentOwner);
124+
125+ if (cancelled) {
126+ return;
127+ }
128+
129+ setIsAvailable(Just(isAvailable));
130+ } catch (error) {
131+ console.error(error);
132+ setError(error.message);
133+ setIsAvailable(Just(false));
134+ }
135+ })();
136+
137+ return () => (cancelled = true);
138+ }, [
139+ _contracts,
140+ galaxyName,
141+ inputsLocked,
142+ setIsAvailable,
143+ syncValidGalaxyName,
144+ ]);
145+
146+ return (
147+ <View inset>
148+ <Grid>
149+ <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
150+
151+ <Grid.Item full as={ViewHeader}>
152+ Create a Galaxy
153+ </Grid.Item>
154+
155+ {confirmed && (
156+ <Grid.Item
157+ full
158+ as={Text}
159+ className={cn('f5', {
160+ green3: confirmed,
161+ })}>
162+ {galaxyName} has been created and is owned by {owner}.
163+ </Grid.Item>
164+ )}
165+
166+ <Grid.Item full as={Input} {...galaxyNameInput} className="mt4" />
167+ <Grid.Item full as={Input} {...ownerInput} className="mb4" />
168+
169+ <Grid.Item
170+ full
171+ as={InlineEthereumTransaction}
172+ {...bind}
173+ onReturn={() => pop()}
189174 />
190− </View>
191− );
192− }
175+ </Grid>
176+ </View>
177+ );
193178}
194−
195−export default compose(
196− withNetwork,
197− withWallet
198−)(CreateGalaxy);