on master help sign in
WIP: generate L2 invites are working
bab2fa8Will Galebach
src/components/L2/Dropdowns/AccountsDropdown.tsx+8−4
@@ -21,6 +21,8 @@ const AccountsDropdown = () => {
21 const { setPointCursor }: any = usePointCursor();
21 const { setPointCursor }: any = usePointCursor();
22 const { controlledPoints }: any = usePointCache();
22 const { controlledPoints }: any = usePointCache();
23
23
24+ const canBitcoin = Just.hasInstance(walletInfo.urbitWallet);
25+
24 const points =
26 const points =
25 controlledPoints?.value?.value?.ownedPoints?.map((point: string) =>
27 controlledPoints?.value?.value?.ownedPoints?.map((point: string) =>
26 Number(point)
28 Number(point)
@@ -78,10 +80,12 @@ const AccountsDropdown = () => {
78 <LayerIndicator layer={2} size="sm" />
80 <LayerIndicator layer={2} size="sm" />
79 </Row>
81 </Row>
80 </Row>
82 </Row>
83+ {canBitcoin && (
81− <Row className="entry" onClick={goToBitcoin}>
84+ <Row className="entry" onClick={goToBitcoin}>
82− <div>Bitcoin</div>
85+ <div>Bitcoin</div>
83− <Icon icon="Bitcoin" />
86+ <Icon icon="Bitcoin" />
84− </Row>
87+ </Row>
88+ )}
85 <Row className="entry" onClick={onLogout}>
89 <Row className="entry" onClick={onLogout}>
86 <div>Logout</div>
90 <div>Logout</div>
87 <Icon icon="LogOut" />
91 <Icon icon="LogOut" />
src/components/L2/Headers/L2PointHeader.tsx+5−3
@@ -11,6 +11,7 @@ import { useRollerStore } from 'store/roller';
11import './L2PointHeader.scss';
11import './L2PointHeader.scss';
12
12
13export interface L2PointHeaderProps {
13export interface L2PointHeaderProps {
14+ numInvites: number;
14 hideTimer: boolean;
15 hideTimer: boolean;
15 hideInvites: boolean;
16 hideInvites: boolean;
16}
17}
@@ -18,9 +19,10 @@ export interface L2PointHeaderProps {
18const L2PointHeader = ({
19const L2PointHeader = ({
19 hideTimer = false,
20 hideTimer = false,
20 hideInvites = false,
21 hideInvites = false,
22+ numInvites = 0,
21}: L2PointHeaderProps) => {
23}: L2PointHeaderProps) => {
22 const { config } = useRoller();
24 const { config } = useRoller();
23− const { nextRoll, currentL2, invites } = useRollerStore();
25+ const { nextRoll, currentL2, pendingTransactions } = useRollerStore();
24
26
25 useEffect(() => {
27 useEffect(() => {
26 console.log('loaded config in L2PointHeader:', config);
28 console.log('loaded config in L2PointHeader:', config);
@@ -44,10 +46,10 @@ const L2PointHeader = ({
44 {!hideInvites && (
46 {!hideInvites && (
45 <Row onClick={goToInvites} className="invites">
47 <Row onClick={goToInvites} className="invites">
46 <InviteIcon />
48 <InviteIcon />
47− {invites.length}
49+ {numInvites}
48 </Row>
50 </Row>
49 )}
51 )}
50− {!hideTimer && currentL2 && (
52+ {!hideTimer && currentL2 && !pendingTransactions.length && (
51 <div className="rollup-timer" onClick={goToHistory}>
53 <div className="rollup-timer" onClick={goToHistory}>
52 {nextRoll}
54 {nextRoll}
53 </div>
55 </div>
src/components/L2/Paginator.scss+1
@@ -8,6 +8,7 @@
8 width: 100%;
8 width: 100%;
9
9
10 .info {
10 .info {
11+ margin-top: 8px;
11 width: 40px;
12 width: 40px;
12 text-align: center;
13 text-align: center;
13 }
14 }
src/lib/useInviter.js+2
@@ -193,6 +193,8 @@ const useInviter = () => {
193 };
193 };
194 }
194 }
195
195
196+ console.log('INVITES', confirmedInvites)
197+ // TODO: store these locally, and also handle the situation
196 return { invites: confirmedInvites };
198 return { invites: confirmedInvites };
197 },
199 },
198 [
200 [
src/lib/useRoller.ts+132−6
@@ -23,6 +23,12 @@ import { useWallet } from 'store/wallet';
23import { usePointCursor } from 'store/pointCursor';
23import { usePointCursor } from 'store/pointCursor';
24import { useNetwork } from 'store/network';
24import { useNetwork } from 'store/network';
25import { signTransactionHash } from './authToken';
25import { signTransactionHash } from './authToken';
26+import { Invite } from 'types/Invite';
27+import { getStoredInvites, setPendingInvites, setStoredInvites } from 'store/storage/roller';
28+import { usePointCache } from 'store/pointCache';
29+import { maybeGetResult } from 'views/Points';
30+
31+const hasPoint = (point: number) => (invite: Invite) => invite.planet === point;
26
32
27const getProxyAndNonce = (
33const getProxyAndNonce = (
28 point: L2Point,
34 point: L2Point,
@@ -47,8 +53,16 @@ export default function useRoller() {
47 const { wallet, walletType, walletHdPath, authToken }: any = useWallet();
53 const { wallet, walletType, walletHdPath, authToken }: any = useWallet();
48 const { pointCursor }: any = usePointCursor();
54 const { pointCursor }: any = usePointCursor();
49 const { web3, contracts }: any = useNetwork();
55 const { web3, contracts }: any = useNetwork();
56+ const allPoints: any = usePointCache();
57+ const controlledPoints = allPoints?.controlledPoints;
50
58
59+ const {
60+ nextBatchTime,
51− const { nextBatchTime, setNextBatchTime, setNextRoll } = useRollerStore();
61+ setNextBatchTime,
62+ setNextRoll,
63+ setPendingTransactions,
64+ setInvites,
65+ } = useRollerStore();
52 const [config, setConfig] = useState<Config | null>(null);
66 const [config, setConfig] = useState<Config | null>(null);
53
67
54 const options: Options = useMemo(() => {
68 const options: Options = useMemo(() => {
@@ -106,7 +120,7 @@ export default function useRoller() {
106 const planets: UnspawnedPoints = await api.getUnspawned(_point);
120 const planets: UnspawnedPoints = await api.getUnspawned(_point);
107 const starInfo = await api.getPoint(_point);
121 const starInfo = await api.getPoint(_point);
108
122
109− const tickets: { ticket: string; planet: string }[] = [];
123+ const tickets: { ticket: string; planet: number; owner: string }[] = [];
110 const requests: Promise<string>[] = [];
124 const requests: Promise<string>[] = [];
111
125
112 const { proxy, nonce } = getProxyAndNonce(starInfo, _wallet.address);
126 const { proxy, nonce } = getProxyAndNonce(starInfo, _wallet.address);
@@ -141,12 +155,22 @@ export default function useRoller() {
141
155
142 const signature = signTransactionHash(txHash, _wallet.privateKey);
156 const signature = signTransactionHash(txHash, _wallet.privateKey);
143 requests.push(api.spawn(signature, from, _wallet.address, data));
157 requests.push(api.spawn(signature, from, _wallet.address, data));
144− tickets.push({ ticket, planet: ob.patp(planet) });
158+ tickets.push({
159+ ticket,
160+ planet,
161+ owner: owner.keys.address,
162+ });
145 }
163 }
146
164
147 const hashes = await Promise.all(requests);
165 const hashes = await Promise.all(requests);
148−
166+ const pendingInvites = hashes.map(
167+ (hash, i): Invite => ({
168+ hash,
149− return hashes.map((hash, i) => ({ hash, ...tickets[i] }));
169+ ...tickets[i],
170+ status: 'pending',
171+ })
172+ );
173+ setPendingInvites(_point, pendingInvites);
150 },
174 },
151 [
175 [
152 api,
176 api,
@@ -180,6 +204,104 @@ export default function useRoller() {
180 [api]
204 [api]
181 );
205 );
182
206
207+ const getPendingTransactions = useCallback(async () => {
208+ try {
209+ const curPoint = need.point(pointCursor);
210+ const newPending = await api.getPendingByShip(Number(curPoint));
211+ console.log('PENDING', newPending);
212+ setPendingTransactions(newPending);
213+
214+ // const allTransactions = await api.getHistory()
215+ } catch (error) {
216+ console.warn('ERROR GETTING PENDING', error);
217+ }
218+ }, [api, setPendingTransactions, pointCursor]);
219+
220+ const getInvites = useCallback(async () => {
221+ try {
222+ const curPoint: string = need.point(pointCursor);
223+ const invites = getStoredInvites(curPoint);
224+ const availableInvites = invites.available;
225+
226+ const pendingTransactions = await api.getPendingByShip(curPoint);
227+ console.log('PENDING', pendingTransactions);
228+ setPendingTransactions(pendingTransactions);
229+
230+ const stillPending = invites.pending.filter(invite => {
231+ const completed = !pendingTransactions.find(
232+ ({ tx }) => tx.tx?.data?.ship === invite.planet
233+ );
234+
235+ if (completed) {
236+ availableInvites.push({ ...invite, status: 'available' });
237+ }
238+
239+ return !completed;
240+ });
241+
242+ setStoredInvites(curPoint, {
243+ available: availableInvites,
244+ pending: stillPending,
245+ claimed: invites.claimed,
246+ });
247+ setInvites(availableInvites);
248+
249+ const allSpawned = await api.getSpawned(Number(curPoint));
250+ const ownedPoints = maybeGetResult(controlledPoints, 'ownedPoints', []);
251+ const spawnedAndOwned = ownedPoints.filter(
252+ (p: number) => allSpawned.includes(p) && p > 2000
253+ );
254+ console.log('SPAWNED AND OWNED', spawnedAndOwned);
255+
256+ // Iterate over all spawned and controlled planets
257+ // If the planet is not in available invites, generate the ticket and add it
258+ const _authToken = authToken.getOrElse(null);
259+ const _contracts = contracts.getOrElse(null);
260+
261+ if (_authToken && _contracts) {
262+ for (let i = 0; i < spawnedAndOwned.length; i++) {
263+ const point = spawnedAndOwned[i];
264+
265+ if (!availableInvites.find(hasPoint(point))) {
266+ console.log('MISSING IN AVAILABLE', point);
267+ const {
268+ ticket,
269+ owner,
270+ } = await wg.generateTemporaryDeterministicWallet(
271+ point,
272+ _authToken
273+ );
274+
275+ availableInvites.push({
276+ ticket,
277+ status: 'available',
278+ planet: point,
279+ hash: '',
280+ owner: owner.keys.address,
281+ });
282+ }
283+ }
284+
285+ setStoredInvites(curPoint, {
286+ available: availableInvites,
287+ pending: stillPending,
288+ claimed: invites.claimed,
289+ });
290+ setInvites(availableInvites);
291+ }
292+ } catch (error) {
293+ console.warn('ERROR GETTING INVITES', error);
294+ }
295+ }, [
296+ api,
297+ pointCursor,
298+ setInvites,
299+ setPendingTransactions,
300+ authToken,
301+ contracts,
302+ controlledPoints,
303+ ]);
304+
183 // On load, get initial config
305 // On load, get initial config
184 useEffect(() => {
306 useEffect(() => {
185 if (config) {
307 if (config) {
@@ -198,16 +320,20 @@ export default function useRoller() {
198 api.getRollerConfig().then(response => {
320 api.getRollerConfig().then(response => {
199 setNextBatchTime(response.nextBatch);
321 setNextBatchTime(response.nextBatch);
200 });
322 });
323+
324+ getInvites();
201 }
325 }
202 }, 1000);
326 }, 1000);
203
327
204 return () => clearInterval(interval);
328 return () => clearInterval(interval);
205− }, [nextBatchTime]); // eslint-disable-line react-hooks/exhaustive-deps
329+ }, [nextBatchTime, getPendingTransactions]); // eslint-disable-line react-hooks/exhaustive-deps
206
330
207 return {
331 return {
208 api,
332 api,
209 config,
333 config,
210 getPoints,
334 getPoints,
335+ getInvites,
336+ getPendingTransactions,
211 generateInviteCodes,
337 generateInviteCodes,
212 };
338 };
213}
339}
src/lib/utils/invite.ts+4−2
@@ -1,6 +1,8 @@
1+import * as ob from 'urbit-ob';
2+
1−export const generateUrl = (ticket: string, planet: string) => {
3+export const generateUrl = (ticket: string, planet: number) => {
2 if (ticket && planet) {
4 if (ticket && planet) {
3− return `urb.it/#${ticket.slice(1)}-${planet.slice(1)}`;
5+ return `urb.it/#${ticket.slice(1)}-${ob.patp(planet).slice(1)}`;
4 }
6 }
5
7
6 return 'No Ticket or Ship';
8 return 'No Ticket or Ship';
src/store/lib/useInvitesStore.js+2
@@ -42,6 +42,8 @@ export default function useInvitesStore() {
42 )
42 )
43 );
43 );
44
44
45+ console.log('INVITES STORE', availableInvites, invitedPoints, activity);
46+
45 const sentInvites = invitedPoints.length;
47 const sentInvites = invitedPoints.length;
46 const acceptedInvites = activity.filter(i => !!i).length;
48 const acceptedInvites = activity.filter(i => !!i).length;
47
49
src/store/roller.ts+13−1
@@ -11,6 +11,7 @@ export interface RollerStore {
11 currentPoint: L2Point | null;
11 currentPoint: L2Point | null;
12 currentL2: boolean;
12 currentL2: boolean;
13 invites: Invite[];
13 invites: Invite[];
14+ recentlyCompleted: number;
14 setNextBatchTime: (nextBatchTime: number) => void;
15 setNextBatchTime: (nextBatchTime: number) => void;
15 setNextRoll: (nextRoll: string) => void;
16 setNextRoll: (nextRoll: string) => void;
16 setPendingTransactions: (pendingTransactions: PendingTransaction[]) => void;
17 setPendingTransactions: (pendingTransactions: PendingTransaction[]) => void;
@@ -25,10 +26,21 @@ export const useRollerStore = create<RollerStore>(set => ({
25 currentPoint: null,
26 currentPoint: null,
26 currentL2: false,
27 currentL2: false,
27 invites: [],
28 invites: [],
29+ recentlyCompleted: 0,
28 setNextBatchTime: (nextBatchTime: number) => set(() => ({ nextBatchTime })),
30 setNextBatchTime: (nextBatchTime: number) => set(() => ({ nextBatchTime })),
29 setNextRoll: (nextRoll: string) => set(() => ({ nextRoll })),
31 setNextRoll: (nextRoll: string) => set(() => ({ nextRoll })),
30 setPendingTransactions: (pendingTransactions: PendingTransaction[]) =>
32 setPendingTransactions: (pendingTransactions: PendingTransaction[]) =>
33+ set(state => {
34+ const oldPending = state.pendingTransactions.length;
35+ if (oldPending > 0 && pendingTransactions.length === 0) {
36+ return {
37+ ...state,
38+ pendingTransactions,
39+ recentlyCompleted: oldPending,
40+ };
41+ }
31− set(() => ({ pendingTransactions })),
42+ return { ...state, pendingTransactions };
43+ }),
32 setCurrentPoint: (currentPoint: L2Point) =>
44 setCurrentPoint: (currentPoint: L2Point) =>
33 set(() => ({
45 set(() => ({
34 currentPoint,
46 currentPoint,
src/store/storage/roller.tsnew file+36
@@ -0,0 +1,36 @@
1+import { Invite } from "types/Invite";
2+
3+const INVITES_KEY = 'invites';
4+
5+export interface StoredInvites {
6+ available: Invite[];
7+ pending: Invite[];
8+ claimed: Invite[];
9+}
10+
11+export const getStoredInvites = (point: string): StoredInvites => {
12+ const invitesString = localStorage.getItem(INVITES_KEY) || '{}';
13+ console.log(invitesString)
14+ const invites = JSON.parse(invitesString);
15+
16+ console.log('STORED INVITES', invites);
17+
18+ if (invites[point]) {
19+ return invites[point];
20+ }
21+
22+ return { available: [], pending: [], claimed: [] };
23+};
24+
25+export const setStoredInvites = (point: string, newInvites: StoredInvites) => {
26+ const invitesString = localStorage.getItem(INVITES_KEY) || '{}';
27+ const invites = JSON.parse(invitesString);
28+ invites[point] = newInvites;
29+ localStorage.setItem(INVITES_KEY, JSON.stringify(invites));
30+};
31+
32+export const setPendingInvites = (point: string, pendingInvites: Invite[]) => {
33+ const invites = getStoredInvites(point);
34+ invites.pending.push(...pendingInvites);
35+ setStoredInvites(point, invites);
36+};
src/types/Invite.ts+3−1
@@ -1,5 +1,7 @@
1export interface Invite {
1export interface Invite {
2 ticket: string;
2 ticket: string;
3− planet: string;
3+ planet: number;
4 hash: string;
4 hash: string;
5+ owner: string;
6+ status: 'pending' | 'sending' | 'claimed' | 'available';
5}
7}
src/views/Invite/Cohort.js+77−178
@@ -9,22 +9,16 @@ import { usePointCache } from 'store/pointCache';
9import { useWallet } from 'store/wallet';
9import { useWallet } from 'store/wallet';
10
10
11import View from 'components/View';
11import View from 'components/View';
12−import Tabs from 'components/Tabs';
13−import BarGraph from 'components/BarGraph';
14import MaybeSigil from 'components/MaybeSigil';
12import MaybeSigil from 'components/MaybeSigil';
15import { matchBlinky } from 'components/Blinky';
13import { matchBlinky } from 'components/Blinky';
16−import Chip from 'components/Chip';
17
14
18import { useLocalRouter } from 'lib/LocalRouter';
15import { useLocalRouter } from 'lib/LocalRouter';
19import useInvites from 'lib/useInvites';
16import useInvites from 'lib/useInvites';
20import * as need from 'lib/need';
17import * as need from 'lib/need';
21import * as wg from 'lib/walletgen';
18import * as wg from 'lib/walletgen';
22
19
23−import Inviter from 'views/Invite/Inviter';
24−
25import { ReactComponent as SearchIcon } from 'assets/search.svg';
20import { ReactComponent as SearchIcon } from 'assets/search.svg';
26import CopyButton from 'components/copiable/CopyButton';
21import CopyButton from 'components/copiable/CopyButton';
27−import { useSyncDetails } from 'lib/useSyncPoints';
28import { useNetwork } from 'store/network';
22import { useNetwork } from 'store/network';
29import L2BackHeader from 'components/L2/Headers/L2BackHeader';
23import L2BackHeader from 'components/L2/Headers/L2BackHeader';
30import Window from 'components/L2/Window/Window';
24import Window from 'components/L2/Window/Window';
@@ -33,7 +27,6 @@ import { Icon, Row, StatelessTextInput } from '@tlon/indigo-react';
33import BodyPane from 'components/L2/Window/BodyPane';
27import BodyPane from 'components/L2/Window/BodyPane';
34import { useRollerStore } from 'store/roller';
28import { useRollerStore } from 'store/roller';
35import useRoller from 'lib/useRoller';
29import useRoller from 'lib/useRoller';
36−import { convertToInt } from 'lib/convertToInt';
37import FeeDropdown from 'components/L2/Dropdowns/FeeDropdown';
30import FeeDropdown from 'components/L2/Dropdowns/FeeDropdown';
38import LoadingOverlay from 'components/L2/LoadingOverlay';
31import LoadingOverlay from 'components/L2/LoadingOverlay';
39import { generateUrl } from 'lib/utils/invite';
32import { generateUrl } from 'lib/utils/invite';
@@ -41,6 +34,7 @@ import CopiableWithTooltip from 'components/copiable/CopiableWithTooltip';
41import Paginator from 'components/L2/Paginator';
34import Paginator from 'components/L2/Paginator';
42
35
43import './Cohort.scss';
36import './Cohort.scss';
37+import useInviter from 'lib/useInviter';
44
38
45const INVITES_PER_PAGE = 7;
39const INVITES_PER_PAGE = 7;
46const DEFAULT_NUM_INVITES = 5;
40const DEFAULT_NUM_INVITES = 5;
@@ -298,69 +292,57 @@ export default function InviteCohort() {
298 currentL2,
292 currentL2,
299 pendingTransactions,
293 pendingTransactions,
300 invites,
294 invites,
301− setInvites,
302 } = useRollerStore();
295 } = useRollerStore();
296+ const { generateInvites } = useInviter();
303− const { generateInviteCodes } = useRoller();
297+ const { generateInviteCodes, getPendingTransactions } = useRoller();
304
298
305 const { pointCursor } = usePointCursor();
299 const { pointCursor } = usePointCursor();
306 const point = need.point(pointCursor);
300 const point = need.point(pointCursor);
307
301
308− const _point = convertToInt(need.point(pointCursor), 10);
309 const [page, setPage] = useState(0);
302 const [page, setPage] = useState(0);
310
303
311− const invitesToDisplay = invites.slice(
304+ const { pendingPoints } = useInvites(point);
305+
306+ const displayedInvites = currentL2 ? invites : pendingPoints.getOrElse([]);
307+
308+ const invitesToDisplay = displayedInvites.slice(
312 page * INVITES_PER_PAGE,
309 page * INVITES_PER_PAGE,
313 (page + 1) * INVITES_PER_PAGE
310 (page + 1) * INVITES_PER_PAGE
314 );
311 );
315
312
316− const {
317− acceptedInvites,
318− acceptedPoints,
319− pendingPoints,
320− availableInvites,
321− sentInvites,
322− } = useInvites(point);
323−
324 const hasPending = Boolean(pendingTransactions.length);
313 const hasPending = Boolean(pendingTransactions.length);
325− const hasInvites = Boolean(invites.length);
314+ const hasInvites = Boolean(displayedInvites.length);
326
315
327 const [numInvites, setNumInvites] = useState(DEFAULT_NUM_INVITES);
316 const [numInvites, setNumInvites] = useState(DEFAULT_NUM_INVITES);
328 const [showInviteForm, setShowInviteForm] = useState(false);
317 const [showInviteForm, setShowInviteForm] = useState(false);
329− const [tab, setTab] = useState(NAMES.ALL);
330 const [loading, setLoading] = useState(false);
318 const [loading, setLoading] = useState(false);
331 const [error, setError] = useState(null);
319 const [error, setError] = useState(null);
332
320
333− const _acceptedPoints = acceptedPoints.getOrElse([]);
334− const _pendingPoints = pendingPoints.getOrElse([]);
335− const _pendingInvites = _pendingPoints.length;
336− const _acceptedInvites = _acceptedPoints.length;
337− const _totalInvites = availableInvites
338− .map(a => sentInvites.getOrElse(0) + a)
339− .getOrElse(0);
340−
341− const [selectedInvite, _setSelectedInvite] = useState();
342−
343− useSyncDetails([..._acceptedPoints, ..._pendingPoints]);
344−
345− const setSelectedInvite = useCallback(
346− p => _setSelectedInvite(old => (old === p ? null : p)),
347− []
348− );
349−
350− const generateInvites = useCallback(async () => {
321+ const createInvites = useCallback(async () => {
351 setLoading(true);
322 setLoading(true);
352 try {
323 try {
324+ if (currentL2) {
353− const invites = await generateInviteCodes(numInvites);
325+ const invites = await generateInviteCodes(numInvites);
354− console.log('INVITES', invites)
326+ console.log('INVITES', invites);
355− // TODO: store these invites somewhere. How do I recover the invites for future use?
327+ getPendingTransactions(point);
356− setInvites(invites);
328+ } else {
329+ const invites = await generateInvites(numInvites);
330+ console.log('INVITES', invites);
331+ }
357 setShowInviteForm(false);
332 setShowInviteForm(false);
358 } catch (error) {
333 } catch (error) {
359 setError(error);
334 setError(error);
360 } finally {
335 } finally {
361 setLoading(false);
336 setLoading(false);
362 }
337 }
338+ }, [
363− }, [generateInviteCodes, numInvites, setInvites]);
339+ generateInviteCodes,
340+ numInvites,
341+ getPendingTransactions,
342+ point,
343+ generateInvites,
344+ currentL2,
345+ ]);
364
346
365 const getContent = () => {
347 const getContent = () => {
366 if (hasInvites) {
348 if (hasInvites) {
@@ -416,8 +398,8 @@ export default function InviteCohort() {
416 const header = (
398 const header = (
417 <h5>
399 <h5>
418 You have
400 You have
419− <span className="number-emphasis"> {invites.length} </span>
401+ <span className="number-emphasis"> {displayedInvites.length} </span>
420− Planet Code{invites.length > 1 ? 's' : ''}
402+ Planet Code{displayedInvites.length === 1 ? '' : 's'}
421 </h5>
403 </h5>
422 );
404 );
423
405
@@ -470,7 +452,7 @@ export default function InviteCohort() {
470 className="generate-codes"
452 className="generate-codes"
471 center
453 center
472 solid
454 solid
473− onClick={generateInvites}>
455+ onClick={createInvites}>
474 Generate Planet Codes ({numInvites})
456 Generate Planet Codes ({numInvites})
475 </Button>
457 </Button>
476 </div>
458 </div>
@@ -479,140 +461,57 @@ export default function InviteCohort() {
479 <LoadingOverlay loading={loading} />
461 <LoadingOverlay loading={loading} />
480 </View>
462 </View>
481 );
463 );
482− } else if (currentL2) {
483− return (
484− <View
485− pop={pop}
486− inset
487− className="cohort"
488− hideBack
489− header={<L2BackHeader />}>
490− <Window>
491− <HeaderPane>
492− {!hasInvites ? (
493− header
494− ) : (
495− <Row className="has-invites-header">
496− {header}
497− <div className="download-csv" onClick={() => null}>
498− <Icon icon="Download" />
499− <div>CSV</div>
500− </div>
501− </Row>
502− )}
503− </HeaderPane>
504− <BodyPane>
505− <div className={`content ${!hasInvites ? 'center' : ''}`}>
506− {getContent()}
507− </div>
508− {!hasPending && !hasInvites && (
509− <Button
510− as={'button'}
511− className="generate-button"
512− center
513− solid
514− onClick={() => setShowInviteForm(true)}>
515− Generate Codes
516− </Button>
517− )}
518− {hasInvites && (
519− <Paginator
520− page={page}
521− numPerPage={INVITES_PER_PAGE}
522− numElements={invites.length}
523− goPrevious={() => setPage(page - 1)}
524− goNext={() => setPage(page + 1)}
525− />
526− )}
527− </BodyPane>
528− </Window>
529− {(hasInvites || hasPending) && (
530− <Button
531− onClick={() => setShowInviteForm(true)}
532− className="add-more"
533− accessory={<Icon icon="ChevronEast" />}>
534− Add More
535− </Button>
536− )}
537− </View>
538− );
539 }
464 }
540
465
541 return (
466 return (
542− <View pop={pop} inset className="cohort" hideBack>
467+ <View pop={pop} inset className="cohort" hideBack header={<L2BackHeader />}>
543− <Grid gap={3}>
468+ <Window>
544− <Grid.Item full>Invite Group</Grid.Item>
469+ <HeaderPane>
545− <Grid.Item full>
470+ {!hasInvites ? (
546− <Flex align="center">
471+ header
547− <Flex.Item>
472+ ) : (
548− {_acceptedInvites} / {_totalInvites}
473+ <Row className="has-invites-header">
549− </Flex.Item>
474+ {header}
550− {_pendingInvites !== 0 && (
475+ <div className="download-csv" onClick={() => null}>
551− <Flex.Item as={Chip} className="bg-yellow4 white">
476+ <Icon icon="Download" />
552− {_pendingInvites} pending
477+ <div>CSV</div>
553− </Flex.Item>
478+ </div>
554− )}
479+ </Row>
555− </Flex>
480+ )}
556− </Grid.Item>
481+ </HeaderPane>
557− <Grid.Item
482+ <BodyPane>
558− full
483+ <div className={`content ${!hasInvites ? 'center' : ''}`}>
559− as={BarGraph}
484+ {getContent()}
560− available={availableInvites}
485+ </div>
561− sent={sentInvites}
486+ {!hasPending && !hasInvites && (
562− accepted={acceptedInvites}
487+ <Button
563− />
488+ as={'button'}
564− {availableInvites.getOrElse(0) !== 0 && !showInviteForm && (
489+ className="generate-button"
565− <Grid.Item
490+ center
566− full
567− solid
491+ solid
568− center
492+ onClick={() => setShowInviteForm(true)}>
569− as={Button}
493+ Generate Codes
494+ </Button>
495+ )}
496+ {hasInvites && (
497+ <Paginator
498+ page={page}
499+ numPerPage={INVITES_PER_PAGE}
500+ numElements={displayedInvites.length}
501+ goPrevious={() => setPage(page - 1)}
502+ goNext={() => setPage(page + 1)}
503+ />
504+ )}
505+ </BodyPane>
506+ </Window>
507+ {(hasInvites || hasPending) && (
508+ <Button
570− onClick={() => setShowInviteForm(true)}>
509+ onClick={() => setShowInviteForm(true)}
571− Add Members
510+ className="add-more"
572− </Grid.Item>
511+ accessory={<Icon icon="ChevronEast" />}>
573− )}
512+ Add More
574− {showInviteForm && <Inviter />}
513+ </Button>
575−
576− {true && (
577− <Grid.Item
578− full
579− center
580− as={Tabs}
581− views={VIEWS}
582− options={OPTIONS}
583− currentTab={tab}
584− onTabChange={setTab}
585− acceptedPoints={_acceptedPoints}
586− pendingPoints={_pendingPoints}
587− onlyPending={tab === NAMES.PENDING}
588− onSelectInvite={setSelectedInvite}
589− />
590− )}
591− {selectedInvite && (
592− <Grid.Item
593− full
594− center
595− as={CohortMemberExpanded}
596− pending
597− point={selectedInvite}
598− onBack={() => setSelectedInvite(null)}
599− />
600− )}
514+ )}
601− </Grid>
602 </View>
515 </View>
603 );
516 );
604}
517}
605−const NAMES = {
606− ALL: 'ALL',
607− PENDING: 'PENDING',
608−};
609−
610−const VIEWS = {
611− [NAMES.PENDING]: CohortList,
612− [NAMES.ALL]: CohortList,
613−};
614−
615−const OPTIONS = [
616− { text: 'All', value: NAMES.ALL },
617− { text: 'Pending', value: NAMES.PENDING },
618−];
src/views/Point.js+28−34
@@ -4,6 +4,7 @@ import { Just } from 'folktale/maybe';
4import { Grid, Flex, Button } from 'indigo-react';
4import { Grid, Flex, Button } from 'indigo-react';
5import { azimuth } from 'azimuth-js';
5import { azimuth } from 'azimuth-js';
6import * as ob from 'urbit-ob';
6import * as ob from 'urbit-ob';
7+import * as wg from 'lib/walletgen';
7
8
8import { usePointCursor } from 'store/pointCursor';
9import { usePointCursor } from 'store/pointCursor';
9import { useWallet } from 'store/wallet';
10import { useWallet } from 'store/wallet';
@@ -122,45 +123,39 @@ function InviteForm({
122
123
123export default function Point() {
124export default function Point() {
124 const { pop, push, names } = useLocalRouter();
125 const { pop, push, names } = useLocalRouter();
126+ const { wallet, authToken } = useWallet();
125 const { pointCursor } = usePointCursor();
127 const { pointCursor } = usePointCursor();
126−
127− const { wallet, urbitWallet } = useWallet();
128 const point = need.point(pointCursor);
128 const point = need.point(pointCursor);
129
129
130 const { getResidents } = usePointCache();
130 const { getResidents } = usePointCache();
131− const { api } = useRoller();
131+ const { api, getInvites } = useRoller();
132 const {
132 const {
133 pendingTransactions,
133 pendingTransactions,
134− setPendingTransactions,
135 nextRoll,
134 nextRoll,
136 setCurrentPoint,
135 setCurrentPoint,
136+ currentL2,
137+ invites,
138+ recentlyCompleted,
137 } = useRollerStore();
139 } = useRollerStore();
138
140
139 useEffect(() => {
141 useEffect(() => {
140− console.log('POINT', point);
141− const getTransactions = async () => {
142+ const loadL2Info = async () => {
142− const newPending = await api.getPendingByShip(Number(point));
143− console.log('PENDING', newPending);
143+ console.log('POINT', point);
144− setPendingTransactions(newPending);
144+ const getTransactions = async () => {
145−
146− // get list of spawned planets, and then determine if they have been claimed
147− const allSpawned = await api.getSpawned(Number(point));
148− console.log('SPAWNED', allSpawned.length, allSpawned);
149−
150− const pointInfo = await api.getPoint(Number(point));
145+ const pointInfo = await api.getPoint(Number(point));
151− setCurrentPoint(pointInfo);
146+ setCurrentPoint(pointInfo);
152− console.log('POINT', pointInfo);
147+ console.log('POINT', pointInfo);
153−
148+
154− const ownerAddress = pointInfo?.ownership?.owner?.address;
149+ if (pointInfo.dominion !== 'l1') {
155− console.log('ADDRESS', ownerAddress);
150+ getInvites();
156− if (ownerAddress) {
151+ }
157− const transactionHistory = await api.getHistory(ownerAddress);
152+ };
158− console.log('TRANSACTION HISTORY', transactionHistory);
153+
159− }
154+ getTransactions();
160 };
155 };
161
156
162− getTransactions();
157+ loadL2Info();
163− }, [api, point]); // eslint-disable-line
158+ }, [api, point, authToken]); // eslint-disable-line
164
159
165 const { residentCount, requestCount } = getResidents(point);
160 const { residentCount, requestCount } = getResidents(point);
166
161
@@ -172,17 +167,15 @@ export default function Point() {
172 canVote,
167 canVote,
173 } = useCurrentPermissions();
168 } = useCurrentPermissions();
174
169
175− const canBitcoin = Just.hasInstance(urbitWallet);
170+ // L1 invites
176−
177− // fetch the invites for the current cursor
178− const invites = useInvites(point);
171+ const l1Invites = useInvites(point);
179 const {
172 const {
180 availableInvites,
173 availableInvites,
181 sentInvites,
174 sentInvites,
182 acceptedInvites,
175 acceptedInvites,
183 pendingPoints,
176 pendingPoints,
184 acceptedPoints,
177 acceptedPoints,
185− } = invites;
178+ } = l1Invites;
186
179
187 const showInvites = !(
180 const showInvites = !(
188 acceptedInvites.getOrElse(0) === 0 && sentInvites.getOrElse(0) === 0
181 acceptedInvites.getOrElse(0) === 0 && sentInvites.getOrElse(0) === 0
@@ -200,8 +193,6 @@ export default function Point() {
200
193
201 const goUrbitOS = useCallback(() => push(names.URBIT_OS), [push, names]);
194 const goUrbitOS = useCallback(() => push(names.URBIT_OS), [push, names]);
202
195
203− const goBitcoin = useCallback(() => push(names.BITCOIN), [push, names]);
204−
205 const goUrbitID = useCallback(() => push(names.URBIT_ID), [push, names]);
196 const goUrbitID = useCallback(() => push(names.URBIT_ID), [push, names]);
206
197
207 const goResidents = useCallback(() => push(names.RESIDENTS), [push, names]);
198 const goResidents = useCallback(() => push(names.RESIDENTS), [push, names]);
@@ -264,13 +255,16 @@ export default function Point() {
264
255
265 const _requestCount = requestCount.getOrElse(0);
256 const _requestCount = requestCount.getOrElse(0);
266 const numPending = pendingTransactions.length;
257 const numPending = pendingTransactions.length;
258+ const numInvites = currentL2 ? invites.length : sentInvites.length;
267
259
268 return (
260 return (
269 <View
261 <View
270 pop={pop}
262 pop={pop}
271 inset
263 inset
272 className="point"
264 className="point"
265+ header={
273− header={<L2PointHeader hideTimer={!!numPending} />}>
266+ <L2PointHeader hideTimer={!!numPending} numInvites={numInvites} />
267+ }>
274 <Greeting point={point} />
268 <Greeting point={point} />
275 {!!numPending && (
269 {!!numPending && (
276 <div className="transaction">
270 <div className="transaction">
src/views/Points.js+44−5
@@ -27,7 +27,7 @@ import NavHeader from 'components/NavHeader';
27import L2PointHeader from 'components/L2/Headers/L2PointHeader';
27import L2PointHeader from 'components/L2/Headers/L2PointHeader';
28import IncomingPoint from 'components/L2/Points/IncomingPoint';
28import IncomingPoint from 'components/L2/Points/IncomingPoint';
29
29
30−const maybeGetResult = (obj, key, defaultValue) =>
30+export const maybeGetResult = (obj, key, defaultValue) =>
31 obj.matchWith({
31 obj.matchWith({
32 Nothing: () => defaultValue,
32 Nothing: () => defaultValue,
33 Just: p =>
33 Just: p =>
@@ -59,10 +59,14 @@ const PointList = function({
59 <Passport.Mini
59 <Passport.Mini
60 locked={locked}
60 locked={locked}
61 point={point}
61 point={point}
62− onClick={() => {
62+ onClick={
63+ locked
64+ ? undefined
65+ : () => {
63− setPointCursor(Just(point));
66+ setPointCursor(Just(point));
64− push(names.POINT);
67+ push(names.POINT);
68+ }
65− }}
69+ }
66 {...rest}
70 {...rest}
67 />
71 />
68 {actions && (
72 {actions && (
@@ -130,6 +134,34 @@ export default function Points() {
130 [getDetails, controlledPoints]
134 [getDetails, controlledPoints]
131 );
135 );
132
136
137+ const maybeLockedPoints = useMemo(
138+ () =>
139+ controlledPoints.chain(points =>
140+ points.matchWith({
141+ Error: () => Nothing(),
142+ Ok: c => {
143+ // TODO: how to determine this?
144+ const points = c.value.ownedPoints.map(point =>
145+ getDetails(point).chain(details =>
146+ Just({ point: point, has: hasTransferProxy(details) })
147+ )
148+ );
149+ // if we have details for every point,
150+ // return the array of pending transfers.
151+ if (points.every(p => Just.hasInstance(p))) {
152+ const locked = points
153+ .filter(p => p.value.has)
154+ .map(p => p.value.point);
155+ return Just(locked);
156+ } else {
157+ return Nothing();
158+ }
159+ },
160+ })
161+ ),
162+ [getDetails, controlledPoints]
163+ );
164+
133 // if we can only interact with a single point, forget about the existence
165 // if we can only interact with a single point, forget about the existence
134 // of this page and jump to the point page.
166 // of this page and jump to the point page.
135 // if there are any pending transfers, incoming or outgoing, stay on this
167 // if there are any pending transfers, incoming or outgoing, stay on this
@@ -194,6 +226,7 @@ export default function Points() {
194 const votingPoints = maybeGetResult(controlledPoints, 'votingPoints', []);
226 const votingPoints = maybeGetResult(controlledPoints, 'votingPoints', []);
195 const spawningPoints = maybeGetResult(controlledPoints, 'spawningPoints', []);
227 const spawningPoints = maybeGetResult(controlledPoints, 'spawningPoints', []);
196 const outgoingPoints = maybeOutgoingPoints.getOrElse([]);
228 const outgoingPoints = maybeOutgoingPoints.getOrElse([]);
229+ const lockedPoints = maybeLockedPoints.getOrElse([]);
197
230
198 const allPoints = [
231 const allPoints = [
199 ...ownedPoints.filter(p => !outgoingPoints.includes(p)),
232 ...ownedPoints.filter(p => !outgoingPoints.includes(p)),
@@ -346,6 +379,12 @@ export default function Points() {
346 </Grid.Item>
379 </Grid.Item>
347 )}
380 )}
348
381
382+ {lockedPoints.length > 0 && (
383+ <Grid.Item full as={Grid} gap={1}>
384+ <Grid.Item full as={PointList} locked points={allPoints} />
385+ </Grid.Item>
386+ )}
387+
349 <Footer>
388 <Footer>
350 <Grid>
389 <Grid>
351 <Grid.Divider />
390 <Grid.Divider />