Skip to content

Commit

Permalink
make_parts_page
Browse files Browse the repository at this point in the history
  • Loading branch information
is2ac2 committed Jun 7, 2024
1 parent 57f90c1 commit 66c1917
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 2 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 Robots from "pages/Robots";
import { Container } from "react-bootstrap";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import "./App.css";
import PartForm from "pages/PartForm";

const App = () => {
return (
Expand All @@ -33,6 +34,7 @@ const App = () => {
<Route path="/about" element={<About />} />
<Route path="/robots/" element={<Robots />} />
<Route path="/robots/add" element={<RobotForm />} />
<Route path="/parts/add" element = {<PartForm />} />
<Route path="/robot/:id" element={<RobotDetails />} />
<Route path="/parts/" element={<Parts />} />
<Route path="/part/:id" element={<PartDetails />} />
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/hooks/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,21 @@ export class api {
}
}
}
public async addPart(part: Part): Promise<void> {
const s = part.part_name;
try {
await this.api.post("/parts/add/", part);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error("Error adding part:" + part.part_name, error.response?.data);
throw new Error(
error.response?.data?.detail || "Error adding part " + s,
);
} else {
console.error("Unexpected error:", error);
throw new Error("Unexpected error");
}
}
}
}

21 changes: 21 additions & 0 deletions frontend/src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,27 @@ const Home = () => {
</Col>
</Row>
)}
{isAuthenticated && (
<Row>
<Col sm={12}>
<Button
variant="success"
size="lg"
style={{
backgroundColor: "light-green",
borderColor: "black",
padding: "10px",
width: "100%",
}}
onClick={() => {
navigate("/parts/add");
}}
>
Make a Part
</Button>
</Col>
</Row>
)}
</div>
);
};
Expand Down
130 changes: 130 additions & 0 deletions frontend/src/pages/PartForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { api, Bom, 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";

const PartForm: React.FC = () => {
const auth = useAuthentication();
const auth_api = new api(auth.api);
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 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 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_name: part_name,
description: part_description,
owner: "Bob",
images: part_images,
};
try {
await auth_api.addPart(newFormData);
setMessage(`Part added successfully.`);
} catch (error) {
setMessage("Error adding Part ");
}
};

return (
<Row>
<h2>Add a New 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">Add Part!</Button>
</Col>
</Form>
</Row>
);
};

export default PartForm;
4 changes: 4 additions & 0 deletions store/app/api/crud/robots.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ async def add_robot(self, robot: Robot) -> None:
table = await self.db.Table("Robots")
await table.put_item(Item=robot.model_dump())

async def add_part(self, part: Part) -> None:
table = await self.db.Table("Parts")
await table.put_item(Item=part.model_dump())

async def list_robots(self) -> list[Robot]:
table = await self.db.Table("Robots")
return [Robot.model_validate(robot) for robot in (await table.scan())["Items"]]
Expand Down
1 change: 0 additions & 1 deletion store/app/api/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,4 @@ class Part(BaseModel):
part_name: str
owner: str
description: str
robot_ids: set[str]
images: list[Image]
19 changes: 18 additions & 1 deletion store/app/api/routers/part.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import logging
from typing import Annotated, List

from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, HTTPException

from store.app.api.crypto import get_new_user_id
from store.app.api.db import Crud
from store.app.api.model import Part
from store.app.api.routers.users import ApiKeyData, get_api_key

parts_router = APIRouter()

Expand All @@ -22,3 +24,18 @@ async def list_parts(crud: Annotated[Crud, Depends(Crud.get)]) -> List[Part]:
@parts_router.get("/{part_id}")
async def get_part(part_id: str, crud: Annotated[Crud, Depends(Crud.get)]) -> Part | None:
return await crud.get_part(part_id)


@parts_router.post("/add/")
async def add_part(
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 add a part")
part.owner = str(user_id)
part.part_id = str(get_new_user_id())
await crud.add_part(part)
return True

0 comments on commit 66c1917

Please sign in to comment.