From a82c4080600c38955527d80e6d1506ab790d6da9 Mon Sep 17 00:00:00 2001 From: Simo Date: Tue, 17 Feb 2026 14:33:11 -0400 Subject: [PATCH 1/8] wip Signed-off-by: Simo --- components/groups/page/create/GroupCreate.tsx | 23 +- .../page/create/config/GroupCreateConfig.tsx | 15 +- .../xtdh-grant/GroupCreateXtdhGrant.tsx | 253 ++++++++++ .../xtdh-grant/GroupCreateXtdhGrantModal.tsx | 473 ++++++++++++++++++ .../page/create/config/xtdh-grant/utils.ts | 47 ++ .../groups/page/list/card/GroupCardConfig.tsx | 1 + .../page/list/card/GroupCardConfigs.tsx | 76 +++ entities/IGroup.ts | 1 + generated/models/ApiSeizeSettings.ts | 7 + hooks/useXtdhGrantsSearchQuery.ts | 166 ++++++ openapi.yaml | 4 + package-lock.json | 170 ++++--- services/groups/groupMutations.ts | 43 +- 13 files changed, 1177 insertions(+), 102 deletions(-) create mode 100644 components/groups/page/create/config/xtdh-grant/GroupCreateXtdhGrant.tsx create mode 100644 components/groups/page/create/config/xtdh-grant/GroupCreateXtdhGrantModal.tsx create mode 100644 components/groups/page/create/config/xtdh-grant/utils.ts create mode 100644 hooks/useXtdhGrantsSearchQuery.ts diff --git a/components/groups/page/create/GroupCreate.tsx b/components/groups/page/create/GroupCreate.tsx index f41ce068e2..d7c37d5a29 100644 --- a/components/groups/page/create/GroupCreate.tsx +++ b/components/groups/page/create/GroupCreate.tsx @@ -75,14 +75,14 @@ export default function GroupCreate({ const [isFetching, setIsFetching] = useState( loadingOriginalGroup || - loadingOriginalGroupWallets || - loadingOriginalGroupExcludedWallets + loadingOriginalGroupWallets || + loadingOriginalGroupExcludedWallets ); useEffect(() => { setIsFetching( loadingOriginalGroup || - loadingOriginalGroupWallets || - loadingOriginalGroupExcludedWallets + loadingOriginalGroupWallets || + loadingOriginalGroupExcludedWallets ); }, [ loadingOriginalGroup, @@ -115,6 +115,7 @@ export default function GroupCreate({ owns_nfts: [], identity_addresses: null, excluded_identity_addresses: null, + is_beneficiary_of_grant_id: null, }, is_private: false, }); @@ -155,6 +156,8 @@ export default function GroupCreate({ owns_nfts: originalGroup.group.owns_nfts, identity_addresses: originalGroupWallets ?? [], excluded_identity_addresses: originalGroupExcludedWallets ?? [], + is_beneficiary_of_grant_id: + originalGroup.group.is_beneficiary_of_grant_id ?? null, }, is_private: originalGroup.is_private ?? false, }); @@ -211,7 +214,7 @@ export default function GroupCreate({
-
+
@@ -244,6 +247,7 @@ export default function GroupCreate({ wallets={groupConfig.group.identity_addresses} excludeWallets={groupConfig.group.excluded_identity_addresses} nfts={groupConfig.group.owns_nfts} + beneficiaryGrantId={groupConfig.group.is_beneficiary_of_grant_id} iAmIncluded={iAmIncluded} setLevel={(level) => setGroupConfig((prev) => ({ @@ -293,6 +297,15 @@ export default function GroupCreate({ group: { ...prev.group, owns_nfts: nfts }, })) } + setBeneficiaryGrantId={(grantId) => + setGroupConfig((prev) => ({ + ...prev, + group: { + ...prev.group, + is_beneficiary_of_grant_id: grantId ?? null, + }, + })) + } /> void; readonly setTDH: (tdh: ApiCreateGroupDescription["tdh"]) => void; @@ -46,16 +50,23 @@ export default function GroupCreateConfig({ wallets: ApiCreateGroupDescription["excluded_identity_addresses"] ) => void; readonly setNfts: (nfts: ApiCreateGroupDescription["owns_nfts"]) => void; + readonly setBeneficiaryGrantId: ( + grantId: ApiCreateGroupDescription["is_beneficiary_of_grant_id"] + ) => void; }) { return (
-
+
-
+
+
diff --git a/components/groups/page/create/config/xtdh-grant/GroupCreateXtdhGrant.tsx b/components/groups/page/create/config/xtdh-grant/GroupCreateXtdhGrant.tsx new file mode 100644 index 0000000000..40010e793e --- /dev/null +++ b/components/groups/page/create/config/xtdh-grant/GroupCreateXtdhGrant.tsx @@ -0,0 +1,253 @@ +"use client"; + +import { useMemo, useState } from "react"; +import { useDebounce } from "react-use"; +import type { ApiCreateGroupDescription } from "@/generated/models/ApiCreateGroupDescription"; +import { useXtdhGrantQuery } from "@/hooks/useXtdhGrantQuery"; +import { + formatAmount, + formatDateTime, +} from "@/components/user/xtdh/utils/xtdhGrantFormatters"; +import GroupCreateXtdhGrantModal from "./GroupCreateXtdhGrantModal"; +import { + getGrantStatusLabel, + isSelectableNonGrantedStatus, + toShortGrantId, +} from "./utils"; + +interface GroupCreateXtdhGrantProps { + readonly beneficiaryGrantId: ApiCreateGroupDescription["is_beneficiary_of_grant_id"]; + readonly setBeneficiaryGrantId: ( + grantId: ApiCreateGroupDescription["is_beneficiary_of_grant_id"] + ) => void; +} + +export default function GroupCreateXtdhGrant({ + beneficiaryGrantId, + setBeneficiaryGrantId, +}: GroupCreateXtdhGrantProps) { + const normalizedGrantId = beneficiaryGrantId?.trim() ?? ""; + const hasSelectedGrant = normalizedGrantId.length > 0; + + const [isManuallyEnabled, setIsManuallyEnabled] = useState(hasSelectedGrant); + const [lookupGrantId, setLookupGrantId] = useState( + hasSelectedGrant ? normalizedGrantId : null + ); + const [isModalOpen, setIsModalOpen] = useState(false); + const isEnabled = isManuallyEnabled || hasSelectedGrant; + + useDebounce( + () => { + setLookupGrantId(hasSelectedGrant ? normalizedGrantId : null); + }, + 250, + [hasSelectedGrant, normalizedGrantId] + ); + + const { grant, isFetching, isError, errorMessage } = useXtdhGrantQuery({ + grantId: lookupGrantId, + enabled: isEnabled && !!lookupGrantId, + }); + + const grantStatusLabel = useMemo(() => { + if (grant?.status === undefined) { + return null; + } + return getGrantStatusLabel({ + status: grant.status, + validFrom: grant.valid_from, + validTo: grant.valid_to, + }); + }, [grant]); + + const showNonGrantedWarning = + grant?.status !== undefined && isSelectableNonGrantedStatus(grant.status); + const showLookupError = Boolean(isEnabled && lookupGrantId && isError); + + const onEnabledChange = (nextEnabled: boolean) => { + setIsManuallyEnabled(nextEnabled); + if (!nextEnabled) { + setLookupGrantId(null); + setBeneficiaryGrantId(null); + setIsModalOpen(false); + } + }; + + const onInputChange = (nextValue: string) => { + const normalized = nextValue.trim(); + setBeneficiaryGrantId(normalized.length ? normalized : null); + if (!normalized.length) { + setIsManuallyEnabled(false); + } + }; + + const onClearSelection = () => { + setIsManuallyEnabled(false); + setLookupGrantId(null); + setBeneficiaryGrantId(null); + }; + + const targetCollectionName = grant?.target_collection_name?.trim() ?? ""; + const selectedTargetLabel = + targetCollectionName.length > 0 + ? targetCollectionName + : (grant?.target_contract ?? "Unknown target"); + + return ( +
+
+
+

+ xTDH Grant Beneficiary +

+

+ Optionally require identities to be beneficiaries of a selected xTDH + grant. +

+
+ +
+ + {isEnabled && ( + <> +
+ + + +
+ + {isFetching && !!lookupGrantId && ( +

+ Validating grant... +

+ )} + + {showLookupError && ( +
+

+ {errorMessage ?? "Unable to resolve grant ID."} +

+

+ The ID will still be submitted as entered:{" "} + + {toShortGrantId(lookupGrantId ?? "")} + +

+
+ )} + + {!!grant && ( +
+
+ {grantStatusLabel && ( + + {grantStatusLabel} + + )} +

+ {selectedTargetLabel} +

+
+ +

+ ID: {toShortGrantId(grant.id)} | Rate:{" "} + {formatAmount(grant.rate)} +

+

+ Valid:{" "} + {formatDateTime(grant.valid_from ?? null, { + fallbackLabel: "Immediately", + includeTime: false, + })}{" "} + {"->"}{" "} + {formatDateTime(grant.valid_to ?? null, { + fallbackLabel: "No expiry", + includeTime: false, + })} +

+
+ )} + + {showNonGrantedWarning && ( +
+

+ Selected grant status is not GRANTED. This filter is still + allowed and will be submitted. +

+
+ )} + + )} + + setIsModalOpen(false)} + onGrantSelect={(selectedGrant) => { + setBeneficiaryGrantId(selectedGrant.id); + setLookupGrantId(selectedGrant.id); + setIsManuallyEnabled(true); + setIsModalOpen(false); + }} + /> +
+ ); +} diff --git a/components/groups/page/create/config/xtdh-grant/GroupCreateXtdhGrantModal.tsx b/components/groups/page/create/config/xtdh-grant/GroupCreateXtdhGrantModal.tsx new file mode 100644 index 0000000000..904abbd9e3 --- /dev/null +++ b/components/groups/page/create/config/xtdh-grant/GroupCreateXtdhGrantModal.tsx @@ -0,0 +1,473 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; +import { useClickAway, useDebounce, useKeyPressEvent } from "react-use"; +import type { CommonSelectItem } from "@/components/utils/select/CommonSelect"; +import CommonDropdown from "@/components/utils/select/dropdown/CommonDropdown"; +import { SortDirection } from "@/entities/ISort"; +import type { ApiXTdhGrant } from "@/generated/models/ApiXTdhGrant"; +import { ApiXTdhGrantStatus } from "@/generated/models/ApiXTdhGrantStatus"; +import { + type XtdhGrantSortField, + useXtdhGrantsSearchQuery, +} from "@/hooks/useXtdhGrantsSearchQuery"; +import { + formatAmount, + formatDateTime, +} from "@/components/user/xtdh/utils/xtdhGrantFormatters"; +import { getGrantStatusLabel, toShortGrantId } from "./utils"; + +interface GroupCreateXtdhGrantModalProps { + readonly isOpen: boolean; + readonly selectedGrantId: string | null; + readonly onClose: () => void; + readonly onGrantSelect: (grant: ApiXTdhGrant) => void; +} + +const STATUS_OPTIONS: readonly CommonSelectItem[] = [ + { + key: ApiXTdhGrantStatus.Pending, + value: ApiXTdhGrantStatus.Pending, + label: "Pending", + }, + { + key: ApiXTdhGrantStatus.Granted, + value: ApiXTdhGrantStatus.Granted, + label: "Granted", + }, + { + key: ApiXTdhGrantStatus.Disabled, + value: ApiXTdhGrantStatus.Disabled, + label: "Revoked", + }, + { + key: ApiXTdhGrantStatus.Failed, + value: ApiXTdhGrantStatus.Failed, + label: "Failed", + }, +]; + +const SORT_OPTIONS: readonly CommonSelectItem[] = [ + { key: "created_at", value: "created_at", label: "Created" }, + { key: "valid_from", value: "valid_from", label: "Valid from" }, + { key: "valid_to", value: "valid_to", label: "Valid to" }, + { key: "rate", value: "rate", label: "Rate" }, +]; + +const DIRECTION_OPTIONS: readonly CommonSelectItem[] = [ + { key: SortDirection.DESC, value: SortDirection.DESC, label: "Desc" }, + { key: SortDirection.ASC, value: SortDirection.ASC, label: "Asc" }, +]; + +const getStatusPillClasses = (statusLabel: string): string => { + if (statusLabel === "ACTIVE") { + return "tw-bg-green/20 tw-text-green"; + } + if (statusLabel === "SCHEDULED") { + return "tw-bg-blue-400/20 tw-text-blue-200"; + } + if (statusLabel === "ENDED") { + return "tw-bg-iron-700/30 tw-text-iron-400"; + } + if (statusLabel === "PENDING") { + return "tw-bg-primary-400/20 tw-text-primary-300"; + } + return "tw-bg-red/20 tw-text-red"; +}; + +export default function GroupCreateXtdhGrantModal({ + isOpen, + selectedGrantId, + onClose, + onGrantSelect, +}: GroupCreateXtdhGrantModalProps) { + const modalRef = useRef(null); + const skipInitialOutsideClick = useRef(true); + + const [grantorInput, setGrantorInput] = useState(""); + const [targetCollectionInput, setTargetCollectionInput] = useState(""); + const [targetContractInput, setTargetContractInput] = useState(""); + const [grantorFilter, setGrantorFilter] = useState(""); + const [targetCollectionFilter, setTargetCollectionFilter] = useState(""); + const [targetContractFilter, setTargetContractFilter] = useState(""); + const [selectedStatuses, setSelectedStatuses] = useState< + ApiXTdhGrantStatus[] + >([]); + const [sortField, setSortField] = useState("created_at"); + const [sortDirection, setSortDirection] = useState( + SortDirection.DESC + ); + + useEffect(() => { + const timeout = globalThis.setTimeout(() => { + skipInitialOutsideClick.current = false; + }, 0); + + return () => { + globalThis.clearTimeout(timeout); + }; + }, [isOpen]); + + useDebounce(() => setGrantorFilter(grantorInput.trim()), 250, [grantorInput]); + useDebounce( + () => setTargetCollectionFilter(targetCollectionInput.trim()), + 250, + [targetCollectionInput] + ); + useDebounce(() => setTargetContractFilter(targetContractInput.trim()), 250, [ + targetContractInput, + ]); + + useClickAway(modalRef, () => { + if (skipInitialOutsideClick.current) { + skipInitialOutsideClick.current = false; + return; + } + onClose(); + }); + useKeyPressEvent("Escape", onClose); + + const { + grants, + totalCount, + isLoading, + isError, + errorMessage, + refetch, + hasNextPage, + fetchNextPage, + isFetchingNextPage, + } = useXtdhGrantsSearchQuery({ + grantor: grantorFilter || null, + targetCollectionName: targetCollectionFilter || null, + targetContract: targetContractFilter || null, + statuses: selectedStatuses, + sortField, + sortDirection, + enabled: isOpen, + pageSize: 20, + }); + + const allStatusesSelected = selectedStatuses.length === 0; + + const onStatusToggle = (status: ApiXTdhGrantStatus) => { + setSelectedStatuses((prev) => { + if (prev.includes(status)) { + return prev.filter((item) => item !== status); + } + + const next = [...prev, status]; + if (next.length === STATUS_OPTIONS.length) { + return []; + } + return next; + }); + }; + + const onResetFilters = () => { + setGrantorInput(""); + setTargetCollectionInput(""); + setTargetContractInput(""); + setGrantorFilter(""); + setTargetCollectionFilter(""); + setTargetContractFilter(""); + setSelectedStatuses([]); + setSortField("created_at"); + setSortDirection(SortDirection.DESC); + }; + + if (!isOpen) { + return null; + } + + return createPortal( +
+
+
+
+
+
+
+

+ Find xTDH Grant +

+

+ Search global grants and select one for beneficiary filtering. +

+
+ +
+ +
+
+ + + +
+ +
+ + {STATUS_OPTIONS.map((statusOption) => { + const isActive = selectedStatuses.includes( + statusOption.value + ); + return ( + + ); + })} +
+ +
+
+ + Sort by + + + items={SORT_OPTIONS} + activeItem={sortField} + setSelected={setSortField} + filterLabel="Sort grants by field" + size="sm" + /> +
+
+ + Direction + + + items={DIRECTION_OPTIONS} + activeItem={sortDirection} + setSelected={setSortDirection} + filterLabel="Sort direction" + size="sm" + /> +
+ +
+ +
+
+

+ Results +

+

+ {totalCount} total +

+
+ +
+ {isLoading && !grants.length && ( +

+ Loading grants... +

+ )} + + {isError && !grants.length && ( +
+

+ {errorMessage ?? "Unable to load grants."} +

+ +
+ )} + + {!isLoading && !isError && !grants.length && ( +

+ No grants matched the selected filters. +

+ )} + + {!!grants.length && ( +
    + {grants.map((grant) => { + const statusLabel = getGrantStatusLabel({ + status: grant.status, + validFrom: grant.valid_from, + validTo: grant.valid_to, + }); + const trimmedCollectionName = + grant.target_collection_name?.trim() ?? ""; + const targetLabel = + trimmedCollectionName.length > 0 + ? trimmedCollectionName + : grant.target_contract || "Unknown target"; + const isSelected = selectedGrantId === grant.id; + + return ( +
  • +
    +
    +
    + + {statusLabel} + +

    + {targetLabel} +

    +
    +

    + ID: {toShortGrantId(grant.id)} | Rate:{" "} + {formatAmount(grant.rate)} +

    +

    + Valid:{" "} + {formatDateTime(grant.valid_from ?? null, { + fallbackLabel: "Immediately", + includeTime: false, + })}{" "} + {"->"}{" "} + {formatDateTime(grant.valid_to ?? null, { + fallbackLabel: "No expiry", + includeTime: false, + })} +

    +
    + +
    +
  • + ); + })} +
+ )} +
+ + {hasNextPage && ( +
+ +
+ )} +
+
+
+
+
+
, + document.body + ); +} diff --git a/components/groups/page/create/config/xtdh-grant/utils.ts b/components/groups/page/create/config/xtdh-grant/utils.ts new file mode 100644 index 0000000000..5cdf39cb8e --- /dev/null +++ b/components/groups/page/create/config/xtdh-grant/utils.ts @@ -0,0 +1,47 @@ +import { ApiXTdhGrantStatus } from "@/generated/models/ApiXTdhGrantStatus"; + +export const getGrantStatusLabel = ({ + status, + validFrom, + validTo, +}: { + readonly status: ApiXTdhGrantStatus; + readonly validFrom?: number | null | undefined; + readonly validTo?: number | null | undefined; +}): string => { + if (status === ApiXTdhGrantStatus.Granted) { + const now = Date.now(); + const normalizedValidFrom = validFrom ?? null; + const normalizedValidTo = validTo ?? null; + + if ( + typeof normalizedValidTo === "number" && + normalizedValidTo > 0 && + normalizedValidTo < now + ) { + return "ENDED"; + } + if (typeof normalizedValidFrom === "number" && normalizedValidFrom > now) { + return "SCHEDULED"; + } + return "ACTIVE"; + } + + if (status === ApiXTdhGrantStatus.Disabled) { + return "REVOKED"; + } + + return status; +}; + +export const isSelectableNonGrantedStatus = ( + status: ApiXTdhGrantStatus +): boolean => status !== ApiXTdhGrantStatus.Granted; + +export const toShortGrantId = (grantId: string): string => { + const normalizedGrantId = grantId.trim(); + if (normalizedGrantId.length <= 16) { + return normalizedGrantId; + } + return `${normalizedGrantId.slice(0, 8)}...${normalizedGrantId.slice(-4)}`; +}; diff --git a/components/groups/page/list/card/GroupCardConfig.tsx b/components/groups/page/list/card/GroupCardConfig.tsx index bb962d8f18..ca66e5656b 100644 --- a/components/groups/page/list/card/GroupCardConfig.tsx +++ b/components/groups/page/list/card/GroupCardConfig.tsx @@ -13,6 +13,7 @@ export default function GroupCardConfig({ [GroupDescriptionType.LEVEL]: "Level", [GroupDescriptionType.OWNS_NFTS]: "Owns NFTs", [GroupDescriptionType.WALLETS]: "Manual list", + [GroupDescriptionType.XTDH_GRANT]: "Grant", }; return ( diff --git a/components/groups/page/list/card/GroupCardConfigs.tsx b/components/groups/page/list/card/GroupCardConfigs.tsx index 70edbc7bbc..10a147ab64 100644 --- a/components/groups/page/list/card/GroupCardConfigs.tsx +++ b/components/groups/page/list/card/GroupCardConfigs.tsx @@ -6,6 +6,7 @@ import type { ApiGroupDescription } from "@/generated/models/ApiGroupDescription import { ApiGroupFilterDirection } from "@/generated/models/ApiGroupFilterDirection"; import type { ApiGroupFull } from "@/generated/models/ApiGroupFull"; import { ApiGroupTdhInclusionStrategy } from "@/generated/models/ApiGroupTdhInclusionStrategy"; +import { ApiXTdhGrantStatus } from "@/generated/models/ApiXTdhGrantStatus"; import GroupCardConfig from "./GroupCardConfig"; export interface GroupCardConfigProps { @@ -19,11 +20,24 @@ export interface GroupCardConfigProps { const MANUAL_LIST_TOOLTIP = "Wallets explicitly listed in this group. Filter-matching wallets are not counted here."; +const GRANT_TOOLTIP = + "Identity must be a beneficiary of the selected xTDH grant."; + +const GRANT_STATUS_LABELS: Record = { + [ApiXTdhGrantStatus.Pending]: "PENDING", + [ApiXTdhGrantStatus.Failed]: "FAILED", + [ApiXTdhGrantStatus.Disabled]: "REVOKED", + [ApiXTdhGrantStatus.Granted]: "GRANTED", +}; +const NOW_MS_AT_MODULE_INIT = Date.now(); + export default function GroupCardConfigs({ group, }: { readonly group?: ApiGroupFull | undefined; }) { + const nowMs = NOW_MS_AT_MODULE_INIT; + const directionLabels: Record = { [ApiGroupFilterDirection.Received]: "from", [ApiGroupFilterDirection.Sent]: "to", @@ -151,6 +165,66 @@ export default function GroupCardConfigs({ }; }; + const toShortGrantId = (grantId: string): string => { + const normalizedId = grantId.trim(); + if (normalizedId.length <= 14) { + return normalizedId; + } + return `${normalizedId.slice(0, 7)}...${normalizedId.slice(-4)}`; + }; + + const getGrantStatusLabel = ( + grant: ApiGroupDescription["is_beneficiary_of_grant"], + now: number | null + ): string | null => { + if (grant?.status === undefined) { + return null; + } + + if (grant.status === ApiXTdhGrantStatus.Granted) { + if (now === null) { + return "GRANTED"; + } + const from = grant.valid_from ?? null; + const to = grant.valid_to ?? null; + + if (typeof to === "number" && to > 0 && to < now) { + return "ENDED"; + } + if (typeof from === "number" && from > now) { + return "SCHEDULED"; + } + return "ACTIVE"; + } + + return GRANT_STATUS_LABELS[grant.status]; + }; + + const getGrantConfig = ( + groupDescription: ApiGroupDescription + ): GroupCardConfigProps | null => { + const grantId = groupDescription.is_beneficiary_of_grant_id; + if (!grantId) { + return null; + } + + const statusLabel = getGrantStatusLabel( + groupDescription.is_beneficiary_of_grant, + nowMs + ); + const shortGrantId = toShortGrantId(grantId); + const value = statusLabel + ? `${statusLabel} (${shortGrantId})` + : shortGrantId; + + return { + key: GroupDescriptionType.XTDH_GRANT, + value, + label: "Grant", + tooltip: GRANT_TOOLTIP, + }; + }; + const getConfigs = (): GroupCardConfigProps[] => { if (!group) { return [ @@ -170,11 +244,13 @@ export default function GroupCardConfigs({ const repConfig = getRepConfig(rep); const cicConfig = getCicConfig(cic); const levelConfig = getLevelConfig(level); + const grantConfig = getGrantConfig(group.group); const walletsConfig = getWalletsConfig(identity_group_identities_count); if (tdhConfig) configs.push(tdhConfig); if (repConfig) configs.push(repConfig); if (cicConfig) configs.push(cicConfig); if (levelConfig) configs.push(levelConfig); + if (grantConfig) configs.push(grantConfig); configs.push(walletsConfig); return configs; diff --git a/entities/IGroup.ts b/entities/IGroup.ts index ffa17d1fd3..16625a784b 100644 --- a/entities/IGroup.ts +++ b/entities/IGroup.ts @@ -11,4 +11,5 @@ export enum GroupDescriptionType { LEVEL = "LEVEL", OWNS_NFTS = "OWNS_NFTS", WALLETS = "WALLETS", + XTDH_GRANT = "XTDH_GRANT", } diff --git a/generated/models/ApiSeizeSettings.ts b/generated/models/ApiSeizeSettings.ts index 3bbfd6b253..12f3bd5e22 100644 --- a/generated/models/ApiSeizeSettings.ts +++ b/generated/models/ApiSeizeSettings.ts @@ -17,6 +17,7 @@ export class ApiSeizeSettings { 'rememes_submission_tdh_threshold': number; 'all_drops_notifications_subscribers_limit': number; 'memes_wave_id': string | null; + 'curation_wave_id': string | null; static readonly discriminator: string | undefined = undefined; @@ -40,6 +41,12 @@ export class ApiSeizeSettings { "baseName": "memes_wave_id", "type": "string", "format": "" + }, + { + "name": "curation_wave_id", + "baseName": "curation_wave_id", + "type": "string", + "format": "" } ]; static getAttributeTypeMap() { diff --git a/hooks/useXtdhGrantsSearchQuery.ts b/hooks/useXtdhGrantsSearchQuery.ts new file mode 100644 index 0000000000..862ee8ba4d --- /dev/null +++ b/hooks/useXtdhGrantsSearchQuery.ts @@ -0,0 +1,166 @@ +"use client"; + +import { useMemo } from "react"; +import { + keepPreviousData, + type InfiniteData, + type UseInfiniteQueryResult, + useInfiniteQuery, +} from "@tanstack/react-query"; +import { QueryKey } from "@/components/react-query-wrapper/ReactQueryWrapper"; +import type { SortDirection } from "@/entities/ISort"; +import type { ApiXTdhGrant } from "@/generated/models/ApiXTdhGrant"; +import type { ApiXTdhGrantsPage } from "@/generated/models/ApiXTdhGrantsPage"; +import type { ApiXTdhGrantStatus } from "@/generated/models/ApiXTdhGrantStatus"; +import { commonApiFetch } from "@/services/api/common-api"; + +export type XtdhGrantSortField = + | "created_at" + | "valid_from" + | "valid_to" + | "rate"; + +interface UseXtdhGrantsSearchQueryParams { + readonly grantor?: string | null | undefined; + readonly targetCollectionName?: string | null | undefined; + readonly targetContract?: string | null | undefined; + readonly statuses?: readonly ApiXTdhGrantStatus[] | undefined; + readonly sortField?: XtdhGrantSortField | undefined; + readonly sortDirection: SortDirection; + readonly pageSize?: number | undefined; + readonly enabled?: boolean | undefined; +} + +type XtdhGrantsInfiniteData = InfiniteData; + +type UseXtdhGrantsSearchQueryResult = UseInfiniteQueryResult< + XtdhGrantsInfiniteData, + Error +> & { + readonly grants: ApiXTdhGrant[]; + readonly totalCount: number; + readonly errorMessage?: string | undefined; + readonly isEnabled: boolean; + readonly firstPage?: ApiXTdhGrantsPage | undefined; +}; + +const DEFAULT_PAGE = 1; +const DEFAULT_PAGE_SIZE = 20; +const MAX_PAGE_SIZE = 2000; +const DEFAULT_SORT_FIELD: XtdhGrantSortField = "created_at"; +const DEFAULT_STALE_TIME = 30_000; // 30 seconds + +const normalizeTextFilter = (value?: string | null): string => { + const normalized = value?.trim() ?? ""; + return normalized.length ? normalized : ""; +}; + +const normalizeStatuses = ( + statuses?: readonly ApiXTdhGrantStatus[] +): ApiXTdhGrantStatus[] => { + if (statuses === undefined || statuses.length === 0) { + return []; + } + return Array.from(new Set(statuses)).sort(); +}; + +export function useXtdhGrantsSearchQuery({ + grantor, + targetCollectionName, + targetContract, + statuses, + sortField = DEFAULT_SORT_FIELD, + sortDirection, + pageSize = DEFAULT_PAGE_SIZE, + enabled = true, +}: Readonly): UseXtdhGrantsSearchQueryResult { + const normalizedGrantor = normalizeTextFilter(grantor); + const normalizedTargetCollectionName = + normalizeTextFilter(targetCollectionName); + const normalizedTargetContract = normalizeTextFilter(targetContract); + const normalizedStatuses = normalizeStatuses(statuses); + const serializedStatuses = normalizedStatuses.join(","); + const normalizedPageSize = Math.min( + MAX_PAGE_SIZE, + Math.max(1, Math.floor(pageSize)) + ); + const isEnabled = enabled; + + const queryKey = useMemo( + () => [ + QueryKey.TDH_GRANTS, + "group-create-grants-search", + normalizedGrantor, + normalizedTargetCollectionName, + normalizedTargetContract, + serializedStatuses || "ALL", + sortField, + sortDirection, + normalizedPageSize, + ], + [ + normalizedGrantor, + normalizedTargetCollectionName, + normalizedTargetContract, + serializedStatuses, + sortField, + sortDirection, + normalizedPageSize, + ] + ); + + const query = useInfiniteQuery({ + queryKey, + queryFn: async ({ pageParam }: { pageParam?: number | undefined }) => { + const currentPage = pageParam ?? DEFAULT_PAGE; + const params: Record = { + page: currentPage.toString(), + page_size: normalizedPageSize.toString(), + sort: sortField, + sort_direction: sortDirection, + }; + + if (normalizedGrantor) { + params["grantor"] = normalizedGrantor; + } + if (normalizedTargetCollectionName) { + params["target_collection_name"] = normalizedTargetCollectionName; + } + if (normalizedTargetContract) { + params["target_contract"] = normalizedTargetContract; + } + if (serializedStatuses) { + params["status"] = serializedStatuses; + } + + return await commonApiFetch({ + endpoint: "xtdh/grants", + params, + }); + }, + initialPageParam: DEFAULT_PAGE, + getNextPageParam: (lastPage) => + lastPage.next ? lastPage.page + 1 : undefined, + enabled: isEnabled, + staleTime: DEFAULT_STALE_TIME, + placeholderData: keepPreviousData, + }); + + const firstPage = query.data?.pages[0]; + const grants = useMemo( + () => query.data?.pages.flatMap((pageData) => pageData.data) ?? [], + [query.data] + ); + const totalCount = firstPage?.count ?? 0; + const errorMessage = + query.error instanceof Error ? query.error.message : undefined; + + return { + ...query, + grants, + totalCount, + errorMessage, + isEnabled, + firstPage, + }; +} diff --git a/openapi.yaml b/openapi.yaml index edcf6bfa3e..f5b1e10939 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -9119,6 +9119,7 @@ components: - rememes_submission_tdh_threshold - all_drops_notifications_subscribers_limit - memes_wave_id + - curation_wave_id properties: rememes_submission_tdh_threshold: type: integer @@ -9129,6 +9130,9 @@ components: memes_wave_id: type: string nullable: true + curation_wave_id: + type: string + nullable: true ApiStartMultipartMediaUploadResponse: required: - upload_id diff --git a/package-lock.json b/package-lock.json index 38a6e2f0b0..1326887961 100644 --- a/package-lock.json +++ b/package-lock.json @@ -310,6 +310,7 @@ "node_modules/@babel/core": { "version": "7.28.5", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -852,6 +853,7 @@ "node_modules/@capacitor/core": { "version": "7.4.1", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -919,6 +921,7 @@ "node_modules/@coinbase/wallet-sdk": { "version": "4.3.6", "license": "Apache-2.0", + "peer": true, "dependencies": { "@noble/hashes": "1.4.0", "clsx": "1.2.1", @@ -1700,6 +1703,7 @@ "node_modules/@fortawesome/fontawesome-svg-core": { "version": "6.7.2", "license": "MIT", + "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.2" }, @@ -1741,6 +1745,7 @@ "node_modules/@gemini-wallet/core": { "version": "0.3.2", "license": "MIT", + "peer": true, "dependencies": { "@metamask/rpc-errors": "7.0.2", "eventemitter3": "5.0.1" @@ -3436,7 +3441,6 @@ "node_modules/@jridgewell/source-map": { "version": "0.3.11", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -4081,6 +4085,7 @@ }, "node_modules/@metamask/sdk": { "version": "0.33.1", + "peer": true, "dependencies": { "@babel/runtime": "^7.26.0", "@metamask/onboarding": "^1.0.1", @@ -4138,6 +4143,7 @@ "node_modules/@metamask/sdk/node_modules/cross-fetch": { "version": "4.1.0", "license": "MIT", + "peer": true, "dependencies": { "node-fetch": "^2.7.0" } @@ -4285,6 +4291,7 @@ "node_modules/@nestjs/common": { "version": "11.1.9", "license": "MIT", + "peer": true, "dependencies": { "file-type": "21.1.0", "iterare": "1.2.1", @@ -4495,6 +4502,7 @@ "node_modules/@noble/ciphers": { "version": "1.3.0", "license": "MIT", + "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -4643,6 +4651,7 @@ "node_modules/@opentelemetry/api": { "version": "1.9.0", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -4660,6 +4669,7 @@ "node_modules/@opentelemetry/context-async-hooks": { "version": "2.2.0", "license": "Apache-2.0", + "peer": true, "engines": { "node": "^18.19.0 || >=20.6.0" }, @@ -4670,6 +4680,7 @@ "node_modules/@opentelemetry/core": { "version": "2.2.0", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -4683,6 +4694,7 @@ "node_modules/@opentelemetry/instrumentation": { "version": "0.208.0", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/api-logs": "0.208.0", "import-in-the-middle": "^2.0.0", @@ -5022,6 +5034,7 @@ "node_modules/@opentelemetry/resources": { "version": "2.2.0", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" @@ -5036,6 +5049,7 @@ "node_modules/@opentelemetry/sdk-trace-base": { "version": "2.2.0", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", @@ -5051,6 +5065,7 @@ "node_modules/@opentelemetry/semantic-conventions": { "version": "1.38.0", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=14" } @@ -5696,6 +5711,7 @@ "version": "1.57.0", "devOptional": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright": "1.57.0" }, @@ -5709,6 +5725,7 @@ "node_modules/@popperjs/core": { "version": "2.11.8", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -7657,6 +7674,7 @@ "node_modules/@solana/kit": { "version": "3.0.3", "license": "MIT", + "peer": true, "dependencies": { "@solana/accounts": "3.0.3", "@solana/addresses": "3.0.3", @@ -8187,6 +8205,7 @@ "node_modules/@tanstack/query-core": { "version": "5.90.11", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -8195,6 +8214,7 @@ "node_modules/@tanstack/react-query": { "version": "5.90.11", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/query-core": "5.90.11" }, @@ -8377,8 +8397,7 @@ "node_modules/@types/aria-query": { "version": "5.0.4", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -8434,7 +8453,6 @@ "node_modules/@types/eslint": { "version": "9.6.1", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -8443,7 +8461,6 @@ "node_modules/@types/eslint-scope": { "version": "3.7.7", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -8727,6 +8744,7 @@ "node_modules/@types/node": { "version": "20.19.25", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -8771,6 +8789,7 @@ "node_modules/@types/react": { "version": "19.2.3", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -8778,6 +8797,7 @@ "node_modules/@types/react-dom": { "version": "19.2.3", "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -8871,6 +8891,7 @@ "version": "8.48.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.0", @@ -8907,6 +8928,7 @@ "version": "8.48.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", @@ -9501,7 +9523,6 @@ "node_modules/@wagmi/core/node_modules/zustand": { "version": "5.0.0", "license": "MIT", - "peer": true, "engines": { "node": ">=12.20.0" }, @@ -9583,6 +9604,7 @@ "node_modules/@walletconnect/ethereum-provider": { "version": "2.21.1", "license": "Apache-2.0", + "peer": true, "dependencies": { "@reown/appkit": "1.7.8", "@walletconnect/jsonrpc-http-connection": "1.0.8", @@ -10546,6 +10568,7 @@ "node_modules/@walletconnect/ethereum-provider/node_modules/ws": { "version": "8.18.0", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -10972,7 +10995,6 @@ "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -10980,23 +11002,19 @@ }, "node_modules/@webassemblyjs/floating-point-hex-parser": { "version": "1.13.2", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -11005,13 +11023,11 @@ }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.13.2", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -11022,7 +11038,6 @@ "node_modules/@webassemblyjs/ieee754": { "version": "1.13.2", "license": "MIT", - "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -11030,20 +11045,17 @@ "node_modules/@webassemblyjs/leb128": { "version": "1.13.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { "version": "1.13.2", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -11058,7 +11070,6 @@ "node_modules/@webassemblyjs/wasm-gen": { "version": "1.14.1", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -11070,7 +11081,6 @@ "node_modules/@webassemblyjs/wasm-opt": { "version": "1.14.1", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -11081,7 +11091,6 @@ "node_modules/@webassemblyjs/wasm-parser": { "version": "1.14.1", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -11094,7 +11103,6 @@ "node_modules/@webassemblyjs/wast-printer": { "version": "1.14.1", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -11106,13 +11114,11 @@ }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/abab": { "version": "2.0.6", @@ -11141,6 +11147,7 @@ "node_modules/acorn": { "version": "8.15.0", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -11167,7 +11174,6 @@ "node_modules/acorn-import-phases": { "version": "1.0.4", "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" }, @@ -11236,7 +11242,6 @@ "node_modules/ajv-formats": { "version": "2.1.1", "license": "MIT", - "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -11252,7 +11257,6 @@ "node_modules/ajv-formats/node_modules/ajv": { "version": "8.17.1", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -11266,8 +11270,7 @@ }, "node_modules/ajv-formats/node_modules/json-schema-traverse": { "version": "1.0.0", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ansi-escapes": { "version": "4.3.2", @@ -11640,6 +11643,7 @@ "node_modules/axios": { "version": "1.13.2", "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -11717,6 +11721,7 @@ "version": "1.0.0", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/types": "^7.26.0" } @@ -11953,6 +11958,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -12023,6 +12029,7 @@ "version": "4.0.9", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -12256,6 +12263,7 @@ "node_modules/chart.js": { "version": "4.5.1", "license": "MIT", + "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -12336,7 +12344,6 @@ "node_modules/chrome-trace-event": { "version": "1.0.4", "license": "MIT", - "peer": true, "engines": { "node": ">=6.0" } @@ -13365,8 +13372,7 @@ "node_modules/dom-accessibility-api": { "version": "0.5.16", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dom-helpers": { "version": "5.2.1", @@ -13480,6 +13486,7 @@ "node_modules/eciesjs": { "version": "0.4.16", "license": "MIT", + "peer": true, "dependencies": { "@ecies/ciphers": "^0.2.4", "@noble/ciphers": "^1.3.0", @@ -13519,7 +13526,8 @@ }, "node_modules/emoji-mart": { "version": "5.6.0", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/emoji-regex": { "version": "10.6.0", @@ -13602,7 +13610,6 @@ "node_modules/enhanced-resolve": { "version": "5.18.3", "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -13745,8 +13752,7 @@ }, "node_modules/es-module-lexer": { "version": "1.7.0", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -13898,6 +13904,7 @@ "version": "9.39.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -14092,6 +14099,7 @@ "version": "2.32.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -14754,7 +14762,8 @@ }, "node_modules/eventemitter2": { "version": "6.4.9", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/eventemitter3": { "version": "5.0.1", @@ -14917,8 +14926,7 @@ "url": "https://opencollective.com/fastify" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/fastest-stable-stringify": { "version": "2.0.2", @@ -15439,8 +15447,7 @@ }, "node_modules/glob-to-regexp": { "version": "0.4.1", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/glob/node_modules/minimatch": { "version": "10.1.1", @@ -16697,7 +16704,6 @@ "node_modules/isomorphic.js": { "version": "0.2.5", "license": "MIT", - "peer": true, "funding": { "type": "GitHub Sponsors ❤", "url": "https://github.com/sponsors/dmonad" @@ -20078,7 +20084,6 @@ "node_modules/lib0": { "version": "0.2.114", "license": "MIT", - "peer": true, "dependencies": { "isomorphic.js": "^0.2.4" }, @@ -20161,7 +20166,6 @@ "node_modules/loader-runner": { "version": "4.3.1", "license": "MIT", - "peer": true, "engines": { "node": ">=6.11.5" }, @@ -20246,7 +20250,6 @@ "version": "1.5.0", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -21348,6 +21351,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", @@ -22522,6 +22526,7 @@ "node_modules/porto": { "version": "0.2.35", "license": "MIT", + "peer": true, "dependencies": { "hono": "^4.10.3", "idb-keyval": "^6.2.1", @@ -22668,6 +22673,7 @@ "node_modules/porto/node_modules/zod": { "version": "4.1.13", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -22696,6 +22702,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -22832,6 +22839,7 @@ "version": "3.7.4", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -22923,7 +22931,6 @@ "version": "27.5.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -22937,7 +22944,6 @@ "version": "5.2.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -22948,8 +22954,7 @@ "node_modules/pretty-format/node_modules/react-is": { "version": "17.0.2", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/prismjs": { "version": "1.30.0", @@ -23006,6 +23011,7 @@ "node_modules/prop-types": { "version": "15.8.1", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -23292,7 +23298,6 @@ "node_modules/randombytes": { "version": "2.1.0", "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -23300,6 +23305,7 @@ "node_modules/react": { "version": "19.2.3", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -23344,6 +23350,7 @@ "node_modules/react-dom": { "version": "19.2.3", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -23397,6 +23404,7 @@ "node_modules/react-redux": { "version": "9.2.0", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -23561,6 +23569,7 @@ "node_modules/readable-stream": { "version": "3.6.2", "license": "MIT", + "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -23611,7 +23620,8 @@ }, "node_modules/redux": { "version": "5.0.1", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -23633,7 +23643,8 @@ }, "node_modules/reflect-metadata": { "version": "0.2.2", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", @@ -23791,7 +23802,6 @@ "node_modules/require-from-string": { "version": "2.0.2", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -23920,6 +23930,7 @@ "node_modules/rollup": { "version": "4.53.3", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -24043,6 +24054,7 @@ "node_modules/rxjs": { "version": "7.8.2", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -24135,6 +24147,7 @@ "node_modules/sass": { "version": "1.94.2", "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -24192,7 +24205,6 @@ "node_modules/schema-utils": { "version": "4.3.3", "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -24225,7 +24237,6 @@ "node_modules/schema-utils/node_modules/ajv-keywords": { "version": "5.1.0", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -24235,8 +24246,7 @@ }, "node_modules/schema-utils/node_modules/json-schema-traverse": { "version": "1.0.0", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/screenfull": { "version": "5.2.0", @@ -24291,7 +24301,6 @@ "node_modules/serialize-javascript": { "version": "6.0.2", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "randombytes": "^2.1.0" } @@ -24593,6 +24602,7 @@ "node_modules/socket.io-client": { "version": "4.8.1", "license": "MIT", + "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", @@ -25238,6 +25248,7 @@ "node_modules/tailwindcss": { "version": "3.4.18", "license": "MIT", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -25297,6 +25308,7 @@ "node_modules/tailwindcss/node_modules/jiti": { "version": "1.21.7", "license": "MIT", + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -25344,7 +25356,6 @@ "node_modules/tapable": { "version": "2.3.0", "license": "MIT", - "peer": true, "engines": { "node": ">=6" }, @@ -25356,7 +25367,6 @@ "node_modules/terser": { "version": "5.44.1", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -25373,7 +25383,6 @@ "node_modules/terser-webpack-plugin": { "version": "5.3.14", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -25406,7 +25415,6 @@ "node_modules/terser-webpack-plugin/node_modules/jest-worker": { "version": "27.5.1", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -25419,7 +25427,6 @@ "node_modules/terser-webpack-plugin/node_modules/supports-color": { "version": "8.1.1", "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -25432,13 +25439,11 @@ }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/terser/node_modules/source-map-support": { "version": "0.5.21", "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -25525,7 +25530,8 @@ }, "node_modules/three": { "version": "0.163.0", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/throttle-debounce": { "version": "3.0.1", @@ -25796,12 +25802,14 @@ }, "node_modules/tslib": { "version": "2.8.1", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsx": { "version": "4.21.0", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -26397,6 +26405,7 @@ "node_modules/typescript": { "version": "5.9.3", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -26861,6 +26870,7 @@ "version": "5.0.10", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -26910,6 +26920,7 @@ "node_modules/valtio": { "version": "2.1.7", "license": "MIT", + "peer": true, "dependencies": { "proxy-compare": "^3.0.1" }, @@ -26962,6 +26973,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", @@ -27088,6 +27100,7 @@ "node_modules/wagmi": { "version": "2.19.5", "license": "MIT", + "peer": true, "dependencies": { "@wagmi/connectors": "6.2.0", "@wagmi/core": "2.22.1", @@ -27139,6 +27152,7 @@ "node_modules/wagmi/node_modules/@wagmi/core": { "version": "2.22.1", "license": "MIT", + "peer": true, "dependencies": { "eventemitter3": "5.0.1", "mipd": "0.0.7", @@ -27164,6 +27178,7 @@ "node_modules/wagmi/node_modules/use-sync-external-store": { "version": "1.4.0", "license": "MIT", + "peer": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -27221,7 +27236,6 @@ "node_modules/watchpack": { "version": "2.4.4", "license": "MIT", - "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -27256,7 +27270,6 @@ "node_modules/webpack": { "version": "5.103.0", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -27314,7 +27327,6 @@ "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -27326,7 +27338,6 @@ "node_modules/webpack/node_modules/estraverse": { "version": "4.3.0", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -27517,6 +27528,7 @@ "node_modules/ws": { "version": "8.18.3", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -27589,6 +27601,7 @@ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "devOptional": true, "license": "ISC", + "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -27651,6 +27664,7 @@ "node_modules/zod": { "version": "3.25.76", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/services/groups/groupMutations.ts b/services/groups/groupMutations.ts index 2cfbe784b8..2d11c085aa 100644 --- a/services/groups/groupMutations.ts +++ b/services/groups/groupMutations.ts @@ -32,22 +32,27 @@ export const toErrorMessage = (error: unknown): string => { const sanitiseGroupPayload = ( payload: ApiCreateGroup, name: string -): ApiCreateGroup => ({ - ...payload, - name, - group: { - ...payload.group, - owns_nfts: [...payload.group.owns_nfts], - identity_addresses: - payload.group.identity_addresses?.length - ? [...payload.group.identity_addresses] - : null, - excluded_identity_addresses: - payload.group.excluded_identity_addresses?.length - ? [...payload.group.excluded_identity_addresses] - : null, - }, -}); +): ApiCreateGroup => { + const identityAddresses = payload.group.identity_addresses; + const excludedIdentityAddresses = payload.group.excluded_identity_addresses; + + return { + ...payload, + name, + group: { + ...payload.group, + owns_nfts: [...payload.group.owns_nfts], + identity_addresses: + identityAddresses && identityAddresses.length > 0 + ? [...identityAddresses] + : null, + excluded_identity_addresses: + excludedIdentityAddresses && excludedIdentityAddresses.length > 0 + ? [...excludedIdentityAddresses] + : null, + }, + }; +}; export const validateGroupPayload = ( payload: ApiCreateGroup @@ -80,6 +85,9 @@ export const validateGroupPayload = ( payload.group.cic.max !== null || payload.group.cic.user_identity !== null; const hasNfts = payload.group.owns_nfts.length > 0; + const hasGrantBeneficiary = + typeof payload.group.is_beneficiary_of_grant_id === "string" && + payload.group.is_beneficiary_of_grant_id.trim().length > 0; if ( !( @@ -89,7 +97,8 @@ export const validateGroupPayload = ( hasTdh || hasRep || hasCic || - hasNfts + hasNfts || + hasGrantBeneficiary ) ) { issues.push("NO_FILTERS"); From 3e6d551ba94c1076e8a80e182d2a0f6c202c6ba6 Mon Sep 17 00:00:00 2001 From: Simo Date: Tue, 17 Feb 2026 15:01:08 -0400 Subject: [PATCH 2/8] wip Signed-off-by: Simo --- .../page/create/config/GroupCreateConfig.tsx | 4 +- .../xtdh-grant/GroupCreateXtdhGrant.tsx | 244 +++++++----------- .../xtdh-grant/GroupCreateXtdhGrantModal.tsx | 162 +++--------- hooks/useXtdhGrantsSearchQuery.ts | 13 +- 4 files changed, 133 insertions(+), 290 deletions(-) diff --git a/components/groups/page/create/config/GroupCreateConfig.tsx b/components/groups/page/create/config/GroupCreateConfig.tsx index 3839289121..d970b87d24 100644 --- a/components/groups/page/create/config/GroupCreateConfig.tsx +++ b/components/groups/page/create/config/GroupCreateConfig.tsx @@ -63,12 +63,12 @@ export default function GroupCreateConfig({ + + - -
diff --git a/components/groups/page/create/config/xtdh-grant/GroupCreateXtdhGrant.tsx b/components/groups/page/create/config/xtdh-grant/GroupCreateXtdhGrant.tsx index 40010e793e..59a66001dd 100644 --- a/components/groups/page/create/config/xtdh-grant/GroupCreateXtdhGrant.tsx +++ b/components/groups/page/create/config/xtdh-grant/GroupCreateXtdhGrant.tsx @@ -29,12 +29,10 @@ export default function GroupCreateXtdhGrant({ const normalizedGrantId = beneficiaryGrantId?.trim() ?? ""; const hasSelectedGrant = normalizedGrantId.length > 0; - const [isManuallyEnabled, setIsManuallyEnabled] = useState(hasSelectedGrant); const [lookupGrantId, setLookupGrantId] = useState( hasSelectedGrant ? normalizedGrantId : null ); const [isModalOpen, setIsModalOpen] = useState(false); - const isEnabled = isManuallyEnabled || hasSelectedGrant; useDebounce( () => { @@ -46,7 +44,7 @@ export default function GroupCreateXtdhGrant({ const { grant, isFetching, isError, errorMessage } = useXtdhGrantQuery({ grantId: lookupGrantId, - enabled: isEnabled && !!lookupGrantId, + enabled: !!lookupGrantId, }); const grantStatusLabel = useMemo(() => { @@ -62,27 +60,14 @@ export default function GroupCreateXtdhGrant({ const showNonGrantedWarning = grant?.status !== undefined && isSelectableNonGrantedStatus(grant.status); - const showLookupError = Boolean(isEnabled && lookupGrantId && isError); - - const onEnabledChange = (nextEnabled: boolean) => { - setIsManuallyEnabled(nextEnabled); - if (!nextEnabled) { - setLookupGrantId(null); - setBeneficiaryGrantId(null); - setIsModalOpen(false); - } - }; + const showLookupError = Boolean(lookupGrantId && isError); const onInputChange = (nextValue: string) => { const normalized = nextValue.trim(); setBeneficiaryGrantId(normalized.length ? normalized : null); - if (!normalized.length) { - setIsManuallyEnabled(false); - } }; const onClearSelection = () => { - setIsManuallyEnabled(false); setLookupGrantId(null); setBeneficiaryGrantId(null); }; @@ -95,146 +80,104 @@ export default function GroupCreateXtdhGrant({ return (
-
-
-

- xTDH Grant Beneficiary -

-

- Optionally require identities to be beneficiaries of a selected xTDH - grant. -

-
-