Skip to content

Commit 08e8f18

Browse files
committed
feat: add max amount for role conditions
1 parent 2fd8763 commit 08e8f18

13 files changed

+101
-39
lines changed

api-schema.graphql

+3
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,7 @@ type Role {
650650

651651
type RoleCondition {
652652
amount: String
653+
amountMax: String
653654
asset: SolanaNetworkAsset
654655
config: JSON
655656
createdAt: DateTime
@@ -721,6 +722,7 @@ input UserCreateCommunityInput {
721722

722723
input UserCreateRoleConditionInput {
723724
amount: String
725+
amountMax: String
724726
config: JSON
725727
filters: JSON
726728
roleId: String!
@@ -859,6 +861,7 @@ input UserUpdateCommunityMemberInput {
859861

860862
input UserUpdateRoleConditionInput {
861863
amount: String
864+
amountMax: String
862865
config: JSON
863866
filters: JSON
864867
}

libs/api/network-token/data-access/src/lib/helpers/get-user-network-token-where-input.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export function getUserNetworkTokenWhereInput(
1212

1313
if (filters.length) {
1414
where.account = { in: filters }
15-
} else {
15+
} else if (input.username) {
1616
where.account = 'none'
1717
}
1818

libs/api/role/data-access/src/lib/api-role-resolver.service.ts

+24-6
Original file line numberDiff line numberDiff line change
@@ -246,25 +246,43 @@ export class ApiRoleResolverService {
246246
return false
247247
}
248248

249+
const amountMin = parseInt(condition.amount ?? '0')
250+
const amountMax = parseInt(condition.amountMax ?? '0')
251+
252+
const assetAmount = this.getAssetAmount(condition, found)
253+
254+
if (amountMin && assetAmount < amountMin) {
255+
return false
256+
}
257+
if (amountMax && assetAmount > amountMax) {
258+
return false
259+
}
260+
return true
261+
}
262+
263+
private getAssetAmount(condition: RoleCondition, assets: NetworkAsset[]) {
249264
const filtered =
250265
condition.type === NetworkTokenType.NonFungible && Object.keys(condition.filters ?? {})
251-
? found
266+
? assets
252267
.filter((assets) => !!Object.keys(assets.attributes ?? {})?.length)
253268
.filter((assets) =>
254269
validateAttributeFilter({
255270
attributes: assets.attributes as [string, string][],
256271
filters: (condition.filters ?? {}) as Record<string, string>,
257272
}),
258273
)
259-
: found
274+
: assets
275+
276+
const nonFungibleAmount = filtered?.length ?? 0
277+
const fungibleAmount = filtered?.reduce((acc, asset) => acc + parseInt(asset.balance ?? '0'), 0) ?? 0
260278

261279
switch (condition.type) {
262280
case NetworkTokenType.NonFungible:
263-
return filtered?.length >= parseInt(condition.amount ?? '0')
281+
return nonFungibleAmount
264282
case NetworkTokenType.Fungible:
265-
return (
266-
filtered?.reduce((acc, asset) => acc + parseInt(asset.balance ?? '0'), 0) >= parseInt(condition.amount ?? '0')
267-
)
283+
return fungibleAmount
284+
default:
285+
return 0
268286
}
269287
}
270288

libs/api/role/data-access/src/lib/dto/user-create-role-condition.input.ts

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export class UserCreateRoleConditionInput {
1313
tokenId!: string
1414
@Field({ nullable: true })
1515
amount?: string | null
16+
@Field({ nullable: true })
17+
amountMax?: string | null
1618
@Field(() => GraphQLJSON, { nullable: true })
1719
config?: Prisma.InputJsonValue | null
1820
@Field(() => GraphQLJSON, { nullable: true })

libs/api/role/data-access/src/lib/dto/user-update-role-condition-input.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { GraphQLJSON } from 'graphql-scalars'
66
export class UserUpdateRoleConditionInput {
77
@Field({ nullable: true })
88
amount?: string | null
9+
@Field({ nullable: true })
10+
amountMax?: string | null
911
@Field(() => GraphQLJSON, { nullable: true })
1012
config?: Prisma.InputJsonValue | null
1113
@Field(() => GraphQLJSON, { nullable: true })

libs/api/role/data-access/src/lib/entity/role-condition.entity.ts

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export class RoleCondition {
1717
type!: NetworkTokenType
1818
@Field({ nullable: true })
1919
amount?: string | null
20+
@Field({ nullable: true })
21+
amountMax?: string | null
2022
@Field(() => GraphQLJSON, { nullable: true })
2123
config?: Prisma.JsonValue | null
2224
@Field(() => GraphQLJSON, { nullable: true })

libs/sdk/src/generated/graphql-sdk.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1156,6 +1156,7 @@ export type Role = {
11561156
export type RoleCondition = {
11571157
__typename?: 'RoleCondition'
11581158
amount?: Maybe<Scalars['String']['output']>
1159+
amountMax?: Maybe<Scalars['String']['output']>
11591160
asset?: Maybe<SolanaNetworkAsset>
11601161
config?: Maybe<Scalars['JSON']['output']>
11611162
createdAt?: Maybe<Scalars['DateTime']['output']>
@@ -1231,6 +1232,7 @@ export type UserCreateCommunityInput = {
12311232

12321233
export type UserCreateRoleConditionInput = {
12331234
amount?: InputMaybe<Scalars['String']['input']>
1235+
amountMax?: InputMaybe<Scalars['String']['input']>
12341236
config?: InputMaybe<Scalars['JSON']['input']>
12351237
filters?: InputMaybe<Scalars['JSON']['input']>
12361238
roleId: Scalars['String']['input']
@@ -1370,6 +1372,7 @@ export type UserUpdateCommunityMemberInput = {
13701372

13711373
export type UserUpdateRoleConditionInput = {
13721374
amount?: InputMaybe<Scalars['String']['input']>
1375+
amountMax?: InputMaybe<Scalars['String']['input']>
13731376
config?: InputMaybe<Scalars['JSON']['input']>
13741377
filters?: InputMaybe<Scalars['JSON']['input']>
13751378
}
@@ -10325,6 +10328,7 @@ export function UserCreateCommunityInputSchema(): z.ZodObject<Properties<UserCre
1032510328
export function UserCreateRoleConditionInputSchema(): z.ZodObject<Properties<UserCreateRoleConditionInput>> {
1032610329
return z.object({
1032710330
amount: z.string().nullish(),
10331+
amountMax: z.string().nullish(),
1032810332
config: definedNonNullAnySchema.nullish(),
1032910333
filters: definedNonNullAnySchema.nullish(),
1033010334
roleId: z.string(),
@@ -10477,6 +10481,7 @@ export function UserUpdateCommunityMemberInputSchema(): z.ZodObject<Properties<U
1047710481
export function UserUpdateRoleConditionInputSchema(): z.ZodObject<Properties<UserUpdateRoleConditionInput>> {
1047810482
return z.object({
1047910483
amount: z.string().nullish(),
10484+
amountMax: z.string().nullish(),
1048010485
config: definedNonNullAnySchema.nullish(),
1048110486
filters: definedNonNullAnySchema.nullish(),
1048210487
})

libs/web/network-token/ui/src/lib/network-token-ui-select.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,5 @@ export function NetworkTokenUiSelect({
2929
}
3030

3131
function NetworkTokenUiNavLink({ token, ...props }: NavLinkProps & { token: NetworkToken }) {
32-
return <NavLink label={<NetworkTokenUiItem networkToken={token} />} variant={'light'} {...props} />
32+
return <NavLink component={'span'} label={<NetworkTokenUiItem networkToken={token} />} variant={'light'} {...props} />
3333
}

libs/web/role/feature/src/lib/user-role-detail.feature.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Group } from '@mantine/core'
22
import { Community } from '@pubkey-link/sdk'
3-
import { useAdminFindManyNetworkToken } from '@pubkey-link/web-network-token-data-access'
3+
import { useUserFindManyNetworkToken } from '@pubkey-link/web-network-token-data-access'
44
import { useUserFindOneRole } from '@pubkey-link/web-role-data-access'
55
import { RoleConditionUiAddButton, RoleUiItem } from '@pubkey-link/web-role-ui'
66
import { UiBack, UiCard, UiCardTitle, UiDebugModal, UiError, UiGroup, UiLoader, UiStack } from '@pubkey-ui/core'
@@ -12,7 +12,7 @@ import { UserRoleDetailSettingsTab } from './user-role-detail-settings.tab'
1212
export function UserRoleDetailFeature({ community }: { community: Community }) {
1313
const { roleId } = useParams<{ roleId: string }>() as { roleId: string }
1414
const { item, query } = useUserFindOneRole({ roleId })
15-
const { items: tokens } = useAdminFindManyNetworkToken({ cluster: community.cluster })
15+
const { items: tokens } = useUserFindManyNetworkToken({ cluster: community.cluster })
1616

1717
return query.isLoading ? (
1818
<UiLoader />

libs/web/role/ui/src/lib/role-condition-ui-create-wizard.tsx

+20-15
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Button, Stepper, Text } from '@mantine/core'
1+
import { Box, Button, Group, Stepper } from '@mantine/core'
22
import {
33
Community,
44
getEnumOptions,
@@ -14,14 +14,15 @@ import { toastError, toastSuccess, UiCard, UiDebug, UiInfo, UiStack } from '@pub
1414
import { useMemo, useState } from 'react'
1515
import { RoleConditionUiItem } from './role-condition-ui-item'
1616
import { RoleConditionUiNavLink } from './role-condition-ui-nav-link'
17-
import { RoleConditionUiTypeForm } from './role-condition-ui-type-form'
17+
import { RoleConditionUiAmountForm, RoleConditionUiTypeForm } from './role-condition-ui-type-form'
1818
import { RoleUiItem } from './role-ui-item'
1919

2020
export function RoleConditionUiCreateWizard(props: { role: Role; community: Community; tokens: NetworkToken[] }) {
2121
const { query, createRoleCondition } = useUserFindOneRole({ roleId: props.role.id })
2222
const [networkTokenType, setNetworkTokenType] = useState<NetworkTokenType | undefined>(undefined)
2323
const [networkToken, setNetworkToken] = useState<NetworkToken | undefined>(undefined)
2424
const [amount, setAmount] = useState<string>('0')
25+
const [amountMax, setAmountMax] = useState<string>('0')
2526
const tokens: NetworkToken[] = useMemo(() => {
2627
if (networkTokenType === NetworkTokenType.Fungible) {
2728
return props.tokens.filter((token) => token.type === NetworkTokenType.Fungible)
@@ -62,12 +63,10 @@ export function RoleConditionUiCreateWizard(props: { role: Role; community: Comm
6263
}, [props.role.id, networkTokenType, networkToken])
6364

6465
async function addCondition(type: NetworkTokenType, token: NetworkToken) {
65-
console.log('addCondition', type, token)
6666
createRoleCondition({ ...config, type, tokenId: token.id })
6767
.then(async (res) => {
68-
console.log('res', res)
6968
toastSuccess('Condition created')
70-
query.refetch()
69+
await query.refetch()
7170
})
7271
.catch((err) => {
7372
toastError('Error creating condition')
@@ -109,9 +108,10 @@ export function RoleConditionUiCreateWizard(props: { role: Role; community: Comm
109108
<Stepper.Step label="Configuration" description="Configure the condition">
110109
{networkTokenType ? (
111110
<UiStack>
111+
<Box px="sm" py="xs">
112+
<RoleConditionUiItem type={networkTokenType} />
113+
</Box>
112114
<RoleConditionUiTypeForm
113-
amount={amount}
114-
setAmount={setAmount}
115115
networkToken={networkToken}
116116
setNetworkToken={setNetworkToken}
117117
type={networkTokenType}
@@ -131,17 +131,22 @@ export function RoleConditionUiCreateWizard(props: { role: Role; community: Comm
131131
['Type', <RoleConditionUiItem type={networkTokenType} />],
132132
networkToken ? ['Token', <NetworkTokenUiItem networkToken={networkToken} />] : undefined,
133133
[
134-
'Amount (min)',
135-
<Text size="xl" fw="bold">
136-
{amount} {networkToken?.symbol}
137-
</Text>,
134+
'Amount',
135+
<Group>
136+
<RoleConditionUiAmountForm label="Amount (min)" amount={amount} setAmount={setAmount} />
137+
<RoleConditionUiAmountForm label="Amount (max)" amount={amountMax} setAmount={setAmountMax} />
138+
</Group>,
139+
],
140+
[
141+
'',
142+
<Group justify="end">
143+
<Button size="xl" onClick={() => addCondition(networkTokenType, networkToken)}>
144+
Create Condition
145+
</Button>
146+
</Group>,
138147
],
139148
]}
140149
/>
141-
142-
<Button size="xl" onClick={() => addCondition(networkTokenType, networkToken)}>
143-
Create Condition
144-
</Button>
145150
</UiStack>
146151
) : (
147152
<UiInfo message="Select a condition type and configure it" />
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
1-
import { TextInput } from '@mantine/core'
1+
import { TextInput, TextInputProps } from '@mantine/core'
22
import { NetworkToken, NetworkTokenType } from '@pubkey-link/sdk'
33
import { NetworkTokenUiSelect } from '@pubkey-link/web-network-token-ui'
44
import { UiStack } from '@pubkey-ui/core'
55

66
export function RoleConditionUiTypeForm({
7-
amount,
8-
setAmount,
97
networkToken,
108
setNetworkToken,
119
type,
1210
tokens,
1311
}: {
14-
amount: string
15-
setAmount: (amount: string) => void
1612
networkToken?: NetworkToken | undefined
1713
setNetworkToken: (token: NetworkToken | undefined) => void
1814
type: NetworkTokenType
@@ -24,16 +20,30 @@ export function RoleConditionUiTypeForm({
2420
return (
2521
<UiStack>
2622
<NetworkTokenUiSelect value={networkToken} setValue={setNetworkToken} tokens={tokens} />
27-
<TextInput
28-
label="Amount"
29-
description="Amount of tokens to match"
30-
placeholder="Amount"
31-
value={amount}
32-
onChange={(event) => setAmount(event.currentTarget.value)}
33-
/>
3423
</UiStack>
3524
)
3625
default:
3726
return <div>Unknown</div>
3827
}
3928
}
29+
30+
export function RoleConditionUiAmountForm({
31+
amount,
32+
setAmount,
33+
...props
34+
}: TextInputProps & {
35+
amount: string
36+
setAmount: (amount: string) => void
37+
}) {
38+
return (
39+
<TextInput
40+
label="Amount"
41+
description="Amount of tokens to match"
42+
step="any"
43+
min="0"
44+
value={amount}
45+
onChange={(event) => setAmount(event.currentTarget.value)}
46+
{...props}
47+
/>
48+
)
49+
}

libs/web/role/ui/src/lib/role-condition-ui-update-form-non-fungible.tsx

+15-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export function RoleConditionUiUpdateFormNonFungible({
1616
const form = useForm<UserUpdateRoleConditionInput>({
1717
initialValues: {
1818
amount: item.amount ?? '',
19+
amountMax: item.amountMax ?? '',
1920
config: JSON.stringify(item.config ?? {}),
2021
filters: JSON.stringify(item.filters ?? {}),
2122
},
@@ -28,7 +29,20 @@ export function RoleConditionUiUpdateFormNonFungible({
2829
})}
2930
>
3031
<UiStack>
31-
<TextInput label="Amount" type="number" min={0} step={1} {...form.getInputProps('amount')} />
32+
<TextInput
33+
label="Amount (Min)"
34+
description="Minimal amount of tokens to match"
35+
step="any"
36+
min="0"
37+
{...form.getInputProps('amount')}
38+
/>
39+
<TextInput
40+
label="Amount (Max)"
41+
description="Maximum amount of tokens to match"
42+
step="any"
43+
min="0"
44+
{...form.getInputProps('amountMax')}
45+
/>
3246
<JsonInput
3347
label="Filters"
3448
placeholder="Textarea will autosize to fit the content"

prisma/schema.prisma

+2-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,8 @@ model RoleCondition {
241241
createdAt DateTime @default(now())
242242
updatedAt DateTime @updatedAt
243243
type NetworkTokenType
244-
amount String @default("0")
244+
amount String @default("0") // TODO: V3: rename to amountMin
245+
amountMax String @default("0")
245246
config Json?
246247
filters Json?
247248
token NetworkToken @relation(fields: [tokenId], references: [id])

0 commit comments

Comments
 (0)