refactor so that consuming app chooses to render modal and what connectors to use

This commit is contained in:
Matthew Russell 2022-02-22 15:06:35 -08:00
parent ff24a4a3ba
commit fba3101753
10 changed files with 134 additions and 98 deletions

View File

@ -0,0 +1,6 @@
import { InjectedConnector, RestConnector } from '@vegaprotocol/react-helpers';
export const Connectors = {
injected: new InjectedConnector(),
rest: new RestConnector(),
};

View File

@ -50,15 +50,19 @@ function VegaTradingApp({ Component, pageProps }: AppProps) {
);
}
const VegaWalletButton = () => {
const { setConnectDialog, disconnect, publicKeys } = useVegaWallet();
interface VegaWalletButtonProps {
setConnectDialog: (isOpen: boolean) => void;
}
const VegaWalletButton = ({ setConnectDialog }: VegaWalletButtonProps) => {
const { disconnect, publicKeys } = useVegaWallet();
const isConnected = publicKeys !== null;
const handleClick = () => {
if (isConnected) {
disconnect();
} else {
setConnectDialog();
setConnectDialog(true);
}
};

View File

@ -1,63 +1,80 @@
import { useState } from 'react';
import { useCallback, useState } from 'react';
import { Dialog } from '@vegaprotocol/ui-toolkit';
import {
InjectedConnector,
RestConnector,
VegaConnector,
} from './vega-wallet-connectors';
import { VegaConnector } from './connectors';
import { RestConnectorForm } from './rest-connector-form';
import { useEffect } from 'react';
import { RestConnector, useVegaWallet } from '.';
export const Connectors = {
injected: new InjectedConnector(),
rest: new RestConnector(),
};
interface ConnectDialogProps {
interface VegaConnectDialogProps {
connectors: { [name: string]: VegaConnector };
dialogOpen: boolean;
setDialogOpen: (isOpen: boolean) => void;
connect: (connector: VegaConnector) => void;
}
export function ConnectDialog({
export function VegaConnectDialog({
connectors,
dialogOpen,
setDialogOpen,
connect,
}: ConnectDialogProps) {
const [isRestConnector, setIsRestConnector] = useState(false);
}: VegaConnectDialogProps) {
const { connect } = useVegaWallet();
const [selectedConnector, setSelectedConnector] =
useState<VegaConnector | null>(null);
const connectAndClose = useCallback(
(connector: VegaConnector) => {
connect(connector);
setDialogOpen(false);
},
[connect, setDialogOpen]
);
useEffect(() => {
if (!dialogOpen) {
setIsRestConnector(false);
if (
selectedConnector !== null &&
selectedConnector instanceof RestConnector === false
) {
connectAndClose(selectedConnector);
}
}, [dialogOpen]);
}, [selectedConnector, connectAndClose]);
return (
<Dialog open={dialogOpen} setOpen={setDialogOpen}>
<div className="bg-black p-5 w-100 text-white">
{isRestConnector ? (
<RestConnectorForm setDialogOpen={setDialogOpen} />
) : (
<div className="flex gap-5 flex-col">
{Object.entries(Connectors).map(([key, connector]) => (
<button
key={key}
onClick={() => {
if (key === 'rest') {
// show form so that we can get an authentication token before 'connecting'
setIsRestConnector(true);
} else {
connect(connector);
setDialogOpen(false);
}
}}
>
{key} provider
</button>
))}
</div>
)}
</div>
{selectedConnector instanceof RestConnector ? (
<RestConnectorForm
connector={selectedConnector}
setDialogOpen={setDialogOpen}
onAuthenticate={() => {
connectAndClose(selectedConnector);
}}
/>
) : (
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
gap: 10,
}}
>
{Object.entries(connectors).map(([key, connector]) => (
<button
key={key}
onClick={() => {
setSelectedConnector(connector);
// if (key === 'rest') {
// // show form so that we can get an authentication token before 'connecting'
// setIsRestConnector(true);
// } else {
// connect(connector);
// setDialogOpen(false);
// }
}}
>
{key} provider
</button>
))}
</div>
)}
</Dialog>
);
}

View File

@ -1,11 +1,10 @@
import { VegaKey } from '@vegaprotocol/vegawallet-service-api-client';
import { createContext } from 'react';
import { VegaConnector } from './vega-wallet-connectors';
import { VegaConnector } from './connectors';
interface VegaWalletContextShape {
publicKey: VegaKey | null;
publicKeys: VegaKey[] | null;
setConnectDialog: (isOpen?: boolean) => void;
connect: (connector: VegaConnector) => Promise<void>;
disconnect: () => Promise<void>;
connector: VegaConnector | null;

View File

@ -1,3 +1,5 @@
export * from './vega-wallet-provider';
export * from './vega-wallet-context';
export * from './provider';
export * from './context';
export * from './hooks';
export * from './connect-dialog';
export * from './connectors';

View File

@ -1,40 +1,31 @@
import { VegaKey } from '@vegaprotocol/vegawallet-service-api-client';
import { ReactNode, useCallback, useMemo, useRef, useState } from 'react';
import { ConnectDialog } from './connect-dialog';
import { VegaConnector } from './vega-wallet-connectors';
import { VegaWalletContext } from './vega-wallet-context';
import { VegaConnector } from './connectors';
import { VegaWalletContext } from './context';
interface VegaWalletProviderProps {
children: ReactNode;
}
export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
const [dialogOpen, setDialogOpen] = useState(false);
const [publicKey, setPublicKey] = useState<VegaKey | null>(null);
const [publicKeys, setPublicKeys] = useState<VegaKey[] | null>(null);
const connector = useRef<VegaConnector | null>(null);
const setConnectDialog = useCallback((isOpen?: boolean) => {
setDialogOpen((curr) => {
if (isOpen === undefined) return !curr;
return isOpen;
});
}, []);
const connect = useCallback(async (c: VegaConnector) => {
connector.current = c;
try {
const res = await c.connect();
setPublicKeys(res);
setPublicKey(res[0]);
} catch (err) {
console.log('connect failed');
}
}, []);
const disconnect = useCallback(async () => {
if (!connector.current) return;
try {
await connector.current.disconnect();
await connector.current?.disconnect();
setPublicKeys(null);
setPublicKey(null);
connector.current = null;
@ -47,21 +38,15 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
return {
publicKey,
publicKeys,
setConnectDialog,
connect,
disconnect,
connector: connector.current,
};
}, [publicKey, publicKeys, connect, disconnect, setConnectDialog, connector]);
}, [publicKey, publicKeys, connect, disconnect, connector]);
return (
<VegaWalletContext.Provider value={contextValue}>
{children}
<ConnectDialog
dialogOpen={dialogOpen}
setDialogOpen={setDialogOpen}
connect={connect}
/>
</VegaWalletContext.Provider>
);
};

View File

@ -1,6 +1,5 @@
import { useForm } from 'react-hook-form';
import { useVegaWallet } from '.';
import { Connectors } from './connect-dialog';
import { RestConnector } from '.';
interface FormFields {
wallet: string;
@ -8,23 +7,31 @@ interface FormFields {
}
interface RestConnectorFormProps {
connector: RestConnector;
setDialogOpen: (isOpen: boolean) => void;
onAuthenticate: () => void;
}
export function RestConnectorForm({ setDialogOpen }: RestConnectorFormProps) {
const { connect } = useVegaWallet();
const { register, handleSubmit } = useForm<FormFields>();
export function RestConnectorForm({
connector,
setDialogOpen,
onAuthenticate,
}: RestConnectorFormProps) {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormFields>();
async function onSubmit(fields: FormFields) {
try {
const success = await Connectors.rest.authenticate({
const success = await connector.authenticate({
wallet: fields.wallet,
passphrase: fields.passphrase,
});
if (success) {
connect(Connectors.rest);
setDialogOpen(false);
onAuthenticate();
} else {
throw new Error('Authentication failed');
}
@ -35,21 +42,21 @@ export function RestConnectorForm({ setDialogOpen }: RestConnectorFormProps) {
return (
<form onSubmit={handleSubmit(onSubmit)} className="vega-wallet-form">
<div className="mb-5">
<div style={{ marginBottom: 10 }}>
<input
{...register('wallet')}
{...register('wallet', { required: 'Required' })}
type="text"
placeholder="Wallet"
className="text-black"
/>
{errors.wallet?.message && <div>{errors.wallet.message}</div>}
</div>
<div className="mb-5">
<div style={{ marginBottom: 10 }}>
<input
{...register('passphrase')}
{...register('passphrase', { required: 'Required' })}
type="text"
placeholder="Passphrase"
className="text-black"
/>
{errors.passphrase?.message && <div>{errors.passphrase.message}</div>}
</div>
<button type="submit">Connect</button>
</form>

View File

@ -1,18 +1,19 @@
// TODO: fine for now however will leak state between tests (we don't really have) in future. Ideally should use a provider
export const LocalStorage = {
getItem: (key: string) => {
if (typeof window === 'undefined') return null;
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : null;
} catch (error) {
console.error(error);
}
},
setItem: (key: string, value: any) => {
if (typeof window === 'undefined') return;
try {
window.localStorage.setItem(key, JSON.stringify(value));
const item = window.localStorage.getItem(key);
return item;
} catch (error) {
console.error(error);
return null;
}
},
setItem: (key: string, value: string) => {
if (typeof window === 'undefined') return;
try {
window.localStorage.setItem(key, value);
} catch (error) {
console.error(error);
}

View File

@ -11,10 +11,25 @@ export function Dialog({ children, open, setOpen }: DialogProps) {
return (
<DialogPrimitives.Root open={open} onOpenChange={(x) => setOpen(x)}>
<DialogPrimitives.Portal>
<DialogPrimitives.Overlay className="bg-gray/75 fixed inset-0" />
<DialogPrimitives.Overlay
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.3)',
}}
/>
<DialogPrimitives.Content
style={{ width: 300, top: 40, left: 'calc(50% - 150px)' }}
className="fixed inset-0"
style={{
position: 'fixed',
width: 300,
background: 'white',
top: 40,
left: 'calc(50% - 150px)',
padding: 20,
}}
>
{children}
</DialogPrimitives.Content>