diff --git a/app/admin/quests/create/page.tsx b/app/admin/quests/create/page.tsx index a2d7bdec..a315f01c 100644 --- a/app/admin/quests/create/page.tsx +++ b/app/admin/quests/create/page.tsx @@ -1,6 +1,12 @@ "use client"; -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React, { + useCallback, + useEffect, + useMemo, + useState, + useRef, +} from "react"; import styles from "@styles/admin.module.css"; import { useRouter } from "next/navigation"; import { AdminService } from "@services/authService"; @@ -23,6 +29,7 @@ import Typography from "@components/UI/typography/typography"; import { TEXT_TYPE } from "@constants/typography"; export default function Page() { + const isSaving = useRef(false); const router = useRouter(); const [currentPage, setCurrentPage] = useState(0); const [questId, setQuestId] = useState(0); @@ -247,161 +254,201 @@ export default function Page() { }, [questInput, boostInput, nfturi]); const handleCreateTask = useCallback(async () => { - setButtonLoading(true); - steps.map(async (step) => { - if (step.type === "Quiz") { - if ( - step.data.quiz_name?.length === 0 || - step.data.quiz_desc?.length === 0 || - step.data.quiz_intro?.length === 0 || - step.data.quiz_cta?.length === 0 || - step.data.quiz_help_link?.length === 0 - ) { - showNotification("Please fill all fields for Quiz", "info"); - return; - } - const response = await AdminService.createQuiz({ - quest_id: questId, - name: step.data.quiz_name, - desc: step.data.quiz_desc, - intro: step.data.quiz_intro, - cta: step.data.quiz_cta, - help_link: step.data.quiz_help_link, - }); - for (const question of step.data.questions) { - try { - await AdminService.createQuizQuestion({ - quiz_id: response.quiz_id, - question: question.question, - options: question.options, - correct_answers: question.correct_answers, - }); - } catch (error) { - console.error("Error executing promise:", error); + if (isSaving.current) return; + try { + isSaving.current = true; + setButtonLoading(true); + const unsavedSteps = steps.filter((step) => !step.data.id); + let failedQuestions = []; + for (const step of unsavedSteps) { + if (step.type === "Quiz") { + if ( + step.data.quiz_name?.length === 0 || + step.data.quiz_desc?.length === 0 || + step.data.quiz_intro?.length === 0 || + step.data.quiz_cta?.length === 0 || + step.data.quiz_help_link?.length === 0 + ) { + showNotification("Please fill all fields for Quiz", "info"); + continue; } - } - } - if (step.type === "TwitterFw") { - if ( - step.data.twfw_name?.length === 0 || - step.data.twfw_desc?.length === 0 || - step.data.twfw_username?.length === 0 - ) { - showNotification("Please fill all fields for Twitter Follow", "info"); - return; - } - await AdminService.createTwitterFw({ - quest_id: questId, - name: step.data.twfw_name, - desc: step.data.twfw_desc, - username: step.data.twfw_username, - }); - } else if (step.type === "TwitterRw") { - if ( - step.data.twrw_name?.length === 0 || - step.data.twrw_desc?.length === 0 || - step.data.twrw_post_link?.length === 0 - ) { - showNotification( - "Please fill all fields for Twitter Retweet", - "info" - ); - return; - } - await AdminService.createTwitterRw({ - quest_id: questId, - name: step.data.twrw_name, - desc: step.data.twrw_desc, - post_link: step.data.twrw_post_link, - }); - } else if (step.type === "Discord") { - if ( - step.data.dc_name?.length === 0 || - step.data.dc_desc?.length === 0 || - step.data.dc_invite_link?.length === 0 || - step.data.dc_guild_id?.length === 0 - ) { - showNotification("Please fill all fields for Discord", "info"); - return; - } - await AdminService.createDiscord({ - quest_id: questId, - name: step.data.dc_name, - desc: step.data.dc_desc, - invite_link: step.data.dc_invite_link, - guild_id: step.data.dc_guild_id, - }); - } else if (step.type === "Custom") { - if ( - step.data.custom_name?.length === 0 || - step.data.custom_desc?.length === 0 || - step.data.custom_cta?.length === 0 || - step.data.custom_href?.length === 0 || - step.data.custom_api?.length === 0 - ) { - showNotification("Please fill all fields for Discord", "info"); - return; - } - await AdminService.createCustom({ - quest_id: questId, - name: step.data.custom_name, - desc: step.data.custom_desc, - cta: step.data.custom_cta, - href: step.data.custom_href, - api: step.data.custom_api, - }); - } else if (step.type === "Domain") { - await AdminService.createDomain({ - quest_id: questId, - name: step.data.domain_name, - desc: step.data.domain_desc, - }); - } else if (step.type === "Balance") { - try { - await AdminService.createBalance({ + const response = await AdminService.createQuiz({ quest_id: questId, - name: step.data.balance_name, - desc: step.data.balance_desc, - contracts: step.data.balance_contracts, - cta: step.data.balance_cta, - href: step.data.balance_href, + name: step.data.quiz_name, + desc: step.data.quiz_desc, + intro: step.data.quiz_intro, + cta: step.data.quiz_cta, + help_link: step.data.quiz_help_link, }); - } catch (error) { - console.error("Error while creating balance task:", error); - } - } else if (step.type === "CustomApi") { - try { - await AdminService.createCustomApi({ + + if (response) { + for (const question of step.data.questions) { + try { + await AdminService.createQuizQuestion({ + quiz_id: response.quiz_id, + question: question.question, + options: question.options, + correct_answers: question.correct_answers, + }); + } catch (error) { + console.error("Error executing promise:", error); + failedQuestions.push(question.question); + } + } + if (failedQuestions.length > 0) { + showNotification( + `Failed to create ${failedQuestions.length} questions. Please review and try again.`, + "warning" + ); + } + step.data.id = response.id; + } + } else if (step.type === "TwitterFw") { + if ( + step.data.twfw_name?.length === 0 || + step.data.twfw_desc?.length === 0 || + step.data.twfw_username?.length === 0 + ) { + showNotification( + "Please fill all fields for Twitter Follow", + "info" + ); + continue; + } + const response = await AdminService.createTwitterFw({ quest_id: questId, - name: step.data.api_name, - desc: step.data.api_desc, - api_url: step.data.api_url, - cta: step.data.api_cta, - href: step.data.api_href, - regex: step.data.api_regex, + name: step.data.twfw_name, + desc: step.data.twfw_desc, + username: step.data.twfw_username, }); - } catch (error) { - console.error("Error while creating balance task:", error); - } - } else if (step.type === "Contract") { - try { - await AdminService.createContract({ + if (response) step.data.id = response.id; + } else if (step.type === "TwitterRw") { + if ( + step.data.twrw_name?.length === 0 || + step.data.twrw_desc?.length === 0 || + step.data.twrw_post_link?.length === 0 + ) { + showNotification( + "Please fill all fields for Twitter Retweet", + "info" + ); + continue; + } + const response = await AdminService.createTwitterRw({ + quest_id: questId, + name: step.data.twrw_name, + desc: step.data.twrw_desc, + post_link: step.data.twrw_post_link, + }); + if (response) step.data.id = response.id; + } else if (step.type === "Discord") { + if ( + step.data.dc_name?.length === 0 || + step.data.dc_desc?.length === 0 || + step.data.dc_invite_link?.length === 0 || + step.data.dc_guild_id?.length === 0 + ) { + showNotification("Please fill all fields for Discord", "info"); + continue; + } + const response = await AdminService.createDiscord({ quest_id: questId, - name: step.data.contract_name, - desc: step.data.contract_desc, - href: step.data.contract_href, - cta: step.data.contract_cta, - calls: JSON.parse(step.data.contract_calls), + name: step.data.dc_name, + desc: step.data.dc_desc, + invite_link: step.data.dc_invite_link, + guild_id: step.data.dc_guild_id, }); - } catch (error) { - console.error("Error while creating contract task:", error); - showNotification(`Error adding ${step.type} task: ${error}`, "error"); + if (response) step.data.id = response.id; + } else if (step.type === "Custom") { + if ( + step.data.custom_name?.length === 0 || + step.data.custom_desc?.length === 0 || + step.data.custom_cta?.length === 0 || + step.data.custom_href?.length === 0 || + step.data.custom_api?.length === 0 + ) { + showNotification("Please fill all fields for Custom", "info"); + continue; + } + const response = await AdminService.createCustom({ + quest_id: questId, + name: step.data.custom_name, + desc: step.data.custom_desc, + cta: step.data.custom_cta, + href: step.data.custom_href, + api: step.data.custom_api, + }); + if (response) step.data.id = response.id; + } else if (step.type === "Domain") { + const response = await AdminService.createDomain({ + quest_id: questId, + name: step.data.domain_name, + desc: step.data.domain_desc, + }); + if (response) step.data.id = response.id; + } else if (step.type === "Balance") { + try { + const response = await AdminService.createBalance({ + quest_id: questId, + name: step.data.balance_name, + desc: step.data.balance_desc, + contracts: step.data.balance_contracts, + cta: step.data.balance_cta, + href: step.data.balance_href, + }); + if (response) step.data.id = response.id; + } catch (error) { + console.error("Error while creating balance task:", error); + } + } else if (step.type === "CustomApi") { + try { + const response = await AdminService.createCustomApi({ + quest_id: questId, + name: step.data.api_name, + desc: step.data.api_desc, + api_url: step.data.api_url, + cta: step.data.api_cta, + href: step.data.api_href, + regex: step.data.api_regex, + }); + if (response) step.data.id = response.id; + } catch (error) { + console.error("Error while creating CustomApi task:", error); + } + } else if (step.type === "Contract") { + try { + const response = await AdminService.createContract({ + quest_id: questId, + name: step.data.contract_name, + desc: step.data.contract_desc, + href: step.data.contract_href, + cta: step.data.contract_cta, + calls: (() => { + try { + return JSON.parse(step.data.contract_calls); + } catch (error) { + showNotification("Invalid contract calls format", "error"); + throw error; + } + })(), + }); + if (response) step.data.id = response.id; + } catch (error) { + console.error("Error while creating contract task:", error); + showNotification( + `Error adding ${step.type} task: ${error}`, + "error" + ); + } } } - }); - setButtonLoading(false); - setCurrentPage((prev) => prev + 1); - }, [steps]); + setSteps([...steps]); + setCurrentPage((prev) => prev + 1); + } finally { + isSaving.current = false; + setButtonLoading(false); + } + }, [steps, questId]); const handleRemoveStep = useCallback( (index: number) => {