Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions backend/src/controllers/products.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export const deleteProductById = async (req: AuthenticatedRequest, res: Response
if (!user) {
return res.status(404).json({ message: "User not found" });
}
if (!user.productList.includes(id)) {
if (!user.productList.includes(new mongoose.Types.ObjectId(id))) {
return res.status(400).json({ message: "User does not own this product" });
}

Expand Down Expand Up @@ -164,7 +164,7 @@ export const updateProductById = [
return res.status(404).json({ message: "User not found" });
}

if (!user.productList.includes(id)) {
if (!user.productList.includes(new mongoose.Types.ObjectId(id))) {
return res.status(400).json({ message: "User does not own this product" });
}

Expand All @@ -191,6 +191,7 @@ export const updateProductById = [
description: req.body.description,
images: finalImages,
timeUpdated: new Date(),
isSold: req.body.isSold,
},
{ new: true },
);
Expand Down
114 changes: 80 additions & 34 deletions backend/src/controllers/users.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { Request, Response } from "express";
import UserModel from "src/models/user";
import { getAuth } from "firebase-admin/auth";
import multer from "multer";
import { v4 as uuidv4 } from "uuid";
import { bucket } from "src/config/firebase";
import { initializeApp } from "firebase/app";
import { firebaseConfig } from "src/config/firebaseConfig";
import { getStorage, ref, getDownloadURL } from "firebase/storage";
import user from "src/models/user";

const upload = multer({ storage: multer.memoryStorage() });

export const getUsers = async (req: Request, res: Response) => {
try {
Expand All @@ -16,7 +25,7 @@ export const getUserById = async (req: Request, res: Response) => {
try {
const firebaseUid = req.params.firebaseUid;

const user = await UserModel.findOne({ firebaseUid: firebaseUid });
const user = await UserModel.findOne({ firebaseUid: firebaseUid }).populate("productList");

if (!user) {
return res.status(404).json({ message: "User not found" });
Expand Down Expand Up @@ -94,37 +103,74 @@ export const deleteUserById = async (req: Request, res: Response) => {
};

// id: firebase user id
export const updateUserById = async (req: Request, res: Response) => {
try {
const { displayName, deactivateAccount } = req.body;
const firebaseUid = req.params.firebaseUid;
const updatedUser = await UserModel.findOne({ firebaseUid: firebaseUid });
if (!updatedUser) {
throw new Error("User not found");
}

// Update fields if provided
if (displayName != undefined) {
updatedUser.displayName = displayName;
}

if (deactivateAccount != undefined) {
updatedUser.activeUser = false;
}

// Update Firebase user
const firebaseUser = await getAuth().getUserByEmail(updatedUser.userEmail);
if (firebaseUser) {
await getAuth().updateUser(firebaseUser.uid, { disabled: true });
export const updateUserById = [
upload.single("profilePic"),
async (req: Request, res: Response) => {
try {
const { displayName, deactivateAccount, biography } = req.body;
const firebaseUid = req.params.firebaseUid;
const updatedUser = await UserModel.findOne({ firebaseUid: firebaseUid });
if (!updatedUser) {
throw new Error("User not found");
}

let newProfilePic;

// Handle optional image uplaod
if (req.file) {
const fileName = `${uuidv4()}-${req.file.originalname}`;
const file = bucket.file(fileName);

await file.save(req.file.buffer, {
metadata: { contentType: req.file.mimetype },
});

const app = initializeApp(firebaseConfig);
const storage = getStorage(app);
newProfilePic = await getDownloadURL(ref(storage, fileName));
}

// Update fields if provided
if (displayName !== undefined) {
updatedUser.displayName = displayName;
}

if (deactivateAccount !== undefined) {
updatedUser.activeUser = false;
}

if (biography !== undefined) {
updatedUser.biography = biography;
}

if (newProfilePic) {
updatedUser.profilePic = newProfilePic;
}

// Update Firebase user
const firebaseUser = await getAuth().getUserByEmail(updatedUser.userEmail);
if (deactivateAccount) {
if (firebaseUser) {
await getAuth().updateUser(firebaseUser.uid, { disabled: true });
}
} else {
if (firebaseUser) {
await getAuth().updateUser(firebaseUser.uid, { disabled: false });
}
}

await updatedUser.save();

res.status(200).json({
message: "User successfully updated",
updatedUser,
});
} catch (error) {
console.error(error);
res.status(500).json({
message: "Error updating user",
error: error instanceof Error ? error.message : error,
});
}

await updatedUser.save();

res.status(200).json({
message: "User successfully updated",
updatedUser,
});
} catch (error) {
res.status(500).json({ message: "Error updating user", error });
}
};
},
];
4 changes: 4 additions & 0 deletions backend/src/models/product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ const productSchema = new Schema({
required: true,
},
images: [{ type: String }],
isSold: {
type: Boolean,
default: false,
}
});

export type Product = HydratedDocument<InferSchemaType<typeof productSchema>>;
Expand Down
17 changes: 12 additions & 5 deletions backend/src/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ const userSchema = new Schema({
type: Date,
required: true,
},
productList: {
type: [String],
required: true,
default: [],
},
productList: [
{
type: Schema.Types.ObjectId,
ref: "Product",
},
],
savedProducts: {
type: [String],
required: true,
Expand All @@ -30,6 +31,12 @@ const userSchema = new Schema({
type: String,
required: true,
},
profilePic: {
type: String,
},
biography: {
type: String,
},
});

export type User = HydratedDocument<InferSchemaType<typeof userSchema>>;
Expand Down
2 changes: 1 addition & 1 deletion backend/src/routes/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ router.get("/:firebaseUid", getUserById);
router.post("/", addUser);
router.post("/:userId/saved-products", toggleSavedProduct);
router.delete("/:id", deleteUserById);
router.patch("/:id", updateUserById);
router.patch("/:firebaseUid", updateUserById);

export default router;
Binary file added frontend/public/profile-pic-default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { AddProduct } from "../src/pages/AddProduct";
import { EditProduct } from "../src/pages/EditProduct";
import { IndividualProductPage } from "../src/pages/Individual-product-page";
import { PageNotFound } from "../src/pages/PageNotFound";
import { ProfilePage } from "../src/pages/ProfilePage";
import { EditProfile } from "../src/pages/EditProfile";
import FirebaseProvider from "../src/utils/FirebaseProvider";
import { SavedProducts } from "./pages/SavedProducts";

Expand Down Expand Up @@ -62,6 +64,22 @@ const router = createBrowserRouter([
path: "*",
element: <PageNotFound />,
},
{
path: "/profile/:id",
element: (
<PrivateRoute>
<ProfilePage />
</PrivateRoute>
),
},
{
path: "/edit-profile",
element: (
<PrivateRoute>
<EditProfile />
</PrivateRoute>
),
},
]);

export default function App() {
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/components/BackButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useNavigate } from "react-router-dom";

function BackButton() {
const navigate = useNavigate();

const handleBack = () => {
if (window.history.length > 1) {
navigate(-1);
} else {
navigate("/products");
}
};

return (
<button className="text-lg text-left mb-4 font-inter hover:underline" onClick={handleBack}>
&larr; Back
</button>
);
}

export default BackButton;
79 changes: 67 additions & 12 deletions frontend/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { faBars, faCartShopping, faUser, faXmark, faHeart } from "@fortawesome/free-solid-svg-icons";
import {
faBars,
faCartShopping,
faUser,
faXmark,
faDoorOpen,
faHeart,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useContext, useEffect, useRef, useState } from "react";
import { FirebaseContext } from "src/utils/FirebaseProvider";
Expand Down Expand Up @@ -71,29 +78,46 @@ export function Navbar() {
onClick={() => (window.location.href = "/saved-products")}
className="font-inter px-4 py-1 bg-transparent border-transparent rounded hover:bg-ucsd-darkblue transition-colors"
>
<FontAwesomeIcon
className="text-lg pr-2"
icon={faHeart}
aria-label="Heart Icon"
/>
<FontAwesomeIcon className="text-lg pr-2" icon={faHeart} aria-label="Heart Icon" />
Saved
</button>
</li>
<li>
{user ? (
<button
onClick={signOutFromFirebase}
onClick={() => {
if (user.uid) window.location.href = `/profile/${user.uid}`;
}}
className="font-inter px-4 py-1 bg-transparent border-transparent rounded hover:bg-ucsd-darkblue transition-colors"
>
<FontAwesomeIcon className="text-lg pr-2" icon={faUser} aria-label="User Icon" />
Your Profile
</button>
) : null}
</li>
<li>
{user ? (
<button
onClick={signOutFromFirebase}
className="font-inter px-4 py-1 bg-transparent border-transparent rounded hover:bg-ucsd-darkblue transition-colors"
>
<FontAwesomeIcon
className="text-lg pr-2"
icon={faDoorOpen}
aria-label="User Icon"
/>
Sign Out
</button>
) : (
<button
onClick={openGoogleAuthentication}
className="font-inter px-4 py-1 bg-transparent border-transparent rounded hover:bg-ucsd-darkblue transition-colors"
>
<FontAwesomeIcon className="text-lg pr-2" icon={faUser} aria-label="User Icon" />
<FontAwesomeIcon
className="text-lg pr-2"
icon={faDoorOpen}
aria-label="User Icon"
/>
Sign In
</button>
)}
Expand All @@ -119,7 +143,7 @@ export function Navbar() {
<button
hidden={user === null}
onClick={() => (window.location.href = "/products")}
className="font-inter w-full text-center px-4 py-2 bg-transparent border-transparent rounded hover:bg-ucsd-darkblue transition-colors"
className="font-inter w-full text-left px-4 py-2 bg-transparent border-transparent rounded hover:bg-ucsd-darkblue transition-colors"
>
<FontAwesomeIcon
className="text-lg pr-2"
Expand All @@ -129,21 +153,52 @@ export function Navbar() {
Products
</button>
</li>
<li className="mb-2">
<button
hidden={user === null}
onClick={() => (window.location.href = "/saved-products")}
className="font-inter w-full text-left px-4 py-2 bg-transparent border-transparent rounded hover:bg-ucsd-darkblue transition-colors"
>
<FontAwesomeIcon className="text-lg pr-2" icon={faHeart} aria-label="Heart Icon" />
Saved
</button>
</li>
<li className="mb-2">
{user ? (
<button
onClick={() => {
if (user.uid) window.location.href = `/profile/${user.uid}`;
}}
className="font-inter w-full text-left px-4 py-2 bg-transparent border-transparent rounded hover:bg-ucsd-darkblue transition-colors"
>
<FontAwesomeIcon className="text-lg pr-2" icon={faUser} aria-label="User Icon" />
Your Profile
</button>
) : null}
</li>
<li>
{user ? (
<button
onClick={signOutFromFirebase}
className="font-inter w-full text-center px-4 py-2 bg-transparent border-transparent rounded hover:bg-ucsd-darkblue transition-colors"
className="font-inter w-full text-left px-4 py-2 bg-transparent border-transparent rounded hover:bg-ucsd-darkblue transition-colors"
>
<FontAwesomeIcon className="text-lg pr-2" icon={faUser} aria-label="User Icon" />
<FontAwesomeIcon
className="text-lg pr-2"
icon={faDoorOpen}
aria-label="User Icon"
/>
Sign Out
</button>
) : (
<button
onClick={openGoogleAuthentication}
className="font-inter w-full text-center px-4 py-2 bg-transparent border-transparent rounded hover:bg-ucsd-darkblue transition-colors"
>
<FontAwesomeIcon className="text-lg pr-2" icon={faUser} aria-label="User Icon" />
<FontAwesomeIcon
className="text-lg pr-2"
icon={faDoorOpen}
aria-label="User Icon"
/>
Sign In
</button>
)}
Expand Down
Loading