@@ -0,0 +1,181 @@
1+import { Nothing, Just } from 'folktale/maybe';
2+import React from 'react';
3+import * as azimuth from 'azimuth-js';
4+import * as ob from 'urbit-ob';
5+import {
6+ Button,
7+ H1,
8+ InnerLabel,
9+ PointInput,
10+ ValidatedSigil,
11+ TicketInput,
12+ Row,
13+ Col,
14+} from '../components/Base';
15+
16+import * as need from '../lib/need';
17+import { compose } from '../lib/lib';
18+import { urbitWalletFromTicket } from '../lib/wallet';
19+import { withHistory } from '../store/history';
20+import { withNetwork } from '../store/network';
21+
22+//TODO should be part of InputWithStatus component
23+const INPUT_STATUS = {
24+ SPIN: Symbol('SPIN'),
25+ GOOD: Symbol('GOOD'),
26+ FAIL: Symbol('FAIL'),
27+};
28+
29+//TODO support "advances" login methods. design under discussion...
30+class Login extends React.Component {
31+ constructor(props) {
32+ super(props);
33+
34+ this.state = {
35+ pointName: '',
36+ ticket: '',
37+ ticketStatus: Nothing(),
38+ wallet: Nothing(),
39+ };
40+
41+ this.handlePointNameInput = this.handlePointNameInput.bind(this);
42+ this.handleTicketInput = this.handleTicketInput.bind(this);
43+ this.verifyTicket = this.verifyTicket.bind(this);
44+ this.continue = this.continue.bind(this);
45+ }
46+
47+ componentDidMount() {
48+ //TODO deduce point name from URL if we can, prefill input if we found it
49+
50+ // const { networkType, setNetworkType } = useNetwork();
51+ console.log(this.props);
52+ // setNetworkType(this.props.initialNetworkType);
53+ }
54+
55+ handlePointNameInput(pointName) {
56+ if (pointName.length < 15) {
57+ this.setState({ pointName });
58+ this.verifyTicket(pointName, this.state.ticket);
59+ }
60+ }
61+
62+ handleTicketInput(ticket) {
63+ this.setState({ ticket });
64+ this.verifyTicket(this.state.pointName, ticket);
65+ }
66+
67+ //TODO maybe want to do this only on-go, because wallet derivation is slow...
68+ async verifyTicket(pointName, ticket) {
69+ if (!ob.isValidPatq(ticket) || !ob.isValidPatp(pointName)) {
70+ this.setState({ ticketStatus: Nothing(), wallet: Nothing() });
71+ return;
72+ }
73+ this.setState({ ticketStatus: Just(INPUT_STATUS.SPIN) });
74+ const contracts = need.contracts(this.props);
75+ const pointNumber = ob.patp2dec(pointName);
76+ const uhdw = await urbitWalletFromTicket(ticket, pointName);
77+ const isOwner = azimuth.azimuth.isOwner(
78+ contracts,
79+ pointNumber,
80+ uhdw.ownership.keys.address
81+ );
82+ const isTransferProxy = azimuth.azimuth.isTransferProxy(
83+ contracts,
84+ pointNumber,
85+ uhdw.ownership.keys.address
86+ );
87+ let newState = { wallet: Just(uhdw) };
88+ newState.ticketStatus =
89+ (await isOwner) || (await isTransferProxy)
90+ ? Just(INPUT_STATUS.GOOD)
91+ : Just(INPUT_STATUS.FAIL);
92+ this.setState(newState);
93+ }
94+
95+ canContinue() {
96+ // this is our only requirement, since we still want people with
97+ // non-standard wallet setups to be able to log in
98+ return Just.hasInstance(this.state.wallet);
99+ }
100+
101+ continue() {
102+ //TODO slightly different for other login methods
103+ this.props.setUrbitWallet(this.state.wallet);
104+ //TODO history.popAndPush(ROUTE_NAMES.POINT_HOME);
105+ }
106+
107+ render() {
108+ const pointInput = (
109+ <PointInput
110+ className="mono mt-8"
111+ prop-size="lg"
112+ prop-format="innerLabel"
113+ type="text"
114+ autoFocus
115+ value={this.state.pointName}
116+ onChange={this.handlePointNameInput}>
117+ <InnerLabel>{'Point'}</InnerLabel>
118+ <ValidatedSigil
119+ className={'tr-0 mt-05 mr-0 abs'}
120+ patp={this.state.pointName}
121+ size={68}
122+ margin={8}
123+ />
124+ </PointInput>
125+ );
126+
127+ const ticketStatus = this.state.ticketStatus.matchWith({
128+ Nothing: () => <span />,
129+ Just: status => {
130+ switch (status.value) {
131+ case INPUT_STATUS.SPIN:
132+ return <span>⋯</span>;
133+ case INPUT_STATUS.GOOD:
134+ return <span>✓</span>;
135+ case INPUT_STATUS.FAIL:
136+ return <span>𐄂</span>;
137+ default:
138+ throw new Error('weird input status ' + status.value);
139+ }
140+ },
141+ });
142+
143+ const ticketInput = (
144+ //TODO NewTicketInput component, that does dashes etc
145+ <TicketInput
146+ className="mono mt-8"
147+ prop-size="lg"
148+ prop-format="innerLabel"
149+ type="password"
150+ name="ticket"
151+ value={this.state.ticket}
152+ onChange={this.handleTicketInput}>
153+ <InnerLabel>{'Code'}</InnerLabel>
154+ {ticketStatus}
155+ </TicketInput>
156+ );
157+
158+ return (
159+ <Row>
160+ <Col>
161+ <H1>{'Login'}</H1>
162+
163+ {pointInput}
164+ {ticketInput}
165+
166+ <Button
167+ className={'mt-10'}
168+ disabled={!this.canContinue()}
169+ onClick={this.continue}>
170+ {'Go →'}
171+ </Button>
172+ </Col>
173+ </Row>
174+ );
175+ }
176+}
177+
178+export default compose(
179+ withHistory,
180+ withNetwork
181+)(Login);