Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
177 commits
Select commit Hold shift + click to select a range
dfd3622
setting up my pages
christina-baldwin Jun 23, 2025
00a0cc1
updating the readme
christina-baldwin Jun 23, 2025
f99520d
setting up routes
christina-baldwin Jun 23, 2025
b748d35
removing un-needed files and adding the notfound page
christina-baldwin Jun 23, 2025
e813557
setting up folders for authorisation and setting up the user model
christina-baldwin Jun 24, 2025
673b47c
setting up the auth.js route
christina-baldwin Jun 24, 2025
02224f9
setting up the auth.js middleware
christina-baldwin Jun 24, 2025
5c6a520
connecting to my mongo database
christina-baldwin Jun 24, 2025
ebb021b
fixing some file changes and npm installs
christina-baldwin Jun 24, 2025
d04b1fd
fixing dotenc config
christina-baldwin Jun 24, 2025
00de23a
setting up login and connecting it
christina-baldwin Jun 24, 2025
3f1c59c
setting up registration page
christina-baldwin Jun 24, 2025
8e44b63
typo fix
christina-baldwin Jun 24, 2025
368ede3
todo message added
christina-baldwin Jun 24, 2025
0178461
setting up tailwind
christina-baldwin Jun 24, 2025
a344b31
removing unnecessary imports
christina-baldwin Jun 24, 2025
bbf7fd4
basic styling
christina-baldwin Jun 24, 2025
f1b12fd
line height
christina-baldwin Jun 24, 2025
d91d17f
adding a logo
christina-baldwin Jun 24, 2025
644ce5f
basic styling for login
christina-baldwin Jun 25, 2025
36a580c
basic styling for registration
christina-baldwin Jun 25, 2025
c5b71f4
todo notes
christina-baldwin Jun 25, 2025
c772ba4
adding register and login validation on the frontned
christina-baldwin Jun 25, 2025
93c057b
validation messages styling
christina-baldwin Jun 25, 2025
e4324ee
secret key issues and authorisation
christina-baldwin Jun 25, 2025
bddd791
so my dahsboard shows
christina-baldwin Jun 25, 2025
1b15cf0
adding the nav
christina-baldwin Jun 26, 2025
182796f
adding a userinfo route
christina-baldwin Jun 27, 2025
44c1a26
fixing some import errors
christina-baldwin Jun 30, 2025
c40986f
getting the name of the user to show in the nav
christina-baldwin Jun 30, 2025
4847f26
getting the logout to work
christina-baldwin Jun 30, 2025
0c2c209
creating and adding a sidebar
christina-baldwin Jun 30, 2025
5944915
organising the structure of my dashboard
christina-baldwin Jun 30, 2025
f82839d
setting up photo upload routes on the backend
christina-baldwin Jul 1, 2025
9504847
file upload on the frontend from and existing file
christina-baldwin Jul 1, 2025
fbc0d20
adding the sidebar to the photo upload page
christina-baldwin Jul 1, 2025
a15d46d
adding the sidebar to the other pages
christina-baldwin Jul 1, 2025
6fd657a
adding a desk schema
christina-baldwin Jul 1, 2025
9388c00
saving a new desk to the database
christina-baldwin Jul 1, 2025
6eebb67
showing latest upload on the uploads page
christina-baldwin Jul 2, 2025
9d187ea
adding delete photo functionality on backend and frontend
christina-baldwin Jul 2, 2025
40ebdf4
editing notes and removing unused imports
christina-baldwin Jul 3, 2025
6af8e44
showing the latest upload preview on my dashboard landing page
christina-baldwin Jul 4, 2025
354f561
renaming
christina-baldwin Jul 4, 2025
3cd2db5
adding a conditional for when there are suggestions already generated
christina-baldwin Jul 4, 2025
38545e6
setting up a button to take a photo
christina-baldwin Jul 7, 2025
6080736
changing back and fixing wording
christina-baldwin Jul 7, 2025
f9c5e4a
setting up the settings page structure
christina-baldwin Jul 7, 2025
13b3ebb
starting the editing functionality. TODO patch on the backend
christina-baldwin Jul 7, 2025
ce13893
patch on the backend and getting it to work with change name
christina-baldwin Jul 8, 2025
1c37bc1
styling changes
christina-baldwin Jul 8, 2025
d111287
adding the change email functionality
christina-baldwin Jul 9, 2025
dcea91e
password change
christina-baldwin Jul 9, 2025
e1009d8
making password change clearer
christina-baldwin Jul 11, 2025
15bcbc9
styling the settings
christina-baldwin Jul 11, 2025
c24c3f9
setting up the ai gnerated suggestions backend route
christina-baldwin Jul 14, 2025
c80bee4
setting up so the ai generates suggestions when clicking the button o…
christina-baldwin Jul 16, 2025
13e11e5
having the desk photo and desk suggestions show on the frontend
christina-baldwin Jul 16, 2025
c747d64
adding a message to be disoplayed and a nnote to maybe change it later
christina-baldwin Jul 18, 2025
0ccd9de
updating my ai text prompt
christina-baldwin Jul 21, 2025
7af72b7
changing the way the ai prompt returns text
christina-baldwin Jul 22, 2025
dbac9f3
adding an input to put in your personal desk problems for the ai to a…
christina-baldwin Jul 22, 2025
eef6cdd
making sidabar titles clearer
christina-baldwin Jul 22, 2025
2d370c8
also displaying old suggestions
christina-baldwin Jul 22, 2025
4b43467
spacing of suggestions
christina-baldwin Jul 22, 2025
6705278
adding notes and placeholders for new features
christina-baldwin Jul 22, 2025
a5dd3a4
styling
christina-baldwin Jul 22, 2025
8ac446f
styling & addinbg placeholder to add/edit problems after upload
christina-baldwin Jul 23, 2025
73200a6
getting popup functionility to work
christina-baldwin Jul 23, 2025
adcf165
hide/load older suggestions
christina-baldwin Jul 23, 2025
7aba243
spacing under image
christina-baldwin Jul 23, 2025
f3999e0
adding a patch request and frontend functionality for the add/edit de…
christina-baldwin Jul 23, 2025
d07dbdb
button wording that makes more sense
christina-baldwin Jul 23, 2025
667b13c
starting the main styling
christina-baldwin Jul 24, 2025
32d5026
figuring out styling for the login page
christina-baldwin Jul 25, 2025
605a823
styling registration to lok like login
christina-baldwin Jul 25, 2025
74a2ddb
messing around with base colours and styling
christina-baldwin Jul 28, 2025
33c7129
finialising design for landing page
christina-baldwin Jul 29, 2025
b2f7a29
setting up colour classes in tailwind for base theme colours
christina-baldwin Jul 31, 2025
773f044
removing old config
christina-baldwin Jul 31, 2025
16111d7
adding the font families in the theme
christina-baldwin Aug 1, 2025
32701e5
styling the login page
christina-baldwin Aug 1, 2025
39a3c21
styling registration page
christina-baldwin Aug 1, 2025
97b7c5f
styling the nav
christina-baldwin Aug 1, 2025
fc116f4
sidebar styling
christina-baldwin Aug 1, 2025
92beec0
styling the dashboard
christina-baldwin Aug 1, 2025
8b16f44
styling the upload page
christina-baldwin Aug 6, 2025
cf370b2
styling the edit problems view
christina-baldwin Aug 6, 2025
08fad6a
fixed width and height for dashboard, styling the suggestions to match
christina-baldwin Aug 12, 2025
7449bb0
styling the settings with the same theme
christina-baldwin Aug 12, 2025
9e6147d
setting up placeholders for dashboard home
christina-baldwin Aug 12, 2025
0770f27
updating the style of the user message so its more visible
christina-baldwin Aug 13, 2025
1850d7f
delete confirmation popu
christina-baldwin Aug 14, 2025
7c61e3a
showing the last logged in date/time stamp
christina-baldwin Aug 14, 2025
c5cb64f
formatting so the last logged in reads well for users
christina-baldwin Aug 14, 2025
ec84c9c
displaying the days since desk photo was uploaded
christina-baldwin Aug 14, 2025
3f89ef8
conditional chaining for when desk photo was uploaded to stop an erro…
christina-baldwin Aug 14, 2025
85df1ae
creating and displaying my quick summary for my exisitng hobby desk
christina-baldwin Aug 14, 2025
fa37733
styling the summary
christina-baldwin Aug 14, 2025
b47360d
summary title
christina-baldwin Aug 14, 2025
4f9b116
implementing drag and drop for photo upload
christina-baldwin Aug 14, 2025
b7d1ad2
wording
christina-baldwin Aug 14, 2025
35e730d
editying the landing page blurb
christina-baldwin Aug 14, 2025
f7349b8
making login and register look cohesive
christina-baldwin Aug 14, 2025
83c566d
changing the focus ring colours
christina-baldwin Aug 14, 2025
84bcee2
wording
christina-baldwin Aug 14, 2025
599ee6e
making the ai prompt more specific
christina-baldwin Aug 15, 2025
03d05e0
changing some phrasing
christina-baldwin Aug 15, 2025
6379fa4
cahanging naming
christina-baldwin Aug 15, 2025
39ab7b0
adding a generating suggestions message
christina-baldwin Aug 15, 2025
e84e136
adding validation to only allow image files with drag-drop
christina-baldwin Aug 15, 2025
3815dbd
rewriting the tips popup so its a bit more instructional
christina-baldwin Aug 15, 2025
6650163
moving the help popup prompt further up
christina-baldwin Aug 15, 2025
43f0bca
adding a lightbulb icon to my tips popup link
christina-baldwin Aug 15, 2025
0f79133
making titles bigger
christina-baldwin Aug 15, 2025
233e491
replacing with the onrender url
christina-baldwin Aug 18, 2025
9031301
added dist to gitignore
christina-baldwin Aug 18, 2025
912e3d8
displaying user messages better
christina-baldwin Aug 18, 2025
161d4c9
protecting my non-public routes
christina-baldwin Aug 18, 2025
dec46f8
fixing list elements to be more acessible
christina-baldwin Aug 18, 2025
1543a54
changing the focus colours to match the theme
christina-baldwin Aug 18, 2025
f4c79ac
resposnive design for landing page
christina-baldwin Aug 20, 2025
17928c7
responsive design for login
christina-baldwin Aug 20, 2025
b1e451c
responsive design for register page
christina-baldwin Aug 20, 2025
73905ac
responsive styling the sidebar
christina-baldwin Aug 20, 2025
4650595
responsive design for dashboard home page
christina-baldwin Aug 20, 2025
335db3e
responsive design for the upload page
christina-baldwin Aug 20, 2025
af6cf92
responsive design for the suggestions page
christina-baldwin Aug 20, 2025
bfde499
responsive design for settings page
christina-baldwin Aug 20, 2025
1ca6177
adding a footer
christina-baldwin Aug 20, 2025
55452a7
fixing some styling
christina-baldwin Aug 20, 2025
5605b0f
isloading state for register and login
christina-baldwin Aug 21, 2025
977d004
fixing the saving error when editing problems
christina-baldwin Aug 21, 2025
a289623
more specific validation
christina-baldwin Aug 21, 2025
5ad5c53
styling and hero img change
christina-baldwin Aug 22, 2025
0e6a68a
making sure problems is saved on backedn at upload
christina-baldwin Aug 22, 2025
c9b8d47
some changes (temporary, will split this up)
christina-baldwin Aug 22, 2025
c74821f
setting up for refactoring the upload page into components
christina-baldwin Aug 22, 2025
2fe0549
setting up zustand for my latest desk
christina-baldwin Aug 22, 2025
78241ff
fixing the zustand import
christina-baldwin Aug 22, 2025
9553861
creating and incorporating the UploaDesk component
christina-baldwin Aug 22, 2025
625ecfc
finalising and incoporating the LatestDesk component into Upload page
christina-baldwin Aug 22, 2025
d87e3b3
removing un-needed code
christina-baldwin Aug 22, 2025
8260fe0
removing comments
christina-baldwin Aug 22, 2025
f5c9f0f
using the deskstore on the dashboard home
christina-baldwin Aug 25, 2025
e6faa1f
changin the wording
christina-baldwin Aug 25, 2025
c74ba55
using the motion library to animate
christina-baldwin Aug 25, 2025
5572905
removing unwanted notes
christina-baldwin Aug 25, 2025
83ecf47
fixing some responsive design widths
christina-baldwin Aug 25, 2025
3284b5c
internal scroll in componente
christina-baldwin Aug 25, 2025
4b5c081
closing popup on escape and when clicking out of the popup, fixing po…
christina-baldwin Aug 27, 2025
8a0a64d
showing all older suggestions and fixing bug when suggestions for lat…
christina-baldwin Aug 27, 2025
f9c9092
adding dates for when desks were uploaded
christina-baldwin Aug 27, 2025
fc7ac00
adding a filter for older desks
christina-baldwin Aug 27, 2025
7c268fa
link that goes back to upload from suggestions if user wants to gener…
christina-baldwin Aug 27, 2025
39596a2
validation on each form input
christina-baldwin Aug 27, 2025
a2662a9
adding an isUploading state
christina-baldwin Aug 28, 2025
0e2ca93
adding notes to help with my demo
christina-baldwin Aug 28, 2025
c37cbac
adding a message for when the desk is deleted successfully
christina-baldwin Aug 28, 2025
101e0c6
adding text if suggestions arent available for older desks
christina-baldwin Aug 28, 2025
2044657
fixing some styling and removing notes
christina-baldwin Aug 28, 2025
9444103
typo fix
christina-baldwin Aug 29, 2025
9f1fdc9
updating so image shows on netlify
christina-baldwin Aug 29, 2025
48cbdfa
removing svg
christina-baldwin Aug 29, 2025
3575d5b
fixing padding
christina-baldwin Aug 29, 2025
0be7473
smaller gap in settings
christina-baldwin Aug 29, 2025
9e6d6cf
Update README.md
christina-baldwin Aug 31, 2025
5c774ef
adding a favicon
christina-baldwin Sep 13, 2025
80c3162
Merge branch 'main' of https://github.com/christina-baldwin/desk-forge
christina-baldwin Sep 13, 2025
c61f880
adding an ai call limit
christina-baldwin Sep 20, 2025
d2ca0ce
making the ai call limit 10
christina-baldwin Sep 20, 2025
97592e2
changed the ai call limit message
christina-baldwin Sep 20, 2025
b29c7b6
adding a max number of accounts that are allowed
christina-baldwin Sep 20, 2025
bbbc00d
logs and not allowing certain file formats
christina-baldwin Oct 18, 2025
fee03fd
fix
christina-baldwin Oct 18, 2025
8f1393b
redirects to login after successful registration
christina-baldwin Oct 18, 2025
5901728
adding stricter checks for file sizes and types
christina-baldwin Nov 1, 2025
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*

package-lock.json
package-lock.json

dist
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"postman.settings.dotenv-detection-notification-visibility": false
}
31 changes: 26 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
# Final Project
# Final Bootcamp Project - DeskForge

Replace this readme with your own information about your project.
The assignment was to create a full-stack React app that solves a clear problem.

Start by briefly describing the assignment in a sentence or two. Keep it short and to the point.
I decided to create an app that helps hobbyists, specifically warhammer 40k, to organise their workspaces by uploading a photo of their desk space and getting AI-generated suggestions.

## The problem

Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next?
To create a web app that allows tearget users (warhammer 40k hobbyists) to upload a phot of their desk, and generate suggestions for improvement. Users can must also be able keep track of previous desks through a suggestions history.

Started by sketching out wireframes to udnerstand general structure and functionality.

Built the project feature-by-feature starting with setting up the backend for a particular feature, then building the frontend, dealt with bugs for that specific feature then repeated for the nex feature until the app was built.

Handling the multiple states needed for the upload and generate states for a desk was a challenge that was solved with the help of creating separate components and using global state management.

Routing is handled by react router.

The AI integration was an integral part of the project and that was solved using OpenAI's api as it is very well-documentated and straight-forward to integrate.

The next steps I would like to implement in my own time is to create a new feature where the location of changes is indicated on the image, to migrate it to React Native for mobile, and use Tamagui for the design so it is cohesive across all platforms.


## The code

The code is separated into a backend and frontend folder.

The backend folder has the main server file with the ai integration, authorisation, and upload routes in sperate files. Middleware is in a separate folder as is the desk and user monogdb models.

The frontend folder has the main file while components and pages are in 2 seperate folders, as is the latestdesk global state. Pages indicate the sperate pages you can navigate to while components are sections within said pages.

## View it live

Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about.
Netfliy link: https://desk-forge.netlify.app/
26 changes: 26 additions & 0 deletions backend/middlewares/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import jwt from "jsonwebtoken";
import dotenv from "dotenv";
dotenv.config();

const JWT_SECRET = process.env.JWT_SECRET;

const authenticate = (req, res, next) => {
const authHeader = req.headers.authorization;

if (!authHeader)
return res.status(401).json({ message: "Authorisation header missing" });

const token = authHeader.split(" ")[1];

if (!token) return res.status(401).json({ message: "Token missing" });

try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ message: "Invalid or expired token" });
}
};

export default authenticate;
32 changes: 32 additions & 0 deletions backend/models/Desk.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import mongoose from "mongoose";

const deskSchema = new mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "User",
},
imageUrl: {
type: String,
required: true,
},
createdAt: {
type: Date,
default: Date.now,
},
problems: { type: String, default: "" },
suggestions: [
{
title: { type: String, required: true },
description: { type: String, required: true },
},
],
summary: { type: String, default: "" },
},
{ timestamps: true }
);

const Desk = mongoose.model("Desk", deskSchema);

export default Desk;
33 changes: 33 additions & 0 deletions backend/models/User.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import bcrypt from "bcrypt";
import mongoose from "mongoose";

const userSchema = new mongoose.Schema(
{
name: { type: String, required: true, minlength: 3 },
email: { type: String, required: true, unique: true, lowercase: true },
password: { type: String, required: true, minlength: 6 },
lastLogin: { type: Date, default: Date.now },
previousLogin: { type: Date },
totalAiCalls: { type: Number, default: 0 },
},
{ timestamps: true }
);

userSchema.pre("save", async function (next) {
if (!this.isModified("password")) return next();

try {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
} catch (error) {
next(error);
}
});

userSchema.methods.comparePassword = async function (candidatePassword) {
return bcrypt.compare(candidatePassword, this.password);
};

const User = mongoose.model("User", userSchema);
export default User;
15 changes: 11 additions & 4 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@
"@babel/core": "^7.17.9",
"@babel/node": "^7.16.8",
"@babel/preset-env": "^7.16.11",
"bcrypt": "^6.0.0",
"cloudinary": "^2.7.0",
"cors": "^2.8.5",
"express": "^4.17.3",
"mongoose": "^8.4.0",
"nodemon": "^3.0.1"
"dotenv": "^16.5.0",
"express": "^4.21.2",
"express-list-endpoints": "^7.1.1",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.16.0",
"multer": "^2.0.1",
"nodemon": "^3.0.1",
"openai": "^5.9.0"
}
}
}
145 changes: 145 additions & 0 deletions backend/routes/ai.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import express from "express";
import Desk from "../models/Desk.js";
import User from "../models/User.js";
import authenticate from "../middlewares/auth.js";
import OpenAI from "openai";
import dotenv from "dotenv";
import path from "path";

dotenv.config();

const router = express.Router();

const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});

router.post("/desks/:id/generate", authenticate, async (req, res) => {
const deskId = req.params.id;
const allowedExtensions = [".jpg", ".jpeg", ".png", ".gif"];
console.log(
"Generate suggestions request for desk ID:",
deskId,
"by user:",
req.user.id
);

try {
const desk = await Desk.findOne({ _id: deskId, userId: req.user.id });
if (!desk) {
console.log("Desk not found for this user:", req.user.id);
return res
.status(404)
.json({ success: false, message: "Desk not found" });
}
console.log("Desk found:", desk._id, "Image URL:", desk.imageUrl);

const fileExtension = path
.extname(desk.imageUrl.split("?")[0])
.toLowerCase();

if (!allowedExtensions.includes(fileExtension)) {
return res.status(400).json({
success: false,
message:
"Cannot generate suggestions: unsupported file type. Please upload JPG, PNG, or GIF.",
});
}

const user = await User.findById(req.user.id);
if (!user)
return res
.status(404)
.json({ success: false, message: "User not found" });

console.log(
"User fetched:",
user._id,
"Email:",
user.email,
"Total AI calls:",
user.totalAiCalls || 0
);

if ((user.totalAiCalls || 0) >= 10) {
return res.status(403).json({
success: false,
message:
"You’ve reached your AI call limit. Please contact us @ christina.baldwin13@yahoo.com",
});
}

const response = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "user",
content: [
{
type: "text",
text: "Use the uploaded image and the below desk problems, if available, to give 5 suggestions to improve my hobby desk setup, specifically for warhammer 40k. Reference specific products if applicable. Reference specific points on the desk where your suggested items would be placed. For each suggestion, provide coordinates using x and y to visually represent where that specific suggestion should be placed assuming x will always be a maximum value of 440px and y being 267px. Reference things on the desk that are not useful and can be removed. Return them as a JSON array of objects, each with a 'title', 'description', 'x' and 'y'. No markdown formatting, no asterisks.",
},
{
type: "text",
text: `Desk problems I'd like fixed: ${desk.problems}`,
},
{ type: "image_url", image_url: { url: desk.imageUrl } },
],
},
],
max_tokens: 500,
});

const aiMessage = response.choices[0]?.message?.content || "";

console.info("aiMessage: ", aiMessage);

try {
desk.suggestions = JSON.parse(aiMessage);
} catch (parseError) {
console.error(
"Failed to parse AI message:",
parseError,
"aiMessage:",
aiMessage
);
return res
.status(500)
.json({ success: false, message: "Invalid AI response format" });
}

const summaryPrompt = `Here are the desk problems ${
desk.problems
} and here are the ai-generated suggestions ${desk.suggestions
.map((s) => `${s.title}: ${s.description}`)
.join(
"\n"
)}. Using these, write a short 1 sentence summary highlighting only the key issues and solutions for my warhammer hobby desk setup`;

try {
const summaryResponse = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: summaryPrompt }],
max_tokens: 150,
});
desk.summary = summaryResponse.choices[0]?.message?.content || "";
} catch (summaryError) {
console.error("OpenAI summary error:", summaryError);
desk.summary = "";
}

user.totalAiCalls = (user.totalAiCalls || 0) + 1;
await user.save();

await desk.save();

res.status(200).json({ success: true, suggestions: desk.suggestions });
} catch (error) {
console.error("Generate suggestions error:", error);
res
.status(500)
.json({ success: false, message: "Failed to generate suggestions" });
}
});

export default router;
Loading