From 413ca252100d6474a66b3c597d63299ef93e9965 Mon Sep 17 00:00:00 2001 From: Ali Maktabi <67747298+alimaktabi@users.noreply.github.com> Date: Tue, 23 Jan 2024 16:44:31 +0330 Subject: [PATCH] Merge dev into master (#48) * fixed bug * email required * set min date * fixed style * change modal style * change modal large size * fixed error displaying * end date condition * fixed bug * fixed reading winners from contract * fixed bug * edit export wallets * fixed variable names * fixed typo * use find instead filter * change variable name * change variable name * create export button * disable token tap & gas tap button * remove contribution hub main page layout * remove context from prizeTap content * new page and layout for create raffle * create show detail rout * create route for display rejection reason * change button name "Raffle is being processed" * fixed bug * fixed style * change discord validation * set export btn * display winner count * get user raffle server side * add new routes * remove signal from get user raffle api * fixed bug - get user token balance * fixed style * use create & detail & verification route * edit style * clean code * remove comment * fixed fadeInOut animation duration * token tap & refactor prizeTap components * refactor error handling * fixed display number of nfts bug * remove log * remove comments * implementing gas tap route * add new style * add new image and remove dead code * fixed bug * add gastap contribution * refactor requirement * search chain input * edit text * set check constraint params validation * check contract address function * Separate validation function * edit url address * fixed gastap components * added donation amount helper text * added 404 page * get user Distribution token tap * add distribution apis * add use token distribution type * add create distribution functions * create user distribution card * remove logs * create token tap timer component * create csvFileInput component * fixed twitter in send to api * token tap form new style * get token tap valid chain * token tap send transaction * added prevent navigation hook * fixed error * added edit button to user profile page * fixed wallet winners modal * send data in form data * fixed api calling * fixed reversed constraints api * remove dead code * add constraint file type to requirement prop * refactor context to use new requirement * using new requirement * refactor sending to api * refactor to use new requirement * duplicate * fixed bug * refactor using constraint details modal * change functionality * remove extra files * fixed bugs * fixed gastap login not fetching data, bright id connection not showing sometimes * fixed build error * fixed bright id meet verified modal in gastap * fixed gastap tooltip, fixed bright id connection status * moved prize-tap components into app dir * fixed winners modal functionality, added enrollments list * fixed namings * fixed build errors --------- Co-authored-by: abbasnosrati --- .vscode/settings.json | 2 +- app/contribution-hub/ChainList.tsx | 107 ++++++ .../ConstraintDetailsModal.tsx | 273 +++++++++++++++ app/contribution-hub/CsvFileInput.tsx | 99 ++++++ app/contribution-hub/SelectMethodInput.tsx | 131 +++++++ app/contribution-hub/content.module.scss | 2 +- .../gas-tap/components/Content.tsx | 158 +++++++++ .../components/ProvideGasFeeContent.tsx | 323 ++++++++++++++++++ .../gas-tap/create/layout.tsx | 43 +++ app/contribution-hub/gas-tap/create/page.tsx | 13 + app/contribution-hub/gas-tap/layout.tsx | 17 +- app/contribution-hub/gas-tap/page.tsx | 14 +- .../prize-tap/components/Content.tsx | 13 +- .../components/Modals/winnersModal.tsx | 2 +- .../OfferPrizeForm/ContactInformation.tsx | 26 +- .../components/DepositContent.tsx | 15 +- .../OfferPrizeForm/DepositPrize/index.tsx | 36 +- .../OfferPrizeForm/NewAddedConstraint.tsx | 23 +- .../PrizeInfo/components/SelectTokenOrNft.tsx | 14 +- .../OfferPrizeForm/PrizeInfo/index.tsx | 2 +- .../components/ConstraintListModal.tsx | 98 ++++++ .../components/ConstraintModal.tsx | 111 ------ .../components/RenderInitialBody.tsx | 32 -- .../components/RequirementModal.tsx | 53 --- .../OfferPrizeForm/Requirements/index.tsx | 11 +- .../components/OfferPrizeForm/index.tsx | 18 +- .../prize-tap/components/SearchInput.tsx | 2 +- .../prize-tap/create/layout.tsx | 15 +- .../prize-tap/verification/[id]/page.tsx | 2 +- app/contribution-hub/styles.scss | 16 +- .../components/CardTimerTokenTap.tsx | 114 +++++++ .../token-tap/components/Content.tsx | 261 ++++++-------- .../ShowPreviewModal/FormYouFilled.tsx | 171 +++------- .../OfferTokenForm/NewAddedConstraint.tsx | 23 +- .../components/ConstraintListModal.tsx | 98 ++++++ .../components/ConstraintModal.tsx | 111 ------ .../components/RenderInitialBody.tsx | 31 -- .../components/RequirementModal.tsx | 53 --- .../TokenTapRequirements/index.tsx | 27 +- .../token-tap/create/layout.tsx | 17 +- .../Modals/FundGasModal/content.tsx | 26 ++ .../Modals/FundTransactionModal/index.tsx | 46 +-- .../Modals/SelectChainModal/chainItem.tsx | 60 ++-- .../Modals/SelectChainModal/index.tsx | 2 +- app/gastap/components/searchInput.tsx | 2 +- app/not-found.tsx | 29 ++ .../Linea/LineaCheckWalletsModal.tsx | 0 .../components}/Linea/LineaWinnersModal.tsx | 91 +++-- .../components}/Linea/check-circle.svg | 0 .../prizetap/components}/Linea/index.tsx | 2 +- .../enroll-body/BrightNotConnectedBody.tsx | 0 .../Modals/enroll-body/InitialBody.tsx | 5 - .../Modals/enroll-body/SuccessBody.tsx | 0 .../Modals/enroll-body/WrongNetworkBody.tsx | 0 .../components}/Modals/enroll-modal.tsx | 5 + .../components}/Modals/wallet-address.tsx | 0 .../components}/Modals/winnersModal.tsx | 50 ++- .../prizetap/components}/RafflesList.tsx | 2 +- .../prizetap/components}/header.tsx | 0 .../prizetap/components}/permissions.tsx | 0 app/prizetap/page.tsx | 10 +- app/profile/edit/@socialAccounts/layout.tsx | 29 ++ app/profile/edit/@socialAccounts/page.tsx | 45 ++- app/profile/edit/page.tsx | 27 +- .../containers/landing/prizeTap/index.tsx | 106 +++--- .../containers/landing/tokenTap/index.tsx | 49 +-- .../modals/brightConnectionModal.tsx | 18 +- .../helpers/checkCollectionAddress.ts | 35 ++ .../helpers/createErc20Raffle.ts | 120 ++++--- .../helpers/createErc20TokenDistribution.ts | 147 ++++++++ .../helpers/createErc721Raffle.ts | 156 +++++---- .../helpers/getErc20TokenContract.ts | 7 +- .../helpers/isValidContractAddress.ts | 14 + .../hooks/useAddRequirement.ts | 41 ++- .../containers/provider-dashboard/layout.tsx | 10 +- .../token-tap/Modals/InitialBody.tsx | 2 +- .../containers/token-tap/searchInput.tsx | 2 +- context/gasTapProvider.tsx | 18 +- context/prizeTapProvider.tsx | 7 +- context/providerDashboardContext.tsx | 166 +++++---- context/providerDashboardTokenTapContext.tsx | 196 +++++------ context/socialAccountContext.tsx | 13 + package.json | 1 + public/assets/images/404.svg | 256 ++++++++++++++ .../images/provider-dashboard/deposit-nft.png | Bin 0 -> 77736 bytes .../provider-dashboard/deposit-token.png | Bin 0 -> 83653 bytes types/provider-dashboard.ts | 52 ++- utils/api/gastap.ts | 2 +- utils/api/provider-dashboard.ts | 34 +- utils/hooks/refresh.tsx | 39 +++ utils/routes.ts | 1 + utils/serverApis/contributionHub.ts | 60 +++- utils/wallet/index.ts | 2 +- yarn.lock | 5 + 94 files changed, 3237 insertions(+), 1330 deletions(-) create mode 100644 app/contribution-hub/ChainList.tsx create mode 100644 app/contribution-hub/ConstraintDetailsModal.tsx create mode 100644 app/contribution-hub/CsvFileInput.tsx create mode 100644 app/contribution-hub/SelectMethodInput.tsx create mode 100644 app/contribution-hub/gas-tap/components/Content.tsx create mode 100644 app/contribution-hub/gas-tap/components/ProvideGasFeeContent.tsx create mode 100644 app/contribution-hub/gas-tap/create/layout.tsx create mode 100644 app/contribution-hub/gas-tap/create/page.tsx create mode 100644 app/contribution-hub/prize-tap/components/OfferPrizeForm/Requirements/components/ConstraintListModal.tsx delete mode 100644 app/contribution-hub/prize-tap/components/OfferPrizeForm/Requirements/components/ConstraintModal.tsx delete mode 100644 app/contribution-hub/prize-tap/components/OfferPrizeForm/Requirements/components/RenderInitialBody.tsx delete mode 100644 app/contribution-hub/prize-tap/components/OfferPrizeForm/Requirements/components/RequirementModal.tsx create mode 100644 app/contribution-hub/token-tap/components/CardTimerTokenTap.tsx create mode 100644 app/contribution-hub/token-tap/components/OfferTokenForm/TokenTapRequirements/components/ConstraintListModal.tsx delete mode 100644 app/contribution-hub/token-tap/components/OfferTokenForm/TokenTapRequirements/components/ConstraintModal.tsx delete mode 100644 app/contribution-hub/token-tap/components/OfferTokenForm/TokenTapRequirements/components/RenderInitialBody.tsx delete mode 100644 app/contribution-hub/token-tap/components/OfferTokenForm/TokenTapRequirements/components/RequirementModal.tsx create mode 100644 app/not-found.tsx rename {components/containers/prize-tap => app/prizetap/components}/Linea/LineaCheckWalletsModal.tsx (100%) rename {components/containers/prize-tap => app/prizetap/components}/Linea/LineaWinnersModal.tsx (70%) rename {components/containers/prize-tap => app/prizetap/components}/Linea/check-circle.svg (100%) rename {components/containers/prize-tap => app/prizetap/components}/Linea/index.tsx (99%) rename {components/containers/prize-tap => app/prizetap/components}/Modals/enroll-body/BrightNotConnectedBody.tsx (100%) rename {components/containers/prize-tap => app/prizetap/components}/Modals/enroll-body/InitialBody.tsx (98%) rename {components/containers/prize-tap => app/prizetap/components}/Modals/enroll-body/SuccessBody.tsx (100%) rename {components/containers/prize-tap => app/prizetap/components}/Modals/enroll-body/WrongNetworkBody.tsx (100%) rename {components/containers/prize-tap => app/prizetap/components}/Modals/enroll-modal.tsx (95%) rename {components/containers/prize-tap => app/prizetap/components}/Modals/wallet-address.tsx (100%) rename {components/containers/prize-tap => app/prizetap/components}/Modals/winnersModal.tsx (71%) rename {components/containers/prize-tap => app/prizetap/components}/RafflesList.tsx (99%) rename {components/containers/prize-tap => app/prizetap/components}/header.tsx (100%) rename {components/containers/prize-tap => app/prizetap/components}/permissions.tsx (100%) create mode 100644 app/profile/edit/@socialAccounts/layout.tsx create mode 100644 components/containers/provider-dashboard/helpers/checkCollectionAddress.ts create mode 100644 components/containers/provider-dashboard/helpers/createErc20TokenDistribution.ts create mode 100644 components/containers/provider-dashboard/helpers/isValidContractAddress.ts create mode 100644 context/socialAccountContext.tsx create mode 100644 public/assets/images/404.svg create mode 100644 public/assets/images/provider-dashboard/deposit-nft.png create mode 100644 public/assets/images/provider-dashboard/deposit-token.png diff --git a/.vscode/settings.json b/.vscode/settings.json index 741a0fbc..5269042c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "cSpell.words": ["Solana", "unitap"], + "cSpell.words": ["Solana", "unitap", "prizetap", "gastap", "tokentap"], "tailwindCSS.includeLanguages": { "typescript": "javascript", "typescriptreact": "javascript" diff --git a/app/contribution-hub/ChainList.tsx b/app/contribution-hub/ChainList.tsx new file mode 100644 index 00000000..fffe26c5 --- /dev/null +++ b/app/contribution-hub/ChainList.tsx @@ -0,0 +1,107 @@ +import Icon from "@/components/ui/Icon"; +import { Chain } from "@/types/gastap"; +import { useOutsideClick } from "@/utils/hooks/dom"; +import React, { useEffect, useRef, useState } from "react"; + +interface Props { + setRequirementParamsList: any; + requirementParamsList: any; + allChainList: Chain[] | undefined; +} + +const ChainList = ({ + setRequirementParamsList, + requirementParamsList, + allChainList, +}: Props) => { + const [selectedChain, setSelectedChain] = useState(); + + const handleSelectChain = (chian: Chain) => { + setSelectedChain(chian); + setRequirementParamsList({ + ...requirementParamsList, + ["CHAIN"]: chian.pk, + }); + setShowItems(false); + setChainName(chian.chainName); + }; + + const [chainName, setChainName] = useState(); + + const [filterChainName, setFilterChainName] = useState( + allChainList + ); + + const [showItems, setShowItems] = useState(false); + + const ref = useRef(null); + + useEffect(() => { + if (!requirementParamsList || !allChainList) return; + if (!requirementParamsList.CHAIN) return; + const chain = allChainList!.find( + (item) => item.pk === requirementParamsList.CHAIN + ); + setSelectedChain(chain); + }, [requirementParamsList]); + + useOutsideClick(ref, () => { + if (showItems) setShowItems(false); + }); + + const handleSearch = (e: string) => { + setSelectedChain(undefined); + setChainName(e); + setShowItems(true); + setFilterChainName( + allChainList?.filter( + (chain) => + chain.chainName.toLocaleLowerCase().includes(e) || + chain.chainId.includes(e) + ) + ); + }; + + return ( +
+
setShowItems(!showItems)} + className="flex items-center gap-2 justify-between cursor-pointer h-[43px] bg-gray40 border-gray50 rounded-xl px-3" + > + {selectedChain && ( + + )} + handleSearch(e.target.value)} + /> + + +
+ {showItems && ( +
+ {filterChainName?.map((chain) => ( +
handleSelectChain(chain)} + key={chain.chainPk} + className="p-2 cursor-pointer hover:bg-gray70 rounded-lg" + > +
+ + {chain.chainName} +
+
+ ))} +
+ )} +
+ ); +}; + +export default ChainList; diff --git a/app/contribution-hub/ConstraintDetailsModal.tsx b/app/contribution-hub/ConstraintDetailsModal.tsx new file mode 100644 index 00000000..84a9464a --- /dev/null +++ b/app/contribution-hub/ConstraintDetailsModal.tsx @@ -0,0 +1,273 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { RequirementProps, ConstraintProps, Chain } from "@/types"; +import useAddRequirement from "@/components/containers/provider-dashboard/hooks/useAddRequirement"; +import Icon from "@/components/ui/Icon"; +import ChainList from "@/app/contribution-hub/ChainList"; +import SelectMethodInput from "@/app/contribution-hub/SelectMethodInput"; +import { useWalletProvider } from "@/utils/wallet"; +import { isAddress } from "viem"; +import { checkCollectionAddress } from "@/components/containers/provider-dashboard/helpers/checkCollectionAddress"; +import CsvFileInput from "./CsvFileInput"; + +interface CreateModalParam { + constraint: ConstraintProps; + setRequirementParamsList: any; + requirementParamsList: any; + constraintFile: any; + allChainList: Chain[]; + setConstraintFile: (item: any) => void; +} + +interface DetailsModal { + constraint: ConstraintProps; + handleBackToConstraintListModal: any; + requirementList: RequirementProps[]; + insertRequirement: any; + updateRequirement: any; + allChainList: Chain[]; +} + +const ConstraintDetailsModal = ({ + constraint, + handleBackToConstraintListModal, + requirementList, + insertRequirement, + updateRequirement, + allChainList, +}: DetailsModal) => { + const provider = useWalletProvider(); + + const addRequirements = useAddRequirement( + handleBackToConstraintListModal, + insertRequirement, + updateRequirement + ); + + const [existRequirement, setExistRequirement] = + useState(null); + + const [requirementParamsList, setRequirementParamsList] = useState(); + + const [isNotSatisfy, setIsNotSatisfy] = useState(false); + + const [constraintFile, setConstraintFile] = useState(); + + const createRequirementParamsList = () => { + if (constraint.params.length > 0) { + setRequirementParamsList( + constraint.params.reduce((obj: any, item: any, index: any) => { + obj[item] = ""; + return obj; + }, {}) + ); + } + }; + + const [errorMessage, setErrorMessage] = useState(); + + useEffect(() => { + const requirement = requirementList.find( + (item) => item.pk == constraint.pk + ); + + setExistRequirement(requirement ? requirement : null); + + if (requirement) { + setIsNotSatisfy(requirement.isNotSatisfy); + setRequirementParamsList(requirement.params); + } else { + createRequirementParamsList(); + } + }, []); + + const checkingParamsValidation = async () => { + if (!requirementParamsList) return false; + if ( + !requirementParamsList.COLLECTION_ADDRESS || + !requirementParamsList.CHAIN || + !requirementParamsList.MINIMUM + ) { + !requirementParamsList.COLLECTION_ADDRESS + ? setErrorMessage("Please enter collection address.") + : !requirementParamsList.CHAIN + ? setErrorMessage("Please select chain.") + : setErrorMessage("Please select minimum amount."); + return false; + } + + if (requirementParamsList.COLLECTION_ADDRESS) { + const step2Check = isAddress(requirementParamsList.COLLECTION_ADDRESS); + const chain = allChainList?.find( + (item) => Number(item.pk) === Number(requirementParamsList.CHAIN) + ); + + if (!chain) return false; + + const res = await checkCollectionAddress( + provider, + requirementParamsList.COLLECTION_ADDRESS, + Number(chain.chainId) + ); + + (!step2Check || !res) && setErrorMessage("Invalid contract address."); + return step2Check && res; + } + }; + + const checkCsvFileUploadedValidation = () => { + console.log(requirementParamsList, requirementParamsList.CSV_FILE); + if (!requirementParamsList) return false; + if (!requirementParamsList.CSV_FILE) { + setErrorMessage("Please upload a csv file."); + return false; + } + return true; + }; + + const handleAddRequirement = async () => { + if (constraint.name === "core.HasNFTVerification") { + const res = await checkingParamsValidation(); + if (!res) return; + } + + if (constraint.name === "core.AllowListVerification") { + const res = checkCsvFileUploadedValidation(); + if (!res) return; + } + + addRequirements( + existRequirement, + constraint.pk, + constraint.name, + constraint.title, + isNotSatisfy, + requirementParamsList, + constraintFile + ); + }; + + const handleSelectNotSatisfy = (isSatisfy: boolean) => { + setIsNotSatisfy(isSatisfy); + }; + + return ( +
+
+ +
+
+
handleSelectNotSatisfy(false)} + className={`w-full flex items-center justify-center rounded-lg h-full cursor-pointer text-white relative overflow-hidden`} + > +
+

Should satisfy

+
+
handleSelectNotSatisfy(true)} + className={`w-full flex items-center justify-center rounded-lg h-full cursor-pointer text-white relative overflow-hidden`} + > +
+

Should not satisfy

+
+
+ +
{constraint.description}
+
{errorMessage}
+
+ Add Requirement +
+
+ ); +}; + +const CreateParams = ({ + constraint, + setRequirementParamsList, + requirementParamsList, + constraintFile, + setConstraintFile, + allChainList, +}: CreateModalParam) => { + const [reqNftAddress, setReqNftAddress] = useState(""); + + useEffect(() => { + if (!requirementParamsList) return; + setReqNftAddress(requirementParamsList.COLLECTION_ADDRESS); + }, [requirementParamsList]); + + const handleChangeCollection = (address: string) => { + setReqNftAddress(address); + setRequirementParamsList({ + ...requirementParamsList, + ["COLLECTION_ADDRESS"]: address, + }); + }; + + if (constraint.params.length === 0) return; + + if (constraint.name === "core.HasNFTVerification") { + return ( +
+ +
+ handleChangeCollection(e.target.value)} + /> +
+ +
+ ); + } + + if (constraint.name === "core.AllowListVerification") { + return ( + + ); + } + return <>; +}; + +export default ConstraintDetailsModal; diff --git a/app/contribution-hub/CsvFileInput.tsx b/app/contribution-hub/CsvFileInput.tsx new file mode 100644 index 00000000..d2bbcad5 --- /dev/null +++ b/app/contribution-hub/CsvFileInput.tsx @@ -0,0 +1,99 @@ +"use client"; +import Icon from "@/components/ui/Icon"; +import React, { useEffect, useState } from "react"; + +interface Prop { + requirementParamsList: any; + setRequirementParamsList: any; + setConstraintFile: (item: any) => void; + constraintFile: any; +} +const CsvFileInput = ({ + setRequirementParamsList, + requirementParamsList, + setConstraintFile, + constraintFile, +}: Prop) => { + const [isUploadedFileValid, setIsUploadedFileValid] = + useState(false); + + console.log(constraintFile); + const [uploadedFileName, setUploadedFileName] = useState(); + + useEffect(() => { + if (!requirementParamsList) return; + if (!requirementParamsList.CSV_FILE) return; + setIsUploadedFileValid(true); + setUploadedFileName(requirementParamsList.CSV_FILE); + }, [requirementParamsList]); + + const handleChangeUploadedFile = (e: any) => { + if (!e.target.files[0]) return; + const timeStamp = e.timeStamp.toFixed(); + const file = e.target.files[0]; + const fileName = file.name; + const lastIndex = fileName.lastIndexOf("."); + const newFileName = + file.name.slice(0, lastIndex) + timeStamp + file.name.slice(lastIndex); + const newFile = new File([file], `${newFileName}`); + console.log(newFile); + const fileSuffix = fileName.slice(lastIndex, fileName.length); + if (fileSuffix != ".csv") { + setIsUploadedFileValid(false); + return; + } else { + setRequirementParamsList({ + ...requirementParamsList, + ["CSV_FILE"]: newFileName, + }); + setIsUploadedFileValid(true); + setUploadedFileName(fileName); + setConstraintFile(newFile); + } + }; + + const handleClearUploadedFile = () => { + setIsUploadedFileValid(false); + setUploadedFileName(null); + setRequirementParamsList({ + ...requirementParamsList, + ["CSV_FILE"]: "", + }); + }; + + return ( +
+ {!isUploadedFileValid ? ( +
+ +
+ handleChangeUploadedFile(e)} + accept=".csv, .txt" + /> +
+
+ ) : ( +
+

file name: {uploadedFileName}

+ +
+ )} +
+ ); +}; + +export default CsvFileInput; diff --git a/app/contribution-hub/SelectMethodInput.tsx b/app/contribution-hub/SelectMethodInput.tsx new file mode 100644 index 00000000..5d207586 --- /dev/null +++ b/app/contribution-hub/SelectMethodInput.tsx @@ -0,0 +1,131 @@ +import Icon from "@/components/ui/Icon"; +import { useOutsideClick } from "@/utils/hooks/dom"; +import React, { useEffect, useRef, useState } from "react"; + +export enum SelectMethod { + Minimum = "Minimum", + Maximum = "Maximum", +} + +interface Prop { + setRequirementParamsList: (e: any) => void; + requirementParamsList: any; +} + +const SelectMethodInput = ({ + setRequirementParamsList, + requirementParamsList, +}: Prop) => { + const [selectedMethod, setSelectedMethod] = useState(); + const [showItems, setShowItems] = useState(false); + + const handleSelectMethod = () => { + setSelectedMethod("Minimum Amount"); + setShowItems(false); + }; + + interface MethodProp { + minimum: number; + maximum: number; + } + + const [methodValues, setMethodValues] = useState({ + minimum: 0, + maximum: 0, + }); + + const ref = useRef(null); + + useOutsideClick(ref, () => { + if (showItems) setShowItems(false); + }); + + const handleChangeMethodValues = (e: string) => { + const value = + e === "increase" + ? methodValues.minimum! + 1 + : Math.max(1, methodValues.minimum - 1); + + setMethodValues({ + ...methodValues, + minimum: value, + }); + + setRequirementParamsList({ ...requirementParamsList, ["MINIMUM"]: value }); + }; + + useEffect(() => { + if (!requirementParamsList) return; + if (!requirementParamsList.MINIMUM) return; + handleSelectMethod(); + setMethodValues({ + ...methodValues, + minimum: requirementParamsList.MINIMUM, + }); + }, [requirementParamsList]); + + return ( +
+
setShowItems(!showItems)} + className="flex w-full items-center justify-between bored border-gray50 bg-gray40 rounded-xl px-3 h-[43px] cursor-pointer" + > +
+ {selectedMethod ? selectedMethod : "Select Method"} +
+ +
+ {showItems && ( +
+
+ Minimum +
+
+ )} + + {selectedMethod && ( +
+ + setMethodValues({ + ...methodValues, + minimum: Number(e.target.value) ?? "", + }) + } + /> +
+ handleChangeMethodValues("increase")} + className="cursor-pointer" + iconSrc="/assets/images/provider-dashboard/arrow-top-dark.svg" + /> + handleChangeMethodValues("decrease")} + className="cursor-pointer" + iconSrc="/assets/images/provider-dashboard/arrow-down-dark.svg" + /> +
+
+ )} +
+ ); +}; + +export default SelectMethodInput; diff --git a/app/contribution-hub/content.module.scss b/app/contribution-hub/content.module.scss index 0cbf109a..5ba719fd 100644 --- a/app/contribution-hub/content.module.scss +++ b/app/contribution-hub/content.module.scss @@ -13,7 +13,7 @@ } .refillToken { - @apply bg-no-repeat bg-cover border border-2 rounded-[16px] border-gray40; + @apply bg-no-repeat bg-cover border-2 rounded-[16px] border-gray40; background-image: url("/assets/images/provider-dashboard/bg.png"); } diff --git a/app/contribution-hub/gas-tap/components/Content.tsx b/app/contribution-hub/gas-tap/components/Content.tsx new file mode 100644 index 00000000..4034ea94 --- /dev/null +++ b/app/contribution-hub/gas-tap/components/Content.tsx @@ -0,0 +1,158 @@ +"use client"; + +import { + ProviderDashboardButton, + ProviderDashboardButtonSuccess, +} from "../../Buttons"; +import Icon from "@/components/ui/Icon"; +import SearchInput from "@/app/gastap/components/searchInput"; +import Link from "next/link"; +import RoutePath from "@/utils/routes"; + +const GasTapContent = () => { + return ( +
+
+
+ +
+
All
+
ongoing
+
verified
+
rejected
+
finished
+
+
+
+
+
+
+

+ Refill Gas Tap Tokens +

{" "} +

Provide Gas Fee.

+
+ +
+ handleSelectProvideGasFee(true)} + href={RoutePath.PROVIDER_GASTAP_CREATE} + className="flex mt-5 sm:mt-0 items-center justify-center cursor-pointer border-2 border-white rounded-[12px] bg-[#0C0C17] w-[226px] h-[46px]" + > + + Provide Gas Fee + +
+
+
+
+
+
+ +

Polygon

+
+
+
+ EVM +
+
+ Mainnet +
+
+
+
+
+
+ Currency Matic +
+
+ Refill Amount 2,137 +
+
+
+ +

Pending...

+
+
+
+
+ +
+
+
+ +

Polygon

+
+
+
+ EVM +
+
+ Mainnet +
+
+
+
+
+
+ Currency Matic +
+
+ Refill Amount 2,137 +
+
+
+ +

Done

+
+
+
+
+ +
+
+
+ +

Polygon

+
+
+
+ EVM +
+
+ Mainnet +
+
+
+
+
+
+ Currency Matic +
+
+ Refill Amount 2,137 +
+
+
+ +

Done

+
+
+
+
+
+
+
+ ); +}; + +export default GasTapContent; diff --git a/app/contribution-hub/gas-tap/components/ProvideGasFeeContent.tsx b/app/contribution-hub/gas-tap/components/ProvideGasFeeContent.tsx new file mode 100644 index 00000000..74f7e557 --- /dev/null +++ b/app/contribution-hub/gas-tap/components/ProvideGasFeeContent.tsx @@ -0,0 +1,323 @@ +import FundTransactionModal from "@/app/gastap/components/Modals/FundTransactionModal"; +import SelectChainModal from "@/app/gastap/components/Modals/SelectChainModal"; +import { ClaimButton } from "@/components/ui/Button/button"; +import Icon from "@/components/ui/Icon"; +import Modal from "@/components/ui/Modal/modal"; +import { useGasTapContext } from "@/context/gasTapProvider"; +import { useUserProfileContext } from "@/context/userProfile"; +import { Chain, ChainType } from "@/types/gastap"; +import { FC, useCallback, useEffect, useMemo, useState } from "react"; +import { parseToLamports } from "@/utils/numbers"; +import { parseEther } from "viem"; +import { submitDonationTxHash } from "@/utils/api"; +import { + estimateGas, + useNetworkSwitcher, + useWalletAccount, + useWalletBalance, + useWalletNetwork, + useWalletProvider, + useWalletSigner, +} from "@/utils/wallet"; +import { useGlobalContext } from "@/context/globalProvider"; +import { USER_DENIED_REQUEST_ERROR_CODE } from "@/utils/web3"; +import { getChainIcon } from "@/utils/chain"; + +const ProvideGasFeeContent: FC<{ initialChainId?: number }> = ({ + initialChainId, +}) => { + const { chainList: originalChainList } = useGasTapContext(); + + const { userToken } = useUserProfileContext(); + const { isConnected, address } = useWalletAccount(); + const provider = useWalletProvider(); + const signer = useWalletSigner(); + const { chain } = useWalletNetwork(); + const chainId = chain?.id; + + const [selectedChain, setSelectedChain] = useState(null); + const balance = useWalletBalance({ + address, + chainId: Number(selectedChain?.chainId), + }); + + const chainList = useMemo(() => { + return originalChainList.filter( + (chain) => chain.chainType !== ChainType.SOLANA + ); + }, [originalChainList]); + + useEffect(() => { + if (chainList.length > 0 && !selectedChain) { + if (initialChainId) { + const chain = chainList.find( + (chain) => chain.pk === Number(initialChainId) + ); + if (chain) { + setSelectedChain(chain); + } + } else { + setSelectedChain(chainList[0]); + } + } + }, [chainList, initialChainId, selectedChain]); + + const { switchChain } = useNetworkSwitcher(); + + const [fundAmount, setFundAmount] = useState(""); + + const [modalState, setModalState] = useState(false); + const [fundTransactionError, setFundTransactionError] = useState(""); + const [txHash, setTxHash] = useState(""); + + const isRightChain = useMemo(() => { + if (!isConnected || !chainId || !selectedChain) return false; + return chainId === Number(selectedChain.chainId); + }, [selectedChain, isConnected, chainId]); + + const { setIsWalletPromptOpen } = useGlobalContext(); + + const handleTransactionError = useCallback((error: any) => { + if (error?.code === USER_DENIED_REQUEST_ERROR_CODE) return; + const message = error?.data?.message || error?.error?.message; + if (message) { + if (message.includes("insufficient funds")) { + setFundTransactionError("Error: Insufficient Funds"); + } else { + setFundTransactionError(message); + } + } else { + setFundTransactionError( + "Unexpected error. Could not estimate gas for this transaction." + ); + } + }, []); + + useEffect(() => { + setFundAmount(""); + }, [chainId]); + + const [submittingFundTransaction, setSubmittingFundTransaction] = + useState(false); + + const loading = useMemo(() => { + if (submittingFundTransaction) return true; + if (!isConnected) return false; + return !chainId || !selectedChain || !address; + }, [address, isConnected, chainId, selectedChain, submittingFundTransaction]); + + const handleSendFunds = useCallback(async () => { + if (!isConnected) { + setIsWalletPromptOpen(true); + return; + } + if (!chainId || !selectedChain || !address || loading) return; + if (!isRightChain) { + await switchChain(Number(selectedChain.chainId)); + return; + } + if (!Number(fundAmount)) { + alert("Enter fund amount"); + return; + } + + if (!provider) return; + + const chainPk = selectedChain.pk; + + let tx = { + to: "0xE6Bc2586fcC1Da738733867BFAf381B846AAe834" as any, + value: BigInt( + selectedChain.symbol === "SOL" + ? parseToLamports(fundAmount) + : parseEther(fundAmount) + ), + }; + + setSubmittingFundTransaction(true); + + const estimatedGas = await estimateGas(provider, { + from: address, + to: "0xE6Bc2586fcC1Da738733867BFAf381B846AAe834", + value: BigInt(tx.value), + }).catch((err: any) => { + return err; + }); + + console.log(estimateGas); + + if (typeof estimatedGas !== "bigint") { + handleTransactionError(estimatedGas); + setSubmittingFundTransaction(false); + return; + } + + signer + ?.sendTransaction({ + ...tx, + ...(estimatedGas ? { gasLimit: estimatedGas } : {}), + // gasPrice /// TODO add gasPrice based on EIP 1559 + }) + .then(async (tx) => { + await provider.waitForTransactionReceipt({ + hash: tx, + confirmations: 1, + }); + return tx; + }) + .then(async (tx) => { + if (userToken) await submitDonationTxHash(tx, chainPk, userToken); + setTxHash(tx); + }) + .catch((err) => { + handleTransactionError(err); + }) + .finally(() => { + setSubmittingFundTransaction(false); + }); + }, [ + isConnected, + chainId, + selectedChain, + address, + loading, + isRightChain, + fundAmount, + provider, + signer, + setIsWalletPromptOpen, + switchChain, + handleTransactionError, + userToken, + ]); + + const closeModalHandler = () => { + setFundTransactionError(""); + setTxHash(""); + setModalState(false); + }; + + const fundActionButtonLabel = useMemo(() => { + if (!isConnected) { + return "Connect Wallet"; + } + if (loading) { + return "Loading..."; + } + return !isRightChain ? "Switch Network" : "Submit Contribution"; + }, [isConnected, isRightChain, loading]); + + useEffect(() => { + balance.refetch(); + }, [isRightChain, address, provider]); + + return ( +
+
+
+ +
+
+ +
+
+
+

Provide Gas Fee

+
+

+ 100% of contributions will fund distributions and transaction + costs of the gas tap. +

+
+
+ +
+
setModalState(true)} + > + {selectedChain ? ( +
+ +

+ {selectedChain?.symbol} +

+
+ ) : ( + + )} + +
+
+
+ setFundAmount(e.target.value)} + /> +
setFundAmount(balance.data?.formatted!)} + className="bg-gray20 select-not hover:bg-gray40 border border-gray100 text-gray100 text-[12px] flex items-center w-[52px] h-[28px] rounded-xl justify-center cursor-pointer" + > + Max +
+
+
+
+ + + + + + + {fundActionButtonLabel} + + + + + +
+
+
+ ); +}; + +export default ProvideGasFeeContent; diff --git a/app/contribution-hub/gas-tap/create/layout.tsx b/app/contribution-hub/gas-tap/create/layout.tsx new file mode 100644 index 00000000..81442027 --- /dev/null +++ b/app/contribution-hub/gas-tap/create/layout.tsx @@ -0,0 +1,43 @@ +import { GasTapProvider } from "@/context/gasTapProvider"; +import { FC, PropsWithChildren } from "react"; +import { Chain } from "@/types"; +import { cookies } from "next/headers"; +import { + getClaimedReceiptsServer, + getFaucetListServer, + getFuelChampionListServerSide, + getOneTimeClaimedReceiptsServer, +} from "@/utils/serverApis"; + +const ProviderDashboardGasTapLayout: FC = async ({ + children, +}) => { + const chainsApi = await getFaucetListServer(); + + const fuelChampionList = await getFuelChampionListServerSide(); + + const cookieStore = cookies(); + + const token = cookieStore.get("userToken"); + + const oneTimeClaimedChains = await getOneTimeClaimedReceiptsServer( + token?.value + ); + + const claimedChains = await getClaimedReceiptsServer(token?.value); + + const chains = chainsApi as Array; + + return ( + + {children} + + ); +}; + +export default ProviderDashboardGasTapLayout; diff --git a/app/contribution-hub/gas-tap/create/page.tsx b/app/contribution-hub/gas-tap/create/page.tsx new file mode 100644 index 00000000..edbd4e16 --- /dev/null +++ b/app/contribution-hub/gas-tap/create/page.tsx @@ -0,0 +1,13 @@ +"use client"; + +import ProvideGasFeeContent from "../components/ProvideGasFeeContent"; + +const Page = () => { + return ( + <> + + + ); +}; + +export default Page; diff --git a/app/contribution-hub/gas-tap/layout.tsx b/app/contribution-hub/gas-tap/layout.tsx index d7968e82..60a32708 100644 --- a/app/contribution-hub/gas-tap/layout.tsx +++ b/app/contribution-hub/gas-tap/layout.tsx @@ -1,12 +1,15 @@ -import ProviderDashboardGasTapContextProvider from "@/context/providerDashboardGasTapContext"; +import { getUserDonationsServer } from "@/utils/serverApis"; +import { cookies } from "next/headers"; import { FC, PropsWithChildren } from "react"; -const ProviderDashboardGasTapLayout: FC = ({ children }) => { - return ( - - {children} - - ); +const ProviderDashboardGasTapLayout: FC = async ({ + children, +}) => { + const cookieStore = cookies(); + const token = cookieStore.get("userToken"); + const userDonationList = await getUserDonationsServer(token!.value); + console.log(userDonationList); + return <>{children}; }; export default ProviderDashboardGasTapLayout; diff --git a/app/contribution-hub/gas-tap/page.tsx b/app/contribution-hub/gas-tap/page.tsx index 1f887c32..2a2635cc 100644 --- a/app/contribution-hub/gas-tap/page.tsx +++ b/app/contribution-hub/gas-tap/page.tsx @@ -1,5 +1,13 @@ +"use client"; + +import GasTapContent from "./components/Content"; + const Page = () => { - return
salam
-} + return ( + <> + + + ); +}; -export default Page +export default Page; diff --git a/app/contribution-hub/prize-tap/components/Content.tsx b/app/contribution-hub/prize-tap/components/Content.tsx index b130eff6..cb70ae81 100644 --- a/app/contribution-hub/prize-tap/components/Content.tsx +++ b/app/contribution-hub/prize-tap/components/Content.tsx @@ -124,9 +124,7 @@ const PrizeCard = ({ prize }: PrizeCardProp) => { className="absolute bottom-3 right-4 left-4" href={RoutePath.PROVIDER_PRIZETAP_VERIFICATION + "/" + prize.pk} > - handleCheckForReason(prize)} - > + Check For Reasons @@ -214,7 +212,7 @@ const PrizeCard = ({ prize }: PrizeCardProp) => { {prize.numberOfOnchainEntries >= 1 && !prize.winnerEntries?.length ? "Raffle is being processed" - : "Check Winner Wallets"} + : "Check Winners"}

@@ -225,13 +223,6 @@ const PrizeCard = ({ prize }: PrizeCardProp) => { }; const PrizeTapContent = () => { - // const { - // selectNewOffer, - // handleSelectNewOffer, - // userRaffles, - // userRafflesLoading, - // isShowingDetails, - // } = usePrizeOfferFormContext(); const { userToken } = useUserProfileContext(); const [userRafflesLoading, setUserRafflesLoading] = useState(false); const [userRaffles, setUserRaffles] = useState([]); diff --git a/app/contribution-hub/prize-tap/components/Modals/winnersModal.tsx b/app/contribution-hub/prize-tap/components/Modals/winnersModal.tsx index f73f6cbe..f05ff583 100644 --- a/app/contribution-hub/prize-tap/components/Modals/winnersModal.tsx +++ b/app/contribution-hub/prize-tap/components/Modals/winnersModal.tsx @@ -3,7 +3,7 @@ import Icon from "@/components/ui/Icon"; import { useCallback, useEffect, useMemo, useState } from "react"; import { Address } from "viem"; -import { WalletWinner } from "@/components/containers/prize-tap/Linea/LineaWinnersModal"; +import { WalletWinner } from "@/app/prizetap/components/Linea/LineaWinnersModal"; import Modal from "@/components/ui/Modal/modal"; import { prizeTap721ABI, prizeTapABI } from "@/types/abis/contracts"; import { readContracts } from "wagmi"; diff --git a/app/contribution-hub/prize-tap/components/OfferPrizeForm/ContactInformation.tsx b/app/contribution-hub/prize-tap/components/OfferPrizeForm/ContactInformation.tsx index d35a22b0..9df35e01 100644 --- a/app/contribution-hub/prize-tap/components/OfferPrizeForm/ContactInformation.tsx +++ b/app/contribution-hub/prize-tap/components/OfferPrizeForm/ContactInformation.tsx @@ -56,7 +56,9 @@ const ContactInformation = ({ isShowingDetails, socialMediaValidation, } = usePrizeOfferFormContext(); + const [showErrors, setShowErrors] = useState(false); + const handleNextPage = () => { const res = canGoStepFive(); setShowErrors(!res); @@ -65,13 +67,27 @@ const ContactInformation = ({ return (
-
+
+
+ +
+

+ Your website, twitter & discord will be shown on PrizeTap card. +

+
+
{contactFields.map((field, index) => (
- {index == 3 ?

Contact info

: ""} + {index == 3 &&

Contact info

}
= 3 ? "mb-2" : "" + } ${ (field.required && showErrors && !data[field.name]) || (showErrors && data[field.name] && @@ -94,7 +110,7 @@ const ContactInformation = ({ />
{field.required && showErrors && !data[field.name] && ( -

+

Required

)} @@ -108,10 +124,10 @@ const ContactInformation = ({
))} -
+