diff --git a/src/atlas_frontend/src/canisters/atlasSpace/api.ts b/src/atlas_frontend/src/canisters/atlasSpace/api.ts index b1859cf..37be8eb 100644 --- a/src/atlas_frontend/src/canisters/atlasSpace/api.ts +++ b/src/atlas_frontend/src/canisters/atlasSpace/api.ts @@ -1,6 +1,7 @@ import type { ActorSubclass } from "@dfinity/agent"; import type { _SERVICE, + AnswerFormat, ClosedTask, State, Submission, @@ -22,6 +23,7 @@ interface CreateSubtaskArg { title: string; description: string; allow_resubmit: boolean; + answer_format: AnswerFormat } interface GetAtlasSpaceArgs { @@ -93,6 +95,7 @@ export const createNewTask = async ({ task_title: arg.title, task_description: arg.description, allow_resubmit: arg.allow_resubmit, + answer_format: arg.answer_format }, })); diff --git a/src/atlas_frontend/src/components/Submissions/index.tsx b/src/atlas_frontend/src/components/Submissions/index.tsx index 6bfe213..44bcf45 100644 --- a/src/atlas_frontend/src/components/Submissions/index.tsx +++ b/src/atlas_frontend/src/components/Submissions/index.tsx @@ -133,7 +133,7 @@ const Submissions = () => {
- +
)} + {"List" in submission.submissionData.submission && ( +
    + {submission.submissionData.submission.List.items.map((item: string, idx: number) => ( +
  • + {item} +
  • + ))} +
+ )} + {submissionState === "Rejected" && submission.submissionData.rejection_reason[0] && submission.submissionData.rejection_reason[0].trim().length > 0 && ( diff --git a/src/atlas_frontend/src/components/Task/tasks/GenericTask.tsx b/src/atlas_frontend/src/components/Task/tasks/GenericTask.tsx index b150eda..d8c6077 100644 --- a/src/atlas_frontend/src/components/Task/tasks/GenericTask.tsx +++ b/src/atlas_frontend/src/components/Task/tasks/GenericTask.tsx @@ -1,11 +1,11 @@ -import React from "react"; +import React, { useMemo } from "react"; import { useState } from "react"; import type { _SERVICE, TaskType, } from "../../../../../declarations/atlas_space/atlas_space.did"; import Button from "../../Shared/Button"; -import { useForm, type SubmitHandler } from "react-hook-form"; +import { useFieldArray, useForm, type SubmitHandler } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; import * as yup from "yup"; import { @@ -32,21 +32,13 @@ interface GenericTaskProps { disabled?: boolean; } -interface GenericTaskFormInput { +interface TextFormData { taskSubmission: string; } -const maxDescriptionLength = 500; - -const schema = yup.object({ - taskSubmission: yup - .string() - .max(maxDescriptionLength) - .trim() - .min(2) - .required() - .label("Task submission"), -}); +interface ListFormData { + items: { value: string }[]; +} const GenericTask = ({ genericTask, @@ -58,22 +50,49 @@ const GenericTask = ({ disabled = false, }: GenericTaskProps) => { const dispatch = useDispatch(); - const { user } = useAuth(); + const { user, connect } = useAuth(); const [openSubmission, setSubmission] = useState(false); - const { register, handleSubmit} = useForm({ - resolver: yupResolver(schema), + const authAtlasSpace = useAuthAtlasSpaceActor(spacePrincipal); + const answerFormatKey = "TitleAndDescription" in genericTask.task_content + ? (Object.keys(genericTask.task_content.TitleAndDescription.answer_format)[0] as + | "Small" + | "Paragraph" + | "Long" + | "List") + : null; + + const maxTextLength = useMemo(() => { + switch (answerFormatKey) { + case "Small": + return 254; + case "Paragraph": + return 600; + case "Long": + return 2500; + default: + return 254; + } + }, [answerFormatKey]); + + const textSchema = yup.object({ + taskSubmission: yup + .string() + .trim() + .min(2) + .max(maxTextLength) + .required("Field is required"), + }); + + const textForm = useForm({ + resolver: yupResolver(textSchema), defaultValues: { - taskSubmission: "", + taskSubmission: "" }, }); - const { connect } = useAuth(); - const authAtlasSpace = useAuthAtlasSpaceActor(spacePrincipal); - const handleSubmitResponse: SubmitHandler = async ({ - taskSubmission, - }) => { + const onSubmitText: SubmitHandler = async ({ taskSubmission }) => { if (!authAtlasSpace || !unAuthAtlasSpace) return; - + await runWithLoading(async () => { const call = submitSubtaskSubmission({ authAtlasSpace, @@ -88,7 +107,61 @@ const GenericTask = ({ }); setSubmission(false); - getSpaceTasks({ + await getSpaceTasks({ + spaceId: spacePrincipal.toString(), + unAuthAtlasSpace, + dispatch, + }); + }, dispatch, () => setSubmission(false)); + }; + + const listSchema = yup.object({ + items: yup + .array() + .of( + yup.object({ + value: yup + .string() + .trim() + .max(254, "Item too long") + .required("Item cannot be empty"), + }) + ) + .max(25, "Max 25 items allowed") + .required(), +}); + +const listForm = useForm({ + resolver: yupResolver(listSchema), + defaultValues: { + items: [{ value: "" }] + }, +}); + + const { fields, append, remove } = useFieldArray({ + control: listForm.control, + name: "items", + }); + + const onSubmitList = async () => { + if (!authAtlasSpace || !unAuthAtlasSpace) return; + + const items = listForm.getValues().items.map(item => item.value.trim()); + await runWithLoading(async () => { + const call = submitSubtaskSubmission({ + authAtlasSpace, + taskId: BigInt(taskId), + subtaskId: BigInt(subtaskId), + submission: { List: { items } }, + }); + await toast.promise(call, { + loading: "Submitting list...", + success: "Submitted response.", + error: getErrorWithInfoToast("Failed to submit response."), + }); + + setSubmission(false); + await getSpaceTasks({ spaceId: spacePrincipal.toString(), unAuthAtlasSpace, dispatch, @@ -112,10 +185,8 @@ const GenericTask = ({ ); const rawState = Object.keys(submissionData?.state || {})[0] ?? null; - const validStates = ["Rejected", "WaitingForReview", "Accepted"] as const; type SubmissionState = typeof validStates[number]; - const submissionState = validStates.includes(rawState as SubmissionState) ? (rawState as SubmissionState) : null; @@ -161,19 +232,54 @@ const GenericTask = ({ )} {canSubmit && openSubmission && !disabled && ( -
-
-

Submit response:

- -
-
- -
-
- )} + <> + {answerFormatKey !== "List" ? ( +
+