Skip to content

Commit

Permalink
Merge pull request #62 from penumbra-zone/feat/#45-v2-nav
Browse files Browse the repository at this point in the history
feat: header with connection button
  • Loading branch information
VanishMax authored Sep 23, 2024
2 parents a23b8ba + 01c944b commit ff14678
Show file tree
Hide file tree
Showing 18 changed files with 761 additions and 16 deletions.
7 changes: 7 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
const nextConfig = {
transpilePackages: ["@penumbra-zone/protobuf"],
webpack: (config) => {
config.module.rules.push({
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: ['@svgr/webpack'],
})

config.experiments.asyncWebAssembly = true;

return config;
},
output: "standalone",
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
"@emotion/styled": "^11.13.0",
"@grpc/proto-loader": "^0.7.13",
"@penumbra-labs/registry": "^11.3.0",
"@penumbra-zone/client": "^18.1.0",
"@penumbra-zone/protobuf": "^6.0.0",
"@penumbra-zone/transport-dom": "^7.5.0",
"@penumbra-zone/ui": "^9.0.0",
"@penumbra-zone/wasm": "^26.2.0",
"@radix-ui/react-icons": "^1.3.0",
Expand All @@ -53,6 +55,7 @@
"grpc-tools": "^1.12.4",
"grpc_tools_node_protoc_ts": "^5.3.3",
"lodash": "^4.17.21",
"lucide-react": "^0.445.0",
"next": "^14.2.7",
"pg": "^8.12.0",
"react": "^18.3.1",
Expand All @@ -70,6 +73,7 @@
"@babel/preset-typescript": "^7.24.7",
"@chakra-ui/react-types": "^2.0.6",
"@eslint/js": "^9.10.0",
"@svgr/webpack": "^8.1.0",
"@types/date-fns": "^2.6.0",
"@types/lodash": "^4.17.7",
"@types/node": "^22.5.4",
Expand Down
434 changes: 423 additions & 11 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion src/app/v2/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@

import { ReactNode } from 'react';
import { PenumbraUIProvider } from '@penumbra-zone/ui/PenumbraUIProvider';
import { Display } from '@penumbra-zone/ui/Display';
import { Header } from '@/components/header';

const V2Layout = ({ children }: { children: ReactNode }) => {
return (
<PenumbraUIProvider>
{children}
<Display>
<Header />
{children}
</Display>
</PenumbraUIProvider>
)
};
Expand Down
4 changes: 0 additions & 4 deletions src/app/v2/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ import { Text } from '@penumbra-zone/ui/Text';
const HomePage = () => {
return (
<section>
<h1 className="text-3xl font-bold text-white">
Hello world!
</h1>

<Text h2>Hi!</Text>
</section>
)
Expand Down
4 changes: 4 additions & 0 deletions src/components/charts/ohlcChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,10 @@ const OHLCChart = ({ asset1Token, asset2Token }: OHLCChartProps) => {
let currentIntervalStart: Date | null = null;

originalOHLCData.forEach((ohlc, index) => {
if (!blockToTimestamp[ohlc.height]) {
return;
}

const timestamp = new Date(blockToTimestamp[ohlc.height]);
const intervalStart = getIntervalStart(timestamp.toISOString());

Expand Down
15 changes: 15 additions & 0 deletions src/components/header/connection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Button } from '@penumbra-zone/ui/Button';
import { useConnect } from '@/utils/penumbra/useConnect.ts';
import { ProviderPopover } from './provider-popover.tsx';

export const Connection = () => {
const { connected, connect } = useConnect();

if (!connected) {
return (
<Button actionType='accent' onClick={() => void connect()}>Connect</Button>
);
}

return <ProviderPopover />
};
23 changes: 23 additions & 0 deletions src/components/header/desktop-nav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useRouter } from 'next/navigation';
import { Tabs } from '@penumbra-zone/ui/Tabs';
import { Density } from '@penumbra-zone/ui/Density';
import { HEADER_LINKS } from './links.ts';
import { usePagePath } from '@/utils/routes/usePagePath.ts';

export const DesktopNav = () => {
const pagePath = usePagePath();
const router = useRouter();

return (
<nav className='hidden rounded-full bg-v2-other-tonalFill5 px-4 py-1 backdrop-blur-xl lg:flex'>
<Density compact>
<Tabs
value={pagePath}
onChange={value => router.push(value)}
options={HEADER_LINKS}
actionType='accent'
/>
</Density>
</nav>
);
};
26 changes: 26 additions & 0 deletions src/components/header/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Density } from '@penumbra-zone/ui/Density';
import { HeaderLogo } from './logo.tsx';
// import { StatusPopover } from './status-popover.tsx';
import { MobileNav } from './mobile-nav.tsx';
import { DesktopNav } from './desktop-nav.tsx';
import { Connection } from './connection.tsx';

export const Header = () => {
return (
<header className='flex items-center justify-between py-5'>
<HeaderLogo />

<DesktopNav />

<Density compact>
<div className='hidden gap-2 lg:flex'>
{/* <StatusPopover />*/}
<Connection />
</div>
<div className='block lg:hidden'>
<MobileNav />
</div>
</Density>
</header>
);
};
9 changes: 9 additions & 0 deletions src/components/header/links.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { MoonStar, ArrowUpFromDot, Coins } from 'lucide-react';
import { PagePath } from '@/utils/routes/pages.ts';

export const HEADER_LINKS = [
{ label: 'Trade', value: PagePath.Trade, icon: ArrowUpFromDot },
{ label: 'Explore', value: PagePath.Explore, icon: Coins },
{ label: 'Inspect', value: PagePath.Inspect, icon: MoonStar },
{ label: 'Portfolio', value: PagePath.Portfolio, icon: Coins },
];
3 changes: 3 additions & 0 deletions src/components/header/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/components/header/logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Link from 'next/link'
import { PagePath } from '@/utils/routes/pages.ts';
import PenumbraLogo from './logo.svg';

export const HeaderLogo = () => {
return (
<Link className='flex h-8 items-center' href={PagePath.Explore}>
<PenumbraLogo />
</Link>
);
};
57 changes: 57 additions & 0 deletions src/components/header/mobile-nav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { Menu, X } from 'lucide-react';
import { Button } from '@penumbra-zone/ui/Button';
import { Dialog } from '@penumbra-zone/ui/Dialog';
import { Display } from '@penumbra-zone/ui/Display';
import { MenuItem } from '@penumbra-zone/ui/MenuItem';
// import { StatusPopover } from './status-popover.tsx';
import { HeaderLogo } from './logo.tsx';
import { HEADER_LINKS } from './links.ts';
import { Connection } from './connection.tsx';

export const MobileNav = () => {
const router = useRouter();
const [isOpen, setIsOpen] = useState(false);

const onNavigate = (link: string) => {
router.push(link);
setIsOpen(false);
};

return (
<Dialog isOpen={isOpen} onClose={() => setIsOpen(false)}>
<Button iconOnly icon={Menu} onClick={() => setIsOpen(true)}>
Menu
</Button>
<Dialog.EmptyContent>
<div className='pointer-events-auto h-full overflow-hidden bg-black'>
<Display>
<nav className='flex items-center justify-between py-5'>
<HeaderLogo />

<div className='flex gap-2'>
{/* <StatusPopover />*/}
<Connection />
<Button iconOnly icon={X} onClick={() => setIsOpen(false)}>
Close
</Button>
</div>
</nav>

<div className='flex flex-col gap-4'>
{HEADER_LINKS.map(link => (
<MenuItem
key={link.value}
label={link.label}
icon={link.icon}
onClick={() => onNavigate(link.value)}
/>
))}
</div>
</Display>
</div>
</Dialog.EmptyContent>
</Dialog>
);
};
51 changes: 51 additions & 0 deletions src/components/header/provider-popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useMemo } from 'react';
import { Link2Off } from 'lucide-react';
import Image from 'next/image';
import { Popover } from '@penumbra-zone/ui/Popover';
import { Button } from '@penumbra-zone/ui/Button';
import { Text } from '@penumbra-zone/ui/Text';
import { useConnect } from '@/utils/penumbra/useConnect.ts';

export const ProviderPopover = () => {
const { disconnect, manifest } = useConnect();

const name = manifest?.['name'] as string;
const version = manifest?.['version'] as string;
const description = manifest?.['description'] as string;

const icon = useMemo(() => {
const icons = (manifest?.['icons'] ?? {}) as Record<string, Blob>;
const blob = icons['32'] ?? icons['128'];
const element = !blob ? null : (
<Image width={16} height={16} src={URL.createObjectURL(blob)} alt={name} className='size-4' />
);
return () => element;
}, [name, manifest]);

return (
<Popover>
<Popover.Trigger>
<Button icon={icon} iconOnly>
{name}
</Button>
</Popover.Trigger>
<Popover.Content align='end' side='bottom'>
{manifest ? (
<div className='flex flex-col gap-2'>
<Text body>
{name} v{version}
</Text>
<Text small>{description}</Text>
</div>
) : (
<Text body>Loading provider manifest...</Text>
)}
<div className='mt-4'>
<Button icon={Link2Off} onClick={() => void disconnect()}>
Disconnect
</Button>
</div>
</Popover.Content>
</Popover>
);
};
6 changes: 6 additions & 0 deletions src/utils/penumbra/penumbra.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createPenumbraClient } from '@penumbra-zone/client';

const PRAX_ID = 'lkpmkhpnhknhmibgnmmhdhgdilepfghe';
export const PRAX_ORIGIN = new URL(`chrome-extension://${PRAX_ID}`).origin;

export const penumbra = createPenumbraClient();
79 changes: 79 additions & 0 deletions src/utils/penumbra/useConnect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { useEffect, useState } from 'react';
import { penumbra, PRAX_ORIGIN } from '@/utils/penumbra/penumbra.ts';
import { PenumbraRequestFailure, PenumbraState, PenumbraManifest } from '@penumbra-zone/client';

export const useConnect = () => {
const [manifest, setManifest] = useState<PenumbraManifest>();
const [connectionLoading, setConnectionLoading] = useState<boolean>(false);
const [connected, setConnected] = useState<boolean>(false);

const reconnect = async () => {
await penumbra.attach(PRAX_ORIGIN);
if (!penumbra.connected) {
return;
}

try {
await penumbra.connect();
setConnected(true);
} catch (error) {
/* no-op */
}
};

const connect = async () => {
try {
setConnectionLoading(true);
await penumbra.connect();
} catch (error) {
if (error instanceof Error && error.cause) {
if (error.cause === PenumbraRequestFailure.Denied) {
// TODO: replace these alerts with toasts
alert('Connection denied: you may need to un-ignore this site in your extension settings.');
}
if (error.cause === PenumbraRequestFailure.NeedsLogin) {
alert('Not logged in: please login into the extension and try again');
}
}
} finally {
setConnectionLoading(false);
}
};

const disconnect = async () => {
if (!penumbra.connected) {
return;
}

try {
await penumbra.disconnect();
} catch (error) {
console.error(error);
}
};

// Monitors the connection
useEffect(() => {
setManifest(penumbra.manifest);

// If Prax is connected on page load, reconnect to ensure the connection is still active
void reconnect();

penumbra.onConnectionStateChange((event) => {
setManifest(penumbra.manifest);
if (event.state === PenumbraState.Connected) {
setConnected(true);
} else {
setConnected(false);
}
});
}, []);

return {
manifest,
connectionLoading,
connected,
connect,
disconnect,
}
};
6 changes: 6 additions & 0 deletions src/utils/routes/pages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum PagePath {
Explore = '/v2',
Trade = '/v2/trade',
Inspect = '/v2/inspect',
Portfolio = '/v2/portfolio',
}
Loading

0 comments on commit ff14678

Please sign in to comment.