From 0c59207aa9c9b80004ca0d64b966ceb5d5e51791 Mon Sep 17 00:00:00 2001 From: ragnep Date: Thu, 12 Mar 2026 11:40:00 +0200 Subject: [PATCH 1/8] brain layout change and created waves Signed-off-by: ragnep --- .../user/brain/UserPageBrainSidebar.test.tsx | 156 +++++++++++++ .../user/brain/UserPageDrops.test.tsx | 26 ++- .../user/brain/UserPageBrainSidebar.tsx | 21 ++ .../brain/UserPageBrainSidebarCreated.tsx | 217 ++++++++++++++++++ ...rPageBrainSidebarMostActivePlaceholder.tsx | 14 ++ components/user/brain/UserPageDrops.tsx | 13 +- 6 files changed, 435 insertions(+), 12 deletions(-) create mode 100644 __tests__/components/user/brain/UserPageBrainSidebar.test.tsx create mode 100644 components/user/brain/UserPageBrainSidebar.tsx create mode 100644 components/user/brain/UserPageBrainSidebarCreated.tsx create mode 100644 components/user/brain/UserPageBrainSidebarMostActivePlaceholder.tsx diff --git a/__tests__/components/user/brain/UserPageBrainSidebar.test.tsx b/__tests__/components/user/brain/UserPageBrainSidebar.test.tsx new file mode 100644 index 0000000000..bfa7f113cb --- /dev/null +++ b/__tests__/components/user/brain/UserPageBrainSidebar.test.tsx @@ -0,0 +1,156 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import UserPageBrainSidebar from "@/components/user/brain/UserPageBrainSidebar"; +import { useWaves } from "@/hooks/useWaves"; + +jest.mock("next/image", () => ({ + __esModule: true, + default: ({ alt, fill, unoptimized, ...props }: any) => ( + {alt + ), +})); +jest.mock("next/link", () => ({ + __esModule: true, + default: ({ children, href, prefetch, ...props }: any) => ( + + {children} + + ), +})); +jest.mock("@/hooks/useWaves", () => ({ + useWaves: jest.fn(), +})); + +const mockedUseWaves = useWaves as jest.MockedFunction; + +const baseProfile = { + handle: "kanetix", + display: "Kanetix", + primary_wallet: "0xabc", +} as any; + +const makeWave = (overrides: Record = {}) => + ({ + id: "wave-1", + name: "TDH Name Vote", + picture: null, + pinned: false, + author: { + handle: null, + banner1_color: null, + banner2_color: null, + }, + visibility: { + scope: { + group: null, + }, + }, + chat: { scope: { group: { is_direct_message: false } } }, + metrics: { + drops_count: 12, + subscribers_count: 25, + latest_drop_timestamp: Date.now(), + }, + ...overrides, + }) as any; + +describe("UserPageBrainSidebar", () => { + beforeEach(() => { + mockedUseWaves.mockReset(); + mockedUseWaves.mockReturnValue({ + waves: [], + isFetching: false, + isFetchingNextPage: false, + hasNextPage: false, + fetchNextPage: jest.fn(), + status: "success", + error: null, + refetch: jest.fn(), + }); + }); + + it("renders created waves and the empty most-active placeholder", () => { + mockedUseWaves.mockReturnValue({ + waves: [makeWave()], + isFetching: false, + isFetchingNextPage: false, + hasNextPage: false, + fetchNextPage: jest.fn(), + status: "success", + error: null, + refetch: jest.fn(), + }); + + render(); + + expect(screen.getByText("Created Waves")).toBeInTheDocument(); + expect(screen.getByText("TDH Name Vote")).toBeInTheDocument(); + expect(screen.getByText("Most Active In")).toBeInTheDocument(); + expect(mockedUseWaves).toHaveBeenCalledTimes(1); + expect(mockedUseWaves).toHaveBeenCalledWith({ + identity: "kanetix", + waveName: null, + enabled: true, + directMessage: false, + limit: 20, + }); + }); + + it("uses the primary wallet when the profile has no handle", () => { + render( + + ); + + expect(mockedUseWaves).toHaveBeenCalledWith({ + identity: "0xdef", + waveName: null, + enabled: true, + directMessage: false, + limit: 20, + }); + }); + + it("hides the created section when there are no created waves", () => { + render(); + + expect(screen.queryByText("Created Waves")).toBeNull(); + expect(mockedUseWaves).toHaveBeenCalledTimes(1); + }); + + it("expands and collapses the created waves list", () => { + mockedUseWaves.mockReturnValue({ + waves: [ + makeWave(), + makeWave({ + id: "wave-2", + name: "Meme Card Curation", + metrics: { + drops_count: 8, + subscribers_count: 10, + latest_drop_timestamp: Date.now(), + }, + }), + ], + isFetching: false, + isFetchingNextPage: false, + hasNextPage: false, + fetchNextPage: jest.fn(), + status: "success", + error: null, + refetch: jest.fn(), + }); + + render(); + + expect(screen.getByText("Show 1 more")).toBeInTheDocument(); + expect(screen.queryByText("Meme Card Curation")).toBeNull(); + + fireEvent.click(screen.getByText("Show 1 more")); + expect(screen.getByText("Meme Card Curation")).toBeInTheDocument(); + expect(screen.getByText("Show less")).toBeInTheDocument(); + + fireEvent.click(screen.getByText("Show less")); + expect(screen.queryByText("Meme Card Curation")).toBeNull(); + }); +}); diff --git a/__tests__/components/user/brain/UserPageDrops.test.tsx b/__tests__/components/user/brain/UserPageDrops.test.tsx index 4c5df24915..7ab13f3541 100644 --- a/__tests__/components/user/brain/UserPageDrops.test.tsx +++ b/__tests__/components/user/brain/UserPageDrops.test.tsx @@ -1,19 +1,27 @@ -import { render } from '@testing-library/react'; -import UserPageDrops from '@/components/user/brain/UserPageDrops'; +import { render } from "@testing-library/react"; +import UserPageDrops from "@/components/user/brain/UserPageDrops"; -jest.mock('@/components/drops/view/Drops', () => ({ +jest.mock("@/components/drops/view/Drops", () => ({ __esModule: true, default: () =>
, })); +jest.mock("@/components/user/brain/UserPageBrainSidebar", () => ({ + __esModule: true, + default: () =>
, +})); -describe('UserPageDrops', () => { - it('renders Drops when profile has handle', () => { - const { getByTestId } = render(); - expect(getByTestId('drops')).toBeInTheDocument(); +describe("UserPageDrops", () => { + it("renders Drops when profile has handle", () => { + const { getByTestId } = render( + + ); + expect(getByTestId("drops")).toBeInTheDocument(); + expect(getByTestId("brain-sidebar")).toBeInTheDocument(); }); - it('hides Drops when no profile handle', () => { + it("hides Drops when no profile handle", () => { const { queryByTestId } = render(); - expect(queryByTestId('drops')).toBeNull(); + expect(queryByTestId("drops")).toBeNull(); + expect(queryByTestId("brain-sidebar")).toBeNull(); }); }); diff --git a/components/user/brain/UserPageBrainSidebar.tsx b/components/user/brain/UserPageBrainSidebar.tsx new file mode 100644 index 0000000000..b6e8664a69 --- /dev/null +++ b/components/user/brain/UserPageBrainSidebar.tsx @@ -0,0 +1,21 @@ +"use client"; + +import type { ApiIdentity } from "@/generated/models/ApiIdentity"; +import UserPageBrainSidebarCreated from "./UserPageBrainSidebarCreated"; +import UserPageBrainSidebarMostActivePlaceholder from "./UserPageBrainSidebarMostActivePlaceholder"; + +export default function UserPageBrainSidebar({ + profile, +}: { + readonly profile: ApiIdentity; +}) { + return ( + + ); +} diff --git a/components/user/brain/UserPageBrainSidebarCreated.tsx b/components/user/brain/UserPageBrainSidebarCreated.tsx new file mode 100644 index 0000000000..aa74e84aa7 --- /dev/null +++ b/components/user/brain/UserPageBrainSidebarCreated.tsx @@ -0,0 +1,217 @@ +"use client"; + +import { useState, type ReactNode } from "react"; +import { FallbackImage } from "@/components/common/FallbackImage"; +import { LockClosedIcon, StarIcon } from "@heroicons/react/24/solid"; +import Link from "next/link"; +import type { ApiIdentity } from "@/generated/models/ApiIdentity"; +import type { ApiWave } from "@/generated/models/ApiWave"; +import { + getRandomColorWithSeed, + getTimeAgoShort, + numberWithCommas, +} from "@/helpers/Helpers"; +import { getScaledImageUri, ImageScale } from "@/helpers/image.helpers"; +import { getWaveRoute } from "@/helpers/navigation.helpers"; +import { useWaves } from "@/hooks/useWaves"; + +const getProfileWaveIdentity = (profile: ApiIdentity): string => + profile.handle ?? profile.query ?? profile.primary_wallet; + +type CreatedWaveDisplay = { + readonly wave: ApiWave; + readonly href: string; + readonly isPrivate: boolean; + readonly dropsCount: string; + readonly lastDropTimestamp: ApiWave["metrics"]["latest_drop_timestamp"]; + readonly image: { + readonly primarySrc: string; + readonly fallbackSrc: string; + } | null; + readonly imageAreaStyle: + | { + readonly background: string; + } + | undefined; +}; + +const getCreatedWaveDisplay = (wave: ApiWave): CreatedWaveDisplay => ({ + wave, + href: getWaveRoute({ + waveId: wave.id, + isDirectMessage: wave.chat.scope.group?.is_direct_message ?? false, + isApp: false, + }), + isPrivate: + Boolean(wave.visibility.scope.group) && + !(wave.chat.scope.group?.is_direct_message ?? false), + dropsCount: numberWithCommas(wave.metrics.drops_count), + lastDropTimestamp: wave.metrics.latest_drop_timestamp, + image: wave.picture + ? { + primarySrc: getScaledImageUri(wave.picture, ImageScale.W_AUTO_H_50), + fallbackSrc: wave.picture, + } + : null, + imageAreaStyle: wave.picture + ? undefined + : { + background: `linear-gradient(135deg, ${ + wave.author.banner1_color ?? + getRandomColorWithSeed(wave.author.handle ?? wave.id) + } 0%, ${ + wave.author.banner2_color ?? getRandomColorWithSeed(`${wave.id}-alt`) + } 100%)`, + }, +}); + +const LoadingState = () => ( +
+ {[0, 1, 2].map((key) => ( +
+
+
+
+
+
+
+ ))} +
+); + +export default function UserPageBrainSidebarCreated({ + profile, +}: { + readonly profile: ApiIdentity; +}) { + const [expandedIdentity, setExpandedIdentity] = useState(null); + const identity = getProfileWaveIdentity(profile); + const hasIdentity = identity.length > 0; + const { waves, status, error } = useWaves({ + identity, + waveName: null, + enabled: hasIdentity, + directMessage: false, + limit: 20, + }); + const showAllWaves = expandedIdentity === identity; + const visibleWaves = showAllWaves ? waves : waves.slice(0, 1); + const visibleWaveItems = visibleWaves.map(getCreatedWaveDisplay); + const remainingWavesCount = Math.max(waves.length - 1, 0); + const showMoreLabel = + remainingWavesCount === 1 + ? "Show 1 more" + : `Show ${remainingWavesCount} more`; + const shouldShowLoading = hasIdentity && status === "pending"; + const shouldShowWaves = !error && waves.length > 0; + const shouldRenderSection = shouldShowLoading || shouldShowWaves; + let section: ReactNode = null; + + if (shouldRenderSection) { + let sectionContent: ReactNode = null; + + if (shouldShowLoading) { + sectionContent = ; + } else if (shouldShowWaves) { + sectionContent = ( + <> + {visibleWaveItems.map( + ({ + wave, + href, + isPrivate, + dropsCount, + lastDropTimestamp, + image, + imageAreaStyle, + }) => ( + +
+
+ {image ? ( + + ) : ( +
+ )} +
+ +
+
+
+ {wave.pinned && ( + + )} + {isPrivate && ( + + )} + + {wave.name} + +
+
+ +
+ {lastDropTimestamp ? ( + <> + {getTimeAgoShort(lastDropTimestamp)} + + {dropsCount} drops + + ) : ( + No drops yet + )} +
+
+ + ) + )} + {waves.length > 1 && ( + + )} + + ); + } + + section = ( +
+ + Created Waves + +
{sectionContent}
+
+ ); + } + + return section; +} diff --git a/components/user/brain/UserPageBrainSidebarMostActivePlaceholder.tsx b/components/user/brain/UserPageBrainSidebarMostActivePlaceholder.tsx new file mode 100644 index 0000000000..b4bd250d06 --- /dev/null +++ b/components/user/brain/UserPageBrainSidebarMostActivePlaceholder.tsx @@ -0,0 +1,14 @@ +"use client"; + +export default function UserPageBrainSidebarMostActivePlaceholder() { + return ( +
+ + Most Active In + +
+ ); +} diff --git a/components/user/brain/UserPageDrops.tsx b/components/user/brain/UserPageDrops.tsx index dda58ad7a5..f0977407a2 100644 --- a/components/user/brain/UserPageDrops.tsx +++ b/components/user/brain/UserPageDrops.tsx @@ -1,16 +1,23 @@ import Drops from "@/components/drops/view/Drops"; import type { ApiIdentity } from "@/generated/models/ApiIdentity"; +import UserPageBrainSidebar from "./UserPageBrainSidebar"; export default function UserPageDrops({ profile, }: { readonly profile: ApiIdentity | null; }) { - const haveProfile = !!profile?.handle; + if (!profile) { + return null; + } + + const haveProfile = Boolean(profile.handle); + return (
-
- {haveProfile && } +
+
{haveProfile && }
+
); From d55b4a274d4a9967710d773e35dcae45dca18ea3 Mon Sep 17 00:00:00 2001 From: ragnep Date: Thu, 12 Mar 2026 11:46:40 +0200 Subject: [PATCH 2/8] code cleanup Signed-off-by: ragnep --- components/user/brain/UserPageDrops.tsx | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/components/user/brain/UserPageDrops.tsx b/components/user/brain/UserPageDrops.tsx index f0977407a2..641da29d18 100644 --- a/components/user/brain/UserPageDrops.tsx +++ b/components/user/brain/UserPageDrops.tsx @@ -1,5 +1,6 @@ import Drops from "@/components/drops/view/Drops"; import type { ApiIdentity } from "@/generated/models/ApiIdentity"; +import type { ReactNode } from "react"; import UserPageBrainSidebar from "./UserPageBrainSidebar"; export default function UserPageDrops({ @@ -7,18 +8,20 @@ export default function UserPageDrops({ }: { readonly profile: ApiIdentity | null; }) { - if (!profile) { - return null; - } + let content: ReactNode = null; - const haveProfile = Boolean(profile.handle); + if (profile) { + const haveProfile = Boolean(profile.handle); - return ( -
-
-
{haveProfile && }
- + content = ( +
+
+
{haveProfile && }
+ +
-
- ); + ); + } + + return content; } From a831f15c0788a2064adbe660041a11b107a4d722 Mon Sep 17 00:00:00 2001 From: ragnep Date: Thu, 12 Mar 2026 15:55:11 +0200 Subject: [PATCH 3/8] most active in block Signed-off-by: ragnep --- .../user/brain/UserPageBrainSidebar.test.tsx | 68 ++++++++- .../react-query-wrapper/ReactQueryWrapper.tsx | 1 + .../user/brain/UserPageBrainSidebar.tsx | 4 +- .../brain/UserPageBrainSidebarCreated.tsx | 132 +----------------- .../brain/UserPageBrainSidebarMostActive.tsx | 68 +++++++++ ...rPageBrainSidebarMostActivePlaceholder.tsx | 14 -- .../brain/UserPageBrainSidebarWaveItem.tsx | 131 +++++++++++++++++ .../brain/userPageBrainSidebar.helpers.ts | 4 + hooks/useFavouriteWavesOfIdentity.ts | 49 +++++++ openapi.yaml | 44 ++++++ 10 files changed, 371 insertions(+), 144 deletions(-) create mode 100644 components/user/brain/UserPageBrainSidebarMostActive.tsx delete mode 100644 components/user/brain/UserPageBrainSidebarMostActivePlaceholder.tsx create mode 100644 components/user/brain/UserPageBrainSidebarWaveItem.tsx create mode 100644 components/user/brain/userPageBrainSidebar.helpers.ts create mode 100644 hooks/useFavouriteWavesOfIdentity.ts diff --git a/__tests__/components/user/brain/UserPageBrainSidebar.test.tsx b/__tests__/components/user/brain/UserPageBrainSidebar.test.tsx index bfa7f113cb..3717d50049 100644 --- a/__tests__/components/user/brain/UserPageBrainSidebar.test.tsx +++ b/__tests__/components/user/brain/UserPageBrainSidebar.test.tsx @@ -1,5 +1,6 @@ import { fireEvent, render, screen } from "@testing-library/react"; import UserPageBrainSidebar from "@/components/user/brain/UserPageBrainSidebar"; +import { useFavouriteWavesOfIdentity } from "@/hooks/useFavouriteWavesOfIdentity"; import { useWaves } from "@/hooks/useWaves"; jest.mock("next/image", () => ({ @@ -19,8 +20,15 @@ jest.mock("next/link", () => ({ jest.mock("@/hooks/useWaves", () => ({ useWaves: jest.fn(), })); +jest.mock("@/hooks/useFavouriteWavesOfIdentity", () => ({ + useFavouriteWavesOfIdentity: jest.fn(), +})); const mockedUseWaves = useWaves as jest.MockedFunction; +const mockedUseFavouriteWavesOfIdentity = + useFavouriteWavesOfIdentity as jest.MockedFunction< + typeof useFavouriteWavesOfIdentity + >; const baseProfile = { handle: "kanetix", @@ -56,6 +64,7 @@ const makeWave = (overrides: Record = {}) => describe("UserPageBrainSidebar", () => { beforeEach(() => { mockedUseWaves.mockReset(); + mockedUseFavouriteWavesOfIdentity.mockReset(); mockedUseWaves.mockReturnValue({ waves: [], isFetching: false, @@ -66,9 +75,16 @@ describe("UserPageBrainSidebar", () => { error: null, refetch: jest.fn(), }); + mockedUseFavouriteWavesOfIdentity.mockReturnValue({ + waves: [], + status: "success", + error: null, + refetch: jest.fn(), + isFetching: false, + }); }); - it("renders created waves and the empty most-active placeholder", () => { + it("renders created waves and most active waves", () => { mockedUseWaves.mockReturnValue({ waves: [makeWave()], isFetching: false, @@ -79,12 +95,25 @@ describe("UserPageBrainSidebar", () => { error: null, refetch: jest.fn(), }); + mockedUseFavouriteWavesOfIdentity.mockReturnValue({ + waves: [ + makeWave({ + id: "wave-2", + name: "Meme Card Curation", + }), + ], + status: "success", + error: null, + refetch: jest.fn(), + isFetching: false, + }); render(); expect(screen.getByText("Created Waves")).toBeInTheDocument(); expect(screen.getByText("TDH Name Vote")).toBeInTheDocument(); expect(screen.getByText("Most Active In")).toBeInTheDocument(); + expect(screen.getByText("Meme Card Curation")).toBeInTheDocument(); expect(mockedUseWaves).toHaveBeenCalledTimes(1); expect(mockedUseWaves).toHaveBeenCalledWith({ identity: "kanetix", @@ -93,6 +122,11 @@ describe("UserPageBrainSidebar", () => { directMessage: false, limit: 20, }); + expect(mockedUseFavouriteWavesOfIdentity).toHaveBeenCalledWith({ + identityKey: "kanetix", + limit: 3, + enabled: true, + }); }); it("uses the primary wallet when the profile has no handle", () => { @@ -109,15 +143,47 @@ describe("UserPageBrainSidebar", () => { directMessage: false, limit: 20, }); + expect(mockedUseFavouriteWavesOfIdentity).toHaveBeenCalledWith({ + identityKey: "0xdef", + limit: 3, + enabled: true, + }); }); it("hides the created section when there are no created waves", () => { + mockedUseFavouriteWavesOfIdentity.mockReturnValue({ + waves: [makeWave({ id: "wave-2", name: "Meme Card Curation" })], + status: "success", + error: null, + refetch: jest.fn(), + isFetching: false, + }); + render(); expect(screen.queryByText("Created Waves")).toBeNull(); + expect(screen.getByText("Most Active In")).toBeInTheDocument(); expect(mockedUseWaves).toHaveBeenCalledTimes(1); }); + it("hides the most active section when there are no favourite waves", () => { + mockedUseWaves.mockReturnValue({ + waves: [makeWave()], + isFetching: false, + isFetchingNextPage: false, + hasNextPage: false, + fetchNextPage: jest.fn(), + status: "success", + error: null, + refetch: jest.fn(), + }); + + render(); + + expect(screen.getByText("Created Waves")).toBeInTheDocument(); + expect(screen.queryByText("Most Active In")).toBeNull(); + }); + it("expands and collapses the created waves list", () => { mockedUseWaves.mockReturnValue({ waves: [ diff --git a/components/react-query-wrapper/ReactQueryWrapper.tsx b/components/react-query-wrapper/ReactQueryWrapper.tsx index 7378eb3c13..1ef54541f6 100644 --- a/components/react-query-wrapper/ReactQueryWrapper.tsx +++ b/components/react-query-wrapper/ReactQueryWrapper.tsx @@ -60,6 +60,7 @@ export enum QueryKey { IDENTITY_FOLLOWERS = "IDENTITY_FOLLOWERS", IDENTITY_NOTIFICATIONS = "IDENTITY_NOTIFICATIONS", IDENTITY_SEARCH = "IDENTITY_SEARCH", + IDENTITY_FAVOURITE_WAVES = "IDENTITY_FAVOURITE_WAVES", WALLET_TDH_HISTORY = "WALLET_TDH_HISTORY", REP_CATEGORIES_SEARCH = "REP_CATEGORIES_SEARCH", MEMES_LITE = "MEMES_LITE", diff --git a/components/user/brain/UserPageBrainSidebar.tsx b/components/user/brain/UserPageBrainSidebar.tsx index b6e8664a69..c63ff08f1a 100644 --- a/components/user/brain/UserPageBrainSidebar.tsx +++ b/components/user/brain/UserPageBrainSidebar.tsx @@ -2,7 +2,7 @@ import type { ApiIdentity } from "@/generated/models/ApiIdentity"; import UserPageBrainSidebarCreated from "./UserPageBrainSidebarCreated"; -import UserPageBrainSidebarMostActivePlaceholder from "./UserPageBrainSidebarMostActivePlaceholder"; +import UserPageBrainSidebarMostActive from "./UserPageBrainSidebarMostActive"; export default function UserPageBrainSidebar({ profile, @@ -15,7 +15,7 @@ export default function UserPageBrainSidebar({ data-testid="brain-sidebar" > - + ); } diff --git a/components/user/brain/UserPageBrainSidebarCreated.tsx b/components/user/brain/UserPageBrainSidebarCreated.tsx index aa74e84aa7..86ef9514ae 100644 --- a/components/user/brain/UserPageBrainSidebarCreated.tsx +++ b/components/user/brain/UserPageBrainSidebarCreated.tsx @@ -1,69 +1,10 @@ "use client"; import { useState, type ReactNode } from "react"; -import { FallbackImage } from "@/components/common/FallbackImage"; -import { LockClosedIcon, StarIcon } from "@heroicons/react/24/solid"; -import Link from "next/link"; import type { ApiIdentity } from "@/generated/models/ApiIdentity"; -import type { ApiWave } from "@/generated/models/ApiWave"; -import { - getRandomColorWithSeed, - getTimeAgoShort, - numberWithCommas, -} from "@/helpers/Helpers"; -import { getScaledImageUri, ImageScale } from "@/helpers/image.helpers"; -import { getWaveRoute } from "@/helpers/navigation.helpers"; import { useWaves } from "@/hooks/useWaves"; - -const getProfileWaveIdentity = (profile: ApiIdentity): string => - profile.handle ?? profile.query ?? profile.primary_wallet; - -type CreatedWaveDisplay = { - readonly wave: ApiWave; - readonly href: string; - readonly isPrivate: boolean; - readonly dropsCount: string; - readonly lastDropTimestamp: ApiWave["metrics"]["latest_drop_timestamp"]; - readonly image: { - readonly primarySrc: string; - readonly fallbackSrc: string; - } | null; - readonly imageAreaStyle: - | { - readonly background: string; - } - | undefined; -}; - -const getCreatedWaveDisplay = (wave: ApiWave): CreatedWaveDisplay => ({ - wave, - href: getWaveRoute({ - waveId: wave.id, - isDirectMessage: wave.chat.scope.group?.is_direct_message ?? false, - isApp: false, - }), - isPrivate: - Boolean(wave.visibility.scope.group) && - !(wave.chat.scope.group?.is_direct_message ?? false), - dropsCount: numberWithCommas(wave.metrics.drops_count), - lastDropTimestamp: wave.metrics.latest_drop_timestamp, - image: wave.picture - ? { - primarySrc: getScaledImageUri(wave.picture, ImageScale.W_AUTO_H_50), - fallbackSrc: wave.picture, - } - : null, - imageAreaStyle: wave.picture - ? undefined - : { - background: `linear-gradient(135deg, ${ - wave.author.banner1_color ?? - getRandomColorWithSeed(wave.author.handle ?? wave.id) - } 0%, ${ - wave.author.banner2_color ?? getRandomColorWithSeed(`${wave.id}-alt`) - } 100%)`, - }, -}); +import UserPageBrainSidebarWaveItem from "./UserPageBrainSidebarWaveItem"; +import { getProfileWaveIdentity } from "./userPageBrainSidebar.helpers"; const LoadingState = () => (
@@ -99,7 +40,6 @@ export default function UserPageBrainSidebarCreated({ }); const showAllWaves = expandedIdentity === identity; const visibleWaves = showAllWaves ? waves : waves.slice(0, 1); - const visibleWaveItems = visibleWaves.map(getCreatedWaveDisplay); const remainingWavesCount = Math.max(waves.length - 1, 0); const showMoreLabel = remainingWavesCount === 1 @@ -118,71 +58,9 @@ export default function UserPageBrainSidebarCreated({ } else if (shouldShowWaves) { sectionContent = ( <> - {visibleWaveItems.map( - ({ - wave, - href, - isPrivate, - dropsCount, - lastDropTimestamp, - image, - imageAreaStyle, - }) => ( - -
-
- {image ? ( - - ) : ( -
- )} -
- -
-
-
- {wave.pinned && ( - - )} - {isPrivate && ( - - )} - - {wave.name} - -
-
- -
- {lastDropTimestamp ? ( - <> - {getTimeAgoShort(lastDropTimestamp)} - - {dropsCount} drops - - ) : ( - No drops yet - )} -
-
- - ) - )} + {visibleWaves.map((wave) => ( + + ))} {waves.length > 1 && ( diff --git a/components/user/brain/UserPageBrainSidebarWaveItem.tsx b/components/user/brain/UserPageBrainSidebarWaveItem.tsx index 358cf2bfa9..f4d62d63c5 100644 --- a/components/user/brain/UserPageBrainSidebarWaveItem.tsx +++ b/components/user/brain/UserPageBrainSidebarWaveItem.tsx @@ -1,32 +1,18 @@ "use client"; import type { ReactNode } from "react"; -import { FallbackImage } from "@/components/common/FallbackImage"; -import { LockClosedIcon, StarIcon } from "@heroicons/react/24/solid"; +import { LockClosedIcon } from "@heroicons/react/24/solid"; import type { ApiWave } from "@/generated/models/ApiWave"; -import { - getRandomColorWithSeed, - getTimeAgoShort, - numberWithCommas, -} from "@/helpers/Helpers"; -import { getScaledImageUri, ImageScale } from "@/helpers/image.helpers"; +import { getTimeAgoShort, numberWithCommas } from "@/helpers/Helpers"; import { getWaveRoute } from "@/helpers/navigation.helpers"; import Link from "next/link"; +import WavePicture from "@/components/waves/WavePicture"; type BrainSidebarWaveItemDisplay = { readonly href: string; readonly isPrivate: boolean; readonly dropsCount: string; readonly lastDropTimestamp: ApiWave["metrics"]["latest_drop_timestamp"]; - readonly image: { - readonly primarySrc: string; - readonly fallbackSrc: string; - } | null; - readonly imageAreaStyle: - | { - readonly background: string; - } - | undefined; }; const getBrainSidebarWaveItemDisplay = ( @@ -42,22 +28,6 @@ const getBrainSidebarWaveItemDisplay = ( !(wave.chat.scope.group?.is_direct_message ?? false), dropsCount: numberWithCommas(wave.metrics.drops_count), lastDropTimestamp: wave.metrics.latest_drop_timestamp, - image: wave.picture - ? { - primarySrc: getScaledImageUri(wave.picture, ImageScale.W_AUTO_H_50), - fallbackSrc: wave.picture, - } - : null, - imageAreaStyle: wave.picture - ? undefined - : { - background: `linear-gradient(135deg, ${ - wave.author.banner1_color ?? - getRandomColorWithSeed(wave.author.handle ?? wave.id) - } 0%, ${ - wave.author.banner2_color ?? getRandomColorWithSeed(`${wave.id}-alt`) - } 100%)`, - }, }); export default function UserPageBrainSidebarWaveItem({ @@ -65,14 +35,12 @@ export default function UserPageBrainSidebarWaveItem({ }: { readonly wave: ApiWave; }) { - const { - href, - isPrivate, - dropsCount, - lastDropTimestamp, - image, - imageAreaStyle, - } = getBrainSidebarWaveItemDisplay(wave); + const contributors = wave.contributors_overview.map((contributor) => ({ + pfp: contributor.contributor_pfp, + identity: contributor.contributor_identity, + })); + const { href, isPrivate, dropsCount, lastDropTimestamp } = + getBrainSidebarWaveItemDisplay(wave); let metaContent: ReactNode = No drops yet; if (lastDropTimestamp) { @@ -93,36 +61,26 @@ export default function UserPageBrainSidebarWaveItem({ >
- {image ? ( - - ) : ( -
- )} +
-
+
- {wave.pinned && ( - - )} {isPrivate && ( )} - + {wave.name}
-
+
{metaContent}
diff --git a/components/waves/drops/WaveCreatorPreviewItem.tsx b/components/waves/drops/WaveCreatorPreviewItem.tsx index 018687be2b..fccbb35b3a 100644 --- a/components/waves/drops/WaveCreatorPreviewItem.tsx +++ b/components/waves/drops/WaveCreatorPreviewItem.tsx @@ -3,15 +3,10 @@ import React from "react"; import type { ApiWave } from "@/generated/models/ApiWave"; import { getWaveRoute } from "@/helpers/navigation.helpers"; -import { getScaledImageUri, ImageScale } from "@/helpers/image.helpers"; import { getTimeAgoShort } from "@/helpers/Helpers"; -import { - ArrowTopRightOnSquareIcon, - ChatBubbleLeftRightIcon, -} from "@heroicons/react/24/outline"; -import Image from "next/image"; +import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline"; import Link from "next/link"; -import WavesIcon from "@/components/common/icons/WavesIcon"; +import WavePicture from "@/components/waves/WavePicture"; interface WaveCreatorPreviewItemProps { readonly wave: ApiWave; @@ -24,16 +19,16 @@ export const WaveCreatorPreviewItem: React.FC = ({ wave, onSelect, }) => { + const contributors = wave.contributors_overview.map((contributor) => ({ + pfp: contributor.contributor_pfp, + identity: contributor.contributor_identity, + })); const isDirectMessage = wave.chat.scope.group?.is_direct_message ?? false; const waveHref = getWaveRoute({ waveId: wave.id, isDirectMessage, isApp: false, }); - const imageSrc = wave.picture - ? getScaledImageUri(wave.picture, ImageScale.W_AUTO_H_50) - : null; - const FallbackIcon = isDirectMessage ? ChatBubbleLeftRightIcon : WavesIcon; return ( = ({ }} className="tw-group tw-flex tw-items-center tw-gap-3 tw-rounded-lg tw-border tw-border-iron-800/70 tw-bg-iron-950/60 tw-px-4 tw-py-3 tw-no-underline tw-transition tw-duration-200 focus:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-primary-400/60 desktop-hover:hover:tw-border-iron-700 desktop-hover:hover:tw-bg-iron-900/60" > -
- {imageSrc ? ( - {wave.name - ) : ( - - )} +
+
From 1e7ab590cab8dccdfa3b0ea8f2e3504d01090733 Mon Sep 17 00:00:00 2001 From: ragnep Date: Fri, 13 Mar 2026 10:29:12 +0200 Subject: [PATCH 5/8] wip Signed-off-by: ragnep --- .../user/brain/UserPageBrainSidebar.test.tsx | 1 + .../UserPageBrainSidebarWaveItem.test.tsx | 41 +++-------------- .../drops/WaveCreatorPreviewItem.test.tsx | 36 +++------------ .../brain/UserPageBrainSidebarWaveItem.tsx | 44 ++++++++++++++----- .../waves/drops/WaveCreatorPreviewItem.tsx | 35 ++++++++++----- 5 files changed, 70 insertions(+), 87 deletions(-) diff --git a/__tests__/components/user/brain/UserPageBrainSidebar.test.tsx b/__tests__/components/user/brain/UserPageBrainSidebar.test.tsx index cb4d0533af..e85e9329f3 100644 --- a/__tests__/components/user/brain/UserPageBrainSidebar.test.tsx +++ b/__tests__/components/user/brain/UserPageBrainSidebar.test.tsx @@ -136,6 +136,7 @@ describe("UserPageBrainSidebar", () => { makeWave({ id: "wave-2", name: "Pinned Private Wave", + picture: "https://example.com/wave.png", pinned: true, visibility: { scope: { diff --git a/__tests__/components/user/brain/UserPageBrainSidebarWaveItem.test.tsx b/__tests__/components/user/brain/UserPageBrainSidebarWaveItem.test.tsx index 7b3c25c200..28f92f169d 100644 --- a/__tests__/components/user/brain/UserPageBrainSidebarWaveItem.test.tsx +++ b/__tests__/components/user/brain/UserPageBrainSidebarWaveItem.test.tsx @@ -1,8 +1,6 @@ import { render, screen } from "@testing-library/react"; import UserPageBrainSidebarWaveItem from "@/components/user/brain/UserPageBrainSidebarWaveItem"; -const mockedWavePicture = jest.fn(() =>
); - jest.mock("next/link", () => ({ __esModule: true, default: ({ children, href, prefetch, ...props }: any) => ( @@ -12,39 +10,21 @@ jest.mock("next/link", () => ({ ), })); -jest.mock("@/components/waves/WavePicture", () => ({ +jest.mock("next/image", () => ({ __esModule: true, - default: (props: any) => mockedWavePicture(props), + default: ({ alt, fill, ...props }: any) => {alt, })); describe("UserPageBrainSidebarWaveItem", () => { - beforeEach(() => { - mockedWavePicture.mockClear(); - }); - - it("passes shared wave avatar data to WavePicture when picture is missing", () => { - render( + it("shows the wave icon fallback when picture is missing", () => { + const { container } = render( { /> ); - expect(screen.getByTestId("wave-picture")).toBeInTheDocument(); - expect(mockedWavePicture).toHaveBeenCalledWith({ - name: "TDH Name Vote", - picture: null, - contributors: [ - { pfp: "alice.png", identity: "alice" }, - { pfp: "bob.png", identity: "bob" }, - ], - }); + expect(screen.queryByRole("img")).toBeNull(); + expect(container.querySelector("svg")).toBeInTheDocument(); }); }); diff --git a/__tests__/components/waves/drops/WaveCreatorPreviewItem.test.tsx b/__tests__/components/waves/drops/WaveCreatorPreviewItem.test.tsx index 677e6d15fc..40de05fbec 100644 --- a/__tests__/components/waves/drops/WaveCreatorPreviewItem.test.tsx +++ b/__tests__/components/waves/drops/WaveCreatorPreviewItem.test.tsx @@ -1,8 +1,6 @@ import { render, screen } from "@testing-library/react"; import { WaveCreatorPreviewItem } from "@/components/waves/drops/WaveCreatorPreviewItem"; -const mockedWavePicture = jest.fn(() =>
); - jest.mock("next/link", () => ({ __esModule: true, default: ({ children, href, prefetch, ...props }: any) => ( @@ -12,34 +10,21 @@ jest.mock("next/link", () => ({ ), })); -jest.mock("@/components/waves/WavePicture", () => ({ +jest.mock("next/image", () => ({ __esModule: true, - default: (props: any) => mockedWavePicture(props), + default: ({ alt, ...props }: any) => {alt, })); describe("WaveCreatorPreviewItem", () => { - beforeEach(() => { - mockedWavePicture.mockClear(); - }); - - it("uses WavePicture with contributor overview when picture is missing", () => { - render( + it("shows the wave icon fallback when picture is missing", () => { + const { container } = render( { /> ); - expect(screen.getByTestId("wave-picture")).toBeInTheDocument(); - expect(mockedWavePicture).toHaveBeenCalledWith({ - name: "TDH Name Vote", - picture: null, - contributors: [ - { pfp: "alice.png", identity: "alice" }, - { pfp: "bob.png", identity: "bob" }, - ], - }); + expect(screen.queryByRole("img")).toBeNull(); + expect(container.querySelector("svg")).toBeInTheDocument(); }); }); diff --git a/components/user/brain/UserPageBrainSidebarWaveItem.tsx b/components/user/brain/UserPageBrainSidebarWaveItem.tsx index f4d62d63c5..0f135d7091 100644 --- a/components/user/brain/UserPageBrainSidebarWaveItem.tsx +++ b/components/user/brain/UserPageBrainSidebarWaveItem.tsx @@ -2,22 +2,28 @@ import type { ReactNode } from "react"; import { LockClosedIcon } from "@heroicons/react/24/solid"; +import { ChatBubbleLeftRightIcon } from "@heroicons/react/24/outline"; import type { ApiWave } from "@/generated/models/ApiWave"; import { getTimeAgoShort, numberWithCommas } from "@/helpers/Helpers"; +import { getScaledImageUri, ImageScale } from "@/helpers/image.helpers"; import { getWaveRoute } from "@/helpers/navigation.helpers"; import Link from "next/link"; -import WavePicture from "@/components/waves/WavePicture"; +import Image from "next/image"; +import WavesIcon from "@/components/common/icons/WavesIcon"; type BrainSidebarWaveItemDisplay = { readonly href: string; readonly isPrivate: boolean; + readonly isDirectMessage: boolean; readonly dropsCount: string; readonly lastDropTimestamp: ApiWave["metrics"]["latest_drop_timestamp"]; + readonly imageSrc: string | null; }; const getBrainSidebarWaveItemDisplay = ( wave: ApiWave ): BrainSidebarWaveItemDisplay => ({ + isDirectMessage: wave.chat.scope.group?.is_direct_message ?? false, href: getWaveRoute({ waveId: wave.id, isDirectMessage: wave.chat.scope.group?.is_direct_message ?? false, @@ -28,6 +34,9 @@ const getBrainSidebarWaveItemDisplay = ( !(wave.chat.scope.group?.is_direct_message ?? false), dropsCount: numberWithCommas(wave.metrics.drops_count), lastDropTimestamp: wave.metrics.latest_drop_timestamp, + imageSrc: wave.picture + ? getScaledImageUri(wave.picture, ImageScale.W_AUTO_H_50) + : null, }); export default function UserPageBrainSidebarWaveItem({ @@ -35,12 +44,15 @@ export default function UserPageBrainSidebarWaveItem({ }: { readonly wave: ApiWave; }) { - const contributors = wave.contributors_overview.map((contributor) => ({ - pfp: contributor.contributor_pfp, - identity: contributor.contributor_identity, - })); - const { href, isPrivate, dropsCount, lastDropTimestamp } = - getBrainSidebarWaveItemDisplay(wave); + const { + href, + isPrivate, + isDirectMessage, + dropsCount, + lastDropTimestamp, + imageSrc, + } = getBrainSidebarWaveItemDisplay(wave); + const FallbackIcon = isDirectMessage ? ChatBubbleLeftRightIcon : WavesIcon; let metaContent: ReactNode = No drops yet; if (lastDropTimestamp) { @@ -61,11 +73,19 @@ export default function UserPageBrainSidebarWaveItem({ >
- + {imageSrc ? ( + {wave.name + ) : ( +
+ +
+ )}
diff --git a/components/waves/drops/WaveCreatorPreviewItem.tsx b/components/waves/drops/WaveCreatorPreviewItem.tsx index fccbb35b3a..38c4eae7a3 100644 --- a/components/waves/drops/WaveCreatorPreviewItem.tsx +++ b/components/waves/drops/WaveCreatorPreviewItem.tsx @@ -3,10 +3,15 @@ import React from "react"; import type { ApiWave } from "@/generated/models/ApiWave"; import { getWaveRoute } from "@/helpers/navigation.helpers"; +import { getScaledImageUri, ImageScale } from "@/helpers/image.helpers"; import { getTimeAgoShort } from "@/helpers/Helpers"; -import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline"; +import { + ArrowTopRightOnSquareIcon, + ChatBubbleLeftRightIcon, +} from "@heroicons/react/24/outline"; import Link from "next/link"; -import WavePicture from "@/components/waves/WavePicture"; +import Image from "next/image"; +import WavesIcon from "@/components/common/icons/WavesIcon"; interface WaveCreatorPreviewItemProps { readonly wave: ApiWave; @@ -19,16 +24,16 @@ export const WaveCreatorPreviewItem: React.FC = ({ wave, onSelect, }) => { - const contributors = wave.contributors_overview.map((contributor) => ({ - pfp: contributor.contributor_pfp, - identity: contributor.contributor_identity, - })); const isDirectMessage = wave.chat.scope.group?.is_direct_message ?? false; const waveHref = getWaveRoute({ waveId: wave.id, isDirectMessage, isApp: false, }); + const imageSrc = wave.picture + ? getScaledImageUri(wave.picture, ImageScale.W_AUTO_H_50) + : null; + const FallbackIcon = isDirectMessage ? ChatBubbleLeftRightIcon : WavesIcon; return ( = ({ }} className="tw-group tw-flex tw-items-center tw-gap-3 tw-rounded-lg tw-border tw-border-iron-800/70 tw-bg-iron-950/60 tw-px-4 tw-py-3 tw-no-underline tw-transition tw-duration-200 focus:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-primary-400/60 desktop-hover:hover:tw-border-iron-700 desktop-hover:hover:tw-bg-iron-900/60" > -
- +
+ {imageSrc ? ( + {wave.name + ) : ( + + )}
From 17c0f82138b7fb866dd86580355d4d806f759dc6 Mon Sep 17 00:00:00 2001 From: ragnep Date: Fri, 13 Mar 2026 12:49:50 +0200 Subject: [PATCH 6/8] mobile responsiveness Signed-off-by: ragnep --- .../user/brain/UserPageBrainSidebar.test.tsx | 111 +++++++++-- .../user/brain/UserPageBrainSidebar.tsx | 83 +++++++- .../brain/UserPageBrainSidebarCreated.tsx | 144 +++++++------- .../brain/UserPageBrainSidebarMobileStrip.tsx | 184 ++++++++++++++++++ .../brain/UserPageBrainSidebarMostActive.tsx | 110 +++++------ .../brain/UserPageBrainSidebarWaveItem.tsx | 16 +- components/user/brain/UserPageDrops.tsx | 6 +- .../waves/drops/WaveCreatorPreviewItem.tsx | 4 +- .../waves/drops/WaveCreatorPreviewModal.tsx | 4 +- .../drops/WaveCreatorPreviewModalContent.tsx | 8 +- .../waves/drops/waveCreatorPreview.types.ts | 4 + 11 files changed, 496 insertions(+), 178 deletions(-) create mode 100644 components/user/brain/UserPageBrainSidebarMobileStrip.tsx create mode 100644 components/waves/drops/waveCreatorPreview.types.ts diff --git a/__tests__/components/user/brain/UserPageBrainSidebar.test.tsx b/__tests__/components/user/brain/UserPageBrainSidebar.test.tsx index e85e9329f3..d8ccc1a6f3 100644 --- a/__tests__/components/user/brain/UserPageBrainSidebar.test.tsx +++ b/__tests__/components/user/brain/UserPageBrainSidebar.test.tsx @@ -1,4 +1,4 @@ -import { fireEvent, render, screen } from "@testing-library/react"; +import { fireEvent, render, screen, within } from "@testing-library/react"; import UserPageBrainSidebar from "@/components/user/brain/UserPageBrainSidebar"; import { useFavouriteWavesOfIdentity } from "@/hooks/useFavouriteWavesOfIdentity"; import { useWaves } from "@/hooks/useWaves"; @@ -23,6 +23,15 @@ jest.mock("@/hooks/useWaves", () => ({ jest.mock("@/hooks/useFavouriteWavesOfIdentity", () => ({ useFavouriteWavesOfIdentity: jest.fn(), })); +jest.mock("@/components/waves/drops/WaveCreatorPreviewModal", () => ({ + WaveCreatorPreviewModal: ({ isOpen, user }: any) => + isOpen ? ( +
+ ) : null, +})); const mockedUseWaves = useWaves as jest.MockedFunction; const mockedUseFavouriteWavesOfIdentity = @@ -110,11 +119,23 @@ describe("UserPageBrainSidebar", () => { }); render(); + const desktopSidebar = screen.getByTestId("brain-sidebar-desktop"); + const mobileStrip = screen.getByTestId("brain-sidebar-mobile-strip"); - expect(screen.getByText("Created Waves")).toBeInTheDocument(); - expect(screen.getByText("TDH Name Vote")).toBeInTheDocument(); - expect(screen.getByText("Most Active In")).toBeInTheDocument(); - expect(screen.getByText("Meme Card Curation")).toBeInTheDocument(); + expect( + within(desktopSidebar).getByText("Created Waves") + ).toBeInTheDocument(); + expect( + within(desktopSidebar).getByText("TDH Name Vote") + ).toBeInTheDocument(); + expect( + within(desktopSidebar).getByText("Most Active In") + ).toBeInTheDocument(); + expect( + within(desktopSidebar).getByText("Meme Card Curation") + ).toBeInTheDocument(); + expect(within(mobileStrip).getByText("Created")).toBeInTheDocument(); + expect(within(mobileStrip).getByText("Active In")).toBeInTheDocument(); expect(mockedUseWaves).toHaveBeenCalledTimes(1); expect(mockedUseWaves).toHaveBeenCalledWith({ identity: "kanetix", @@ -153,12 +174,13 @@ describe("UserPageBrainSidebar", () => { isFetching: false, }); - const { container } = render( - - ); + render(); + const desktopSidebar = screen.getByTestId("brain-sidebar-desktop"); - expect(screen.getByText("Pinned Private Wave")).toBeInTheDocument(); - expect(container.querySelectorAll("svg")).toHaveLength(1); + expect( + within(desktopSidebar).getByText("Pinned Private Wave") + ).toBeInTheDocument(); + expect(screen.queryByTestId("wave-creator-preview-modal")).toBeNull(); }); it("uses the primary wallet when the profile has no handle", () => { @@ -192,9 +214,12 @@ describe("UserPageBrainSidebar", () => { }); render(); + const desktopSidebar = screen.getByTestId("brain-sidebar-desktop"); - expect(screen.queryByText("Created Waves")).toBeNull(); - expect(screen.getByText("Most Active In")).toBeInTheDocument(); + expect(within(desktopSidebar).queryByText("Created Waves")).toBeNull(); + expect( + within(desktopSidebar).getByText("Most Active In") + ).toBeInTheDocument(); expect(mockedUseWaves).toHaveBeenCalledTimes(1); }); @@ -211,9 +236,12 @@ describe("UserPageBrainSidebar", () => { }); render(); + const desktopSidebar = screen.getByTestId("brain-sidebar-desktop"); - expect(screen.getByText("Created Waves")).toBeInTheDocument(); - expect(screen.queryByText("Most Active In")).toBeNull(); + expect( + within(desktopSidebar).getByText("Created Waves") + ).toBeInTheDocument(); + expect(within(desktopSidebar).queryByText("Most Active In")).toBeNull(); }); it("expands and collapses the created waves list", () => { @@ -240,15 +268,56 @@ describe("UserPageBrainSidebar", () => { }); render(); + const desktopSidebar = screen.getByTestId("brain-sidebar-desktop"); - expect(screen.getByText("Show 1 more")).toBeInTheDocument(); - expect(screen.queryByText("Meme Card Curation")).toBeNull(); + expect(within(desktopSidebar).getByText("Show 1 more")).toBeInTheDocument(); + expect(within(desktopSidebar).queryByText("Meme Card Curation")).toBeNull(); - fireEvent.click(screen.getByText("Show 1 more")); - expect(screen.getByText("Meme Card Curation")).toBeInTheDocument(); - expect(screen.getByText("Show less")).toBeInTheDocument(); + fireEvent.click(within(desktopSidebar).getByText("Show 1 more")); + expect( + within(desktopSidebar).getByText("Meme Card Curation") + ).toBeInTheDocument(); + expect(within(desktopSidebar).getByText("Show less")).toBeInTheDocument(); - fireEvent.click(screen.getByText("Show less")); - expect(screen.queryByText("Meme Card Curation")).toBeNull(); + fireEvent.click(within(desktopSidebar).getByText("Show less")); + expect(within(desktopSidebar).queryByText("Meme Card Curation")).toBeNull(); + }); + + it("opens the created waves modal from the compact strip overflow chip", () => { + mockedUseWaves.mockReturnValue({ + waves: [ + makeWave(), + makeWave({ + id: "wave-2", + name: "Meme Card Curation", + metrics: { + drops_count: 8, + subscribers_count: 10, + latest_drop_timestamp: Date.now(), + }, + }), + ], + isFetching: false, + isFetchingNextPage: false, + hasNextPage: false, + fetchNextPage: jest.fn(), + status: "success", + error: null, + refetch: jest.fn(), + }); + + render(); + const mobileStrip = screen.getByTestId("brain-sidebar-mobile-strip"); + + fireEvent.click( + within(mobileStrip).getByRole("button", { + name: /view all created waves/i, + }) + ); + + expect(screen.getByTestId("wave-creator-preview-modal")).toHaveAttribute( + "data-user-primary-address", + "0xabc" + ); }); }); diff --git a/components/user/brain/UserPageBrainSidebar.tsx b/components/user/brain/UserPageBrainSidebar.tsx index c63ff08f1a..25038631b6 100644 --- a/components/user/brain/UserPageBrainSidebar.tsx +++ b/components/user/brain/UserPageBrainSidebar.tsx @@ -1,21 +1,98 @@ "use client"; +import { useMemo } from "react"; import type { ApiIdentity } from "@/generated/models/ApiIdentity"; +import { useFavouriteWavesOfIdentity } from "@/hooks/useFavouriteWavesOfIdentity"; +import { useWaves } from "@/hooks/useWaves"; +import { useWaveCreatorPreviewModal } from "@/hooks/useWaveCreatorPreviewModal"; +import { WaveCreatorPreviewModal } from "@/components/waves/drops/WaveCreatorPreviewModal"; import UserPageBrainSidebarCreated from "./UserPageBrainSidebarCreated"; +import UserPageBrainSidebarMobileStrip from "./UserPageBrainSidebarMobileStrip"; import UserPageBrainSidebarMostActive from "./UserPageBrainSidebarMostActive"; +import { getProfileWaveIdentity } from "./userPageBrainSidebar.helpers"; export default function UserPageBrainSidebar({ profile, }: { readonly profile: ApiIdentity; }) { + const identity = getProfileWaveIdentity(profile); + const hasIdentity = identity.length > 0; + const { + waves: createdWaves, + status: createdStatus, + error: createdError, + } = useWaves({ + identity, + waveName: null, + enabled: hasIdentity, + directMessage: false, + limit: 20, + }); + const { + waves: mostActiveWaves, + status: mostActiveStatus, + error: mostActiveError, + } = useFavouriteWavesOfIdentity({ + identityKey: identity, + limit: 3, + enabled: hasIdentity, + }); + const { isModalOpen, handleBadgeClick, handleModalClose } = + useWaveCreatorPreviewModal(); + const modalUser = useMemo( + () => ({ + handle: profile.handle, + primary_address: profile.primary_wallet, + }), + [profile.handle, profile.primary_wallet] + ); + const shouldShowCreated = + createdStatus === "pending" || createdWaves.length > 0; + const shouldShowMostActive = + mostActiveStatus === "pending" || mostActiveWaves.length > 0; + + if (!shouldShowCreated && !shouldShowMostActive) { + return null; + } + return ( ); } diff --git a/components/user/brain/UserPageBrainSidebarCreated.tsx b/components/user/brain/UserPageBrainSidebarCreated.tsx index d9f453b9a6..774782a02b 100644 --- a/components/user/brain/UserPageBrainSidebarCreated.tsx +++ b/components/user/brain/UserPageBrainSidebarCreated.tsx @@ -1,43 +1,24 @@ "use client"; -import { useState, type ReactNode } from "react"; -import type { ApiIdentity } from "@/generated/models/ApiIdentity"; -import { useWaves } from "@/hooks/useWaves"; +import type { QueryStatus } from "@tanstack/react-query"; +import { useState } from "react"; +import type { ApiWave } from "@/generated/models/ApiWave"; import UserPageBrainSidebarWaveItem from "./UserPageBrainSidebarWaveItem"; -import { getProfileWaveIdentity } from "./userPageBrainSidebar.helpers"; -const LoadingState = () => ( -
- {[0, 1, 2].map((key) => ( -
-
-
-
-
-
-
- ))} -
-); +interface UserPageBrainSidebarCreatedProps { + readonly identity: string; + readonly waves: ApiWave[]; + readonly status: QueryStatus; + readonly error: unknown; +} export default function UserPageBrainSidebarCreated({ - profile, -}: { - readonly profile: ApiIdentity; -}) { + identity, + waves, + status, + error, +}: UserPageBrainSidebarCreatedProps) { const [expandedIdentity, setExpandedIdentity] = useState(null); - const identity = getProfileWaveIdentity(profile); - const hasIdentity = identity.length > 0; - const { waves, status, error } = useWaves({ - identity, - waveName: null, - enabled: hasIdentity, - directMessage: false, - limit: 20, - }); const showAllWaves = expandedIdentity === identity; const visibleWaves = showAllWaves ? waves : waves.slice(0, 1); const remainingWavesCount = Math.max(waves.length - 1, 0); @@ -45,51 +26,58 @@ export default function UserPageBrainSidebarCreated({ remainingWavesCount === 1 ? "Show 1 more" : `Show ${remainingWavesCount} more`; - const shouldShowLoading = hasIdentity && status === "pending"; - const shouldShowWaves = !error && waves.length > 0; - const shouldRenderSection = shouldShowLoading || shouldShowWaves; - let section: ReactNode = null; - - if (shouldRenderSection) { - let sectionContent: ReactNode = null; - - if (shouldShowLoading) { - sectionContent = ; - } else if (shouldShowWaves) { - sectionContent = ( - <> - {visibleWaves.map((wave) => ( - - ))} - {waves.length > 1 && ( - - )} - - ); - } - - section = ( -
- - Created Waves - -
{sectionContent}
-
- ); + const shouldShowLoading = status === "pending"; + const shouldShowWaves = + (error === null || error === undefined) && waves.length > 0; + if (!shouldShowLoading && !shouldShowWaves) { + return null; } - return section; + return ( +
+ + Created Waves + +
+ {shouldShowLoading ? ( +
+ {[0, 1, 2].map((key) => ( +
+
+
+
+
+
+
+ ))} +
+ ) : ( + <> + {visibleWaves.map((wave) => ( + + ))} + {waves.length > 1 && ( + + )} + + )} +
+
+ ); } diff --git a/components/user/brain/UserPageBrainSidebarMobileStrip.tsx b/components/user/brain/UserPageBrainSidebarMobileStrip.tsx new file mode 100644 index 0000000000..408e57d804 --- /dev/null +++ b/components/user/brain/UserPageBrainSidebarMobileStrip.tsx @@ -0,0 +1,184 @@ +"use client"; + +import type { QueryStatus } from "@tanstack/react-query"; +import type { ReactNode } from "react"; +import type { ApiWave } from "@/generated/models/ApiWave"; +import { getScaledImageUri, ImageScale } from "@/helpers/image.helpers"; +import { getWaveRoute } from "@/helpers/navigation.helpers"; +import { ChatBubbleLeftRightIcon, PlusIcon } from "@heroicons/react/24/outline"; +import Link from "next/link"; +import Image from "next/image"; +import WavesIcon from "@/components/common/icons/WavesIcon"; + +interface UserPageBrainSidebarMobileStripProps { + readonly createdWaves: ApiWave[]; + readonly createdStatus: QueryStatus; + readonly createdError: unknown; + readonly mostActiveWaves: ApiWave[]; + readonly mostActiveStatus: QueryStatus; + readonly mostActiveError: unknown; + readonly onOpenCreatedWaves: () => void; +} + +function UserPageBrainSidebarMobileWavePill({ + wave, +}: { + readonly wave: ApiWave; +}) { + const isDirectMessage = wave.chat.scope.group?.is_direct_message ?? false; + const href = getWaveRoute({ + waveId: wave.id, + isDirectMessage, + isApp: false, + }); + const imageSrc = wave.picture + ? getScaledImageUri(wave.picture, ImageScale.W_200_H_200) + : null; + const FallbackIcon = isDirectMessage ? ChatBubbleLeftRightIcon : WavesIcon; + + return ( + +
+ {imageSrc ? ( + {wave.name + ) : ( + + )} +
+ + {wave.name} + + + ); +} + +function MobileWavePillSkeleton({ keyId }: { readonly keyId: string }) { + return ( +
+
+
+
+ ); +} + +export default function UserPageBrainSidebarMobileStrip({ + createdWaves, + createdStatus, + createdError, + mostActiveWaves, + mostActiveStatus, + mostActiveError, + onOpenCreatedWaves, +}: UserPageBrainSidebarMobileStripProps) { + const shouldShowCreatedLoading = createdStatus === "pending"; + const shouldShowCreatedWaves = + (createdError === null || createdError === undefined) && + createdWaves.length > 0; + const shouldShowMostActiveLoading = mostActiveStatus === "pending"; + const shouldShowMostActiveWaves = + (mostActiveError === null || mostActiveError === undefined) && + mostActiveWaves.length > 0; + const shouldShowCreatedSection = + shouldShowCreatedLoading || shouldShowCreatedWaves; + const shouldShowMostActiveSection = + shouldShowMostActiveLoading || shouldShowMostActiveWaves; + + if (!shouldShowCreatedSection && !shouldShowMostActiveSection) { + return null; + } + + const firstCreatedWave = createdWaves[0]; + const remainingCreatedCount = Math.max(createdWaves.length - 1, 0); + let createdSectionContent: ReactNode = null; + + if (shouldShowCreatedLoading) { + createdSectionContent = ( + <> + + + + ); + } else if (firstCreatedWave) { + createdSectionContent = ( + <> + + {remainingCreatedCount > 0 && ( + + )} + + ); + } + + return ( +
+
+
+ {shouldShowCreatedSection && ( +
+ + Created + +
+ {createdSectionContent} +
+
+ )} + + {shouldShowCreatedSection && shouldShowMostActiveSection && ( +
+ )} + + {shouldShowMostActiveSection && ( +
+ + Active In + +
+ {shouldShowMostActiveLoading + ? [0, 1, 2].map((key) => ( + + )) + : mostActiveWaves + .slice(0, 3) + .map((wave) => ( + + ))} +
+
+ )} +
+
+
+ ); +} diff --git a/components/user/brain/UserPageBrainSidebarMostActive.tsx b/components/user/brain/UserPageBrainSidebarMostActive.tsx index 352dfe6d9d..9c0dff0b02 100644 --- a/components/user/brain/UserPageBrainSidebarMostActive.tsx +++ b/components/user/brain/UserPageBrainSidebarMostActive.tsx @@ -1,68 +1,60 @@ "use client"; -import type { ReactNode } from "react"; -import type { ApiIdentity } from "@/generated/models/ApiIdentity"; -import { useFavouriteWavesOfIdentity } from "@/hooks/useFavouriteWavesOfIdentity"; +import type { QueryStatus } from "@tanstack/react-query"; +import type { ApiWave } from "@/generated/models/ApiWave"; import UserPageBrainSidebarWaveItem from "./UserPageBrainSidebarWaveItem"; -import { getProfileWaveIdentity } from "./userPageBrainSidebar.helpers"; -const LoadingState = () => ( -
- {[0, 1, 2].map((key) => ( -
-
-
-
-
-
-
- ))} -
-); +interface UserPageBrainSidebarMostActiveProps { + readonly waves: ApiWave[]; + readonly status: QueryStatus; + readonly error: unknown; +} export default function UserPageBrainSidebarMostActive({ - profile, -}: { - readonly profile: ApiIdentity; -}) { - const identityKey = getProfileWaveIdentity(profile); - const hasIdentityKey = identityKey.length > 0; - const { waves, status, error } = useFavouriteWavesOfIdentity({ - identityKey, - limit: 3, - enabled: hasIdentityKey, - }); - const shouldShowLoading = hasIdentityKey && status === "pending"; - const shouldShowWaves = !error && waves.length > 0; - const shouldRenderSection = shouldShowLoading || shouldShowWaves; - let section: ReactNode = null; - - if (shouldRenderSection) { - let sectionContent: ReactNode = null; - - if (shouldShowLoading) { - sectionContent = ; - } else if (shouldShowWaves) { - sectionContent = waves.map((wave) => ( - - )); - } - - section = ( -
- - Most Active In - -
{sectionContent}
-
- ); + waves, + status, + error, +}: UserPageBrainSidebarMostActiveProps) { + const shouldShowLoading = status === "pending"; + const shouldShowWaves = + (error === null || error === undefined) && waves.length > 0; + if (!shouldShowLoading && !shouldShowWaves) { + return null; } - return section; + return ( +
+ + Most Active In + +
+ {shouldShowLoading ? ( +
+ {[0, 1, 2].map((key) => ( +
+
+
+
+
+
+
+ ))} +
+ ) : ( + waves.map((wave) => ( + + )) + )} +
+
+ ); } diff --git a/components/user/brain/UserPageBrainSidebarWaveItem.tsx b/components/user/brain/UserPageBrainSidebarWaveItem.tsx index 0f135d7091..27c01bb13c 100644 --- a/components/user/brain/UserPageBrainSidebarWaveItem.tsx +++ b/components/user/brain/UserPageBrainSidebarWaveItem.tsx @@ -1,7 +1,7 @@ "use client"; import type { ReactNode } from "react"; -import { LockClosedIcon } from "@heroicons/react/24/solid"; +import { LockClosedIcon, ChevronRightIcon } from "@heroicons/react/24/solid"; import { ChatBubbleLeftRightIcon } from "@heroicons/react/24/outline"; import type { ApiWave } from "@/generated/models/ApiWave"; import { getTimeAgoShort, numberWithCommas } from "@/helpers/Helpers"; @@ -35,7 +35,7 @@ const getBrainSidebarWaveItemDisplay = ( dropsCount: numberWithCommas(wave.metrics.drops_count), lastDropTimestamp: wave.metrics.latest_drop_timestamp, imageSrc: wave.picture - ? getScaledImageUri(wave.picture, ImageScale.W_AUTO_H_50) + ? getScaledImageUri(wave.picture, ImageScale.W_200_H_200) : null, }); @@ -59,7 +59,7 @@ export default function UserPageBrainSidebarWaveItem({ metaContent = ( <> {getTimeAgoShort(lastDropTimestamp)} - + {dropsCount} drops ); @@ -69,21 +69,21 @@ export default function UserPageBrainSidebarWaveItem({
-
+
{imageSrc ? ( {wave.name ) : (
- +
)}
@@ -104,6 +104,8 @@ export default function UserPageBrainSidebarWaveItem({ {metaContent}
+ + ); } diff --git a/components/user/brain/UserPageDrops.tsx b/components/user/brain/UserPageDrops.tsx index 641da29d18..ab647f7fc8 100644 --- a/components/user/brain/UserPageDrops.tsx +++ b/components/user/brain/UserPageDrops.tsx @@ -15,8 +15,10 @@ export default function UserPageDrops({ content = (
-
-
{haveProfile && }
+
+
+ {haveProfile && } +
diff --git a/components/waves/drops/WaveCreatorPreviewItem.tsx b/components/waves/drops/WaveCreatorPreviewItem.tsx index 38c4eae7a3..8b3ee8a7bd 100644 --- a/components/waves/drops/WaveCreatorPreviewItem.tsx +++ b/components/waves/drops/WaveCreatorPreviewItem.tsx @@ -47,14 +47,14 @@ export const WaveCreatorPreviewItem: React.FC = ({ }} className="tw-group tw-flex tw-items-center tw-gap-3 tw-rounded-lg tw-border tw-border-iron-800/70 tw-bg-iron-950/60 tw-px-4 tw-py-3 tw-no-underline tw-transition tw-duration-200 focus:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-primary-400/60 desktop-hover:hover:tw-border-iron-700 desktop-hover:hover:tw-bg-iron-900/60" > -
+
{imageSrc ? ( {wave.name ) : ( diff --git a/components/waves/drops/WaveCreatorPreviewModal.tsx b/components/waves/drops/WaveCreatorPreviewModal.tsx index a8ae1d524f..b22d76cd00 100644 --- a/components/waves/drops/WaveCreatorPreviewModal.tsx +++ b/components/waves/drops/WaveCreatorPreviewModal.tsx @@ -1,6 +1,5 @@ "use client"; -import type { ApiProfileMin } from "@/generated/models/ApiProfileMin"; import useDeviceInfo from "@/hooks/useDeviceInfo"; import { Dialog, @@ -12,11 +11,12 @@ import { Fragment, useEffect } from "react"; import { createPortal } from "react-dom"; import ArtistPreviewAppWrapper from "./ArtistPreviewAppWrapper"; import { WaveCreatorPreviewModalContent } from "./WaveCreatorPreviewModalContent"; +import type { WaveCreatorPreviewUser } from "./waveCreatorPreview.types"; interface WaveCreatorPreviewModalProps { readonly isOpen: boolean; readonly onClose: () => void; - readonly user: ApiProfileMin; + readonly user: WaveCreatorPreviewUser; } export const WaveCreatorPreviewModal = ({ diff --git a/components/waves/drops/WaveCreatorPreviewModalContent.tsx b/components/waves/drops/WaveCreatorPreviewModalContent.tsx index a48435f2cc..b9d93e5c42 100644 --- a/components/waves/drops/WaveCreatorPreviewModalContent.tsx +++ b/components/waves/drops/WaveCreatorPreviewModalContent.tsx @@ -1,7 +1,6 @@ "use client"; import React, { useCallback } from "react"; -import type { ApiProfileMin } from "@/generated/models/ApiProfileMin"; import { useWaves } from "@/hooks/useWaves"; import CircleLoader, { CircleLoaderSize, @@ -12,9 +11,10 @@ import { WaveCreatorPreviewItem } from "./WaveCreatorPreviewItem"; import { shortenAddress } from "@/helpers/address.helpers"; import { useRouter } from "next/navigation"; import type { ApiWave } from "@/generated/models/ApiWave"; +import type { WaveCreatorPreviewUser } from "./waveCreatorPreview.types"; interface WaveCreatorPreviewModalContentProps { - readonly user: ApiProfileMin; + readonly user: WaveCreatorPreviewUser; readonly isOpen: boolean; readonly onClose: () => void; readonly isApp?: boolean | undefined; @@ -73,11 +73,11 @@ export const WaveCreatorPreviewModalContent: React.FC< return (
-
+
Waves by {displayName}
diff --git a/components/waves/drops/waveCreatorPreview.types.ts b/components/waves/drops/waveCreatorPreview.types.ts new file mode 100644 index 0000000000..e0c272377e --- /dev/null +++ b/components/waves/drops/waveCreatorPreview.types.ts @@ -0,0 +1,4 @@ +export interface WaveCreatorPreviewUser { + readonly handle: string | null; + readonly primary_address: string; +} From bbbeb0a8b1f46df39786673f70978a2ea10f8013 Mon Sep 17 00:00:00 2001 From: ragnep Date: Fri, 13 Mar 2026 13:17:34 +0200 Subject: [PATCH 7/8] wip Signed-off-by: ragnep --- .../user/brain/UserPageBrainSidebar.tsx | 25 ++++++------------- .../brain/UserPageBrainSidebarCreated.tsx | 7 ++---- .../brain/UserPageBrainSidebarMobileStrip.tsx | 18 +++++-------- .../brain/UserPageBrainSidebarMostActive.tsx | 7 ++---- 4 files changed, 17 insertions(+), 40 deletions(-) diff --git a/components/user/brain/UserPageBrainSidebar.tsx b/components/user/brain/UserPageBrainSidebar.tsx index 25038631b6..fe82d25859 100644 --- a/components/user/brain/UserPageBrainSidebar.tsx +++ b/components/user/brain/UserPageBrainSidebar.tsx @@ -18,26 +18,19 @@ export default function UserPageBrainSidebar({ }) { const identity = getProfileWaveIdentity(profile); const hasIdentity = identity.length > 0; - const { - waves: createdWaves, - status: createdStatus, - error: createdError, - } = useWaves({ + const { waves: createdWaves, status: createdStatus } = useWaves({ identity, waveName: null, enabled: hasIdentity, directMessage: false, limit: 20, }); - const { - waves: mostActiveWaves, - status: mostActiveStatus, - error: mostActiveError, - } = useFavouriteWavesOfIdentity({ - identityKey: identity, - limit: 3, - enabled: hasIdentity, - }); + const { waves: mostActiveWaves, status: mostActiveStatus } = + useFavouriteWavesOfIdentity({ + identityKey: identity, + limit: 3, + enabled: hasIdentity, + }); const { isModalOpen, handleBadgeClick, handleModalClose } = useWaveCreatorPreviewModal(); const modalUser = useMemo( @@ -64,10 +57,8 @@ export default function UserPageBrainSidebar({ @@ -79,12 +70,10 @@ export default function UserPageBrainSidebar({ identity={identity} waves={createdWaves} status={createdStatus} - error={createdError} />
diff --git a/components/user/brain/UserPageBrainSidebarCreated.tsx b/components/user/brain/UserPageBrainSidebarCreated.tsx index 774782a02b..368f6ab213 100644 --- a/components/user/brain/UserPageBrainSidebarCreated.tsx +++ b/components/user/brain/UserPageBrainSidebarCreated.tsx @@ -9,14 +9,12 @@ interface UserPageBrainSidebarCreatedProps { readonly identity: string; readonly waves: ApiWave[]; readonly status: QueryStatus; - readonly error: unknown; } export default function UserPageBrainSidebarCreated({ identity, waves, status, - error, }: UserPageBrainSidebarCreatedProps) { const [expandedIdentity, setExpandedIdentity] = useState(null); const showAllWaves = expandedIdentity === identity; @@ -26,9 +24,8 @@ export default function UserPageBrainSidebarCreated({ remainingWavesCount === 1 ? "Show 1 more" : `Show ${remainingWavesCount} more`; - const shouldShowLoading = status === "pending"; - const shouldShowWaves = - (error === null || error === undefined) && waves.length > 0; + const shouldShowLoading = status === "pending" && waves.length === 0; + const shouldShowWaves = waves.length > 0; if (!shouldShowLoading && !shouldShowWaves) { return null; } diff --git a/components/user/brain/UserPageBrainSidebarMobileStrip.tsx b/components/user/brain/UserPageBrainSidebarMobileStrip.tsx index 408e57d804..1231c1f479 100644 --- a/components/user/brain/UserPageBrainSidebarMobileStrip.tsx +++ b/components/user/brain/UserPageBrainSidebarMobileStrip.tsx @@ -13,10 +13,8 @@ import WavesIcon from "@/components/common/icons/WavesIcon"; interface UserPageBrainSidebarMobileStripProps { readonly createdWaves: ApiWave[]; readonly createdStatus: QueryStatus; - readonly createdError: unknown; readonly mostActiveWaves: ApiWave[]; readonly mostActiveStatus: QueryStatus; - readonly mostActiveError: unknown; readonly onOpenCreatedWaves: () => void; } @@ -77,20 +75,16 @@ function MobileWavePillSkeleton({ keyId }: { readonly keyId: string }) { export default function UserPageBrainSidebarMobileStrip({ createdWaves, createdStatus, - createdError, mostActiveWaves, mostActiveStatus, - mostActiveError, onOpenCreatedWaves, }: UserPageBrainSidebarMobileStripProps) { - const shouldShowCreatedLoading = createdStatus === "pending"; - const shouldShowCreatedWaves = - (createdError === null || createdError === undefined) && - createdWaves.length > 0; - const shouldShowMostActiveLoading = mostActiveStatus === "pending"; - const shouldShowMostActiveWaves = - (mostActiveError === null || mostActiveError === undefined) && - mostActiveWaves.length > 0; + const shouldShowCreatedLoading = + createdStatus === "pending" && createdWaves.length === 0; + const shouldShowCreatedWaves = createdWaves.length > 0; + const shouldShowMostActiveLoading = + mostActiveStatus === "pending" && mostActiveWaves.length === 0; + const shouldShowMostActiveWaves = mostActiveWaves.length > 0; const shouldShowCreatedSection = shouldShowCreatedLoading || shouldShowCreatedWaves; const shouldShowMostActiveSection = diff --git a/components/user/brain/UserPageBrainSidebarMostActive.tsx b/components/user/brain/UserPageBrainSidebarMostActive.tsx index 9c0dff0b02..9cfc1634dc 100644 --- a/components/user/brain/UserPageBrainSidebarMostActive.tsx +++ b/components/user/brain/UserPageBrainSidebarMostActive.tsx @@ -7,17 +7,14 @@ import UserPageBrainSidebarWaveItem from "./UserPageBrainSidebarWaveItem"; interface UserPageBrainSidebarMostActiveProps { readonly waves: ApiWave[]; readonly status: QueryStatus; - readonly error: unknown; } export default function UserPageBrainSidebarMostActive({ waves, status, - error, }: UserPageBrainSidebarMostActiveProps) { - const shouldShowLoading = status === "pending"; - const shouldShowWaves = - (error === null || error === undefined) && waves.length > 0; + const shouldShowLoading = status === "pending" && waves.length === 0; + const shouldShowWaves = waves.length > 0; if (!shouldShowLoading && !shouldShowWaves) { return null; } From 0013b73d55b52aed79f8c22303f23b0e8fa5c339 Mon Sep 17 00:00:00 2001 From: ragnep Date: Fri, 13 Mar 2026 13:28:57 +0200 Subject: [PATCH 8/8] wip Signed-off-by: ragnep --- components/user/brain/UserPageBrainSidebarWaveItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/user/brain/UserPageBrainSidebarWaveItem.tsx b/components/user/brain/UserPageBrainSidebarWaveItem.tsx index 27c01bb13c..9fa26f6a90 100644 --- a/components/user/brain/UserPageBrainSidebarWaveItem.tsx +++ b/components/user/brain/UserPageBrainSidebarWaveItem.tsx @@ -69,7 +69,7 @@ export default function UserPageBrainSidebarWaveItem({