Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -1,29 +1,53 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { WaveSmallLeaderboardItemOutcomes } from '@/components/waves/small-leaderboard/WaveSmallLeaderboardItemOutcomes';
import { ApiWaveOutcomeCredit } from '@/generated/models/ApiWaveOutcomeCredit';
import { ApiWaveOutcomeType } from '@/generated/models/ApiWaveOutcomeType';

const mockUseWaveRankReward = jest.fn();

jest.mock('@/hooks/waves/useWaveRankReward', () => ({
useWaveRankReward: (args: any) => mockUseWaveRankReward(args),
}));

describe('WaveSmallLeaderboardItemOutcomes', () => {
const drop: any = { rank: 1 };
const wave: any = {
outcomes: [
{ credit: ApiWaveOutcomeCredit.Cic, distribution: [{ amount: 1 }] },
{ credit: ApiWaveOutcomeCredit.Rep, distribution: [{ amount: 2 }] },
{ type: ApiWaveOutcomeType.Manual, distribution: [{ amount: 1, description: 'Award' }] },
],
};
const drop: any = { rank: 1, wave: { id: 'w1' } };

beforeEach(() => {
mockUseWaveRankReward.mockReset();
});

it('renders button when outcomes exist', () => {
render(<WaveSmallLeaderboardItemOutcomes drop={drop} wave={wave} />);
mockUseWaveRankReward.mockReturnValue({
nicTotal: 10,
repTotal: 20,
manualOutcomes: ['Award'],
isLoading: false
});

render(<WaveSmallLeaderboardItemOutcomes drop={drop} />);
expect(screen.getByRole('button')).toBeInTheDocument();
expect(screen.getByText('Outcome:')).toBeInTheDocument();
});

it('hides when no outcomes', () => {
const waveEmpty = { outcomes: [] } as any;
const { container } = render(<WaveSmallLeaderboardItemOutcomes drop={drop} wave={waveEmpty} />);
it('hides when no outcomes and not loading', () => {
mockUseWaveRankReward.mockReturnValue({
nicTotal: 0,
repTotal: 0,
manualOutcomes: [],
isLoading: false
});

const { container } = render(<WaveSmallLeaderboardItemOutcomes drop={drop} />);
expect(container.firstChild).toBeNull();
});

it('shows loading state', () => {
mockUseWaveRankReward.mockReturnValue({
nicTotal: 0,
repTotal: 0,
manualOutcomes: [],
isLoading: true
});
const { container } = render(<WaveSmallLeaderboardItemOutcomes drop={drop} />);
expect(container.querySelector('.tw-animate-pulse')).toBeInTheDocument();
});
});
1 change: 1 addition & 0 deletions components/react-query-wrapper/ReactQueryWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export enum QueryKey {
WAVE_DECISIONS = "WAVE_DECISIONS",
WAVE_OUTCOMES = "WAVE_OUTCOMES",
WAVE_OUTCOME_DISTRIBUTION = "WAVE_OUTCOME_DISTRIBUTION",
WAVE_OUTCOME_DISTRIBUTION_PAGE = "WAVE_OUTCOME_DISTRIBUTION_PAGE",
}

interface InitProfileRatersParamsAndData {
Expand Down
2 changes: 1 addition & 1 deletion components/waves/drop/SingleWaveDropInfoAuthorSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const SingleWaveDropInfoAuthorSection: React.FC<
</div>
</div>
{wave && drop && (
<WaveSmallLeaderboardItemOutcomes drop={drop} wave={wave} />
<WaveSmallLeaderboardItemOutcomes drop={drop} />
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import WaveDropActionsOpen from "@/components/waves/drops/WaveDropActionsOpen";
import WaveDropActionsOptions from "@/components/waves/drops/WaveDropActionsOptions";
import WaveDropMobileMenuDelete from "@/components/waves/drops/WaveDropMobileMenuDelete";
import WaveDropMobileMenuOpen from "@/components/waves/drops/WaveDropMobileMenuOpen";
import { ApiWave } from "@/generated/models/ObjectSerializer";
import { ExtendedDrop } from "@/helpers/waves/drop.helpers";
import { useDropInteractionRules } from "@/hooks/drops/useDropInteractionRules";
import useIsMobileScreen from "@/hooks/isMobileScreen";
Expand All @@ -22,13 +21,12 @@ import { WaveLeaderboardDropRaters } from "./header/WaveleaderboardDropRaters";

interface DefaultWaveLeaderboardDropProps {
readonly drop: ExtendedDrop;
readonly wave: ApiWave;
readonly onDropClick: (drop: ExtendedDrop) => void;
}

export const DefaultWaveLeaderboardDrop: React.FC<
DefaultWaveLeaderboardDropProps
> = ({ drop, wave, onDropClick }) => {
> = ({ drop, onDropClick }) => {
const { canShowVote, canDelete } = useDropInteractionRules(drop);
const [isVotingModalOpen, setIsVotingModalOpen] = useState(false);
const { hasTouchScreen } = useDeviceInfo();
Expand Down Expand Up @@ -87,7 +85,7 @@ export const DefaultWaveLeaderboardDrop: React.FC<
<div className="tw-mt-3 tw-inline-flex tw-flex-col @[700px]:tw-flex-row tw-justify-between @[700px]:tw-items-center sm:tw-ml-[3.5rem] tw-space-y-3 @[700px]:tw-space-y-0 tw-gap-x-2">
<div className="tw-flex tw-flex-wrap tw-items-center tw-gap-y-2 tw-gap-x-4">
<WaveLeaderboardDropRaters drop={drop} />
<WaveLeaderboardDropFooter drop={drop} wave={wave} />
<WaveLeaderboardDropFooter drop={drop} />
</div>
{canShowVote && (
<div
Expand Down
1 change: 0 additions & 1 deletion components/waves/leaderboard/drops/WaveLeaderboardDrop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export const WaveLeaderboardDrop: React.FC<WaveLeaderboardDropProps> = ({
return (
<DefaultWaveLeaderboardDrop
drop={drop}
wave={wave}
onDropClick={onDropClick}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import React from "react";
import { ExtendedDrop } from "@/helpers/waves/drop.helpers";
import { ApiWave } from "@/generated/models/ApiWave";
import { WaveSmallLeaderboardItemOutcomes } from "@/components/waves/small-leaderboard/WaveSmallLeaderboardItemOutcomes";

interface WaveLeaderboardDropFooterProps {
readonly drop: ExtendedDrop;
readonly wave: ApiWave;
}

export const WaveLeaderboardDropFooter: React.FC<
WaveLeaderboardDropFooterProps
> = ({ drop, wave }) => {
return <WaveSmallLeaderboardItemOutcomes drop={drop} wave={wave} />;
> = ({ drop }) => {
return <WaveSmallLeaderboardItemOutcomes drop={drop} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,25 @@ import React from "react";
import { ExtendedDrop } from "@/helpers/waves/drop.helpers";
import { WaveSmallLeaderboardTopThreeDrop } from "./WaveSmallLeaderboardTopThreeDrop";
import { WaveSmallLeaderboardDefaultDrop } from "./WaveSmallLeaderboardDefaultDrop";
import { ApiWave } from "@/generated/models/ApiWave";

interface DefaultWaveSmallLeaderboardDropProps {
readonly drop: ExtendedDrop;
readonly wave: ApiWave;
readonly onDropClick: (drop: ExtendedDrop) => void;
}

export const DefaultWaveSmallLeaderboardDrop: React.FC<
DefaultWaveSmallLeaderboardDropProps
> = ({ drop, wave, onDropClick }) => {
> = ({ drop, onDropClick }) => {
return (
<div className="tw-cursor-pointer" onClick={() => onDropClick(drop)}>
{drop.rank && drop.rank <= 3 ? (
<WaveSmallLeaderboardTopThreeDrop
drop={drop}
wave={wave}
onDropClick={onDropClick}
/>
) : (
<WaveSmallLeaderboardDefaultDrop
drop={drop}
wave={wave}
onDropClick={onDropClick}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,23 @@ import React from "react";
import { ExtendedDrop } from "@/helpers/waves/drop.helpers";
import { WaveSmallLeaderboardTopThreeDrop } from "./WaveSmallLeaderboardTopThreeDrop";
import { WaveSmallLeaderboardDefaultDrop } from "./WaveSmallLeaderboardDefaultDrop";
import { ApiWave } from "@/generated/models/ApiWave";

interface MemesWaveSmallLeaderboardDropProps {
readonly drop: ExtendedDrop;
readonly wave: ApiWave;
readonly onDropClick: (drop: ExtendedDrop) => void;
}

export const MemesWaveSmallLeaderboardDrop: React.FC<MemesWaveSmallLeaderboardDropProps> = ({ drop, wave, onDropClick }) => {
export const MemesWaveSmallLeaderboardDrop: React.FC<MemesWaveSmallLeaderboardDropProps> = ({ drop, onDropClick }) => {
return (
<div className="tw-cursor-pointer" onClick={() => onDropClick(drop)}>
{drop.rank && drop.rank <= 3 ? (
<WaveSmallLeaderboardTopThreeDrop
drop={drop}
wave={wave}
onDropClick={onDropClick}
/>
) : (
<WaveSmallLeaderboardDefaultDrop
drop={drop}
wave={wave}
onDropClick={onDropClick}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from "react";
import { ExtendedDrop } from "@/helpers/waves/drop.helpers";
import { ApiWave } from "@/generated/models/ApiWave";
import Link from "next/link";
import { CICType } from "@/entities/IProfile";
import { cicToType, formatNumberWithCommas } from "@/helpers/Helpers";
Expand All @@ -14,13 +13,12 @@ import UserProfileTooltipWrapper from "@/components/utils/tooltip/UserProfileToo

interface WaveSmallLeaderboardDefaultDropProps {
readonly drop: ExtendedDrop;
readonly wave: ApiWave;
readonly onDropClick: (drop: ExtendedDrop) => void;
}

export const WaveSmallLeaderboardDefaultDrop: React.FC<
WaveSmallLeaderboardDefaultDropProps
> = ({ drop, wave, onDropClick }) => {
> = ({ drop, onDropClick }) => {
const getCICColor = (cic: number): string => {
const cicType = cicToType(cic);
switch (cicType) {
Expand Down Expand Up @@ -129,7 +127,7 @@ export const WaveSmallLeaderboardDefaultDrop: React.FC<
/>
</div>
<div className="tw-mt-3">
<WaveSmallLeaderboardItemOutcomes drop={drop} wave={wave} />
<WaveSmallLeaderboardItemOutcomes drop={drop} />
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,13 @@ export const WaveSmallLeaderboardDrop: React.FC<
return (
<MemesWaveSmallLeaderboardDrop
drop={drop}
wave={wave}
onDropClick={onDropClick}
/>
);
} else {
return (
<DefaultWaveSmallLeaderboardDrop
drop={drop}
wave={wave}
onDropClick={onDropClick}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,97 +5,17 @@ import { Tooltip } from "react-tooltip";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAddressCard, faStar } from "@fortawesome/free-regular-svg-icons";
import { faAward } from "@fortawesome/free-solid-svg-icons";

import { ApiWave } from "@/generated/models/ApiWave";
import { ApiWaveOutcomeCredit } from "@/generated/models/ApiWaveOutcomeCredit";
import { ApiWaveOutcomeType } from "@/generated/models/ApiWaveOutcomeType";
import { ApiDrop } from "@/generated/models/ApiDrop";
import { useWaveRankReward } from "@/hooks/waves/useWaveRankReward";

interface WaveSmallLeaderboardItemOutcomesProps {
readonly drop: ApiDrop;
readonly wave: ApiWave;
readonly isMobile?: boolean;
}

interface OutcomeSummary {
nicTotal: number;
repTotal: number;
manualOutcomes: string[];
}

const calculateNIC = ({
drop,
wave,
}: {
drop: ApiDrop;
wave: ApiWave;
}): number => {
const rank = drop.rank;
if (!rank) return 0;
const outcomes = wave.outcomes;
const nicOutcomes = outcomes.filter(
(outcome) => outcome.credit === ApiWaveOutcomeCredit.Cic
);
const nic = nicOutcomes.reduce((acc, outcome) => {
return acc + (outcome.distribution?.[rank - 1]?.amount ?? 0);
}, 0);
return nic;
};

const calculateRep = ({
drop,
wave,
}: {
drop: ApiDrop;
wave: ApiWave;
}): number => {
const rank = drop.rank;
if (!rank) return 0;
const outcomes = wave.outcomes;
const repOutcomes = outcomes.filter(
(outcome) => outcome.credit === ApiWaveOutcomeCredit.Rep
);
const rep = repOutcomes.reduce((acc, outcome) => {
return acc + (outcome.distribution?.[rank - 1]?.amount ?? 0);
}, 0);
return rep;
};

const calculateManualOutcomes = ({
drop,
wave,
}: {
drop: ApiDrop;
wave: ApiWave;
}): string[] => {
const rank = drop.rank;
if (!rank) return [];
const outcomes = wave.outcomes;
const manualOutcomes = outcomes.filter(
(outcome) => outcome.type === ApiWaveOutcomeType.Manual
);
return manualOutcomes
.filter((outcome) => !!outcome.distribution?.[rank - 1]?.amount)
.map((outcome) => outcome.distribution?.[rank - 1]?.description ?? "");
};

const calculateOutcomeSummary = ({
drop,
wave,
}: {
drop: ApiDrop;
wave: ApiWave;
}): OutcomeSummary => {
return {
nicTotal: calculateNIC({ drop, wave }),
repTotal: calculateRep({ drop, wave }),
manualOutcomes: calculateManualOutcomes({ drop, wave }),
};
};

export const WaveSmallLeaderboardItemOutcomes: React.FC<
WaveSmallLeaderboardItemOutcomesProps
> = ({ drop, wave, isMobile = false }) => {
> = ({ drop, isMobile = false }) => {
const [isTouch, setIsTouch] = useState(false);
const [isOpen, setIsOpen] = useState(false);

Expand All @@ -110,17 +30,24 @@ export const WaveSmallLeaderboardItemOutcomes: React.FC<
}
};

const { nicTotal, repTotal, manualOutcomes } = calculateOutcomeSummary({
drop,
wave,
const { nicTotal, repTotal, manualOutcomes, isLoading } = useWaveRankReward({
waveId: drop.wave.id,
rank: drop.rank,
});
Comment thread
simo6529 marked this conversation as resolved.

const totalOutcomes =
(nicTotal ? 1 : 0) + (repTotal ? 1 : 0) + manualOutcomes.length;

if (totalOutcomes === 0) {
if (totalOutcomes === 0 && !isLoading) {
return null;
}

if (isLoading) {
return (
<div className={`tw-animate-pulse tw-h-6 tw-w-16 tw-bg-iron-800 tw-rounded-lg`} />
)
}

const tooltipContent = (
<div className="tw-p-3 tw-space-y-3 tw-min-w-[200px]">
<div className="tw-space-y-2">
Expand Down Expand Up @@ -184,11 +111,9 @@ export const WaveSmallLeaderboardItemOutcomes: React.FC<
<>
<button
onClick={handleClick}
className={`tw-border-0 tw-rounded-lg tw-flex tw-items-center ${
isMobile ? "tw-gap-4" : "tw-gap-2"
} tw-min-w-6 tw-py-1.5 tw-px-2 tw-bg-iron-800 tw-ring-1 tw-ring-iron-700 ${
isTouch ? "tw-cursor-pointer" : ""
}`}
className={`tw-border-0 tw-rounded-lg tw-flex tw-items-center ${isMobile ? "tw-gap-4" : "tw-gap-2"
} tw-min-w-6 tw-py-1.5 tw-px-2 tw-bg-iron-800 tw-ring-1 tw-ring-iron-700 ${isTouch ? "tw-cursor-pointer" : ""
}`}
data-tooltip-id={`wave-outcomes-${drop.id}`}>
<span className="tw-text-xs tw-font-medium tw-text-iron-200">
Outcome:
Expand Down
Loading