Skip to content

Commit

Permalink
feat(react-wallet): Create app layout with left nav and app bar (#3941)
Browse files Browse the repository at this point in the history
* feat(react-wallet): Create app layout with left nav and app bar

* feat(react-wallet): Fix font family quote usage
  • Loading branch information
samsiegart authored Oct 11, 2021
1 parent 5c9f5f0 commit 18807af
Show file tree
Hide file tree
Showing 16 changed files with 374 additions and 59 deletions.
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.14.3"
},
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(','),
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">
<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(','),
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}>
<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

0 comments on commit 18807af

Please sign in to comment.