Skip to content

Commit

Permalink
edit and delete parts features + minor fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
is2ac2 committed Jun 12, 2024
1 parent 7300cc8 commit e2e2da6
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 12 deletions.
2 changes: 2 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import RobotDetails from "pages/RobotDetails";
import RobotForm from "pages/RobotForm";
import Robots from "pages/Robots";
import YourParts from "pages/YourParts";
import EditPartForm from "pages/EditPartForm";
import YourRobots from "pages/YourRobots";
import { Container } from "react-bootstrap";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
Expand All @@ -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, Bom, Image, Part, Robot } 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;
27 changes: 22 additions & 5 deletions frontend/src/pages/PartDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,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 @@ -225,8 +243,7 @@ const PartDetails = () => {
</Modal.Footer>
</Modal>
<>
{/* {part.owner === userId && ( */}
{part.owner === part.owner && (
{part.owner === userId && (
<>
<Row className="justify-content-end mt-2">
<Col md={3} sm={12}>
Expand All @@ -243,7 +260,7 @@ const PartDetails = () => {
navigate(`/edit-part/${id}/`);
}}
>
Edit Robot
Edit Part
</Button>
</Col>
<Col md={3} sm={12}>
Expand Down Expand Up @@ -281,8 +298,8 @@ const PartDetails = () => {
<Button
variant="danger"
onClick={async () => {
// await auth_api.deletePart(id);
navigate(`/parts/`);
await auth_api.deletePart(id);
navigate(`/parts/your/`);
}}
>
Delete Part
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
4 changes: 4 additions & 0 deletions store/app/crud/robots.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ async def delete_robot(self, robot_id: str) -> None:
table = await self.db.Table("Robots")
await table.delete_item(Key={"robot_id": robot_id})

async def update_part(self, part_id: str, part: Part) -> None:
await self.delete_part(part_id)
await self.add_part(part)

async def update_robot(self, robot_id: str, robot: Robot) -> None:
await self.delete_robot(robot_id)
await self.add_robot(robot)
29 changes: 26 additions & 3 deletions store/app/routers/part.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

parts_router = APIRouter()


logger = logging.getLogger(__name__)


Expand All @@ -28,7 +27,7 @@ async def list_your_parts(
try:
user_id = await crud.get_user_id_from_api_key(data.api_key)
if user_id is None:
raise HTTPException(status_code=401, detail="Must be logged in to view your robots")
raise HTTPException(status_code=401, detail="Must be logged in to view your parts")
total = await crud.list_parts()
user_parts = [part for part in total if str(part.owner) == str(user_id)]
return user_parts
Expand All @@ -41,6 +40,14 @@ async def get_part(part_id: str, crud: Annotated[Crud, Depends(Crud.get)]) -> Pa
return await crud.get_part(part_id)


@parts_router.get("/user/")
async def current_user(
crud: Annotated[Crud, Depends(Crud.get)], data: Annotated[ApiKeyData, Depends(get_api_key)]
) -> str | None:
user_id = await crud.get_user_id_from_api_key(data.api_key)
return str(user_id)


@parts_router.post("/add/")
async def add_part(
part: Part,
Expand All @@ -66,7 +73,23 @@ async def delete_part(
if part is None:
raise HTTPException(status_code=404, detail="Part not found")
user_id = await crud.get_user_id_from_api_key(data.api_key)
if part.owner != user_id:
if str(part.owner) != str(user_id):
raise HTTPException(status_code=403, detail="You do not own this part")
await crud.delete_part(part_id)
return True


@parts_router.post("/edit-part/{part_id}/")
async def edit_part(
part_id: str,
part: Part,
data: Annotated[ApiKeyData, Depends(get_api_key)],
crud: Annotated[Crud, Depends(Crud.get)],
) -> bool:
user_id = await crud.get_user_id_from_api_key(data.api_key)
if user_id is None:
raise HTTPException(status_code=401, detail="Must be logged in to edit a part")
part.owner = str(user_id)
part.part_id = part_id
await crud.update_part(part_id, part)
return True

0 comments on commit e2e2da6

Please sign in to comment.