on master help sign in
star-ops: Sponsor can detach Sponsee

- new roller util: #detach - new useRoller function: #kickPoint - show banner in Point view - styling

commit137116f5393d6799131799b3a657b965b3b08cd1 authortomholford <tomholford@users.noreply.github.com> dateSat 13 Nov 2021, 15:57 (5 years ago) parent56bd3b5
8 files changed+310−36
src/lib/useRoller.ts+24−1 src/lib/utils/roller.ts+22 src/views/Ops/Requests.scss+75 src/views/Ops/Requests.tsx+53−11 src/views/Ops/ResidentRow.tsx+47 src/views/Ops/Residents.scss+58 src/views/Ops/Residents.tsx+29−24 src/views/Point/Point.tsx+2
src/lib/useRoller.ts+24−1
@@ -6,8 +6,9 @@ import SecureLS from 'secure-ls';
66import { Invite } from 'lib/types/Invite';
77import { convertToInt } from './convertToInt';
88import { isDevelopment, isRopsten } from './flags';
9−import { generateInviteWallet, getPendingSpawns } from './utils/roller';
9+import { detach, generateInviteWallet, getPendingSpawns } from './utils/roller';
1010import { ROLLER_HOSTS } from './constants';
11+import { patp2dec } from './patp2dec';
1112
1213import { useNetwork } from 'store/network';
1314import { usePointCursor } from 'store/pointCursor';
@@ -636,6 +637,27 @@ export default function useRoller() {
636637 [api, pointCursor, wallet, web3, walletType]
637638 );
638639
640+ const kickPoint = useCallback(
641+ async (point: Ship) => {
642+ const sponsor = need.point(pointCursor);
643+ const _wallet = wallet.getOrElse(null);
644+
645+ const sponsorInfo = await api.getPoint(sponsor);
646+ const ownership = sponsorInfo.ownership!;
647+ const proxy = getManagerProxy(ownership, _wallet.address);
648+
649+ if (proxy === undefined)
650+ throw new Error("Error: Address doesn't match expected proxy");
651+
652+ const nonce = await api.getNonce({ ship: sponsor, proxy });
653+ const txHash = await detach(api, _wallet, sponsor, proxy, nonce, point);
654+ const pendingTx = await api.getPendingTx(txHash);
655+
656+ return pendingTx;
657+ },
658+ [api, pointCursor, wallet]
659+ );
660+
639661 const transferPoint = useCallback(
640662 async (address: EthAddress, reset?: boolean) => {
641663 const _point = need.point(pointCursor);
@@ -706,6 +728,7 @@ export default function useRoller() {
706728 getInvites,
707729 getPendingTransactions,
708730 generateInviteCodes,
731+ kickPoint,
709732 transferPoint,
710733 setProxyAddress,
711734 configureNetworkingKeys,
src/lib/utils/roller.ts+22
@@ -189,6 +189,28 @@ export const transferPointRequest = async (
189189 return api.transferPoint(sig, from, wallet.address, data);
190190};
191191
192+export const detach = async (
193+ api: RollerRPCAPI,
194+ _wallet: any,
195+ sponsor: Ship,
196+ proxy: string,
197+ nonce: number,
198+ ship: Ship
199+) => {
200+ const from = {
201+ ship: sponsor, //ship that is detaching the planet
202+ proxy,
203+ };
204+
205+ const data = {
206+ ship, // ship to detach
207+ };
208+
209+ const hash = await api.getUnsignedTx(nonce, from, 'detach', data);
210+ const sSig = signTransactionHash(hash, _wallet.privateKey);
211+ return api.detach(sSig, from, _wallet.address, data);
212+};
213+
192214const proxyType = (proxy: Proxy) => {
193215 switch (proxy) {
194216 case 'manage':
src/views/Ops/Requests.scssnew file+75
@@ -0,0 +1,75 @@
1+.ops-requests {
2+ div.content-container {
3+ height: 550px;
4+ overflow-y: scroll;
5+ width: 100%;
6+
7+ // hide the vertical scrollbar
8+ -ms-overflow-style: none; /* IE and Edge */
9+ scrollbar-width: none; /* Firefox */
10+ &::-webkit-scrollbar {
11+ display: none; /* Chrome */
12+ }
13+ }
14+
15+ .loading {
16+ &> svg {
17+ width: 24px;
18+ height: 24px;
19+ }
20+ }
21+
22+ .loading, .no-results {
23+ width: 100%;
24+ min-height: 500px;
25+ display: flex;
26+ flex-flow: column nowrap;
27+ justify-content: center;
28+ align-items: center;
29+ color: rgba(0,0,0,0.6);
30+ }
31+
32+ ul.requests-list {
33+ padding-left: unset;
34+ li {
35+ list-style: none;
36+ margin-bottom: 16px;
37+ height: 24px;
38+ display: flex;
39+ flex-flow: row nowrap;
40+
41+ &:first {
42+ margin-top: unset;
43+ }
44+
45+ div.sigil {
46+ height: 24px;
47+ width: 24px;
48+ margin-right: 8px;
49+ border-radius: 2px;
50+ background: black;
51+
52+ div.sigil-container {
53+ height: 16px;
54+ width: 16px;
55+ padding: 4px;
56+
57+ svg {
58+
59+ }
60+ }
61+
62+ }
63+
64+ .patp {
65+ font-family: Source Code Pro;
66+ font-size: 14px;
67+ font-style: normal;
68+ font-weight: 500;
69+ line-height: 16px;
70+ letter-spacing: 0em;
71+ align-self: center;
72+ }
73+ }
74+ }
75+}
src/views/Ops/Requests.tsx+53−11
@@ -1,5 +1,6 @@
11import * as need from 'lib/need';
2−import { Row } from '@tlon/indigo-react';
2+import ob from 'urbit-ob';
3+import { Box, LoadingSpinner, Row } from '@tlon/indigo-react';
34
45import Window from 'components/L2/Window/Window';
56import HeaderPane from 'components/L2/Window/HeaderPane';
@@ -8,25 +9,40 @@ import { useLocalRouter } from 'lib/LocalRouter';
89import View from 'components/View';
910import L2BackHeader from 'components/L2/Headers/L2BackHeader';
1011import { usePointCursor } from 'store/pointCursor';
11−import { usePointCache } from 'store/pointCache';
12−import { useEffect } from 'react';
12+import { useCallback, useEffect, useState } from 'react';
13+
14+import './Requests.scss';
15+import Sigil from 'components/Sigil';
16+import useRoller from 'lib/useRoller';
17+import { Ship } from '@urbit/roller-api';
1318
1419export const Requests = () => {
1520 const { pop }: any = useLocalRouter();
1621 const { pointCursor } = usePointCursor();
1722 const point = need.point(pointCursor);
23+ const { api } = useRoller();
24+ const [requestingPoints, setRequestingPoints] = useState<Ship[]>([]);
25+ const [loading, setLoading] = useState<boolean>(false);
26+
27+ const fetchRequests = useCallback(async () => {
28+ if (!point) {
29+ return;
30+ }
31+
32+ setLoading(true);
33+ const { requests } = await api.getSponsoredPoints(point);
1834
19− const { getResidents, syncResidents } = usePointCache();
20− const { requestCount } = getResidents(point);
35+ setRequestingPoints((requests || []).sort());
36+ setLoading(false);
37+ }, [api, point]);
2138
2239 useEffect(() => {
23− console.log('sync');
24− syncResidents(point);
25− }, [point, syncResidents]);
40+ fetchRequests();
41+ }, [fetchRequests]);
2642
2743 return (
2844 <View
29− className="ops-residents"
45+ className="ops-requests"
3046 pop={pop}
3147 inset
3248 hideBack
@@ -36,12 +52,38 @@ export const Requests = () => {
3652 <Row className="header-row">
3753 <h5>
3854 Requests{' '}
39− <span className="header-count">{requestCount?.value || 0}</span>
55+ <span className="header-count">{requestingPoints.length}</span>
4056 </h5>
4157 </Row>
4258 </HeaderPane>
4359 <BodyPane>
44− <p>Requests go here</p>
60+ <Box className="content-container">
61+ {loading ? (
62+ <Box className={'loading'}>
63+ <LoadingSpinner />
64+ </Box>
65+ ) : requestingPoints.length > 0 ? (
66+ <ul className="requests-list">
67+ {requestingPoints.map(sp => (
68+ <li>
69+ <Box className="sigil">
70+ <Box className="sigil-container">
71+ <Sigil
72+ icon
73+ patp={ob.patp(sp)}
74+ size={16}
75+ colors={['#000000', '#FFFFFF']}
76+ />
77+ </Box>
78+ </Box>
79+ <Box className={'patp'}>{ob.patp(sp)}</Box>
80+ </li>
81+ ))}
82+ </ul>
83+ ) : (
84+ <Box className="no-results">No requests</Box>
85+ )}
86+ </Box>
4587 </BodyPane>
4688 </Window>
4789 </View>
src/views/Ops/ResidentRow.tsxnew file+47
@@ -0,0 +1,47 @@
1+import ob from 'urbit-ob';
2+import { Box, Icon, Text} from '@tlon/indigo-react';
3+import { Ship } from '@urbit/roller-api';
4+import Sigil from 'components/Sigil';
5+import { useCallback, useState } from 'react';
6+
7+interface ResidentRowProps {
8+ point: Ship;
9+ onKick: (point: Ship) => Promise<void>;
10+}
11+
12+export const ResidentRow = ({ point, onKick }: ResidentRowProps) => {
13+ const [showDropdown, setShowDropdown] = useState<boolean>(false);
14+
15+ const handleKickClick = useCallback(async () => {
16+ setShowDropdown(false);
17+ await onKick(point);
18+ }, [onKick, point]);
19+
20+ return (
21+ <li key={point}>
22+ <Box className={'point-wrapper'}>
23+ <Box className="sigil">
24+ <Box className="sigil-container">
25+ <Sigil
26+ icon
27+ patp={ob.patp(point)}
28+ size={16}
29+ colors={['#000000', '#FFFFFF']}
30+ />
31+ </Box>
32+ </Box>
33+ <Box className={'patp'}>{ob.patp(point)}</Box>
34+ </Box>
35+ <Box
36+ className="button-wrapper"
37+ onClick={() => setShowDropdown(!showDropdown)}>
38+ <Icon icon="Ellipsis" size="18px" />
39+ {showDropdown && (
40+ <Box className={'kick-dropdown'} onClick={handleKickClick}>
41+ <Text>Kick</Text>
42+ </Box>
43+ )}
44+ </Box>
45+ </li>
46+ );
47+};
src/views/Ops/Residents.scss+58
@@ -12,6 +12,23 @@
1212 }
1313 }
1414
15+ .loading {
16+ &> svg {
17+ width: 24px;
18+ height: 24px;
19+ }
20+ }
21+
22+ .loading, .no-results {
23+ width: 100%;
24+ min-height: 500px;
25+ display: flex;
26+ flex-flow: column nowrap;
27+ justify-content: center;
28+ align-items: center;
29+ color: rgba(0,0,0,0.6);
30+ }
31+
1532 ul.residents-list {
1633 padding-left: unset;
1734 li {
@@ -20,11 +37,52 @@
2037 height: 24px;
2138 display: flex;
2239 flex-flow: row nowrap;
40+ justify-content: space-between;
2341
2442 &:first {
2543 margin-top: unset;
2644 }
2745
46+ .point-wrapper {
47+ display: flex;
48+ flex-flow: row nowrap;
49+ }
50+
51+ .button-wrapper {
52+ cursor: pointer;
53+ display: flex;
54+ flex-flow: column nowrap;
55+ justify-content: center;
56+ position: relative;
57+
58+ .kick-dropdown {
59+ z-index: 9999;
60+ display: flex;
61+ flex-direction: column;
62+ align-items: flex-start;
63+ justify-content: center;
64+ width: 100px;
65+ height: 48px;
66+ background-color: #fff;
67+ border: 1px solid rgba(0, 0, 0, 0.2);
68+ box-sizing: border-box;
69+ border-radius: 4px;
70+ position: absolute;
71+ top: 28px;
72+ right: 0px;
73+ padding: 16px;
74+
75+ span {
76+ font-family: Inter;
77+ font-size: 14px;
78+ font-style: normal;
79+ font-weight: 500;
80+ line-height: 16px;
81+ color: rgba(255, 65, 54, 1);
82+ }
83+ }
84+ }
85+
2886 div.sigil {
2987 height: 24px;
3088 width: 24px;
src/views/Ops/Residents.tsx+29−24
@@ -1,6 +1,5 @@
11import * as need from 'lib/need';
2−import ob from 'urbit-ob';
3−import { Box, Row } from '@tlon/indigo-react';
2+import { Box, LoadingSpinner, Row } from '@tlon/indigo-react';
43
54import Window from 'components/L2/Window/Window';
65import HeaderPane from 'components/L2/Window/HeaderPane';
@@ -9,31 +8,41 @@ import { useLocalRouter } from 'lib/LocalRouter';
98import View from 'components/View';
109import L2BackHeader from 'components/L2/Headers/L2BackHeader';
1110import { usePointCursor } from 'store/pointCursor';
12−import { useCallback, useEffect, useState } from 'react';
11+import { useCallback, useEffect, useMemo, useState } from 'react';
1312
1413import './Residents.scss';
15−import Sigil from 'components/Sigil';
1614import useRoller from 'lib/useRoller';
1715import { Ship } from '@urbit/roller-api';
16+import { ResidentRow } from './ResidentRow';
1817
1918export const Residents = () => {
2019 const { pop }: any = useLocalRouter();
2120 const { pointCursor } = usePointCursor();
22− const point = need.point(pointCursor);
23− const { api } = useRoller();
21+ const point = useMemo(() => need.point(pointCursor), [pointCursor]);
22+ const { api, kickPoint } = useRoller();
2423 const [sponsoredPoints, setSponsoredPoints] = useState<Ship[]>([]);
24+ const [loading, setLoading] = useState<boolean>(false);
2525
2626 const fetchResidents = useCallback(async () => {
2727 if (!point) {
2828 return;
2929 }
3030
31+ setLoading(true);
3132 // TODO: remove this hack once the API is updated
3233 const { residentes, residents } = await api.getSponsoredPoints(point);
33−
3434 setSponsoredPoints((residents || residentes || []).sort());
35+ setLoading(false);
3536 }, [api, point]);
3637
38+ const handleKick = useCallback(
39+ async (point: Ship) => {
40+ await kickPoint(point);
41+ await fetchResidents();
42+ },
43+ [fetchResidents, kickPoint]
44+ );
45+
3746 useEffect(() => {
3847 fetchResidents();
3948 }, [fetchResidents]);
@@ -56,23 +65,19 @@ export const Residents = () => {
5665 </HeaderPane>
5766 <BodyPane>
5867 <Box className="content-container">
59− <ul className="residents-list">
60− {sponsoredPoints.map(sp => (
61− <li>
62− <Box className="sigil">
63− <Box className="sigil-container">
64− <Sigil
65− icon
66− patp={ob.patp(sp)}
67− size={16}
68− colors={['#000000', '#FFFFFF']}
69− />
70− </Box>
71− </Box>
72− <Box className={'patp'}>{ob.patp(sp)}</Box>
73− </li>
74− ))}
75− </ul>
68+ {loading ? (
69+ <Box className={'loading'}>
70+ <LoadingSpinner />
71+ </Box>
72+ ) : sponsoredPoints.length > 0 ? (
73+ <ul className="residents-list">
74+ {sponsoredPoints.map(sp => (
75+ <ResidentRow point={sp} onKick={handleKick} />
76+ ))}
77+ </ul>
78+ ) : (
79+ <Box className="no-results">No residents</Box>
80+ )}
7681 </Box>
7782 </BodyPane>
7883 </Window>
src/views/Point/Point.tsx+2
@@ -201,6 +201,8 @@ export default function Point() {
201201 ? 'Transfer Proxy Changed'
202202 : pendingTx.rawTx?.tx?.type === 'set-spawn-proxy'
203203 ? 'Spawn Proxy Changed'
204+ : pendingTx.rawTx?.tx?.type === 'detach'
205+ ? 'Sponsee Detached'
204206 : ''}
205207 </div>
206208 <div className="rollup-timer">