on df0c310 help sign in
bridge/src/components/L2/InviteConverter.tsx
292 lines 8.6 kB history raw
1import {
2 Checkbox,
3 Col,
4 Row,
5 Button as IndigoButton,
6 Text,
7 Action,
8} from '@tlon/indigo-react';
9import { Button } from 'indigo-react';
10import Modal from './Modal';
11import { useCallback, useEffect, useState } from 'react';
12import { ReactComponent as InviteIcon } from 'assets/invite.svg';
13import Point from 'lib/types/Point';
14import useRoller from 'lib/useRoller';
15import BodyPane from './Window/BodyPane';
16import {
17 InviteGeneratingStatus,
18 useInvites,
19 useInviteStore,
20} from 'views/Invite/useInvites';
21import { usePointCache } from 'store/pointCache';
22import { CreatingInvitesModal } from './CreatingInvitesModal';
23import { useWallet } from 'store/wallet';
24import { WALLET_TYPES } from 'lib/constants';
26interface StarMap {
27 [key: string]: {
28 point: Point;
29 children: Point[];
30 };
33interface InviteConverterProps {
34 points: Point[];
37function selectText(selected: string[], children: Point[]) {
38 if (
39 allChildrenSelected(
40 selected,
41 children.map(p => p.patp)
42 )
43 ) {
44 return 'Deselect All';
45 }
47 return 'Select All';
50function allChildrenSelected(selected: string[], childrenPatps: string[]) {
51 const filtered = childrenPatps.filter(p => selected.includes(p));
52 return filtered.length === childrenPatps.length;
55export const InviteConverter = ({ points }: InviteConverterProps) => {
56 const { syncControlledPoints }: any = usePointCache();
57 const { walletType }: any = useWallet();
58 const signatureFree =
59 walletType === WALLET_TYPES.TICKET || walletType === WALLET_TYPES.SHARDS;
60 const { api, getPendingTransactions, getAndUpdatePoint } = useRoller();
61 const { inviteJobs } = useInviteStore();
62 const { generateInviteCodes } = useInvites();
63 const [show, setShow] = useState(false);
64 const [selected, setSelected] = useState<string[]>([]);
65 const [starMap, setStarMap] = useState<StarMap>({});
66 const [generatingStatus, setGeneratingStatus] = useState<
67 InviteGeneratingStatus
68 >('initial');
69 const [error, setError] = useState('');
70 const [created, setCreated] = useState(0);
71 const [currentPoint, setCurrentPoint] = useState<Point>();
72 const currentJob = inviteJobs[currentPoint?.value || 0];
73 const signatureCount = selected.length * 3;
75 useEffect(() => {
76 if (!points) {
77 return;
78 }
80 async function collect() {
81 const pointMap: StarMap = {};
82 const stars = points.filter(point => point.isStar && point.isL2Spawn);
84 for (const star of stars) {
85 const spawns = await api.getSpawned(star.value);
86 const filteredPoints = points.filter(
87 p => spawns.includes(p.value) && p.isL2Spawn
88 );
90 if (filteredPoints.length !== 0) {
91 pointMap[star.patp] = {
92 point: star,
93 children: filteredPoints,
94 };
95 }
96 }
98 setStarMap(pointMap);
99 }
101 collect();
102 }, [points]);
104 const selectChildren = useCallback(
105 (children: Point[]) => {
106 setSelected(state => {
107 const childrenPatps = children.map(p => p.patp);
108 const withoutChildren = state.filter(p => !childrenPatps.includes(p));
110 if (allChildrenSelected(state, childrenPatps)) {
111 return withoutChildren;
112 } else {
113 return withoutChildren.concat(childrenPatps);
114 }
115 });
116 },
117 [setSelected]
118 );
120 const togglePoint = useCallback(
121 (point: Point) => {
122 setSelected(state => {
123 if (state.includes(point.patp)) {
124 return state.filter(p => p !== point.patp);
125 } else {
126 return state.concat([point.patp]);
127 }
128 });
129 },
130 [setSelected]
131 );
133 const convert = useCallback(async () => {
134 if (generatingStatus !== 'initial') {
135 return;
136 }
138 setGeneratingStatus('generating');
140 try {
141 for (const { point, children } of Object.values(starMap)) {
142 const filtered = children
143 .filter(p => selected.includes(p.patp))
144 .map(p => p.value);
146 if (filtered.length <= 0) {
147 continue;
148 }
150 setCurrentPoint(point);
151 await generateInviteCodes(point, filtered, false);
152 await getAndUpdatePoint(point.value);
153 setCreated(children.length);
154 }
156 setGeneratingStatus('finished');
157 } catch (error) {
158 if (typeof error === 'object' && (error as Error)?.message) {
159 setError((error as Error).message);
160 } else if (typeof error === 'string') {
161 setError(error);
162 } else {
163 setError('Creating invites failed');
164 }
165 setGeneratingStatus('errored');
166 }
167 }, [
168 generatingStatus,
169 selected,
170 starMap,
171 generateInviteCodes,
172 getAndUpdatePoint,
173 ]);
175 const hasPlanets =
176 Object.entries(starMap).reduce((sum, [, star]) => {
177 return (sum += star.children.length);
178 }, 0) > 0;
180 if (generatingStatus !== 'initial') {
181 return (
182 <CreatingInvitesModal
183 status={generatingStatus}
184 current={created + currentJob?.generatingNum}
185 total={selected.length}
186 error={error}
187 hide={() => {
188 setGeneratingStatus('initial');
189 setSelected([]);
190 setShow(false);
191 setCreated(0);
192 getPendingTransactions();
193 syncControlledPoints();
194 }}
195 />
196 );
197 }
199 return (
200 <>
201 {/*
202 @ts-ignore */}
203 <Button className="create-invites" center onClick={() => setShow(true)}>
204 Update Invites
205 </Button>
206 <Modal small show={show} hide={() => setShow(false)}>
207 <BodyPane p={0}>
208 <Col width="368px">
209 <Row alignItems="center" mb={3}>
210 <InviteIcon />
211 <Text bold ml={2}>
212 Updates invites for planets
213 </Text>
214 </Row>
215 <Text mb={5}>
216 Select L2 planets you own to convert them back into invite links
217 under each star.{' '}
218 <Text color="red" lineHeight="24px">
219 Note: this will re-ticket the planets and render their existing
220 keys invalid.
221 </Text>
222 </Text>
223 {!signatureFree && (
224 <Text bold className={!!signatureCount ? '' : 'hidden'}>
225 {signatureCount} manual signatures will be required.
226 </Text>
227 )}
228 <Col
229 bg="washedGray"
230 height="300px"
231 mt={3}
232 p={3}
233 borderRadius={2}
234 overflowY="auto"
235 alignItems={hasPlanets ? '' : 'center'}
236 justifyContent={hasPlanets ? '' : 'center'}>
237 {Object.entries(starMap).map(([patp, { children }]) => (
238 <Col mb={4}>
239 <Row>
240 <Text bold mb={2}>
241 Owned by {patp}
242 </Text>
243 <Action
244 backgroundColor="transparent"
245 ml="auto"
246 onClick={() => selectChildren(children)}>
247 {selectText(selected, children)}
248 </Action>
249 </Row>
250 {children.map((point, index) => (
251 <Row
252 alignItems="center"
253 justifyContent="space-between"
254 py={1}
255 borderBottomWidth={
256 index === children.length - 1 ? '0px' : '1px'
257 }
258 borderBottomStyle="solid"
259 borderBottomColor="washedGray">
260 <Text>{point.patp}</Text>
261 <Checkbox
262 selected={selected.includes(point.patp)}
263 onClick={() => togglePoint(point)}
264 />
265 </Row>
266 ))}
267 </Col>
268 ))}
269 {!hasPlanets && <Text>No planets to update</Text>}
270 </Col>
271 <Row mt={3} justifyContent="end">
272 <IndigoButton
273 className="secondary"
274 onClick={() => setShow(false)}>
275 Close
276 </IndigoButton>
277 {/*
278 @ts-ignore */}
279 <IndigoButton
280 className={selected.length === 0 ? '' : 'primary'}
281 ml={2}
282 onClick={convert}
283 disabled={selected.length === 0}>
284 Convert Selected
285 </IndigoButton>
286 </Row>
287 </Col>
288 </BodyPane>
289 </Modal>
290 </>
291 );
292};