Skip to content

Commit bef2d8e

Browse files
authored
Merge pull request #942 from polkadot-fellows/nik-add-login-functionality
2 parents af1af9d + 2aa7023 commit bef2d8e

File tree

8 files changed

+714
-176
lines changed

8 files changed

+714
-176
lines changed

package.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "fellowship-dashboard",
33
"private": true,
44
"version": "0.0.2",
5-
"packageManager": "pnpm@10.24.0",
5+
"packageManager": "pnpm@10.26.1+sha512.664074abc367d2c9324fdc18037097ce0a8f126034160f709928e9e9f95d98714347044e5c3164d65bd5da6c59c6be362b107546292a8eecb7999196e5ce58fa",
66
"type": "module",
77
"scripts": {
88
"preinstall": "npx only-allow pnpm",
@@ -18,8 +18,8 @@
1818
},
1919
"dependencies": {
2020
"@polkadot-api/descriptors": "file:.papi/descriptors",
21-
"@polkadot-ui/react": "0.0.1-alpha.27",
22-
"@polkadot-ui/utils": "0.0.1-alpha.5",
21+
"@polkadot-ui/react": "0.0.1-alpha.35",
22+
"@polkadot-ui/utils": "0.0.4",
2323
"@radix-ui/react-accordion": "^1.2.12",
2424
"@radix-ui/react-dialog": "^1.1.15",
2525
"@radix-ui/react-dropdown-menu": "^2.1.16",
@@ -32,11 +32,14 @@
3232
"@radix-ui/react-toggle": "^1.1.10",
3333
"@radix-ui/react-toggle-group": "^1.1.11",
3434
"@radix-ui/react-tooltip": "^1.2.8",
35+
"@reactive-dot/core": "^0.67.3",
36+
"@reactive-dot/react": "^0.67.3",
3537
"@tanstack/react-table": "^8.21.3",
3638
"buffer": "^6.0.3",
3739
"class-variance-authority": "^0.7.1",
3840
"clsx": "^2.1.1",
3941
"copy-to-clipboard": "^3.3.3",
42+
"dot-connect": "^0.28.1",
4043
"lucide-react": "^0.562.0",
4144
"next-themes": "^0.4.6",
4245
"polkadot-api": "^1.23.1",
@@ -51,7 +54,7 @@
5154
"vaul": "^1.1.2"
5255
},
5356
"devDependencies": {
54-
"@types/node": "^24.10.2",
57+
"@types/node": "^25.0.3",
5558
"@types/react": "^19.2.7",
5659
"@types/react-dom": "^19.2.3",
5760
"@types/react-router-dom": "^5.3.3",

pnpm-lock.yaml

Lines changed: 512 additions & 34 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/contexts/AccountContextProvider.tsx

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
/* eslint-disable react-refresh/only-export-components */
2-
import React, {
3-
useState,
4-
createContext,
5-
useContext,
6-
useEffect,
7-
// useCallback,
8-
// useEffect,
9-
} from 'react'
10-
import { InjectedPolkadotAccount } from 'polkadot-api/pjs-signer'
2+
import React, { useState, createContext, useContext, useEffect } from 'react'
113
import { api } from '@/clients'
124
import { SS58String } from 'polkadot-api'
5+
import { useAccounts } from '@reactive-dot/react'
6+
import type { WalletAccount } from '@reactive-dot/core/wallets.js'
137
// const LOCALSTORAGE_SELECTED_ACCOUNT_KEY = 'polkadot-fellowship.selectedAccount'
148

159
type AccountContextProps = {
@@ -23,45 +17,71 @@ type ExtraInfo = {
2317
lastProof?: number
2418
}
2519

26-
type ExtraPolkadotAccount = InjectedPolkadotAccount & {
27-
membership: ExtraInfo
20+
type ExtraPolkadotAccount = WalletAccount & {
21+
membership?: ExtraInfo
2822
}
2923

3024
export interface IAccountContext {
3125
enchancedAccount?: ExtraPolkadotAccount
32-
selectedAccount?: InjectedPolkadotAccount
33-
setSelectedAccount: (account: InjectedPolkadotAccount | undefined) => void
26+
selectedAccount?: WalletAccount
27+
accounts: WalletAccount[]
28+
setSelectedAccount: (account: WalletAccount | undefined) => void
3429
}
3530

3631
const AccountContext = createContext<IAccountContext | undefined>(undefined)
3732

3833
const AccountContextProvider = ({ children }: AccountContextProps) => {
34+
const accounts = useAccounts()
3935
const [enchancedAccount, setEnchancedAccount] = useState<
4036
ExtraPolkadotAccount | undefined
4137
>()
4238
const [selectedAccount, setSelectedAccount] = useState<
43-
InjectedPolkadotAccount | undefined
39+
WalletAccount | undefined
4440
>()
4541

4642
useEffect(() => {
43+
if (!accounts.length) {
44+
setSelectedAccount(undefined)
45+
setEnchancedAccount(undefined)
46+
return
47+
}
48+
49+
if (
50+
selectedAccount &&
51+
accounts.some(
52+
(account) => account.address === selectedAccount.address,
53+
)
54+
) {
55+
return
56+
}
57+
58+
setSelectedAccount(accounts[0])
59+
}, [accounts, selectedAccount])
60+
61+
useEffect(() => {
62+
if (!selectedAccount?.address) {
63+
setEnchancedAccount(undefined)
64+
return
65+
}
66+
4767
const getExtraInfo = async () => {
48-
let updateAccount: ExtraPolkadotAccount = {} as ExtraPolkadotAccount
4968
const member = await api.query.FellowshipCore.Member.getValue(
50-
selectedAccount?.address as SS58String,
69+
selectedAccount.address as SS58String,
5170
)
52-
updateAccount = {
71+
const updateAccount: ExtraPolkadotAccount = {
5372
...selectedAccount,
5473
membership: member
5574
? {
56-
isActive: member?.is_active,
57-
lastPromotion: member?.last_promotion,
58-
lastProof: member?.last_proof,
75+
isActive: member.is_active,
76+
lastPromotion: member.last_promotion,
77+
lastProof: member.last_proof,
5978
}
6079
: undefined,
61-
} as ExtraPolkadotAccount
80+
}
6281
setEnchancedAccount(updateAccount)
6382
}
64-
selectedAccount?.address && getExtraInfo()
83+
84+
getExtraInfo()
6585
}, [selectedAccount])
6686

6787
// const selectAccount = useCallback(
@@ -94,6 +114,7 @@ const AccountContextProvider = ({ children }: AccountContextProps) => {
94114
return (
95115
<AccountContext.Provider
96116
value={{
117+
accounts,
97118
enchancedAccount,
98119
selectedAccount,
99120
setSelectedAccount,

src/header.tsx

Lines changed: 102 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
/* eslint-disable react-hooks/exhaustive-deps */
2-
import {
3-
// AccountProvider,
4-
// ExtensionProvider,
5-
SelectedAccountType,
6-
} from '@polkadot-ui/react'
72
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'
83
import { Button } from '@/components/ui/button'
9-
import { RouterType, openInNewTab, resources, routes } from '@/lib/utils'
4+
import { RouterType, openInNewTab, resources, routes, cn } from '@/lib/utils'
105

11-
import { PanelLeft, Moon, Sun, NotebookText, BookOpenText } from 'lucide-react'
6+
import {
7+
PanelLeft,
8+
Moon,
9+
Sun,
10+
NotebookText,
11+
BookOpenText,
12+
Wallet,
13+
Check,
14+
} from 'lucide-react'
1215
import { getLinks } from './Resources'
1316
import {
1417
// FaCheckCircle,
@@ -17,7 +20,7 @@ import {
1720
// import { TbLoaderQuarter } from 'react-icons/tb'
1821
import { useTheme } from './components/theme-provider'
1922
import { useEffect, useState } from 'react'
20-
import { collectiveClient } from './clients'
23+
import { collectiveClient, polkadotClient } from './clients'
2124
import {
2225
Dialog,
2326
DialogContent,
@@ -35,6 +38,7 @@ import {
3538
} from '@radix-ui/react-accordion'
3639
import { SiElement } from 'react-icons/si'
3740
import { Link } from 'react-router-dom'
41+
import { Badge } from '@/components/ui/badge'
3842

3943
// import { Polkicon } from '@polkadot-ui/react'
4044
// import {
@@ -45,6 +49,7 @@ import { Link } from 'react-router-dom'
4549
// DropdownMenuTrigger,
4650
// } from '@/components/ui/dropdown-menu'
4751

52+
import { ConnectionButton } from 'dot-connect/react.js'
4853
import { useAccount } from './contexts/AccountContextProvider'
4954

5055
interface Props {
@@ -53,12 +58,11 @@ interface Props {
5358
}
5459

5560
export const Header = ({ lightClientLoaded, setLightClientLoaded }: Props) => {
56-
const { setSelectedAccount } = useAccount()
57-
58-
const [
59-
selAccount,
60-
// setSelAccount
61-
] = useState<SelectedAccountType>({} as SelectedAccountType)
61+
const { accounts, selectedAccount, setSelectedAccount } = useAccount()
62+
const [accountDialogOpen, setAccountDialogOpen] = useState(false)
63+
const [latestBlockNumber, setLatestBlockNumber] = useState<number | null>(
64+
null,
65+
)
6266

6367
useEffect(() => {
6468
collectiveClient.finalizedBlock$.subscribe((finalizedBlock) => {
@@ -67,12 +71,19 @@ export const Header = ({ lightClientLoaded, setLightClientLoaded }: Props) => {
6771
}
6872
})
6973
}, [lightClientLoaded, setLightClientLoaded])
70-
const { theme, setTheme } = useTheme()
7174

72-
// TODO Correctly map the account
7375
useEffect(() => {
74-
selAccount?.address && setSelectedAccount(selAccount)
75-
}, [selAccount])
76+
const subscription = polkadotClient.finalizedBlock$.subscribe(
77+
(finalizedBlock) => {
78+
if (typeof finalizedBlock.number === 'number') {
79+
setLatestBlockNumber(finalizedBlock.number)
80+
}
81+
},
82+
)
83+
84+
return () => subscription.unsubscribe()
85+
}, [])
86+
const { theme, setTheme } = useTheme()
7687

7788
return (
7889
<header className="sticky top-5 z-30 flex h-14 items-center gap-4 border-b bg-background px-4 sm:sticky sm:h-auto sm:border-0 sm:bg-transparent sm:px-6">
@@ -228,7 +239,7 @@ export const Header = ({ lightClientLoaded, setLightClientLoaded }: Props) => {
228239
</div>
229240
</SheetContent>
230241
</Sheet>
231-
<div className="flex w-full justify-between">
242+
<div className="flex w-full items-center justify-between">
232243
<div>
233244
{/* <Menubar>
234245
<MenubarMenu>
@@ -246,70 +257,78 @@ export const Header = ({ lightClientLoaded, setLightClientLoaded }: Props) => {
246257
</MenubarMenu>
247258
</Menubar> */}
248259
</div>
249-
{/* TODO - ACTIVATE THIS FOR CONNECT BUTTON
250-
<div>
251-
{!enchancedAccount?.address ? (
252-
<Dialog>
253-
<DialogTrigger asChild>
254-
<Button>Connect</Button>
255-
</DialogTrigger>
256-
<DialogContent className="sm:max-w-[425px]">
257-
<DialogHeader>
258-
<DialogTitle className="text-primary font-bold">
259-
Connect Wallet
260-
</DialogTitle>
261-
</DialogHeader>
262-
<ExtensionProvider setSelected={setSelAccount}>
263-
<AccountProvider
264-
selected={selAccount}
265-
setSelected={setSelAccount}
266-
/>
267-
</ExtensionProvider>
268-
</DialogContent>
269-
</Dialog>
270-
) : (
271-
<Dialog>
272-
<DropdownMenu>
273-
<DropdownMenuTrigger asChild>
274-
<Button
275-
variant="outline"
276-
size="icon"
277-
className="overflow-hidden rounded-full"
278-
>
279-
<Polkicon size={36} address={enchancedAccount?.address} />
280-
</Button>
281-
</DropdownMenuTrigger>
282-
<DropdownMenuContent align="end">
283-
<DialogTrigger asChild>
284-
<DropdownMenuItem className="cursor-pointer">
285-
Switch Account
286-
</DropdownMenuItem>
287-
</DialogTrigger>
288-
<DropdownMenuSeparator />
289-
<DropdownMenuItem
290-
className="cursor-pointer"
291-
onClick={() => setSelectedAccount(undefined)}
292-
>
293-
Logout
294-
</DropdownMenuItem>
295-
</DropdownMenuContent>
296-
</DropdownMenu>
297-
<DialogContent className="sm:max-w-[425px]">
298-
<DialogHeader>
299-
<DialogTitle className="text-primary font-bold">
300-
Connect Wallet
301-
</DialogTitle>
302-
</DialogHeader>
303-
<ExtensionProvider setSelected={setSelAccount}>
304-
<AccountProvider
305-
selected={selAccount}
306-
setSelected={setSelAccount}
307-
/>
308-
</ExtensionProvider>
309-
</DialogContent>
310-
</Dialog>
311-
)}
312-
</div> */}
260+
<div className="flex items-center gap-3">
261+
<Dialog open={accountDialogOpen} onOpenChange={setAccountDialogOpen}>
262+
<DialogTrigger asChild>
263+
<Button variant="outline" size="lg" className="rounded-3xl p-5">
264+
Manage accounts
265+
</Button>
266+
</DialogTrigger>
267+
<DialogContent className="sm:max-w-[420px]">
268+
<DialogHeader>
269+
<DialogTitle className="font-bold text-primary">
270+
Connected accounts
271+
</DialogTitle>
272+
<DialogDescription>
273+
Choose which account the dashboard should use.
274+
</DialogDescription>
275+
</DialogHeader>
276+
{accounts.length ? (
277+
<div className="flex flex-col gap-2">
278+
{accounts.map((account) => {
279+
const isSelected =
280+
selectedAccount?.address === account.address
281+
const shortAddress =
282+
account.address.length > 12
283+
? `${account.address.slice(0, 6)}...${account.address.slice(-4)}`
284+
: account.address
285+
286+
return (
287+
<button
288+
type="button"
289+
key={account.address}
290+
onClick={() => {
291+
setSelectedAccount(account)
292+
setAccountDialogOpen(false)
293+
}}
294+
className={cn(
295+
'flex w-full items-center justify-between rounded-md border p-3 text-left transition-colors hover:bg-muted',
296+
isSelected
297+
? 'border-primary bg-primary/5'
298+
: 'border-border bg-background',
299+
)}
300+
>
301+
<div className="flex flex-col">
302+
<span className="text-sm font-medium">
303+
{account.name || 'Wallet account'}
304+
</span>
305+
<span className="text-xs text-muted-foreground">
306+
{shortAddress}
307+
</span>
308+
</div>
309+
{isSelected ? (
310+
<Check className="h-4 w-4 text-primary" />
311+
) : (
312+
<Wallet className="h-4 w-4 text-muted-foreground" />
313+
)}
314+
</button>
315+
)
316+
})}
317+
</div>
318+
) : (
319+
<p className="text-sm text-muted-foreground">
320+
Connect a wallet to view available accounts.
321+
</p>
322+
)}
323+
</DialogContent>
324+
</Dialog>
325+
<ConnectionButton />
326+
<Badge variant="secondary" className="text-nowrap p-3">
327+
{latestBlockNumber
328+
? `#${latestBlockNumber.toLocaleString()}`
329+
: 'Syncing block...'}
330+
</Badge>
331+
</div>
313332
</div>
314333
</header>
315334
)

0 commit comments

Comments
 (0)