From 78cc842de6ee4437b469ef08e42eabff28b58eb0 Mon Sep 17 00:00:00 2001 From: ragnep Date: Wed, 4 Mar 2026 15:01:04 +0200 Subject: [PATCH 1/7] wip Signed-off-by: ragnep --- .../user/identity/header/RateNicCta.tsx | 67 ++++ .../header/UserPageIdentityHeader.tsx | 8 +- .../header/UserPageIdentityHeaderCIC.tsx | 20 +- .../UserPageIdentityHeaderCICRate.tsx | 75 +--- .../statements/UserPageIdentityStatements.tsx | 15 +- ...ityStatementsConsolidatedAddressesItem.tsx | 4 +- .../user/rep/MobileIdentityTabContent.tsx | 89 +++++ components/user/rep/MobileRepTabContent.tsx | 175 +++++++++ components/user/rep/MobileTabCards.tsx | 153 ++++++++ components/user/rep/UserPageRep.tsx | 66 +++- components/user/rep/UserPageRepMobile.tsx | 356 ++---------------- .../user/rep/header/UserPageRepHeader.tsx | 29 +- 12 files changed, 645 insertions(+), 412 deletions(-) create mode 100644 components/user/identity/header/RateNicCta.tsx create mode 100644 components/user/rep/MobileIdentityTabContent.tsx create mode 100644 components/user/rep/MobileRepTabContent.tsx create mode 100644 components/user/rep/MobileTabCards.tsx diff --git a/components/user/identity/header/RateNicCta.tsx b/components/user/identity/header/RateNicCta.tsx new file mode 100644 index 0000000000..6ef637f1a5 --- /dev/null +++ b/components/user/identity/header/RateNicCta.tsx @@ -0,0 +1,67 @@ +import { formatNumberWithCommas } from "@/helpers/Helpers"; + +export const FingerprintIcon = ({ + className, +}: { + readonly className?: string; +}) => ( + +); + +export function RateNicButton({ + onRateClick, +}: { + readonly onRateClick: () => void; +}) { + return ( + + ); +} + +export function RateNicInfo({ + userContribution, +}: { + readonly userContribution: number; +}) { + return ( +
+
+ + + Your Rate + +
+ = 0 ? "tw-text-emerald-400" : "tw-text-rose-400" + }`} + > + {formatNumberWithCommas(userContribution)} + +
+ ); +} diff --git a/components/user/identity/header/UserPageIdentityHeader.tsx b/components/user/identity/header/UserPageIdentityHeader.tsx index 6e6448ad48..36dfaaac3d 100644 --- a/components/user/identity/header/UserPageIdentityHeader.tsx +++ b/components/user/identity/header/UserPageIdentityHeader.tsx @@ -5,9 +5,11 @@ import UserPageIdentityHeaderCIC from "./UserPageIdentityHeaderCIC"; export default function UserPageIdentityHeader({ profile, cicOverview, + onRateClick, }: { readonly profile: ApiIdentity; readonly cicOverview: ApiCicOverview | null; + readonly onRateClick?: () => void; }) { return (
@@ -20,7 +22,11 @@ export default function UserPageIdentityHeader({ identity?

- + ); } diff --git a/components/user/identity/header/UserPageIdentityHeaderCIC.tsx b/components/user/identity/header/UserPageIdentityHeaderCIC.tsx index ff8c3821d3..979cfa2891 100644 --- a/components/user/identity/header/UserPageIdentityHeaderCIC.tsx +++ b/components/user/identity/header/UserPageIdentityHeaderCIC.tsx @@ -7,15 +7,18 @@ import type { ApiIdentity } from "@/generated/models/ApiIdentity"; import { formatNumberWithCommas } from "@/helpers/Helpers"; import { useMemo } from "react"; import OverlappingAvatars from "@/components/common/OverlappingAvatars"; +import { RateNicButton, RateNicInfo } from "./RateNicCta"; const TOP_RATERS_COUNT = 5; export default function UserPageIdentityHeaderCIC({ profile, cicOverview, + onRateClick, }: { readonly profile: ApiIdentity; readonly cicOverview: ApiCicOverview | null; + readonly onRateClick?: () => void; }) { const cicRating = cicOverview?.total_cic ?? profile.cic; const raterCount = cicOverview?.contributor_count ?? 0; @@ -45,10 +48,13 @@ export default function UserPageIdentityHeaderCIC({ return (
-
- NIC +
+
+ NIC +
+ {onRateClick && }
-
+
{formatNumberWithCommas(cicRating)} @@ -74,6 +80,14 @@ export default function UserPageIdentityHeaderCIC({
+ {cicOverview !== null && cicOverview.authenticated_user_contribution !== null && + cicOverview.authenticated_user_contribution !== 0 && ( +
+ +
+ )}
); } diff --git a/components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRate.tsx b/components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRate.tsx index 36a9d265aa..706536666c 100644 --- a/components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRate.tsx +++ b/components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRate.tsx @@ -5,10 +5,7 @@ import { useContext, useEffect, useState } from "react"; import type { ApiProfileRaterCicState } from "@/entities/IProfile"; import { getStringAsNumberOrZero } from "@/helpers/Helpers"; import { AuthContext } from "@/components/auth/Auth"; -import { - commonApiFetch, - commonApiPost, -} from "@/services/api/common-api"; +import { commonApiFetch, commonApiPost } from "@/services/api/common-api"; import { useMutation, useQuery } from "@tanstack/react-query"; import { QueryKey, @@ -192,8 +189,7 @@ export default function UserPageIdentityHeaderCICRate({ const haveChanged = newRating !== originalRating; const isProxy = !!activeProfileProxy; const isValidValue = - isProxy || - (newRating >= minMaxValues.min && newRating <= minMaxValues.max); + isProxy || (newRating >= minMaxValues.min && newRating <= minMaxValues.max); const isSaveDisabled = !haveChanged || !isValidValue; const onSave = async () => { @@ -219,9 +215,10 @@ export default function UserPageIdentityHeaderCICRate({ const rateInput = (
+ className={`tw-relative tw-flex tw-w-full ${ + isTooltip ? "tw-mt-1.5" : "tw-mb-2" + }`} + > - {!isTooltip && ( -
- - - Rate NIC - -
- )} +
-
+ {isTooltip ? ( <>
{rateInput}
-
+
@@ -345,7 +308,8 @@ export default function UserPageIdentityHeaderCICRate({ <> @@ -359,8 +323,9 @@ export default function UserPageIdentityHeaderCICRate({ className={`${ isSaveDisabled ? "tw-cursor-not-allowed tw-opacity-50" - : "hover:tw-bg-emerald-400 hover:tw-border-emerald-400 active:tw-scale-[0.98]" - } tw-mt-4 tw-w-full tw-bg-emerald-500 tw-py-3.5 tw-text-sm tw-font-semibold tw-text-white tw-border tw-border-solid tw-border-emerald-500 tw-rounded-lg tw-shadow-lg tw-shadow-emerald-500/20 tw-transition tw-duration-300 tw-ease-out`}> + : "hover:tw-border-emerald-400 hover:tw-bg-emerald-400 active:tw-scale-[0.98]" + } tw-mt-4 tw-w-full tw-rounded-lg tw-border tw-border-solid tw-border-emerald-500 tw-bg-emerald-500 tw-py-3.5 tw-text-sm tw-font-semibold tw-text-white tw-shadow-lg tw-shadow-emerald-500/20 tw-transition tw-duration-300 tw-ease-out`} + > {fullButtonContent} diff --git a/components/user/identity/statements/UserPageIdentityStatements.tsx b/components/user/identity/statements/UserPageIdentityStatements.tsx index d6899670b7..bca38d7c0e 100644 --- a/components/user/identity/statements/UserPageIdentityStatements.tsx +++ b/components/user/identity/statements/UserPageIdentityStatements.tsx @@ -85,13 +85,11 @@ export default function UserPageIdentityStatements({ }, [statements]); return ( -
+
- -
-
+ +
+
@@ -138,8 +136,9 @@ export default function UserPageIdentityStatements({ positionStrategy="fixed" offset={8} opacity={1} - style={TOOLTIP_STYLES}> -
    + style={TOOLTIP_STYLES} + > +
    • All statements are optional.
    • All statements are fully and permanently public.
    • diff --git a/components/user/identity/statements/consolidated-addresses/UserPageIdentityStatementsConsolidatedAddressesItem.tsx b/components/user/identity/statements/consolidated-addresses/UserPageIdentityStatementsConsolidatedAddressesItem.tsx index d4e77b1fe3..b4ba3d1fb8 100644 --- a/components/user/identity/statements/consolidated-addresses/UserPageIdentityStatementsConsolidatedAddressesItem.tsx +++ b/components/user/identity/statements/consolidated-addresses/UserPageIdentityStatementsConsolidatedAddressesItem.tsx @@ -189,7 +189,7 @@ export default function UserPageIdentityStatementsConsolidatedAddressesItem({ return (
    • -
      +
      @@ -291,7 +291,7 @@ export default function UserPageIdentityStatementsConsolidatedAddressesItem({ {isOpen && (
      diff --git a/components/user/rep/MobileIdentityTabContent.tsx b/components/user/rep/MobileIdentityTabContent.tsx new file mode 100644 index 0000000000..e5067523d7 --- /dev/null +++ b/components/user/rep/MobileIdentityTabContent.tsx @@ -0,0 +1,89 @@ +import type { ActivityLogParams } from "@/components/profile-activity/ProfileActivityLogs"; +import type { ApiCicOverview } from "@/generated/models/ApiCicOverview"; +import type { ApiIdentity } from "@/generated/models/ApiIdentity"; +import { formatNumberWithCommas } from "@/helpers/Helpers"; +import { RateMatter } from "@/types/enums"; +import { FingerprintIcon } from "../identity/header/RateNicCta"; +import UserPageIdentityStatementsAddButton from "../identity/statements/add/UserPageIdentityStatementsAddButton"; +import UserPageIdentityStatements from "../identity/statements/UserPageIdentityStatements"; +import UserPageRateWrapper from "../utils/rate/UserPageRateWrapper"; +import UserPageCombinedActivityLog from "./UserPageCombinedActivityLog"; + +export default function MobileIdentityTabContent({ + profile, + cicOverview, + initialActivityLogParams, + canEditNic, + canEditStatements, + onRateNic, +}: { + readonly profile: ApiIdentity; + readonly cicOverview: ApiCicOverview | null; + readonly initialActivityLogParams: ActivityLogParams; + readonly canEditNic: boolean; + readonly canEditStatements: boolean; + readonly onRateNic: () => void; +}) { + return ( + <> + {/* Rate NIC CTA */} + {canEditNic && ( +
      + +
      + {cicOverview !== null && cicOverview.authenticated_user_contribution !== null && + cicOverview.authenticated_user_contribution !== 0 ? ( + + + Your Rate:{" "} + = 0 ? "tw-text-emerald-400" : "tw-text-rose-400"}`} + > + {formatNumberWithCommas( + cicOverview.authenticated_user_contribution + )} + + + ) : ( + + Verify this identity + + )} + +
      +
      +
      + )} + + {/* Identity Statements */} +
      +

      + ID Statements +

      + {canEditStatements && ( + + )} +
      +
      + +
      + +
      + +
      + + ); +} diff --git a/components/user/rep/MobileRepTabContent.tsx b/components/user/rep/MobileRepTabContent.tsx new file mode 100644 index 0000000000..0489fa140e --- /dev/null +++ b/components/user/rep/MobileRepTabContent.tsx @@ -0,0 +1,175 @@ +import type { ActivityLogParams } from "@/components/profile-activity/ProfileActivityLogs"; +import type { ApiRepCategory } from "@/generated/models/ApiRepCategory"; +import type { ApiIdentity } from "@/generated/models/ApiIdentity"; +import { RateMatter } from "@/types/enums"; +import { ArrowDownLeftIcon, ArrowUpRightIcon } from "@heroicons/react/24/solid"; +import type { RepDirection } from "./UserPageRep.helpers"; +import RepCategoryPill from "./RepCategoryPill"; +import UserPageCombinedActivityLog from "./UserPageCombinedActivityLog"; +import UserPageRateWrapper from "../utils/rate/UserPageRateWrapper"; + +function RepEmptyState({ + loading, + repDirection, +}: { + readonly loading: boolean; + readonly repDirection: RepDirection; +}) { + if (loading) { + return ( +
      +
      +
      + ); + } + return ( +

      + {repDirection === "given" ? "No rep given yet." : "No rep received yet."} +

      + ); +} + +export default function MobileRepTabContent({ + profile, + categories, + repDirection, + onRepDirectionChange, + initialActivityLogParams, + loading, + canEditRep, + visibleCount, + onShowMore, + onGrantRep, + onEditCategory, +}: { + readonly profile: ApiIdentity; + readonly categories: ApiRepCategory[]; + readonly repDirection: RepDirection; + readonly onRepDirectionChange: (direction: RepDirection) => void; + readonly initialActivityLogParams: ActivityLogParams; + readonly loading: boolean; + readonly canEditRep: boolean; + readonly visibleCount: number; + readonly onShowMore: () => void; + readonly onGrantRep: () => void; + readonly onEditCategory: (category: string) => void; +}) { + return ( + <> + {canEditRep && repDirection === "received" && ( +
      + +
      + + Add rep to this identity + + +
      +
      +
      + )} + + {/* Rep Categories */} +
      +
      + Rep Categories +
      + + {/* Received / Given toggle */} +
      + + +
      +
      + + {categories.length > 0 && ( +
      +
      + {categories.slice(0, visibleCount).map((cat) => ( + + ))} + {categories.length > visibleCount && ( + + )} +
      +
      + )} + + {categories.length === 0 && ( + + )} + +
      + +
      + + ); +} diff --git a/components/user/rep/MobileTabCards.tsx b/components/user/rep/MobileTabCards.tsx new file mode 100644 index 0000000000..ad324de14d --- /dev/null +++ b/components/user/rep/MobileTabCards.tsx @@ -0,0 +1,153 @@ +import type { ComponentProps } from "react"; +import type { ApiRepOverview } from "@/generated/models/ApiRepOverview"; +import type { ApiCicOverview } from "@/generated/models/ApiCicOverview"; +import type { ApiIdentity } from "@/generated/models/ApiIdentity"; +import { formatNumberWithCommas } from "@/helpers/Helpers"; +import UserCICStatus from "../utils/user-cic-status/UserCICStatus"; +import UserCICTypeIcon from "../utils/user-cic-type/UserCICTypeIcon"; +import OverlappingAvatars from "@/components/common/OverlappingAvatars"; +import { getContributorLabel, type RepDirection } from "./UserPageRep.helpers"; + +type MobileTab = "rep" | "identity"; + +export default function MobileTabCards({ + activeTab, + onTabChange, + overview, + cicOverview, + profile, + repDirection, + cicAvatarItems, +}: { + readonly activeTab: MobileTab; + readonly onTabChange: (tab: MobileTab) => void; + readonly overview: ApiRepOverview | null; + readonly cicOverview: ApiCicOverview | null; + readonly profile: ApiIdentity; + readonly repDirection: RepDirection; + readonly cicAvatarItems: ComponentProps["items"]; +}) { + return ( +
      + + + {/* NIC Score */} + +
      + ); +} diff --git a/components/user/rep/UserPageRep.tsx b/components/user/rep/UserPageRep.tsx index 74ae9fe590..2ce152e455 100644 --- a/components/user/rep/UserPageRep.tsx +++ b/components/user/rep/UserPageRep.tsx @@ -7,10 +7,15 @@ import type { ApiRepOverview } from "@/generated/models/ApiRepOverview"; import type { ApiRepCategoriesPage } from "@/generated/models/ApiRepCategoriesPage"; import type { ApiCicOverview } from "@/generated/models/ApiCicOverview"; import { commonApiFetch } from "@/services/api/common-api"; +import { AuthContext } from "@/components/auth/Auth"; +import { useSeizeConnectContext } from "@/components/auth/SeizeConnectContext"; +import MobileWrapperDialog from "@/components/mobile-wrapper-dialog/MobileWrapperDialog"; +import { ApiProfileProxyActionType } from "@/generated/models/ApiProfileProxyActionType"; +import { amIUser } from "@/helpers/Helpers"; import { RateMatter } from "@/types/enums"; import { useQuery } from "@tanstack/react-query"; import { useParams } from "next/navigation"; -import { useState } from "react"; +import { useContext, useMemo, useState } from "react"; import UserPageIdentityHeader from "../identity/header/UserPageIdentityHeader"; import UserPageIdentityHeaderCICRate from "../identity/header/cic-rate/UserPageIdentityHeaderCICRate"; import UserPageIdentityStatements from "../identity/statements/UserPageIdentityStatements"; @@ -29,9 +34,24 @@ export default function UserPageRep({ readonly initialActivityLogParams: ActivityLogParams; }) { const params = useParams(); - const user = (params?.["user"] as string)?.toLowerCase(); + const user = (params["user"] as string).toLowerCase(); + const { connectedProfile, activeProfileProxy } = useContext(AuthContext); + const { address } = useSeizeConnectContext(); const [repDirection, setRepDirection] = useState("received"); + const [isNicRateOpen, setIsNicRateOpen] = useState(false); + + const canEditNic = useMemo((): boolean => { + if (!connectedProfile?.handle) return false; + if (activeProfileProxy) { + if (profile.handle === activeProfileProxy.created_by.handle) return false; + return activeProfileProxy.actions.some( + (action) => action.action_type === ApiProfileProxyActionType.AllocateCic + ); + } + if (amIUser({ profile, address })) return false; + return true; + }, [connectedProfile, profile, activeProfileProxy, address]); // --- Incoming (received) rep --- const { data: repOverview, isFetching: isFetchingOverview } = @@ -166,25 +186,43 @@ export default function UserPageRep({ setIsNicRateOpen(true) } + : {})} /> -
      - - - -
      + + setIsNicRateOpen(false)} + tabletModal + maxWidthClass="md:tw-max-w-md" + > +
      + + setIsNicRateOpen(false)} + /> + +
      + +
      +
      +
      ); } diff --git a/components/user/rep/UserPageRepMobile.tsx b/components/user/rep/UserPageRepMobile.tsx index fc0a57ebac..c77c27d5e9 100644 --- a/components/user/rep/UserPageRepMobile.tsx +++ b/components/user/rep/UserPageRepMobile.tsx @@ -12,45 +12,19 @@ import { ApiProfileProxyActionType } from "@/generated/models/ApiProfileProxyAct import { amIUser, formatNumberWithCommas } from "@/helpers/Helpers"; import { RateMatter } from "@/types/enums"; import { AnimatePresence, motion } from "framer-motion"; -import { useContext, useEffect, useMemo, useState } from "react"; +import { useContext, useMemo, useState } from "react"; import UserPageIdentityHeaderCICRate from "../identity/header/cic-rate/UserPageIdentityHeaderCICRate"; -import UserPageIdentityStatementsAddButton from "../identity/statements/add/UserPageIdentityStatementsAddButton"; -import UserPageIdentityStatements from "../identity/statements/UserPageIdentityStatements"; import UserPageRateWrapper from "../utils/rate/UserPageRateWrapper"; -import UserCICStatus from "../utils/user-cic-status/UserCICStatus"; -import UserCICTypeIcon from "../utils/user-cic-type/UserCICTypeIcon"; -import OverlappingAvatars from "@/components/common/OverlappingAvatars"; import UserPageRepModifyModal from "./modify-rep/UserPageRepModifyModal"; import GrantRepDialog from "./new-rep/GrantRepDialog"; -import { ArrowDownLeftIcon, ArrowUpRightIcon } from "@heroicons/react/24/solid"; -import { getContributorLabel, type RepDirection } from "./UserPageRep.helpers"; -import RepCategoryPill from "./RepCategoryPill"; -import UserPageCombinedActivityLog from "./UserPageCombinedActivityLog"; +import type { RepDirection } from "./UserPageRep.helpers"; import { getCanEditRep } from "./UserPageRep.helpers"; +import MobileTabCards from "./MobileTabCards"; +import MobileRepTabContent from "./MobileRepTabContent"; +import MobileIdentityTabContent from "./MobileIdentityTabContent"; type MobileTab = "rep" | "identity"; -function RepEmptyState({ - loading, - repDirection, -}: { - readonly loading: boolean; - readonly repDirection: RepDirection; -}) { - if (loading) { - return ( -
      -
      -
      - ); - } - return ( -

      - {repDirection === "given" ? "No rep given yet." : "No rep received yet."} -

      - ); -} - export default function UserPageRepMobile({ profile, overview, @@ -78,10 +52,11 @@ export default function UserPageRepMobile({ const [isNicRateOpen, setIsNicRateOpen] = useState(false); const [visibleCount, setVisibleCount] = useState(5); const [editCategory, setEditCategory] = useState(null); - - useEffect(() => { + const [prevCategories, setPrevCategories] = useState(categories); + if (categories !== prevCategories) { + setPrevCategories(categories); setVisibleCount(5); - }, [categories]); + } const canEditRep = useMemo( () => @@ -107,7 +82,7 @@ export default function UserPageRepMobile({ const canEditStatements = !activeProfileProxy && - !!profile?.handle && + !!profile.handle && (profile.wallets ?? []).some( (w) => w.wallet.toLowerCase() === address?.toLowerCase() ); @@ -135,134 +110,16 @@ export default function UserPageRepMobile({ return (
      -
      - - - {/* NIC Score */} - -
      + - {/* Tab Content */} {activeTab === "rep" ? ( - {/* Received / Given toggle */} -
      - - -
      - - {/* Rep Categories */} - {categories.length > 0 && ( -
      -
      - Rep Categories -
      -
      - {categories.slice(0, visibleCount).map((cat) => ( - - ))} - {categories.length > visibleCount && ( - - )} -
      -
      - )} - - {categories.length === 0 && ( - - )} - - {canEditRep && repDirection === "received" && ( -
      - -
      - - Add rep to this identity - - -
      -
      -
      - )} - -
      - -
      + setVisibleCount((prev) => prev + 10)} + onGrantRep={() => setIsGrantRepOpen(true)} + onEditCategory={setEditCategory} + />
      ) : ( - {/* Rate NIC CTA */} - {canEditNic && ( -
      - -
      - - Verify this identity - - -
      -
      -
      - )} - - {/* Identity Statements */} -
      -

      - ID Statements -

      - {canEditStatements && ( - - )} -
      -
      - -
      - -
      - -
      + setIsNicRateOpen(true)} + />
      )}
      diff --git a/components/user/rep/header/UserPageRepHeader.tsx b/components/user/rep/header/UserPageRepHeader.tsx index bbb272e0a0..9216aa5bfe 100644 --- a/components/user/rep/header/UserPageRepHeader.tsx +++ b/components/user/rep/header/UserPageRepHeader.tsx @@ -6,7 +6,7 @@ import type { ApiRepCategory } from "@/generated/models/ApiRepCategory"; import type { ApiIdentity } from "@/generated/models/ApiIdentity"; import { formatNumberWithCommas } from "@/helpers/Helpers"; import { ArrowDownLeftIcon, ArrowUpRightIcon } from "@heroicons/react/24/solid"; -import { useContext, useEffect, useMemo, useState } from "react"; +import { useContext, useMemo, useState } from "react"; import RepCategoryPill from "../RepCategoryPill"; import UserPageRepModifyModal from "../modify-rep/UserPageRepModifyModal"; import GrantRepDialog from "../new-rep/GrantRepDialog"; @@ -34,10 +34,11 @@ export default function UserPageRepHeader({ const { connectedProfile, activeProfileProxy } = useContext(AuthContext); const [visibleCount, setVisibleCount] = useState(5); - - useEffect(() => { + const [prevCategories, setPrevCategories] = useState(categories); + if (categories !== prevCategories) { + setPrevCategories(categories); setVisibleCount(5); - }, [categories]); + } const visibleCategories = categories.slice(0, visibleCount); const hasMore = categories.length > visibleCount; @@ -141,31 +142,33 @@ export default function UserPageRepHeader({ {(visibleCategories.length > 0 || (canEditRep && repDirection === "received")) && ( -
      -
      - Rep Categories -
      -
      +
      +
      +
      + Rep Categories +
      {canEditRep && repDirection === "received" && ( )} +
      +
      {visibleCategories.map((cat) => ( Date: Wed, 4 Mar 2026 15:19:20 +0200 Subject: [PATCH 2/7] wip Signed-off-by: ragnep --- components/user/rep/MobileTabCards.tsx | 2 ++ components/user/rep/header/UserPageRepHeader.tsx | 1 + 2 files changed, 3 insertions(+) diff --git a/components/user/rep/MobileTabCards.tsx b/components/user/rep/MobileTabCards.tsx index ad324de14d..9e1586f518 100644 --- a/components/user/rep/MobileTabCards.tsx +++ b/components/user/rep/MobileTabCards.tsx @@ -31,6 +31,7 @@ export default function MobileTabCards({
      diff --git a/components/user/identity/header/UserPageIdentityHeaderCIC.tsx b/components/user/identity/header/UserPageIdentityHeaderCIC.tsx index 979cfa2891..6f3ea76c50 100644 --- a/components/user/identity/header/UserPageIdentityHeaderCIC.tsx +++ b/components/user/identity/header/UserPageIdentityHeaderCIC.tsx @@ -7,7 +7,7 @@ import type { ApiIdentity } from "@/generated/models/ApiIdentity"; import { formatNumberWithCommas } from "@/helpers/Helpers"; import { useMemo } from "react"; import OverlappingAvatars from "@/components/common/OverlappingAvatars"; -import { RateNicButton, RateNicInfo } from "./RateNicCta"; +import { RateNicButton } from "./RateNicCta"; const TOP_RATERS_COUNT = 5; @@ -47,12 +47,9 @@ export default function UserPageIdentityHeaderCIC({ ); return ( -
      -
      -
      - NIC -
      - {onRateClick && } +
      +
      + NIC
      @@ -80,14 +77,26 @@ export default function UserPageIdentityHeaderCIC({
      - {cicOverview !== null && cicOverview.authenticated_user_contribution !== null && - cicOverview.authenticated_user_contribution !== 0 && ( -
      - -
      - )} + {onRateClick && ( +
      + + {cicOverview !== null && + cicOverview.authenticated_user_contribution !== null && + cicOverview.authenticated_user_contribution !== 0 && ( +
      + + Your Rating: + + + {cicOverview.authenticated_user_contribution > 0 && "+"} + {formatNumberWithCommas( + cicOverview.authenticated_user_contribution + )} + +
      + )} +
      + )}
      ); } diff --git a/components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRate.tsx b/components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRate.tsx index 706536666c..cd69f95d88 100644 --- a/components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRate.tsx +++ b/components/user/identity/header/cic-rate/UserPageIdentityHeaderCICRate.tsx @@ -36,10 +36,12 @@ export default function UserPageIdentityHeaderCICRate({ profile, isTooltip, onSuccess, + onCancel, }: { readonly profile: ApiIdentity; readonly isTooltip: boolean; readonly onSuccess?: () => void; + readonly onCancel?: () => void; }) { const { address } = useSeizeConnectContext(); const { requestAuth, setToast, connectedProfile, activeProfileProxy } = @@ -317,17 +319,28 @@ export default function UserPageIdentityHeaderCICRate({ {adjustmentHelper} - +
      + + {onCancel && ( + + )} +
      )} diff --git a/components/user/rep/MobileIdentityTabContent.tsx b/components/user/rep/MobileIdentityTabContent.tsx index e5067523d7..21290f43cb 100644 --- a/components/user/rep/MobileIdentityTabContent.tsx +++ b/components/user/rep/MobileIdentityTabContent.tsx @@ -26,7 +26,6 @@ export default function MobileIdentityTabContent({ }) { return ( <> - {/* Rate NIC CTA */} {canEditNic && (
      -
      + -
      + +
      )} diff --git a/components/user/rep/MobileRepTabContent.tsx b/components/user/rep/MobileRepTabContent.tsx index 0489fa140e..d7dfa82127 100644 --- a/components/user/rep/MobileRepTabContent.tsx +++ b/components/user/rep/MobileRepTabContent.tsx @@ -1,6 +1,8 @@ import type { ActivityLogParams } from "@/components/profile-activity/ProfileActivityLogs"; import type { ApiRepCategory } from "@/generated/models/ApiRepCategory"; +import type { ApiRepOverview } from "@/generated/models/ApiRepOverview"; import type { ApiIdentity } from "@/generated/models/ApiIdentity"; +import { formatNumberWithCommas } from "@/helpers/Helpers"; import { RateMatter } from "@/types/enums"; import { ArrowDownLeftIcon, ArrowUpRightIcon } from "@heroicons/react/24/solid"; import type { RepDirection } from "./UserPageRep.helpers"; @@ -31,6 +33,7 @@ function RepEmptyState({ export default function MobileRepTabContent({ profile, + overview, categories, repDirection, onRepDirectionChange, @@ -43,6 +46,7 @@ export default function MobileRepTabContent({ onEditCategory, }: { readonly profile: ApiIdentity; + readonly overview: ApiRepOverview | null; readonly categories: ApiRepCategory[]; readonly repDirection: RepDirection; readonly onRepDirectionChange: (direction: RepDirection) => void; @@ -63,14 +67,29 @@ export default function MobileRepTabContent({ type={RateMatter.REP} hideOwnProfileMessage > -
      - - Add rep to this identity - - -
      + +
      )} - {/* Rep Categories */}
      -
      +
      Rep Categories
      @@ -120,7 +138,7 @@ export default function MobileRepTabContent({ onClick={() => onRepDirectionChange("given")} className={`tw-inline-flex tw-cursor-pointer tw-items-center tw-gap-1.5 tw-border-0 tw-bg-transparent tw-p-0 tw-text-xs tw-font-medium tw-transition-colors tw-duration-200 ${ repDirection === "given" - ? "tw-text-iron-100" + ? "tw-text-iron-100 tw-font-semibold" : "tw-text-iron-500 hover:tw-text-iron-300" }`} > diff --git a/components/user/rep/MobileTabCards.tsx b/components/user/rep/MobileTabCards.tsx index 9e1586f518..6340ac0720 100644 --- a/components/user/rep/MobileTabCards.tsx +++ b/components/user/rep/MobileTabCards.tsx @@ -1,4 +1,4 @@ -import type { ComponentProps } from "react"; +import { useMemo, type ComponentProps } from "react"; import type { ApiRepOverview } from "@/generated/models/ApiRepOverview"; import type { ApiCicOverview } from "@/generated/models/ApiCicOverview"; import type { ApiIdentity } from "@/generated/models/ApiIdentity"; @@ -27,6 +27,27 @@ export default function MobileTabCards({ readonly repDirection: RepDirection; readonly cicAvatarItems: ComponentProps["items"]; }) { + const repAvatarItems = useMemo( + () => + (overview?.contributors.data ?? []).slice(0, 3).map((c) => ({ + key: c.profile.handle ?? c.profile.primary_address, + pfpUrl: c.profile.pfp ?? null, + href: `/${c.profile.handle ?? c.profile.primary_address}`, + ariaLabel: c.profile.handle ?? c.profile.primary_address, + fallback: c.profile.handle + ? c.profile.handle.charAt(0).toUpperCase() + : "?", + title: c.profile.handle ?? c.profile.primary_address, + tooltipContent: ( + + {c.profile.handle ?? c.profile.primary_address} ·{" "} + {formatNumberWithCommas(c.contribution)} + + ), + })), + [overview?.contributors.data] + ); + return (
      - {/* NIC Score */} -
      diff --git a/components/user/rep/header/UserPageRepHeader.tsx b/components/user/rep/header/UserPageRepHeader.tsx index ab68c0271f..42017b68f0 100644 --- a/components/user/rep/header/UserPageRepHeader.tsx +++ b/components/user/rep/header/UserPageRepHeader.tsx @@ -5,8 +5,13 @@ import type { ApiRepOverview } from "@/generated/models/ApiRepOverview"; import type { ApiRepCategory } from "@/generated/models/ApiRepCategory"; import type { ApiIdentity } from "@/generated/models/ApiIdentity"; import { formatNumberWithCommas } from "@/helpers/Helpers"; -import { ArrowDownLeftIcon, ArrowUpRightIcon } from "@heroicons/react/24/solid"; +import { + ArrowDownLeftIcon, + ArrowUpRightIcon, + PlusIcon, +} from "@heroicons/react/24/solid"; import { useContext, useMemo, useState } from "react"; +import OverlappingAvatars from "@/components/common/OverlappingAvatars"; import RepCategoryPill from "../RepCategoryPill"; import UserPageRepModifyModal from "../modify-rep/UserPageRepModifyModal"; import GrantRepDialog from "../new-rep/GrantRepDialog"; @@ -53,6 +58,31 @@ export default function UserPageRepHeader({ [connectedProfile, profile, activeProfileProxy] ); + const TOP_CONTRIBUTORS_COUNT = 5; + + const avatarItems = useMemo( + () => + (overview?.contributors.data ?? []) + .slice(0, TOP_CONTRIBUTORS_COUNT) + .map((c) => ({ + key: c.profile.handle ?? c.profile.primary_address, + pfpUrl: c.profile.pfp ?? null, + href: `/${c.profile.handle ?? c.profile.primary_address}`, + ariaLabel: c.profile.handle ?? c.profile.primary_address, + fallback: c.profile.handle + ? c.profile.handle.charAt(0).toUpperCase() + : "?", + title: c.profile.handle ?? c.profile.primary_address, + tooltipContent: ( + + {c.profile.handle ?? c.profile.primary_address} ·{" "} + {formatNumberWithCommas(c.contribution)} + + ), + })), + [overview?.contributors.data] + ); + const [editCategory, setEditCategory] = useState(null); const [isGrantRepOpen, setIsGrantRepOpen] = useState(false); @@ -65,7 +95,7 @@ export default function UserPageRepHeader({
      -
      +

      Rep @@ -75,101 +105,111 @@ export default function UserPageRepHeader({ ? "What others recognize this identity for." : "What this identity recognizes others for."}

      +
      + + +

      - {overview ? ( -
      -
      - Total Rep -
      -
      - {formatNumberWithCommas(overview.total_rep)} -
      - - {formatNumberWithCommas(overview.contributor_count)}{" "} - {getContributorLabel( - repDirection, - overview.contributor_count - )} - +
      +
      + Total Rep
      - ) : ( -
      -
      - Total Rep -
      -
      - — -
      +
      + {overview + ? formatNumberWithCommas(overview.total_rep) + : "—"}
      - )} -
      - -
      - - + {overview && ( +
      + {avatarItems.length > 0 && ( + + )} + + {formatNumberWithCommas(overview.contributor_count)}{" "} + {getContributorLabel( + repDirection, + overview.contributor_count + )} + +
      + )} +
      {(visibleCategories.length > 0 || (canEditRep && repDirection === "received")) && ( -
      +
      Rep Categories
      + {overview !== null && + overview.authenticated_user_contribution !== null && + overview.authenticated_user_contribution !== 0 && ( +
      + + {repDirection === "given" + ? "Assigned To You:" + : "You Assigned:"} + + + {overview.authenticated_user_contribution > 0 && "+"} + {formatNumberWithCommas( + overview.authenticated_user_contribution + )} + +
      + )} +
      +
      {canEditRep && repDirection === "received" && ( )} -
      -
      {visibleCategories.map((cat) => ( - {repDirection === "given" - ? "This identity hasn't given any rep yet." - : "This identity hasn't received any rep yet."} -

      +
      +

      + {repDirection === "given" + ? "This identity hasn't given any rep yet." + : "This identity hasn't received any rep yet."} +

      +
      )} {categories.length === 0 && loading && !(canEditRep && repDirection === "received") && ( -
      +
      )} diff --git a/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx b/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx index 39e84f095b..49a66d48d0 100644 --- a/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx +++ b/components/user/rep/new-rep/UserPageRepNewRepSearch.tsx @@ -372,7 +372,7 @@ export default function UserPageRepNewRepSearch({ diff --git a/components/user/user-page-header/UserPageHeaderClient.tsx b/components/user/user-page-header/UserPageHeaderClient.tsx index 9475a451f9..45cf83e9df 100644 --- a/components/user/user-page-header/UserPageHeaderClient.tsx +++ b/components/user/user-page-header/UserPageHeaderClient.tsx @@ -184,7 +184,7 @@ export default function UserPageHeaderClient({ />
      -
      +
      diff --git a/components/user/user-page-header/name/UserPageHeaderName.tsx b/components/user/user-page-header/name/UserPageHeaderName.tsx index d38b892227..41d3c7a278 100644 --- a/components/user/user-page-header/name/UserPageHeaderName.tsx +++ b/components/user/user-page-header/name/UserPageHeaderName.tsx @@ -85,17 +85,17 @@ export default function UserPageHeaderName({
      {profile?.classification && ( -
      +
      {CLASSIFICATIONS[profile.classification].title}
      )} {profileEnabledLabel && ( - + )} {profileEnabledLabel && (

      Profile enabled: {profileEnabledLabel} From e2ef07bc0b170b4fa44710e6f2626a9e874e14f3 Mon Sep 17 00:00:00 2001 From: ragnep Date: Thu, 5 Mar 2026 09:25:25 +0200 Subject: [PATCH 5/7] wip Signed-off-by: ragnep --- .../user/identity/header/RateNicCta.tsx | 26 ------------------- .../user/rep/header/UserPageRepHeader.tsx | 2 +- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/components/user/identity/header/RateNicCta.tsx b/components/user/identity/header/RateNicCta.tsx index deb231b0b8..59f2c8c3ba 100644 --- a/components/user/identity/header/RateNicCta.tsx +++ b/components/user/identity/header/RateNicCta.tsx @@ -1,5 +1,3 @@ -import { formatNumberWithCommas } from "@/helpers/Helpers"; - export const FingerprintIcon = ({ className, }: { @@ -41,27 +39,3 @@ export function RateNicButton({ ); } - -export function RateNicInfo({ - userContribution, -}: { - readonly userContribution: number; -}) { - return ( -

      -
      - - - Your Rate - -
      - = 0 ? "tw-text-emerald-400" : "tw-text-rose-400" - }`} - > - {formatNumberWithCommas(userContribution)} - -
      - ); -} diff --git a/components/user/rep/header/UserPageRepHeader.tsx b/components/user/rep/header/UserPageRepHeader.tsx index 42017b68f0..e0ff24aae3 100644 --- a/components/user/rep/header/UserPageRepHeader.tsx +++ b/components/user/rep/header/UserPageRepHeader.tsx @@ -196,7 +196,7 @@ export default function UserPageRepHeader({
      )}
      -
      +
      {canEditRep && repDirection === "received" && (
      {/* Received / Given toggle */} -
      - - +
      +
      diff --git a/components/user/rep/MobileTabCards.tsx b/components/user/rep/MobileTabCards.tsx index 6340ac0720..a98b9b442e 100644 --- a/components/user/rep/MobileTabCards.tsx +++ b/components/user/rep/MobileTabCards.tsx @@ -6,6 +6,7 @@ import { formatNumberWithCommas } from "@/helpers/Helpers"; import UserCICStatus from "../utils/user-cic-status/UserCICStatus"; import UserCICTypeIcon from "../utils/user-cic-type/UserCICTypeIcon"; import OverlappingAvatars from "@/components/common/OverlappingAvatars"; +import { buildRepAvatarItems } from "./buildRepAvatarItems"; import { getContributorLabel, type RepDirection } from "./UserPageRep.helpers"; type MobileTab = "rep" | "identity"; @@ -28,23 +29,7 @@ export default function MobileTabCards({ readonly cicAvatarItems: ComponentProps["items"]; }) { const repAvatarItems = useMemo( - () => - (overview?.contributors.data ?? []).slice(0, 3).map((c) => ({ - key: c.profile.handle ?? c.profile.primary_address, - pfpUrl: c.profile.pfp ?? null, - href: `/${c.profile.handle ?? c.profile.primary_address}`, - ariaLabel: c.profile.handle ?? c.profile.primary_address, - fallback: c.profile.handle - ? c.profile.handle.charAt(0).toUpperCase() - : "?", - title: c.profile.handle ?? c.profile.primary_address, - tooltipContent: ( - - {c.profile.handle ?? c.profile.primary_address} ·{" "} - {formatNumberWithCommas(c.contribution)} - - ), - })), + () => buildRepAvatarItems(overview?.contributors.data ?? [], 3), [overview?.contributors.data] ); diff --git a/components/user/rep/RepDirectionToggle.tsx b/components/user/rep/RepDirectionToggle.tsx new file mode 100644 index 0000000000..af470505c1 --- /dev/null +++ b/components/user/rep/RepDirectionToggle.tsx @@ -0,0 +1,49 @@ +import { ArrowDownLeftIcon, ArrowUpRightIcon } from "@heroicons/react/24/solid"; +import type { RepDirection } from "./UserPageRep.helpers"; + +export default function RepDirectionToggle({ + repDirection, + onRepDirectionChange, + compact, +}: { + readonly repDirection: RepDirection; + readonly onRepDirectionChange: (direction: RepDirection) => void; + readonly compact?: boolean; +}) { + const iconClass = compact + ? "tw-h-3 tw-w-3 tw-flex-shrink-0" + : "tw-h-3.5 tw-w-3.5 tw-flex-shrink-0"; + const textClass = compact ? "tw-text-xs" : "tw-text-[13px]"; + const activeExtra = compact ? "tw-font-semibold" : ""; + + return ( +
      + + +
      + ); +} diff --git a/components/user/rep/UserPageRepMobile.tsx b/components/user/rep/UserPageRepMobile.tsx index 7c3bff7491..3c446ebb64 100644 --- a/components/user/rep/UserPageRepMobile.tsx +++ b/components/user/rep/UserPageRepMobile.tsx @@ -8,7 +8,7 @@ import type { ApiRepOverview } from "@/generated/models/ApiRepOverview"; import type { ApiRepCategory } from "@/generated/models/ApiRepCategory"; import type { ApiCicOverview } from "@/generated/models/ApiCicOverview"; import type { ApiIdentity } from "@/generated/models/ApiIdentity"; -import { formatNumberWithCommas } from "@/helpers/Helpers"; +import { buildRepAvatarItems } from "./buildRepAvatarItems"; import { RateMatter } from "@/types/enums"; import { AnimatePresence, motion } from "framer-motion"; import { useContext, useMemo, useState } from "react"; @@ -51,6 +51,7 @@ export default function UserPageRepMobile({ const [isNicRateOpen, setIsNicRateOpen] = useState(false); const [visibleCount, setVisibleCount] = useState(5); const [editCategory, setEditCategory] = useState(null); + const [prevCategories, setPrevCategories] = useState(categories); if (categories !== prevCategories) { setPrevCategories(categories); @@ -85,24 +86,11 @@ export default function UserPageRepMobile({ (w) => w.wallet.toLowerCase() === address?.toLowerCase() ); - // Build CIC avatar items from overview contributors const cicAvatarItems = useMemo( () => - (cicOverview?.contributors.data ?? []).slice(0, 3).map((c) => ({ - key: c.profile.handle ?? c.profile.primary_address, - pfpUrl: c.profile.pfp ?? null, - ariaLabel: c.profile.handle ?? c.profile.primary_address, - fallback: c.profile.handle - ? c.profile.handle.charAt(0).toUpperCase() - : "?", - title: c.profile.handle ?? c.profile.primary_address, - tooltipContent: ( - - {c.profile.handle ?? c.profile.primary_address} ·{" "} - {formatNumberWithCommas(c.contribution)} - - ), - })), + buildRepAvatarItems(cicOverview?.contributors.data ?? [], 3, { + omitHref: true, + }), [cicOverview?.contributors.data] ); diff --git a/components/user/rep/buildRepAvatarItems.tsx b/components/user/rep/buildRepAvatarItems.tsx new file mode 100644 index 0000000000..90f7a16f64 --- /dev/null +++ b/components/user/rep/buildRepAvatarItems.tsx @@ -0,0 +1,35 @@ +import { formatNumberWithCommas } from "@/helpers/Helpers"; + +interface ContributorData { + readonly profile: { + readonly handle: string | null; + readonly primary_address: string; + readonly pfp: string | null; + }; + readonly contribution: number; +} + +export function buildRepAvatarItems( + contributors: readonly ContributorData[], + maxCount: number, + options?: { omitHref?: boolean } +) { + return contributors.slice(0, maxCount).map((c) => ({ + key: c.profile.handle ?? c.profile.primary_address, + pfpUrl: c.profile.pfp ?? null, + ...(options?.omitHref + ? {} + : { href: `/${c.profile.handle ?? c.profile.primary_address}` }), + ariaLabel: c.profile.handle ?? c.profile.primary_address, + fallback: c.profile.handle + ? c.profile.handle.charAt(0).toUpperCase() + : "?", + title: c.profile.handle ?? c.profile.primary_address, + tooltipContent: ( + + {c.profile.handle ?? c.profile.primary_address} ·{" "} + {formatNumberWithCommas(c.contribution)} + + ), + })); +} diff --git a/components/user/rep/header/UserPageRepHeader.tsx b/components/user/rep/header/UserPageRepHeader.tsx index e0ff24aae3..ec9a61ba80 100644 --- a/components/user/rep/header/UserPageRepHeader.tsx +++ b/components/user/rep/header/UserPageRepHeader.tsx @@ -5,14 +5,12 @@ import type { ApiRepOverview } from "@/generated/models/ApiRepOverview"; import type { ApiRepCategory } from "@/generated/models/ApiRepCategory"; import type { ApiIdentity } from "@/generated/models/ApiIdentity"; import { formatNumberWithCommas } from "@/helpers/Helpers"; -import { - ArrowDownLeftIcon, - ArrowUpRightIcon, - PlusIcon, -} from "@heroicons/react/24/solid"; +import { PlusIcon } from "@heroicons/react/24/solid"; import { useContext, useMemo, useState } from "react"; import OverlappingAvatars from "@/components/common/OverlappingAvatars"; import RepCategoryPill from "../RepCategoryPill"; +import RepDirectionToggle from "../RepDirectionToggle"; +import { buildRepAvatarItems } from "../buildRepAvatarItems"; import UserPageRepModifyModal from "../modify-rep/UserPageRepModifyModal"; import GrantRepDialog from "../new-rep/GrantRepDialog"; import { @@ -39,6 +37,7 @@ export default function UserPageRepHeader({ const { connectedProfile, activeProfileProxy } = useContext(AuthContext); const [visibleCount, setVisibleCount] = useState(5); + const [prevCategories, setPrevCategories] = useState(categories); if (categories !== prevCategories) { setPrevCategories(categories); @@ -62,24 +61,10 @@ export default function UserPageRepHeader({ const avatarItems = useMemo( () => - (overview?.contributors.data ?? []) - .slice(0, TOP_CONTRIBUTORS_COUNT) - .map((c) => ({ - key: c.profile.handle ?? c.profile.primary_address, - pfpUrl: c.profile.pfp ?? null, - href: `/${c.profile.handle ?? c.profile.primary_address}`, - ariaLabel: c.profile.handle ?? c.profile.primary_address, - fallback: c.profile.handle - ? c.profile.handle.charAt(0).toUpperCase() - : "?", - title: c.profile.handle ?? c.profile.primary_address, - tooltipContent: ( - - {c.profile.handle ?? c.profile.primary_address} ·{" "} - {formatNumberWithCommas(c.contribution)} - - ), - })), + buildRepAvatarItems( + overview?.contributors.data ?? [], + TOP_CONTRIBUTORS_COUNT + ), [overview?.contributors.data] ); @@ -105,39 +90,11 @@ export default function UserPageRepHeader({ ? "What others recognize this identity for." : "What this identity recognizes others for."}

      -
      - - +
      +
      From 9d421575810fc1c260ddf623a0865e01a3110103 Mon Sep 17 00:00:00 2001 From: ragnep Date: Thu, 5 Mar 2026 12:13:07 +0200 Subject: [PATCH 7/7] wip Signed-off-by: ragnep --- components/user/rep/MobileRepTabContent.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/components/user/rep/MobileRepTabContent.tsx b/components/user/rep/MobileRepTabContent.tsx index cabe4400cd..fe548f4cea 100644 --- a/components/user/rep/MobileRepTabContent.tsx +++ b/components/user/rep/MobileRepTabContent.tsx @@ -109,6 +109,23 @@ export default function MobileRepTabContent({
      )} + {repDirection === "given" && + overview !== null && + overview.authenticated_user_contribution !== null && + overview.authenticated_user_contribution !== 0 && ( +
      + + Assigned To You: + + + {overview.authenticated_user_contribution > 0 && "+"} + {formatNumberWithCommas( + overview.authenticated_user_contribution + )} + +
      + )} +
      Rep Categories