From 4cf61b1e4623eb30603051eb2738d72f0b104f6a Mon Sep 17 00:00:00 2001 From: ragnep Date: Fri, 27 Feb 2026 12:16:19 +0200 Subject: [PATCH 01/10] wip Signed-off-by: ragnep --- components/common/OverlappingAvatars.tsx | 56 +++++++++---- .../user/rep/header/UserPageRepHeader.tsx | 4 +- .../user/rep/new-rep/GrantRepDialog.tsx | 4 +- .../rep/new-rep/UserPageRepNewRepSearch.tsx | 83 +++++++++---------- .../UserPageRepNewRepSearchDropdown.tsx | 83 ++++++++++--------- 5 files changed, 129 insertions(+), 101 deletions(-) diff --git a/components/common/OverlappingAvatars.tsx b/components/common/OverlappingAvatars.tsx index 63003fb60c..a1c9bb05ea 100644 --- a/components/common/OverlappingAvatars.tsx +++ b/components/common/OverlappingAvatars.tsx @@ -5,7 +5,7 @@ import { TOOLTIP_STYLES } from "@/helpers/tooltip.helpers"; import useIsTouchDevice from "@/hooks/useIsTouchDevice"; import Link from "next/link"; import type { MouseEvent, ReactNode } from "react"; -import { useId } from "react"; +import { useId, useState } from "react"; import { Tooltip } from "react-tooltip"; interface OverlappingAvatarItem { @@ -34,6 +34,41 @@ const SIZE_CLASS = { md: "tw-h-7 tw-w-7", } as const; +function AvatarContent({ + pfpUrl, + ariaLabel, + fallback, + avatarRing, +}: { + pfpUrl: string | null; + ariaLabel?: string; + fallback?: string; + avatarRing: string; +}) { + const [imgError, setImgError] = useState(false); + + if (!pfpUrl || imgError) { + return ( +
+ + {fallback ?? "?"} + +
+ ); + } + + return ( + {ariaLabel setImgError(true)} + className={`tw-h-full tw-w-full tw-flex-shrink-0 tw-rounded-full ${avatarRing}`} + /> + ); +} + export default function OverlappingAvatars({ items, maxCount = 5, @@ -60,20 +95,13 @@ export default function OverlappingAvatars({ else if (index === slice.length - 1) transformOrigin = "right center"; else transformOrigin = "center center"; - const content = item.pfpUrl ? ( - {item.ariaLabel - ) : ( -
- - {item.fallback ?? "?"} - -
); const showTooltip = diff --git a/components/user/rep/header/UserPageRepHeader.tsx b/components/user/rep/header/UserPageRepHeader.tsx index 05148b3026..44b9936017 100644 --- a/components/user/rep/header/UserPageRepHeader.tsx +++ b/components/user/rep/header/UserPageRepHeader.tsx @@ -96,10 +96,10 @@ export default function UserPageRepHeader({ - {selectedCategory && ( - - )} diff --git a/components/user/rep/new-rep/UserPageRepNewRepSearchDropdown.tsx b/components/user/rep/new-rep/UserPageRepNewRepSearchDropdown.tsx index 1cd5558d18..cdc765a1b5 100644 --- a/components/user/rep/new-rep/UserPageRepNewRepSearchDropdown.tsx +++ b/components/user/rep/new-rep/UserPageRepNewRepSearchDropdown.tsx @@ -1,3 +1,4 @@ +import CircleLoader from "@/components/distribution-plan-tool/common/CircleLoader"; import { RepSearchState } from "./UserPageRepNewRepSearch"; export default function UserPageRepNewRepSearchDropdown({ @@ -13,46 +14,50 @@ export default function UserPageRepNewRepSearchDropdown({ readonly minSearchLength: number; readonly maxSearchLength: number; }) { - return ( -
-
-
    - {state === RepSearchState.MIN_LENGTH_ERROR && ( -
  • - Type at least {minSearchLength} characters -
  • - )} - {state === RepSearchState.MAX_LENGTH_ERROR && ( -
  • - Type at most {maxSearchLength} characters -
  • - )} - {state === RepSearchState.LOADING && ( -
  • - Loading... -
  • - )} - {state === RepSearchState.HAVE_RESULTS && - categories.map((category) => ( -
  • - -
  • - ))} -
+ if (state === RepSearchState.MIN_LENGTH_ERROR) { + return ( +

+ Type at least {minSearchLength} characters +

+ ); + } + + if (state === RepSearchState.MAX_LENGTH_ERROR) { + return ( +

+ Type at most {maxSearchLength} characters +

+ ); + } + + if (state === RepSearchState.LOADING) { + return ( +
+ + Searching...
+ ); + } + + if (!categories.length) { + return null; + } + + return ( +
+ {categories.map((category) => ( + + ))}
); } From 68f3298ab5422f4f0822adef8089e909a8702ba8 Mon Sep 17 00:00:00 2001 From: ragnep Date: Fri, 27 Feb 2026 12:21:01 +0200 Subject: [PATCH 02/10] wip Signed-off-by: ragnep --- components/common/OverlappingAvatars.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/components/common/OverlappingAvatars.tsx b/components/common/OverlappingAvatars.tsx index a1c9bb05ea..32f0442541 100644 --- a/components/common/OverlappingAvatars.tsx +++ b/components/common/OverlappingAvatars.tsx @@ -3,6 +3,7 @@ import { getScaledImageUri, ImageScale } from "@/helpers/image.helpers"; import { TOOLTIP_STYLES } from "@/helpers/tooltip.helpers"; import useIsTouchDevice from "@/hooks/useIsTouchDevice"; +import Image from "next/image"; import Link from "next/link"; import type { MouseEvent, ReactNode } from "react"; import { useId, useState } from "react"; @@ -41,8 +42,8 @@ function AvatarContent({ avatarRing, }: { pfpUrl: string | null; - ariaLabel?: string; - fallback?: string; + ariaLabel?: string | undefined; + fallback?: string | undefined; avatarRing: string; }) { const [imgError, setImgError] = useState(false); @@ -58,13 +59,14 @@ function AvatarContent({ } return ( - {ariaLabel setImgError(true)} - className={`tw-h-full tw-w-full tw-flex-shrink-0 tw-rounded-full ${avatarRing}`} + className={`tw-object-cover tw-rounded-full ${avatarRing}`} /> ); } From 3cecf857b3b1ef943d146fb197375cb26b04e13d Mon Sep 17 00:00:00 2001 From: ragnep Date: Fri, 27 Feb 2026 12:22:44 +0200 Subject: [PATCH 03/10] wip Signed-off-by: ragnep --- components/common/OverlappingAvatars.tsx | 8 ++++---- components/user/rep/new-rep/UserPageRepNewRepSearch.tsx | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/components/common/OverlappingAvatars.tsx b/components/common/OverlappingAvatars.tsx index 32f0442541..0bcae409d1 100644 --- a/components/common/OverlappingAvatars.tsx +++ b/components/common/OverlappingAvatars.tsx @@ -41,10 +41,10 @@ function AvatarContent({ fallback, avatarRing, }: { - pfpUrl: string | null; - ariaLabel?: string | undefined; - fallback?: string | undefined; - avatarRing: string; + readonly pfpUrl: string | null; + readonly ariaLabel?: string | undefined; + readonly fallback?: string | undefined; + readonly avatarRing: string; }) { const [imgError, setImgError] = useState(false); diff --git a/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx b/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx index 50ca56e98b..9e0fb00ce7 100644 --- a/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx +++ b/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx @@ -319,8 +319,10 @@ export default function UserPageRepNewRepSearch({
{isOpen && !selectedCategory && (
e.stopPropagation()}> + onClick={(e) => e.stopPropagation()} + onKeyDown={(e) => e.stopPropagation()}> Date: Fri, 27 Feb 2026 12:52:46 +0200 Subject: [PATCH 04/10] wip Signed-off-by: ragnep --- components/user/rep/new-rep/UserPageRepNewRepSearch.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx b/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx index 9e0fb00ce7..50ca56e98b 100644 --- a/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx +++ b/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx @@ -319,10 +319,8 @@ export default function UserPageRepNewRepSearch({
{isOpen && !selectedCategory && (
e.stopPropagation()} - onKeyDown={(e) => e.stopPropagation()}> + onClick={(e) => e.stopPropagation()}> Date: Fri, 27 Feb 2026 13:45:21 +0200 Subject: [PATCH 05/10] wip Signed-off-by: ragnep --- .../UserPageRepNewRepSearchDropdown.test.tsx | 2 +- .../user/rep/new-rep/GrantRepDialog.tsx | 26 +-- .../user/rep/new-rep/UserPageRepNewRep.tsx | 7 +- .../rep/new-rep/UserPageRepNewRepSearch.tsx | 173 +++++++++--------- .../UserPageRepNewRepSearchDropdown.tsx | 12 +- .../user/rep/new-rep/rep-search-types.ts | 6 + 6 files changed, 116 insertions(+), 110 deletions(-) create mode 100644 components/user/rep/new-rep/rep-search-types.ts diff --git a/__tests__/components/user/rep/new-rep/UserPageRepNewRepSearchDropdown.test.tsx b/__tests__/components/user/rep/new-rep/UserPageRepNewRepSearchDropdown.test.tsx index 8f87794e05..f0161d92dd 100644 --- a/__tests__/components/user/rep/new-rep/UserPageRepNewRepSearchDropdown.test.tsx +++ b/__tests__/components/user/rep/new-rep/UserPageRepNewRepSearchDropdown.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import React from "react"; import UserPageRepNewRepSearchDropdown from "@/components/user/rep/new-rep/UserPageRepNewRepSearchDropdown"; -import { RepSearchState } from "@/components/user/rep/new-rep/UserPageRepNewRepSearch"; +import { RepSearchState } from "@/components/user/rep/new-rep/rep-search-types"; describe("UserPageRepNewRepSearchDropdown", () => { it("renders categories and handles selection", async () => { diff --git a/components/user/rep/new-rep/GrantRepDialog.tsx b/components/user/rep/new-rep/GrantRepDialog.tsx index 9aeebb6696..0347135f87 100644 --- a/components/user/rep/new-rep/GrantRepDialog.tsx +++ b/components/user/rep/new-rep/GrantRepDialog.tsx @@ -23,24 +23,14 @@ export default function GrantRepDialog({ onClose={onClose} tabletModal > -
- - - -
- -
-
+ + + ); } diff --git a/components/user/rep/new-rep/UserPageRepNewRep.tsx b/components/user/rep/new-rep/UserPageRepNewRep.tsx index b156723a55..10815e6b52 100644 --- a/components/user/rep/new-rep/UserPageRepNewRep.tsx +++ b/components/user/rep/new-rep/UserPageRepNewRep.tsx @@ -1,8 +1,6 @@ "use client"; -import type { - ApiProfileRepRatesState, -} from "@/entities/IProfile"; +import type { ApiProfileRepRatesState } from "@/entities/IProfile"; import UserPageRepNewRepSearch from "./UserPageRepNewRepSearch"; import type { ApiIdentity } from "@/generated/models/ApiIdentity"; @@ -10,10 +8,12 @@ export default function UserPageRepNewRep({ profile, repRates, onSuccess, + onCancel, }: { readonly profile: ApiIdentity; readonly repRates: ApiProfileRepRatesState | null; readonly onSuccess?: () => void; + readonly onCancel?: () => void; }) { const searchProps = onSuccess ? { onSuccess } : {}; @@ -21,6 +21,7 @@ export default function UserPageRepNewRep({ ); diff --git a/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx b/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx index 50ca56e98b..47f0afe116 100644 --- a/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx +++ b/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx @@ -2,11 +2,12 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { commonApiFetch, commonApiPost } from "@/services/api/common-api"; -import { useContext, useEffect, useRef, useState } from "react"; +import { useContext, useMemo, useRef, useState } from "react"; import { useClickAway, useDebounce, useKeyPressEvent } from "react-use"; -import { AnimatePresence } from "framer-motion"; +import { AnimatePresence, motion } from "framer-motion"; import type { ApiProfileRepRatesState } from "@/entities/IProfile"; import UserPageRepNewRepSearchDropdown from "./UserPageRepNewRepSearchDropdown"; +import { RepSearchState } from "./rep-search-types"; import CircleLoader from "@/components/distribution-plan-tool/common/CircleLoader"; import UserPageRepNewRepError from "./UserPageRepNewRepError"; import { @@ -15,7 +16,10 @@ import { } from "@/components/react-query-wrapper/ReactQueryWrapper"; import { AuthContext } from "@/components/auth/Auth"; import type { ApiIdentity } from "@/generated/models/ApiIdentity"; -import { formatNumberWithCommas, getStringAsNumberOrZero } from "@/helpers/Helpers"; +import { + formatNumberWithCommas, + getStringAsNumberOrZero, +} from "@/helpers/Helpers"; import UserRateAdjustmentHelper from "@/components/user/utils/rate/UserRateAdjustmentHelper"; import UserPageRateInput from "@/components/user/utils/rate/UserPageRateInput"; import { useRepAllocation } from "@/hooks/useRepAllocation"; @@ -41,21 +45,16 @@ const getErrorMessage = (error: unknown): string => { return "Something went wrong."; }; -export enum RepSearchState { - MIN_LENGTH_ERROR = "MIN_LENGTH_ERROR", - MAX_LENGTH_ERROR = "MAX_LENGTH_ERROR", - LOADING = "LOADING", - HAVE_RESULTS = "HAVE_RESULTS", -} - export default function UserPageRepNewRepSearch({ repRates, profile, onSuccess, + onCancel, }: { readonly repRates: ApiProfileRepRatesState | null; readonly profile: ApiIdentity; - readonly onSuccess?: () => void; + readonly onSuccess?: (() => void) | undefined; + readonly onCancel?: (() => void) | undefined; }) { const { onProfileRepModify } = useContext(ReactQueryWrapperContext); const { requestAuth, setToast, connectedProfile, activeProfileProxy } = @@ -78,17 +77,9 @@ export default function UserPageRepNewRepSearch({ [repSearch] ); - const [matchingSearchLength, setMatchingSearchLength] = useState( + const matchingSearchLength = debouncedValue.length >= SEARCH_LENGTH.MIN && - debouncedValue.length <= SEARCH_LENGTH.MAX - ); - - useEffect(() => { - setMatchingSearchLength( - debouncedValue.length >= SEARCH_LENGTH.MIN && - debouncedValue.length <= SEARCH_LENGTH.MAX - ); - }, [debouncedValue]); + debouncedValue.length <= SEARCH_LENGTH.MAX; const { isFetching, data: categories } = useQuery({ queryKey: [QueryKey.REP_CATEGORIES_SEARCH, debouncedValue], @@ -107,12 +98,16 @@ export default function UserPageRepNewRepSearch({ category: selectedCategory, }); - // Pre-fill amountStr when category is selected - useEffect(() => { + // Pre-fill amountStr when repState changes (e.g. after category selection). + // Uses the React "adjust state during render" pattern so the user can still + // freely edit the value after pre-fill. + const [prevRepState, setPrevRepState] = useState(repState); + if (repState !== prevRepState) { + setPrevRepState(repState); if (repState) { setAmountStr(`${repState.rater_contribution}`); } - }, [repState]); + } const amountNum = getStringAsNumberOrZero(amountStr); const isValidValue = @@ -147,8 +142,8 @@ export default function UserPageRepNewRepSearch({ setRepSearch(rep); setErrorMsg(null); setIsOpen(false); - } catch (error: any) { - setErrorMsg(error); + } catch (error: unknown) { + setErrorMsg(getErrorMessage(error)); } finally { setCheckingAvailability(false); } @@ -163,7 +158,7 @@ export default function UserPageRepNewRepSearch({ category: string; }) => await commonApiPost<{ amount: number; category: string }, void>({ - endpoint: `profiles/${profile?.query}/rep/rating`, + endpoint: `profiles/${profile.query ?? ""}/rep/rating`, body: { amount, category }, }), onSuccess: () => { @@ -202,43 +197,31 @@ export default function UserPageRepNewRepSearch({ } }; - const onSearchSubmit = async (event: React.FormEvent) => { + const onSearchSubmit = (event: React.FormEvent) => { event.preventDefault(); if (!debouncedValue) return; - onRepSelect(debouncedValue); + void onRepSelect(debouncedValue); }; - const [categoriesToDisplay, setCategoriesToDisplay] = useState([]); - useEffect(() => { + const categoriesToDisplay = useMemo(() => { const items: string[] = []; if (matchingSearchLength) { items.push(debouncedValue); } - - if (categories?.length) { - items.push( - ...categories.filter((category) => category !== debouncedValue) - ); + if ((categories?.length ?? 0) > 0) { + items.push(...categories!.filter((c) => c !== debouncedValue)); } - - setCategoriesToDisplay(items); + return items; }, [debouncedValue, categories, matchingSearchLength]); - const [repSearchState, setRepSearchState] = useState( - RepSearchState.MIN_LENGTH_ERROR - ); - - useEffect(() => { - if (debouncedValue.length < SEARCH_LENGTH.MIN) { - setRepSearchState(RepSearchState.MIN_LENGTH_ERROR); - } else if (debouncedValue.length > SEARCH_LENGTH.MAX) { - setRepSearchState(RepSearchState.MAX_LENGTH_ERROR); - } else if (isFetching) { - setRepSearchState(RepSearchState.LOADING); - } else { - setRepSearchState(RepSearchState.HAVE_RESULTS); - } - }, [debouncedValue, isFetching, categories]); + const repSearchState = useMemo(() => { + if (debouncedValue.length < SEARCH_LENGTH.MIN) + return RepSearchState.MIN_LENGTH_ERROR; + if (debouncedValue.length > SEARCH_LENGTH.MAX) + return RepSearchState.MAX_LENGTH_ERROR; + if (isFetching) return RepSearchState.LOADING; + return RepSearchState.HAVE_RESULTS; + }, [debouncedValue, isFetching]); const handleRepSearchChange = ( event: React.ChangeEvent @@ -252,24 +235,27 @@ export default function UserPageRepNewRepSearch({ }; const isGrantDisabled = - !selectedCategory || !amountStr || !haveChanged || !isValidValue || mutating; + !selectedCategory || + !amountStr || + !haveChanged || + !isValidValue || + mutating; return ( -
+
-
-
-
+
+
+
Your available Rep: {formatNumberWithCommas(heroAvailableRep)} - + Your Rep assigned to{" "} @@ -283,16 +269,18 @@ export default function UserPageRepNewRepSearch({
-
+
-
+ className="tw-relative tw-w-full" + > +
+
)}
- {isOpen && !selectedCategory && ( -
e.stopPropagation()}> - -
- )} + + {isOpen && !selectedCategory && ( + +
e.stopPropagation()} + > + +
+
+ )} +
@@ -352,7 +351,7 @@ export default function UserPageRepNewRepSearch({ )}
-
+
+ {onCancel && ( + + )}
diff --git a/components/user/rep/new-rep/UserPageRepNewRepSearchDropdown.tsx b/components/user/rep/new-rep/UserPageRepNewRepSearchDropdown.tsx index cdc765a1b5..0acdf5e48c 100644 --- a/components/user/rep/new-rep/UserPageRepNewRepSearchDropdown.tsx +++ b/components/user/rep/new-rep/UserPageRepNewRepSearchDropdown.tsx @@ -1,5 +1,5 @@ import CircleLoader from "@/components/distribution-plan-tool/common/CircleLoader"; -import { RepSearchState } from "./UserPageRepNewRepSearch"; +import { RepSearchState } from "./rep-search-types"; export default function UserPageRepNewRepSearchDropdown({ categories, @@ -16,7 +16,7 @@ export default function UserPageRepNewRepSearchDropdown({ }) { if (state === RepSearchState.MIN_LENGTH_ERROR) { return ( -

+

Type at least {minSearchLength} characters

); @@ -24,7 +24,7 @@ export default function UserPageRepNewRepSearchDropdown({ if (state === RepSearchState.MAX_LENGTH_ERROR) { return ( -

+

Type at most {maxSearchLength} characters

); @@ -32,7 +32,7 @@ export default function UserPageRepNewRepSearchDropdown({ if (state === RepSearchState.LOADING) { return ( -
+
Searching...
@@ -44,7 +44,7 @@ export default function UserPageRepNewRepSearchDropdown({ } return ( -
+
{categories.map((category) => ( diff --git a/components/user/rep/new-rep/rep-search-types.ts b/components/user/rep/new-rep/rep-search-types.ts new file mode 100644 index 0000000000..53170c1c50 --- /dev/null +++ b/components/user/rep/new-rep/rep-search-types.ts @@ -0,0 +1,6 @@ +export enum RepSearchState { + MIN_LENGTH_ERROR = "MIN_LENGTH_ERROR", + MAX_LENGTH_ERROR = "MAX_LENGTH_ERROR", + LOADING = "LOADING", + HAVE_RESULTS = "HAVE_RESULTS", +} From d3b581fd9eb75a1088db57d48c9d377855997aca Mon Sep 17 00:00:00 2001 From: ragnep Date: Fri, 27 Feb 2026 14:10:18 +0200 Subject: [PATCH 06/10] wip Signed-off-by: ragnep --- .../user/rep/new-rep/UserPageRepNewRepSearch.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx b/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx index 47f0afe116..6209753349 100644 --- a/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx +++ b/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx @@ -128,7 +128,8 @@ export default function UserPageRepNewRepSearch({ const [checkingAvailability, setCheckingAvailability] = useState(false); const onRepSelect = async (rep: string) => { - if (!matchingSearchLength) return; + if (rep.length < SEARCH_LENGTH.MIN || rep.length > SEARCH_LENGTH.MAX) + return; if (checkingAvailability) return; setCheckingAvailability(true); try { @@ -180,7 +181,7 @@ export default function UserPageRepNewRepSearch({ }); const onGrantRep = async () => { - if (mutating || !selectedCategory || !amountStr) return; + if (mutating || !selectedCategory || !amountStr || !profile.query) return; const amount = Number.parseInt(amountStr, 10); if (Number.isNaN(amount)) return; if (!haveChanged) return; @@ -199,8 +200,8 @@ export default function UserPageRepNewRepSearch({ const onSearchSubmit = (event: React.FormEvent) => { event.preventDefault(); - if (!debouncedValue) return; - void onRepSelect(debouncedValue); + if (!repSearch) return; + void onRepSelect(repSearch); }; const categoriesToDisplay = useMemo(() => { @@ -317,6 +318,7 @@ export default function UserPageRepNewRepSearch({
e.stopPropagation()} + onKeyDown={(e) => e.stopPropagation()} > Date: Fri, 27 Feb 2026 14:13:50 +0200 Subject: [PATCH 07/10] wip Signed-off-by: ragnep --- components/user/rep/new-rep/UserPageRepNewRepSearch.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx b/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx index 6209753349..6ebc03401f 100644 --- a/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx +++ b/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx @@ -316,6 +316,7 @@ export default function UserPageRepNewRepSearch({ className="tw-will-change-transform md:tw-absolute md:tw-left-0 md:tw-right-0 md:tw-top-full md:tw-z-10 md:tw-mt-1" >
e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()} From 7e08421244a875e38d549b89ab55db6706ede593 Mon Sep 17 00:00:00 2001 From: ragnep Date: Fri, 27 Feb 2026 14:16:38 +0200 Subject: [PATCH 08/10] wip Signed-off-by: ragnep --- components/user/rep/new-rep/UserPageRepNewRepSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx b/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx index 6ebc03401f..0baeab3bd2 100644 --- a/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx +++ b/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx @@ -315,8 +315,8 @@ export default function UserPageRepNewRepSearch({ transition={{ duration: 0.15, ease: "easeOut" }} className="tw-will-change-transform md:tw-absolute md:tw-left-0 md:tw-right-0 md:tw-top-full md:tw-z-10 md:tw-mt-1" > + {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()} From 3fa4ecf09b5183c8fa50dd6d434f2d5dc72280e4 Mon Sep 17 00:00:00 2001 From: ragnep Date: Fri, 27 Feb 2026 14:22:06 +0200 Subject: [PATCH 09/10] wip Signed-off-by: ragnep --- .../user/rep/new-rep/UserPageRepNewRepSearch.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx b/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx index 0baeab3bd2..63d273d465 100644 --- a/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx +++ b/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx @@ -2,7 +2,7 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { commonApiFetch, commonApiPost } from "@/services/api/common-api"; -import { useContext, useMemo, useRef, useState } from "react"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; import { useClickAway, useDebounce, useKeyPressEvent } from "react-use"; import { AnimatePresence, motion } from "framer-motion"; import type { ApiProfileRepRatesState } from "@/entities/IProfile"; @@ -98,16 +98,11 @@ export default function UserPageRepNewRepSearch({ category: selectedCategory, }); - // Pre-fill amountStr when repState changes (e.g. after category selection). - // Uses the React "adjust state during render" pattern so the user can still - // freely edit the value after pre-fill. - const [prevRepState, setPrevRepState] = useState(repState); - if (repState !== prevRepState) { - setPrevRepState(repState); + useEffect(() => { if (repState) { setAmountStr(`${repState.rater_contribution}`); } - } + }, [repState]); const amountNum = getStringAsNumberOrZero(amountStr); const isValidValue = From f2429d20da292d4a2440e10bb461b04859b342ef Mon Sep 17 00:00:00 2001 From: ragnep Date: Fri, 27 Feb 2026 14:37:16 +0200 Subject: [PATCH 10/10] wip Signed-off-by: ragnep --- components/user/rep/header/UserPageRepHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/user/rep/header/UserPageRepHeader.tsx b/components/user/rep/header/UserPageRepHeader.tsx index 44b9936017..f12becacf8 100644 --- a/components/user/rep/header/UserPageRepHeader.tsx +++ b/components/user/rep/header/UserPageRepHeader.tsx @@ -96,7 +96,7 @@ export default function UserPageRepHeader({