Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react-wallet): Create app layout with left nav and app bar #3941

Merged
merged 3 commits into from
Oct 11, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/dapp-svelte-wallet/react-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"eslint-plugin-jest": "^24.5.2",
"react": "^16.8.0",
"react-dom": "^16.8.0",
"react-router-dom": "^5.3.0",
"react-scripts": "4.0.3",
"ses": "^0.12.6"
},
Expand Down
6 changes: 5 additions & 1 deletion packages/dapp-svelte-wallet/react-ui/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
<meta name="theme-color" content="#000000" />
<meta name="description" content="Agoric wallet" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link
href="https://fonts.googleapis.com/css?family=Montserrat"
rel="stylesheet"
/>
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
Expand All @@ -44,7 +48,7 @@
-->
<title>Agoric Wallet</title>
</head>
<body>
<body style="overflow-x: hidden; overflow-y: scroll">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
Expand Down
14 changes: 0 additions & 14 deletions packages/dapp-svelte-wallet/react-ui/src/App.css

This file was deleted.

89 changes: 76 additions & 13 deletions packages/dapp-svelte-wallet/react-ui/src/App.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,85 @@
/* eslint-disable react/display-name */
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { CssBaseline } from '@material-ui/core';
import {
createTheme,
makeStyles,
ThemeProvider,
} from '@material-ui/core/styles';

import WalletConnection from './components/WalletConnection';
import AppBar from './components/AppBar';
import NavMenu from './components/NavMenu';
import Contacts from './views/Contacts.js';
import Dapps from './views/Dapps.js';
import Dashboard from './views/Dashboard.js';
import Purses from './views/Purses.js';
import Issuers from './views/Issuers.js';

import './App.css';
import { withApplicationContext } from './contexts/Application';
const appBarHeight = '64px';
const navMenuWidth = '240px';

const App = ({ connectionState }) => {
const appTheme = createTheme({
palette: {
background: {
default: '#ffffff',
},
},
typography: {
fontFamily: ['"Roboto"', '"Helvetica"', '"Arial"', 'sans-serif'].join(','),
samsiegart marked this conversation as resolved.
Show resolved Hide resolved
fontWeightRegular: 500,
h1: {
fontFamily: ['"Montserrat"', '"Arial"', 'sans-serif'].join(','),
fontSize: '32px',
fontWeight: '700',
letterSpacing: '-1.5px',
lineHeight: '48px',
margin: 0,
},
},
appBarHeight,
navMenuWidth,
});

const useStyles = makeStyles(_ => ({
main: {
boxSizing: 'border-box',
padding: '32px',
marginLeft: navMenuWidth,
position: 'absolute',
width: `calc(100vw - ${navMenuWidth})`,
top: appBarHeight,
},
}));

const App = () => {
const classes = useStyles();
return (
<div className="App">
<header className="App-header">
Connection Status: {connectionState}
</header>
<WalletConnection></WalletConnection>
</div>
<ThemeProvider theme={appTheme}>
<CssBaseline />
<NavMenu />
<main className={classes.main}>
<Switch>
<Route path="/purses">
michaelfig marked this conversation as resolved.
Show resolved Hide resolved
<Purses />
</Route>
<Route path="/dapps">
<Dapps />
</Route>
<Route path="/contacts">
<Contacts />
</Route>
<Route path="/issuers">
<Issuers />
</Route>
<Route path="/">
<Dashboard />
</Route>
</Switch>
</main>
<AppBar />
</ThemeProvider>
);
};

export default withApplicationContext(App, context => ({
connectionState: context.connectionState,
}));
export default App;
61 changes: 61 additions & 0 deletions packages/dapp-svelte-wallet/react-ui/src/components/AppBar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { makeStyles, useTheme } from '@material-ui/core/styles';

import WalletConnection from './WalletConnection';

const useStyles = makeStyles(theme => ({
header: {
backgroundColor: theme.palette.background.default,
borderBottom: '1px solid #eaecef',
margin: 'auto',
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: theme.appBarHeight,
display: 'flex',
alignItems: 'center',
flexShrink: 0,
justifyContent: 'space-between',
flexDirection: 'row',
flexWrap: 'nowrap',
},
productLink: {
marginLeft: '16px',
alignItems: 'center',
display: 'flex',
},
productLogo: {
transform: 'scale(0.85)',
},
appBarSection: {
display: 'flex',
flexDirection: 'row',
padding: '4px',
height: '100%',
},
}));

const AppBar = () => {
const classes = useStyles(useTheme());
return (
<header className={classes.header}>
<div className={classes.appBarSection}>
<a href="https://agoric.com" className={classes.productLink}>
<img
src="https://agoric.com/wp-content/themes/agoric_2021_theme/assets/img/logo.svg"
className={classes.productLogo}
alt="Agoric"
width="200"
/>
</a>
</div>
<div className={classes.appBarSection}>
<div className={classes.connector}>
<WalletConnection></WalletConnection>
</div>
</div>
</header>
);
};

export default AppBar;
80 changes: 80 additions & 0 deletions packages/dapp-svelte-wallet/react-ui/src/components/NavMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useMemo, forwardRef } from 'react';
import { Link, useRouteMatch } from 'react-router-dom';
import { makeStyles, useTheme } from '@material-ui/core/styles';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import { ListItemText, ListItemIcon } from '@material-ui/core';
import DashboardIcon from '@material-ui/icons/Dashboard';
import AccountBalanceWallet from '@material-ui/icons/AccountBalanceWallet';
import Apps from '@material-ui/icons/Apps';
import People from '@material-ui/icons/People';
import AddCircle from '@material-ui/icons/AddCircle';

const useStyles = makeStyles(theme => ({
nav: {
position: 'fixed',
top: theme.appBarHeight,
width: theme.navMenuWidth,
height: `calc(100vh - ${theme.appBarHeight})`,
overflowY: 'auto',
},
sectionHeader: {
padding: '16px',
fontFamily: ['Montserrat', 'Arial', 'sans-serif'].join(','),
samsiegart marked this conversation as resolved.
Show resolved Hide resolved
fontWeight: 700,
letterSpacing: '0.15px',
fontSize: '16px',
},
}));

const ListItemLink = ({ icon, primary, to }) => {
const match = useRouteMatch({
path: to,
exact: true,
});

const renderLink = useMemo(
() =>
forwardRef((itemProps, ref) => {
return <Link to={to} ref={ref} {...itemProps} role={undefined} />;
}),
[to],
);

return (
<li>
<ListItem
selected={match !== null}
button
style={{ borderRadius: '0 32px 32px 0' }}
component={renderLink}
>
{icon ? <ListItemIcon>{icon}</ListItemIcon> : null}
<ListItemText primary={primary} />
</ListItem>
</li>
);
};

const NavMenu = () => {
const styles = useStyles(useTheme());

return (
<nav className={styles.nav}>
<div className={styles.sectionHeader}>Wallet</div>
<List>
<ListItemLink to="/" primary="Dashboard" icon={<DashboardIcon />} />
<ListItemLink
to="/purses"
primary="Purses"
icon={<AccountBalanceWallet />}
/>
<ListItemLink to="/dapps" primary="Dapps" icon={<Apps />} />
<ListItemLink to="/contacts" primary="Contacts" icon={<People />} />
<ListItemLink to="/issuers" primary="Issuers" icon={<AddCircle />} />
</List>
</nav>
);
};

export default NavMenu;
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable react/display-name */
import { makeReactAgoricWalletConnection } from '@agoric/wallet-connection/react.js';

import React, { useCallback } from 'react';
import { E } from '@agoric/eventual-send';
import { makeStyles } from '@material-ui/core/styles';
import { Typography } from '@material-ui/core';

import { withApplicationContext } from '../contexts/Application';

const useStyles = makeStyles(_ => ({
hidden: {
display: 'none',
},
connector: {
marginRight: '16px',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
height: '100%',
},
}));

// Create a wrapper for agoric-wallet-connection that is specific to
// the app's instance of React.
const AgoricWalletConnection = makeReactAgoricWalletConnection(React);
Expand Down Expand Up @@ -51,7 +66,8 @@ const getAccessToken = () => {
return accessToken;
};

const WalletConnection = ({ setConnectionState }) => {
const WalletConnection = ({ setConnectionState, connectionState }) => {
const classes = useStyles();
const onWalletState = useCallback(ev => {
const { walletConnection, state } = ev.detail;
console.log('onWalletState', state);
Expand All @@ -78,12 +94,20 @@ const WalletConnection = ({ setConnectionState }) => {
}, []);

return (
<span className="hidden">
<AgoricWalletConnection onState={onWalletState} />
</span>
<div className={classes.connector}>
samsiegart marked this conversation as resolved.
Show resolved Hide resolved
<Typography variant="body1">
Connection Status:{' '}
{connectionState === 'admin' ? 'Connected' : 'Disconnected'}
</Typography>
<AgoricWalletConnection
onState={onWalletState}
className={classes.hidden}
/>
</div>
);
};

export default withApplicationContext(WalletConnection, context => ({
setConnectionState: context.setConnectionState,
connectionState: context.connectionState,
}));
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,15 @@ jest.mock('@agoric/wallet-connection/react.js', () => {
});

const setConnectionState = jest.fn();
let connectionStatus = 'idle';
const withApplicationContext = (Component, _) => ({ ...props }) => {
return <Component setConnectionState={setConnectionState} {...props} />;
return (
<Component
setConnectionState={setConnectionState}
connectionState={connectionStatus}
{...props}
/>
);
};
jest.mock('../../contexts/Application', () => {
return { withApplicationContext };
Expand All @@ -45,6 +52,22 @@ describe('WalletConnection', () => {
expect(setConnectionState).toHaveBeenCalledWith('connecting');
});

test('displays the current connection status', () => {
let currentConnectionStatus = component.findWhere(
node =>
node.type() === 'p' && node.text().startsWith('Connection Status:'),
);
expect(currentConnectionStatus.text()).toContain('Disconnected');

connectionStatus = 'admin';
component.setProps({ connectionStatus });
currentConnectionStatus = component.findWhere(
node =>
node.type() === 'p' && node.text().startsWith('Connection Status:'),
);
expect(currentConnectionStatus.text()).toContain('Connected');
});

test('resets the connection on error state', () => {
const reset = jest.fn();

Expand Down
Loading