Skip to content

Commit

Permalink
add functionality to add an aside
Browse files Browse the repository at this point in the history
  • Loading branch information
sstraatemans committed Nov 1, 2024
1 parent b81b888 commit 84aec9b
Show file tree
Hide file tree
Showing 15 changed files with 312 additions and 216 deletions.
2 changes: 2 additions & 0 deletions .changeset/smooth-cats-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
19 changes: 16 additions & 3 deletions packages/apps/dev-wallet/src/App/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
MonoWorkspaces,
} from '@kadena/kode-icons/system';

import { Aside } from '@/Components/Aside/Aside';
import { NetworkSelector } from '@/Components/NetworkSelector/NetworkSelector';
import { BreadCrumbs } from '@/pages/BreadCrumbs/BreadCrumbs';
import { Themes, useTheme } from '@kadena/kode-ui';
Expand All @@ -14,15 +15,25 @@ import {
SideBarLayout,
useSideBar,
} from '@kadena/kode-ui/patterns';
import { FC, useEffect } from 'react';
import { Link, Outlet, useLocation } from 'react-router-dom';
import { FC, useEffect, useMemo } from 'react';
import { Link, Outlet, useLocation, useNavigate } from 'react-router-dom';
import { BetaHeader } from './../BetaHeader';
import { SideBar } from './SideBar';

export const Layout: FC = () => {
const { theme, setTheme } = useTheme();
const { breadCrumbs, setBreadCrumbs, setAppContext } = useSideBar();
const location = useLocation();
const navigate = useNavigate();

const innerLocation = useMemo(
() => ({
url: location.pathname,
hash: location.hash,
push: navigate,
}),
[location],

Check warning on line 35 in packages/apps/dev-wallet/src/App/Layout/Layout.tsx

View workflow job for this annotation

GitHub Actions / Build & unit test

React Hook useMemo has a missing dependency: 'navigate'. Either include it or remove the dependency array
);

useEffect(() => {
if (
Expand All @@ -38,12 +49,14 @@ export const Layout: FC = () => {
const newTheme = theme === Themes.dark ? Themes.light : Themes.dark;
setTheme(newTheme);
};

return (
<>
<SideBarLayout
aside={<Aside />}
topBanner={<BetaHeader />}
breadcrumbs={<BreadCrumbs />}
activeUrl={location.pathname}
location={innerLocation}
sidebar={<SideBar />}
footer={
<SideBarFooter>
Expand Down
41 changes: 41 additions & 0 deletions packages/apps/dev-wallet/src/Components/Aside/Aside.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { FC, lazy, ReactElement, Suspense, useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';

const importView = async (key: string) =>
lazy(() => import(`./views/${key}`).catch(() => import(`./views/Error`)));

export const Aside: FC = () => {
const [view, setView] = useState<ReactElement | undefined>();
const location = useLocation();

const loadView = async (data: Record<string, string>) => {
const Result = await importView(data.aside);

setView(<Result {...data} />);
};
useEffect(() => {
if (!location.hash) return;

const hashArray = location.hash
.slice(1)
.split('&')
.reduce<Record<string, string>>((acc, val) => {
const arr = val.split('=');
if (arr.length < 2) return acc;

//make sure that the component name is capitalized
const value = arr[1].charAt(0).toUpperCase() + String(arr[1]).slice(1);

acc[arr[0]] = value;
return acc;
}, {});

loadView(hashArray);
}, [location.hash]);

return (
<div>
<Suspense fallback="Loading view...">{view}</Suspense>
</div>
);
};
7 changes: 7 additions & 0 deletions packages/apps/dev-wallet/src/Components/Aside/views/Error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { FC } from 'react';

const Error: FC = () => {
return <div>There was an error loading the view</div>;
};

export default Error;
87 changes: 87 additions & 0 deletions packages/apps/dev-wallet/src/Components/Aside/views/KeySource.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { useHDWallet } from '@/modules/key-source/hd-wallet/hd-wallet';
import { keySourceManager } from '@/modules/key-source/key-source-manager';
import { WebAuthnService } from '@/modules/key-source/web-authn/webauthn';
import { useWallet } from '@/modules/wallet/wallet.hook';

import { AddKeySourceForm } from '@/pages/keys/Components/AddKeySourceForm';
import { Notification, Stack } from '@kadena/kode-ui';
import { useSideBar } from '@kadena/kode-ui/patterns';
import { FC, useState } from 'react';

const KeySource: FC = () => {
const { handleSetAsideExpanded } = useSideBar();
const { keySources, profile, askForPassword } = useWallet();
const [error, setError] = useState<string | null>(null);
const { createHDWallet } = useHDWallet();

async function createWebAuthn() {
if (!profile) {
throw new Error('No profile found');
}
if (keySources.find((keySource) => keySource.source === 'web-authn')) {
// basically its possible to have multiple web-authn sources
// but for now just for simplicity we will only allow one
alert('WebAuthn already created');
throw new Error('WebAuthn already created');
}
const service = (await keySourceManager.get(
'web-authn',
)) as WebAuthnService;

await service.register(profile.uuid);
}

const registerHDWallet =
(type: 'HD-BIP44' | 'HD-chainweaver') => async () => {
const password = await askForPassword();
if (!password || !profile) {
return;
}
await createHDWallet(profile?.uuid, type, password);
};

return (
<>
{error && (
<Stack marginBlock={'lg'}>
<Notification intent="negative" role="alert">
{error}
</Notification>
</Stack>
)}

<AddKeySourceForm
onClose={() => handleSetAsideExpanded(false)}
onSave={async (sourcesToInstall) => {
try {
await Promise.all(
sourcesToInstall.map(async (source) => {
switch (source) {
case 'HD-BIP44':
await registerHDWallet('HD-BIP44')();
break;
case 'HD-chainweaver':
await registerHDWallet('HD-chainweaver')();
break;
case 'web-authn':
await createWebAuthn();
break;
default:
throw new Error('Unsupported key source');
}
}),
);
} catch (error: any) {

Check warning on line 74 in packages/apps/dev-wallet/src/Components/Aside/views/KeySource.tsx

View workflow job for this annotation

GitHub Actions / Build & unit test

Unexpected any. Specify a different type
setError(error?.message ?? JSON.stringify(error));
} finally {
handleSetAsideExpanded(false);
}
keySourceManager.disconnect();
}}
installed={keySources.map((k) => k.source)}
/>
</>
);
};

export default KeySource;

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { KeySourceType } from '@/modules/wallet/wallet.repository';
import { Button, Checkbox, Divider, Stack, Text } from '@kadena/kode-ui';
import { useState } from 'react';

export function AddKeySourceForm({
onClose,
onSave,
installed,
}: {
installed: KeySourceType[];
onClose: () => void;
onSave: (sourcesToInstall: KeySourceType[]) => void;
}) {
const [localInstalled, setLocalInstalled] =
useState<KeySourceType[]>(installed);
const onChange = (source: KeySourceType) => (isSelected: boolean) => {
if (isSelected) {
setLocalInstalled([...localInstalled, source]);
} else if (!installed.includes(source)) {
setLocalInstalled(localInstalled.filter((i) => i !== source));
}
};
const checkBoxProps = (source: KeySourceType) => ({
isDisabled: installed.includes(source),
onChange: onChange(source),
isSelected: localInstalled.includes(source),
});
return (
<>
<Stack flexDirection={'column'} gap={'sm'} paddingBlockStart={'lg'}>
<Stack flexDirection={'column'} gap={'sm'}>
<Checkbox {...checkBoxProps('HD-BIP44')}>BIP44 algorithm</Checkbox>
<Text size="small">
This is the default and recommended algorithm for generating keys.
</Text>
</Stack>
<Divider />
<Stack flexDirection={'column'} gap={'sm'}>
<Checkbox {...checkBoxProps('HD-chainweaver')}>
Legacy algorithm (chainweaver 1)
</Checkbox>
<Text size="small">
This is a legacy algorithm used in chainweaver 1, it's not
recommended to use it for new wallets, use this only if you have a
wallet created in chainweaver 1
</Text>
</Stack>
<Divider />
<Stack flexDirection={'column'} gap={'sm'}>
<Checkbox {...checkBoxProps('web-authn')}>
WebAuthn External keys (Experimental)
</Checkbox>
<Text size="small">
This is an experimental feature that allows you to use your
compatible devices to generate keys, The keys are stored in the
device and cant be recovered with the wallet if lost the device
</Text>
</Stack>
</Stack>

<Button variant="outlined" onPress={onClose}>
Cancel
</Button>
<Button
variant="primary"
onPress={() =>
onSave(localInstalled.filter((i) => !installed.includes(i)))
}
>
Save
</Button>
</>
);
}
Loading

0 comments on commit 84aec9b

Please sign in to comment.