From 2815bd75d37e585a2acca65c5110c33d0fe83767 Mon Sep 17 00:00:00 2001 From: Yaroslav Grachev Date: Thu, 26 Dec 2024 15:02:40 +0300 Subject: [PATCH] Feat: Fellowship member (#2903) --- .../collectives/model/members/service.ts | 12 ++--- .../components/ProfileCard.tsx | 45 ++++++++++++++++--- .../fellowship-profile/model/profile.ts | 22 ++++++--- .../fellowship-profile/model/status.ts | 1 + .../fellowship-voting/model/status.ts | 2 +- .../fellowship-voting/model/votingStatus.ts | 6 ++- .../pages/Fellowship/ui/Fellowship.tsx | 2 +- src/renderer/shared/i18n/locales/en.json | 45 ++++++++++--------- .../shared/ui-kit/Tooltip/Tooltip.tsx | 2 +- 9 files changed, 94 insertions(+), 43 deletions(-) diff --git a/src/renderer/domains/collectives/model/members/service.ts b/src/renderer/domains/collectives/model/members/service.ts index 2b30b1efb1..abb6a98110 100644 --- a/src/renderer/domains/collectives/model/members/service.ts +++ b/src/renderer/domains/collectives/model/members/service.ts @@ -1,19 +1,19 @@ -import { type Account, type Chain } from '@/shared/core'; +import { type Account, type Chain, type Wallet } from '@/shared/core'; import { dictionary } from '@/shared/lib/utils'; import { accountUtils } from '@/entities/wallet'; import { type CoreMember, type Member } from './types'; -const findMachingMember = (accounts: Account[], members: Member[], chain: Chain) => { +const findMatchingMember = (wallet: Wallet, accounts: Account[], chain: Chain, members: Member[]) => { const walletAccounts = accounts.filter(account => { - return !accountUtils.isBaseAccount(account) && accountUtils.isChainAndCryptoMatch(account, chain); + return accountUtils.isNonBaseVaultAccount(account, wallet) && accountUtils.isChainAndCryptoMatch(account, chain); }); const accountsDictionary = dictionary(walletAccounts, 'accountId'); return members.find(member => member.accountId in accountsDictionary) ?? null; }; -const findMachingAccount = (accounts: Account[], member: Member) => { +const findMatchingAccount = (accounts: Account[], member: Member) => { return accounts.find(a => a.accountId === member.accountId) ?? null; }; @@ -26,7 +26,7 @@ const isCoreMember = (member: Member | CoreMember): member is CoreMember => { }; export const membersService = { - findMachingMember, - findMachingAccount, + findMatchingMember, + findMatchingAccount, isCoreMember, }; diff --git a/src/renderer/features/fellowship-profile/components/ProfileCard.tsx b/src/renderer/features/fellowship-profile/components/ProfileCard.tsx index f53f4ca011..0135919def 100644 --- a/src/renderer/features/fellowship-profile/components/ProfileCard.tsx +++ b/src/renderer/features/fellowship-profile/components/ProfileCard.tsx @@ -2,10 +2,10 @@ import { useGate, useUnit } from 'effector-react'; import { memo } from 'react'; import { useI18n } from '@/shared/i18n'; -import { toAddress } from '@/shared/lib/utils'; +import { nonNullable, nullable, toAddress } from '@/shared/lib/utils'; import { FootnoteText, Icon, SmallTitleText } from '@/shared/ui'; import { Address } from '@/shared/ui-entities'; -import { Box, Skeleton, Surface } from '@/shared/ui-kit'; +import { Box, Skeleton, Surface, Tooltip } from '@/shared/ui-kit'; import { ERROR } from '../constants'; import { profileModel } from '../model/profile'; import { profileFeatureStatus } from '../model/status'; @@ -18,7 +18,8 @@ export const ProfileCard = memo(() => { const featureInput = useUnit(profileFeatureStatus.input); const member = useUnit(profileModel.$currentMember); const identity = useUnit(profileModel.$identity); - const fulfilled = useUnit(profileModel.$fulfilled); + const pending = useUnit(profileModel.$pending); + const isAccountExist = useUnit(profileModel.$isAccountExist); const isNetworkDisabled = featureState.status === 'failed' && featureState.error.message === ERROR.networkDisabled; @@ -30,8 +31,40 @@ export const ProfileCard = memo(() => { {t('fellowship.yourProfile')} - - {member ? ( + + {!isAccountExist && ( + + {t('fellowship.noAccount')} + + + +
+ +
+
+ + {t('fellowship.tooltips.noAccount', { chain: featureInput?.chain.name || '' })} + +
+
+ )} + + {isAccountExist && nullable(member) && ( + + {t('fellowship.noProfile')} + + + +
+ +
+
+ {t('fellowship.tooltips.noProfile')} +
+
+ )} + + {isAccountExist && nonNullable(member) && (
{ /> - ) : ( - {t('fellowship.noProfile')} )} diff --git a/src/renderer/features/fellowship-profile/model/profile.ts b/src/renderer/features/fellowship-profile/model/profile.ts index 54e9f2529c..c079dae3ec 100644 --- a/src/renderer/features/fellowship-profile/model/profile.ts +++ b/src/renderer/features/fellowship-profile/model/profile.ts @@ -1,15 +1,17 @@ import { combine, sample } from 'effector'; -import { and, not, or } from 'patronum'; +import { and, or } from 'patronum'; import { attachToFeatureInput } from '@/shared/effector'; -import { nonNullable, nullable } from '@/shared/lib/utils'; +import { nullable } from '@/shared/lib/utils'; import { collectiveDomain } from '@/domains/collectives'; import { identityDomain } from '@/domains/identity'; +import { accountUtils } from '@/entities/wallet'; import { fellowshipModel } from './fellowship'; import { profileFeatureStatus } from './status'; -const $members = fellowshipModel.$store.map(x => x?.members ?? []); +const $members = fellowshipModel.$store.map(store => store?.members ?? []); + const $identities = combine(profileFeatureStatus.input, identityDomain.identity.$list, (featureInput, list) => { if (nullable(featureInput)) return {}; @@ -19,7 +21,9 @@ const $identities = combine(profileFeatureStatus.input, identityDomain.identity. const $currentMember = combine(profileFeatureStatus.input, $members, (featureInput, members) => { if (nullable(featureInput) || members.length === 0) return null; - return collectiveDomain.membersService.findMachingMember(featureInput.accounts, members, featureInput.chain); + const { wallet, accounts, chain } = featureInput; + + return collectiveDomain.membersService.findMatchingMember(wallet, accounts, chain, members); }); const $identity = combine($currentMember, $identities, (member, identities) => { @@ -28,6 +32,14 @@ const $identity = combine($currentMember, $identities, (member, identities) => { return identities[member.accountId] ?? null; }); +const $isAccountExist = profileFeatureStatus.input.map(store => { + if (!store) return false; + + return store.accounts.some(account => { + return !accountUtils.isBaseAccount(account) && accountUtils.isChainAndCryptoMatch(account, store.chain); + }); +}); + const $pendingMember = and(collectiveDomain.members.pending, $currentMember.map(nullable)); const $pendingIdentity = and(identityDomain.identity.pending, $identity.map(nullable)); @@ -55,6 +67,6 @@ sample({ export const profileModel = { $currentMember, $identity, + $isAccountExist, $pending: or($pendingMember, $pendingIdentity, profileFeatureStatus.isStarting), - $fulfilled: and($currentMember.map(nonNullable), not($pendingMember), not($pendingIdentity)), }; diff --git a/src/renderer/features/fellowship-profile/model/status.ts b/src/renderer/features/fellowship-profile/model/status.ts index 4d7c2e02f8..954ab7d1be 100644 --- a/src/renderer/features/fellowship-profile/model/status.ts +++ b/src/renderer/features/fellowship-profile/model/status.ts @@ -19,6 +19,7 @@ const $input = combine( chainId: network.chainId, palletType: network.palletType, accounts: wallet.accounts, + wallet, }; }, ); diff --git a/src/renderer/features/fellowship-voting/model/status.ts b/src/renderer/features/fellowship-voting/model/status.ts index 9665616835..0806679e6f 100644 --- a/src/renderer/features/fellowship-voting/model/status.ts +++ b/src/renderer/features/fellowship-voting/model/status.ts @@ -20,9 +20,9 @@ const $input = combine( chain: network.chain, chainId: network.chainId, palletType: network.palletType, + accounts: wallet.accounts, wallets, wallet, - accounts: wallet.accounts, }; }, ); diff --git a/src/renderer/features/fellowship-voting/model/votingStatus.ts b/src/renderer/features/fellowship-voting/model/votingStatus.ts index 65b314b786..f09df5f9cd 100644 --- a/src/renderer/features/fellowship-voting/model/votingStatus.ts +++ b/src/renderer/features/fellowship-voting/model/votingStatus.ts @@ -27,13 +27,15 @@ const $referendum = combine($referendums, $referendumId, (referendums, referendu const $currentMember = combine(votingFeatureStatus.input, $members, (featureInput, members) => { if (nullable(featureInput)) return null; - return collectiveDomain.membersService.findMachingMember(featureInput.accounts, members, featureInput.chain); + const { wallet, accounts, chain } = featureInput; + + return collectiveDomain.membersService.findMatchingMember(wallet, accounts, chain, members); }); const $votingAccount = combine(votingFeatureStatus.input, $currentMember, (input, member) => { if (nullable(member) || nullable(input)) return null; - return collectiveDomain.membersService.findMachingAccount(input.accounts, member); + return collectiveDomain.membersService.findMatchingAccount(input.accounts, member); }); const $hasRequiredRank = combine( diff --git a/src/renderer/pages/Fellowship/ui/Fellowship.tsx b/src/renderer/pages/Fellowship/ui/Fellowship.tsx index b876665b80..3b90fa5f67 100644 --- a/src/renderer/pages/Fellowship/ui/Fellowship.tsx +++ b/src/renderer/pages/Fellowship/ui/Fellowship.tsx @@ -32,7 +32,7 @@ export const Fellowship = () => { const selectedChain = useUnit(fellowshipNetworkFeature.model.network.$selectedChainId); useLayoutEffect(() => { - if (chainId && chainId.startsWith('0x')) { + if (chainId?.startsWith('0x')) { fellowshipNetworkFeature.model.network.selectCollective({ chainId: chainId as ChainId }); } else { // navigate to default chain diff --git a/src/renderer/shared/i18n/locales/en.json b/src/renderer/shared/i18n/locales/en.json index 37de3bb181..4ab6df84bd 100644 --- a/src/renderer/shared/i18n/locales/en.json +++ b/src/renderer/shared/i18n/locales/en.json @@ -307,6 +307,9 @@ "reloadButton": "Refresh" }, "fellowship": { + "details": { + "noDetails": "Details not found" + }, "errors": { "disconnect": { "action": "Open network settings", @@ -322,22 +325,7 @@ "modalAccountTitle": "Account", "modalTitle": "Fellowship members" }, - "voting": { - "voted": "Voted:", - "errors": { - "rankThreshold": "You cannot vote in this referendum because your rank is below the required level." - }, - "votingStatus": "Voting status", - "summary": "Summary", - "title": "Vote on", - "threshold": "Threshold", - "aye": "Aye", - "nay": "Nay", - "confirmation": { - "vote": "Vote", - "fee": "Fee" - } - }, + "noAccount": "Account not found", "noProfile": "Profile doesn’t exist", "referendums": { "tracks": { @@ -365,7 +353,26 @@ } }, "title": "Fellowship", - "yourProfile": "Your profile", + "tooltips": { + "noProfile": "You are not a fellowship member", + "noAccount": "Please add or create account to the { chain } network" + }, + "voting": { + "voted": "Voted:", + "errors": { + "rankThreshold": "You cannot vote in this referendum because your rank is below the required level." + }, + "votingStatus": "Voting status", + "summary": "Summary", + "title": "Vote on", + "threshold": "Threshold", + "aye": "Aye", + "nay": "Nay", + "confirmation": { + "vote": "Vote", + "fee": "Fee" + } + }, "votingHistory": { "modalTitle": "Vote history", "votes_one": "{count} vote", @@ -374,9 +381,7 @@ "emptyList": "There is no vote history", "votingPowerDescription": "Voting power is a number of votes calculated based on the member's rank." }, - "details": { - "noDetails": "Details not found" - } + "yourProfile": "Your profile" }, "general": { "actions": { diff --git a/src/renderer/shared/ui-kit/Tooltip/Tooltip.tsx b/src/renderer/shared/ui-kit/Tooltip/Tooltip.tsx index 7ca64900d6..a756397849 100644 --- a/src/renderer/shared/ui-kit/Tooltip/Tooltip.tsx +++ b/src/renderer/shared/ui-kit/Tooltip/Tooltip.tsx @@ -30,7 +30,7 @@ const Root = ({ onToggle, children, side = 'top', - sideOffset = 2, + sideOffset = 1, align = 'center', alignOffset = 0, testId = 'Tooltip',