Skip to content

Commit

Permalink
feat: edit earnings (#2101)
Browse files Browse the repository at this point in the history
* feat: add edit earnings option and track rewards for submit and vote

* feat: add small changes for rewards address

* feat: add mobile optimizations

* feat: add correct handleError call from hook

* feat: prevent edit of the earnings if contest is completed

* feat: add canceled state for edit earnings
  • Loading branch information
nakedfool authored Aug 15, 2024
1 parent 5bfd5b9 commit e600689
Show file tree
Hide file tree
Showing 18 changed files with 679 additions and 212 deletions.
32 changes: 32 additions & 0 deletions packages/react-app-revamp/components/UI/DialogModalV4/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Dialog, DialogBackdrop, DialogPanel } from "@headlessui/react";
import { FC } from "react";

interface DialogModalV4Props {
isOpen: boolean;
children: React.ReactNode;
onClose: (value: boolean) => void;
}

const DialogModalV4: FC<DialogModalV4Props> = ({ isOpen, onClose, children }) => {
return (
<Dialog open={isOpen} onClose={onClose} className="relative z-10">
<DialogBackdrop
transition
className="fixed inset-0 bg-neutral-8 bg-opacity-40 transition-opacity data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in"
/>

<div className="fixed inset-0 z-10 w-screen overflow-y-auto">
<div className="flex min-h-full items-end justify-center text-center lg:items-center p-0">
<DialogPanel
transition
className="relative w-full transform overflow-hidden rounded-t-[40px] border-t border-neutral-9 bg-true-black text-left shadow-xl transition-all data-[closed]:translate-y-4 data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in lg:my-8 lg:w-full lg:max-w-lg lg:rounded-[10px] lg:border-none data-[closed]:lg:translate-y-0 data-[closed]:lg:scale-95"
>
<div className="pb-[64px] lg:pb-0 lg:p-6">{children}</div>
</DialogPanel>
</div>
</div>
</Dialog>
);
};

export default DialogModalV4;
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import ContestParamsSplitFeeDestination from "@components/_pages/Create/pages/ContestMonetization/components/Charge/components/SplitFeeDestination";
import DialogModalV4 from "@components/UI/DialogModalV4";
import { chains, config } from "@config/wagmi";
import { extractPathSegments } from "@helpers/extractPath";
import { addressRegex } from "@helpers/regex";
import { useContestStore } from "@hooks/useContest/store";
import { useCreatorSplitDestination } from "@hooks/useCreatorSplitDestination";
import { JK_LABS_SPLIT_DESTINATION_DEFAULT } from "@hooks/useDeployContest";
import { Charge, SplitFeeDestinationType } from "@hooks/useDeployContest/types";
import { switchChain } from "@wagmi/core";
import Image from "next/image";
import { usePathname } from "next/navigation";
import { FC, useState } from "react";
import { useAccount } from "wagmi";

interface ContestParamsEarningsModalProps {
charge: Charge;
isOpen: boolean;
onClose: (value: boolean) => void;
}

const ContestParamsEarningsModal: FC<ContestParamsEarningsModalProps> = ({ charge, isOpen, onClose }) => {
const pathname = usePathname();
const { chainName: contestChainName } = extractPathSegments(pathname);
const contestChainId = chains.find(chain => chain.name.toLowerCase() === contestChainName.toLowerCase())?.id;
const { address: userAddress, chainId } = useAccount();
const isUserOnCorrectChain = contestChainId === chainId;
const { rewardsModuleAddress } = useContestStore(state => state);
const [splitFeeDestinationError, setSplitFeeDestinationError] = useState("");
const { setCreatorSplitDestination, isLoading, isConfirmed } = useCreatorSplitDestination();
const [localCharge, setLocalCharge] = useState<Charge>(charge);

const handleSplitFeeDestinationTypeChange = (type: SplitFeeDestinationType) => {
let newAddress = charge.splitFeeDestination.address;

switch (type) {
case SplitFeeDestinationType.RewardsPool:
newAddress = rewardsModuleAddress;
break;
case SplitFeeDestinationType.AnotherWallet:
newAddress = "";
break;
case SplitFeeDestinationType.NoSplit:
newAddress = JK_LABS_SPLIT_DESTINATION_DEFAULT;
break;
case SplitFeeDestinationType.CreatorWallet:
newAddress = userAddress;
}

const newSplitFeeDestination = {
...charge.splitFeeDestination,
type,
address: newAddress,
};

const isValidAddress = addressRegex.test(newAddress ?? "");
const error = type === SplitFeeDestinationType.AnotherWallet && !isValidAddress;

setLocalCharge?.({
...charge,
splitFeeDestination: newSplitFeeDestination,
error,
});
};

const handleSplitFeeDestinationAddressChange = (address: string) => {
const isValidAddress = addressRegex.test(address);
const newSplitFeeDestination = {
...charge.splitFeeDestination,
type: SplitFeeDestinationType.AnotherWallet,
address,
};

setSplitFeeDestinationError(isValidAddress ? "" : "invalid address");

setLocalCharge?.({
...charge,
splitFeeDestination: newSplitFeeDestination,
error: !isValidAddress,
});
};

const onSaveHandler = async () => {
if (!contestChainId) return;

if (!isUserOnCorrectChain) {
await switchChain(config, { chainId: contestChainId });
}

setCreatorSplitDestination(localCharge.splitFeeDestination);
};

return (
<DialogModalV4 isOpen={isOpen} onClose={onClose}>
<div className="flex flex-col gap-8 md:gap-20 py-6 md:py-16 pl-8 md:pl-32 pr-4 md:pr-16">
<div className="flex justify-between items-center">
<p className="text-[24px] text-neutral-11 font-bold">edit earnings</p>
<Image
src="/modal/modal_close.svg"
width={39}
height={33}
alt="close"
className="hidden md:block cursor-pointer"
onClick={() => onClose(true)}
/>
</div>
<ContestParamsSplitFeeDestination
splitFeeDestination={localCharge.splitFeeDestination}
splitFeeDestinationError={splitFeeDestinationError}
includeRewardsPool={rewardsModuleAddress !== ""}
rewardsModuleAddress={rewardsModuleAddress}
onSplitFeeDestinationTypeChange={handleSplitFeeDestinationTypeChange}
onSplitFeeDestinationAddressChange={handleSplitFeeDestinationAddressChange}
/>
<button
disabled={(isLoading && !isConfirmed) || localCharge.error}
className="mt-4 bg-gradient-purple rounded-[40px] w-80 h-10 text-center text-true-black text-[16px] font-bold hover:opacity-80 transition-opacity duration-300 ease-in-out"
onClick={onSaveHandler}
>
save
</button>
</div>
</DialogModalV4>
);
};

export default ContestParamsEarningsModal;
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import shortenEthereumAddress from "@helpers/shortenEthereumAddress";
import { Charge } from "@hooks/useDeployContest/types";
import { FC } from "react";
import { PencilSquareIcon } from "@heroicons/react/24/outline";
import { ContestStateEnum, useContestStateStore } from "@hooks/useContestState/store";
import { Charge, SplitFeeDestinationType } from "@hooks/useDeployContest/types";
import { FC, useState } from "react";
import { useAccount } from "wagmi";
import ContestParamsEarningsModal from "./components/Modal";

interface ContestParametersEarningsProps {
charge: Charge;
Expand All @@ -9,11 +13,17 @@ interface ContestParametersEarningsProps {
}

const ContestParametersEarnings: FC<ContestParametersEarningsProps> = ({ charge, blockExplorerUrl, contestAuthor }) => {
const { address } = useAccount();
const isConnectedWalletAuthor = address === contestAuthor;
const isCreatorSplitEnabled = charge.percentageToCreator > 0;
const creatorSplitDestination = charge.splitFeeDestination.address
? charge.splitFeeDestination.address
: contestAuthor;
const blockExplorerAddressUrl = blockExplorerUrl ? `${blockExplorerUrl}/address/${creatorSplitDestination}` : "";
const [isEditEarningsModalOpen, setIsEditEarningsModalOpen] = useState(false);
const { contestState } = useContestStateStore(state => state);
const isContestFinishedOrCanceled =
contestState === ContestStateEnum.Completed || contestState === ContestStateEnum.Canceled;

const percentageToCreatorMessage = () => {
if (charge.percentageToCreator === 50) {
Expand All @@ -22,24 +32,45 @@ const ContestParametersEarnings: FC<ContestParametersEarningsProps> = ({ charge,
return `all earnings go to JokeRace`;
}
};

const creatorEarningsDestinationMessage = () => {
if (charge.splitFeeDestination.type === SplitFeeDestinationType.RewardsPool) {
return "creator earnings go to rewards pool";
} else {
return (
<>
creator earnings go to{" "}
<a
className="underline cursor-pointer hover:text-positive-11 transition-colors duration-300"
target="_blank"
href={blockExplorerAddressUrl}
>
{shortenEthereumAddress(creatorSplitDestination)}
</a>
</>
);
}
};

return (
<div className="flex flex-col gap-12">
<p className="text-[20px] font-bold text-neutral-11">earnings</p>
<div className="flex flex-col gap-8">
<div className="flex gap-4 items-center">
<p className="text-[20px] font-bold text-neutral-14">earnings</p>
{isConnectedWalletAuthor && !isContestFinishedOrCanceled && (
<button onClick={() => setIsEditEarningsModalOpen(true)}>
<PencilSquareIcon className="w-6 h-6 text-neutral-9 hover:text-neutral-11 transition-colors duration-300 ease-in-out" />
</button>
)}
</div>
<ul className="pl-4 text-[16px] font-bold">
<li className="list-disc">{percentageToCreatorMessage()}</li>
{isCreatorSplitEnabled ? (
<li className="list-disc">
creator earnings go to{" "}
<a
className="underline cursor-pointer hover:text-positive-11 transition-colors duration-300"
target="_blank"
href={blockExplorerAddressUrl}
>
{shortenEthereumAddress(creatorSplitDestination)}
</a>
</li>
) : null}
{isCreatorSplitEnabled && <li className="list-disc">{creatorEarningsDestinationMessage()}</li>}
</ul>
<ContestParamsEarningsModal
charge={charge}
isOpen={isEditEarningsModalOpen}
onClose={() => setIsEditEarningsModalOpen(false)}
/>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ const ContestParametersSubmissions: FC<ContestParametersSubmissionsProps> = ({
);

return (
<div className="flex flex-col gap-12">
<p className="text-[20px] font-bold text-neutral-11">submissions</p>
<div className="flex flex-col gap-8">
<p className="text-[20px] font-bold text-neutral-14">submissions</p>
<ul className="pl-4 text-[16px] font-bold">
<li className="list-disc">
qualified wallets can enter{" "}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ interface ContestParametersTimelineProps {

const ContestParametersTimeline: FC<ContestParametersTimelineProps> = ({ submissionsOpen, votesOpen, votesClose }) => {
return (
<div className="flex flex-col gap-12">
<p className="text-[20px] font-bold text-neutral-11">timeline</p>
<div className="flex flex-col gap-8">
<p className="text-[20px] font-bold text-neutral-14">timeline</p>
<ContestTimeline />
<div className="flex flex-col lg:hidden gap-4">
<div className="flex justify-between items-end text-[16px] font-bold border-b border-neutral-10 pb-3">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ const ContestParametersVoting: FC<ContestParametersVotingProps> = ({
);

return (
<div className="flex flex-col gap-12">
<p className="text-[20px] font-bold text-neutral-11">voting</p>
<div className="flex flex-col gap-8">
<p className="text-[20px] font-bold text-neutral-14">voting</p>
<ul className="pl-4 text-[16px] font-bold">
<li className="list-disc">{address ? qualifyToVoteMessage : walletNotConnected}</li>
{anyoneCanVote ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Dialog, DialogBackdrop, DialogPanel } from "@headlessui/react";
import DialogModalV4 from "@components/UI/DialogModalV4";
import Image from "next/image";
import { FC } from "react";

Expand All @@ -14,50 +14,36 @@ const CancelContestModal: FC<CancelContestModalProps> = ({
cancelContestHandler,
}) => {
return (
<Dialog open={isCloseContestModalOpen} onClose={setIsCloseContestModalOpen} className="relative z-10">
<DialogBackdrop
transition
className="fixed inset-0 bg-neutral-8 bg-opacity-40 transition-opacity data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in"
/>

<div className="fixed inset-0 z-10 w-screen overflow-y-auto">
<div className="flex min-h-full items-end justify-center text-center sm:items-center sm:p-0">
<DialogPanel
transition
className="relative mb-14 md:mb-0 transform overflow-hidden rounded-t-[40px] border-t border-neutral-9 md:border-none md:rounded-[10px] bg-true-black text-left shadow-xl transition-all data-[closed]:translate-y-4 data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in sm:my-8 sm:w-full sm:max-w-lg sm:p-6 data-[closed]:sm:translate-y-0 data-[closed]:sm:scale-95"
>
<div className="flex flex-col gap-8 py-6 md:py-16 pl-8 md:pl-32 pr-4 md:pr-16">
<div className="flex justify-between items-center">
<p className="text-[24px] text-neutral-11 font-bold">cancel contest?? 😬</p>
<Image
src="/modal/modal_close.svg"
width={39}
height={33}
alt="close"
className="hidden md:block cursor-pointer"
onClick={() => setIsCloseContestModalOpen(false)}
/>
</div>
<div className="flex flex-col gap-4 text-neutral-11 text-[16px]">
<p>if you proceed, you will cancel this contest.</p>
<p className="font-bold">🚨 players will not be able to keep playing.</p>
<p>
🚨 the contest and entries <i>will</i> remain visible on our site.
</p>
<p>🚨 you can withdraw any funds on the rewards page.</p>
<p>are you really, really, really sure you want to proceed?</p>
</div>
<button
className="mt-4 bg-negative-11 rounded-[40px] w-80 h-10 text-center text-true-black text-[16px] font-bold hover:opacity-80 transition-opacity duration-300 ease-in-out"
onClick={cancelContestHandler}
>
cancel contest 😈
</button>
</div>
</DialogPanel>
<DialogModalV4 isOpen={isCloseContestModalOpen} onClose={setIsCloseContestModalOpen}>
<div className="flex flex-col gap-8 py-6 md:py-16 pl-8 md:pl-32 pr-4 md:pr-16">
<div className="flex justify-between items-center">
<p className="text-[24px] text-neutral-11 font-bold">cancel contest?? 😬</p>
<Image
src="/modal/modal_close.svg"
width={39}
height={33}
alt="close"
className="hidden md:block cursor-pointer"
onClick={() => setIsCloseContestModalOpen(false)}
/>
</div>
<div className="flex flex-col gap-4 text-neutral-11 text-[16px]">
<p>if you proceed, you will cancel this contest.</p>
<p className="font-bold">🚨 players will not be able to keep playing.</p>
<p>
🚨 the contest and entries <i>will</i> remain visible on our site.
</p>
<p>🚨 you can withdraw any funds on the rewards page.</p>
<p>are you really, really, really sure you want to proceed?</p>
</div>
<button
className="mt-4 bg-negative-11 rounded-[40px] w-80 h-10 text-center text-true-black text-[16px] font-bold hover:opacity-80 transition-opacity duration-300 ease-in-out"
onClick={cancelContestHandler}
>
cancel contest 😈
</button>
</div>
</Dialog>
</DialogModalV4>
);
};

Expand Down
Loading

0 comments on commit e600689

Please sign in to comment.