diff --git a/src/components/AddTaskForm/AddTaskForm.component.scss b/src/components/AddTaskForm/AddTaskForm.component.scss new file mode 100755 index 0000000..df49153 --- /dev/null +++ b/src/components/AddTaskForm/AddTaskForm.component.scss @@ -0,0 +1,126 @@ +.c-AddTaskForm { + display: flex; + flex-direction: column; + gap: 1.2rem; + background: transparent; +} + +.c-AddTaskForm__field { + display: flex; + flex-direction: column; + gap: 0.3rem; +} + +label { + font-size: 1rem; + font-weight: 500; + color: #fff; +} + +input, +textarea, +select { + padding: 0.5rem 0.7rem; + border-radius: 6px; + border: 1px solid #444; + background: #23232b; + color: #fff; + font-size: 1rem; + outline: none; + transition: border 0.2s; +} +input:focus, +textarea:focus, +select:focus { + border-color: #7c3aed; +} + +.c-AddTaskForm__error { + color: #ff4d4f; + font-size: 0.95rem; + margin-bottom: 0.5rem; +} + +.c-AddTaskForm__submit { + padding: 0.6rem 1.2rem; + background: #7c3aed; + color: #fff; + border: none; + border-radius: 6px; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + box-shadow: 0 2px 8px rgba(124, 58, 237, 0.08); + transition: background 0.2s; +} +.c-AddTaskForm__submit:hover { + background: #5b21b6; +} + +.c-AddTaskForm__error { + color: #ff4d4f; + font-size: 0.95rem; + margin-top: 0.2rem; + margin-bottom: 0.2rem; + font-weight: 500; +} + +.c-AddTaskForm__actions { + display: flex; + gap: 1rem; + margin-top: 0.5rem; +} + +.c-AddTaskForm__submit { + flex: 1; + padding: 0.6rem 1.2rem; + background: #7c3aed; + color: #fff; + border: none; + border-radius: 6px; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + box-shadow: 0 2px 8px rgba(124, 58, 237, 0.08); + transition: background 0.2s, transform 0.1s; +} +.c-AddTaskForm__submit:active { + transform: scale(0.98); +} +.c-AddTaskForm__submit:disabled { + background: #5b21b6; + cursor: not-allowed; + opacity: 0.7; +} + +.c-AddTaskForm__cancel { + flex: 1; + padding: 0.6rem 1.2rem; + background: #23232b; + color: #fff; + border: 1px solid #444; + border-radius: 6px; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: background 0.2s, border 0.2s; +} +.c-AddTaskForm__cancel:hover { + background: #2d2d3a; + border-color: #7c3aed; +} + +@media (max-width: 500px) { + .c-AddTaskForm { + gap: 0.8rem; + } + .c-AddTaskForm__actions { + flex-direction: column; + gap: 0.5rem; + } + .c-AddTaskForm__submit, + .c-AddTaskForm__cancel { + font-size: 0.95rem; + padding: 0.5rem 0.8rem; + } +} diff --git a/src/components/AddTaskForm/AddTaskForm.test.ts b/src/components/AddTaskForm/AddTaskForm.test.ts new file mode 100755 index 0000000..079c7c3 --- /dev/null +++ b/src/components/AddTaskForm/AddTaskForm.test.ts @@ -0,0 +1,8 @@ +import React from "react" +import AddTaskForm from "./AddTaskForm" + +describe("AddTaskForm", () => { + it("renders without error", () => { + + }) +}) diff --git a/src/components/AddTaskForm/AddTaskForm.tsx b/src/components/AddTaskForm/AddTaskForm.tsx new file mode 100755 index 0000000..1742d45 --- /dev/null +++ b/src/components/AddTaskForm/AddTaskForm.tsx @@ -0,0 +1,129 @@ +import React, { useState, useRef, useEffect, useContext } from "react"; +import { API_ROUTES } from "@config/constants"; +import { Network } from "@utils"; +import { KaContext } from "src/contexts/KaContext"; +import "./AddTaskForm.component.scss" + +interface AddTaskFormProps { + statusOptions: StatusOption[]; + onCancel?: () => void; +} + +const AddTaskForm = (props: AddTaskFormProps): JSX.Element => { + const { statusOptions, onCancel } = props; + const { setModal, currentBoard, setCurrentBoard } = useContext(KaContext); + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [status, setStatus] = useState(statusOptions[0]?.value || ""); + const [error, setError] = useState(""); + const [submitting, setSubmitting] = useState(false); + const titleRef = useRef(null); + + useEffect(() => { + titleRef.current?.focus(); + }, []); + + useEffect(() => { + // If statusOptions change, reset status to first + setStatus(statusOptions[0]?.value || ""); + }, [statusOptions]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!title.trim()) { + setError("Title is required."); + return; + } + setError(""); + setSubmitting(true); + try { + const boardId = currentBoard?.id; + if (!boardId) throw new Error("No board selected"); + const url = API_ROUTES.TASKS.replace(":boardId", String(boardId)); + const payload = { + title: title.trim(), + description: description.trim(), + status, + }; + await Network.post(url, payload, { + headers: { Authorization: "Bearer testtoken" }, + }); + setSubmitting(false); + setTitle(""); + setDescription(""); + setStatus(statusOptions[0]?.value || ""); + setModal({ open: false, title: "", body: null }); + setCurrentBoard({ ...currentBoard }); // Trigger refresh in parent + } catch (err: any) { + setSubmitting(false); + setError(err.message || "Failed to add task"); + } + }; + + const handleCancel = () => { + setTitle(""); + setDescription(""); + setStatus(statusOptions[0]?.value || ""); + setError(""); + onCancel?.(); + setModal({ open: false, title: "", body: null }); + }; + + return ( +
+
+ + setTitle(e.target.value)} + required + ref={titleRef} + aria-describedby={error ? "task-title-error" : undefined} + /> + {error &&
{error}
} +
+
+ +