Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions __tests__/components/waves/outcome/WaveOutcome.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,32 @@ import { ApiWaveOutcomeCredit } from "@/generated/models/ApiWaveOutcomeCredit";

describe("WaveOutcome", () => {
it("renders rep outcome", () => {
render(<WaveOutcome outcome={{ credit: ApiWaveOutcomeCredit.Rep } as any} />);
render(
<WaveOutcome
waveId="wave-1"
outcome={{ credit: ApiWaveOutcomeCredit.Rep, index: 0 } as any}
/>
);
expect(screen.getByTestId("rep")).toBeInTheDocument();
});

it("renders nic outcome", () => {
render(<WaveOutcome outcome={{ credit: ApiWaveOutcomeCredit.Cic } as any} />);
render(
<WaveOutcome
waveId="wave-2"
outcome={{ credit: ApiWaveOutcomeCredit.Cic, index: 1 } as any}
/>
);
expect(screen.getByTestId("nic")).toBeInTheDocument();
});

it("renders manual outcome", () => {
render(<WaveOutcome outcome={{ credit: "OTHER" } as any} />);
render(
<WaveOutcome
waveId="wave-3"
outcome={{ credit: "OTHER", index: 2 } as any}
/>
);
expect(screen.getByTestId("manual")).toBeInTheDocument();
});
});
78 changes: 63 additions & 15 deletions components/brain/my-stream/MyStreamWaveOutcome.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,81 @@
"use client"
"use client";

import React, { useMemo } from "react";
import React, { FC, useMemo, useRef } from "react";
import { ApiWave } from "@/generated/models/ApiWave";
import { WaveOutcome } from "@/components/waves/outcome/WaveOutcome";
import { useLayout } from "./layout/LayoutContext";
import SpinnerLoader from "@/components/common/SpinnerLoader";
import { useInfiniteScroll } from "@/hooks/useInfiniteScroll";
import { useWaveOutcomesQuery } from "@/hooks/waves/useWaveOutcomesQuery";

interface MyStreamWaveOutcomeProps {
readonly wave: ApiWave;
}

// Removed in favor of useWaveViewHeight hook

const MyStreamWaveOutcome: React.FC<MyStreamWaveOutcomeProps> = ({ wave }) => {
const MyStreamWaveOutcome: FC<MyStreamWaveOutcomeProps> = ({ wave }) => {
// Get the pre-calculated style from LayoutContext
const { outcomeViewStyle } = useLayout();

const scrollContainerRef = useRef<HTMLDivElement>(null);
const sentinelRef = useRef<HTMLDivElement>(null);

const {
outcomes,
fetchNextPage,
hasNextPage,
isFetching,
isFetchingNextPage,
isLoading,
errorMessage,
} = useWaveOutcomesQuery({ waveId: wave.id });


const containerClassName = useMemo(() => {
return `tw-pt-4 tw-pb-4 tw-w-full tw-flex tw-flex-col tw-overflow-y-auto no-scrollbar lg:tw-scrollbar-thin tw-scrollbar-thumb-iron-500 tw-scrollbar-track-iron-800 desktop-hover:hover:tw-scrollbar-thumb-iron-300 tw-flex-grow lg:tw-pr-2`;
}, []);

useInfiniteScroll(
hasNextPage,
isFetchingNextPage,
fetchNextPage,
scrollContainerRef,
sentinelRef,
"200px"
);

const hasOutcomes = outcomes.length > 0;
const isInitialLoading = isLoading && !hasOutcomes;
return (
<div className={containerClassName} style={outcomeViewStyle}>
<div className="tw-px-2 sm:tw-px-4 tw-space-y-4">
{wave.outcomes.map((outcome, index) => (
<WaveOutcome
key={`${outcome.credit}-${outcome.type}-${index}`}
outcome={outcome}
/>
))}
</div>
<div
className={containerClassName}
style={outcomeViewStyle}
ref={scrollContainerRef}
>
{isInitialLoading && <SpinnerLoader text="Loading outcomes..." />}
{!isInitialLoading && errorMessage && (
<div className="tw-px-4 tw-text-sm tw-text-red-400">
{errorMessage}
</div>
)}
{!isInitialLoading && !errorMessage && !hasOutcomes && !isFetching && (
<div className="tw-px-4 tw-text-sm tw-text-iron-500">
No outcomes to show.
</div>
)}
{hasOutcomes && (
<div className="tw-px-2 sm:tw-px-4 tw-space-y-4">
{outcomes.map((outcome, index) => (
<WaveOutcome
waveId={wave.id}
key={`${outcome.index ?? index}-${outcome.type}`}
outcome={outcome}
/>
))}
<div ref={sentinelRef} style={{ height: "1px" }} />
{isFetchingNextPage && (
<SpinnerLoader text="Loading more outcomes..." />
)}
</div>
)}
</div>
);
};
Expand Down
8 changes: 8 additions & 0 deletions components/react-query-wrapper/ReactQueryWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ export enum QueryKey {
WAVE_FOLLOWERS = "WAVE_FOLLOWERS",
FEED_ITEMS = "FEED_ITEMS",
WAVE_DECISIONS = "WAVE_DECISIONS",
WAVE_OUTCOMES = "WAVE_OUTCOMES",
WAVE_OUTCOME_DISTRIBUTION = "WAVE_OUTCOME_DISTRIBUTION",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

interface InitProfileRatersParamsAndData {
Expand Down Expand Up @@ -1139,6 +1141,12 @@ export default function ReactQueryWrapper({
queryClient.invalidateQueries({
queryKey: [QueryKey.WAVE],
});
queryClient.invalidateQueries({
queryKey: [QueryKey.WAVE_OUTCOMES],
});
queryClient.invalidateQueries({
queryKey: [QueryKey.WAVE_OUTCOME_DISTRIBUTION],
});
};

const invalidateDrops = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { ExtendedDrop } from "@/helpers/waves/drop.helpers";
import { cicToType } from "@/helpers/Helpers";

import Link from "next/link";
import Image from "next/image";
import UserCICAndLevel, {
UserCICAndLevelSize,
} from "@/components/user/utils/UserCICAndLevel";
import WinnerDropBadge from "@/components/waves/drops/winner/WinnerDropBadge";
import WaveDropTime from "@/components/waves/drops/time/WaveDropTime";
import UserProfileTooltipWrapper from "@/components/utils/tooltip/UserProfileTooltipWrapper";
import { resolveIpfsUrlSync } from "@/components/ipfs/IPFSContext";

interface WaveLeaderboardDropAuthorProps {
readonly drop: ExtendedDrop;
Expand All @@ -28,11 +30,13 @@ export const WaveLeaderboardDropAuthor: React.FC<
{drop.author.pfp ? (
<div className="tw-rounded-lg tw-h-full tw-w-full">
<div className="tw-h-full tw-w-full tw-max-w-full tw-rounded-lg tw-overflow-hidden tw-bg-iron-900 tw-ring-1 tw-ring-white/10">
<div className="tw-h-full tw-text-center tw-flex tw-items-center tw-justify-center tw-rounded-lg tw-overflow-hidden">
<img
src={drop.author.pfp}
<div className="tw-h-full tw-text-center tw-flex tw-items-center tw-justify-center tw-rounded-lg tw-overflow-hidden tw-relative">
<Image
src={resolveIpfsUrlSync(drop.author.pfp)}
alt="Profile picture"
className="tw-bg-transparent tw-max-w-full tw-max-h-full tw-h-auto tw-w-auto tw-mx-auto tw-object-contain"
fill
sizes="44px"
className="tw-bg-transparent tw-object-contain"
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import DropVoteProgressing from "@/components/drops/view/utils/DropVoteProgressing";
import { WAVE_VOTING_LABELS, WAVE_VOTE_STATS_LABELS } from "@/helpers/waves/waves.constants";
import { ApiWaveCreditType } from "@/generated/models/ApiWaveCreditType";
import { resolveIpfsUrlSync } from "@/components/ipfs/IPFSContext";

interface WaveLeaderboardDropRatersProps {
readonly drop: ExtendedDrop;
Expand Down Expand Up @@ -76,10 +77,10 @@ export const WaveLeaderboardDropRaters: React.FC<
<Link href={`/${voter.profile.handle}`}>
{voter.profile.pfp ? (
<Image
src={getScaledImageUri(
src={resolveIpfsUrlSync(getScaledImageUri(
voter.profile.pfp,
ImageScale.W_AUTO_H_50
)}
))}
alt={`${voter.profile.handle}'s Profile`}
width={20}
height={20}
Expand Down
59 changes: 31 additions & 28 deletions components/waves/outcome/WaveManualOutcome.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,49 @@
"use client";

import { FC, useState, useEffect } from "react";
import { FC, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { formatNumberWithCommas } from "@/helpers/Helpers";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAward } from "@fortawesome/free-solid-svg-icons";
import { ApiWaveOutcomeOld } from "@/generated/models/ApiWaveOutcomeOld";
import type { ApiWaveOutcome } from "@/generated/models/ApiWaveOutcome";
import type { WaveOutcomeDistributionState } from "@/types/waves.types";

interface WaveManualOutcomeProps {
readonly outcome: ApiWaveOutcomeOld;
readonly outcome: ApiWaveOutcome;
readonly distribution: WaveOutcomeDistributionState;
}

const DEFAULT_AMOUNTS_TO_SHOW = 3;

export const WaveManualOutcome: FC<WaveManualOutcomeProps> = ({ outcome }) => {
export const WaveManualOutcome: FC<WaveManualOutcomeProps> = ({
outcome,
distribution,
}) => {
const [isOpen, setIsOpen] = useState(false);
const winnersCount = outcome.distribution?.filter((d) => !!d).length ?? 0;
const totalCount = outcome.distribution?.length ?? 0;
const [showAll, setShowAll] = useState(false);
const { items, totalCount, hasNextPage, isFetchingNextPage, fetchNextPage } =
distribution;
const winnersCount = totalCount;
const visibleItems = showAll
? items
: items.slice(0, DEFAULT_AMOUNTS_TO_SHOW);
const remainingCount = Math.max(totalCount - visibleItems.length, 0);
const shouldShowMore =
hasNextPage || (!showAll && items.length > DEFAULT_AMOUNTS_TO_SHOW);

const getAmounts = (): number[] => {
if (showAll) {
return outcome.distribution?.map((d) => d.amount ?? 0) ?? [];
const onViewMore = () => {
if (!showAll) {
setShowAll(true);
}
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
return (
outcome.distribution
?.slice(0, DEFAULT_AMOUNTS_TO_SHOW)
.map((d) => d.amount ?? 0) ?? []
);
};

const [amounts, setAmounts] = useState<number[]>(getAmounts());

useEffect(() => {
setAmounts(getAmounts());
}, [showAll]);

return (
<div className="tw-overflow-hidden tw-rounded-lg tw-border tw-border-solid tw-border-iron-800 tw-transition-all tw-duration-300 desktop-hover:hover:tw-border-iron-700">
<button
type="button"
onClick={() => setIsOpen(!isOpen)}
className="tw-w-full tw-border-0 tw-px-4 tw-py-3 tw-bg-iron-950">
<div className="tw-flex tw-items-center tw-justify-between">
Expand Down Expand Up @@ -97,31 +102,29 @@ export const WaveManualOutcome: FC<WaveManualOutcomeProps> = ({ outcome }) => {
transition={{ duration: 0.2 }}
className="tw-overflow-hidden tw-bg-gradient-to-b tw-from-iron-900/50 tw-to-iron-950/50">
<div className="tw-divide-y tw-divide-iron-800/30 tw-divide-solid tw-divide-x-0">
{amounts.map((_, i) => (
{visibleItems.map((item, i) => (
<div
key={`wave-manual-outcome-${outcome.distribution?.[i].amount}-${outcome.distribution?.[i].description}`}
key={`wave-manual-outcome-${item.amount ?? "na"}-${item.description ?? "na"}-${item.index}`}
className="tw-px-4 tw-py-3 tw-bg-gradient-to-r hover:tw-from-amber-500/5 hover:tw-to-transparent tw-transition-colors tw-duration-300">
<div className="tw-flex tw-items-center tw-gap-4">
<span className="tw-flex tw-items-center tw-justify-center tw-size-8 tw-rounded-lg tw-bg-gradient-to-br tw-from-amber-400/10 tw-to-amber-600/5 tw-text-amber-200 tw-text-sm tw-font-semibold">
{i + 1}
</span>
<span className="tw-whitespace-nowrap tw-text-amber-100 tw-text-base tw-font-medium">
{outcome.distribution?.[i].amount
? outcome.distribution?.[i].description
: ""}
{item.description ?? ""}
</span>
</div>
</div>
))}

{totalCount > DEFAULT_AMOUNTS_TO_SHOW && !showAll && (
{shouldShowMore && (
<button
className="tw-border-0 tw-w-full tw-px-4 tw-py-3 tw-text-left tw-bg-iron-900 tw-text-amber-300/80 tw-text-sm hover:tw-text-amber-300 tw-transition-all tw-duration-300"
onClick={() => setShowAll(true)}>
onClick={onViewMore}>
<span>View more</span>
<span className="tw-ml-1 tw-text-iron-400">•</span>
<span className="tw-ml-1 tw-text-iron-400">
{totalCount - DEFAULT_AMOUNTS_TO_SHOW} more
{remainingCount} more
</span>
</button>
)}
Expand Down
Loading