on master help sign in
Updating residents, sponsorship, spawning, UX issues
commita3a8f64f8b746e9f8b3d9b778c1066b3b8f39e9a authorWill Galebach <willbach@gmail.com> dateSat 4 Dec 2021, 08:36 (5 years ago) parent6f1e06f
26 files changed+375−351
src/Bridge.js+3−1 src/components/L2/Headers/L2PointHeader.tsx+2−2 src/components/L2/LoadingOverlay.tsx+1−1 src/components/Passport.js+12−2 src/components/SigilMini.scss+5−1 src/form/Inputs.tsx+3−2 src/indigo-react/components/Input.js+1−1 src/lib/constants.ts+5−5 src/lib/types/L2Transaction.ts+6−1 src/lib/types/Point.ts+8 src/lib/useRoller.ts+59−71 src/lib/utils/roller.ts+22−101 src/store/rollerStore.ts+4 src/views/Ops/Requests.tsx+19−23 src/views/Ops/ResidentRow.tsx+9−3 src/views/Ops/Residents.scss+10−1 src/views/Ops/Residents.tsx+17−22 src/views/Point/AddressButton.tsx+1−1 src/views/Point/IssueChild.tsx+42−17 src/views/Point/Point.tsx+13−3 src/views/Points.js+30−29 src/views/Transactions/TransactionHistory.scss+1 src/views/Transactions/TransactionHistory.tsx+10−8 src/views/Transactions/TransactionRow.tsx+9−8 src/views/UrbitID/Transfer.js+1−1 src/views/UrbitOS/ChangeSponsor.js+82−47
src/Bridge.js+3−1
@@ -21,6 +21,7 @@ import GlobalErrorBoundary from 'components/GlobalErrorBoundary';
2121import Modal from 'components/L2/Modal';
2222import { useRollerStore } from 'store/rollerStore';
2323import { Box } from '@tlon/indigo-react';
24+import LoadingOverlay from 'components/L2/LoadingOverlay';
2425
2526const INITIAL_NETWORK_TYPE = isRopsten
2627 ? NETWORK_TYPES.ROPSTEN
@@ -70,7 +71,7 @@ function useInitialRoutes() {
7071
7172function Bridge() {
7273 const initialRoutes = useInitialRoutes();
73− const { modalText, setModalText } = useRollerStore();
74+ const { loading, modalText, setModalText } = useRollerStore();
7475
7576 // full reload if the user changes their selected network in Metamask
7677 //
@@ -98,6 +99,7 @@ function Bridge() {
9899 <Modal show={modalText !== undefined} hide={() => setModalText()}>
99100 <Box width="280px">{modalText}</Box>
100101 </Modal>
102+ <LoadingOverlay loading={loading} />
101103 </Provider>
102104 </WithErrorBoundary>
103105 );
src/components/L2/Headers/L2PointHeader.tsx+2−2
@@ -26,7 +26,7 @@ const L2PointHeader = ({
2626 hideInvites = false,
2727 showMigrate = false,
2828}: L2PointHeaderProps) => {
29− const { point, invitesLoading } = useRollerStore();
29+ const { point, pointList, invitesLoading } = useRollerStore();
3030 const { pointCursor, setPointCursor }: any = usePointCursor();
3131
3232 const { popTo, push, names }: any = useHistory();
@@ -76,7 +76,7 @@ const L2PointHeader = ({
7676 return (
7777 <Row className="l2-point-header">
7878 <Row>
79− {!hideHome && (
79+ {!hideHome && pointList.length > 1 && (
8080 <HeaderButton className="home" icon="Home" onClick={goToHome} />
8181 )}
8282 <AccountsDropdown showMigrate={showMigrate} />
src/components/L2/LoadingOverlay.tsx+1−1
@@ -17,7 +17,7 @@ const LoadingOverlay = ({
1717 <Box className="loading-overlay">
1818 <Box className={`${text ? 'solid' : ''}`}>
1919 {!!text && <Box className="loader-text">{text}</Box>}
20− <LoadingSpinner foreground="white" background="rgba(0,0,0,0.3)" />
20+ <LoadingSpinner foreground="rgba(0,0,0,0.3)" background="white" />
2121 </Box>
2222 </Box>
2323 );
src/components/Passport.js+12−2
@@ -211,7 +211,13 @@ const Cell = props => {
211211 * point is number
212212 *
213213 */
214−function MiniPassport({ point, inverted, locked = false, ...rest }) {
214+function MiniPassport({
215+ point,
216+ inverted,
217+ locked = false,
218+ processing = false,
219+ ...rest
220+}) {
215221 useSyncDetails([point]);
216222 // const { wallet } = useWallet();
217223 // const address = need.addressFromWallet(wallet);
@@ -226,9 +232,13 @@ function MiniPassport({ point, inverted, locked = false, ...rest }) {
226232 makeSigil(50, patp, inverted ? ['white', contrast] : [contrast, 'white']),
227233 [inverted, patp, contrast]
228234 );
235+ const className = `sigil ${locked ? 'locked' : ''} ${
236+ processing ? 'processing' : ''
237+ }`;
238+
229239 return (
230240 <div {...rest} className="sigil-mini">
231− <div className={`sigil ${locked ? 'locked' : ''}`}>{sigil}</div>
241+ <div className={className}>{sigil}</div>
232242 <span className="patp">{patp}</span>
233243 {locked && <Icon color="white" icon="Locked" className="lock" />}
234244 </div>
src/components/SigilMini.scss+5−1
@@ -21,7 +21,11 @@
2121 border-radius: $large-border-radius;
2222
2323 &.locked {
24− background-color: $dark-gray;
24+ opacity: 0.6;
25+ }
26+
27+ &.processing {
28+ opacity: 0.6;
2529 }
2630 }
2731
src/form/Inputs.tsx+3−2
@@ -19,7 +19,8 @@ import {
1919 StatelessTextInputProps,
2020} from '@tlon/indigo-react';
2121
22−const PLACEHOLDER_POINT = '~sampel-ponnym';
22+const PLACEHOLDER_STAR = '~sampel';
23+const PLACEHOLDER_PLANET = '~sampel-ponnym';
2324const PLACEHOLDER_HD_PATH = DEFAULT_HD_PATH;
2425const PLACEHOLDER_MNEMONIC =
2526 'example crew supreme gesture quantum web media hazard theory mercy wing kitten';
@@ -122,7 +123,7 @@ export function PointInput({ name, size = 4, ...rest }) {
122123 type="text"
123124 label="Urbit ID"
124125 name={name}
125− placeholder={PLACEHOLDER_POINT}
126+ placeholder={size === 4 ? PLACEHOLDER_PLANET : PLACEHOLDER_STAR}
126127 autoCapitalize="none"
127128 autoComplete="off"
128129 autoCorrect="off"
src/indigo-react/components/Input.js+1−1
@@ -125,7 +125,7 @@ export default function Input({
125125
126126 {indicateError && (
127127 <Flex.Item as={ErrorText} className="mv1">
128− {error ? JSON.stringify(error) : submitError}
128+ {error ? error : submitError}
129129 </Flex.Item>
130130 )}
131131 </Flex>
src/lib/constants.ts+5−5
@@ -150,17 +150,17 @@ const TRANSACTION_TYPE_ICONS = {
150150};
151151
152152const TRANSACTION_TYPE_TITLES = {
153− adopt: 'Point Sponsored',
153+ adopt: 'Ship Sponsored',
154154 'cancel-escape': 'Escape Canceled',
155155 'configure-keys': 'Network Keys Configured',
156156 detach: 'Sponsee Detached',
157− escape: 'Sponsor Escaped',
158− reject: 'Sponsorhip Rejected',
157+ escape: 'Escaped Sponsor',
158+ reject: 'Sponsorship Rejected',
159159 'set-management-proxy': 'Management Proxy Changed',
160160 'set-spawn-proxy': 'Spawn Proxy Changed',
161161 'set-transfer-proxy': 'Transfer Proxy Changed',
162− spawn: 'Point Spawned',
163− 'transfer-point': 'Point Transferred',
162+ spawn: 'Ship Spawned',
163+ 'transfer-point': 'Ship Transferred',
164164};
165165const DUMMY_L2_ADDRESS = '0x1111111111111111111111111111111111111111';
166166
src/lib/types/L2Transaction.ts+6−1
@@ -5,13 +5,18 @@ export type TransactionType =
55 | 'transferPoint'
66 | 'spawn'
77 | 'configureKeys'
8− | 'escape';
8+ | 'escape'
9+ | 'cancelEscape'
10+ | 'adopt'
11+ | 'detach'
12+ | 'reject';
913
1014export interface SendL2Params {
1115 address?: string; // Destination address
1216 breach?: boolean;
1317 networkSeed?: any;
1418 newSponsor?: Ship;
19+ sponsee?: Ship;
1520 nonce: number;
1621 pointToSpawn?: number;
1722 proxy: Proxy;
src/lib/types/Point.ts+8
@@ -109,6 +109,7 @@ export default class Point {
109109 showInvites: boolean;
110110 isDefault: boolean;
111111 isPlaceholder: boolean;
112+ shouldDisplay: boolean;
112113
113114 constructor({
114115 value,
@@ -173,6 +174,13 @@ export default class Point {
173174 this.showInvites = this.canManage || this.canSpawn;
174175 this.isDefault = value === -1;
175176 this.isPlaceholder = isPlaceholder;
177+ this.shouldDisplay =
178+ !this.isPlaceholder &&
179+ !this.isDefault &&
180+ (this.isOwner ||
181+ this.isSpawnProxy ||
182+ this.isManagementProxy ||
183+ this.isTransferProxy);
176184 }
177185
178186 equals = (point: Point) => {
src/lib/useRoller.ts+59−71
@@ -19,7 +19,6 @@ import {
1919 setStoredInvites,
2020} from 'store/storage/roller';
2121import { usePointCache } from 'store/pointCache';
22−import { getOutgoingPoints } from 'views/Points';
2322
2423import { getUpdatedPointMessage, isPlanet, toL1Details } from './utils/point';
2524import { convertToInt } from './convertToInt';
@@ -30,9 +29,6 @@ import {
3029 submitL2Transaction,
3130 getTimeToNextBatch,
3231 registerProxyAddress,
33− adopt,
34− detach,
35− reject,
3632 isL2Spawn,
3733} from './utils/roller';
3834import { ETH_ZERO_ADDR, ROLLER_HOSTS, TEN_SECONDS } from './constants';
@@ -81,7 +77,6 @@ export default function useRoller() {
8177 const { pointCursor }: any = usePointCursor();
8278 const { web3, contracts }: any = useNetwork();
8379 const allPoints: any = usePointCache();
84− const controlledPoints = allPoints?.controlledPoints;
8580 const getDetails = allPoints?.getDetails;
8681
8782 const { setNextRoll } = useTimerStore();
@@ -90,6 +85,7 @@ export default function useRoller() {
9085 nextBatchTime,
9186 point,
9287 points,
88+ pointList,
9389 nextQuotaTime,
9490 removeInvite,
9591 setNextBatchTime,
@@ -263,7 +259,7 @@ export default function useRoller() {
263259 );
264260
265261 const spawnPoint = useCallback(
266− async (pointToSpawn: number) => {
262+ async (pointToSpawn: number, destinationAddress: string) => {
267263 if (quotaReached()) {
268264 return;
269265 }
@@ -291,7 +287,7 @@ export default function useRoller() {
291287
292288 await submitL2Transaction({
293289 api,
294− address: _wallet.address,
290+ address: destinationAddress,
295291 wallet: _wallet,
296292 ship: _point,
297293 proxy,
@@ -347,10 +343,11 @@ export default function useRoller() {
347343 curPoint
348344 );
349345
350− const outgoingPoints = getOutgoingPoints(
351− controlledPoints,
352− getDetails
353− ).filter((p: number) => isPlanet(p) && availablePoints.includes(p));
346+ const outgoingPoints = pointList
347+ .filter(
348+ ({ value, isPlanet }) => isPlanet && availablePoints.includes(value)
349+ )
350+ .map(({ value }) => value);
354351
355352 const invitePlanets = spawnedPoints
356353 .concat(
@@ -405,11 +402,10 @@ export default function useRoller() {
405402 api,
406403 authToken,
407404 contracts,
408− controlledPoints,
409− getDetails,
410405 pointCursor,
411406 ls,
412407 point,
408+ pointList,
413409 removeInvite,
414410 setInvites,
415411 setInvitesLoading,
@@ -789,75 +785,40 @@ export default function useRoller() {
789785 [api, connector, point, wallet, web3, walletType, quotaReached]
790786 );
791787
792− const adoptPoint = useCallback(
793− async (ship: Ship) => {
788+ const changeSponsorship = useCallback(
789+ async (sponsee: Ship, type: 'adopt' | 'detach' | 'reject') => {
794790 if (quotaReached()) {
795791 return;
796792 }
797793
798794 const _wallet = wallet.getOrElse(null);
795+ const _web3 = web3.getOrElse(null);
796+ const ship = point.value;
799797
800798 const proxy = point.getManagerProxy();
801799
802800 if (proxy === undefined)
803801 throw new Error("Error: Address doesn't match expected proxy");
804802
805− const nonce = await api.getNonce({ ship: point.value, proxy });
806− const txHash = await adopt(api, _wallet, point.value, proxy, nonce, ship);
807−
808− return api.getPendingTx(txHash);
809− },
810− [api, point, wallet, quotaReached]
811− );
812−
813− const kickPoint = useCallback(
814− async (ship: Ship) => {
815− if (quotaReached()) {
816− return;
817− }
818−
819− if (point.isDefault) {
820− throw new Error('Internal Error: point is default');
821− }
822−
823− const sponsor = point.value;
824− const _wallet = wallet.getOrElse(null);
825− const proxy = point.getManagerProxy();
826−
827− if (proxy === undefined)
828− throw new Error("Error: Address doesn't match expected proxy");
829−
830− const nonce = await api.getNonce({ ship: sponsor, proxy });
831− const txHash = await detach(api, _wallet, sponsor, proxy, nonce, ship);
832−
833− return api.getPendingTx(txHash);
834− },
835− [api, point, wallet, quotaReached]
836− );
837−
838− const rejectPoint = useCallback(
839− async (ship: Ship) => {
840− if (quotaReached()) {
841− return;
842− }
843−
844− if (point.isDefault) {
845− throw new Error('Internal Error: point is default');
846− }
847−
848− const sponsor = point.value;
849− const _wallet = wallet.getOrElse(null);
850− const proxy = point.getManagerProxy();
851−
852− if (proxy === undefined)
853− throw new Error("Error: Address doesn't match expected proxy");
854−
855− const nonce = await api.getNonce({ ship: sponsor, proxy });
856− const txHash = await reject(api, _wallet, sponsor, proxy, nonce, ship);
803+ const nonce = await api.getNonce({ ship, proxy });
804+ // const txHash = await adopt(api, _wallet, point.value, proxy, nonce, ship);
805+ const txHash = await submitL2Transaction({
806+ api,
807+ wallet: _wallet,
808+ ship,
809+ sponsee,
810+ proxy,
811+ nonce,
812+ address: _wallet.address,
813+ type,
814+ walletType,
815+ web3: _web3,
816+ connector,
817+ });
857818
858819 return api.getPendingTx(txHash);
859820 },
860− [api, point, wallet, quotaReached]
821+ [api, point, wallet, walletType, web3, connector, quotaReached]
861822 );
862823
863824 const transferPoint = useCallback(
@@ -960,6 +921,34 @@ export default function useRoller() {
960921 [api, point, sendL2Transaction, wallet]
961922 );
962923
924+ const cancelEscape = useCallback(async () => {
925+ if (point.isDefault) {
926+ throw new Error('Internal Error: point is default');
927+ }
928+
929+ const curPoint = point.value;
930+ const _wallet = wallet.getOrElse(null);
931+
932+ if (!_wallet) {
933+ // not using need because we want a custom error
934+ throw new Error('Internal Error: Missing Wallet');
935+ }
936+
937+ const proxy = point.getManagerProxy();
938+
939+ if (proxy === undefined)
940+ throw new Error("Error: Address doesn't match proxy");
941+
942+ const nonce = await api.getNonce({ ship: curPoint, proxy });
943+
944+ return sendL2Transaction({
945+ nonce,
946+ proxy,
947+ type: 'cancelEscape',
948+ ship: curPoint,
949+ });
950+ }, [api, point, sendL2Transaction, wallet]);
951+
963952 const getPointsDetails = useCallback(
964953 async (
965954 ownedPoints: number[],
@@ -1029,9 +1018,10 @@ export default function useRoller() {
10291018 }, [point, nextBatchTime, getPendingTransactions, getInvites]); // eslint-disable-line react-hooks/exhaustive-deps
10301019
10311020 return {
1032− adoptPoint,
10331021 api,
1022+ changeSponsorship,
10341023 changeSponsor,
1024+ cancelEscape,
10351025 checkForUpdates,
10361026 config,
10371027 configureNetworkingKeys,
@@ -1040,10 +1030,8 @@ export default function useRoller() {
10401030 getPoints,
10411031 getPointsDetails,
10421032 getPendingTransactions,
1043− kickPoint,
10441033 ls,
10451034 performL2Reticket,
1046− rejectPoint,
10471035 setProxyAddress,
10481036 spawnPoint,
10491037 transferPoint,
src/lib/utils/roller.ts+22−101
@@ -83,41 +83,6 @@ export const generateHashAndSign = async (
8383 }
8484};
8585
86−export const spawn = async (
87− api: RollerRPCAPI,
88− wallet: any,
89− _point: number,
90− proxy: string,
91− nonce: number,
92− planet: number,
93− walletType: symbol,
94− web3: any,
95− connector: WalletConnect | null
96−) => {
97− const from = {
98− ship: _point, //ship that is spawning the planet
99− proxy,
100− };
101−
102− const data = {
103− address: wallet.address,
104− ship: planet, // ship to spawn
105− };
106−
107− const sig = await generateHashAndSign(
108− api,
109− wallet,
110− nonce,
111− from,
112− 'spawn',
113− data,
114− walletType,
115− web3,
116− connector
117− );
118− return api.spawn(sig, from, wallet.address, data);
119−};
120−
12186export const configureKeys = async (
12287 api: RollerRPCAPI,
12388 wallet: any,
@@ -205,72 +170,6 @@ export const transferPointRequest = async (
205170 return api.transferPoint(sig, from, wallet.address, data);
206171};
207172
208−export const adopt = async (
209− api: RollerRPCAPI,
210− _wallet: any,
211− sponsor: Ship,
212− proxy: string,
213− nonce: number,
214− ship: Ship
215−) => {
216− const from = {
217− ship: sponsor, // ship that is adopting the planet
218− proxy,
219− };
220−
221− const data = {
222− ship, // ship to adopt
223− };
224−
225− const hash = await api.getUnsignedTx(nonce, from, 'detach', data);
226− const sSig = signTransactionHash(hash, _wallet.privateKey);
227− return api.adopt(sSig, from, _wallet.address, data);
228−};
229−
230−export const detach = async (
231− api: RollerRPCAPI,
232− _wallet: any,
233− sponsor: Ship,
234− proxy: string,
235− nonce: number,
236− ship: Ship
237−) => {
238− const from = {
239− ship: sponsor, // ship that is detaching the planet
240− proxy,
241− };
242−
243− const data = {
244− ship, // ship to detach
245− };
246−
247− const hash = await api.getUnsignedTx(nonce, from, 'detach', data);
248− const sSig = signTransactionHash(hash, _wallet.privateKey);
249− return api.detach(sSig, from, _wallet.address, data);
250−};
251−
252−export const reject = async (
253− api: RollerRPCAPI,
254− _wallet: any,
255− sponsor: Ship,
256− proxy: string,
257− nonce: number,
258− ship: Ship
259−) => {
260− const from = {
261− ship: sponsor, // ship that is rejecting the planet
262− proxy,
263− };
264−
265− const data = {
266− ship, // ship to reject
267− };
268−
269− const hash = await api.getUnsignedTx(nonce, from, 'detach', data);
270− const sSig = signTransactionHash(hash, _wallet.privateKey);
271− return api.reject(sSig, from, _wallet.address, data);
272−};
273−
274173const proxyType = (proxy: Proxy) => {
275174 switch (proxy) {
276175 case 'manage':
@@ -346,6 +245,8 @@ const getDataAndMethod = (args: L2TransactionArgs) => {
346245 reset,
347246 pointToSpawn,
348247 newSponsor,
248+ sponsee,
249+ ship,
349250 } = args;
350251 const result: TransactionData = { data: { address }, method: () => null };
351252
@@ -384,6 +285,26 @@ const getDataAndMethod = (args: L2TransactionArgs) => {
384285 result.data.ship = newSponsor;
385286 delete result.data.address;
386287 return result;
288+ case 'cancelEscape':
289+ result.method = api.cancelEscape;
290+ result.data.ship = ship;
291+ delete result.data.address;
292+ return result;
293+ case 'adopt':
294+ result.method = api.adopt;
295+ result.data.ship = sponsee;
296+ delete result.data.address;
297+ return result;
298+ case 'detach':
299+ result.method = api.detach;
300+ result.data.ship = sponsee;
301+ delete result.data.address;
302+ return result;
303+ case 'reject':
304+ result.method = api.reject;
305+ result.data.ship = sponsee;
306+ delete result.data.address;
307+ return result;
387308 }
388309};
389310
src/store/rollerStore.ts+4
@@ -19,6 +19,7 @@ export const EMPTY_POINT = new Point({
1919});
2020
2121export interface RollerStore {
22+ loading: boolean;
2223 nextBatchTime: number;
2324 nextQuotaTime: number;
2425 pendingTransactions: PendingTransaction[];
@@ -30,6 +31,7 @@ export interface RollerStore {
3031 recentlyCompleted: number;
3132 inviteGeneratingNum: number;
3233 invitesLoading: boolean;
34+ setLoading: (loading: boolean) => void;
3335 removeInvite: (point: number, planet: number) => void;
3436 setInvites: (points: number, invites: Invite[]) => void;
3537 setInviteGeneratingNum: (numGenerating: number) => void;
@@ -45,6 +47,7 @@ export interface RollerStore {
4547}
4648
4749export const useRollerStore = create<RollerStore>(set => ({
50+ loading: false,
4851 nextBatchTime: new Date().getTime() + HOUR,
4952 nextQuotaTime: new Date().getTime() + 24 * HOUR,
5053 pendingTransactions: [],
@@ -55,6 +58,7 @@ export const useRollerStore = create<RollerStore>(set => ({
5558 recentlyCompleted: 0,
5659 inviteGeneratingNum: 0,
5760 invitesLoading: false,
61+ setLoading: (loading: boolean) => set(() => ({ loading })),
5862 setNextBatchTime: (nextBatchTime: number) => set(() => ({ nextBatchTime })),
5963 setNextQuotaTime: (nextQuotaTime: number) => set(() => ({ nextQuotaTime })),
6064 setPendingTransactions: (pendingTransactions: PendingTransaction[]) =>
src/views/Ops/Requests.tsx+19−23
@@ -1,5 +1,9 @@
1−import * as need from 'lib/need';
2−import { Box, LoadingSpinner, Row } from '@tlon/indigo-react';
1+import { useCallback, useEffect, useState } from 'react';
2+import { Box, Row } from '@tlon/indigo-react';
3+import { Ship } from '@urbit/roller-api';
4+
5+import { useRollerStore } from 'store/rollerStore';
6+import useRoller from 'lib/useRoller';
37
48import Window from 'components/L2/Window/Window';
59import HeaderPane from 'components/L2/Window/HeaderPane';
@@ -7,21 +11,15 @@ import BodyPane from 'components/L2/Window/BodyPane';
711import { useLocalRouter } from 'lib/LocalRouter';
812import View from 'components/View';
913import L2BackHeader from 'components/L2/Headers/L2BackHeader';
10−import { usePointCursor } from 'store/pointCursor';
11−import { useCallback, useEffect, useState } from 'react';
1214
13−import './Requests.scss';
14−import useRoller from 'lib/useRoller';
15−import { Ship } from '@urbit/roller-api';
1615import { RequestRow } from './RequestRow';
16+import './Requests.scss';
1717
1818export const Requests = () => {
1919 const { pop }: any = useLocalRouter();
20− const { pointCursor } = usePointCursor();
21− const point = need.point(pointCursor);
22− const { api, adoptPoint, rejectPoint } = useRoller();
20+ const { point, setLoading } = useRollerStore();
21+ const { api, changeSponsorship } = useRoller();
2322 const [requestingPoints, setRequestingPoints] = useState<Ship[]>([]);
24− const [loading, setLoading] = useState<boolean>(false);
2523
2624 const fetchRequests = useCallback(async () => {
2725 if (!point) {
@@ -29,28 +27,29 @@ export const Requests = () => {
2927 }
3028
3129 setLoading(true);
32− const { requests } = await api.getSponsoredPoints(point);
33−
30+ const { requests } = await api.getSponsoredPoints(point.value);
3431 setRequestingPoints((requests || []).sort());
3532 setLoading(false);
36− }, [api, point]);
33+ }, [api, point, setLoading]);
3734
3835 const handleAcceptClick = useCallback(
3936 async (point: Ship) => {
4037 setLoading(true);
41− await adoptPoint(point);
38+ await changeSponsorship(point, 'adopt');
4239 await fetchRequests();
40+ setLoading(false);
4341 },
44− [adoptPoint, fetchRequests]
42+ [changeSponsorship, fetchRequests, setLoading]
4543 );
4644
4745 const handleRejectClick = useCallback(
4846 async (point: Ship) => {
4947 setLoading(true);
50− await rejectPoint(point);
48+ await changeSponsorship(point, 'reject');
5149 await fetchRequests();
50+ setLoading(false);
5251 },
53− [rejectPoint, fetchRequests]
52+ [changeSponsorship, fetchRequests, setLoading]
5453 );
5554
5655 useEffect(() => {
@@ -75,14 +74,11 @@ export const Requests = () => {
7574 </HeaderPane>
7675 <BodyPane>
7776 <Box className="content-container">
78− {loading ? (
79− <Box className={'loading'}>
80− <LoadingSpinner />
81− </Box>
82− ) : requestingPoints.length > 0 ? (
77+ {requestingPoints.length > 0 ? (
8378 <ul className="requests-list">
8479 {requestingPoints.map(sp => (
8580 <RequestRow
81+ key={sp}
8682 point={sp}
8783 onAccept={handleAcceptClick}
8884 onReject={handleRejectClick}
src/views/Ops/ResidentRow.tsx+9−3
@@ -48,9 +48,15 @@ export const ResidentRow = ({ point, onKick }: ResidentRowProps) => {
4848 onClick={() => setShowDropdown(!showDropdown)}
4949 />
5050 {showDropdown && (
51− <Box className={'kick-dropdown'} onClick={handleKickClick}>
52− <Text>Kick</Text>
53− </Box>
51+ <>
52+ <Box
53+ className="close-background"
54+ onClick={() => setShowDropdown(false)}
55+ />
56+ <Box className="kick-dropdown" onClick={handleKickClick}>
57+ <Text>Kick</Text>
58+ </Box>
59+ </>
5460 )}
5561 </Box>
5662 </li>
src/views/Ops/Residents.scss+10−1
@@ -57,7 +57,7 @@
5757 }
5858
5959 .kick-dropdown {
60− z-index: 9999;
60+ z-index: 2;
6161 display: flex;
6262 flex-direction: column;
6363 align-items: flex-start;
@@ -113,4 +113,13 @@
113113 }
114114 }
115115 }
116+
117+ .close-background {
118+ z-index: 1;
119+ position: fixed;
120+ top: 0;
121+ bottom: 0;
122+ right: 0;
123+ left: 0;
124+ }
116125}
src/views/Ops/Residents.tsx+17−22
@@ -1,27 +1,25 @@
1−import * as need from 'lib/need';
2−import { Box, LoadingSpinner, Row } from '@tlon/indigo-react';
1+import { useCallback, useEffect, useState } from 'react';
2+import { Box, Row } from '@tlon/indigo-react';
3+import { Ship } from '@urbit/roller-api';
4+
5+import { useRollerStore } from 'store/rollerStore';
6+import useRoller from 'lib/useRoller';
7+import { useLocalRouter } from 'lib/LocalRouter';
38
49import Window from 'components/L2/Window/Window';
510import HeaderPane from 'components/L2/Window/HeaderPane';
611import BodyPane from 'components/L2/Window/BodyPane';
7−import { useLocalRouter } from 'lib/LocalRouter';
812import View from 'components/View';
913import L2BackHeader from 'components/L2/Headers/L2BackHeader';
10−import { usePointCursor } from 'store/pointCursor';
11−import { useCallback, useEffect, useMemo, useState } from 'react';
1214
13−import './Residents.scss';
14−import useRoller from 'lib/useRoller';
15−import { Ship } from '@urbit/roller-api';
1615import { ResidentRow } from './ResidentRow';
16+import './Residents.scss';
1717
1818export const Residents = () => {
1919 const { pop }: any = useLocalRouter();
20− const { pointCursor } = usePointCursor();
21− const point = useMemo(() => need.point(pointCursor), [pointCursor]);
22− const { api, kickPoint } = useRoller();
20+ const { point, setLoading } = useRollerStore();
21+ const { api, changeSponsorship } = useRoller();
2322 const [sponsoredPoints, setSponsoredPoints] = useState<Ship[]>([]);
24− const [loading, setLoading] = useState<boolean>(false);
2523
2624 const fetchResidents = useCallback(async () => {
2725 if (!point) {
@@ -30,18 +28,19 @@ export const Residents = () => {
3028
3129 setLoading(true);
3230 // TODO: remove this hack once the API is updated
33− const { residentes, residents } = await api.getSponsoredPoints(point);
31+ const { residentes, residents } = await api.getSponsoredPoints(point.value);
3432 setSponsoredPoints((residents || residentes || []).sort());
3533 setLoading(false);
36− }, [api, point]);
34+ }, [api, point, setLoading]);
3735
3836 const handleKick = useCallback(
3937 async (point: Ship) => {
4038 setLoading(true);
41− await kickPoint(point);
39+ await changeSponsorship(point, 'detach');
4240 await fetchResidents();
41+ setLoading(false);
4342 },
44− [fetchResidents, kickPoint]
43+ [fetchResidents, changeSponsorship, setLoading]
4544 );
4645
4746 useEffect(() => {
@@ -66,14 +65,10 @@ export const Residents = () => {
6665 </HeaderPane>
6766 <BodyPane>
6867 <Box className="content-container">
69− {loading ? (
70− <Box className={'loading'}>
71− <LoadingSpinner />
72− </Box>
73− ) : sponsoredPoints.length > 0 ? (
68+ {sponsoredPoints.length > 0 ? (
7469 <ul className="residents-list">
7570 {sponsoredPoints.map(sp => (
76− <ResidentRow point={sp} onKick={handleKick} />
71+ <ResidentRow key={sp} point={sp} onKick={handleKick} />
7772 ))}
7873 </ul>
7974 ) : (
src/views/Point/AddressButton.tsx+1−1
@@ -22,7 +22,7 @@ export const AddressButton = ({ children, ...rest }: AddressButtonProps) => {
2222 );
2323
2424 return (
25− <Button onClick={onClick} {...rest} className={'secondary'}>
25+ <Button onClick={onClick} {...rest} className={'secondary'} type="button">
2626 {children}
2727 </Button>
2828 );
src/views/Point/IssueChild.tsx+42−17
@@ -7,6 +7,7 @@ import BodyPane from 'components/L2/Window/BodyPane';
77
88import * as azimuth from 'azimuth-js';
99import ob from 'urbit-ob';
10+import { Box, Icon, Row, Text } from '@tlon/indigo-react';
1011
1112import { useNetwork } from 'store/network';
1213import { usePointCache } from 'store/pointCache';
@@ -20,9 +21,15 @@ import { patp2dec } from 'lib/patp2dec';
2021import { getSpawnCandidate } from 'lib/child';
2122import { useLocalRouter } from 'lib/LocalRouter';
2223import { validateChild } from 'lib/validators';
24+import { convertToInt } from 'lib/convertToInt';
25+import { isGalaxy } from 'lib/utils/point';
26+import useRoller from 'lib/useRoller';
2327
2428import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
2529import View from 'components/View';
30+import CopiableAddress from 'components/copiable/CopiableAddress';
31+import L2BackHeader from 'components/L2/Headers/L2BackHeader';
32+import LoadingOverlay from 'components/L2/LoadingOverlay';
2633import { PointInput, AddressInput } from 'form/Inputs';
2734import {
2835 composeValidator,
@@ -32,16 +39,10 @@ import {
3239} from 'form/validators';
3340import BridgeForm from 'form/BridgeForm';
3441import FormError from 'form/FormError';
35−import CopiableAddress from 'components/copiable/CopiableAddress';
36−import { convertToInt } from 'lib/convertToInt';
37−import { Box, Icon, Row, Text } from '@tlon/indigo-react';
38−import { isGalaxy } from 'lib/utils/point';
39−import L2BackHeader from 'components/L2/Headers/L2BackHeader';
4042
41−import './IssueChild.scss';
4243import { PatpBadge } from './PatpBadge';
43−import useRoller from 'lib/useRoller';
4444import { AddressButton } from './AddressButton';
45+import './IssueChild.scss';
4546
4647export function useIssueChild() {
4748 const { contracts }: any = useNetwork();
@@ -76,7 +77,9 @@ export default function IssueChild() {
7677 );
7778 const [candidates, setCandidates] = useState<string[]>([]);
7879 const [pointToSpawn, setPointToSpawn] = useState<number | null>();
80+ const [destinationAddress, setDestinationAddress] = useState<string | null>();
7981 const [l2SpawnSent, setL2SpawnSent] = useState(false);
82+ const [loading, setLoading] = useState(false);
8083
8184 const fetchAvailablePoints = useCallback(async () => {
8285 const available = await api.getUnspawned(_point);
@@ -112,13 +115,15 @@ export default function IssueChild() {
112115 }, [fetchAvailablePoints, shuffle]);
113116
114117 const spawnNewPoint = useCallback(async () => {
115− if (pointToSpawn) {
116− await spawnPoint(pointToSpawn);
118+ if (pointToSpawn && destinationAddress) {
119+ setLoading(true);
120+ await spawnPoint(pointToSpawn, destinationAddress);
117121 checkForUpdates(pointToSpawn);
118122 setPointToSpawn(null);
119123 setL2SpawnSent(true);
124+ setLoading(false);
120125 }
121− }, [pointToSpawn, spawnPoint, checkForUpdates]);
126+ }, [pointToSpawn, destinationAddress, spawnPoint, checkForUpdates]);
122127
123128 const {
124129 isDefaultState,
@@ -173,16 +178,37 @@ export default function IssueChild() {
173178 const onValues = useCallback(
174179 ({ valid, values }) => {
175180 if (valid) {
176− const pointDecimal = patp2dec(values.point);
177− construct(pointDecimal, values.owner);
178− setPointToSpawn(pointDecimal);
181+ console.log('VALID')
182+ construct(patp2dec(values.point), values.owner);
179183 } else {
180184 unconstruct();
181185 }
186+
187+ try {
188+ setPointToSpawn(patp2dec(values.point));
189+ } catch (err) {
190+ setPointToSpawn(null);
191+ }
192+
193+ setDestinationAddress(values.owner);
182194 },
183195 [construct, unconstruct]
184196 );
185197
198+ const validPoint =
199+ pointToSpawn && availablePoints && availablePoints.has(pointToSpawn);
200+ const validAddress =
201+ destinationAddress && !buildAddressValidator()(destinationAddress);
202+
203+ const getButtonText = () => {
204+ if (!validPoint) {
205+ return `Please enter a valid ${point.isStar ? 'planet' : 'star'}`;
206+ } else if (!validAddress) {
207+ return `Please enter a valid destination address`;
208+ }
209+ return `Spawn ${ob.patp(pointToSpawn)}`;
210+ };
211+
186212 return (
187213 <View
188214 pop={pop}
@@ -276,11 +302,9 @@ export default function IssueChild() {
276302 className="mt4"
277303 center
278304 solid
279− disabled={!pointToSpawn}
305+ disabled={!validPoint || !validAddress}
280306 onClick={spawnNewPoint}>
281− {pointToSpawn
282− ? `Spawn ${ob.patp(pointToSpawn)}`
283− : `Please enter a ${point.isStar ? 'planet' : 'star'}`}
307+ {getButtonText()}
284308 </Grid.Item>
285309 ) : (
286310 <Grid.Item
@@ -296,6 +320,7 @@ export default function IssueChild() {
296320 </Box>
297321 </BodyPane>
298322 </Window>
323+ <LoadingOverlay loading={loading} />
299324 </View>
300325 );
301326}
src/views/Point/Point.tsx+13−3
@@ -66,12 +66,14 @@ export default function Point() {
6666 const getTransactions = async () => {
6767 setLoading(true);
6868 getPendingTransactions();
69− getInvites();
69+ if (point.isStar && (point.canSpawn || point.canManage)) {
70+ getInvites();
71+ }
7072 setTimeout(() => setLoading(false), 100);
7173 };
7274
7375 await getTransactions();
74− }, [getInvites, getPendingTransactions, setLoading]);
76+ }, [getInvites, getPendingTransactions, setLoading]); // eslint-disable-line react-hooks/exhaustive-deps
7577
7678 useEffect(() => {
7779 loadL1Info();
@@ -166,8 +168,16 @@ export default function Point() {
166168 ? 'Transfer Proxy Changed'
167169 : pendingTx.rawTx?.tx?.type === 'set-spawn-proxy'
168170 ? 'Spawn Proxy Changed'
171+ : pendingTx.rawTx?.tx?.type === 'escape'
172+ ? 'Change Sponsor'
173+ : pendingTx.rawTx?.tx?.type === 'cancel-escape'
174+ ? 'Cancel Escape'
175+ : pendingTx.rawTx?.tx?.type === 'adopt'
176+ ? 'Accept Sponsorship'
177+ : pendingTx.rawTx?.tx?.type === 'reject'
178+ ? 'Reject Sponsorship'
169179 : pendingTx.rawTx?.tx?.type === 'detach'
170− ? 'Sponsee Detached'
180+ ? 'Detach Sponsee'
171181 : ''}
172182 </div>
173183 <div className="rollup-timer">
src/views/Points.js+30−29
@@ -28,7 +28,6 @@ import NavHeader from 'components/NavHeader';
2828import L2PointHeader from 'components/L2/Headers/L2PointHeader';
2929import IncomingPoint from 'components/L2/Points/IncomingPoint';
3030import LoadingOverlay from 'components/L2/LoadingOverlay';
31−import { onlyUnique } from 'lib/utils/array';
3231
3332export const maybeGetResult = (obj, key, defaultValue) =>
3433 obj.matchWith({
@@ -43,31 +42,6 @@ export const maybeGetResult = (obj, key, defaultValue) =>
4342export const hasTransferProxy = details =>
4443 !isZeroAddress(details.transferProxy);
4544
46−export const getOutgoingPoints = (controlledPoints, getDetails) => {
47− return controlledPoints
48− .chain(points =>
49− points.matchWith({
50− Error: () => Nothing(),
51− Ok: c => {
52− const points = c.value.ownedPoints.map(point =>
53− getDetails(point).chain(details =>
54− Just({ point: point, has: hasTransferProxy(details) })
55− )
56− );
57− if (points.every(p => Just.hasInstance(p))) {
58− const outgoing = points
59− .filter(p => p.value.has)
60− .map(p => p.value.point);
61− return Just(outgoing);
62− } else {
63− return Nothing();
64− }
65− },
66− })
67− )
68− .getOrElse([]);
69−};
70−
7145export const isLocked = (details, contracts) =>
7246 details.owner === contracts.linearSR ||
7347 details.owner === contracts.conditionalSR;
@@ -77,6 +51,7 @@ const PointList = function({
7751 className,
7852 actions,
7953 locked = false,
54+ processing = false,
8055 ...rest
8156}) {
8257 const { setPointCursor } = usePointCursor();
@@ -91,9 +66,10 @@ const PointList = function({
9166 <Flex col>
9267 <Passport.Mini
9368 locked={locked}
69+ processing={processing}
9470 point={point}
9571 onClick={
96− locked
72+ locked || processing
9773 ? undefined
9874 : () => {
9975 setPointCursor(Just(point));
@@ -143,7 +119,11 @@ export default function Points() {
143119 const { syncStarReleaseDetails, starReleaseDetails } = useStarReleaseCache();
144120
145121 const outgoingPoints = useMemo(
146− () => pointList.filter(({ isTransferProxySet }) => isTransferProxySet),
122+ () =>
123+ pointList.filter(
124+ ({ isTransferProxySet, isTransferProxy }) =>
125+ isTransferProxySet && !isTransferProxy
126+ ),
147127 [pointList]
148128 );
149129
@@ -237,7 +217,14 @@ export default function Points() {
237217 const lockedPoints = maybeLockedPoints.getOrElse([]);
238218
239219 const allPoints = pointList
240− .filter(({ isTransferProxySet }) => !isTransferProxySet)
220+ .filter(
221+ ({ isTransferProxySet, shouldDisplay }) =>
222+ !isTransferProxySet && shouldDisplay
223+ )
224+ .map(({ value }) => value);
225+
226+ const processingPoints = pointList
227+ .filter(({ shouldDisplay }) => !shouldDisplay)
241228 .map(({ value }) => value);
242229
243230 const displayEmptyState =
@@ -393,6 +380,20 @@ export default function Points() {
393380 </Grid.Item>
394381 )}
395382
383+ {processingPoints.length > 0 && (
384+ <Grid.Item full as={Grid} gap={1} className="mv6">
385+ <Grid.Item full as={H5}>
386+ Awaiting L2 Rollup
387+ </Grid.Item>
388+ <Grid.Item
389+ full
390+ as={PointList}
391+ points={processingPoints}
392+ processing
393+ />
394+ </Grid.Item>
395+ )}
396+
396397 <Footer>
397398 <Grid>
398399 <Grid.Divider />
src/views/Transactions/TransactionHistory.scss+1
@@ -253,6 +253,7 @@
253253 background-color: $rollup-yellow;
254254 color: #000;
255255 width: 100px;
256+ width: 105px;
256257
257258 svg {
258259 path {
src/views/Transactions/TransactionHistory.tsx+10−8
@@ -33,7 +33,7 @@ const TransactionHistory = () => {
3333 const [loading, setLoading] = useState(true);
3434 const [transactions, setTransactions] = useState<RollerTransaction[]>([]);
3535 const [dropdownOpen, setDropdownOpen] = useState<boolean>(false);
36− const [selectedPatp, setSelectedPatp] = useState<string | null>(null);
36+ const [selectedPatp, setSelectedPatp] = useState<string | null>();
3737
3838 // Pre-filter the results if navigating from Point view
3939 const filterByPoint = useMemo(() => {
@@ -51,7 +51,7 @@ const TransactionHistory = () => {
5151 const txns = await api.getHistory(address);
5252 setTransactions(txns);
5353 if (filterByPoint) {
54− setSelectedPatp(filterByPoint);
54+ setSelectedPatp(filterByPoint.slice(1));
5555 }
5656 setLoading(false);
5757 }, [address, api, filterByPoint]);
@@ -64,10 +64,10 @@ const TransactionHistory = () => {
6464 const txnsByPatp = useMemo(() => {
6565 return transactions.reduce((memo, tx) => {
6666 const patp = tx.ship;
67− if (Object.keys(memo).includes(patp)) {
67+ if (patp && Object.keys(memo).includes(patp as string)) {
6868 memo[patp].push(tx);
6969 } else {
70− memo[patp] = [tx];
70+ memo[patp || '~'] = [tx];
7171 }
7272 return memo;
7373 }, {} as GroupedTransactions);
@@ -90,8 +90,7 @@ const TransactionHistory = () => {
9090 useEffect(() => {
9191 fetchTransactions();
9292 // Load the TX history only once
93− // eslint-disable-next-line react-hooks/exhaustive-deps
94− }, []);
93+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
9594
9695 return (
9796 <View
@@ -149,7 +148,10 @@ const TransactionHistory = () => {
149148 <BodyPane>
150149 {loading ? (
151150 <Box className={'loading'}>
152− <LoadingSpinner />
151+ <LoadingSpinner
152+ foreground="rgba(0,0,0,0.3)"
153+ background="white"
154+ />
153155 </Box>
154156 ) : (
155157 <Box className="transaction-container">
@@ -161,7 +163,7 @@ const TransactionHistory = () => {
161163 <PatpRow patp={patp} />
162164 {txnsByPatp[patp]
163165 .sort((a, b) => {
164− return b.time - a.time;
166+ return (b?.time || 0) - (a?.time || 0);
165167 })
166168 .map(tx => (
167169 <TransactionRow key={tx.time} {...tx} />
src/views/Transactions/TransactionRow.tsx+9−8
@@ -1,19 +1,20 @@
1+import { useMemo } from 'react';
12import cn from 'classnames';
23import * as ob from 'urbit-ob';
34import { Box, Icon, Row } from '@tlon/indigo-react';
45import { RollerTransaction } from '@urbit/roller-api';
56import { format, fromUnixTime } from 'date-fns';
6−import { titleize } from 'form/formatters';
7+
8+import { useTimerStore } from 'store/timerStore';
79import {
810 TRANSACTION_STATUS_ICONS,
911 TRANSACTION_TYPE_ICONS,
1012 TRANSACTION_TYPE_TITLES,
1113} from 'lib/constants';
1214import { abbreviateAddress } from 'lib/utils/address';
13−import { useMemo } from 'react';
14−import { useRollerStore } from 'store/rollerStore';
15−import LayerIndicator from 'components/L2/LayerIndicator';
1615import { isPlanet, isStar } from 'lib/utils/point';
16+import { titleize } from 'form/formatters';
17+import LayerIndicator from 'components/L2/LayerIndicator';
1718
1819export const TransactionRow = ({
1920 ship,
@@ -22,7 +23,7 @@ export const TransactionRow = ({
2223 status,
2324 time,
2425}: RollerTransaction) => {
25− const { nextRoll } = useRollerStore();
26+ const { nextRoll } = useTimerStore();
2627
2728 // For spawn events (associated with the spawner), we want to show the spawnee tier;
2829 // For all other events we will show the event's ship's tier
@@ -40,15 +41,15 @@ export const TransactionRow = ({
4041 }, [ship, type]);
4142
4243 const label = useMemo(() => {
43− return TRANSACTION_TYPE_TITLES[type].replace('Point', pointLabel) || type;
44− }, [pointLabel, type]);
44+ return TRANSACTION_TYPE_TITLES[type] || type;
45+ }, [type]);
4546
4647 const icon = useMemo(() => {
4748 return <Icon icon={TRANSACTION_TYPE_ICONS[type] || 'Bug'} />;
4849 }, [type]);
4950
5051 const shortHash = useMemo(() => {
51− return abbreviateAddress(hash);
52+ return hash ? abbreviateAddress(hash) : '';
5253 }, [hash]);
5354
5455 const parsedDate = useMemo(() => {
src/views/UrbitID/Transfer.js+1−1
@@ -120,7 +120,7 @@ export default function AdminTransfer() {
120120 <>
121121 {!completed && (
122122 <Grid.Item full as={Text} className={cn('f5 wrap')}>
123− Transfer {name} to a new owner.
123+ Transfer <code>{name}</code> to a new owner.
124124 </Grid.Item>
125125 )}
126126 {!completed && needLockupWarning && (
src/views/UrbitOS/ChangeSponsor.js+82−47
@@ -2,33 +2,31 @@ import React, { useState, useMemo, useCallback, useEffect } from 'react';
22import { Grid, Button } from 'indigo-react';
33import * as ob from 'urbit-ob';
44import { azimuth, ecliptic } from 'azimuth-js';
5+import { Box, Row } from '@tlon/indigo-react';
56
67import { useNetwork } from 'store/network';
78import { usePointCache } from 'store/pointCache';
9+import { usePointCursor } from 'store/pointCursor';
10+import { useRollerStore } from 'store/rollerStore';
11+import { useHistory } from 'store/history';
12+
13+import * as need from 'lib/need';
14+import { validateMinimumPatpByteLength } from 'lib/validators';
15+import useEthereumTransaction from 'lib/useEthereumTransaction';
16+import useRoller from 'lib/useRoller';
817
918import { PointInput } from 'form/Inputs';
1019import FormError from 'form/FormError';
1120import BridgeForm from 'form/BridgeForm';
12−
13−import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
14−import ViewHeader from 'components/ViewHeader';
1521import {
1622 composeValidator,
1723 buildPointValidator,
1824 hasErrors,
1925} from 'form/validators';
20−
21−import * as need from 'lib/need';
22−import { validateMinimumPatpByteLength } from 'lib/validators';
23−import { usePointCursor } from 'store/pointCursor';
24−import useEthereumTransaction from 'lib/useEthereumTransaction';
25−import { isStar } from 'lib/utils/point';
26+import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
2627import Window from 'components/L2/Window/Window';
2728import HeaderPane from 'components/L2/Window/HeaderPane';
28−import { Box, Row } from '@tlon/indigo-react';
2929import BodyPane from 'components/L2/Window/BodyPane';
30−import { useRollerStore } from 'store/rollerStore';
31−import useRoller from 'lib/useRoller';
3230
3331function useChangeSponsor() {
3432 const { contracts } = useNetwork();
@@ -51,23 +49,27 @@ function useChangeSponsor() {
5149
5250function ChangeSponsor({ onDone }) {
5351 const { contracts } = useNetwork();
54− const { pointCursor } = usePointCursor();
55− const { changeSponsor } = useRoller();
56− const {
57− point: { isL2 },
58− } = useRollerStore();
52+ const { pop } = useHistory();
53+ const { changeSponsor, checkForUpdates } = useRoller();
54+ const { point, setLoading } = useRollerStore();
5955
6056 const _contracts = need.contracts(contracts);
61− const point = need.point(pointCursor);
6257 const { construct, unconstruct, inputsLocked, bind } = useChangeSponsor();
6358
6459 const [newSponsor, setNewSponsor] = useState();
6560
6661 const requestNewSponsor = useCallback(async () => {
6762 if (newSponsor !== undefined) {
68− changeSponsor(newSponsor);
63+ setLoading(true);
64+ await changeSponsor(newSponsor);
65+ checkForUpdates(
66+ point.value,
67+ `${point.patp} has requested ${ob.patp(newSponsor)} as a sponsor`
68+ );
69+ setLoading(false);
70+ pop();
6971 }
70− }, [newSponsor, changeSponsor]);
72+ }, [point, newSponsor, changeSponsor, setLoading, checkForUpdates, pop]);
7173
7274 const validateFormAsync = useCallback(
7375 async values => {
@@ -92,7 +94,7 @@ function ChangeSponsor({ onDone }) {
9294 );
9395
9496 const validate = useMemo(() => {
95− const sponsorSize = isStar(point) ? 1 : 2;
97+ const sponsorSize = point.isStar ? 1 : 2;
9698 return composeValidator(
9799 {
98100 sponsor: buildPointValidator(sponsorSize, [
@@ -117,6 +119,8 @@ function ChangeSponsor({ onDone }) {
117119 [construct, unconstruct]
118120 );
119121
122+ const buttonText = 'Request New Sponsor';
123+
120124 return (
121125 <Window>
122126 <HeaderPane>
@@ -135,17 +139,17 @@ function ChangeSponsor({ onDone }) {
135139 disabled={inputsLocked}
136140 label="New sponsor"
137141 className="mv4"
142+ size={2}
138143 />
139144 <Grid.Item full as={FormError} />
140− {isL2 ? (
145+ {point.isL2 ? (
141146 <Grid.Item
142147 as={Button}
143148 full
144− className=""
145149 center
146150 solid
147151 onClick={requestNewSponsor}>
148− {'Request New Sponsor'}
152+ {buttonText}
149153 </Grid.Item>
150154 ) : (
151155 <Grid.Item
@@ -153,7 +157,7 @@ function ChangeSponsor({ onDone }) {
153157 as={InlineEthereumTransaction}
154158 {...bind}
155159 onReturn={onDone}
156− label="Request"
160+ label={buttonText}
157161 />
158162 )}
159163 </Box>
@@ -183,35 +187,66 @@ function useCancelEscape() {
183187
184188function CurrentEscape({ onDone }) {
185189 const { construct, bind } = useCancelEscape();
190+ const { point } = useRollerStore();
186191
187− const { pointCursor } = usePointCursor();
188− const { getDetails } = usePointCache();
189−
190− const point = need.point(pointCursor);
191−
192− const details = need.details(getDetails(point));
192+ const { pop } = useHistory();
193+ const { cancelEscape, checkForUpdates } = useRoller();
194+ const {
195+ point: { isL2 },
196+ setLoading,
197+ } = useRollerStore();
193198
194− const newSponsor = useMemo(() => ob.patp(details.escapeRequestedTo), [
195− details,
196− ]);
199+ const newSponsor = useMemo(() => ob.patp(point.escapeRequestedTo), [point]);
197200
198201 useEffect(() => {
199202 construct();
200203 }, [construct]);
204+
205+ const cancelRequest = useCallback(async () => {
206+ if (newSponsor !== undefined) {
207+ setLoading(true);
208+ await cancelEscape(newSponsor);
209+ checkForUpdates(point.value, `${point.patp}'s sponsor change cancelled`);
210+ setLoading(false);
211+ pop();
212+ }
213+ }, [point, newSponsor, cancelEscape, setLoading, checkForUpdates, pop]);
214+
215+ const buttonText = 'Cancel Request';
216+
201217 return (
202− <Grid>
203− <Grid.Item full as={ViewHeader}>
204− Currently requesting <span className="mono">{newSponsor}</span> as a new
205− sponsor
206− </Grid.Item>
207− <Grid.Item
208− full
209− as={InlineEthereumTransaction}
210− label="Cancel Request"
211− {...bind}
212− onReturn={onDone}
213− />
214− </Grid>
218+ <Window>
219+ <HeaderPane>
220+ <Row className="header-row">
221+ <h5>Change Sponsor</h5>
222+ </Row>
223+ </HeaderPane>
224+ <BodyPane>
225+ <Box width="100%" fontSize={14} className="mb4">
226+ Currently requesting <span className="mono">{newSponsor}</span> as a
227+ new sponsor
228+ </Box>
229+ {isL2 ? (
230+ <Grid.Item
231+ as={Button}
232+ full
233+ center
234+ solid
235+ style={{ width: '100%' }}
236+ onClick={cancelRequest}>
237+ {buttonText}
238+ </Grid.Item>
239+ ) : (
240+ <Grid.Item
241+ full
242+ as={InlineEthereumTransaction}
243+ label={buttonText}
244+ {...bind}
245+ onReturn={onDone}
246+ />
247+ )}
248+ </BodyPane>
249+ </Window>
215250 );
216251}
217252