Skip to content

Commit

Permalink
change profile
Browse files Browse the repository at this point in the history
  • Loading branch information
sstraatemans committed Nov 8, 2024
1 parent 55e8e1e commit 8312790
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 29 deletions.
2 changes: 2 additions & 0 deletions .changeset/bright-falcons-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
1 change: 1 addition & 0 deletions packages/apps/dev-wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"elliptic": "^6.5.5",
"js-yaml": "~4.1.0",
"react": "^18.2.0",
"react-aria": "^3.31.1",
"react-console-emulator": "^5.0.2",
"react-dom": "^18.2.0",
"react-hook-form": "^7.45.4",
Expand Down
21 changes: 18 additions & 3 deletions packages/apps/dev-wallet/src/App/Layout/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
MonoDarkMode,
MonoKey,
MonoLightMode,
MonoLogout,
MonoNetworkCheck,
MonoSignature,
MonoSwapHoriz,
Expand All @@ -14,6 +15,8 @@ import {

import { NetworkSelector } from '@/Components/NetworkSelector/NetworkSelector';

import { ProfileChanger } from '@/Components/ProfileChanger/ProfileChanger';
import { useWallet } from '@/modules/wallet/wallet.hook';
import { Button, Themes, useTheme } from '@kadena/kode-ui';
import {
SideBarItem,
Expand All @@ -27,6 +30,7 @@ import { KLogo } from './KLogo';

export const SideBar: FC = () => {
const { theme, setTheme } = useTheme();
const { lockProfile } = useWallet();
const { isExpanded } = useLayout();

const toggleTheme = (): void => {
Expand All @@ -37,9 +41,12 @@ export const SideBar: FC = () => {
return (
<SideBarUI
logo={
<Link to="/">
<KLogo height={40} />
</Link>
<>
<Link to="/">
<KLogo height={40} />
</Link>
<ProfileChanger />
</>
}
appContext={
<SideBarItem visual={<MonoNetworkCheck />} label="Select network">
Expand Down Expand Up @@ -105,6 +112,14 @@ export const SideBar: FC = () => {
context={
<>
<SideBarItemsInline>
<SideBarItem visual={<MonoContrast />} label="Logout">
<Button
isCompact
variant="transparent"
onPress={lockProfile}
startVisual={<MonoLogout />}
/>
</SideBarItem>
<SideBarItem
visual={<MonoContrast />}
onPress={toggleTheme}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { recipe } from '@kadena/kode-ui';
import { atoms, globalStyle, token } from '@kadena/kode-ui/styles';
import { style } from '@vanilla-extract/css';

export const profileClass = recipe({
base: [
atoms({
borderRadius: 'round',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 'primaryFont.medium',
cursor: 'pointer',
}),
{
border: 0,
width: '32px',
height: '32px',
aspectRatio: '1/1',
color: token('color.background.base.default'),
willChange: 'transform',
transition: 'transform .4s ease',
selectors: {
'&:hover': {
opacity: '.8',
},
},
},
],
variants: {
isActive: {
true: {
selectors: {
'&:after': {
content: '',
position: 'absolute',
backgroundColor: token(
'color.background.accent.primary.inverse.default',
),
borderRadius: '50%',
width: '10px',
height: '10px',
bottom: 0,
right: 0,
},
},
},
false: {},
},
},
});

export const profileListClass = style([
atoms({
position: 'relative',
padding: 'no',
margin: 'no',
listStyleType: 'none',
alignItems: 'center',
cursor: 'pointer',
gap: 'xs',
}),
]);

globalStyle(
`${profileListClass}:hover ${profileClass()}[data-hasMoreOptions="true"]`,
{
background: 'green',
transform: 'translateX(0)!important',
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { useWallet } from '@/modules/wallet/wallet.hook';
import { IProfilePicked, unlockWithWebAuthn } from '@/utils/unlockWithWebAuthn';
import { recoverPublicKey, retrieveCredential } from '@/utils/webAuthn';
import { MonoMoreHoriz } from '@kadena/kode-icons/system';
import {
Button,
ContextMenu,
ContextMenuDivider,
ContextMenuItem,
Stack,
} from '@kadena/kode-ui';
import { FC } from 'react';
import { useNavigate } from 'react-router-dom';
import { Profile } from './components/Profile';
import { profileClass, profileListClass } from './ProfileChanger.css';

const getInitial = (name: string): string => {
return name.at(0)?.toUpperCase() ?? '-';
};

export const ProfileChanger: FC = () => {
const {
profileList,
profile: currentProfile,
unlockProfile,
lockProfile,
} = useWallet();
const navigate = useNavigate();

const handleSelect = async (profile: IProfilePicked) => {
if (profile.options.authMode === 'WEB_AUTHN') {
await unlockWithWebAuthn(profile, unlockProfile);
} else {
navigate(`/unlock-profile/${profile.uuid}`);
}
};

const filteredProfile = profileList.filter(
(p) => p.uuid !== currentProfile?.uuid,
)[0];

const hasMoreOptions = profileList.length > 2;

return (
<Stack as="section" className={profileListClass}>
{hasMoreOptions && (
<ContextMenu
placement="bottom start"
trigger={
<Button className={profileClass()}>
<MonoMoreHoriz />
</Button>
}
>
{profileList.map((profile) => {
return (
<ContextMenuItem
key={profile.uuid}
label={profile.name}
onClick={() => {
handleSelect(profile);
}}
/>
);
})}
<ContextMenuDivider />
<ContextMenuItem label="logout" onClick={lockProfile} />
</ContextMenu>
)}

{filteredProfile && (
<Profile
key={filteredProfile.uuid}
color={filteredProfile.accentColor}
idx={hasMoreOptions ? 1 : 0}
hasMoreOptions={hasMoreOptions}
isActive={currentProfile?.uuid === filteredProfile.uuid}
onClick={() => handleSelect(filteredProfile)}
>
{getInitial(filteredProfile.name)}
</Profile>
)}

{currentProfile && (
<Profile
key={currentProfile.uuid}
color={currentProfile.accentColor}
idx={hasMoreOptions ? 2 : 0}
hasMoreOptions={hasMoreOptions}
isActive={currentProfile?.uuid === currentProfile.uuid}
onClick={() => handleSelect(currentProfile)}
>
{getInitial(currentProfile.name)}
</Profile>
)}
</Stack>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { IProfile } from '@/modules/wallet/wallet.repository';
import { Stack } from '@kadena/kode-ui';
import { FC, PropsWithChildren } from 'react';
import { profileClass } from '../ProfileChanger.css';

interface IProps extends PropsWithChildren {
idx?: number;
color: string;
isActive: boolean;
onClick: () => void;
hasMoreOptions: boolean;
}

export const Profile: FC<IProps> = ({
children,
color,
idx,
isActive,
onClick,
hasMoreOptions,
}) => {
return (
<Stack
as="button"
data-hasMoreOptions={hasMoreOptions}
onClick={() => onClick()}
className={profileClass({ isActive })}
style={{
backgroundColor: color,
zIndex: idx ?? 0 + 1,
transform:
idx && hasMoreOptions
? `translateX(-${60 * idx}%)`
: `translateX(-100%)`,
}}
>
{children}
</Stack>
);
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useWallet } from '@/modules/wallet/wallet.hook';
import { IProfile } from '@/modules/wallet/wallet.repository';
import { unlockWithWebAuthn } from '@/utils/unlockWithWebAuthn';
import { recoverPublicKey, retrieveCredential } from '@/utils/webAuthn';
import { MonoAdd } from '@kadena/kode-icons';
import { Box, Heading, Stack } from '@kadena/kode-ui';
Expand All @@ -20,27 +21,6 @@ export function SelectProfile() {
const { profileList, unlockProfile } = useWallet();
const [params] = useSearchParams();

const unlockWithWebAuthn = async (
profile: Pick<IProfile, 'name' | 'uuid' | 'accentColor' | 'options'>,
) => {
if (profile.options.authMode !== 'WEB_AUTHN') {
throw new Error('Profile does not support WebAuthn');
}
const credentialId = profile.options.webAuthnCredential;
const credential = await retrieveCredential(credentialId);
if (!credential) {
throw new Error('Failed to retrieve credential');
}
const keys = await recoverPublicKey(credential);
for (const key of keys) {
const result = await unlockProfile(profile.uuid, key);
if (result) {
return;
}
}
console.error('Failed to unlock profile');
};

const redirect = params.get('redirect');

return (
Expand All @@ -65,7 +45,7 @@ export function SelectProfile() {
key={profile.uuid}
className={cardClass}
onClick={() => {
unlockWithWebAuthn(profile);
unlockWithWebAuthn(profile, unlockProfile);
}}
>
<Stack alignItems="center" gap="md">
Expand Down
43 changes: 43 additions & 0 deletions packages/apps/dev-wallet/src/utils/unlockWithWebAuthn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
IAccount,
IWatchedAccount,
} from '@/modules/account/account.repository';
import { IKeySource, IProfile } from '@/modules/wallet/wallet.repository';
import { recoverPublicKey, retrieveCredential } from '@/utils/webAuthn';

export type IProfilePicked = Pick<
IProfile,
'name' | 'uuid' | 'accentColor' | 'options'
>;

type IUnlockType = (
uuid: string,
key: string,
) => Promise<{
profile: IProfile;
accounts: Array<IAccount>;
watchedAccounts: Array<IWatchedAccount>;
keySources: IKeySource[];
} | null>;

export const unlockWithWebAuthn = async (
profile: IProfilePicked,
unlockProfile: IUnlockType,
) => {
if (profile.options.authMode !== 'WEB_AUTHN') {
throw new Error('Profile does not support WebAuthn');
}
const credentialId = profile.options.webAuthnCredential;
const credential = await retrieveCredential(credentialId);
if (!credential) {
throw new Error('Failed to retrieve credential');
}
const keys = await recoverPublicKey(credential);
for (const key of keys) {
const result = await unlockProfile(profile.uuid, key);
if (result) {
return;
}
}
console.error('Failed to unlock profile');
};
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const ContextMenu: FC<IContextMenuProps> = ({
} as AriaMenuProps<{}>);
const { menuProps } = useMenu({ ...newMenuWrapperProps }, treeState, ref);

console.log({ trigger });
return (
<>
{React.cloneElement(trigger, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ export const headerWrapperClass = recipe({
justifyContent: 'flex-start',
height: minHeaderHeight,
gridArea: 'sidebarlayout-header',
zIndex: 1,
zIndex: 2,
},
],
variants: {
Expand Down
Loading

0 comments on commit 8312790

Please sign in to comment.