diff --git a/__tests__/components/user/identity/header/UserPageIdentityHeaderCIC.test.tsx b/__tests__/components/user/identity/header/UserPageIdentityHeaderCIC.test.tsx index 1d50ad37da..20c9203c43 100644 --- a/__tests__/components/user/identity/header/UserPageIdentityHeaderCIC.test.tsx +++ b/__tests__/components/user/identity/header/UserPageIdentityHeaderCIC.test.tsx @@ -1,43 +1,86 @@ -import { render, screen } from '@testing-library/react'; -import UserPageIdentityHeaderCIC from '@/components/user/identity/header/UserPageIdentityHeaderCIC'; -import type { ApiIdentity } from '@/generated/models/ApiIdentity'; +import { render, screen } from "@testing-library/react"; +import UserPageIdentityHeaderCIC from "@/components/user/identity/header/UserPageIdentityHeaderCIC"; +import type { ApiIdentity } from "@/generated/models/ApiIdentity"; +import type { ApiCicOverview } from "@/generated/models/ApiCicOverview"; -jest.mock('@tanstack/react-query', () => ({ - useQuery: jest.fn(() => ({ data: { count: 2 } })), -})); +jest.mock( + "@/components/user/utils/user-cic-type/UserCICTypeIconWrapper", + () => ({ + __esModule: true, + default: () =>
, + }) +); -jest.mock('@/components/user/utils/user-cic-type/UserCICTypeIconWrapper', () => ({ - __esModule: true, - default: () =>
, -})); - -jest.mock('@/components/user/rep/header/TopRaterAvatars', () => ({ +jest.mock("@/components/common/OverlappingAvatars", () => ({ __esModule: true, default: () =>
, })); -jest.mock('@/components/user/utils/user-cic-status/UserCICStatus', () => ({ +jest.mock("@/components/user/utils/user-cic-status/UserCICStatus", () => ({ __esModule: true, default: ({ cic }: { cic: number }) =>
{cic}
, })); -describe('UserPageIdentityHeaderCIC', () => { - const baseProfile: ApiIdentity = { cic: 1000, handle: 'alice' } as ApiIdentity; +const makeCicOverview = ( + totalCic: number, + contributorCount: number +): ApiCicOverview => + ({ + total_cic: totalCic, + authenticated_user_contribution: null, + contributor_count: contributorCount, + contributors: { + data: [ + { + contribution: 500, + profile: { + id: "1", + handle: "bob", + pfp: null, + primary_address: "0x123", + }, + }, + ], + page: 1, + next: false, + }, + }) as unknown as ApiCicOverview; + +describe("UserPageIdentityHeaderCIC", () => { + const baseProfile: ApiIdentity = { + cic: 1000, + handle: "alice", + } as ApiIdentity; - it('displays NIC and status info', () => { - render(); - expect(screen.getByText('NIC')).toBeInTheDocument(); - expect(screen.getByText('1,000')).toBeInTheDocument(); - expect(screen.getByTestId('raters-avatars')).toBeInTheDocument(); - expect(screen.getByText('2 raters')).toBeInTheDocument(); - expect(screen.getByTestId('icon')).toBeInTheDocument(); - expect(screen.getByTestId('status')).toHaveTextContent('1000'); + it("displays NIC and status info from cicOverview", () => { + const overview = makeCicOverview(1000, 2); + render( + + ); + expect(screen.getByText("NIC")).toBeInTheDocument(); + expect(screen.getByText("1,000")).toBeInTheDocument(); + expect(screen.getByTestId("raters-avatars")).toBeInTheDocument(); + expect(screen.getByText("2 raters")).toBeInTheDocument(); + expect(screen.getByTestId("icon")).toBeInTheDocument(); + expect(screen.getByTestId("status")).toHaveTextContent("1000"); }); - it('updates when profile prop changes', () => { - const { rerender } = render(); - rerender(); - expect(screen.getByText('2,000')).toBeInTheDocument(); - expect(screen.getByTestId('status')).toHaveTextContent('2000'); + it("updates when cicOverview prop changes", () => { + const overview1 = makeCicOverview(1000, 2); + const overview2 = makeCicOverview(2000, 3); + const { rerender } = render( + + ); + rerender( + + ); + expect(screen.getByText("2,000")).toBeInTheDocument(); + expect(screen.getByTestId("status")).toHaveTextContent("2000"); }); }); diff --git a/__tests__/components/user/rep/header/UserPageRepHeader.test.tsx b/__tests__/components/user/rep/header/UserPageRepHeader.test.tsx index 1d17256f5a..a03cb1e961 100644 --- a/__tests__/components/user/rep/header/UserPageRepHeader.test.tsx +++ b/__tests__/components/user/rep/header/UserPageRepHeader.test.tsx @@ -1,25 +1,44 @@ -import { render, screen } from '@testing-library/react'; -import UserPageRepHeader from '@/components/user/rep/header/UserPageRepHeader'; +import { render, screen } from "@testing-library/react"; +import UserPageRepHeader from "@/components/user/rep/header/UserPageRepHeader"; const mockProfile = { - handle: 'testuser', - display: 'Test User', - query: 'testuser', + handle: "testuser", + display: "Test User", + query: "testuser", } as any; -describe('UserPageRepHeader', () => { - it('shows rep totals when provided', () => { - const repRates = { - total_rep_rating: 1500, - number_of_raters: 25, - rating_stats: [], +describe("UserPageRepHeader", () => { + it("shows rep totals when provided", () => { + const overview = { + total_rep: 1500, + contributor_count: 25, + authenticated_user_contribution: null, + contributors: { data: [], page: 1, next: false }, } as any; - render(); - expect(screen.getByText('1,500')).toBeInTheDocument(); + render( + {}} + loading={false} + /> + ); + expect(screen.getByText("1,500")).toBeInTheDocument(); }); - it('renders without repRates', () => { - const { container } = render(); - expect(container).toHaveTextContent('Rep'); + it("renders without overview", () => { + const { container } = render( + {}} + loading={false} + /> + ); + expect(container).toHaveTextContent("Rep"); }); }); diff --git a/components/mobile-wrapper-dialog/MobileWrapperDialog.tsx b/components/mobile-wrapper-dialog/MobileWrapperDialog.tsx index fa17b2114e..d704a1e685 100644 --- a/components/mobile-wrapper-dialog/MobileWrapperDialog.tsx +++ b/components/mobile-wrapper-dialog/MobileWrapperDialog.tsx @@ -20,6 +20,7 @@ export default function MobileWrapperDialog({ fixedHeight, tabletModal, showScrollbar, + maxWidthClass, }: { readonly title?: string | undefined; readonly isOpen: boolean; @@ -32,6 +33,7 @@ export default function MobileWrapperDialog({ readonly fixedHeight?: boolean | undefined; readonly tabletModal?: boolean | undefined; readonly showScrollbar?: boolean | undefined; + readonly maxWidthClass?: string | undefined; }) { const { isCapacitor, isIos } = useCapacitor(); @@ -50,7 +52,7 @@ export default function MobileWrapperDialog({ const panelClassNames = `mobile-wrapper-dialog tw-pointer-events-auto tw-relative tw-w-screen${ tabletModal ? "" : " md:tw-max-w-screen-md" }${isIos ? "" : " tw-transform-gpu tw-will-change-transform"}${ - tabletModal ? " md:tw-w-full md:tw-max-w-md" : "" + tabletModal ? ` md:tw-w-full ${maxWidthClass ?? "md:tw-max-w-md"}` : "" }`; const containerClassNames = `tw-pointer-events-none tw-fixed tw-inset-x-0 tw-bottom-0 tw-flex tw-max-w-full tw-justify-center tw-pt-10${ diff --git a/components/react-query-wrapper/ReactQueryWrapper.tsx b/components/react-query-wrapper/ReactQueryWrapper.tsx index e47ec37546..7378eb3c13 100644 --- a/components/react-query-wrapper/ReactQueryWrapper.tsx +++ b/components/react-query-wrapper/ReactQueryWrapper.tsx @@ -106,6 +106,9 @@ export enum QueryKey { COMMUNITY_METRICS_SERIES = "COMMUNITY_METRICS_SERIES", MINT_METRICS = "MINT_METRICS", MARKETPLACE_PREVIEW = "MARKETPLACE_PREVIEW", + REP_OVERVIEW = "REP_OVERVIEW", + REP_CATEGORIES = "REP_CATEGORIES", + CIC_OVERVIEW = "CIC_OVERVIEW", } interface ProfileRatersParams { @@ -540,6 +543,9 @@ const createReactQueryContextValue = ( }) => { invalidateProfile(targetProfile); invalidateLogs(); + queryClient.invalidateQueries({ + queryKey: [QueryKey.CIC_OVERVIEW], + }); invalidateProfileRaters({ profile: targetProfile, matter: RateMatter.NIC, @@ -620,6 +626,12 @@ const createReactQueryContextValue = ( invalidateProfile(targetProfile); invalidateProfileRepRatings(targetProfile); invalidateLogs(); + queryClient.invalidateQueries({ + queryKey: [QueryKey.REP_OVERVIEW], + }); + queryClient.invalidateQueries({ + queryKey: [QueryKey.REP_CATEGORIES], + }); invalidateProfileRaters({ profile: targetProfile, matter: RateMatter.REP, @@ -866,6 +878,15 @@ const createReactQueryContextValue = ( queryClient.invalidateQueries({ queryKey: [QueryKey.PROFILE_REP_RATINGS], }); + queryClient.invalidateQueries({ + queryKey: [QueryKey.REP_OVERVIEW], + }); + queryClient.invalidateQueries({ + queryKey: [QueryKey.REP_CATEGORIES], + }); + queryClient.invalidateQueries({ + queryKey: [QueryKey.CIC_OVERVIEW], + }); queryClient.invalidateQueries({ queryKey: [QueryKey.COMMUNITY_MEMBERS_TOP], }); diff --git a/components/user/followers/UserPageFollowersModal.tsx b/components/user/followers/UserPageFollowersModal.tsx index 032b9c3994..0908be79f2 100644 --- a/components/user/followers/UserPageFollowersModal.tsx +++ b/components/user/followers/UserPageFollowersModal.tsx @@ -24,6 +24,7 @@ export default function UserPageFollowersModal({ tall fixedHeight tabletModal + maxWidthClass="md:tw-max-w-md" showScrollbar >
diff --git a/components/user/identity/header/UserPageIdentityHeader.tsx b/components/user/identity/header/UserPageIdentityHeader.tsx index 07c168b337..6e6448ad48 100644 --- a/components/user/identity/header/UserPageIdentityHeader.tsx +++ b/components/user/identity/header/UserPageIdentityHeader.tsx @@ -1,10 +1,13 @@ +import type { ApiCicOverview } from "@/generated/models/ApiCicOverview"; import type { ApiIdentity } from "@/generated/models/ApiIdentity"; import UserPageIdentityHeaderCIC from "./UserPageIdentityHeaderCIC"; export default function UserPageIdentityHeader({ profile, + cicOverview, }: { readonly profile: ApiIdentity; + readonly cicOverview: ApiCicOverview | null; }) { return (
@@ -12,11 +15,12 @@ export default function UserPageIdentityHeader({

Network Identity Check (NIC)

-

- Does the network believe this profile accurately represents its identity? +

+ Does the network believe this profile accurately represents its + identity?

- +
); } diff --git a/components/user/identity/header/UserPageIdentityHeaderCIC.tsx b/components/user/identity/header/UserPageIdentityHeaderCIC.tsx index d1cb7853ef..ff8c3821d3 100644 --- a/components/user/identity/header/UserPageIdentityHeaderCIC.tsx +++ b/components/user/identity/header/UserPageIdentityHeaderCIC.tsx @@ -1,58 +1,47 @@ "use client"; -import { QueryKey } from "@/components/react-query-wrapper/ReactQueryWrapper"; -import type { RatingWithProfileInfoAndLevel } from "@/entities/IProfile"; -import { SortDirection } from "@/entities/ISort"; import UserCICStatus from "@/components/user/utils/user-cic-status/UserCICStatus"; import UserCICTypeIconWrapper from "@/components/user/utils/user-cic-type/UserCICTypeIconWrapper"; +import type { ApiCicOverview } from "@/generated/models/ApiCicOverview"; import type { ApiIdentity } from "@/generated/models/ApiIdentity"; import { formatNumberWithCommas } from "@/helpers/Helpers"; -import type { Page } from "@/helpers/Types"; -import { commonApiFetch } from "@/services/api/common-api"; -import { ProfileRatersParamsOrderBy, RateMatter } from "@/types/enums"; -import { useQuery } from "@tanstack/react-query"; -import { useEffect, useState } from "react"; -import TopRaterAvatars from "../../rep/header/TopRaterAvatars"; +import { useMemo } from "react"; +import OverlappingAvatars from "@/components/common/OverlappingAvatars"; const TOP_RATERS_COUNT = 5; export default function UserPageIdentityHeaderCIC({ profile, + cicOverview, }: { readonly profile: ApiIdentity; + readonly cicOverview: ApiCicOverview | null; }) { - const [cicRating, setCicRating] = useState(profile.cic); + const cicRating = cicOverview?.total_cic ?? profile.cic; + const raterCount = cicOverview?.contributor_count ?? 0; - const { data: ratings } = useQuery>({ - queryKey: [ - QueryKey.PROFILE_RATERS, - { - handleOrWallet: profile.handle, - matter: RateMatter.NIC, - page: 1, - pageSize: 1, - order: SortDirection.DESC, - orderBy: ProfileRatersParamsOrderBy.RATING, - given: false, - }, - ], - queryFn: async () => - await commonApiFetch>({ - endpoint: `profiles/${profile.handle}/cic/ratings/by-rater`, - params: { - page: `${1}`, - page_size: `${1}`, - order: SortDirection.DESC.toLowerCase(), - order_by: ProfileRatersParamsOrderBy.RATING.toLowerCase(), - given: "false", - }, - }), - enabled: !!profile.handle, - }); - - useEffect(() => { - setCicRating(profile.cic); - }, [profile]); + const avatarItems = useMemo( + () => + (cicOverview?.contributors.data ?? []) + .slice(0, TOP_RATERS_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)} + + ), + })), + [cicOverview?.contributors.data] + ); return (
@@ -72,15 +61,16 @@ export default function UserPageIdentityHeaderCIC({
- + {avatarItems.length > 0 && ( + + )} - {formatNumberWithCommas(ratings?.count ?? 0)}{" "} - {(ratings?.count ?? 0) === 1 ? "rater" : "raters"} + {formatNumberWithCommas(raterCount)}{" "} + {raterCount === 1 ? "rater" : "raters"}
diff --git a/components/user/rep/RepCategoryPill.tsx b/components/user/rep/RepCategoryPill.tsx index de70adf8db..f839428737 100644 --- a/components/user/rep/RepCategoryPill.tsx +++ b/components/user/rep/RepCategoryPill.tsx @@ -1,57 +1,86 @@ -import type { RatingStats } from "@/entities/IProfile"; +import type { ApiRepCategory } from "@/generated/models/ApiRepCategory"; import { formatNumberWithCommas } from "@/helpers/Helpers"; import type { MouseEvent } from "react"; -import TopRaterAvatars from "./header/TopRaterAvatars"; +import { useMemo } from "react"; +import OverlappingAvatars from "@/components/common/OverlappingAvatars"; +import { getContributorLabel, type RepDirection } from "./UserPageRep.helpers"; const stopPropagation = (e: MouseEvent) => e.stopPropagation(); export default function RepCategoryPill({ - rep, - profileHandle, + category, canEdit, onEdit, compact = false, + direction = "received", }: { - readonly rep: RatingStats; - readonly profileHandle: string; + readonly category: ApiRepCategory; readonly canEdit: boolean; readonly onEdit: (category: string) => void; readonly compact?: boolean; + readonly direction?: RepDirection; }) { const paddingClass = compact ? "tw-px-3 tw-py-2" : "tw-px-4 tw-py-2.5"; + const avatarItems = useMemo( + () => + category.top_contributors.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)} + + ), + })), + [category.top_contributors] + ); + const content = ( <> - - {rep.category} + + {category.category} - {formatNumberWithCommas(rep.rating)} + {formatNumberWithCommas(category.total_rep)} · - - - + {avatarItems.length > 0 && ( + + stopPropagation(e)} + /> + + )} - {formatNumberWithCommas(rep.contributor_count)}{" "} - {rep.contributor_count === 1 ? "rater" : "raters"} + {formatNumberWithCommas(category.contributor_count)}{" "} + {getContributorLabel(direction, category.contributor_count)} - {!!rep.rater_contribution && ( + {!!category.authenticated_user_contribution && ( <> · - My Rate:{" "} + {direction === "given" ? "To Me:" : "My Rate:"}{" "} - {formatNumberWithCommas(rep.rater_contribution)} + {formatNumberWithCommas(category.authenticated_user_contribution)} @@ -65,7 +94,7 @@ export default function RepCategoryPill({ return (
- {repRates - ? formatNumberWithCommas(repRates.total_rep_rating) - : "\u2014"} + {overview ? formatNumberWithCommas(overview.total_rep) : "\u2014"}
- {repRates && ( + {overview && (
- {formatNumberWithCommas(repRates.number_of_raters)}{" "} - {repRates.number_of_raters === 1 ? "rater" : "raters"} + {formatNumberWithCommas(overview.contributor_count)}{" "} + {getContributorLabel( + repDirection, + overview.contributor_count + )}
)} @@ -236,29 +231,31 @@ export default function UserPageRepMobile({ NIC
- {formatNumberWithCommas(profile.cic)} + {formatNumberWithCommas(cicOverview?.total_cic ?? profile.cic)}
- + - +
-
- -
+ {cicAvatarItems.length > 0 && ( +
+ +
+ )} - {formatNumberWithCommas(nicRatings?.count ?? 0)}{" "} - {(nicRatings?.count ?? 0) === 1 ? "rater" : "raters"} + {formatNumberWithCommas(cicOverview?.contributor_count ?? 0)}{" "} + {(cicOverview?.contributor_count ?? 0) === 1 + ? "rater" + : "raters"}
@@ -275,37 +272,77 @@ export default function UserPageRepMobile({ exit={{ opacity: 0 }} transition={{ duration: 0.15, ease: "easeInOut" }} > + {/* Received / Given toggle */} +
+ + +
+ {/* Rep Categories */} - {reps.length > 0 && ( + {categories.length > 0 && (
-
+
Rep Categories
- {reps.slice(0, visibleCount).map((rep) => ( + {categories.slice(0, visibleCount).map((cat) => ( ))} - {reps.length > visibleCount && ( + {categories.length > visibleCount && ( )}
)} - {canEditRep && ( + {categories.length === 0 && ( + + )} + + {canEditRep && repDirection === "received" && (
- {/* Grant Rep Bottom Sheet */} setIsGrantRepOpen(false)} /> - {/* Rate NIC Bottom Sheet */} setIsNicRateOpen(false)} tabletModal + maxWidthClass="md:tw-max-w-md" >
diff --git a/components/user/rep/header/TopRaterAvatars.tsx b/components/user/rep/header/TopRaterAvatars.tsx deleted file mode 100644 index c1f5d4b822..0000000000 --- a/components/user/rep/header/TopRaterAvatars.tsx +++ /dev/null @@ -1,125 +0,0 @@ -"use client"; - -import { useQuery, useQueries } from "@tanstack/react-query"; -import { QueryKey } from "@/components/react-query-wrapper/ReactQueryWrapper"; -import { commonApiFetch } from "@/services/api/common-api"; -import type { RatingWithProfileInfoAndLevel } from "@/entities/IProfile"; -import type { ApiIdentity } from "@/generated/models/ApiIdentity"; -import type { Page } from "@/helpers/Types"; -import OverlappingAvatars from "@/components/common/OverlappingAvatars"; -import { RateMatter } from "@/types/enums"; -import { formatNumberWithCommas } from "@/helpers/Helpers"; -import type { MouseEvent } from "react"; - -const STALE_TIME = 5 * 60 * 1000; - -export default function TopRaterAvatars({ - handleOrWallet, - category, - matter = RateMatter.REP, - withLinks = true, - onAvatarClick, - count = 5, - size = "sm", -}: { - readonly handleOrWallet: string; - readonly category?: string; - readonly matter?: RateMatter.REP | RateMatter.NIC; - readonly withLinks?: boolean; - readonly onAvatarClick?: (e: MouseEvent) => void; - readonly count?: number; - readonly size?: "sm" | "md"; -}) { - const params: Record = { - page: "1", - page_size: `${count}`, - order: "desc", - order_by: "rating", - given: "false", - }; - if (matter === RateMatter.REP && category) { - params["category"] = category; - } - - const ratingsEndpoint = - matter === RateMatter.NIC - ? `profiles/${handleOrWallet}/cic/ratings/by-rater` - : `profiles/${handleOrWallet}/rep/ratings/by-rater`; - - const { data: ratersPage } = useQuery>({ - queryKey: [ - QueryKey.PROFILE_RATERS, - { - handleOrWallet: handleOrWallet.toLowerCase(), - matter, - category, - count, - }, - ], - queryFn: async () => - await commonApiFetch>({ - endpoint: ratingsEndpoint, - params, - }), - enabled: !!handleOrWallet, - staleTime: STALE_TIME, - }); - - const raterHandles = ratersPage?.data.map((r) => r.handle) ?? []; - - const ratingByHandle = new Map( - ratersPage?.data.map((r) => [r.handle.toLowerCase(), r.rating]) ?? [] - ); - - const identityQueries = useQueries({ - queries: raterHandles.map((handle) => ({ - queryKey: [QueryKey.PROFILE, handle.toLowerCase()], - queryFn: async () => - await commonApiFetch({ - endpoint: `identities/${handle.toLowerCase()}`, - }), - enabled: !!handle, - staleTime: STALE_TIME, - })), - }); - - const items = identityQueries - .filter((q) => q.data) - .map((q) => { - const identity = q.data!; - const target = identity.handle ?? identity.primary_wallet; - const rating = target - ? ratingByHandle.get(target.toLowerCase()) - : undefined; - const tooltipContent = - rating === undefined ? undefined : ( - - {target} · {formatNumberWithCommas(rating)} - - ); - return { - key: target, - pfpUrl: identity.pfp ?? null, - ...(withLinks && target ? { href: `/${target}` } : {}), - ariaLabel: target, - fallback: identity.handle - ? identity.handle.charAt(0).toUpperCase() - : "?", - title: target, - tooltipContent, - }; - }); - - if (items.length === 0) { - return null; - } - - return ( - onAvatarClick?.(e)} - /> - ); -} diff --git a/components/user/rep/header/UserPageRepHeader.tsx b/components/user/rep/header/UserPageRepHeader.tsx index f12becacf8..bbb272e0a0 100644 --- a/components/user/rep/header/UserPageRepHeader.tsx +++ b/components/user/rep/header/UserPageRepHeader.tsx @@ -1,40 +1,46 @@ "use client"; import { AuthContext } from "@/components/auth/Auth"; -import type { ApiProfileRepRatesState } from "@/entities/IProfile"; +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 { useContext, useEffect, useMemo, useState } from "react"; import RepCategoryPill from "../RepCategoryPill"; import UserPageRepModifyModal from "../modify-rep/UserPageRepModifyModal"; import GrantRepDialog from "../new-rep/GrantRepDialog"; import { getCanEditRep, - sortRepsByRatingAndContributors, + getContributorLabel, + type RepDirection, } from "../UserPageRep.helpers"; export default function UserPageRepHeader({ - repRates, + overview, + categories, profile, + repDirection, + onRepDirectionChange, + loading, }: { - readonly repRates: ApiProfileRepRatesState | null; + readonly overview: ApiRepOverview | null; + readonly categories: ApiRepCategory[]; readonly profile: ApiIdentity; + readonly repDirection: RepDirection; + readonly onRepDirectionChange: (direction: RepDirection) => void; + readonly loading: boolean; }) { const { connectedProfile, activeProfileProxy } = useContext(AuthContext); - const allReps = useMemo( - () => sortRepsByRatingAndContributors(repRates?.rating_stats ?? []), - [repRates?.rating_stats] - ); - const [visibleCount, setVisibleCount] = useState(5); useEffect(() => { setVisibleCount(5); - }, [repRates?.rating_stats]); + }, [categories]); - const visibleReps = allReps.slice(0, visibleCount); - const hasMore = allReps.length > visibleCount; + const visibleCategories = categories.slice(0, visibleCount); + const hasMore = categories.length > visibleCount; const canEditRep = useMemo( () => @@ -64,35 +70,83 @@ export default function UserPageRepHeader({ Rep

- What others recognize this identity for. + {repDirection === "received" + ? "What others recognize this identity for." + : "What this identity recognizes others for."}

-
-
- Total Rep -
-
- {repRates - ? formatNumberWithCommas(repRates.total_rep_rating) - : ""} -
- {repRates && ( + {overview ? ( +
+
+ Total Rep +
+
+ {formatNumberWithCommas(overview.total_rep)} +
- {formatNumberWithCommas(repRates.number_of_raters)}{" "} - {repRates.number_of_raters === 1 ? "rater" : "raters"} + {formatNumberWithCommas(overview.contributor_count)}{" "} + {getContributorLabel( + repDirection, + overview.contributor_count + )} - )} -
+
+ ) : ( +
+
+ Total Rep +
+
+ — +
+
+ )}
- {(visibleReps.length > 0 || canEditRep) && ( +
+ + +
+ + {(visibleCategories.length > 0 || + (canEditRep && repDirection === "received")) && (
Rep Categories
- {canEditRep && ( + {canEditRep && repDirection === "received" && ( )} - {visibleReps.map((rep) => ( + {visibleCategories.map((cat) => ( ))} {hasMore && ( @@ -127,12 +181,30 @@ export default function UserPageRepHeader({ onClick={() => setVisibleCount((prev) => prev + 10)} className="tw-inline-flex tw-h-11 tw-cursor-pointer tw-items-center tw-gap-2 tw-rounded-lg tw-border tw-border-solid tw-border-white/10 tw-bg-white/5 tw-px-4 tw-text-sm tw-font-medium tw-text-iron-400 tw-backdrop-blur-md tw-transition-all tw-duration-300 tw-ease-out hover:tw-border-white/20 hover:tw-bg-white/10 hover:tw-text-white" > - +{allReps.length - visibleCount} more + +{categories.length - visibleCount} more )}
)} + + {categories.length === 0 && + !loading && + !(canEditRep && repDirection === "received") && ( +

+ {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") && ( +
+
+
+ )}
@@ -146,7 +218,7 @@ export default function UserPageRepHeader({ setIsGrantRepOpen(false)} /> diff --git a/components/user/rep/new-rep/GrantRepDialog.tsx b/components/user/rep/new-rep/GrantRepDialog.tsx index 0347135f87..cb2cb3921f 100644 --- a/components/user/rep/new-rep/GrantRepDialog.tsx +++ b/components/user/rep/new-rep/GrantRepDialog.tsx @@ -1,4 +1,4 @@ -import type { ApiProfileRepRatesState } from "@/entities/IProfile"; +import type { ApiRepOverview } from "@/generated/models/ApiRepOverview"; import type { ApiIdentity } from "@/generated/models/ApiIdentity"; import { RateMatter } from "@/types/enums"; import MobileWrapperDialog from "@/components/mobile-wrapper-dialog/MobileWrapperDialog"; @@ -7,12 +7,12 @@ import UserPageRepNewRep from "./UserPageRepNewRep"; export default function GrantRepDialog({ profile, - repRates, + overview, isOpen, onClose, }: { readonly profile: ApiIdentity; - readonly repRates: ApiProfileRepRatesState | null; + readonly overview: ApiRepOverview | null; readonly isOpen: boolean; readonly onClose: () => void; }) { @@ -22,11 +22,12 @@ export default function GrantRepDialog({ isOpen={isOpen} onClose={onClose} tabletModal + maxWidthClass="md:tw-max-w-md" > diff --git a/components/user/rep/new-rep/UserPageRepNewRep.tsx b/components/user/rep/new-rep/UserPageRepNewRep.tsx index 10815e6b52..8539a9959a 100644 --- a/components/user/rep/new-rep/UserPageRepNewRep.tsx +++ b/components/user/rep/new-rep/UserPageRepNewRep.tsx @@ -1,17 +1,17 @@ "use client"; -import type { ApiProfileRepRatesState } from "@/entities/IProfile"; +import type { ApiRepOverview } from "@/generated/models/ApiRepOverview"; import UserPageRepNewRepSearch from "./UserPageRepNewRepSearch"; import type { ApiIdentity } from "@/generated/models/ApiIdentity"; export default function UserPageRepNewRep({ profile, - repRates, + overview, onSuccess, onCancel, }: { readonly profile: ApiIdentity; - readonly repRates: ApiProfileRepRatesState | null; + readonly overview: ApiRepOverview | null; readonly onSuccess?: () => void; readonly onCancel?: () => void; }) { @@ -19,7 +19,7 @@ export default function UserPageRepNewRep({ return ( { }; export default function UserPageRepNewRepSearch({ - repRates, + overview, profile, onSuccess, onCancel, }: { - readonly repRates: ApiProfileRepRatesState | null; + readonly overview: ApiRepOverview | null; readonly profile: ApiIdentity; readonly onSuccess?: (() => void) | undefined; readonly onCancel?: (() => void) | undefined; @@ -259,7 +259,7 @@ export default function UserPageRepNewRepSearch({ {formatNumberWithCommas( - repRates?.total_rep_rating_by_rater ?? 0 + overview?.authenticated_user_contribution ?? 0 )} diff --git a/generated/models/ApiCicContributor.ts b/generated/models/ApiCicContributor.ts new file mode 100644 index 0000000000..17ebccf5aa --- /dev/null +++ b/generated/models/ApiCicContributor.ts @@ -0,0 +1,45 @@ +// @ts-nocheck +/** + * 6529.io API + * This is the API interface description. Brief terminology overview and an authentication example can be found at https://6529.io/about/api. + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { ApiProfileMin } from '../models/ApiProfileMin'; +import { HttpFile } from '../http/http'; + +export class ApiCicContributor { + 'contribution': number; + 'profile': ApiProfileMin; + + static readonly discriminator: string | undefined = undefined; + + static readonly mapping: {[index: string]: string} | undefined = undefined; + + static readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [ + { + "name": "contribution", + "baseName": "contribution", + "type": "number", + "format": "int64" + }, + { + "name": "profile", + "baseName": "profile", + "type": "ApiProfileMin", + "format": "" + } ]; + + static getAttributeTypeMap() { + return ApiCicContributor.attributeTypeMap; + } + + public constructor() { + } +} diff --git a/generated/models/ApiCicContributorsPage.ts b/generated/models/ApiCicContributorsPage.ts new file mode 100644 index 0000000000..fc7fca88d0 --- /dev/null +++ b/generated/models/ApiCicContributorsPage.ts @@ -0,0 +1,52 @@ +// @ts-nocheck +/** + * 6529.io API + * This is the API interface description. Brief terminology overview and an authentication example can be found at https://6529.io/about/api. + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { ApiCicContributor } from '../models/ApiCicContributor'; +import { HttpFile } from '../http/http'; + +export class ApiCicContributorsPage { + 'data': Array; + 'page': number; + 'next': boolean; + + static readonly discriminator: string | undefined = undefined; + + static readonly mapping: {[index: string]: string} | undefined = undefined; + + static readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [ + { + "name": "data", + "baseName": "data", + "type": "Array", + "format": "" + }, + { + "name": "page", + "baseName": "page", + "type": "number", + "format": "int64" + }, + { + "name": "next", + "baseName": "next", + "type": "boolean", + "format": "" + } ]; + + static getAttributeTypeMap() { + return ApiCicContributorsPage.attributeTypeMap; + } + + public constructor() { + } +} diff --git a/generated/models/ApiCicOverview.ts b/generated/models/ApiCicOverview.ts new file mode 100644 index 0000000000..98f9bbadd7 --- /dev/null +++ b/generated/models/ApiCicOverview.ts @@ -0,0 +1,59 @@ +// @ts-nocheck +/** + * 6529.io API + * This is the API interface description. Brief terminology overview and an authentication example can be found at https://6529.io/about/api. + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { ApiCicContributorsPage } from '../models/ApiCicContributorsPage'; +import { HttpFile } from '../http/http'; + +export class ApiCicOverview { + 'total_cic': number; + 'authenticated_user_contribution': number | null; + 'contributor_count': number; + 'contributors': ApiCicContributorsPage; + + static readonly discriminator: string | undefined = undefined; + + static readonly mapping: {[index: string]: string} | undefined = undefined; + + static readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [ + { + "name": "total_cic", + "baseName": "total_cic", + "type": "number", + "format": "int64" + }, + { + "name": "authenticated_user_contribution", + "baseName": "authenticated_user_contribution", + "type": "number", + "format": "int64" + }, + { + "name": "contributor_count", + "baseName": "contributor_count", + "type": "number", + "format": "int64" + }, + { + "name": "contributors", + "baseName": "contributors", + "type": "ApiCicContributorsPage", + "format": "" + } ]; + + static getAttributeTypeMap() { + return ApiCicOverview.attributeTypeMap; + } + + public constructor() { + } +} diff --git a/generated/models/ApiRepCategoriesPage.ts b/generated/models/ApiRepCategoriesPage.ts new file mode 100644 index 0000000000..10695d46b3 --- /dev/null +++ b/generated/models/ApiRepCategoriesPage.ts @@ -0,0 +1,52 @@ +// @ts-nocheck +/** + * 6529.io API + * This is the API interface description. Brief terminology overview and an authentication example can be found at https://6529.io/about/api. + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { ApiRepCategory } from '../models/ApiRepCategory'; +import { HttpFile } from '../http/http'; + +export class ApiRepCategoriesPage { + 'data': Array; + 'page': number; + 'next': boolean; + + static readonly discriminator: string | undefined = undefined; + + static readonly mapping: {[index: string]: string} | undefined = undefined; + + static readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [ + { + "name": "data", + "baseName": "data", + "type": "Array", + "format": "" + }, + { + "name": "page", + "baseName": "page", + "type": "number", + "format": "int64" + }, + { + "name": "next", + "baseName": "next", + "type": "boolean", + "format": "" + } ]; + + static getAttributeTypeMap() { + return ApiRepCategoriesPage.attributeTypeMap; + } + + public constructor() { + } +} diff --git a/generated/models/ApiRepCategory.ts b/generated/models/ApiRepCategory.ts new file mode 100644 index 0000000000..3d0afdc352 --- /dev/null +++ b/generated/models/ApiRepCategory.ts @@ -0,0 +1,66 @@ +// @ts-nocheck +/** + * 6529.io API + * This is the API interface description. Brief terminology overview and an authentication example can be found at https://6529.io/about/api. + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { ApiRepContributor } from '../models/ApiRepContributor'; +import { HttpFile } from '../http/http'; + +export class ApiRepCategory { + 'category': string; + 'total_rep': number; + 'contributor_count': number; + 'authenticated_user_contribution': number | null; + 'top_contributors': Array; + + static readonly discriminator: string | undefined = undefined; + + static readonly mapping: {[index: string]: string} | undefined = undefined; + + static readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [ + { + "name": "category", + "baseName": "category", + "type": "string", + "format": "" + }, + { + "name": "total_rep", + "baseName": "total_rep", + "type": "number", + "format": "int64" + }, + { + "name": "contributor_count", + "baseName": "contributor_count", + "type": "number", + "format": "int64" + }, + { + "name": "authenticated_user_contribution", + "baseName": "authenticated_user_contribution", + "type": "number", + "format": "int64" + }, + { + "name": "top_contributors", + "baseName": "top_contributors", + "type": "Array", + "format": "" + } ]; + + static getAttributeTypeMap() { + return ApiRepCategory.attributeTypeMap; + } + + public constructor() { + } +} diff --git a/generated/models/ApiRepContributor.ts b/generated/models/ApiRepContributor.ts new file mode 100644 index 0000000000..3e992a8c6b --- /dev/null +++ b/generated/models/ApiRepContributor.ts @@ -0,0 +1,45 @@ +// @ts-nocheck +/** + * 6529.io API + * This is the API interface description. Brief terminology overview and an authentication example can be found at https://6529.io/about/api. + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { ApiProfileMin } from '../models/ApiProfileMin'; +import { HttpFile } from '../http/http'; + +export class ApiRepContributor { + 'contribution': number; + 'profile': ApiProfileMin; + + static readonly discriminator: string | undefined = undefined; + + static readonly mapping: {[index: string]: string} | undefined = undefined; + + static readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [ + { + "name": "contribution", + "baseName": "contribution", + "type": "number", + "format": "int64" + }, + { + "name": "profile", + "baseName": "profile", + "type": "ApiProfileMin", + "format": "" + } ]; + + static getAttributeTypeMap() { + return ApiRepContributor.attributeTypeMap; + } + + public constructor() { + } +} diff --git a/generated/models/ApiRepContributorsPage.ts b/generated/models/ApiRepContributorsPage.ts new file mode 100644 index 0000000000..baea5ab4d5 --- /dev/null +++ b/generated/models/ApiRepContributorsPage.ts @@ -0,0 +1,52 @@ +// @ts-nocheck +/** + * 6529.io API + * This is the API interface description. Brief terminology overview and an authentication example can be found at https://6529.io/about/api. + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { ApiRepContributor } from '../models/ApiRepContributor'; +import { HttpFile } from '../http/http'; + +export class ApiRepContributorsPage { + 'data': Array; + 'page': number; + 'next': boolean; + + static readonly discriminator: string | undefined = undefined; + + static readonly mapping: {[index: string]: string} | undefined = undefined; + + static readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [ + { + "name": "data", + "baseName": "data", + "type": "Array", + "format": "" + }, + { + "name": "page", + "baseName": "page", + "type": "number", + "format": "int64" + }, + { + "name": "next", + "baseName": "next", + "type": "boolean", + "format": "" + } ]; + + static getAttributeTypeMap() { + return ApiRepContributorsPage.attributeTypeMap; + } + + public constructor() { + } +} diff --git a/generated/models/ApiRepDirection.ts b/generated/models/ApiRepDirection.ts new file mode 100644 index 0000000000..d6d87b3834 --- /dev/null +++ b/generated/models/ApiRepDirection.ts @@ -0,0 +1,19 @@ +// @ts-nocheck +/** + * 6529.io API + * This is the API interface description. Brief terminology overview and an authentication example can be found at https://6529.io/about/api. + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { HttpFile } from '../http/http'; + +export enum ApiRepDirection { + Incoming = 'incoming', + Outgoing = 'outgoing' +} diff --git a/generated/models/ApiRepOverview.ts b/generated/models/ApiRepOverview.ts new file mode 100644 index 0000000000..0817b44ed3 --- /dev/null +++ b/generated/models/ApiRepOverview.ts @@ -0,0 +1,59 @@ +// @ts-nocheck +/** + * 6529.io API + * This is the API interface description. Brief terminology overview and an authentication example can be found at https://6529.io/about/api. + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { ApiRepContributorsPage } from '../models/ApiRepContributorsPage'; +import { HttpFile } from '../http/http'; + +export class ApiRepOverview { + 'total_rep': number; + 'authenticated_user_contribution': number | null; + 'contributor_count': number; + 'contributors': ApiRepContributorsPage; + + static readonly discriminator: string | undefined = undefined; + + static readonly mapping: {[index: string]: string} | undefined = undefined; + + static readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [ + { + "name": "total_rep", + "baseName": "total_rep", + "type": "number", + "format": "int64" + }, + { + "name": "authenticated_user_contribution", + "baseName": "authenticated_user_contribution", + "type": "number", + "format": "int64" + }, + { + "name": "contributor_count", + "baseName": "contributor_count", + "type": "number", + "format": "int64" + }, + { + "name": "contributors", + "baseName": "contributors", + "type": "ApiRepContributorsPage", + "format": "" + } ]; + + static getAttributeTypeMap() { + return ApiRepOverview.attributeTypeMap; + } + + public constructor() { + } +} diff --git a/generated/models/ObjectSerializer.ts b/generated/models/ObjectSerializer.ts index b59001ccfd..c74ab6f13d 100644 --- a/generated/models/ObjectSerializer.ts +++ b/generated/models/ObjectSerializer.ts @@ -20,6 +20,9 @@ export * from '../models/ApiBulkRepTarget'; export * from '../models/ApiChangeGroupVisibility'; export * from '../models/ApiChangeProfileCicRating'; export * from '../models/ApiChangeProfileRepRating'; +export * from '../models/ApiCicContributor'; +export * from '../models/ApiCicContributorsPage'; +export * from '../models/ApiCicOverview'; export * from '../models/ApiCommunityMemberMinimal'; export * from '../models/ApiCommunityMemberOverview'; export * from '../models/ApiCommunityMembersPage'; @@ -150,6 +153,12 @@ export * from '../models/ApiRatingWithProfileInfoAndLevelPage'; export * from '../models/ApiRedeemRefreshTokenRequest'; export * from '../models/ApiRedeemRefreshTokenResponse'; export * from '../models/ApiRegisterPushNotificationTokenRequest'; +export * from '../models/ApiRepCategoriesPage'; +export * from '../models/ApiRepCategory'; +export * from '../models/ApiRepContributor'; +export * from '../models/ApiRepContributorsPage'; +export * from '../models/ApiRepDirection'; +export * from '../models/ApiRepOverview'; export * from '../models/ApiRepRating'; export * from '../models/ApiReplyToDrop'; export * from '../models/ApiReplyToDropResponse'; @@ -284,6 +293,9 @@ import { ApiBulkRepTarget } from '../models/ApiBulkRepTarget'; import { ApiChangeGroupVisibility } from '../models/ApiChangeGroupVisibility'; import { ApiChangeProfileCicRating } from '../models/ApiChangeProfileCicRating'; import { ApiChangeProfileRepRating } from '../models/ApiChangeProfileRepRating'; +import { ApiCicContributor } from '../models/ApiCicContributor'; +import { ApiCicContributorsPage } from '../models/ApiCicContributorsPage'; +import { ApiCicOverview } from '../models/ApiCicOverview'; import { ApiCommunityMemberMinimal } from '../models/ApiCommunityMemberMinimal'; import { ApiCommunityMemberOverview } from '../models/ApiCommunityMemberOverview'; import { ApiCommunityMembersPage } from '../models/ApiCommunityMembersPage'; @@ -414,6 +426,12 @@ import { ApiRatingWithProfileInfoAndLevelPage } from '../models/ApiRatingWithPro import { ApiRedeemRefreshTokenRequest } from '../models/ApiRedeemRefreshTokenRequest'; import { ApiRedeemRefreshTokenResponse } from '../models/ApiRedeemRefreshTokenResponse'; import { ApiRegisterPushNotificationTokenRequest } from '../models/ApiRegisterPushNotificationTokenRequest'; +import { ApiRepCategoriesPage } from '../models/ApiRepCategoriesPage'; +import { ApiRepCategory } from '../models/ApiRepCategory'; +import { ApiRepContributor } from '../models/ApiRepContributor'; +import { ApiRepContributorsPage } from '../models/ApiRepContributorsPage'; +import { ApiRepDirection } from '../models/ApiRepDirection'; +import { ApiRepOverview } from '../models/ApiRepOverview'; import { ApiRepRating } from '../models/ApiRepRating'; import { ApiReplyToDrop } from '../models/ApiReplyToDrop'; import { ApiReplyToDropResponse } from '../models/ApiReplyToDropResponse'; @@ -558,6 +576,7 @@ let enumsMap: Set = new Set([ "ApiProfileClassification", "ApiProfileProxyActionType", "ApiRateMatter", + "ApiRepDirection", "ApiUpcomingMemeSubscriptionStatusSourceEnum", "ApiWaveCreditScope", "ApiWaveCreditType", @@ -596,6 +615,9 @@ let typeMap: {[index: string]: any} = { "ApiChangeGroupVisibility": ApiChangeGroupVisibility, "ApiChangeProfileCicRating": ApiChangeProfileCicRating, "ApiChangeProfileRepRating": ApiChangeProfileRepRating, + "ApiCicContributor": ApiCicContributor, + "ApiCicContributorsPage": ApiCicContributorsPage, + "ApiCicOverview": ApiCicOverview, "ApiCommunityMemberMinimal": ApiCommunityMemberMinimal, "ApiCommunityMemberOverview": ApiCommunityMemberOverview, "ApiCommunityMembersPage": ApiCommunityMembersPage, @@ -712,6 +734,11 @@ let typeMap: {[index: string]: any} = { "ApiRedeemRefreshTokenRequest": ApiRedeemRefreshTokenRequest, "ApiRedeemRefreshTokenResponse": ApiRedeemRefreshTokenResponse, "ApiRegisterPushNotificationTokenRequest": ApiRegisterPushNotificationTokenRequest, + "ApiRepCategoriesPage": ApiRepCategoriesPage, + "ApiRepCategory": ApiRepCategory, + "ApiRepContributor": ApiRepContributor, + "ApiRepContributorsPage": ApiRepContributorsPage, + "ApiRepOverview": ApiRepOverview, "ApiRepRating": ApiRepRating, "ApiReplyToDrop": ApiReplyToDrop, "ApiReplyToDropResponse": ApiReplyToDropResponse, diff --git a/openapi.yaml b/openapi.yaml index 547d4f0aa6..1fada4a708 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2705,6 +2705,84 @@ paths: $ref: "#/components/schemas/ApiRatingWithProfileInfoAndLevelPage" "404": description: Profile not found + /profiles/{identity}/cic/overview: + get: + tags: + - ProfileCIC + summary: Get profile CIC overview + operationId: getProfileCicOverview + parameters: + - name: identity + in: path + required: true + schema: + type: string + - name: direction + in: query + required: false + schema: + $ref: "#/components/schemas/ApiRepDirection" + - name: page + in: query + required: false + schema: + type: integer + minimum: 1 + - name: page_size + in: query + required: false + schema: + type: integer + minimum: 1 + maximum: 200 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/ApiCicOverview" + "404": + description: Profile not found + /profiles/{identity}/cic/contributors: + get: + tags: + - ProfileCIC + summary: Get profile CIC contributors + operationId: getProfileCicContributors + parameters: + - name: identity + in: path + required: true + schema: + type: string + - name: direction + in: query + required: false + schema: + $ref: "#/components/schemas/ApiRepDirection" + - name: page + in: query + required: false + schema: + type: integer + minimum: 1 + - name: page_size + in: query + required: false + schema: + type: integer + minimum: 1 + maximum: 200 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/ApiCicContributorsPage" + "404": + description: Profile not found /profiles/{identity}/rep/rating: post: tags: @@ -2820,6 +2898,138 @@ paths: $ref: "#/components/schemas/ApiRatingWithProfileInfoAndLevelPage" "404": description: Profile not found + /profiles/{identity}/rep/overview: + get: + tags: + - ProfileREP + summary: Get profile REP overview + operationId: getProfileRepOverview + parameters: + - name: identity + in: path + required: true + schema: + type: string + - name: direction + in: query + required: false + schema: + $ref: "#/components/schemas/ApiRepDirection" + - name: page + in: query + required: false + schema: + type: integer + minimum: 1 + - name: page_size + in: query + required: false + schema: + type: integer + minimum: 1 + maximum: 200 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/ApiRepOverview" + "404": + description: Profile not found + /profiles/{identity}/rep/categories: + get: + tags: + - ProfileREP + summary: Get profile REP categories + operationId: getProfileRepCategories + parameters: + - name: identity + in: path + required: true + schema: + type: string + - name: direction + in: query + required: false + schema: + $ref: "#/components/schemas/ApiRepDirection" + - name: page + in: query + required: false + schema: + type: integer + minimum: 1 + - name: page_size + in: query + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + - name: top_contributors_limit + in: query + required: false + schema: + type: integer + minimum: 1 + maximum: 10 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/ApiRepCategoriesPage" + "404": + description: Profile not found + /profiles/{identity}/rep/categories/{category}/contributors: + get: + tags: + - ProfileREP + summary: Get profile REP contributors for category + operationId: getProfileRepCategoryContributors + parameters: + - name: identity + in: path + required: true + schema: + type: string + - name: category + in: path + required: true + schema: + type: string + minLength: 1 + maxLength: 100 + pattern: ^[a-zA-Z0-9?!,."() ]{1,100}$ + - name: direction + in: query + required: false + schema: + $ref: "#/components/schemas/ApiRepDirection" + - name: page + in: query + required: false + schema: + type: integer + minimum: 1 + - name: page_size + in: query + required: false + schema: + type: integer + minimum: 1 + maximum: 200 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/ApiRepContributorsPage" + "404": + description: Profile not found /profiles: post: tags: @@ -4843,6 +5053,22 @@ paths: required: false schema: type: string + - name: min_price + in: query + description: Optional minimum price filter used when sorting by PRICE + required: false + schema: + type: number + format: double + minimum: 0 + - name: max_price + in: query + description: Optional maximum price filter used when sorting by PRICE + required: false + schema: + type: number + format: double + minimum: 0 - name: curated_by_group in: query required: false @@ -6130,6 +6356,48 @@ components: maxLength: 100 minLength: 1 pattern: ^[a-zA-Z0-9?!,."() ]{1,100}$ + ApiCicContributor: + type: object + required: + - contribution + - profile + properties: + contribution: + type: number + format: int64 + profile: + $ref: "#/components/schemas/ApiProfileMin" + ApiCicContributorsPage: + type: object + required: + - data + allOf: + - $ref: "#/components/schemas/ApiPageWithoutCount" + properties: + data: + type: array + items: + $ref: "#/components/schemas/ApiCicContributor" + ApiCicOverview: + type: object + required: + - total_cic + - authenticated_user_contribution + - contributor_count + - contributors + properties: + total_cic: + type: number + format: int64 + authenticated_user_contribution: + type: number + format: int64 + nullable: true + contributor_count: + type: number + format: int64 + contributors: + $ref: "#/components/schemas/ApiCicContributorsPage" ApiCommunityMemberMinimal: type: object required: @@ -9288,6 +9556,69 @@ components: platform: type: string description: Optional platform (ios, android, web) + ApiRepCategoriesPage: + type: object + required: + - data + allOf: + - $ref: "#/components/schemas/ApiPageWithoutCount" + properties: + data: + type: array + items: + $ref: "#/components/schemas/ApiRepCategory" + ApiRepCategory: + type: object + required: + - category + - total_rep + - contributor_count + - authenticated_user_contribution + - top_contributors + properties: + category: + type: string + total_rep: + type: number + format: int64 + contributor_count: + type: number + format: int64 + authenticated_user_contribution: + type: number + format: int64 + nullable: true + top_contributors: + type: array + items: + $ref: "#/components/schemas/ApiRepContributor" + ApiRepContributor: + type: object + required: + - contribution + - profile + properties: + contribution: + type: number + format: int64 + profile: + $ref: "#/components/schemas/ApiProfileMin" + ApiRepContributorsPage: + type: object + required: + - data + allOf: + - $ref: "#/components/schemas/ApiPageWithoutCount" + properties: + data: + type: array + items: + $ref: "#/components/schemas/ApiRepContributor" + ApiRepDirection: + type: string + enum: + - incoming + - outgoing ApiReplyToDrop: type: object required: @@ -9315,6 +9646,26 @@ components: type: boolean drop: $ref: "#/components/schemas/ApiDropWithoutWave" + ApiRepOverview: + type: object + required: + - total_rep + - authenticated_user_contribution + - contributor_count + - contributors + properties: + total_rep: + type: number + format: int64 + authenticated_user_contribution: + type: number + format: int64 + nullable: true + contributor_count: + type: number + format: int64 + contributors: + $ref: "#/components/schemas/ApiRepContributorsPage" ApiRepRating: type: object required: diff --git a/package-lock.json b/package-lock.json index 86ddc93fe0..38a6e2f0b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22487,6 +22487,7 @@ }, "node_modules/playwright/node_modules/fsevents": { "version": "2.3.2", + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -25822,6 +25823,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -25838,6 +25840,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -25854,6 +25857,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -25870,6 +25874,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -25884,6 +25889,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -25900,6 +25906,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -25916,6 +25923,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -25932,6 +25940,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -25948,6 +25957,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -25964,6 +25974,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -25980,6 +25991,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -25996,6 +26008,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26012,6 +26025,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26028,6 +26042,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26044,6 +26059,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26060,6 +26076,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26076,6 +26093,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26092,6 +26110,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26108,6 +26127,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26124,6 +26144,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26140,6 +26161,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26156,6 +26178,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26172,6 +26195,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26188,6 +26212,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26204,6 +26229,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26220,6 +26246,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [