Skip to content

Commit

Permalink
Edit delete parts (#107)
Browse files Browse the repository at this point in the history
* ui changes

* homepage ui styling

* formatting

* adding edit/delete parts buttons

* edit and delete parts features + minor fixes

* formatting
  • Loading branch information
is2ac2 authored Jun 12, 2024
1 parent b5993e5 commit 3b8eb69
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 7 deletions.
2 changes: 2 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AlertQueue, AlertQueueProvider } from "hooks/alerts";
import { AuthenticationProvider, OneTimePasswordWrapper } from "hooks/auth";
import { ThemeProvider } from "hooks/theme";
import About from "pages/About";
import EditPartForm from "pages/EditPartForm";
import EditRobotForm from "pages/EditRobotForm";
import Home from "pages/Home";
import NotFound from "pages/NotFound";
Expand Down Expand Up @@ -39,6 +40,7 @@ const App = () => {
<Route path="/robots/add" element={<RobotForm />} />
<Route path="/parts/add" element={<PartForm />} />
<Route path="/robot/:id" element={<RobotDetails />} />
<Route path="/edit-part/:id" element={<EditPartForm />} />
<Route path="/edit-robot/:id" element={<EditRobotForm />} />
<Route path="/parts/" element={<Parts />} />
<Route path="/part/:id" element={<PartDetails />} />
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/hooks/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,36 @@ export class api {
}
}
}
public async deletePart(id: string | undefined): Promise<void> {
const s = id;
try {
await this.api.delete(`parts/delete/${id}/`);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error("Error deleting part:", error.response?.data);
throw new Error(
error.response?.data?.detail || "Error deleting part " + s,
);
} else {
console.error("Unexpected error:", error);
throw new Error("Unexpected error");
}
}
}
public async editPart(part: Part): Promise<void> {
const s = part.part_name;
try {
await this.api.post(`parts/edit-part/${part.part_id}/`, part);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error("Error editing part:", error.response?.data);
throw new Error(
error.response?.data?.detail || "Error editing part " + s,
);
} else {
console.error("Unexpected error:", error);
throw new Error("Unexpected error");
}
}
}
}
157 changes: 157 additions & 0 deletions frontend/src/pages/EditPartForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { humanReadableError } from "constants/backend";
import { useAlertQueue } from "hooks/alerts";
import { api, Image, Part } from "hooks/api";
import { useAuthentication } from "hooks/auth";
import React, { ChangeEvent, FormEvent, useEffect, useState } from "react";
import { Button, Col, Form, Row } from "react-bootstrap";
import { useNavigate, useParams } from "react-router-dom";

const EditPartForm: React.FC = () => {
const auth = useAuthentication();
const auth_api = new api(auth.api);

// Parse the ID from the URL.
const { id } = useParams();

// States.
const [message, setMessage] = useState<string | null>(null);
const [Part_name, setName] = useState<string>("");
const [Part_description, setDescription] = useState<string>("");
const [Part_images, setImages] = useState<Image[]>([]);
const [Part_id, setPartId] = useState<string>("");

const { addAlert } = useAlertQueue();

useEffect(() => {
const fetchPart = async () => {
try {
const PartData = await auth_api.getPartById(id);
setName(PartData.part_name);
setDescription(PartData.description);
setImages(PartData.images);
setPartId(PartData.part_id);
} catch (err) {
addAlert(humanReadableError(err), "error");
}
};
fetchPart();
}, [id]);

const handleImageChange = (
index: number,
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const { name, value } = e.target;
const newImages = [...Part_images];
newImages[index][name as keyof Image] = value;
setImages(newImages);
};
const navigate = useNavigate();
const handleAddImage = () => {
setImages([...Part_images, { url: "", caption: "" }]);
};

const handleRemoveImage = (index: number) => {
const newImages = Part_images.filter((_, i) => i !== index);
setImages(newImages);
};

const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (Part_images.length === 0) {
setMessage("Please upload at least one image.");
return;
}
const newFormData: Part = {
part_id: Part_id,
part_name: Part_name,
description: Part_description,
owner: "",
images: Part_images,
};
try {
await auth_api.editPart(newFormData);
setMessage(`Part edited successfully.`);
navigate(`/parts/your/`);
} catch (error) {
setMessage("Error adding part ");
}
};

return (
<Row>
<h2>Edit Part</h2>
{message && <p>{message}</p>}
<Form onSubmit={handleSubmit} className="mb-3">
Name:
<Form.Control
className="mb-3"
type="text"
placeholder="Part Name:"
onChange={(e) => {
setName(e.target.value);
}}
value={Part_name}
required
/>
Description:
<Form.Control
className="mb-3"
type="text"
placeholder="Part Description:"
onChange={(e) => {
setDescription(e.target.value);
}}
value={Part_description}
required
/>
Images:
{Part_images.map((image, index) => (
<Row key={index} className="mb-3">
<Col md={12}>
<Form.Control
className="mb-1"
type="text"
placeholder="Image URL"
name="url"
value={image.url}
onChange={(e) => handleImageChange(index, e)}
required
/>
<Form.Control
className="mb-1"
type="text"
placeholder="Image Caption"
name="caption"
value={image.caption}
onChange={(e) => handleImageChange(index, e)}
required
/>
</Col>
<Col md={12}>
<Button
className="mb-3"
size="sm"
variant="danger"
onClick={() => handleRemoveImage(index)}
>
Remove
</Button>
</Col>
</Row>
))}
<Col md={6}>
<Button className="mb-3" variant="primary" onClick={handleAddImage}>
Add Image
</Button>
</Col>
Submit:
<Col md={6}>
<Button type="submit">Confirm Changes!</Button>
</Col>
</Form>
</Row>
);
};

export default EditPartForm;
98 changes: 98 additions & 0 deletions frontend/src/pages/PartDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,21 @@ const PartDetails = () => {
const { addAlert } = useAlertQueue();
const auth = useAuthentication();
const auth_api = new api(auth.api);
const [userId, setUserId] = useState<string | null>(null);
const { id } = useParams();
const [show, setShow] = useState(false);
const [ownerEmail, setOwnerEmail] = useState<string | null>(null);
const [part, setPart] = useState<PartDetailsResponse | null>(null);
const [imageIndex, setImageIndex] = useState(0);
const [error, setError] = useState<string | null>(null);
const [showDelete, setShowDelete] = useState(false);

const handleClose = () => setShow(false);
const handleShow = () => setShow(true);

const handleShowDelete = () => setShowDelete(true);
const handleCloseDelete = () => setShowDelete(false);

useEffect(() => {
const fetchPart = async () => {
try {
Expand All @@ -57,6 +62,24 @@ const PartDetails = () => {

const navigate = useNavigate();

useEffect(() => {
if (auth.isAuthenticated) {
try {
const fetchUserId = async () => {
const user_id = await auth_api.currentUser();
setUserId(user_id);
};
fetchUserId();
} catch (err) {
if (err instanceof Error) {
setError(err.message);
} else {
setError("An unexpected error occurred");
}
}
}
}, [auth.isAuthenticated]);

useEffect(() => {
if (error) {
addAlert(error, "error");
Expand Down Expand Up @@ -219,6 +242,81 @@ const PartDetails = () => {
</ButtonGroup>
</Modal.Footer>
</Modal>
<>
{part.owner === userId && (
<>
<Row className="justify-content-end mt-2">
<Col md={3} sm={12}>
<Button
variant="primary"
size="lg"
style={{
backgroundColor: "light-green",
borderColor: "",
padding: "10px",
width: "100%",
}}
onClick={() => {
navigate(`/edit-part/${id}/`);
}}
>
Edit Part
</Button>
</Col>
<Col md={3} sm={12}>
<Button
variant="danger"
size="lg"
style={{
backgroundColor: "light-green",
borderColor: "",
padding: "10px",
width: "100%",
}}
onClick={() => {
handleShowDelete();
}}
>
Delete Part
</Button>
</Col>
</Row>
<Modal
show={showDelete}
onHide={handleCloseDelete}
fullscreen="md-down"
centered
size="lg"
scrollable
>
<Modal.Header closeButton>
<Modal.Title>
Are you sure you want to delete this part?
</Modal.Title>
</Modal.Header>
<Modal.Footer className="d-flex justify-content-start">
<Button
variant="danger"
onClick={async () => {
await auth_api.deletePart(id);
navigate(`/parts/your/`);
}}
>
Delete Part
</Button>
<Button
variant="outline-secondary"
onClick={() => {
handleCloseDelete();
}}
>
Cancel
</Button>
</Modal.Footer>
</Modal>
</>
)}
</>
</>
);
};
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/pages/PartForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { api, Image, Part } from "hooks/api";
import { useAuthentication } from "hooks/auth";
import React, { ChangeEvent, FormEvent, useState } from "react";
import { Button, Col, Form, Row } from "react-bootstrap";
import { useNavigate } from "react-router-dom";

const PartForm: React.FC = () => {
const auth = useAuthentication();
Expand Down Expand Up @@ -29,7 +30,7 @@ const PartForm: React.FC = () => {
const newImages = part_images.filter((_, i) => i !== index);
setImages(newImages);
};

const navigate = useNavigate();
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (part_images.length === 0) {
Expand All @@ -46,6 +47,7 @@ const PartForm: React.FC = () => {
try {
await auth_api.addPart(newFormData);
setMessage(`Part added successfully.`);
navigate("/parts/your/");
} catch (error) {
setMessage("Error adding Part ");
}
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/pages/RobotDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ const RobotDetails = () => {
};
fetchRobot();
}, [id]);

useEffect(() => {
if (auth.isAuthenticated) {
try {
Expand Down Expand Up @@ -343,7 +342,7 @@ const RobotDetails = () => {
variant="danger"
onClick={async () => {
await auth_api.deleteRobot(id);
navigate(`/robots/`);
navigate(`/robots/your/`);
}}
>
Delete Robot
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/YourParts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const YourParts = () => {
<>
<Breadcrumb>
<Breadcrumb.Item onClick={() => navigate("/")}>Home</Breadcrumb.Item>
<Breadcrumb.Item active>Parts</Breadcrumb.Item>
<Breadcrumb.Item active>Your Parts</Breadcrumb.Item>
</Breadcrumb>

<Row className="mt-5">
Expand Down
Loading

0 comments on commit 3b8eb69

Please sign in to comment.