@@ -0,0 +1,298 @@
1+import { useCallback, useMemo, useRef, useState } from 'react';
2+import { Just } from 'folktale/maybe';
3+import * as kg from 'urbit-key-generation';
4+import { Grid, ToggleInput, AccessoryIcon } from 'indigo-react';
5+import { FORM_ERROR } from 'final-form';
6+import { Icon, Row } from '@tlon/indigo-react';
8+import { useWallet } from 'store/wallet';
9+import { usePointCursor } from 'store/pointCursor';
11+import { urbitWalletFromTicket } from 'lib/wallet';
12+import { WALLET_TYPES } from 'lib/constants';
13+import useImpliedPoint from 'lib/useImpliedPoint';
14+import useLoginView from 'lib/useLoginView';
15+import { patp2dec } from 'lib/patp2dec';
16+import { timeout } from 'lib/timeout';
17+import useRoller from 'lib/useRoller';
19+import BridgeForm from 'form/BridgeForm';
20+import Condition from 'form/Condition';
21+import { TicketInput, PassphraseInput, PointInput } from 'form/Inputs';
24+ buildCheckboxValidator,
27+ buildPassphraseValidator,
29+} from 'form/validators';
30+import FormError from 'form/FormError';
31+import SubmitButton from 'form/SubmitButton';
32+import { WARNING } from 'form/helpers';
33+import Window from 'components/L2/Window/Window';
34+import HeaderPane from 'components/L2/Window/HeaderPane';
35+import BodyPane from 'components/L2/Window/BodyPane';
36+import AdvancedOptions from 'components/L2/Headers/AdvancedOptions';
38+function TicketInputAccessory({ name }: { name: any }) {
44+ inverseLabel={<Icon className="icon" icon="Visible" />}
45+ label={<Icon className="icon" icon="Hidden" />}
51+interface TicketProps {
56+export default function Ticket({ className, goHome }: TicketProps) {
57+ useLoginView(WALLET_TYPES.TICKET);
59+ const { setUrbitWallet }: any = useWallet();
60+ const { setPointCursor }: any = usePointCursor();
61+ const impliedPoint = useImpliedPoint();
62+ const didWarn = useRef(false);
63+ const { getPoints } = useRoller();
65+ const [usePassphrase, setUsePassphrase] = useState(false);
66+ const [useShards, setUseShards] = useState(false);
68+ const validateForm = useCallback((values, errors) => {
69+ didWarn.current = false;
75+ if (values.useShards) {
76+ if (errors.shard1 || errors.shard2 || errors.shard3) {
79+ const empty = ['shard1', 'shard2', 'shard3'].filter(s => !values[s]);
80+ if (empty.length > 1) {
83+ errors[s] = 'Please provide at least two out of three shards.';
94+ const onSubmit = useCallback(
96+ const ticket = values.useShards
98+ [values.shard1, values.shard2, values.shard3].map(v =>
99+ v === '' ? undefined : v
105+ // const _contracts = need.contracts(contracts);
106+ const point = patp2dec(values.point);
108+ await timeout(16); // allow ui events to flush
109+ const urbitWallet = await urbitWalletFromTicket(
114+ const [ownedPoints, incomingPoints] = await Promise.all([
115+ getPoints('own', urbitWallet.ownership.keys.address),
116+ getPoints('transfer', urbitWallet.ownership.keys.address),
118+ const isOwner = ownedPoints.length !== 0;
119+ const isTransferProxy = incomingPoints.length !== 0;
121+ const noPermissions = !isOwner && !isTransferProxy;
123+ if (noPermissions && !didWarn.current) {
124+ didWarn.current = true;
127+ 'This wallet is not the owner or transfer proxy for this point.',
131+ setUrbitWallet(Just(urbitWallet));
132+ setPointCursor(Just(patp2dec(values.point)));
134+ console.error(error);
136+ [FORM_ERROR]: `Unable to derive wallet from ${
137+ values.useShards ? 'shards' : 'ticket'
142+ [setPointCursor, setUrbitWallet, getPoints]
145+ const validate = useMemo(
149+ useAdvanced: buildCheckboxValidator(),
150+ usePassphrase: buildCheckboxValidator(),
151+ useShards: buildCheckboxValidator(),
152+ point: buildPointValidator(4),
153+ ticket: buildPatqValidator(),
154+ shard1: buildShardValidator(),
155+ shard2: buildShardValidator(),
156+ shard3: buildShardValidator(),
157+ ticketHidden: buildCheckboxValidator(),
158+ passphrase: buildPassphraseValidator(),
165+ const initialValues = useMemo(
167+ point: impliedPoint || '',
168+ usePasshrase: false,
176+ const advancedOptions = [
178+ selected: usePassphrase,
179+ key: 'usePassphrase',
180+ label: 'Passphrase',
181+ onClick: () => setUsePassphrase(!usePassphrase),
184+ selected: useShards,
187+ onClick: () => setUseShards(!useShards),
191+ const ticketInputClass = usePassphrase && useShards ? 'ticket-input' : '';
194+ <Window className="master-ticket">
196+ <Row className="header-row">
197+ <h5>Master Ticket</h5>
198+ <AdvancedOptions options={advancedOptions} />
201+ <BodyPane className="login-body-pane">
202+ <Grid className="input-form" gap={1}>
206+ afterSubmit={goHome}
207+ className="flex-col justify-between"
208+ onValues={() => null}
209+ initialValues={initialValues}>
210+ {({ handleSubmit, values, submitting }) => (
211+ <Grid.Item full className="flex-col justify-between">
217+ className={ticketInputClass}
220+ <Condition when="useShards" is={false}>
222+ className={ticketInputClass}
225+ hidden={values.ticketHidden}
226+ accessory={<TicketInputAccessory name="ticketHidden" />}
228+ label="Master Ticket"
229+ style={{ height: 40 }}
236+ className={ticketInputClass}
239+ accessory={<TicketInputAccessory name="ticketHidden" />}
240+ hidden={values.ticketHidden}
245+ className={ticketInputClass}
248+ accessory={<TicketInputAccessory name="ticketHidden" />}
249+ hidden={values.ticketHidden}
254+ className={ticketInputClass}
257+ accessory={<TicketInputAccessory name="ticketHidden" />}
258+ hidden={values.ticketHidden}
268+ as={PassphraseInput}
270+ label="Wallet Passphrase"
271+ className={ticketInputClass}
275+ <Grid.Item full as={FormError} />
282+ handleSubmit={handleSubmit}>
283+ {(isWarning: boolean) =>