Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
395ef44
installing dependencies in backend
marinalendt Feb 9, 2026
0fb0d50
adding listendpoints from express
marinalendt Feb 9, 2026
56f316c
creating sign-up
marinalendt Feb 9, 2026
9b51e74
installing bcrypt, dotenv, list-endpoints
marinalendt Feb 9, 2026
59cea3f
creating authMiddleware
marinalendt Feb 9, 2026
8ed0112
adding files
marinalendt Feb 9, 2026
4202a5c
creating mongoose-models
marinalendt Feb 10, 2026
db03428
adding model for activity and dailyplan
marinalendt Feb 10, 2026
308c1d2
adding endpoints to activitiesRoutes
marinalendt Feb 10, 2026
87dad9d
adding seeding data
marinalendt Feb 10, 2026
b50fbee
adding seeding data
marinalendt Feb 10, 2026
2508cb8
adding endpoints for dailyplan
marinalendt Feb 11, 2026
c91ffa3
adding files and folders
marinalendt Feb 11, 2026
ec90e4d
adding api-connection for activities, for get, post, patch and delet…
marinalendt Feb 11, 2026
d776ba5
adding authenticateUser to activitiesRoutes
marinalendt Feb 11, 2026
4dad7b5
adding logic to LoginForm + adding styled components
marinalendt Feb 11, 2026
25ac814
creating routes in app.jsx
marinalendt Feb 11, 2026
6838382
connecting the pages to the routes
marinalendt Feb 11, 2026
cfebf54
added a navbar for the logout button
marinalendt Feb 11, 2026
ef6693e
created signupform
marinalendt Feb 11, 2026
b955ceb
created protectedroute so that you cant reach the dailyplan-site with…
marinalendt Feb 11, 2026
84701f6
adding loadingActivities, toggleActivities and also totalEnergy
marinalendt Feb 11, 2026
92e703d
adding function to show and choose energylevel per day, and also to a…
marinalendt Feb 12, 2026
2ab737c
adding function for energy left and recovery per day + styling
marinalendt Feb 12, 2026
f78056a
adding logic for login and logout to userStore(Zustand). Token will b…
marinalendt Feb 12, 2026
799e2d6
adding background image
marinalendt Feb 12, 2026
bcdb57b
adding GlobalStyle
marinalendt Feb 12, 2026
16bd98d
Styling of DailyPlan
marinalendt Feb 12, 2026
e0f0fb8
small change of activities list
marinalendt Feb 13, 2026
ac57c7f
changing title
marinalendt Feb 13, 2026
9d45f88
fixing styling, and adding more colors to global style
marinalendt Feb 13, 2026
87ae051
Adding styling etc to homepage, adding buttons for login/signup
marinalendt Feb 13, 2026
bd3a337
adding accessToken, so the userinformation is stored in the database …
marinalendt Feb 13, 2026
f638dd2
creating ActivityCard, adding icons to activities from phosphor
marinalendt Feb 13, 2026
f1b87b3
changing to new colors in global style
marinalendt Feb 13, 2026
607c23e
creating a battery
marinalendt Feb 14, 2026
7399794
creating new files for the 3 "steps"
marinalendt Feb 16, 2026
2611e4e
adding colors to global styles
marinalendt Feb 16, 2026
5847d6f
changing colors, style and size of different components, battery, inf…
marinalendt Feb 16, 2026
0877b39
styling the battery some more
marinalendt Feb 16, 2026
c8b4968
moving structure and logic from DailyPlan to EnergyPicker, ActivityPl…
marinalendt Feb 16, 2026
0c3cc66
creating about and tips page
marinalendt Feb 18, 2026
de7de88
adding routes for about and tips
marinalendt Feb 18, 2026
5217d15
creating add activity
marinalendt Feb 18, 2026
edcd85e
fix styling in DaySummary
marinalendt Feb 18, 2026
92839e0
adding more links and content to navbar and
marinalendt Feb 18, 2026
ff91125
adding authenticateUser to dailyplan endpoint and also adding endpoin…
marinalendt Feb 18, 2026
23be802
adding endpoints to dailyplan, delete post and patch
marinalendt Feb 18, 2026
05efe3a
making save dailyplan work and also addning a saved message
marinalendt Feb 18, 2026
ca99e65
fixing styling on button in daysummary
marinalendt Feb 18, 2026
80aba2a
creating get endpoint for dailyplan
marinalendt Feb 18, 2026
69df22f
creating fetch from the dailyplan get endpoint
marinalendt Feb 18, 2026
d3d2a6f
Adding history page and logic
marinalendt Feb 18, 2026
f1cc694
adding a delete-button to new activities + adding a welcome message t…
marinalendt Feb 19, 2026
a58bfca
adding start-script for deployment on Render
marinalendt Feb 19, 2026
8d912bd
fix in package.json
marinalendt Feb 19, 2026
edb46ae
adding onrender-adress to base_url
marinalendt Feb 19, 2026
0a5abcc
adding a backbutton
marinalendt Feb 19, 2026
696fcc0
small fixes with clean code
marinalendt Feb 19, 2026
18d2a41
installing framer motion
marinalendt Feb 19, 2026
9b92ccf
adding the battery to modalcontent in activityplanner, for more overs…
marinalendt Feb 23, 2026
f49412d
adding netlify.toml to deploy to netlify
marinalendt Feb 23, 2026
555198d
changing colors for better accessibility and fixing padding, size etc
marinalendt Feb 24, 2026
18144ae
fixes for better lighthouse-score
marinalendt Feb 24, 2026
c79812b
adding empty state to history
marinalendt Feb 24, 2026
bd4b07e
adding useCallback as new hook
marinalendt Feb 24, 2026
093246e
adding accessibility variables
marinalendt Feb 24, 2026
c17ed0b
adding favicon
marinalendt Feb 24, 2026
56285b1
adding favicon-img
marinalendt Feb 24, 2026
40396e7
adding filters for activitycategory, for more oversight
marinalendt Feb 25, 2026
c8c8598
changing colors
marinalendt Feb 25, 2026
aff7d0a
chancing colors again to
marinalendt Feb 25, 2026
7c40f30
adding a mascot with a tipcard, instead of hte BalanceAssessment
marinalendt Feb 25, 2026
bdd75e6
stylingfix overall
marinalendt Feb 26, 2026
eb9933d
changing background, padding and border-radius for accessibility and …
marinalendt Feb 27, 2026
51eed69
change: the save button will be unsaved when a plan is changed
marinalendt Feb 27, 2026
8d88564
changing: styling of the mascot
marinalendt Feb 27, 2026
b120b82
adding: highlight to username
marinalendt Feb 27, 2026
576e867
creating: a graf to show all plans for one week or for one month
marinalendt Feb 28, 2026
2252eec
Adding: the Energygraf to History and fixing som styling bugs
marinalendt Feb 28, 2026
84b2778
fixing: some small styling bugs
marinalendt Mar 1, 2026
e38d446
creating: a new mascot
marinalendt Mar 1, 2026
183656b
uppdating framer motion, to deploy on netlify
marinalendt Mar 1, 2026
d187749
fixing: some more styling and placement of the mascot and tipbubble
marinalendt Mar 1, 2026
78fac0e
changing: colors and adding to global style
marinalendt Mar 2, 2026
e587954
adding: useLocation so that the page always finds the previous page
marinalendt Mar 2, 2026
6a388e4
adding: logic so the user only can create one dailyplan a day
marinalendt Mar 2, 2026
2ccac03
adding: icons to activitychip
marinalendt Mar 2, 2026
8b52fcc
adding: more activities to seedlist and to activitycard
marinalendt Mar 2, 2026
d617948
adding: more tips to the mascot-tipbubble
marinalendt Mar 2, 2026
cb21269
fixing: scroll behavior in mobil
marinalendt Mar 2, 2026
ed7b1e6
adding: a Energyrow to show how much startenergy + energyleft you hav…
marinalendt Mar 2, 2026
b012598
adding: a notefield to whrite about your thoughts for the day
marinalendt Mar 2, 2026
a4c6f14
changing: some styling in history, cleaning code, and adding aria-lab…
marinalendt Mar 3, 2026
96a161e
fixing: accessibility, clean code and semantik
marinalendt Mar 3, 2026
fbccf73
fixing: accessibility, clean code and semantik in every component
marinalendt Mar 3, 2026
b756f9f
fixing: small bugs in backend
marinalendt Mar 3, 2026
1d97ed6
fixing: small bugs in activityplanner and history
marinalendt Mar 3, 2026
4f6de4a
fixing: small bugs to get better accessibility in Lighthouse
marinalendt Mar 3, 2026
1d21bf0
fixing: accessibility, safari support and color token overall
marinalendt Mar 3, 2026
fb36f71
changing: order in historycards, newest dailyplan comes first
marinalendt Mar 4, 2026
e941c88
fixing: small stylingfixes
marinalendt Mar 4, 2026
4ea8b3e
adding: comments to backend files
marinalendt Mar 4, 2026
c6038fb
removing: Register.jsx, double code with home.jsx
marinalendt Mar 4, 2026
cd1c8f1
adding: comments to frontend files
marinalendt Mar 4, 2026
50d9fc6
changing: fontsize in login and signup to prevent zoom in in mobile
marinalendt Mar 4, 2026
7fefc1c
fixing: some more zoom in issues
marinalendt Mar 4, 2026
c87665e
fixing: textarea font-size to prevent iOS zoom
marinalendt Mar 4, 2026
417e99e
fixing: add static CSS rule to prevent iOS input zoom before JS loads
marinalendt Mar 4, 2026
a9156f2
fixing: force BatteryWrapper to GPU layer to prevent iOS filter overf…
marinalendt Mar 4, 2026
df32253
fixing: filter activities by user so custom activities are private
marinalendt Mar 4, 2026
46d0fc6
adding: a new hook. useRef. Focus will automatically be moved to the …
marinalendt Mar 5, 2026
65a29b8
fixing: small typing bug
marinalendt Mar 5, 2026
ee33ae9
Adding: README file
marinalendt Mar 7, 2026
2765b5e
Adding: Loading state in history, to prevent bug
marinalendt Mar 7, 2026
793bbe4
adding: keyframe to daysummary, to prevent bug
marinalendt Mar 7, 2026
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
63 changes: 56 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,62 @@
# Final Project
# Balans — Energy Management App

Replace this readme with your own information about your project.
**Balans** is a mobile-first web app for people who need to manage their daily energy carefully. Inspired by Spoon Theory — a framework often used by people with chronic illness (depression, burnout etc) to describe limited energy resources — Balans helps you plan your day around how much energy you actually have, not how much you wish you had.

Start by briefly describing the assignment in a sentence or two. Keep it short and to the point.
---

## The problem
## 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?
People living with chronic fatigue or energy-limiting conditions often struggle to plan their days in a way that respects their capacity. Traditional to-do apps treat all tasks as equal and don't account for energy cost. Balans fills that gap by letting you log your current energy, plan activities based on what drains or restores you, and reflect on patterns over time.

## 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.
## Features

- **Energy check-in** — Log your energy level (1–10) at the start of each day using an interactive battery visualisation
- **Activity planner** — Add activities to your day, each tagged as energy-draining or energy-restoring, with a cost/gain value
- **Day summary** — See a breakdown of your planned day and get a personalised tip from the mascot based on your energy balance
- **History** — Review past days and track your energy patterns over time
- **Custom activities** — Add your own activities (private to your account) alongside the built-in defaults
- **Animated mascot** — EnergyBlob reacts to your energy level with different expressions and animations
- **User accounts** — Register, log in, and keep your data private with JWT-based authentication

---

## Tech Stack

### Frontend
- React + Vite
- styled-components (with CSS custom properties for theming)
- Framer Motion (animations)
- Zustand (global state)
- React Router
- Phosphor Icons

### Backend
- Node.js + Express
- MongoDB + Mongoose
- bcrypt (password hashing)
- Babel + Nodemon

---

## Deployment

- **Frontend:** [Netlify](https://final-project-balans.netlify.app)
- **Backend:** [Render](https://project-final-balans.onrender.com)

---

## What's Next

- Mood tracking alongside energy levels
- AI-powered suggestions based on your personal energy history
- Weekly and monthly energy overview charts
- Adding a calender to plan a week ahead
- Checkboxes in history, to be able to check the activities you completed in the end om the day

---

## About the Name

*Balans* is the Swedish word for balance — because the goal isn't to do more, it's to find the balance that works for you.
8 changes: 0 additions & 8 deletions backend/README.md

This file was deleted.

90 changes: 90 additions & 0 deletions backend/data/seedActivities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Activity } from "../models/Activity.js";

export const defaults = [
// Activities that gives energy
{
name: "Promenad",
energyImpact: 2,
category: "rörelse"
},
{
name: "Yoga",
energyImpact: 3,
category: "rörelse"
},
{
name: "Träning lätt",
energyImpact: 1,
category: "rörelse"
},
{
name: "Meditation",
energyImpact: 3,
category: "vila"
},
{
name: "Powernap",
energyImpact: 3,
category: "vila",
},
{
name: "Umgås",
energyImpact: 2,
category: "socialt"
},
{
name: "Natur",
energyImpact: 2,
category: "vardag",
},

// Activities that takes energy.
{
name: "Jobb",
energyImpact: -2,
category: "jobb"
},
{
name: "Möte",
energyImpact: -2,
category: "jobb"
},
{
name: "Städning",
energyImpact: -2,
category: "vardag"
},
{
name: "Skärmtid",
energyImpact: -1,
category: "vardag"
},
{
name: "Matlagning",
energyImpact: -1,
category: "vardag"
},
{
name: "Pendling",
energyImpact: -2,
category: "vardag"
},
{
name: "Handla",
energyImpact: -2,
category: "vardag"
},
{
name: "Träning tung",
energyImpact: -3,
category: "rörelse",
}
];

// Fills the database with standard activities once when the app starts.
export const seedActivities = async () => {
const count = await Activity.countDocuments();
if (count === 0) {
await Activity.insertMany(defaults);
}
};
16 changes: 16 additions & 0 deletions backend/middleware/authMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { User } from "../models/User.js";

//This is the "safeguard" for the protected endpoints, the one that uses auth.
export const authenticateUser = async (req, res, next) => {
const user = await User.findOne({
accessToken: req.header("Authorization")
});
if (user) {
req.user = user
next();
} else {
res.status(401).json({
loggedOut: true
});
}
};
22 changes: 22 additions & 0 deletions backend/models/Activity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Schema, model } from "mongoose";

const activitySchema = new Schema({
name: {
type: String,
required: true,
},
user: {
type: Schema.Types.ObjectId,
ref: "User",
},
energyImpact: {
type: Number,
required: true,
},
category: {
type: String,
required: true,
},
});

export const Activity = model("activity", activitySchema);
32 changes: 32 additions & 0 deletions backend/models/DailyPlan.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Schema, model } from "mongoose";

const dailyPlanSchema = new Schema({
// One unique id-person that points to the User model. The owner of the dailyplan.
user: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
date: {
type: Date,
default: () => new Date(),
},
startingEnergy: {
type: Number,
},

activities: [{
type: Schema.Types.ObjectId,
ref: "activity",
}],
currentEnergy: {
type: Number,
},
// The users own note about the day (in History.jsx)
notes: {
type: String,
default: "",
}
});

export const dailyPlan = model("dailyPlan", dailyPlanSchema);
25 changes: 25 additions & 0 deletions backend/models/User.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Schema, model } from "mongoose";
import crypto from "crypto";

// These are required (name, email, password). accessToken is generated with crypto.
const userSchema = new Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
accessToken: {
type: String,
default: () => crypto.randomBytes(128).toString("hex")
}
});

export const User = model("User", userSchema);
5 changes: 4 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@
"@babel/core": "^7.17.9",
"@babel/node": "^7.16.8",
"@babel/preset-env": "^7.16.11",
"bcrypt": "^6.0.0",
"cors": "^2.8.5",
"dotenv": "^17.2.4",
"express": "^4.17.3",
"express-list-endpoints": "^7.1.1",
"mongoose": "^8.4.0",
"nodemon": "^3.0.1"
}
}
}
64 changes: 64 additions & 0 deletions backend/routes/activitiesRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import express from "express";
import { Activity } from "../models/Activity.js";
import { authenticateUser } from "../middleware/authMiddleware.js";

export const router = express.Router();

// GET /activities. Gets default activities + the current user's own activities.
router.get("/activities", authenticateUser, async (req, res) => {
try {
const activities = await Activity.find({
$or: [{ user: { $exists: false } }, { user: null }, { user: req.user._id }]
});
res.json(activities);
} catch (error) {
res.status(500).json({ error: "Could not fetch activities" });
}
});

// POST /activities. Adding a new activity to the database. needs a auth.
router.post("/activities", authenticateUser, async (req, res) => {
try {
const { name, energyImpact, category } = req.body;
if (!name || !energyImpact || !category) {
return res.status(400).json({ error: "Name, energyImpact and category are required" });
}

const activity = new Activity({ name, energyImpact, category, user: req.user._id });
await activity.save();
res.status(201).json(activity);
} catch (error) {
res.status(500).json({ error: "Could not create activity" });
}
});

// PATCH /activities. Updates an activity.
router.patch("/activities/:id", authenticateUser, async (req, res) => {
try {
const { id } = req.params;
const updates = req.body;
const activity = await Activity.findByIdAndUpdate(id, updates, { new: true });

if (!activity) {
return res.status(404).json({ error: "Activity not found" });
}
res.json(activity);
} catch (error) {
res.status(400).json({ error: "Could not update activity" });
}
});

// DELETE /activities. Deletes an activity
router.delete("/activities/:id", authenticateUser, async (req, res) => {
try {
const { id } = req.params;
const deletedActivity = await Activity.findByIdAndDelete(id);

if (!deletedActivity) {
return res.status(404).json({ error: `Activity with id ${id} does not exist` });
}
res.json(deletedActivity);
} catch (error) {
res.status(500).json({ error: "Could not delete activity" });
}
});
Loading