-
Notifications
You must be signed in to change notification settings - Fork 144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix: Prevent task duplication when 'Save all tasks' is clicked multip… #998
Changes from 6 commits
7c4d26d
1701cdf
c8a7858
5ec0a61
ad23990
798cc9d
e12cc41
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<number>(0); | ||
|
@@ -247,162 +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.dc_name, | ||
desc: step.data.dc_desc, | ||
invite_link: step.data.dc_invite_link, | ||
guild_id: step.data.dc_guild_id, | ||
}); | ||
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.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.custom_name, | ||
desc: step.data.custom_desc, | ||
cta: step.data.custom_cta, | ||
href: step.data.custom_href, | ||
api: step.data.custom_api, | ||
}); | ||
} 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 === "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") { | ||
Comment on lines
+389
to
+403
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Standardize error handling for Balance and CustomApi tasks The error handling for Balance and CustomApi tasks only logs to console, while Contract tasks show notifications. This inconsistency could lead to poor user experience. Add user notifications: } catch (error) {
console.error("Error while creating balance task:", error);
+ showNotification(
+ `Error adding Balance task: ${error}`,
+ "error"
+ );
} } catch (error) {
console.error("Error while creating CustomApi task:", error);
+ showNotification(
+ `Error adding CustomApi task: ${error}`,
+ "error"
+ );
} Also applies to: 403-418 |
||
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) => { | ||
|
@@ -495,7 +541,7 @@ export default function Page() { | |
<AdminQuestDetails | ||
quest={finalQuestData} | ||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
setShowDomainPopup={() => { }} | ||
setShowDomainPopup={() => {}} | ||
hasRootDomain={false} | ||
rewardButtonTitle={finalQuestData.disabled ? "Enable" : "Disable"} | ||
onRewardButtonClick={async () => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add validation for Domain task type
The Domain task type lacks field validation unlike other task types. This could lead to creation of incomplete tasks.
Add validation similar to other task types:
📝 Committable suggestion