Skip to content

Commit 6beb532

Browse files
author
CsB-Polymesh
committed
updated code based on suggestions
1 parent 5352913 commit 6beb532

12 files changed

Lines changed: 287 additions & 121 deletions

File tree

src/layouts/SecondaryKeys/components/AddPermission/components/AssetPermissionSelector.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -330,14 +330,15 @@ export const AssetPermissionSelector = ({
330330
return { error: '', assetId: trimmedValue };
331331
}
332332

333-
// Check if it could be a ticker (12 characters or less, alphanumeric with allowed symbols)
333+
// Check if it could be a ticker (12 characters or less, uppercase alphanumeric - Polymesh ticker format)
334+
const uppercasedValue = trimmedValue.toUpperCase();
334335
const isTickerFormat =
335-
trimmedValue.length <= 12 && /^[A-Z0-9_\-./]+$/i.test(trimmedValue);
336+
uppercasedValue.length <= 12 && /^[A-Z0-9]+$/.test(uppercasedValue);
336337

337338
if (isTickerFormat) {
338339
// Try to fetch asset by ticker
339340
try {
340-
const asset = await fetchAsset(trimmedValue);
341+
const asset = await fetchAsset(uppercasedValue);
341342
if (asset) {
342343
return { error: '', assetId: asset.id };
343344
}

src/layouts/SecondaryKeys/components/AddPermission/components/ExtrinsicPermissionSelector.tsx

Lines changed: 70 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useCallback } from 'react';
1+
import { useState, useCallback, useEffect } from 'react';
22
import styled from 'styled-components';
33
import { TxGroup, TxTag } from '@polymeshassociation/polymesh-sdk/types';
44
import { txGroupToTxTags } from '@polymeshassociation/polymesh-sdk/utils';
@@ -92,7 +92,7 @@ const GroupCheckbox = styled.input.attrs({ type: 'checkbox' })`
9292
}
9393
`;
9494

95-
const GroupLabel = styled.div`
95+
const GroupLabel = styled.label`
9696
font-weight: 500;
9797
font-size: 14px;
9898
color: ${({ theme }) => theme.colors.textPrimary};
@@ -145,6 +145,13 @@ const TransactionLabel = styled.span`
145145
word-break: break-word;
146146
`;
147147

148+
const TransactionCheckboxLabel = styled.label`
149+
display: flex;
150+
align-items: center;
151+
width: 100%;
152+
cursor: pointer;
153+
`;
154+
148155
export const ExtrinsicPermissionSelector = ({
149156
selectedExtrinsics,
150157
onChange,
@@ -162,45 +169,56 @@ export const ExtrinsicPermissionSelector = ({
162169
initial[group] = new Set();
163170
});
164171

165-
// Initialize from selectedExtrinsics
166-
selectedExtrinsics.forEach(({ pallet, extrinsics }) => {
172+
// If selectedExtrinsics is empty, it means "Whole" (all permissions)
173+
// In this case, select all transactions for all groups
174+
if (selectedExtrinsics.length === 0) {
167175
ALL_TX_GROUPS.forEach((group) => {
168176
const allTransactions = txGroupToTxTags(group as TxGroup) || [];
169-
if (extrinsics && extrinsics.length > 0) {
170-
extrinsics.forEach((ext) => {
171-
const txTag = `${pallet}.${ext}` as TxTag;
172-
if (allTransactions.includes(txTag)) {
173-
initial[group].add(txTag);
174-
}
175-
});
176-
} else {
177-
allTransactions.forEach((tx) => {
178-
if (tx.startsWith(`${pallet}.`)) {
179-
initial[group].add(tx);
180-
}
181-
});
182-
}
177+
allTransactions.forEach((tx) => {
178+
initial[group].add(tx);
179+
});
183180
});
184-
});
181+
} else {
182+
// Initialize from selectedExtrinsics
183+
selectedExtrinsics.forEach(({ pallet, extrinsics }) => {
184+
ALL_TX_GROUPS.forEach((group) => {
185+
const allTransactions = txGroupToTxTags(group as TxGroup) || [];
186+
if (extrinsics && extrinsics.length > 0) {
187+
extrinsics.forEach((ext) => {
188+
const txTag = `${pallet}.${ext}` as TxTag;
189+
if (allTransactions.includes(txTag)) {
190+
initial[group].add(txTag);
191+
}
192+
});
193+
} else {
194+
allTransactions.forEach((tx) => {
195+
if (tx.startsWith(`${pallet}.`)) {
196+
initial[group].add(tx);
197+
}
198+
});
199+
}
200+
});
201+
});
202+
}
185203

186204
return initial;
187205
});
188206

189207
const emitSelection = useCallback(
190208
(updated: Record<TxGroup, Set<string>>) => {
191209
// Get all selected transactions across all groups
192-
const allSelectedTxTags = new Set<string>();
210+
const allSelectedTxTagsSet = new Set<string>();
193211

194212
ALL_TX_GROUPS.forEach((group) => {
195213
updated[group].forEach((tx) => {
196-
allSelectedTxTags.add(tx);
214+
allSelectedTxTagsSet.add(tx);
197215
});
198216
});
199217

200218
// Group transactions by pallet
201219
const palletMap = new Map<string, Set<string>>();
202220

203-
allSelectedTxTags.forEach((txTag) => {
221+
allSelectedTxTagsSet.forEach((txTag) => {
204222
const [pallet, extrinsic] = txTag.split('.');
205223
if (!palletMap.has(pallet)) {
206224
palletMap.set(pallet, new Set());
@@ -220,6 +238,18 @@ export const ExtrinsicPermissionSelector = ({
220238
[onChange],
221239
);
222240

241+
// When selectedExtrinsics is empty on mount (meaning "Whole" is being shown as "These")
242+
// emit the current state which should have all transactions selected
243+
useEffect(() => {
244+
if (
245+
selectedExtrinsics.length === 0 &&
246+
Object.keys(selectedByGroup).length > 0
247+
) {
248+
// Emit current selection which has everything selected
249+
emitSelection(selectedByGroup);
250+
}
251+
}, [selectedExtrinsics.length, selectedByGroup, emitSelection]);
252+
223253
const toggleGroupExpansion = useCallback((group: TxGroup) => {
224254
setExpandedGroups((prev) => {
225255
const newSet = new Set(prev);
@@ -306,16 +336,21 @@ export const ExtrinsicPermissionSelector = ({
306336
<GroupHeader>
307337
<GroupHeaderLeft>
308338
<GroupCheckbox
339+
id={`group-${group}`}
309340
checked={checked}
310341
ref={(checkboxInput: HTMLInputElement | null) => {
311342
if (checkboxInput) {
343+
// HTML checkbox `indeterminate` is not exposed as a React prop,
344+
// so we must set it directly on the DOM node in the ref callback.
312345
// eslint-disable-next-line no-param-reassign
313346
checkboxInput.indeterminate = indeterminate;
314347
}
315348
}}
316349
onChange={() => toggleGroup(group)}
317350
/>
318-
<GroupLabel>{TX_GROUP_LABELS[group]}</GroupLabel>
351+
<GroupLabel htmlFor={`group-${group}`}>
352+
{TX_GROUP_LABELS[group]}
353+
</GroupLabel>
319354
</GroupHeaderLeft>
320355

321356
<ExpandButton
@@ -336,11 +371,18 @@ export const ExtrinsicPermissionSelector = ({
336371

337372
return (
338373
<TransactionItem key={`${group}-${transaction}`}>
339-
<TransactionCheckbox
340-
checked={isTransactionSelected || false}
341-
onChange={() => toggleTransaction(group, transaction)}
342-
/>
343-
<TransactionLabel>{transaction}</TransactionLabel>
374+
<TransactionCheckboxLabel
375+
htmlFor={`tx-${group}-${transaction}`}
376+
>
377+
<TransactionCheckbox
378+
id={`tx-${group}-${transaction}`}
379+
checked={isTransactionSelected || false}
380+
onChange={() =>
381+
toggleTransaction(group, transaction)
382+
}
383+
/>
384+
<TransactionLabel>{transaction}</TransactionLabel>
385+
</TransactionCheckboxLabel>
344386
</TransactionItem>
345387
);
346388
})}

src/layouts/SecondaryKeys/components/AddPermission/components/PermissionSummary.tsx

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useContext, useMemo } from 'react';
22
import { AssetContext } from '~/context/AssetContext';
3+
import { deduplicateAssetsByID, formatAssetDisplay } from '../../../utils';
34
import {
45
SummarySection,
56
SummaryCard,
@@ -8,7 +9,6 @@ import {
89
SelectedItemsList,
910
SelectedItem,
1011
} from '../styles';
11-
import { formatAssetDisplay } from '../../../utils';
1212

1313
interface IPermissionSummaryProps {
1414
secondaryKey: string;
@@ -34,15 +34,26 @@ export const PermissionSummary = ({
3434
}: IPermissionSummaryProps) => {
3535
const { ownedAssets, managedAssets } = useContext(AssetContext);
3636

37-
// Combine all available assets
38-
const allAssets = useMemo(() => {
39-
const combined = [...ownedAssets, ...managedAssets];
40-
// Remove duplicates by ID
41-
return combined.filter(
42-
(asset, index, self) =>
43-
index === self.findIndex((a) => a.id === asset.id),
44-
);
45-
}, [ownedAssets, managedAssets]);
37+
// Combine all available assets and deduplicate
38+
const allAssets = useMemo(
39+
() => deduplicateAssetsByID(ownedAssets, managedAssets),
40+
[ownedAssets, managedAssets],
41+
);
42+
43+
// Calculate the number of extrinsics for truncation
44+
const totalExtrinsics = useMemo(() => {
45+
return permissions.transactions.values.reduce((count, item) => {
46+
if (item.extrinsics && item.extrinsics.length > 0) {
47+
return count + item.extrinsics.length;
48+
}
49+
return count + 1; // Count pallets with "all methods"
50+
}, 0);
51+
}, [permissions.transactions.values]);
52+
53+
const MAX_DISPLAY_ITEMS = 5;
54+
const shouldTruncateExtrinsics =
55+
totalExtrinsics > MAX_DISPLAY_ITEMS &&
56+
permissions.transactions.type === 'These';
4657

4758
const formatPermissionType = (
4859
type: 'Whole' | 'These' | 'Except' | 'None',
@@ -91,18 +102,41 @@ export const PermissionSummary = ({
91102
</SummaryText>
92103
{permissions.transactions.values.length > 0 && (
93104
<SelectedItemsList>
94-
{permissions.transactions.values.flatMap((item) =>
95-
item.extrinsics && item.extrinsics.length > 0 ? (
96-
item.extrinsics.map((extrinsic) => (
97-
<SelectedItem key={`${item.pallet}.${extrinsic}`}>
98-
{item.pallet}.{extrinsic}
99-
</SelectedItem>
100-
))
101-
) : (
102-
<SelectedItem key={item.pallet}>
103-
{item.pallet} (all methods)
105+
{shouldTruncateExtrinsics ? (
106+
<>
107+
{permissions.transactions.values
108+
.slice(0, MAX_DISPLAY_ITEMS)
109+
.flatMap((item) =>
110+
item.extrinsics && item.extrinsics.length > 0 ? (
111+
item.extrinsics.map((extrinsic) => (
112+
<SelectedItem key={`${item.pallet}.${extrinsic}`}>
113+
{item.pallet}.{extrinsic}
114+
</SelectedItem>
115+
))
116+
) : (
117+
<SelectedItem key={item.pallet}>
118+
{item.pallet} (all methods)
119+
</SelectedItem>
120+
),
121+
)}
122+
<SelectedItem style={{ fontStyle: 'italic', opacity: 0.7 }}>
123+
+ {totalExtrinsics - MAX_DISPLAY_ITEMS} more
104124
</SelectedItem>
105-
),
125+
</>
126+
) : (
127+
permissions.transactions.values.flatMap((item) =>
128+
item.extrinsics && item.extrinsics.length > 0 ? (
129+
item.extrinsics.map((extrinsic) => (
130+
<SelectedItem key={`${item.pallet}.${extrinsic}`}>
131+
{item.pallet}.{extrinsic}
132+
</SelectedItem>
133+
))
134+
) : (
135+
<SelectedItem key={item.pallet}>
136+
{item.pallet} (all methods)
137+
</SelectedItem>
138+
),
139+
)
106140
)}
107141
</SelectedItemsList>
108142
)}

src/layouts/SecondaryKeys/components/AddPermission/components/PortfolioPermissionSelector.tsx

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ const DropdownMenu = styled.div<{ $isOpen: boolean }>`
4949
z-index: 1000;
5050
`;
5151

52+
const SearchInputLabel = styled.label`
53+
display: block;
54+
padding: 8px 16px 4px 16px;
55+
font-size: 12px;
56+
font-weight: 500;
57+
color: ${({ theme }) => theme.colors.textSecondary};
58+
`;
59+
5260
const SearchInput = styled.input`
5361
width: 100%;
5462
padding: 12px 16px;
@@ -68,6 +76,13 @@ const SearchInput = styled.input`
6876
}
6977
`;
7078

79+
const PortfolioCheckboxLabel = styled.label`
80+
display: flex;
81+
align-items: center;
82+
width: 100%;
83+
cursor: pointer;
84+
`;
85+
7186
const PortfolioOption = styled.div<{ $isSelected: boolean }>`
7287
display: flex;
7388
align-items: center;
@@ -195,7 +210,11 @@ export const PortfolioPermissionSelector = ({
195210
</SelectedPortfoliosDisplay>
196211

197212
<DropdownMenu $isOpen={isOpen}>
213+
<SearchInputLabel htmlFor="portfolioSearch">
214+
Search Portfolios
215+
</SearchInputLabel>
198216
<SearchInput
217+
id="portfolioSearch"
199218
type="text"
200219
placeholder="Search by ID or name..."
201220
value={searchTerm}
@@ -212,21 +231,24 @@ export const PortfolioPermissionSelector = ({
212231
$isSelected={isPortfolioSelected(portfolio.id)}
213232
onClick={() => handleTogglePortfolio(portfolio)}
214233
>
215-
<Checkbox
216-
type="checkbox"
217-
checked={isPortfolioSelected(portfolio.id)}
218-
readOnly
219-
/>
220-
<PortfolioInfo>
221-
<PortfolioId>
222-
{portfolio.id === 'default'
223-
? 'Default Portfolio'
224-
: `Portfolio ${portfolio.id}`}
225-
</PortfolioId>
226-
{portfolio.name && portfolio.id !== 'default' && (
227-
<PortfolioName>{portfolio.name}</PortfolioName>
228-
)}
229-
</PortfolioInfo>
234+
<PortfolioCheckboxLabel htmlFor={`portfolio-${portfolio.id}`}>
235+
<Checkbox
236+
id={`portfolio-${portfolio.id}`}
237+
type="checkbox"
238+
checked={isPortfolioSelected(portfolio.id)}
239+
readOnly
240+
/>
241+
<PortfolioInfo>
242+
<PortfolioId>
243+
{portfolio.id === 'default'
244+
? 'Default Portfolio'
245+
: `Portfolio ${portfolio.id}`}
246+
</PortfolioId>
247+
{portfolio.name && (
248+
<PortfolioName>{portfolio.name}</PortfolioName>
249+
)}
250+
</PortfolioInfo>
251+
</PortfolioCheckboxLabel>
230252
</PortfolioOption>
231253
))
232254
)}

src/layouts/SecondaryKeys/components/AddPermission/components/ValidationError.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import styled from 'styled-components';
22

3+
/**
4+
* ValidationError component for displaying validation errors.
5+
* Uses hardcoded colors for now, but could be updated to use theme colors
6+
* if styled-components theme is passed through the component hierarchy.
7+
*/
38
export const ValidationError = styled.div`
49
padding: 12px;
510
margin-top: 12px;

src/layouts/SecondaryKeys/components/AddPermission/hooks.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const useAddPermissionModal = (
4040
permissions.portfolios.type === 'These' &&
4141
permissions.portfolios.values.length === 0
4242
) {
43-
return 'At least one portfolio must be selected when using "Specific portfolios only" permission type';
43+
return VALIDATION_MESSAGES.PORTFOLIO_THESE_REQUIRED;
4444
}
4545
return '';
4646
}, [permissions.assets, permissions.transactions, permissions.portfolios]);

0 commit comments

Comments
 (0)