Skip to content

Commit

Permalink
Parts and robots (#46)
Browse files Browse the repository at this point in the history
* convert from using tokens to using api keys

* convert frontend to only use api keys instead of refresh tokens

* call the logout endpoint

* Add back google authentication

* fix lint

* add otp wrapper

* add config fields

* added robot backend

* hooked up robot backend to frontend

* Connected robots page and individual robots to the dynamodb local backend

* connected parts and robots frontend to local dynamodb backend

* added create robot

* minor fixes

* fix everything

* only add necessary cors stuff

* frnotend improvements for robocreation button

* change add robot path

* hooks/rob -> hooks/api

* delete unwanted dynamodb global var

* Fix dynamodb -> Crud

TODO: move crud methods into helper crud

* email_utils -> utils/email

* fix add_robot -> add/robot

* fix add_robot -> add/robot

* samesite lax

* insert actual user id

* fix all for real

* pass tests

---------

Co-authored-by: Isaac Light <[email protected]>
  • Loading branch information
chennisden and is2ac2 authored Jun 5, 2024
1 parent acc0475 commit 9f6032e
Show file tree
Hide file tree
Showing 15 changed files with 726 additions and 160 deletions.
2 changes: 2 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import NotFound from "pages/NotFound";
import PartDetails from "pages/PartDetails";
import Parts from "pages/Parts";
import RobotDetails from "pages/RobotDetails";
import RobotForm from "pages/RobotForm";
import Robots from "pages/Robots";
import { Container } from "react-bootstrap";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
Expand All @@ -35,6 +36,7 @@ const App = () => {
<Route path="/parts/" element={<Parts />} />
<Route path="/part/:id" element={<PartDetails />} />
<Route path="/404" element={<NotFound />} />
<Route path="/add/robot" element={<RobotForm />} />
<Route path="*" element={<NotFoundRedirect />} />
</Routes>
</Container>
Expand Down
134 changes: 134 additions & 0 deletions frontend/src/hooks/api.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import axios from "axios";

export interface PurchaseLink {
url: string;
price: number;
name: string;
}

export interface UsedBy {
name: string;
id: string;
stars: number;
}

export interface Part {
name: string;
owner: string;
description: string;
images: Image[];
part_id: string;
used_by: UsedBy[];
purchase_links: PurchaseLink[];
}

export interface Bom {
id: string;
name: string;
quantity: number;
price: number;
}

export interface Image {
caption: string;
url: string;
}

export interface Robot {
robot_id: string;
name: string;
description: string;
owner: string;
bom: Bom[];
images: Image[];
}

class api {
private api;

constructor(baseURL: string | undefined) {
this.api = axios.create({
baseURL,
headers: {
"Content-Type": "application/json",
},
withCredentials: true, // Ensure credentials are sent
});
}
public async getRobots(): Promise<Robot[]> {
try {
const response = await this.api.get("/robots");
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}`);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
console.error("Error fetching robot:", error.response?.data);
throw new Error(error.response?.data?.detail || "Error fetching robot");
} else {
console.error("Unexpected error:", error);
throw new Error("Unexpected error");
}
}
}
public async getPartById(partId: string | undefined): Promise<Part> {
try {
const response = await this.api.get(`/parts/${partId}`);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
console.error("Error fetching robot:", error.response?.data);
throw new Error(error.response?.data?.detail || "Error fetching robot");
} else {
console.error("Unexpected error:", error);
throw new Error("Unexpected error");
}
}
}
public async addRobot(robot: Robot): Promise<void> {
const s = robot.name;
try {
await this.api.post("/add/robot/", robot);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error("Error adding robot:", error.response?.data);
throw new Error(
error.response?.data?.detail || "Error adding robot " + s,
);
} else {
console.error("Unexpected error:", error);
throw new Error("Unexpected error");
}
}
}
public async getParts(): Promise<Part[]> {
try {
const response = await this.api.get("/parts");
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");
}
}
}
}

export default new api("http://127.0.0.1:8080/api");
31 changes: 25 additions & 6 deletions frontend/src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import { Card, Col, Row } from "react-bootstrap";
import { Button, Card, Col, Row } from "react-bootstrap";
import { useNavigate } from "react-router-dom";

const Home = () => {
const navigate = useNavigate();

return (
<Col className="pt-5">
<Row className="mb-5">
<div className="flex-column pt-5 gap-4" style={{ display: "flex" }}>
<Row className="mb-4">
<h1 className="display-4">robolist</h1>
<p className="lead">Buy and sell robots and robot parts</p>
</Row>
<Row>
<Col md={6} sm={12} className="mt-2">
<Col md={6} sm={12}>
<Card onClick={() => navigate(`/robots`)}>
<Card.Body>
<Card.Title>Robots</Card.Title>
<Card.Text>Buy and sell robot</Card.Text>
</Card.Body>
</Card>
</Col>
<Col md={6} sm={12} className="mt-2">
<Col md={6} sm={12}>
<Card onClick={() => navigate(`/parts`)}>
<Card.Body>
<Card.Title>Parts</Card.Title>
Expand All @@ -28,7 +28,26 @@ const Home = () => {
</Card>
</Col>
</Row>
</Col>
<Row>
<Col sm={12}>
<Button
variant="success"
size="lg"
style={{
backgroundColor: "light-green",
borderColor: "black",
padding: "10px",
width: "100%",
}}
onClick={() => {
navigate("/add/robot");
}}
>
Make a Robot
</Button>
</Col>
</Row>
</div>
);
};

Expand Down
74 changes: 38 additions & 36 deletions frontend/src/pages/PartDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState } from "react";
import api from "hooks/api";
import { useEffect, useState } from "react";
import {
Breadcrumb,
Button,
Expand All @@ -23,50 +24,51 @@ interface PartDetailsResponse {
const PartDetails = () => {
const { id } = useParams();
const [show, setShow] = useState(false);
const [part, setPart] = useState<PartDetailsResponse | null>(null);
const [imageIndex, setImageIndex] = useState(0);
const [error, setError] = useState<string | null>(null);

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

// This is a placeholder before the backend is hooked up.
useEffect(() => {
const fetchPart = async () => {
try {
const partData = await api.getPartById(id);
setPart(partData);
} catch (err) {
if (err instanceof Error) {
setError(err.message);
} else {
setError("An unexpected error occurred");
}
}
};
fetchPart();
}, [id]);

const navigate = useNavigate();

useEffect(() => {
if (error) {
navigate("/404"); // Redirect to a 404 page
}
}, [error, navigate]);

if (!part) {
return <p>Loading</p>;
}

const response: PartDetailsResponse = {
name: "RMD X8",
owner: "MyActuator",
description: `The RMD X8 is a quasi-direct drive motor from MyActuator.`,
images: [
{
url: "https://media.robolist.xyz/rmd_x8.png",
caption: "Actuator 1",
},
{
url: "https://media.robolist.xyz/rmd_x8.png",
caption: "Actuator 2",
},
{
url: "https://media.robolist.xyz/rmd_x8.png",
caption: "Actuator 3",
},
],
purchase_links: [
{
name: "RobotShop",
url: "https://www.robotshop.com/products/myactuator-rmd-x8-v3-can-bus-16-helical-mc-x-500-o-brushless-servo-driver",
price: 389,
},
],
used_by: [
{
name: "Stompy",
id: "1234",
stars: 5,
},
],
name: part.name,
owner: part.owner,
description: part.description,
images: part.images,
purchase_links: part.purchase_links,
used_by: part.used_by,
};

const { name, owner, description, images } = response;

const navigate = useNavigate();

return (
<>
<Breadcrumb>
Expand Down
Loading

0 comments on commit 9f6032e

Please sign in to comment.