Skip to content

Commit

Permalink
make_parts_page (#54)
Browse files Browse the repository at this point in the history
* make_parts_page

* formatting fixes

* shows owner email instead of id

* fix bg where user is created twice

* view your robots page

* view your parts

* part ids too

* formatting

* commutativity strikes again

move catchall to end

---------

Co-authored-by: Isaac Light <[email protected]>
  • Loading branch information
chennisden and is2ac2 authored Jun 7, 2024
1 parent 6b8c7d1 commit d40eb7d
Show file tree
Hide file tree
Showing 16 changed files with 490 additions and 10 deletions.
6 changes: 6 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ import About from "pages/About";
import Home from "pages/Home";
import NotFound from "pages/NotFound";
import PartDetails from "pages/PartDetails";
import PartForm from "pages/PartForm";
import Parts from "pages/Parts";
import RobotDetails from "pages/RobotDetails";
import RobotForm from "pages/RobotForm";
import Robots from "pages/Robots";
import YourParts from "pages/YourParts";
import YourRobots from "pages/YourRobots";
import { Container } from "react-bootstrap";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import "./App.css";
Expand All @@ -33,9 +36,12 @@ 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 />} />
<Route path="robots/your" element={<YourRobots />} />
<Route path="/parts/your" element={<YourParts />} />
<Route path="/404" element={<NotFound />} />
<Route path="*" element={<NotFoundRedirect />} />
</Routes>
Expand Down
53 changes: 53 additions & 0 deletions frontend/src/hooks/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ export class api {
constructor(api: AxiosInstance) {
this.api = api;
}
public async getUserById(userId: string | undefined): Promise<string> {
const response = await this.api.get(`/users/${userId}`);
return response.data.email;
}
public async getRobots(): Promise<Robot[]> {
try {
const response = await this.api.get("/robots/");
Expand All @@ -49,6 +53,22 @@ export class api {
}
}
}
public async getYourRobots(): Promise<Robot[]> {
try {
const response = await this.api.get("/robots/your/");
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
console.error("Error fetching robots:", error.response?.data);
throw new Error(
error.response?.data?.detail || "Error fetching robots",
);
} else {
console.error("Unexpected error:", error);
throw new Error("Unexpected error");
}
}
}
public async getRobotById(robotId: string | undefined): Promise<Robot> {
try {
const response = await this.api.get(`/robots/${robotId}`);
Expand Down Expand Up @@ -107,4 +127,37 @@ export class api {
}
}
}
public async getYourParts(): Promise<Part[]> {
try {
const response = await this.api.get("/parts/your/");
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
console.error("Error fetching parts:", error.response?.data);
throw new Error(error.response?.data?.detail || "Error fetching parts");
} else {
console.error("Unexpected error:", error);
throw new Error("Unexpected error");
}
}
}
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");
}
}
}
}
2 changes: 1 addition & 1 deletion frontend/src/hooks/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export const OneTimePasswordWrapper = ({
}
}
})();
}, [searchParams, navigate, setApiKey, api]);
}, []);

return <>{children}</>;
};
57 changes: 56 additions & 1 deletion frontend/src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,45 @@ const Home = () => {
</Row>
{isAuthenticated && (
<Row>
<Col sm={12}>
<Col md={6} sm={12}>
<Button
variant="primary"
size="lg"
style={{
backgroundColor: "light-purple",
borderColor: "black",
padding: "10px",
width: "100%",
}}
onClick={() => {
navigate("/robots/your/");
}}
>
View Your Robots
</Button>
</Col>
<Col md={6} sm={12}>
<Button
variant="primary"
size="lg"
style={{
backgroundColor: "dark-purple",
borderColor: "black",
padding: "10px",
width: "100%",
}}
onClick={() => {
navigate("/parts/your/");
}}
>
View Your Parts
</Button>
</Col>
</Row>
)}
{isAuthenticated && (
<Row>
<Col md={6} sm={12}>
<Button
variant="success"
size="lg"
Expand All @@ -49,6 +87,23 @@ const Home = () => {
Make a Robot
</Button>
</Col>
<Col md={6} 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
7 changes: 5 additions & 2 deletions frontend/src/pages/PartDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const PartDetails = () => {
const auth_api = new api(auth.api);
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);
Expand All @@ -37,6 +38,8 @@ const PartDetails = () => {
try {
const partData = await auth_api.getPartById(id);
setPart(partData);
const ownerEmail = await auth_api.getUserById(partData.owner);
setOwnerEmail(ownerEmail);
} catch (err) {
if (err instanceof Error) {
setError(err.message);
Expand Down Expand Up @@ -66,7 +69,7 @@ const PartDetails = () => {
description: part.description,
images: part.images,
};
const { part_name, owner, description, images } = response;
const { part_name, description, images } = response;

return (
<>
Expand All @@ -85,7 +88,7 @@ const PartDetails = () => {
<h1>{part_name}</h1>
<small className="text-muted">ID: {id}</small>
<br />
<i>{owner}</i>
<a href={"mailto:" + ownerEmail}>{ownerEmail}</a>
</Col>
</Row>
<hr />
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, 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;
13 changes: 12 additions & 1 deletion frontend/src/pages/Parts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,23 @@ const Parts = () => {
const auth = useAuthentication();
const auth_api = new api(auth.api);
const [partsData, setParts] = useState<Part[] | null>(null);
const [idMap, setIdMap] = useState<Map<string, string>>(new Map());
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetch_parts = async () => {
try {
const partsQuery = await auth_api.getParts();
setParts(partsQuery);
const ids = new Set<string>();
partsQuery.forEach((part) => {
ids.add(part.owner);
});
const idMap = await Promise.all(
Array.from(ids).map(async (id) => {
return [id, await auth_api.getUserById(id)];
}),
);
setIdMap(new Map(idMap.map(([key, value]) => [key, value])));
} catch (err) {
if (err instanceof Error) {
setError(err.message);
Expand Down Expand Up @@ -58,7 +69,7 @@ const Parts = () => {
<Card.Body>
<Card.Title>{part.part_name}</Card.Title>
<Card.Subtitle className="mb-2 text-muted">
{part.part_id}
{idMap.get(part.owner)}
</Card.Subtitle>
<Card.Text>{part.description}</Card.Text>
</Card.Body>
Expand Down
Loading

0 comments on commit d40eb7d

Please sign in to comment.