diff --git a/__tests__/components/brain/BrainMobile.test.tsx b/__tests__/components/brain/BrainMobile.test.tsx
index 86627206db..2148e78014 100644
--- a/__tests__/components/brain/BrainMobile.test.tsx
+++ b/__tests__/components/brain/BrainMobile.test.tsx
@@ -1,98 +1,148 @@
-import { render, screen, waitFor } from '@testing-library/react';
-import BrainMobile from '@/components/brain/BrainMobile';
+import { render, screen, waitFor } from "@testing-library/react";
+import BrainMobile from "@/components/brain/BrainMobile";
-jest.mock('next/image', () => ({ __esModule: true, default: (props:any) =>
}));
+jest.mock("next/image", () => ({
+ __esModule: true,
+ default: (props: any) =>
,
+}));
let mockSearchParams = new URLSearchParams();
-let mockPathname = '/';
+let mockPathname = "/";
const mockPush = jest.fn();
-jest.mock('next/navigation', () => ({
+jest.mock("next/navigation", () => ({
useRouter: () => ({ push: mockPush }),
useSearchParams: () => mockSearchParams,
usePathname: () => mockPathname,
}));
let isApp = true;
-jest.mock('@/hooks/useDeviceInfo', () => ({ __esModule: true, default: () => ({ isApp }) }));
+jest.mock("@/hooks/useDeviceInfo", () => ({
+ __esModule: true,
+ default: () => ({ isApp }),
+}));
let dropData: any = null;
let waveData: any = null;
-jest.mock('@tanstack/react-query', () => ({
+jest.mock("@tanstack/react-query", () => ({
keepPreviousData: {},
useQuery: jest.fn(() => ({ data: dropData })),
}));
-jest.mock('@/hooks/useWaveData', () => ({
- useWaveData: () => ({ data: waveData })
+jest.mock("@/hooks/useWaveData", () => ({
+ useWaveData: () => ({ data: waveData }),
}));
-jest.mock('@/hooks/useWave', () => ({
- useWave: () => ({ isMemesWave: false, isRankWave: true })
+jest.mock("@/hooks/useWave", () => ({
+ useWave: () => ({
+ isMemesWave: false,
+ isCurationWave: false,
+ isRankWave: true,
+ }),
}));
-jest.mock('@/hooks/useWaveTimers', () => ({
- useWaveTimers: () => ({ voting: { isCompleted: false }, decisions: { firstDecisionDone: true } })
+jest.mock("@/hooks/useWaveTimers", () => ({
+ useWaveTimers: () => ({
+ voting: { isCompleted: false },
+ decisions: { firstDecisionDone: true },
+ }),
}));
-jest.mock('@/components/brain/BrainDesktopDrop', () => ({ __esModule: true, default: (props:any) =>
drop
}));
+jest.mock("@/components/brain/BrainDesktopDrop", () => ({
+ __esModule: true,
+ default: (props: any) => (
+
+ drop
+
+ ),
+}));
-jest.mock('@/components/brain/mobile/BrainMobileTabs', () => ({ __esModule: true, default: () => }));
+jest.mock("@/components/brain/mobile/BrainMobileTabs", () => ({
+ __esModule: true,
+ default: () => ,
+}));
-jest.mock('@/components/brain/mobile/BrainMobileAbout', () => ({ __esModule: true, default: () => }));
+jest.mock("@/components/brain/mobile/BrainMobileAbout", () => ({
+ __esModule: true,
+ default: () => ,
+}));
-jest.mock('@/components/brain/mobile/BrainMobileWaves', () => ({ __esModule: true, default: () => }));
+jest.mock("@/components/brain/mobile/BrainMobileWaves", () => ({
+ __esModule: true,
+ default: () => ,
+}));
-jest.mock('@/components/brain/mobile/BrainMobileMessages', () => ({ __esModule: true, default: () => }));
+jest.mock("@/components/brain/mobile/BrainMobileMessages", () => ({
+ __esModule: true,
+ default: () => ,
+}));
-jest.mock('@/components/brain/notifications', () => ({ __esModule: true, default: () => }));
+jest.mock("@/components/brain/notifications", () => ({
+ __esModule: true,
+ default: () => ,
+}));
-jest.mock('@/components/brain/my-stream/MyStreamWaveLeaderboard', () => ({ __esModule: true, default: () => }));
+jest.mock("@/components/brain/my-stream/MyStreamWaveLeaderboard", () => ({
+ __esModule: true,
+ default: () => ,
+}));
-jest.mock('@/components/brain/my-stream/MyStreamWaveOutcome', () => ({ __esModule: true, default: () => }));
+jest.mock("@/components/brain/my-stream/MyStreamWaveOutcome", () => ({
+ __esModule: true,
+ default: () => ,
+}));
-jest.mock('@/components/waves/winners/WaveWinners', () => ({ __esModule: true, WaveWinners: () => }));
+jest.mock("@/components/waves/winners/WaveWinners", () => ({
+ __esModule: true,
+ WaveWinners: () => ,
+}));
-jest.mock('@/components/brain/my-stream/votes/MyStreamWaveMyVotes', () => ({ __esModule: true, default: () => }));
+jest.mock("@/components/brain/my-stream/votes/MyStreamWaveMyVotes", () => ({
+ __esModule: true,
+ default: () => ,
+}));
-jest.mock('@/components/brain/my-stream/MyStreamWaveFAQ', () => ({ __esModule: true, default: () => }));
+jest.mock("@/components/brain/my-stream/MyStreamWaveFAQ", () => ({
+ __esModule: true,
+ default: () => ,
+}));
// Tests
-describe('BrainMobile', () => {
+describe("BrainMobile", () => {
beforeEach(() => {
mockSearchParams = new URLSearchParams();
- mockPathname = '/';
+ mockPathname = "/";
mockPush.mockClear();
dropData = null;
waveData = null;
isApp = true;
});
- it('renders BrainDesktopDrop when drop is open', () => {
- mockSearchParams.set('drop', 'd1');
- dropData = { id: 'd1' };
+ it("renders BrainDesktopDrop when drop is open", () => {
+ mockSearchParams.set("drop", "d1");
+ dropData = { id: "d1" };
render(child);
- expect(screen.getByTestId('drop')).toBeInTheDocument();
+ expect(screen.getByTestId("drop")).toBeInTheDocument();
});
- it('shows notifications view when path matches', async () => {
- mockPathname = '/notifications';
+ it("shows notifications view when path matches", async () => {
+ mockPathname = "/notifications";
render(child);
await waitFor(() => {
- expect(screen.getByTestId('notifications')).toBeInTheDocument();
+ expect(screen.getByTestId("notifications")).toBeInTheDocument();
});
});
- it('shows tabs only when wave active or not in app', async () => {
+ it("shows tabs only when wave active or not in app", async () => {
isApp = true;
render(child);
- expect(screen.queryByTestId('tabs')).toBeNull();
+ expect(screen.queryByTestId("tabs")).toBeNull();
- mockSearchParams.set('wave', '1');
+ mockSearchParams.set("wave", "1");
const { rerender } = render(child);
- await waitFor(() => expect(screen.getByTestId('tabs')).toBeInTheDocument());
+ await waitFor(() => expect(screen.getByTestId("tabs")).toBeInTheDocument());
rerender();
});
});
diff --git a/__tests__/components/brain/mobile/BrainMobileTabs.test.tsx b/__tests__/components/brain/mobile/BrainMobileTabs.test.tsx
index 68140fa0d2..4746715f31 100644
--- a/__tests__/components/brain/mobile/BrainMobileTabs.test.tsx
+++ b/__tests__/components/brain/mobile/BrainMobileTabs.test.tsx
@@ -18,12 +18,12 @@ enum BrainView {
const push = jest.fn();
-jest.mock("next/navigation", () => ({
- useRouter: () => ({ push }),
+jest.mock("next/navigation", () => ({
+ useRouter: () => ({ push }),
useSearchParams: () => ({
- get: jest.fn().mockReturnValue(null)
+ get: jest.fn().mockReturnValue(null),
}),
- usePathname: () => "/brain"
+ usePathname: () => "/brain",
}));
jest.mock("react-use", () => ({
@@ -72,6 +72,7 @@ describe("BrainMobileTabs", () => {
jest.clearAllMocks();
(useWave as jest.Mock).mockReturnValue({
isMemesWave: false,
+ isCurationWave: false,
isRankWave: false,
});
(useUnreadIndicator as jest.Mock).mockReturnValue({ hasUnread: false });
@@ -83,6 +84,7 @@ describe("BrainMobileTabs", () => {
it("renders back button and navigates to My Stream", async () => {
(useWave as jest.Mock).mockReturnValue({
isMemesWave: false,
+ isCurationWave: false,
isRankWave: false,
});
render(
@@ -106,6 +108,7 @@ describe("BrainMobileTabs", () => {
it("shows unread indicators and handles message/notification clicks", async () => {
(useWave as jest.Mock).mockReturnValue({
isMemesWave: false,
+ isCurationWave: false,
isRankWave: false,
});
(useUnreadIndicator as jest.Mock).mockReturnValue({ hasUnread: true });
@@ -141,6 +144,7 @@ describe("BrainMobileTabs", () => {
it("renders leaderboard and extra tabs for memes rank wave", () => {
(useWave as jest.Mock).mockReturnValue({
isMemesWave: true,
+ isCurationWave: false,
isRankWave: true,
});
@@ -168,4 +172,27 @@ describe("BrainMobileTabs", () => {
expect(screen.getByText("Outcome")).toBeInTheDocument();
expect(screen.getByText("FAQ")).toBeInTheDocument();
});
+
+ it("renders My Votes for curation rank wave", () => {
+ (useWave as jest.Mock).mockReturnValue({
+ isMemesWave: false,
+ isCurationWave: true,
+ isRankWave: true,
+ });
+
+ render(
+
+ );
+
+ expect(screen.getByText("My Votes")).toBeInTheDocument();
+ expect(screen.queryByText("FAQ")).toBeNull();
+ });
});
diff --git a/__tests__/components/brain/my-stream/MyStreamWaveDesktopTabs.test.tsx b/__tests__/components/brain/my-stream/MyStreamWaveDesktopTabs.test.tsx
index 203b5f7849..26d75070a1 100644
--- a/__tests__/components/brain/my-stream/MyStreamWaveDesktopTabs.test.tsx
+++ b/__tests__/components/brain/my-stream/MyStreamWaveDesktopTabs.test.tsx
@@ -120,6 +120,38 @@ describe("MyStreamWaveDesktopTabs", () => {
expect(screen.queryByText("My Votes")).toBeNull();
});
+ it("shows My Votes for curation waves", () => {
+ mockWaveInfo = {
+ isChatWave: false,
+ isMemesWave: false,
+ isCurationWave: true,
+ isRankWave: false,
+ };
+ mockAvailableTabs = [
+ MyStreamWaveTab.CHAT,
+ MyStreamWaveTab.MY_VOTES,
+ MyStreamWaveTab.LEADERBOARD,
+ ];
+ renderComponent(MyStreamWaveTab.MY_VOTES);
+
+ expect(screen.getByText("My Votes")).toBeInTheDocument();
+ expect(setActiveTab).not.toHaveBeenCalled();
+ });
+
+ it("keeps FAQ hidden outside memes waves", () => {
+ mockWaveInfo = {
+ isChatWave: false,
+ isMemesWave: false,
+ isCurationWave: true,
+ isRankWave: false,
+ };
+ mockAvailableTabs = [MyStreamWaveTab.CHAT, MyStreamWaveTab.FAQ];
+ renderComponent(MyStreamWaveTab.FAQ);
+
+ expect(screen.queryByText("FAQ")).toBeNull();
+ expect(setActiveTab).toHaveBeenCalledWith(MyStreamWaveTab.CHAT);
+ });
+
it("does not render countdown; parent header handles it", () => {
const spy = jest.spyOn(Time, "currentMillis").mockReturnValue(0);
mockWaveInfo = {
@@ -147,7 +179,6 @@ describe("MyStreamWaveDesktopTabs", () => {
expect(updateAvailableTabs).toHaveBeenCalledWith(
expect.objectContaining({
- waveId: expect.anything(),
isChatWave: false,
isMemesWave: false,
isCurationWave: true,
diff --git a/__tests__/components/brain/my-stream/votes/MyStreamWaveMyVote.test.tsx b/__tests__/components/brain/my-stream/votes/MyStreamWaveMyVote.test.tsx
index 2ab6e1a40f..b9170b5733 100644
--- a/__tests__/components/brain/my-stream/votes/MyStreamWaveMyVote.test.tsx
+++ b/__tests__/components/brain/my-stream/votes/MyStreamWaveMyVote.test.tsx
@@ -1,9 +1,15 @@
import MyStreamWaveMyVote from "@/components/brain/my-stream/votes/MyStreamWaveMyVote";
-import { fireEvent, render } from "@testing-library/react";
+import { fireEvent, render, screen } from "@testing-library/react";
+
+const mockMediaDisplay = jest.fn();
+const mockIsCurationWave = jest.fn(() => false);
jest.mock("@/components/drops/view/item/content/media/MediaDisplay", () => ({
__esModule: true,
- default: () => ,
+ default: (props: any) => {
+ mockMediaDisplay(props);
+ return ;
+ },
}));
jest.mock("@/components/brain/my-stream/votes/MyStreamWaveMyVoteVotes", () => ({
@@ -30,16 +36,32 @@ jest.mock("@/components/waves/drop/SingleWaveDropPosition", () => ({
default: ({ rank }: any) => {rank}
,
}));
+jest.mock("@/contexts/SeizeSettingsContext", () => ({
+ useSeizeSettings: () => ({
+ isCurationWave: mockIsCurationWave,
+ }),
+}));
+
describe("MyStreamWaveMyVote", () => {
const drop: any = {
id: "d1",
title: "Drop Title",
parts: [{ media: [{ url: "a", mime_type: "image/jpeg" }] }],
+ metadata: [],
+ wave: { id: "w1" },
+ nft_links: [],
+ top_raters: [],
author: { handle: "alice", cic: 1, level: 2 },
rating: 0,
raters_count: 3,
};
+ beforeEach(() => {
+ mockMediaDisplay.mockClear();
+ mockIsCurationWave.mockReset();
+ mockIsCurationWave.mockReturnValue(false);
+ });
+
it("triggers onDropClick when no text selected", () => {
const onDropClick = jest.fn();
(globalThis.getSelection as any) = () => ({ toString: () => "" });
@@ -75,4 +97,96 @@ describe("MyStreamWaveMyVote", () => {
fireEvent.click(checkbox!);
expect(onToggleCheck).toHaveBeenCalledWith("d1");
});
+
+ it("uses curation nft preview media when drop has no attached media", () => {
+ mockIsCurationWave.mockReturnValue(true);
+ const curationDrop = {
+ ...drop,
+ wave: { id: "W1" },
+ parts: [{ media: [] }],
+ nft_links: [
+ {
+ url_in_text: "https://opensea.io/assets/ethereum/0xabc/1",
+ data: {
+ media_uri: "https://cdn.example.com/fallback.png",
+ media_preview: {
+ status: "READY",
+ card_url: "https://cdn.example.com/card.webp",
+ small_url: "https://cdn.example.com/small.jpg",
+ thumb_url: "https://cdn.example.com/thumb.jpg",
+ mime_type: "image/webp",
+ },
+ },
+ },
+ ],
+ };
+
+ render();
+
+ expect(screen.getByTestId("media")).toBeInTheDocument();
+ expect(mockIsCurationWave).toHaveBeenCalledWith("w1");
+ const props = mockMediaDisplay.mock.calls.at(-1)?.[0];
+ expect(props?.media_url).toBe("https://cdn.example.com/card.webp");
+ expect(props?.media_mime_type).toBe("image/webp");
+ });
+
+ it("falls back to media_uri when preview status is not ready", () => {
+ mockIsCurationWave.mockReturnValue(true);
+ const curationDrop = {
+ ...drop,
+ parts: [{ media: [] }],
+ nft_links: [
+ {
+ url_in_text: "https://opensea.io/assets/ethereum/0xabc/1",
+ data: {
+ media_uri: "https://cdn.example.com/fallback.png",
+ media_preview: {
+ status: "PROCESSING",
+ card_url: "https://cdn.example.com/card.webp",
+ small_url: "https://cdn.example.com/small.jpg",
+ thumb_url: "https://cdn.example.com/thumb.jpg",
+ mime_type: "image/webp",
+ },
+ },
+ },
+ ],
+ };
+
+ render();
+
+ expect(screen.getByTestId("media")).toBeInTheDocument();
+ const props = mockMediaDisplay.mock.calls.at(-1)?.[0];
+ expect(props?.media_url).toBe("https://cdn.example.com/fallback.png");
+ expect(props?.media_mime_type).toBe("image/png");
+ });
+
+ it("does not use nft preview media outside curation waves", () => {
+ mockIsCurationWave.mockReturnValue(false);
+ const nonCurationDrop = {
+ ...drop,
+ parts: [{ media: [] }],
+ nft_links: [
+ {
+ url_in_text: "https://opensea.io/assets/ethereum/0xabc/1",
+ data: {
+ media_uri: "https://cdn.example.com/fallback.png",
+ media_preview: {
+ status: "READY",
+ card_url: "https://cdn.example.com/card.webp",
+ small_url: "https://cdn.example.com/small.jpg",
+ thumb_url: "https://cdn.example.com/thumb.jpg",
+ mime_type: "image/webp",
+ },
+ },
+ },
+ ],
+ };
+
+ render(
+
+ );
+
+ expect(screen.queryByTestId("media")).not.toBeInTheDocument();
+ expect(mockMediaDisplay).not.toHaveBeenCalled();
+ });
});
diff --git a/components/brain/BrainMobile.tsx b/components/brain/BrainMobile.tsx
index 3a4d195bc5..d0e8207877 100644
--- a/components/brain/BrainMobile.tsx
+++ b/components/brain/BrainMobile.tsx
@@ -107,7 +107,7 @@ const BrainMobile: React.FC = ({ children }) => {
},
});
- const { isMemesWave } = useWave(wave);
+ const { isMemesWave, isCurationWave } = useWave(wave);
const {
voting: { isCompleted },
@@ -208,7 +208,7 @@ const BrainMobile: React.FC = ({ children }) => {
const shouldResetToDefault =
(activeView === BrainView.LEADERBOARD && isCompleted) ||
(activeView === BrainView.WINNERS && !firstDecisionDone) ||
- (activeView === BrainView.MY_VOTES && !isMemesWave) ||
+ (activeView === BrainView.MY_VOTES && !isMemesWave && !isCurationWave) ||
(activeView === BrainView.FAQ && !isMemesWave);
if (shouldResetToDefault) {
@@ -232,6 +232,7 @@ const BrainMobile: React.FC = ({ children }) => {
firstDecisionDone,
activeView,
isMemesWave,
+ isCurationWave,
waveId,
pathname,
]);
diff --git a/components/brain/mobile/BrainMobileTabs.tsx b/components/brain/mobile/BrainMobileTabs.tsx
index 5c293a5ed7..8e717e94ff 100644
--- a/components/brain/mobile/BrainMobileTabs.tsx
+++ b/components/brain/mobile/BrainMobileTabs.tsx
@@ -51,7 +51,7 @@ const BrainMobileTabs: React.FC = ({
[registerRef]
);
- const { isMemesWave, isRankWave } = useWave(wave);
+ const { isMemesWave, isCurationWave, isRankWave } = useWave(wave);
// Get unread indicator for messages
const { hasUnread: hasUnreadMessages } = useUnreadIndicator({
@@ -158,8 +158,9 @@ const BrainMobileTabs: React.FC = ({
return (
-
+ className="tw-overflow-x-auto tw-px-2 tw-py-2 sm:tw-px-4 md:tw-px-6"
+ >
+
{waveActive && showStreamBack && !isApp && (
<>
{/* Divider */}
-
+
>
)}
{!waveActive && showWavesTab && (
@@ -185,7 +187,8 @@ const BrainMobileTabs: React.FC
= ({
tabRefs.current[BrainView.WAVES] = el;
}}
onClick={() => onViewChange(BrainView.WAVES)}
- className={wavesButtonClasses}>
+ className={wavesButtonClasses}
+ >
Waves
)}
@@ -195,11 +198,12 @@ const BrainMobileTabs: React.FC = ({
tabRefs.current[BrainView.MESSAGES] = el;
}}
onClick={() => onViewChange(BrainView.MESSAGES)}
- className={messagesButtonClasses}>
+ className={messagesButtonClasses}
+ >
Messages
{hasUnreadMessages && (
-
+
)}
@@ -209,7 +213,8 @@ const BrainMobileTabs: React.FC = ({
tabRefs.current[BrainView.DEFAULT] = el;
}}
onClick={onChatClick}
- className={chatButtonClasses}>
+ className={chatButtonClasses}
+ >
{waveActive ? "Chat" : "My Stream"}
@@ -220,7 +225,8 @@ const BrainMobileTabs: React.FC = ({
tabRefs.current[BrainView.ABOUT] = el;
}}
onClick={() => onViewChange(BrainView.ABOUT)}
- className={aboutButtonClasses}>
+ className={aboutButtonClasses}
+ >
About
)}
@@ -234,14 +240,15 @@ const BrainMobileTabs: React.FC = ({
tabRefs.current[view] = el;
}}
/>
- {isMemesWave && (
+ {(isMemesWave || isCurationWave) && (
<>
>
@@ -251,7 +258,8 @@ const BrainMobileTabs: React.FC = ({
tabRefs.current[BrainView.OUTCOME] = el;
}}
onClick={() => onViewChange(BrainView.OUTCOME)}
- className={outcomeButtonClasses}>
+ className={outcomeButtonClasses}
+ >
Outcome
{isMemesWave && (
@@ -260,17 +268,19 @@ const BrainMobileTabs: React.FC = ({
tabRefs.current[BrainView.FAQ] = el;
}}
onClick={() => onViewChange(BrainView.FAQ)}
- className={`tw-border-none tw-no-underline tw-flex tw-justify-center tw-items-center tw-px-2 tw-py-1.5 tw-gap-1 tw-flex-1 tw-rounded-md ${
+ className={`tw-flex tw-flex-1 tw-items-center tw-justify-center tw-gap-1 tw-rounded-md tw-border-none tw-px-2 tw-py-1.5 tw-no-underline ${
activeView === BrainView.FAQ
? "tw-bg-iron-800"
: "tw-bg-iron-950"
- }`}>
+ }`}
+ >
+ }`}
+ >
FAQ
@@ -283,11 +293,12 @@ const BrainMobileTabs: React.FC = ({
tabRefs.current[BrainView.NOTIFICATIONS] = el;
}}
onClick={onNotificationsClick}
- className={notificationsButtonClasses}>
+ className={notificationsButtonClasses}
+ >
Notifications
{haveUnreadNotifications && (
-
+
)}
diff --git a/components/brain/my-stream/MyStreamWaveDesktopTabs.tsx b/components/brain/my-stream/MyStreamWaveDesktopTabs.tsx
index f545ce19c1..1d7546bbdb 100644
--- a/components/brain/my-stream/MyStreamWaveDesktopTabs.tsx
+++ b/components/brain/my-stream/MyStreamWaveDesktopTabs.tsx
@@ -28,6 +28,15 @@ const getContentTabPanelId = (tab: MyStreamWaveTab): string =>
const AUTO_EXPAND_LIMIT = 5;
+const TAB_LABELS: Record = {
+ [MyStreamWaveTab.CHAT]: "Chat",
+ [MyStreamWaveTab.LEADERBOARD]: "Leaderboard",
+ [MyStreamWaveTab.WINNERS]: "Winners",
+ [MyStreamWaveTab.OUTCOME]: "Outcome",
+ [MyStreamWaveTab.MY_VOTES]: "My Votes",
+ [MyStreamWaveTab.FAQ]: "FAQ",
+};
+
const MyStreamWaveDesktopTabs: React.FC = ({
activeTab,
wave,
@@ -156,41 +165,35 @@ const MyStreamWaveDesktopTabs: React.FC = ({
}
}, [wave?.wave?.type, setActiveContentTab]);
- // Map enum values to label names
- const tabLabels: Record = {
- [MyStreamWaveTab.CHAT]: "Chat",
- [MyStreamWaveTab.LEADERBOARD]: "Leaderboard",
- [MyStreamWaveTab.WINNERS]: "Winners",
- [MyStreamWaveTab.OUTCOME]: "Outcome",
- [MyStreamWaveTab.MY_VOTES]: "My Votes",
- [MyStreamWaveTab.FAQ]: "FAQ",
- };
-
const options: TabOption[] = React.useMemo(
() =>
availableTabs
- .filter(
- (tab) =>
- isMemesWave ||
- ![MyStreamWaveTab.MY_VOTES, MyStreamWaveTab.FAQ].includes(tab)
- )
+ .filter((tab) => {
+ if (tab === MyStreamWaveTab.MY_VOTES) {
+ return isMemesWave || isCurationWave;
+ }
+ if (tab === MyStreamWaveTab.FAQ) {
+ return isMemesWave;
+ }
+ return true;
+ })
.map((tab) => ({
key: tab,
- label: tabLabels[tab],
+ label: TAB_LABELS[tab],
panelId: getContentTabPanelId(tab),
})),
- [availableTabs, isMemesWave]
+ [availableTabs, isMemesWave, isCurationWave]
);
useEffect(() => {
- if (
- !isMemesWave &&
- [MyStreamWaveTab.MY_VOTES, MyStreamWaveTab.FAQ].includes(activeTab) &&
- options.length > 0
- ) {
+ const isMyVotesHidden =
+ activeTab === MyStreamWaveTab.MY_VOTES && !isMemesWave && !isCurationWave;
+ const isFaqHidden = activeTab === MyStreamWaveTab.FAQ && !isMemesWave;
+
+ if ((isMyVotesHidden || isFaqHidden) && options.length > 0) {
setActiveTab(options[0]?.key!);
}
- }, [isMemesWave, activeTab, options, setActiveTab]);
+ }, [isMemesWave, isCurationWave, activeTab, options, setActiveTab]);
// For simple waves, don't render any tabs
if (isChatWave) {
diff --git a/components/brain/my-stream/votes/MyStreamWaveMyVote.tsx b/components/brain/my-stream/votes/MyStreamWaveMyVote.tsx
index f4fe308b15..a3a2a5d3b9 100644
--- a/components/brain/my-stream/votes/MyStreamWaveMyVote.tsx
+++ b/components/brain/my-stream/votes/MyStreamWaveMyVote.tsx
@@ -1,5 +1,7 @@
import MediaTypeBadge from "@/components/drops/media/MediaTypeBadge";
import MediaDisplay from "@/components/drops/view/item/content/media/MediaDisplay";
+import { useSeizeSettings } from "@/contexts/SeizeSettingsContext";
+import { ApiNftLinkMediaPreviewStatusEnum } from "@/generated/models/ApiNftLinkMediaPreview";
import UserCICAndLevel, {
UserCICAndLevelSize,
} from "@/components/user/utils/UserCICAndLevel";
@@ -23,6 +25,109 @@ interface MyStreamWaveMyVoteProps {
readonly isResetting?: boolean | undefined;
}
+type ResolvedPreviewMedia = {
+ readonly url: string;
+ readonly mimeType: string;
+};
+
+const DEFAULT_MIME_TYPE = "image/jpeg";
+
+const MIME_BY_EXTENSION: Record = {
+ avif: "image/avif",
+ gif: "image/gif",
+ jpeg: DEFAULT_MIME_TYPE,
+ jpg: DEFAULT_MIME_TYPE,
+ m4v: "video/x-m4v",
+ mov: "video/quicktime",
+ mp3: "audio/mpeg",
+ mp4: "video/mp4",
+ ogg: "audio/ogg",
+ ogv: "video/ogg",
+ flac: "audio/flac",
+ png: "image/png",
+ svg: "image/svg+xml",
+ wav: "audio/wav",
+ webm: "video/webm",
+ webp: "image/webp",
+ glb: "model/gltf-binary",
+ gltf: "model/gltf+json",
+ usdz: "model/vnd.usdz",
+};
+
+const toNonEmptyString = (value: unknown): string | null => {
+ if (typeof value !== "string") {
+ return null;
+ }
+
+ const trimmed = value.trim();
+ return trimmed.length > 0 ? trimmed : null;
+};
+
+const inferMimeTypeFromUrl = (url: string): string | undefined => {
+ try {
+ const parsed = new URL(url);
+ const extensionMatch = parsed.pathname
+ .toLowerCase()
+ .match(/\.([a-z0-9]+)$/);
+ if (!extensionMatch?.[1]) {
+ return undefined;
+ }
+
+ return MIME_BY_EXTENSION[extensionMatch[1]];
+ } catch {
+ const path = url.split("?")[0]?.toLowerCase() ?? "";
+ const extensionMatch = path.match(/\.([a-z0-9]+)$/);
+ if (!extensionMatch?.[1]) {
+ return undefined;
+ }
+
+ return MIME_BY_EXTENSION[extensionMatch[1]];
+ }
+};
+
+const resolveCurationPreviewMedia = (
+ nftLinks: ExtendedDrop["nft_links"]
+): ResolvedPreviewMedia | null => {
+ if (typeof nftLinks?.length !== "number" || nftLinks.length === 0) {
+ return null;
+ }
+
+ for (const nftLink of nftLinks) {
+ const preview = nftLink.data?.media_preview;
+ const previewMimeType = toNonEmptyString(preview?.mime_type);
+ const previewUrl =
+ preview?.status === ApiNftLinkMediaPreviewStatusEnum.Ready
+ ? (toNonEmptyString(preview.card_url) ??
+ toNonEmptyString(preview.small_url) ??
+ toNonEmptyString(preview.thumb_url))
+ : null;
+ if (previewUrl) {
+ return {
+ url: previewUrl,
+ mimeType:
+ previewMimeType ??
+ inferMimeTypeFromUrl(previewUrl) ??
+ DEFAULT_MIME_TYPE,
+ };
+ }
+
+ const fallbackUrl = toNonEmptyString(nftLink.data?.media_uri);
+ if (!fallbackUrl) {
+ continue;
+ }
+
+ return {
+ url: fallbackUrl,
+ mimeType:
+ inferMimeTypeFromUrl(fallbackUrl) ??
+ previewMimeType ??
+ DEFAULT_MIME_TYPE,
+ };
+ }
+
+ return null;
+};
+
const MyStreamWaveMyVote: React.FC = ({
drop,
onDropClick,
@@ -30,11 +135,23 @@ const MyStreamWaveMyVote: React.FC = ({
onToggleCheck,
isResetting = false,
}) => {
+ const { isCurationWave } = useSeizeSettings();
const artWork = drop.parts.at(0)?.media.at(0);
const previewImageUrl = useMemo(
() => getDropPreviewImageUrl(drop.metadata),
[drop.metadata]
);
+ const curationPreviewMedia = useMemo(
+ () =>
+ !artWork && isCurationWave(drop.wave.id.toLowerCase())
+ ? resolveCurationPreviewMedia(drop.nft_links)
+ : null,
+ [artWork, drop.nft_links, drop.wave.id, isCurationWave]
+ );
+ const resolvedMediaUrl = artWork?.url ?? curationPreviewMedia?.url ?? null;
+ const resolvedMediaMimeType =
+ artWork?.mime_type ?? curationPreviewMedia?.mimeType ?? DEFAULT_MIME_TYPE;
+ const badgeMimeType = artWork?.mime_type ?? curationPreviewMedia?.mimeType;
const handleClick = () => {
if (window.getSelection()?.toString()) {
@@ -93,10 +210,10 @@ const MyStreamWaveMyVote: React.FC = ({
- {artWork && (
+ {resolvedMediaUrl && (
= ({
@@ -118,7 +235,9 @@ const MyStreamWaveMyVote: React.FC = ({
{drop.title}
- {drop.rank &&
}
+ {typeof drop.rank === "number" && (
+
+ )}
@@ -136,11 +255,14 @@ const MyStreamWaveMyVote: React.FC = ({
user={drop.author.handle ?? drop.author.id}
>
{
e.stopPropagation();
e.preventDefault();
- window.open(`/${drop.author.handle}`, "_blank");
+ window.open(
+ `/${drop.author.handle ?? drop.author.primary_address}`,
+ "_blank"
+ );
}}
className="tw-truncate tw-text-md tw-font-medium tw-text-iron-200 tw-no-underline tw-transition-colors tw-duration-200 desktop-hover:hover:tw-text-opacity-80 desktop-hover:hover:tw-underline"
>
@@ -159,12 +281,14 @@ const MyStreamWaveMyVote: React.FC = ({
- {drop.top_raters?.slice(0, 3).map((voter) => (
-
+ {drop.top_raters.slice(0, 3).map((voter) => (
+
e.stopPropagation()}
- data-tooltip-id={`my-vote-voter-${drop.id}-${voter.profile.handle}`}
+ data-tooltip-id={`my-vote-voter-${drop.id}-${voter.profile.handle ?? voter.profile.primary_address}`}
>
{voter.profile.pfp ? (
= ({
)}
= ({
pointerEvents: "none",
}}
>
- {voter.profile.handle} -{" "}
- {formatNumberWithCommas(voter.rating)}
+ {voter.profile.handle ?? voter.profile.primary_address}{" "}
+ - {formatNumberWithCommas(voter.rating)}
))}
diff --git a/components/profile-activity/ProfileName.tsx b/components/profile-activity/ProfileName.tsx
deleted file mode 100644
index 08175ce44b..0000000000
--- a/components/profile-activity/ProfileName.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-"use client";
-
-import type { ApiIdentity } from "@/generated/models/ApiIdentity";
-import { assertUnreachable } from "@/helpers/AllowlistToolHelpers";
-import { createPossessionStr } from "@/helpers/Helpers";
-import { useIdentity } from "@/hooks/useIdentity";
-import { useParams } from "next/navigation";
-import { useEffect, useState } from "react";
-export enum ProfileNameType {
- POSSESSION = "POSSESSION",
- DEFAULT = "DEFAULT",
-}
-
-export default function ProfileName({
- type,
-}: {
- readonly type: ProfileNameType;
-}) {
- const params = useParams();
- const handleOrWallet = params?.["user"]?.toString().toLowerCase();
-
- const { profile } = useIdentity({
- handleOrWallet: handleOrWallet,
- initialProfile: null,
- });
-
- const createName = (profile: ApiIdentity | null): string => {
- switch (type) {
- case ProfileNameType.POSSESSION:
- return createPossessionStr(profile?.handle ?? null);
- case ProfileNameType.DEFAULT:
- return profile?.handle ?? "";
- default:
- assertUnreachable(type);
- return "";
- }
- };
-
- const [name, setName] = useState(createName(profile ?? null));
-
- useEffect(() => {
- setName(createName(profile ?? null));
- }, [profile]);
-
- return <>{name}>;
-}
diff --git a/components/react-query-wrapper/ReactQueryWrapper.tsx b/components/react-query-wrapper/ReactQueryWrapper.tsx
index 233d7e4d38..4086e75832 100644
--- a/components/react-query-wrapper/ReactQueryWrapper.tsx
+++ b/components/react-query-wrapper/ReactQueryWrapper.tsx
@@ -16,7 +16,7 @@ import { convertActivityLogParams } from "@/helpers/profile-logs.helpers";
import { Time } from "@/helpers/time";
import type { CountlessPage, Page } from "@/helpers/Types";
import { useQueryKeyListener } from "@/hooks/useQueryKeyListener";
-import { RateMatter } from "@/types/enums";
+import { type ProfileRatersParamsOrderBy, RateMatter } from "@/types/enums";
import {
type InfiniteData,
@@ -26,7 +26,6 @@ import {
import Cookies from "js-cookie";
import { createContext, useMemo } from "react";
import type { ActivityLogParams } from "../profile-activity/ProfileActivityLogs";
-import type { ProfileRatersParams } from "../user/utils/raters-table/wrapper/ProfileRatersTableWrapper";
import { addDropToDrops } from "./utils/addDropsToDrops";
import { increaseWavesOverviewDropsCount } from "./utils/increaseWavesOverviewDropsCount";
import {
@@ -34,6 +33,7 @@ import {
WAVE_FOLLOWING_WAVES_PARAMS,
} from "./utils/query-utils";
import { toggleWaveFollowing } from "./utils/toggleWaveFollowing";
+import type { SortDirection } from "@/entities/ISort";
export enum QueryKey {
PROFILE = "PROFILE",
@@ -108,6 +108,16 @@ export enum QueryKey {
MARKETPLACE_PREVIEW = "MARKETPLACE_PREVIEW",
}
+interface ProfileRatersParams {
+ readonly page: number;
+ readonly pageSize: number;
+ readonly given: boolean;
+ readonly order: SortDirection;
+ readonly orderBy: ProfileRatersParamsOrderBy;
+ readonly handleOrWallet: string;
+ readonly matter: RateMatter;
+}
+
interface InitProfileRatersParamsAndData {
readonly data: Page;
readonly params: ProfileRatersParams;
@@ -787,7 +797,10 @@ const createReactQueryContextValue = (
(
oldData: { pages: Page[]; pageParams: number[] } | undefined
): { pages: Page[]; pageParams: number[] } => {
- if (!oldData?.pages.length) {
+ if (
+ typeof oldData?.pages.length !== "number" ||
+ oldData.pages.length === 0
+ ) {
return {
pageParams: [1],
pages: [
diff --git a/components/user/rep/new-rep/UserPageRepNewRepSearchHeader.tsx b/components/user/rep/new-rep/UserPageRepNewRepSearchHeader.tsx
deleted file mode 100644
index b7528570b9..0000000000
--- a/components/user/rep/new-rep/UserPageRepNewRepSearchHeader.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-"use client";
-
-import { useContext, useEffect, useState } from "react";
-import type { ApiProfileRepRatesState } from "@/entities/IProfile";
-import { formatNumberWithCommas } from "@/helpers/Helpers";
-import { AuthContext } from "@/components/auth/Auth";
-import { ApiProfileProxyActionType } from "@/generated/models/ApiProfileProxyActionType";
-import { useQuery } from "@tanstack/react-query";
-import { commonApiFetch } from "@/services/api/common-api";
-import Link from "next/link";
-import { QueryKey } from "@/components/react-query-wrapper/ReactQueryWrapper";
-import type { ApiIdentity } from "@/generated/models/ApiIdentity";
-export default function UserPageRepNewRepSearchHeader({
- repRates,
- profile,
-}: {
- readonly repRates: ApiProfileRepRatesState | null;
- readonly profile: ApiIdentity;
-}) {
- const { activeProfileProxy } = useContext(AuthContext);
-
- const { data: proxyGrantorRepRates } = useQuery({
- queryKey: [
- QueryKey.PROFILE_REP_RATINGS,
- {
- rater: activeProfileProxy?.created_by.handle,
- handleOrWallet: profile?.handle,
- },
- ],
- queryFn: async () =>
- await commonApiFetch({
- endpoint: `profiles/${profile?.query}/rep/ratings/received`,
- params: activeProfileProxy?.created_by.handle
- ? { rater: activeProfileProxy.created_by.handle }
- : {},
- }),
- enabled: !!activeProfileProxy?.created_by.handle,
- });
-
- const getActiveRepRates = (): {
- available: number;
- rated: number;
- proxyCreditLeft: number | null;
- } => {
- const repProxy = activeProfileProxy?.actions.find(
- (a) => a.action_type === ApiProfileProxyActionType.AllocateRep
- );
- if (!repProxy) {
- return {
- available: repRates?.rep_rates_left_for_rater ?? 0,
- rated: repRates?.total_rep_rating_by_rater ?? 0,
- proxyCreditLeft: null,
- };
- }
- if (!proxyGrantorRepRates) {
- return {
- available: 0,
- rated: 0,
- proxyCreditLeft: null,
- };
- }
- const proxyGrantorAvailableRep =
- proxyGrantorRepRates.rep_rates_left_for_rater ?? 0;
- const proxyGrantorTotalRep =
- proxyGrantorRepRates.total_rep_rating_by_rater ?? 0;
- return {
- available: proxyGrantorAvailableRep,
- rated: proxyGrantorTotalRep,
- proxyCreditLeft: Math.max(
- 0,
- (repProxy.credit_amount ?? 0) - (repProxy.credit_spent ?? 0)
- ),
- };
- };
-
- const [activeRepRates, setActiveRepRates] = useState<{
- available: number;
- rated: number;
- proxyCreditLeft: number | null;
- }>(getActiveRepRates());
-
- useEffect(
- () => setActiveRepRates(getActiveRepRates()),
- [activeProfileProxy, proxyGrantorRepRates, repRates]
- );
-
- const getAvailableCredit = (): number => {
- if (!activeProfileProxy) {
- return activeRepRates.available;
- }
- return Math.abs(activeRepRates.available) <
- Math.abs(activeRepRates.proxyCreditLeft ?? 0)
- ? activeRepRates.available
- : activeRepRates.proxyCreditLeft ?? 0;
- };
-
- const [availableCredit, setAvailableCredit] = useState(getAvailableCredit());
- useEffect(
- () => setAvailableCredit(getAvailableCredit()),
- [activeRepRates, activeRepRates.proxyCreditLeft]
- );
- return (
-
- {!!activeProfileProxy && (
-
- You are acting as proxy for:
-
-
- {activeProfileProxy.created_by.handle}
-
-
-
- )}
-
- {!activeProfileProxy && (
- <>
-
- Your available Rep:
-
- {formatNumberWithCommas(availableCredit)}
-
-
-
- >
- )}
-
-
- Your Rep assigned to{" "}
- {profile.query ?? profile.handle ?? profile.display}:
-
-
- {repRates ? formatNumberWithCommas(activeRepRates.rated) : ""}
-
-
-
-
- );
-}
diff --git a/components/user/utils/UserTableHeaderWrapper.tsx b/components/user/utils/UserTableHeaderWrapper.tsx
deleted file mode 100644
index 6cb2b1d44a..0000000000
--- a/components/user/utils/UserTableHeaderWrapper.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-export default function UserTableHeaderWrapper({
- children,
-}: {
- readonly children: React.ReactNode;
-}) {
- return (
-
-
- {children}
-
-
- );
-}
diff --git a/components/user/utils/raters-table/ProfileRatersTable.tsx b/components/user/utils/raters-table/ProfileRatersTable.tsx
deleted file mode 100644
index 17adedc30a..0000000000
--- a/components/user/utils/raters-table/ProfileRatersTable.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import CommonTablePagination from "@/components/utils/table/paginator/CommonTablePagination";
-import type { RatingWithProfileInfoAndLevel } from "@/entities/IProfile";
-import type { SortDirection } from "@/entities/ISort";
-import type {
- ProfileRatersParamsOrderBy,
- ProfileRatersTableType,
-} from "@/types/enums";
-import ProfileRatersTableBody from "./ProfileRatersTableBody";
-import ProfileRatersTableHeader from "./ProfileRatersTableHeader";
-
-export default function ProfileRatersTable({
- ratings,
- type,
- order,
- orderBy,
- loading,
- totalPages,
- currentPage,
- setCurrentPage,
- onSortTypeClick,
- noRatingsMessage,
-}: {
- readonly ratings: RatingWithProfileInfoAndLevel[];
- readonly type: ProfileRatersTableType;
- readonly order: SortDirection;
- readonly orderBy: ProfileRatersParamsOrderBy;
- readonly loading: boolean;
- readonly totalPages: number;
- readonly currentPage: number;
- readonly setCurrentPage: (page: number) => void;
- readonly onSortTypeClick: (type: ProfileRatersParamsOrderBy) => void;
- readonly noRatingsMessage: string;
-}) {
- return (
- <>
- {ratings.length ? (
-
-
-
- {totalPages > 1 && (
-
- )}
-
- ) : (
-
-
- {noRatingsMessage}
-
-
- )}
- >
- );
-}
diff --git a/components/user/utils/raters-table/ProfileRatersTableBody.tsx b/components/user/utils/raters-table/ProfileRatersTableBody.tsx
deleted file mode 100644
index 55169d2fde..0000000000
--- a/components/user/utils/raters-table/ProfileRatersTableBody.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import type { RatingWithProfileInfoAndLevel } from "@/entities/IProfile";
-import { getRandomObjectId } from "@/helpers/AllowlistToolHelpers";
-import ProfileRatersTableItem from "./ProfileRatersTableItem";
-
-export default function ProfileRatersTableBody({
- ratings,
-}: {
- readonly ratings: RatingWithProfileInfoAndLevel[];
-}) {
- return (
-
- {ratings.map((rating) => (
-
- ))}
-
- );
-}
diff --git a/components/user/utils/raters-table/ProfileRatersTableHeader.tsx b/components/user/utils/raters-table/ProfileRatersTableHeader.tsx
deleted file mode 100644
index e92b17e2c5..0000000000
--- a/components/user/utils/raters-table/ProfileRatersTableHeader.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import type { SortDirection } from "@/entities/ISort";
-import { assertUnreachable } from "@/helpers/AllowlistToolHelpers";
-import {
- ProfileRatersParamsOrderBy,
- ProfileRatersTableType,
-} from "@/types/enums";
-import ProfileRatersTableHeaderSortableCell from "./ProfileRatersTableHeaderSortableCell";
-
-export default function ProfileRatersTableHeader({
- type,
- sortDirection,
- sortOrderBy,
- isLoading,
- onSortTypeClick,
-}: {
- readonly type: ProfileRatersTableType;
- readonly sortDirection: SortDirection;
- readonly sortOrderBy: ProfileRatersParamsOrderBy;
- readonly isLoading: boolean;
- readonly onSortTypeClick: (newSortType: ProfileRatersParamsOrderBy) => void;
-}) {
- const getTotalTitle = (): string => {
- switch (type) {
- case ProfileRatersTableType.CIC_RECEIVED:
- case ProfileRatersTableType.CIC_GIVEN:
- return "Total NIC";
- case ProfileRatersTableType.REP_RECEIVED:
- case ProfileRatersTableType.REP_GIVEN:
- return "Total Rep";
- default:
- assertUnreachable(type);
- return "";
- }
- };
-
- return (
-
-
- |
- Name
- |
-
-
-
-
- );
-}
diff --git a/components/user/utils/raters-table/ProfileRatersTableHeaderSortableCell.tsx b/components/user/utils/raters-table/ProfileRatersTableHeaderSortableCell.tsx
deleted file mode 100644
index cdbf1d4d37..0000000000
--- a/components/user/utils/raters-table/ProfileRatersTableHeaderSortableCell.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-"use client";
-
-import CircleLoader, {
- CircleLoaderSize,
-} from "@/components/distribution-plan-tool/common/CircleLoader";
-import { SortDirection } from "@/entities/ISort";
-import type { ProfileRatersParamsOrderBy } from "@/types/enums";
-import { useEffect, useState } from "react";
-import CommonTableSortIcon from "../icons/CommonTableSortIcon";
-
-export default function ProfileRatersTableHeaderSortableCell({
- title,
- sortType,
- sortDirection,
- sortOrderBy,
- isLoading,
- onSortTypeClick,
-}: {
- readonly title: string;
- readonly sortType: ProfileRatersParamsOrderBy;
- readonly sortDirection: SortDirection;
- readonly sortOrderBy: ProfileRatersParamsOrderBy;
- readonly isLoading: boolean;
- readonly onSortTypeClick: (newSortType: ProfileRatersParamsOrderBy) => void;
-}) {
- const [isActive, setIsActive] = useState(sortType === sortOrderBy);
- useEffect(() => {
- setIsActive(sortType === sortOrderBy);
- }, [sortType, sortOrderBy]);
-
- return (
- onSortTypeClick(sortType)}
- className="tw-group tw-cursor-pointer tw-whitespace-nowrap tw-px-4 tw-py-3.5 tw-text-right tw-text-[11px] tw-font-bold tw-text-iron-500 tw-uppercase tw-tracking-widest sm:tw-px-6 lg:tw-pl-4"
- >
-
- {title}
-
- {isLoading && isActive ? (
-
-
-
- ) : (
-
-
-
- )}
- |
- );
-}
diff --git a/components/user/utils/raters-table/ProfileRatersTableItem.tsx b/components/user/utils/raters-table/ProfileRatersTableItem.tsx
deleted file mode 100644
index ec899a82a8..0000000000
--- a/components/user/utils/raters-table/ProfileRatersTableItem.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-"use client";
-
-import type { RatingWithProfileInfoAndLevel } from "@/entities/IProfile";
-import { formatNumberWithCommas, getTimeAgo } from "@/helpers/Helpers";
-import Link from "next/link";
-import UserCICAndLevel from "../UserCICAndLevel";
-
-export default function ProfileRatersTableItem({
- rating,
-}: {
- readonly rating: RatingWithProfileInfoAndLevel;
-}) {
- const getRatingStr = (rating: number) => {
- return rating > 0
- ? `+${formatNumberWithCommas(rating)}`
- : `${formatNumberWithCommas(rating)}`;
- };
- const ratingStr = getRatingStr(rating.rating);
- const isPositiveRating = rating.rating > 0;
- const ratingColor = isPositiveRating ? "tw-text-green" : "tw-text-red";
- const timeAgo = getTimeAgo(new Date(rating.last_modified).getTime());
-
- const getProfileRoute = (): string => `/${rating.handle}/identity`;
-
- const profileRoute = getProfileRoute();
-
- return (
-
-
-
-
-
-
-
- {rating.handle}
-
-
-
-
- |
-
-
- {ratingStr}
-
- |
-
-
- {timeAgo}
-
- |
-
- );
-}
diff --git a/components/user/utils/raters-table/wrapper/ProfileRatersTableWrapper.tsx b/components/user/utils/raters-table/wrapper/ProfileRatersTableWrapper.tsx
deleted file mode 100644
index 7c5b61aed2..0000000000
--- a/components/user/utils/raters-table/wrapper/ProfileRatersTableWrapper.tsx
+++ /dev/null
@@ -1,165 +0,0 @@
-"use client";
-
-import { QueryKey } from "@/components/react-query-wrapper/ReactQueryWrapper";
-import CommonSkeletonLoader from "@/components/utils/animation/CommonSkeletonLoader";
-import type { RatingWithProfileInfoAndLevel } from "@/entities/IProfile";
-
-import { SortDirection } from "@/entities/ISort";
-import { assertUnreachable } from "@/helpers/AllowlistToolHelpers";
-import type { Page } from "@/helpers/Types";
-import { commonApiFetch } from "@/services/api/common-api";
-import type { ProfileRatersParamsOrderBy } from "@/types/enums";
-import { ProfileRatersTableType, RateMatter } from "@/types/enums";
-import { keepPreviousData, useQuery } from "@tanstack/react-query";
-import { useParams } from "next/navigation";
-import { useEffect, useState } from "react";
-import ProfileRatersTable from "../ProfileRatersTable";
-import ProfileRatersTableWrapperHeader from "./ProfileRatersTableWrapperHeader";
-
-export interface ProfileRatersParams {
- readonly page: number;
- readonly pageSize: number;
- readonly given: boolean;
- readonly order: SortDirection;
- readonly orderBy: ProfileRatersParamsOrderBy;
- readonly handleOrWallet: string;
- readonly matter: RateMatter;
-}
-
-export default function ProfileRatersTableWrapper({
- initialParams,
-}: {
- readonly initialParams: ProfileRatersParams;
-}) {
- const params = useParams();
- const handleOrWallet = (params?.["user"] as string)?.toLowerCase();
- const pageSize = initialParams.pageSize;
- const given = initialParams.given;
- const matter = initialParams.matter;
-
- const [currentPage, setCurrentPage] = useState(initialParams.page);
- const [order, setOrder] = useState(initialParams.order);
- const [orderBy, setOrderBy] = useState(
- initialParams.orderBy
- );
-
- const getType = (): ProfileRatersTableType => {
- switch (matter) {
- case RateMatter.NIC:
- return given
- ? ProfileRatersTableType.CIC_GIVEN
- : ProfileRatersTableType.CIC_RECEIVED;
- case RateMatter.REP:
- case RateMatter.DROP_REP:
- return given
- ? ProfileRatersTableType.REP_GIVEN
- : ProfileRatersTableType.REP_RECEIVED;
- default:
- assertUnreachable(matter);
- return ProfileRatersTableType.CIC_RECEIVED;
- }
- };
-
- const type = getType();
-
- const {
- isLoading,
- isFetching,
- data: ratings,
- } = useQuery>({
- queryKey: [
- QueryKey.PROFILE_RATERS,
- {
- handleOrWallet,
- matter,
- page: `${currentPage}`,
- pageSize: `${pageSize}`,
- order,
- orderBy,
- given,
- },
- ],
- queryFn: async () =>
- await commonApiFetch>({
- endpoint: `profiles/${handleOrWallet}/${
- matter === RateMatter.NIC ? "cic" : matter.toLowerCase()
- }/ratings/by-rater`,
- params: {
- page: `${currentPage}`,
- page_size: `${pageSize}`,
- order: order.toLowerCase(),
- order_by: orderBy.toLowerCase(),
- given: given ? "true" : "false",
- },
- }),
- enabled: !!handleOrWallet,
- placeholderData: keepPreviousData,
- });
-
- const [totalPages, setTotalPages] = useState(1);
-
- useEffect(() => {
- if (isLoading) return;
- if (!ratings?.count) {
- setCurrentPage(1);
- setTotalPages(1);
- return;
- }
- setTotalPages(Math.ceil(ratings.count / pageSize));
- }, [ratings?.count, ratings?.page, isLoading]);
-
- const getNoRatingsMessage = (): string => {
- switch (type) {
- case ProfileRatersTableType.CIC_RECEIVED:
- return "No NIC received ratings";
- case ProfileRatersTableType.CIC_GIVEN:
- return "No NIC given ratings";
- case ProfileRatersTableType.REP_RECEIVED:
- return "No Rep received ratings";
- case ProfileRatersTableType.REP_GIVEN:
- return "No Rep given ratings";
- default:
- assertUnreachable(type);
- return "";
- }
- };
-
- const onSortTypeClick = (newSortType: ProfileRatersParamsOrderBy) => {
- if (newSortType === orderBy) {
- setOrder((prev) =>
- prev === SortDirection.ASC ? SortDirection.DESC : SortDirection.ASC
- );
- } else {
- setOrderBy(newSortType);
- setOrder(SortDirection.DESC);
- }
- };
-
- return (
-
-
-
- {isLoading ? (
-
-
-
- ) : (
-
- )}
-
-
- );
-}
diff --git a/components/user/utils/raters-table/wrapper/ProfileRatersTableWrapperHeader.tsx b/components/user/utils/raters-table/wrapper/ProfileRatersTableWrapperHeader.tsx
deleted file mode 100644
index 926a205b6e..0000000000
--- a/components/user/utils/raters-table/wrapper/ProfileRatersTableWrapperHeader.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import ProfileName, {
- ProfileNameType,
-} from "@/components/profile-activity/ProfileName";
-import UserTableHeaderWrapper from "@/components/user/utils/UserTableHeaderWrapper";
-import { ProfileRatersTableType } from "@/types/enums";
-
-export default function ProfileRatersTableWrapperHeader({
- type,
-}: {
- readonly type: ProfileRatersTableType;
-}) {
- return (
-
- {type === ProfileRatersTableType.CIC_RECEIVED && (
-
- )}
- {type === ProfileRatersTableType.CIC_GIVEN && (
-
- )}
- {type === ProfileRatersTableType.REP_RECEIVED && (
-
- )}
- {type === ProfileRatersTableType.REP_GIVEN && (
-
- )}
-
- );
-}
diff --git a/components/utils/CommonFilterTargetSelect.tsx b/components/utils/CommonFilterTargetSelect.tsx
deleted file mode 100644
index a496a210fe..0000000000
--- a/components/utils/CommonFilterTargetSelect.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-"use client";
-
-import { useId } from "react";
-
-import { ProfileActivityFilterTargetType } from "@/types/enums";
-
-const TARGETS = [
- { id: ProfileActivityFilterTargetType.ALL, name: "All" },
- { id: ProfileActivityFilterTargetType.OUTGOING, name: "Outgoing" },
- { id: ProfileActivityFilterTargetType.INCOMING, name: "Incoming" },
-];
-
-export default function CommonFilterTargetSelect({
- selected,
- onChange,
-}: {
- readonly selected: ProfileActivityFilterTargetType;
- readonly onChange: (filter: ProfileActivityFilterTargetType) => void;
-}) {
- const baseId = useId().replaceAll(":", "");
- const groupName = `filter-target-${baseId}`;
-
- return (
-
- );
-}
diff --git a/components/utils/table/CommonTableWrapper.tsx b/components/utils/table/CommonTableWrapper.tsx
deleted file mode 100644
index 40c393c4c7..0000000000
--- a/components/utils/table/CommonTableWrapper.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-export default function CommonTableWrapper({
- children,
-}: {
- readonly children: React.ReactNode;
-}) {
- return (
-
- );
-}
diff --git a/helpers/server.helpers.ts b/helpers/server.helpers.ts
index 14f63d53e8..ea45fe3bfc 100644
--- a/helpers/server.helpers.ts
+++ b/helpers/server.helpers.ts
@@ -1,11 +1,7 @@
import type { ActivityLogParamsConverted } from "@/components/profile-activity/ProfileActivityLogs";
-import type { ProfileRatersParams } from "@/components/user/utils/raters-table/wrapper/ProfileRatersTableWrapper";
import type { ProfileActivityLog } from "@/entities/IProfile";
-import { SortDirection } from "@/entities/ISort";
import type { ApiIdentity } from "@/generated/models/ApiIdentity";
import { commonApiFetch } from "@/services/api/common-api";
-import type { RateMatter } from "@/types/enums";
-import { ProfileRatersParamsOrderBy } from "@/types/enums";
import type { Page } from "./Types";
export const getUserProfile = async ({
@@ -121,21 +117,3 @@ export const getUserProfileActivityLogs = async ({
};
}
};
-
-export const getInitialRatersParams = ({
- handleOrWallet,
- given,
- matter,
-}: {
- handleOrWallet: string;
- given: boolean;
- matter: RateMatter;
-}): ProfileRatersParams => ({
- page: 1,
- pageSize: 7,
- given,
- matter,
- order: SortDirection.DESC,
- orderBy: ProfileRatersParamsOrderBy.RATING,
- handleOrWallet,
-});
diff --git a/types/enums.ts b/types/enums.ts
index 278f0c0098..acd51c0d19 100644
--- a/types/enums.ts
+++ b/types/enums.ts
@@ -156,13 +156,6 @@ export enum RateMatter {
DROP_REP = "DROP_REP",
}
-export enum ProfileRatersTableType {
- CIC_RECEIVED = "CIC_RECEIVED",
- CIC_GIVEN = "CIC_GIVEN",
- REP_RECEIVED = "REP_RECEIVED",
- REP_GIVEN = "REP_GIVEN",
-}
-
export enum ProfileRatersParamsOrderBy {
RATING = "RATING",
LAST_MODIFIED = "LAST_MODIFIED",