Skip to content
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

Ephemeral Categories & Modal Confirmations #65

Merged
merged 19 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a89231f
feat: :label: add pendingCategories to addToolContext
JasonWarrenUK Dec 9, 2024
66f3b2f
feat: :construction: create new states in addToolTags
JasonWarrenUK Dec 9, 2024
21ba6e3
feat: :construction: add tool confirmation now uses modal
JasonWarrenUK Dec 9, 2024
77907c9
feat: :construction: use modals in all tool creation edge cases
JasonWarrenUK Dec 9, 2024
51ce4e6
Merge branch 'main' of https://github.com/fac30/things-we-do into fea…
JasonWarrenUK Dec 9, 2024
be41783
feat: :label: added click handler to NavLink to allow modals
JasonWarrenUK Dec 9, 2024
d33ddb5
Merge branch 'main' of https://github.com/fac30/things-we-do into fea…
JasonWarrenUK Dec 10, 2024
86d0080
feat: :construction: update `addToolTags`
JasonWarrenUK Dec 10, 2024
d58b05e
refactor: :recycle: update addToolInputs to use new database syntax
JasonWarrenUK Dec 10, 2024
f30e752
fix: :bug: make AddToolContext client-side
JasonWarrenUK Dec 10, 2024
1009a8f
fix: :bug: force correct dependency despite esLint
JasonWarrenUK Dec 10, 2024
26dd405
style: :lipstick: replace alerts with modals
JasonWarrenUK Dec 10, 2024
8b7fbd8
style: :art: restyle modals
JasonWarrenUK Dec 10, 2024
0a6a0e8
test: :white_check_mark:
JasonWarrenUK Dec 10, 2024
6d8704c
Accept NavLink reversion
JasonWarrenUK Dec 10, 2024
af8ce90
Accept NavLink reversion
JasonWarrenUK Dec 10, 2024
03f96c6
Accept NavLink reversion
JasonWarrenUK Dec 10, 2024
d2f1f45
Add noWrap to Modal
JasonWarrenUK Dec 10, 2024
c066943
Add noWrap to Modal
JasonWarrenUK Dec 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion __tests__/toolkitAdd.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ describe("AddToolInputs Component", () => {
fireEvent.click(submitButton);
});

expect(window.alert).toHaveBeenCalledWith("Invalid URL");
await waitFor(() => {
expect(screen.getByText("Invalid URL")).toBeInTheDocument();
});
});

it("inserts data into the database", async () => {
Expand Down
67 changes: 60 additions & 7 deletions src/app/toolkit/add-tool/components/AddToolHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,68 @@
"use client";

import NavLink from "@/ui/shared/NavLink";
import Button from "@/ui/shared/Button";
import Modal from "@/ui/shared/Modal";
import Spacer from "./Spacer";
import { ChevronLeftIcon as ChevronLeft } from "@heroicons/react/24/outline";
import { useAddToolForm } from "@/context/AddToolContext";
import { useState } from "react";
import { useRouter } from "next/navigation";

export default function Header() {
const [modalOpen, setModalOpen] = useState(false);
const router = useRouter();
const { formState } = useAddToolForm();

const hasFormValues = () => {
return (
formState.name !== "" ||
formState.description !== "" ||
formState.imageUrl !== "" ||
formState.infoUrl !== "" ||
formState.categories.length > 0
);
};

const handleNavigation = () => {
if (hasFormValues()) {
setModalOpen(true);
} else {
router.push("/toolkit");
}
};

const modalForwardButton = {
label: "Yes, leave",
action: () => {
setModalOpen(false);
router.push("/toolkit");
},
};

const modalBackButton = {
label: "No, stay",
action: () => setModalOpen(false),
};

return (
<div className="flex justify-around">
<NavLink Icon={ChevronLeft} destination="/toolkit" />
<h2 className="text-white text-2xl font-bold">Add Tool</h2>
<Spacer />
</div>
<>
<div className="flex justify-around">
<Button
label="< Go Back"
onClick={handleNavigation}
JasonWarrenUK marked this conversation as resolved.
Show resolved Hide resolved
className="bg-twd-secondary-purple text-white"
/>

<h2 className="text-white text-2xl font-bold">Add Tool</h2>

<Spacer />
</div>

<Modal
title="Are you sure you want to leave? All your changes will be lost."
modalOpen={modalOpen}
forwardButton={modalForwardButton}
backButton={modalBackButton}
/>
</>
);
}
231 changes: 151 additions & 80 deletions src/app/toolkit/add-tool/components/AddToolInputs.tsx
Original file line number Diff line number Diff line change
@@ -1,107 +1,178 @@
"use client";

import { useRouter } from "next/navigation";
import { useState } from "react";
import AddDescription from "./AddToolDescription";
import AddImageUrl from "./AddToolImageUrl";
import AddInfoUrl from "./AddToolInfoUrl";
import AddName from "./AddToolName";
import AddTags from "./AddToolTags";
import { AddToolProvider, useAddToolForm } from "@/context/AddToolContext";
import Modal from "@/ui/shared/Modal";
import Button from "@/ui/shared/Button";
import { useDatabase } from "@/context/DatabaseContext";
import { validateUrl } from "@/lib/utils/validateUrl";
import { useAddToolForm } from "@/context/AddToolContext";

function SubmitButton() {
const database = useDatabase();
export default function Inputs() {
const router = useRouter();
const { formState /* setFormState */ } = useAddToolForm();
const database = useDatabase();

const handleSubmit = async () => {
console.log(`Validating form with state: ${JSON.stringify(formState)}`);
const { formState } = useAddToolForm();

if (formState.categories.length === 0) {
alert("Please select at least one categories");
return;
}
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
const [unusedCategoryModalOpen, setUnusedCategoryModalOpen] = useState(false);
const [unusedCategory, setUnusedCategory] = useState([""]);
const [saveUnusedCategory, setSaveUnusedCategory] = useState(false);
const [categoryErrorModal, setCategoryErrorModal] = useState(false);
const [infoUrlErrorModal, setInfoUrlErrorModal] = useState(false);
const [imageUrlErrorModal, setImageUrlErrorModal] = useState(false);
const [submitErrorModal, setSubmitErrorModal] = useState(false);
const [submitErrorMessage, setSubmitErrorMessage] = useState("");

if (formState.infoUrl) {
const infoUrlValidation = validateUrl(formState.infoUrl, "Info URL");
if (!infoUrlValidation.isValid) {
console.error(`Info URL validation failed: ${infoUrlValidation.error}`);
alert(infoUrlValidation.error);
function SubmitButton() {
const handleSubmit = async () => {
console.log(`Validating form with state: ${JSON.stringify(formState)}`);

if (formState.categories.length === 0) {
setCategoryErrorModal(true);
return;
}
console.log(`Info URL validated successfully: ${infoUrlValidation.url}`);
}

if (formState.infoUrl) {
const infoUrlValidation = validateUrl(formState.infoUrl, "Info URL");
if (!infoUrlValidation.isValid) {
console.error(`Info URL validation failed: ${infoUrlValidation.error}`);
setSubmitErrorMessage(infoUrlValidation.error || "");
setInfoUrlErrorModal(true);
return;
}
console.log(`Info URL validated successfully: ${infoUrlValidation.url}`);
}

if (formState.imageUrl) {
const imageUrlValidation = validateUrl(formState.imageUrl, "Image URL");
if (!imageUrlValidation.isValid) {
console.error(`Image URL validation failed: ${imageUrlValidation.error}`);
setSubmitErrorMessage(imageUrlValidation.error || "");
setImageUrlErrorModal(true);
return;
}
console.log(`Image URL validated successfully: ${imageUrlValidation.url}`);
}

try {
for (const category of formState.pendingCategories) {
if (formState.categories.includes(category)) {
await database.addCategories(category);
} else {
await setUnusedCategoryModalOpen(true);
if (saveUnusedCategory) {
setUnusedCategory(unusedCategory.concat(category));
await database.addCategories(category);
}
}
}

if (formState.imageUrl) {
const imageUrlValidation = validateUrl(formState.imageUrl, "Image URL");
if (!imageUrlValidation.isValid) {
console.error(
`Image URL validation failed: ${imageUrlValidation.error}`
);
alert(imageUrlValidation.error);
return;
await database.addToDb("toolkit_items", {
id: crypto.randomUUID(),
name: formState.name,
categories: formState.categories,
description: formState.description,
checked: false,
infoUrl: formState.infoUrl,
imageUrl: formState.imageUrl,
timestamp: new Date().toISOString(),
});

console.log(`Created ${formState.name} in the database`);

setConfirmationModalOpen(true);
} catch (error) {
console.error("Error submitting form:", error);
setSubmitErrorMessage(error instanceof Error ? error.message : "Unknown error");
setSubmitErrorModal(true);
}
console.log(
`Image URL validated successfully: ${imageUrlValidation.url}`
);
}
};

return (
<Button label="Add Tool"
onClick={handleSubmit}
className="w-full mt-4 bg-twd-primary-purple"
/>
);
}

try {
console.log(`Inserting into database`);
return (
<div className="space-y-4 p-4">
<AddName />
<AddTags />
<AddDescription />
<AddImageUrl />
<AddInfoUrl />
<SubmitButton />

database.addToDb("toolkit_items", {
id: crypto.randomUUID(),
name: formState.name,
categories: formState.categories,
description: formState.description,
checked: false,
infoUrl: formState.infoUrl,
imageUrl: formState.imageUrl,
timestamp: new Date().toISOString(),
});
<Modal
title="Tool Added"
modalOpen={confirmationModalOpen}
forwardButton={{ label: "Continue",
action: () => {
setConfirmationModalOpen(false);
router.push("/toolkit");
}
}}
/>

console.log(`Created ${formState.name} in the database`);
<Modal
title="You created an unused category. What would you like to save?"
modalOpen={unusedCategoryModalOpen}
forwardButton={{ label: "Tool & Category",
action: () => {
setSaveUnusedCategory(true);
setUnusedCategoryModalOpen(false);
}
}}
backButton={{ label: "Tool",
action: () => {
setSaveUnusedCategory(false);
setUnusedCategoryModalOpen(false);
}
}}
/>

// Show success message and wait for user acknowledgment
const userAcknowledged = window.confirm(
`Successfully added ${formState.name} to your toolkit!\n\nClick OK to return to the toolkit.`
);
<Modal
title="Please select at least one category"
modalOpen={categoryErrorModal}
forwardButton={{
label: "OK",
action: () => setCategoryErrorModal(false)
}}
/>

if (userAcknowledged) {
router.push("/toolkit");
}
} catch (error) {
console.error("Error submitting form:", error);
alert(
`Failed to save tool: ${
error instanceof Error ? error.message : "Unknown error"
}`
);
}
};
<Modal
title={submitErrorMessage}
modalOpen={infoUrlErrorModal}
forwardButton={{
label: "OK",
action: () => setInfoUrlErrorModal(false)
}}
/>

return (
<Button
label="Add Tool"
onClick={handleSubmit}
className="w-full mt-4 bg-twd-primary-purple "
/>
);
}
<Modal
title={submitErrorMessage}
modalOpen={imageUrlErrorModal}
forwardButton={{
label: "OK",
action: () => setImageUrlErrorModal(false)
}}
/>

export default function Inputs() {
return (
<AddToolProvider>
<div className="space-y-4 p-4">
<AddName />
<AddTags />
<AddDescription />
<AddImageUrl />
<AddInfoUrl />
<SubmitButton />
</div>
</AddToolProvider>
<Modal
title={`Failed to save tool: ${submitErrorMessage}`}
modalOpen={submitErrorModal}
forwardButton={{
label: "OK",
action: () => setSubmitErrorModal(false)
}}
/>
</div>
);
}
}
Loading
Loading