Skip to content

Commit e263160

Browse files
authored
Merge pull request #180 from application-research/feature-connect-wallet
Feature connect wallet
2 parents e095ffa + 58ae38d commit e263160

File tree

8 files changed

+165
-24
lines changed

8 files changed

+165
-24
lines changed

common/utilities.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,32 @@ export const getViewerFromToken = async (token) => {
146146
}
147147
};
148148

149+
export const getAuthAddress = async (headers) => {
150+
try {
151+
const token = Cookies.get(headers, C.auth);
152+
const url = `${C.api.authSvcHost}/user/auth-address`;
153+
const response = await fetch(url, {
154+
headers: {
155+
Authorization: `Bearer ${token}`,
156+
},
157+
});
158+
159+
const json = await response.json();
160+
161+
if (!json) {
162+
return null;
163+
}
164+
165+
if (json.error) {
166+
return null;
167+
}
168+
169+
return json;
170+
} catch (e) {
171+
return null;
172+
}
173+
}
174+
149175
export const getViewerFromHeader = async (headers) => {
150176
try {
151177
const token = Cookies.get(headers, C.auth);

components/Button.module.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,8 @@
3131
cursor: wait;
3232
padding: 4px 48px 4px 48px;
3333
}
34+
35+
.disabledButton {
36+
background-color: var(--main-button-background-secondary);
37+
cursor: not-allowed;
38+
}

components/Button.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ import styles from '@components/Button.module.scss';
66
import LoaderSpinner from '@components/LoaderSpinner';
77

88
const Button = (props: any) => {
9+
if (props.disabled) {
10+
return <button style={props.style}
11+
className={U.classNames(styles.button, styles.disabledButton)}
12+
children={props.children}
13+
disabled={props.disabled}/>;
14+
}
915
if (props.loading) {
1016
return (
1117
<button className={U.classNames(styles.button, styles.loading)} style={props.style}>

components/Wallet.tsx

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import style from '@components/Wallet.module.scss';
22

33
import * as React from 'react';
4-
import * as R from '@common/requests';
54
import * as C from '@common/constants';
65

76
import Web3 from 'web3';
8-
import Cookies from 'js-cookie';
97
import Button from '@components/Button';
108
import Jazzicon from "@metamask/jazzicon";
119
import styled from "@emotion/styled";
@@ -24,12 +22,6 @@ import {
2422
useId
2523
} from "@floating-ui/react";
2624

27-
function logout(props) {
28-
const token = Cookies.get(C.auth);
29-
const response = R.del(`/user/api-keys/${token}`, props.api);
30-
Cookies.remove(C.auth);
31-
window.location.href = '/';
32-
}
3325
function Wallet(props) {
3426
const [open, setOpen] = React.useState(false);
3527
const { x, y, refs, strategy, context } = useFloating({
@@ -54,7 +46,7 @@ function Wallet(props) {
5446
]);
5547

5648
const headingId = useId();
57-
const [state, setState] = React.useState({ account: null, fil: null, price: null, balance: null, loading: true});
49+
const [state, setState] = React.useState({ account: null, fil: null, price: null, balance: null, loading: true });
5850

5951
let web3: any;
6052

@@ -64,14 +56,6 @@ function Wallet(props) {
6456
}
6557
web3 = new Web3(window.ethereum);
6658
const run = async () => {
67-
window.ethereum.on('accountsChanged', function (accounts) {
68-
logout(props)
69-
})
70-
71-
window.ethereum.on('chainChanged', function (networkId) {
72-
logout(props)
73-
})
74-
7559
// Check if User is already connected by retrieving the accounts
7660
const accounts = await web3.eth.getAccounts()
7761
if (accounts) {
@@ -174,7 +158,7 @@ function Wallet(props) {
174158
</FloatingFocusManager>
175159
)}
176160
</div>
177-
) : null;
161+
) : null
178162
}
179163

180164
export default Wallet;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"scripts": {
88
"dev": "NEXT_PUBLIC_ESTUARY_API=${ESTUARY_API:-http://localhost:3004} next dev -p 4444",
99
"dev-docker": "NEXT_PUBLIC_ESTUARY_API=${ESTUARY_API:-http://localhost:3004} next dev -p 4444",
10-
"dev-production": "NEXT_PUBLIC_ESTUARY_API=${ESTUARY_API:-https://api.estuary.tech} NEXT_PUBLIC_ESTUARY_METRICS_API=https://metrics-api.estuary.tech next dev -p 4444",
10+
"dev-production": "NEXT_PUBLIC_ESTUARY_API=${ESTUARY_API:-https://api.estuary.tech} NEXT_PUBLIC_ESTUARY_METRICS_API=https://metrics-api.estuary.tech NEXT_PUBLIC_ESTUARY_AUTH_API=https://auth-svc.onrender.com next dev -p 4444",
1111
"build": "next build",
1212
"check-types": "tsc --noemit",
1313
"start": "NODE_ENV=production next start",

pages/settings.tsx

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import styles from '@pages/app.module.scss';
22

3+
import * as C from '@common/constants';
34
import * as Crypto from '@common/crypto';
45
import * as R from '@common/requests';
56
import * as U from '@common/utilities';
@@ -15,9 +16,11 @@ import Page from '@components/Page';
1516
import SingleColumnLayout from '@components/SingleColumnLayout';
1617

1718
import { H2, H3, H4, P } from '@components/Typography';
19+
import Modal from '@components/Modal';
1820

1921
export async function getServerSideProps(context) {
2022
const viewer = await U.getViewerFromHeader(context.req.headers);
23+
const wallet = await U.getAuthAddress(context.req.headers);
2124
const host = context.req.headers.host;
2225
const protocol = host.split(':')[0] === 'localhost' ? 'http' : 'https';
2326

@@ -30,6 +33,8 @@ export async function getServerSideProps(context) {
3033
};
3134
}
3235

36+
viewer.wallet = wallet;
37+
3338
return {
3439
props: { host, protocol, viewer, api: process.env.NEXT_PUBLIC_ESTUARY_API, hostname: `https://${context.req.headers.host}` },
3540
};
@@ -80,12 +85,83 @@ const onSubmit = async (event, state, setState, host) => {
8085
return setState({ ...state, new: '', confirm: '', loading: false });
8186
};
8287

88+
const connectWallet = async (e, metamask, setMetamask) => {
89+
setMetamask({ ...metamask, loading: true});
90+
if (!window.ethereum) {
91+
alert("You must have MetaMask installed!");
92+
return;
93+
}
94+
95+
const accounts = await window.ethereum.request({ method: "eth_requestAccounts" });
96+
97+
if (window.ethereum.networkVersion !== C.network.chainId) {
98+
try {
99+
await window.ethereum.request({
100+
method: 'wallet_switchEthereumChain',
101+
params: [{ chainId: C.network.chainId }]
102+
});
103+
} catch (err) {
104+
// This error code indicates that the chain has not been added to MetaMask
105+
if (err.code === 4902) {
106+
await window.ethereum.request({
107+
method: 'wallet_addEthereumChain',
108+
params: [ C.network ]
109+
});
110+
}
111+
}
112+
}
113+
114+
return addAuthAddress(accounts[0], metamask, setMetamask)
115+
}
116+
117+
async function addAuthAddress(address, metamask, setMetamask) {
118+
setMetamask({ ...metamask, loading: true});
119+
let response;
120+
try {
121+
response = await R.post('/user/auth-address', { address }, C.api.authSvcHost);
122+
await U.delay(1000);
123+
124+
if (!response.success) {
125+
alert(response.details);
126+
return setMetamask({ ...metamask, loading: false});
127+
}
128+
} catch (e) {
129+
console.log(e);
130+
alert('Something went wrong');
131+
return setMetamask({ ...metamask, loading: false});
132+
}
133+
134+
return setMetamask({ ...metamask, address, loading: false});
135+
}
136+
137+
async function removeAuthAddress(metamask, setMetamask) {
138+
setMetamask({ ...metamask, loading: true});
139+
let response;
140+
try {
141+
response = await R.del('/user/auth-address', { address: metamask.address }, C.api.authSvcHost);
142+
await U.delay(1000);
143+
144+
if (!response.success) {
145+
alert(response.details);
146+
return setMetamask({ ...metamask, loading: false});
147+
}
148+
} catch (e) {
149+
console.log(e);
150+
alert('Something went wrong');
151+
return setMetamask({ ...metamask, loading: false});
152+
}
153+
154+
return setMetamask({ ...metamask, address: "", loading: false});
155+
}
156+
83157
function SettingsPage(props: any) {
84158
const { viewer } = props;
85159
const [fissionUser, setFissionUser] = React.useState(null);
86160
const [state, setState] = React.useState({ loading: false, old: '', new: '', confirm: '' });
87161
const [address, setAddress] = React.useState('');
88162
const [balance, setBalance] = React.useState(0);
163+
const [metamask, setMetamask] = React.useState({ address: viewer.wallet ? viewer.wallet.address : null, loading: false });
164+
const [showUnlinkAddressModal, setShowUnlinkAddressModal] = React.useState(false);
89165

90166
const sidebarElement = <AuthenticatedSidebar active="SETTINGS" viewer={viewer} />;
91167

@@ -141,6 +217,45 @@ function SettingsPage(props: any) {
141217
</Button>
142218
</div>
143219

220+
<H3 style={{ marginTop: 64 }}>Link Metamask</H3>
221+
<P style={{ marginTop: 16 }}>Enable authenticating with your Metamask account.</P>
222+
223+
{ metamask.address ? (
224+
<div>
225+
<Input style={{ marginTop: 8 }} readOnly value={metamask.address} />
226+
<Button style={{ marginTop: 14, width: 175}}
227+
loading={metamask.loading}
228+
disabled={metamask.address == viewer.username}
229+
onClick={() => setShowUnlinkAddressModal(true) }>Unlink Account</Button>
230+
{showUnlinkAddressModal && (
231+
<Modal
232+
title="Unlink Account"
233+
onClose={() => {
234+
setShowUnlinkAddressModal(false);
235+
}}
236+
show={showUnlinkAddressModal}
237+
>
238+
<div className={styles.group} style={{ paddingTop: '16px' }}>
239+
<P style={{ maxWidth: '620px' }}>
240+
By unlinking your metamask account, you will no longer be able to log into your account using this authentication method. Are you sure you want to unlink?
241+
</P>
242+
<H4 style={{ marginTop: '16px', width: '488px', display: 'inline-block' }}></H4>
243+
<Button style={{ marginTop: 14, width: 175}}
244+
loading={metamask.loading}
245+
disabled={metamask.address == viewer.username}
246+
onClick={async () => {
247+
setShowUnlinkAddressModal(false);
248+
await removeAuthAddress(metamask, setMetamask)
249+
}}>Unlink Account</Button>
250+
</div>
251+
</Modal>
252+
)}
253+
</div>
254+
) : (
255+
<Button style={{ marginTop: 14, width: 175 }}
256+
loading={metamask.loading}
257+
onClick={(e) => connectWallet(e, metamask, setMetamask)}>Connect Wallet</Button>
258+
)}
144259
<H3 style={{ marginTop: 64 }}>Default settings (read only)</H3>
145260
<P style={{ marginTop: 16 }}>Estuary is configured to default settings for deals. You can not change these values, yet.</P>
146261

pages/sign-in.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,12 @@ async function handleSignInWithMetaMask(state: any, host) {
7171
},
7272
});
7373

74+
const respJson = await response.json();
75+
7476
if (response.status !== 200) {
75-
return { error: 'Failed to Generate Nonce Message' };
77+
return { error: respJson.details };
7678
}
7779

78-
const respJson = await response.json();
7980
if (respJson.error) {
8081
return respJson;
8182
}

pages/sign-up.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,11 @@ async function handleRegisterWithMetaMask(state: any, host) {
7676
});
7777

7878
if (userCreationResp.status !== 200) {
79-
return { error: 'Failed to Create User' };
79+
const userCreationRespJson = await userCreationResp.json();
80+
if (!userCreationRespJson.details) {
81+
return { error: 'Our server failed to register your account. Please contact us.' };
82+
}
83+
return { error: userCreationRespJson.details };
8084
}
8185

8286
let from = accounts[0];
@@ -90,11 +94,11 @@ async function handleRegisterWithMetaMask(state: any, host) {
9094
},
9195
});
9296

97+
const respJson = await response.json();
9398
if (response.status !== 200) {
94-
return { error: 'Failed to Generate Nonce Message' };
99+
return { error: respJson.details };
95100
}
96101

97-
const respJson = await response.json();
98102
if (respJson.error) {
99103
return respJson;
100104
}

0 commit comments

Comments
 (0)