Skip to content

Commit fb46425

Browse files
committed
fix(conflicts): resolve
2 parents 4c79677 + a111fc2 commit fb46425

File tree

23 files changed

+802
-16
lines changed

23 files changed

+802
-16
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { truncateString } from "@/common/lib/string"
2+
import { LogoButton } from "@/components/menu-bar"
3+
import { MinaKeyConst } from "@palladxyz/key-management"
4+
import { type SingleCredentialState, useVault } from "@palladxyz/vault"
5+
import { EyeOff, Pencil } from "lucide-react"
6+
import { useTranslation } from "react-i18next"
7+
import QRCode from "react-qr-code"
8+
import { useNavigate } from "react-router-dom"
9+
import { toast } from "sonner"
10+
import { DetailsDropdown } from "./details-dropdown"
11+
12+
type AccountDetailsProps = {
13+
account: SingleCredentialState
14+
onCopyWalletAddress: () => void
15+
}
16+
17+
function getDerivationPath(accountIndex: number, addressIndex: number) {
18+
return `m/${MinaKeyConst.PURPOSE}'/${MinaKeyConst.MINA_COIN_TYPE}'/${accountIndex}'/0/${addressIndex}`
19+
}
20+
21+
export const AccountDetails = ({
22+
account,
23+
onCopyWalletAddress,
24+
}: AccountDetailsProps) => {
25+
const { t } = useTranslation()
26+
const navigate = useNavigate()
27+
28+
const {
29+
removeCredential,
30+
credentials,
31+
setCredential,
32+
setCurrentWallet,
33+
keyAgentName,
34+
} = useVault()
35+
36+
const dropdownOptions = [
37+
{
38+
name: t("accountManagement.remove"),
39+
icon: <EyeOff className="w-4 h-4" />,
40+
onClick: async (account: SingleCredentialState) => {
41+
const serializedList = Object.values(credentials)
42+
if (
43+
account.credential?.accountIndex === 0 ||
44+
account.credential?.addressIndex === 0
45+
) {
46+
toast.error(t("accountManagement.cannotRemoveFirstCredential"))
47+
return
48+
}
49+
50+
if (
51+
account.credential?.accountIndex === serializedList.length - 1 &&
52+
serializedList.length > 1
53+
) {
54+
removeCredential(account?.credentialName)
55+
setCredential(serializedList[0])
56+
setCurrentWallet({
57+
keyAgentName,
58+
credentialName: serializedList[0]?.credentialName,
59+
currentAccountIndex:
60+
serializedList[0]?.credential?.accountIndex ?? 0,
61+
currentAddressIndex:
62+
serializedList[0]?.credential?.addressIndex ?? 0,
63+
})
64+
toast.success(t("accountManagement.accountRemoved"))
65+
} else {
66+
toast.error(t("accountManagement.onlyLastCredentialCanBeRemoved"))
67+
}
68+
},
69+
},
70+
{
71+
name: t("accountManagement.edit"),
72+
icon: <Pencil className="w-4 h-4" />,
73+
onClick: async (account: SingleCredentialState) => {
74+
navigate(`/accounts/edit/${account.credential?.addressIndex}`)
75+
},
76+
},
77+
]
78+
79+
return (
80+
<div className="flex flex-col text-center justify-center items-center space-y-8 card bg-accent p-3">
81+
<nav className="flex justify-between w-full relative">
82+
<div>
83+
<LogoButton onClick={() => {}} color={"#191725"} />
84+
</div>
85+
<DetailsDropdown options={dropdownOptions} account={account} />
86+
</nav>
87+
<div className="flex w-full justify-between">
88+
<div className="flex flex-col py-3 justify-end items-start text-left">
89+
<div className="text-xl text-secondary">
90+
{account?.credentialName &&
91+
truncateString({
92+
value: account?.credentialName,
93+
endCharCount: 1,
94+
firstCharCount: 12,
95+
})}
96+
</div>
97+
<div className="text-xl text-secondary">
98+
{account?.credentialName &&
99+
truncateString({
100+
value: account?.credential?.address ?? "",
101+
endCharCount: 3,
102+
firstCharCount: 5,
103+
})}
104+
</div>
105+
<div className="text-xl text-secondary">
106+
{getDerivationPath(
107+
account?.credential?.accountIndex ?? 0,
108+
account?.credential?.addressIndex ?? 0,
109+
)}
110+
</div>
111+
</div>
112+
{account?.credential?.address && (
113+
<QRCode
114+
value={account?.credential?.address}
115+
bgColor={"#25233A"}
116+
fgColor={"#D1B4F4"}
117+
className="relative w-[120px] h-[120px]"
118+
onClick={() => {
119+
onCopyWalletAddress()
120+
}}
121+
/>
122+
)}
123+
</div>
124+
</div>
125+
)
126+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { truncateString } from "@/common/lib/string"
2+
import type { SingleCredentialState } from "@palladxyz/vault"
3+
import clsx from "clsx"
4+
import { useEffect, useState } from "react"
5+
6+
type AccountListProps = {
7+
accounts: SingleCredentialState[]
8+
handleSelect: (account: SingleCredentialState) => void
9+
}
10+
11+
export const AccountList = ({ accounts, handleSelect }: AccountListProps) => {
12+
const [sortedAccounts, setSortedAccounts] = useState<SingleCredentialState[]>(
13+
[],
14+
)
15+
16+
useEffect(() => {
17+
const sorted = accounts
18+
? [...accounts].sort(
19+
(a, b) =>
20+
//orderd by addressIndex - not sure if this is the correct way to sort
21+
(a?.credential?.addressIndex ?? Number.POSITIVE_INFINITY) -
22+
(b?.credential?.addressIndex ?? Number.POSITIVE_INFINITY),
23+
)
24+
: []
25+
26+
setSortedAccounts(sorted)
27+
}, [accounts])
28+
29+
return (
30+
<div className="space-y-2 py-5">
31+
<div className="text-mint">Other</div>
32+
<div className="text-2xl">Wallets</div>
33+
{sortedAccounts?.map((account) => {
34+
return (
35+
<button
36+
key={account?.credentialName}
37+
type="button"
38+
className={clsx(
39+
"bg-secondary w-full px-6 py-4 flex justify-between rounded-2xl hover:bg-primary",
40+
)}
41+
onClick={() => handleSelect(account)}
42+
>
43+
<p>
44+
{account?.credentialName &&
45+
truncateString({
46+
value: account?.credentialName,
47+
endCharCount: 1,
48+
firstCharCount: 12,
49+
})}
50+
</p>
51+
<p>
52+
{account?.credential?.address &&
53+
truncateString({
54+
value: account?.credential?.address,
55+
endCharCount: 3,
56+
firstCharCount: 5,
57+
})}
58+
</p>
59+
</button>
60+
)
61+
})}
62+
</div>
63+
)
64+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type { SingleCredentialState } from "@palladxyz/vault"
2+
import { Ellipsis, X } from "lucide-react"
3+
import { useState } from "react"
4+
import { useTranslation } from "react-i18next"
5+
import type { DropdownOption } from "../types"
6+
7+
type DetailsDropdownProps = {
8+
options: DropdownOption[]
9+
account: SingleCredentialState
10+
}
11+
12+
export const DetailsDropdown = ({ options, account }: DetailsDropdownProps) => {
13+
const [isOpen, setIsOpen] = useState(false)
14+
15+
const { t } = useTranslation()
16+
17+
return (
18+
<div className="relative">
19+
<button
20+
type="button"
21+
className="btn btn-circle bg-white min-h-10 h-10 w-10 border-none outline-none"
22+
onClick={() => setIsOpen(!isOpen)}
23+
>
24+
{isOpen ? (
25+
<X width={24} height={24} color={"#191725"} />
26+
) : (
27+
<Ellipsis width={24} height={24} color={"#191725"} />
28+
)}
29+
</button>
30+
{isOpen && (
31+
<div className="absolute right-0 mt-2 w-30 bg-secondary rounded-lg shadow-lg z-10">
32+
<ul className="p-2 shadow menu dropdown-content border-2 border-secondary z-[1] bg-neutral rounded-box w-40">
33+
{options.map((option) => (
34+
<li key={`${option.name}`}>
35+
<button
36+
type="button"
37+
onClick={() => {
38+
if (account) {
39+
option.onClick(account)
40+
setIsOpen(false)
41+
}
42+
}}
43+
className="flex justify-between items-center px-4 py-2 w-full text-left"
44+
>
45+
<span>{t(option.name)}</span>
46+
{option.icon}
47+
</button>
48+
</li>
49+
))}
50+
</ul>
51+
</div>
52+
)}
53+
</div>
54+
)
55+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { type StoryDefault, action } from "@ladle/react"
2+
import { Network } from "@palladxyz/pallad-core"
3+
import type { SingleCredentialState } from "@palladxyz/vault"
4+
import { AccountManagementView } from "./views/account-management"
5+
import { AddEditAccountView } from "./views/add-edit-account"
6+
const keyAgentName = "TestKeyAgent"
7+
const walletName = "TesWallet"
8+
9+
const accounts: SingleCredentialState[] = [
10+
{
11+
credentialName: "First",
12+
keyAgentName: keyAgentName,
13+
credential: {
14+
"@context": ["https://w3id.org/wallet/v1"],
15+
id: "mina-cred-123",
16+
type: "MinaAddress",
17+
controller: "did:key:z6MkjQkU7hD8q1fS7P3X4f3kY9v2",
18+
name: "Sample Mina Account",
19+
description: "This is a dummy Mina credential for testing purposes.",
20+
chain: Network.Mina,
21+
addressIndex: 0,
22+
accountIndex: 0,
23+
address: "B62qjzAXN9NkX8K6JfGzYz4qVQoMrkL4uJ3FgkFjVZd4W5X5cQFQkCG",
24+
encryptedPrivateKeyBytes: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
25+
},
26+
},
27+
{
28+
credentialName: "Second",
29+
keyAgentName: keyAgentName,
30+
credential: {
31+
"@context": ["https://w3id.org/wallet/v1"],
32+
id: "mina-cred-123",
33+
type: "MinaAddress",
34+
controller: "did:key:z6MkjQkU7hD8q1fS7P3X4f3kY9v2",
35+
name: "Sample Mina Account",
36+
description: "This is a dummy Mina credential for testing purposes.",
37+
chain: Network.Mina,
38+
addressIndex: 1,
39+
accountIndex: 1,
40+
address: "B62qjzAXN9NkX8K6JfGzYz4qVQoMrkL4uJ3FgkFjVZd4W5X5cQFQkCF",
41+
encryptedPrivateKeyBytes: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 11]),
42+
},
43+
},
44+
]
45+
46+
export const AccountManagement = () => {
47+
const selectedIndex = 0
48+
return (
49+
<AccountManagementView
50+
onGoBack={action("Go Back")}
51+
walletName={walletName}
52+
accounts={accounts}
53+
onSelectAccount={(account: SingleCredentialState): void => {
54+
action(`${account}`)
55+
}}
56+
selectedAccount={accounts[selectedIndex]}
57+
onCopyWalletAddress={action("Copy Wallet Address")}
58+
/>
59+
)
60+
}
61+
62+
export const AddAccount = () => {
63+
return (
64+
<AddEditAccountView
65+
title={"Add Account"}
66+
handleAddEditAccount={async (credentialName: string): Promise<void> => {
67+
action(`${credentialName}`)
68+
}}
69+
isLoading={false}
70+
/>
71+
)
72+
}
73+
74+
export const EditAccount = () => {
75+
const selectedIndex = 0
76+
return (
77+
<AddEditAccountView
78+
title={"Edit Account"}
79+
account={accounts[selectedIndex]}
80+
handleAddEditAccount={async (credentialName: string): Promise<void> => {
81+
action(`${credentialName}`)
82+
}}
83+
isLoading={false}
84+
/>
85+
)
86+
}
87+
88+
export default {
89+
title: "Wallet Management",
90+
} satisfies StoryDefault
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { useAccount } from "@/common/hooks/use-account"
2+
import { type SingleCredentialState, useVault } from "@palladxyz/vault"
3+
import { useEffect, useState } from "react"
4+
import { useNavigate } from "react-router-dom"
5+
import { AccountManagementView } from "../views/account-management"
6+
7+
export const AccountManagementRoute = () => {
8+
const navigate = useNavigate()
9+
const {
10+
setCurrentWallet,
11+
setCredential,
12+
keyAgentName,
13+
credentials,
14+
walletName,
15+
getCurrentWallet,
16+
} = useVault()
17+
const [accountList, setAccountlist] = useState<SingleCredentialState[]>([])
18+
const [selectedAccount, setSelectedAccount] = useState<SingleCredentialState>(
19+
accountList[0],
20+
)
21+
const { copyWalletAddress } = useAccount()
22+
23+
const selectAccount = (account: SingleCredentialState) => {
24+
setSelectedAccount(account)
25+
setCredential(account)
26+
setCurrentWallet({
27+
keyAgentName,
28+
credentialName: account?.credentialName,
29+
currentAccountIndex: account?.credential?.accountIndex ?? 0,
30+
currentAddressIndex: account?.credential?.addressIndex ?? 0,
31+
})
32+
}
33+
34+
useEffect(() => {
35+
const serializedList = Object.values(credentials)
36+
setAccountlist(serializedList)
37+
setSelectedAccount(getCurrentWallet().credential)
38+
}, [credentials, getCurrentWallet])
39+
40+
return (
41+
<AccountManagementView
42+
onGoBack={() => navigate(-1)}
43+
walletName={walletName}
44+
accounts={accountList}
45+
onSelectAccount={selectAccount}
46+
selectedAccount={selectedAccount}
47+
onCopyWalletAddress={copyWalletAddress}
48+
/>
49+
)
50+
}

0 commit comments

Comments
 (0)