Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into redo-auth
Browse files Browse the repository at this point in the history
  • Loading branch information
chennisden committed Jun 12, 2024
2 parents d8470f0 + 3b8eb69 commit 61ddb15
Show file tree
Hide file tree
Showing 13 changed files with 460 additions and 97 deletions.
17 changes: 16 additions & 1 deletion frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,20 @@ div.content {

html,
body {
transition: color 0.4s ease;
transition: color 0.1s ease;
}

/* .custom-button {
color: var(--button-color);
background-color: var(--button-bg-color);
border-color: var(--button-border-color);
padding: 10px;
width: 100%;
transition: all 0.3s ease;
}
.custom-button:hover {
color: var(--button-hover-color);
background-color: var(--button-hover-bg-color);
border-color: var(--button-hover-border-color);
} */
1 change: 1 addition & 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 } 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 Login from "pages/Login";
Expand Down
14 changes: 10 additions & 4 deletions frontend/src/components/auth/EmailAuthComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Row,
Tooltip,
} from "react-bootstrap";
import { CheckCircle } from "react-bootstrap-icons";

const isValidEmail = (email: string) => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
Expand Down Expand Up @@ -52,12 +53,17 @@ const EmailAuthComponent = () => {
<Row>
<Col>
<Row>
<Col>
<h3>Success!</h3>
<Col style={{ display: "flex", alignItems: "left" }}>
<CheckCircle size={28} color="green" />
<h3 style={{ marginLeft: "10px", marginBottom: "0px" }}>
Verification Email Sent!
</h3>
</Col>
</Row>
<Row>
<Col>Check your email for a login link.</Col>
<Row className="mb-3" style={{ alignItems: "center" }}>
<Col style={{ alignItems: "center" }}>
Check your email for a login link.
</Col>
</Row>
</Col>
</Row>
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 @@ -249,4 +249,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");
}
}
}
}
11 changes: 10 additions & 1 deletion frontend/src/hooks/theme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,25 @@ const THEME_KEY = "__THEME";
interface ThemeColors {
backgroundColor: string;
color: string;
buttonBorder: string;
buttonHover: string;
text_color: string;
}

const COLORS: { [key in Theme]: ThemeColors } = {
light: {
backgroundColor: "#ffffff",
color: "#201a42",
buttonBorder: "#0D6EFD",
buttonHover: "#0D6EFD",
text_color: "#f5f2ef",
},
dark: {
backgroundColor: "#000000",
color: "#f5f2ef",
buttonBorder: "#8AB9FE",
buttonHover: "#0D6EFD",
text_color: "#201a42",
},
};

Expand Down Expand Up @@ -66,7 +75,7 @@ export const ThemeProvider = (props: ThemeProviderProps) => {
document.body.classList.toggle("light-mode", theme === "light");
document.body.style.backgroundColor = COLORS[theme].backgroundColor;
document.body.style.color = COLORS[theme].color;
}, [theme]);
}, [theme, COLORS]);

return (
<ThemeContext.Provider
Expand Down
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;
Loading

0 comments on commit 61ddb15

Please sign in to comment.