From 96c8c346a514e4aa154c8ebc2524ee46c6806944 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Sun, 15 Feb 2026 16:01:51 +0100 Subject: [PATCH 01/48] installed zustand, react-router-dom, styled-components, framer-motion, date,fns, usehooks-ts, react-toastify --- frontend/package.json | 9 ++++++++- package.json | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 7b2747e94..e593e22f5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,8 +10,15 @@ "preview": "vite preview" }, "dependencies": { + "date-fns": "^4.1.0", + "framer-motion": "^12.34.0", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^7.13.0", + "react-toastify": "^11.0.5", + "styled-components": "^6.3.9", + "usehooks-ts": "^3.1.1", + "zustand": "^5.0.11" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/package.json b/package.json index 680d19077..917ab571d 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,8 @@ "version": "1.0.0", "scripts": { "postinstall": "npm install --prefix backend" + }, + "dependencies": { + "date-fns": "^4.1.0" } -} \ No newline at end of file +} From 92322415cb4a94a300ffb297f1f39c3f520659c1 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Thu, 19 Feb 2026 11:24:05 +0100 Subject: [PATCH 02/48] created files in backend and frontend, did globalstyles, theme and landing page --- .../index.css => backend/models/Medication.js | 0 backend/models/Moodentry.js | 0 backend/models/Symptom.js | 0 backend/models/User.js | 24 ++ backend/routes/auth.js | 0 backend/routes/medications.js | 0 backend/routes/moods.js | 0 backend/routes/symptoms.js | 0 frontend/src/App.jsx | 18 +- frontend/src/components/Layout.jsx | 128 ++++++++ frontend/src/components/Navbar.jsx | 144 +++++++++ frontend/src/data/questions.js | 0 frontend/src/main.jsx | 2 +- frontend/src/pages/LandingPage.jsx | 282 ++++++++++++++++++ frontend/src/pages/LoginPage.jsx | 0 frontend/src/pages/SymptomCheckPage.jsx | 0 frontend/src/styles/Globalstyles.js | 25 ++ frontend/src/styles/theme.js | 32 ++ 18 files changed, 651 insertions(+), 4 deletions(-) rename frontend/src/index.css => backend/models/Medication.js (100%) create mode 100644 backend/models/Moodentry.js create mode 100644 backend/models/Symptom.js create mode 100644 backend/models/User.js create mode 100644 backend/routes/auth.js create mode 100644 backend/routes/medications.js create mode 100644 backend/routes/moods.js create mode 100644 backend/routes/symptoms.js create mode 100644 frontend/src/components/Layout.jsx create mode 100644 frontend/src/components/Navbar.jsx create mode 100644 frontend/src/data/questions.js create mode 100644 frontend/src/pages/LandingPage.jsx create mode 100644 frontend/src/pages/LoginPage.jsx create mode 100644 frontend/src/pages/SymptomCheckPage.jsx create mode 100644 frontend/src/styles/Globalstyles.js create mode 100644 frontend/src/styles/theme.js diff --git a/frontend/src/index.css b/backend/models/Medication.js similarity index 100% rename from frontend/src/index.css rename to backend/models/Medication.js diff --git a/backend/models/Moodentry.js b/backend/models/Moodentry.js new file mode 100644 index 000000000..e69de29bb diff --git a/backend/models/Symptom.js b/backend/models/Symptom.js new file mode 100644 index 000000000..e69de29bb diff --git a/backend/models/User.js b/backend/models/User.js new file mode 100644 index 000000000..99d991efc --- /dev/null +++ b/backend/models/User.js @@ -0,0 +1,24 @@ +import mongoose from "mongoose" + +const userSchema = new mongoose.Schema({ + + firstname: { + type: String, + required: true + }, + lastname: { + type: String, + required: true + }, + email: { + type: String, + required: true, + unique: true + }, + password:{ + type: String, + required: true + } +}) + +export const User = mongoose.model('User', userSchema) diff --git a/backend/routes/auth.js b/backend/routes/auth.js new file mode 100644 index 000000000..e69de29bb diff --git a/backend/routes/medications.js b/backend/routes/medications.js new file mode 100644 index 000000000..e69de29bb diff --git a/backend/routes/moods.js b/backend/routes/moods.js new file mode 100644 index 000000000..e69de29bb diff --git a/backend/routes/symptoms.js b/backend/routes/symptoms.js new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 0a24275e6..e028fe0fa 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,8 +1,20 @@ +import LandingPage from "./pages/LandingPage"; +import { ThemeProvider } from "styled-components"; +import GlobalStyles from "./styles/Globalstyles"; +import { theme } from "./styles/theme"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; + + export const App = () => { return ( - <> -

Welcome to Final Project!

- + + + + + } /> + + + ); }; diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx new file mode 100644 index 000000000..7a7b81b6f --- /dev/null +++ b/frontend/src/components/Layout.jsx @@ -0,0 +1,128 @@ +import styled, { keyframes } from 'styled-components'; +import { theme } from '../styles/theme'; + +// ─── Animations ─────────────────────────────────────────────── +export const fadeUp = keyframes` + from { opacity: 0; transform: translateY(18px); } + to { opacity: 1; transform: translateY(0); } +`; + +export const fadeLeft = keyframes` + from { opacity: 0; transform: translateX(14px); } + to { opacity: 1; transform: translateX(0); } +`; + +export const pulse = keyframes` + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.6; transform: scale(0.85); } +`; + +export const growBar = keyframes` + from { width: 0; } +`; + +// ─── Page frame ─────────────────────────────────────────────── +export const Frame = styled.div` + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; + background: ${theme.colors.dark}; +`; + +// ─── Main card ──────────────────────────────────────────────── +export const Card = styled.div` + width: 100%; + max-width: ${({ maxWidth }) => maxWidth || '1100px'}; + min-height: ${({ minHeight }) => minHeight || '620px'}; + border-radius: ${theme.radius.xl}; + overflow: hidden; + position: relative; + background: ${theme.gradients.card}; + box-shadow: ${theme.shadow.card}, inset 0 1px 0 rgba(255,255,255,0.15); + display: flex; + flex-direction: column; + + /* dark overlay for text readability */ + &::after { + content: ''; + position: absolute; + inset: 0; + background: ${theme.overlay}; + pointer-events: none; + z-index: 2; + } + + /* vertical stripe texture */ + &::before { + content: ''; + position: absolute; + inset: 0; + background-image: repeating-linear-gradient( + 90deg, + transparent, transparent 3px, + rgba(255,255,255,0.025) 3px, rgba(255,255,255,0.025) 4px + ); + pointer-events: none; + z-index: 1; + } +`; + +// ─── Corner labels (decorative) ─────────────────────────────── +export const CornerLabel = styled.span` + position: absolute; + color: rgba(255,255,255,0.5); + font-size: 0.62rem; + letter-spacing: 0.05em; + z-index: 15; + ${({ pos }) => ({ + 'tl': 'top: 1rem; left: 1.2rem;', + 'tr': 'top: 1rem; right: 1.2rem;', + 'bl': 'bottom: 1rem; left: 1.2rem;', + 'br': 'bottom: 1rem; right: 1.2rem;', + }[pos])} +`; + +// ─── Shared button styles ───────────────────────────────────── +export const BtnPrimary = styled.button` + display: inline-flex; + align-items: center; + gap: 0.5rem; + background: rgba(255,255,255,0.92); + color: ${theme.colors.tealDeep}; + border: none; + border-radius: ${theme.radius.pill}; + padding: 0.85rem 2rem; + font-size: 0.88rem; + font-weight: 500; + cursor: pointer; + letter-spacing: 0.01em; + box-shadow: ${theme.shadow.btn}; + transition: transform 0.2s, box-shadow 0.2s, background 0.2s; + text-decoration: none; + + &:hover { + background: #fff; + transform: translateY(-2px); + box-shadow: 0 12px 40px rgba(0,0,0,0.25); + } +`; + +export const BtnGhost = styled.button` + background: transparent; + border: 1px solid rgba(255,255,255,0.25); + border-radius: ${theme.radius.pill}; + padding: 0.85rem 1.6rem; + font-size: 0.85rem; + font-weight: 400; + color: rgba(255,255,255,0.8); + cursor: pointer; + letter-spacing: 0.01em; + transition: border-color 0.2s, color 0.2s; + + &:hover { + border-color: rgba(255,255,255,0.5); + color: #fff; + } +`; diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx new file mode 100644 index 000000000..21c91c2ce --- /dev/null +++ b/frontend/src/components/Navbar.jsx @@ -0,0 +1,144 @@ +import React from 'react'; +import styled from 'styled-components'; +import { theme } from '../styles/theme'; + +// ─── Styles ─────────────────────────────────────────────────── +const Nav = styled.nav` + position: relative; + z-index: 10; + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.8rem 2.5rem; +`; + +const LogoWrap = styled.a` + display: flex; + align-items: center; + gap: 0.6rem; + text-decoration: none; +`; + +const LogoIcon = styled.div` + width: 32px; + height: 32px; + border-radius: 50%; + background: rgba(255,255,255,0.2); + border: 1.5px solid rgba(255,255,255,0.35); + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(8px); +`; + +const LogoText = styled.span` + font-size: 0.9rem; + font-weight: 500; + color: rgba(255,255,255,0.9); +`; + +const NavLinks = styled.ul` + display: flex; + align-items: center; + gap: 2.5rem; + list-style: none; + + @media (max-width: 768px) { display: none; } +`; + +const NavLink = styled.a` + font-size: 0.85rem; + font-weight: 400; + color: rgba(255,255,255,0.88); + text-decoration: none; + transition: color 0.2s; + &:hover { color: #fff; } +`; + +const NavCta = styled.a` + font-size: 0.82rem; + font-weight: 500; + color: ${theme.colors.tealDeep}; + background: rgba(255,255,255,0.92); + border: none; + border-radius: 100px; + padding: 0.55rem 1.3rem; + cursor: pointer; + letter-spacing: 0.01em; + transition: background 0.2s, transform 0.15s; + text-decoration: none; + + &:hover { background: #fff; transform: translateY(-1px); } +`; + +const UserPill = styled.div` + display: flex; + align-items: center; + gap: 0.5rem; + background: rgba(0,0,0,0.25); + border: 1px solid rgba(255,255,255,0.25); + border-radius: 100px; + padding: 0.35rem 0.9rem; + font-size: 0.78rem; + color: rgba(255,255,255,0.9); +`; + +const Avatar = styled.div` + width: 22px; + height: 22px; + border-radius: 50%; + background: rgba(125,255,212,0.25); + border: 1px solid rgba(125,255,212,0.5); + display: flex; + align-items: center; + justify-content: center; + font-size: 0.62rem; + color: ${theme.colors.mint}; + font-weight: 500; +`; + +// ─── Pulse icon ─────────────────────────────────────────────── +const PulseIcon = () => ( + + + +); + +// ─── Component ─────────────────────────────────────────────── +const Navbar = ({ variant = 'default', user = null, logoHref = '/' }) => { + return ( + + ); +}; + +export default Navbar; diff --git a/frontend/src/data/questions.js b/frontend/src/data/questions.js new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 51294f399..bc52a5633 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -1,7 +1,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { App } from "./App.jsx"; -import "./index.css"; + ReactDOM.createRoot(document.getElementById("root")).render( diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx new file mode 100644 index 000000000..7af58d2f9 --- /dev/null +++ b/frontend/src/pages/LandingPage.jsx @@ -0,0 +1,282 @@ +import React from 'react'; +import styled, { keyframes } from 'styled-components'; +import { Frame, Card, CornerLabel, BtnPrimary, fadeUp, fadeLeft, pulse } from '../components/Layout.jsx'; +import Navbar from '../components/Navbar.jsx'; +import { theme } from '../styles/theme'; + +// ─── Styles ─────────────────────────────────────────────────── +const growBar = keyframes`from { width: 0; }`; + +const Hero = styled.div` + position: relative; + z-index: 10; + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + padding: 3rem 3rem 5rem; + gap: 1.8rem; + + @media (max-width: 768px) { + padding: 2rem 1.8rem 6rem; + } +`; + +const Badge = styled.div` + display: inline-flex; + align-items: center; + gap: 0.5rem; + background: rgba(0,0,0,0.3); + border: 1px solid rgba(255,255,255,0.25); + backdrop-filter: blur(10px); + border-radius: 100px; + padding: 0.35rem 1rem; + font-size: 0.75rem; + font-weight: 500; + color: rgba(255,255,255,0.9); + letter-spacing: 0.03em; + animation: ${fadeUp} 0.8s ease both; +`; + +const BadgeDot = styled.span` + width: 6px; + height: 6px; + border-radius: 50%; + background: ${theme.colors.mint}; + box-shadow: 0 0 8px ${theme.colors.mint}; + animation: ${pulse} 2s infinite; +`; + +const H1 = styled.h1` + font-family: ${theme.fonts.serif}; + font-size: clamp(3rem, 6vw, 5.2rem); + font-weight: 300; + line-height: 1.1; + letter-spacing: -0.01em; + color: #fff; + max-width: 700px; + animation: ${fadeUp} 0.8s 0.1s ease both; + text-shadow: 0 2px 12px rgba(0,0,0,0.3); + + em { + font-style: italic; + color: rgba(255,255,255,0.75); + } +`; + +const HeroSub = styled.p` + font-size: 1rem; + font-weight: 300; + color: rgba(255,255,255,0.88); + max-width: 440px; + line-height: 1.7; + animation: ${fadeUp} 0.8s 0.2s ease both; + text-shadow: 0 1px 4px rgba(0,0,0,0.3); +`; + +const HeroActions = styled.div` + display: flex; + gap: 1rem; + align-items: center; + animation: ${fadeUp} 0.8s 0.3s ease both; + flex-wrap: wrap; + justify-content: center; +`; + +const BtnGhostLink = styled.a` + font-size: 0.85rem; + font-weight: 400; + color: rgba(255,255,255,0.88); + cursor: pointer; + letter-spacing: 0.01em; + text-decoration: none; + display: inline-flex; + align-items: center; + gap: 0.4rem; + transition: color 0.2s; + text-shadow: 0 1px 3px rgba(0,0,0,0.3); + &:hover { color: #fff; } +`; + +const Stats = styled.div` + position: absolute; + bottom: 2.5rem; + left: 0; + right: 0; + z-index: 10; + display: flex; + justify-content: center; + gap: 1rem; + padding: 0 3rem; + animation: ${fadeUp} 0.8s 0.4s ease both; + flex-wrap: wrap; +`; + +const StatPill = styled.div` + background: rgba(0,0,0,0.3); + backdrop-filter: blur(12px); + border: 1px solid rgba(255,255,255,0.2); + border-radius: 100px; + padding: 0.5rem 1.2rem; + display: flex; + align-items: center; + gap: 0.6rem; + font-size: 0.78rem; + color: rgba(255,255,255,0.9); + + strong { font-weight: 500; color: #fff; } +`; + +const StatSep = styled.span` + width: 1px; + height: 14px; + background: rgba(255,255,255,0.25); +`; + +const FloatCard = styled.div` + position: absolute; + top: 50%; + right: 3.5rem; + transform: translateY(-60%); + z-index: 20; + background: rgba(10,40,46,0.8); + backdrop-filter: blur(20px); + border: 1px solid rgba(255,255,255,0.2); + border-radius: 18px; + padding: 1.4rem 1.6rem; + min-width: 220px; + box-shadow: ${theme.shadow.float}; + animation: ${fadeLeft} 0.8s 0.5s ease both; + + @media (max-width: 900px) { display: none; } +`; + +const FloatLabel = styled.div` + font-size: 0.7rem; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; + color: rgba(255,255,255,0.65); + margin-bottom: 0.6rem; +`; + +const FloatTitle = styled.div` + font-family: ${theme.fonts.serif}; + font-size: 1.3rem; + font-weight: 400; + color: #fff; + line-height: 1.3; + margin-bottom: 0.5rem; +`; + +const FloatStatus = styled.div` + display: flex; + align-items: center; + gap: 0.4rem; + font-size: 0.75rem; + color: rgba(255,255,255,0.75); +`; + +const StatusDot = styled.span` + width: 6px; + height: 6px; + border-radius: 50%; + background: ${theme.colors.mint}; +`; + +const ProgressTrack = styled.div` + margin-top: 1rem; + height: 4px; + background: rgba(255,255,255,0.15); + border-radius: 2px; + overflow: hidden; +`; + +const ProgressFill = styled.div` + height: 100%; + width: 68%; + background: linear-gradient(90deg, rgba(255,255,255,0.5), rgba(255,255,255,0.9)); + border-radius: 2px; + animation: ${growBar} 1.5s 1s ease both; +`; + +const FloatMeta = styled.div` + display: flex; + justify-content: space-between; + margin-top: 0.5rem; + font-size: 0.7rem; + color: rgba(255,255,255,0.65); +`; + +// ─── Page ───────────────────────────────────────────────────── +const LandingPage = () => ( + + + {/* Beta v0.1 */} + {/* Medicinsk App */} + Stoppa Proppen © + 2025 + + + + + {/* + + Beta — Nu tillgänglig + */} + +

+ Skydda dig mot
+ blodproppar +

+ + + Enkel, medicinsk vägledning för att förstå, förebygga och + agera vid tecken på blodpropp — alltid i din ficka. + + + + {/* + + Kom igång gratis + */} + + Läs mer + + +
+ + + Medicinsk vägledningCommunity & stöd + GratisAlltid tillgänglig + Snabb hjälpSymtomkoll på under 60 sek + + + + Din riskbedömning + Symtomanalys
pågår…
+ 3 av 5 steg klara + + Genomfört68% +
+
+ +); + +// ─── Inline SVG icons ───────────────────────────────────────── +const CheckIcon = () => ( + + + + +); + +const ArrowIcon = () => ( + + + +); + +export default LandingPage; diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/pages/SymptomCheckPage.jsx b/frontend/src/pages/SymptomCheckPage.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/styles/Globalstyles.js b/frontend/src/styles/Globalstyles.js new file mode 100644 index 000000000..931e0f0be --- /dev/null +++ b/frontend/src/styles/Globalstyles.js @@ -0,0 +1,25 @@ +import { createGlobalStyle } from 'styled-components'; + +const GlobalStyles = createGlobalStyle` + @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;1,300;1,400&family=DM+Sans:wght@300;400;500&display=swap'); + + *, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; + } + + html, body { + min-height: 100%; + font-family: 'DM Sans', sans-serif; + background: #0a2e33; + color: #ffffff; + overflow-x: hidden; + -webkit-font-smoothing: antialiased; + } + + a { text-decoration: none; color: inherit; } + button { font-family: 'DM Sans', sans-serif; } +`; + +export default GlobalStyles; diff --git a/frontend/src/styles/theme.js b/frontend/src/styles/theme.js new file mode 100644 index 000000000..5c45f200c --- /dev/null +++ b/frontend/src/styles/theme.js @@ -0,0 +1,32 @@ +export const theme = { + colors: { + tealDeep: '#0d4a52', + tealMid: '#1a6b76', + tealLight: '#4a9fa8', + mint: '#7dffd4', + amber: '#ffd97d', + coral: '#ff8a7d', + white: '#ffffff', + dark: '#0a2e33', + }, + gradients: { + card: 'linear-gradient(160deg, #1a6b76 0%, #1a6b76 20%, #4a9fa8 55%, #1a6b76 80%, #0d4a52 100%)', + }, + overlay: 'rgba(8, 35, 40, 0.45)', + fonts: { + serif: "'Cormorant Garamond', Georgia, serif", + sans: "'DM Sans', sans-serif", + }, + radius: { + sm: '10px', + md: '16px', + lg: '18px', + xl: '24px', + pill: '100px', + }, + shadow: { + card: '0 40px 100px rgba(0,0,0,0.5)', + btn: '0 8px 24px rgba(0,0,0,0.2)', + float: '0 20px 60px rgba(0,0,0,0.35)', + }, +}; From d4ecf47373ac2f28340b7d9e2a98b572906a6caa Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Fri, 20 Feb 2026 11:17:26 +0100 Subject: [PATCH 03/48] finished the loginpage --- frontend/src/App.jsx | 2 + frontend/src/components/Layout.jsx | 86 ++++-- frontend/src/components/Navbar.jsx | 222 +++++++++++---- frontend/src/pages/LandingPage.jsx | 6 +- frontend/src/pages/LoginPage.jsx | 420 +++++++++++++++++++++++++++++ 5 files changed, 651 insertions(+), 85 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index e028fe0fa..0078f70a0 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -3,6 +3,7 @@ import { ThemeProvider } from "styled-components"; import GlobalStyles from "./styles/Globalstyles"; import { theme } from "./styles/theme"; import { BrowserRouter, Routes, Route } from "react-router-dom"; +import LoginPage from "./pages/LoginPage"; export const App = () => { @@ -13,6 +14,7 @@ export const App = () => { } /> + } /> diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 7a7b81b6f..9336a8130 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -27,34 +27,47 @@ export const Frame = styled.div` display: flex; align-items: center; justify-content: center; - padding: 2rem; + + /* mobil: ingen padding, kortet fyller hela skärmen */ + padding: 0; background: ${theme.colors.dark}; + + @media (min-width: 768px) { + padding: 2rem; + } `; // ─── Main card ──────────────────────────────────────────────── export const Card = styled.div` width: 100%; - max-width: ${({ maxWidth }) => maxWidth || '1100px'}; - min-height: ${({ minHeight }) => minHeight || '620px'}; - border-radius: ${theme.radius.xl}; - overflow: hidden; + min-height: 100vh; position: relative; background: ${theme.gradients.card}; - box-shadow: ${theme.shadow.card}, inset 0 1px 0 rgba(255,255,255,0.15); display: flex; flex-direction: column; - /* dark overlay for text readability */ + /* mobil: inga rundade hörn, fyller hela skärmen */ + border-radius: 0; + box-shadow: none; + + @media (min-width: 768px) { + max-width: ${({ maxWidth }) => maxWidth || '1100px'}; + min-height: ${({ minHeight }) => minHeight || '620px'}; + border-radius: 24px; + box-shadow: ${theme.shadow.card}, inset 0 1px 0 rgba(255,255,255,0.15); + } + + /* dark overlay */ &::after { content: ''; position: absolute; inset: 0; - background: ${theme.overlay}; + background: rgba(8, 35, 40, 0.45); pointer-events: none; z-index: 2; } - /* vertical stripe texture */ + /* stripe texture */ &::before { content: ''; position: absolute; @@ -69,38 +82,50 @@ export const Card = styled.div` } `; -// ─── Corner labels (decorative) ─────────────────────────────── +// ─── Corner labels — bara synliga på desktop ────────────────── export const CornerLabel = styled.span` - position: absolute; - color: rgba(255,255,255,0.5); - font-size: 0.62rem; - letter-spacing: 0.05em; - z-index: 15; - ${({ pos }) => ({ - 'tl': 'top: 1rem; left: 1.2rem;', - 'tr': 'top: 1rem; right: 1.2rem;', - 'bl': 'bottom: 1rem; left: 1.2rem;', - 'br': 'bottom: 1rem; right: 1.2rem;', - }[pos])} + display: none; + + @media (min-width: 768px) { + display: block; + position: absolute; + color: rgba(255,255,255,0.5); + font-size: 0.62rem; + letter-spacing: 0.05em; + z-index: 15; + + ${({ pos }) => ({ + 'tl': 'top: 1rem; left: 1.2rem;', + 'tr': 'top: 1rem; right: 1.2rem;', + 'bl': 'bottom: 1rem; left: 1.2rem;', + 'br': 'bottom: 1rem; right: 1.2rem;', + }[pos])} + } `; -// ─── Shared button styles ───────────────────────────────────── +// ─── Shared buttons ─────────────────────────────────────────── export const BtnPrimary = styled.button` display: inline-flex; align-items: center; + justify-content: center; gap: 0.5rem; background: rgba(255,255,255,0.92); color: ${theme.colors.tealDeep}; border: none; border-radius: ${theme.radius.pill}; - padding: 0.85rem 2rem; - font-size: 0.88rem; + padding: 0.9rem 2rem; + font-size: 0.9rem; font-weight: 500; cursor: pointer; letter-spacing: 0.01em; box-shadow: ${theme.shadow.btn}; transition: transform 0.2s, box-shadow 0.2s, background 0.2s; text-decoration: none; + width: 100%; + + @media (min-width: 480px) { + width: auto; + } &:hover { background: #fff; @@ -110,16 +135,25 @@ export const BtnPrimary = styled.button` `; export const BtnGhost = styled.button` + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; background: transparent; border: 1px solid rgba(255,255,255,0.25); border-radius: ${theme.radius.pill}; - padding: 0.85rem 1.6rem; - font-size: 0.85rem; + padding: 0.9rem 2rem; + font-size: 0.88rem; font-weight: 400; color: rgba(255,255,255,0.8); cursor: pointer; letter-spacing: 0.01em; transition: border-color 0.2s, color 0.2s; + width: 100%; + + @media (min-width: 480px) { + width: auto; + } &:hover { border-color: rgba(255,255,255,0.5); diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index 21c91c2ce..fe6b3c80b 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import styled from 'styled-components'; import { theme } from '../styles/theme'; @@ -9,7 +9,11 @@ const Nav = styled.nav` display: flex; align-items: center; justify-content: space-between; - padding: 1.8rem 2.5rem; + padding: 1.2rem 1.4rem; + + @media (min-width: 768px) { + padding: 1.8rem 2.5rem; + } `; const LogoWrap = styled.a` @@ -17,33 +21,36 @@ const LogoWrap = styled.a` align-items: center; gap: 0.6rem; text-decoration: none; + z-index: 200; `; const LogoIcon = styled.div` - width: 32px; - height: 32px; + width: 30px; + height: 30px; border-radius: 50%; background: rgba(255,255,255,0.2); border: 1.5px solid rgba(255,255,255,0.35); display: flex; align-items: center; justify-content: center; - backdrop-filter: blur(8px); `; const LogoText = styled.span` - font-size: 0.9rem; + font-size: 0.88rem; font-weight: 500; color: rgba(255,255,255,0.9); `; +// ─── Desktop nav links ──────────────────────────────────────── const NavLinks = styled.ul` - display: flex; - align-items: center; - gap: 2.5rem; - list-style: none; - - @media (max-width: 768px) { display: none; } + display: none; + + @media (min-width: 768px) { + display: flex; + align-items: center; + gap: 2.5rem; + list-style: none; + } `; const NavLink = styled.a` @@ -56,21 +63,109 @@ const NavLink = styled.a` `; const NavCta = styled.a` - font-size: 0.82rem; + display: none; + + @media (min-width: 768px) { + display: inline-block; + font-size: 0.82rem; + font-weight: 500; + color: ${theme.colors.tealDeep}; + background: rgba(255,255,255,0.92); + border: none; + border-radius: 100px; + padding: 0.55rem 1.3rem; + cursor: pointer; + letter-spacing: 0.01em; + transition: background 0.2s, transform 0.15s; + text-decoration: none; + &:hover { background: #fff; transform: translateY(-1px); } + } +`; + +// ─── Hamburger button (bara mobil) ──────────────────────────── +const HamburgerBtn = styled.button` + display: flex; + flex-direction: column; + justify-content: center; + gap: 5px; + background: transparent; + border: none; + cursor: pointer; + padding: 0.4rem; + position: fixed; + top: 1.1rem; + right: 1.2rem; + z-index: 999; + + @media (min-width: 768px) { + display: none; + } +`; + +const HamburgerLine = styled.span` + display: block; + width: 22px; + height: 1.5px; + background: rgba(255,255,255,0.9); + border-radius: 2px; + transition: transform 0.25s, opacity 0.25s; + + &:nth-child(1) { + transform: ${({ $open }) => $open ? 'translateY(6.5px) rotate(45deg)' : 'none'}; + } + &:nth-child(2) { + opacity: ${({ $open }) => $open ? 0 : 1}; + } + &:nth-child(3) { + transform: ${({ $open }) => $open ? 'translateY(-6.5px) rotate(-45deg)' : 'none'}; + } +`; + +// ─── Mobile menu drawer ─────────────────────────────────────── +const MobileMenu = styled.div` + position: fixed; + inset: 0; + background: rgba(10,40,46,0.97); + backdrop-filter: blur(16px); + z-index: 500; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.5rem; + transform: ${({ $open }) => $open ? 'translateX(0)' : 'translateX(100%)'}; + transition: transform 0.3s ease; + + @media (min-width: 768px) { + display: none; + } +`; + +const MobileLink = styled.a` + font-family: ${theme.fonts.serif}; + font-size: 2rem; + font-weight: 300; + color: rgba(255,255,255,0.85); + text-decoration: none; + padding: 0.6rem 2rem; + transition: color 0.2s; + &:hover { color: #fff; } +`; + +const MobileCta = styled.a` + margin-top: 1.5rem; + font-size: 0.9rem; font-weight: 500; color: ${theme.colors.tealDeep}; background: rgba(255,255,255,0.92); - border: none; border-radius: 100px; - padding: 0.55rem 1.3rem; - cursor: pointer; - letter-spacing: 0.01em; - transition: background 0.2s, transform 0.15s; + padding: 0.8rem 2.5rem; text-decoration: none; - - &:hover { background: #fff; transform: translateY(-1px); } + transition: background 0.2s; + &:hover { background: #fff; } `; +// ─── User pill (app variant) ────────────────────────────────── const UserPill = styled.div` display: flex; align-items: center; @@ -78,7 +173,7 @@ const UserPill = styled.div` background: rgba(0,0,0,0.25); border: 1px solid rgba(255,255,255,0.25); border-radius: 100px; - padding: 0.35rem 0.9rem; + padding: 0.3rem 0.8rem; font-size: 0.78rem; color: rgba(255,255,255,0.9); `; @@ -97,47 +192,62 @@ const Avatar = styled.div` font-weight: 500; `; -// ─── Pulse icon ─────────────────────────────────────────────── -const PulseIcon = () => ( - - - -); - -// ─── Component ─────────────────────────────────────────────── +// ─── Component ──────────────────────────────────────────────── const Navbar = ({ variant = 'default', user = null, logoHref = '/' }) => { - return ( - + ); }; diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx index 7af58d2f9..2a7ae5201 100644 --- a/frontend/src/pages/LandingPage.jsx +++ b/frontend/src/pages/LandingPage.jsx @@ -249,8 +249,8 @@ const LandingPage = () => ( - Medicinsk vägledningCommunity & stöd - GratisAlltid tillgänglig + Medicinsk vägledningGratis + Community & stödAlltid tillgänglig Snabb hjälpSymtomkoll på under 60 sek @@ -265,7 +265,7 @@ const LandingPage = () => ( ); -// ─── Inline SVG icons ───────────────────────────────────────── +// Inline SVG icons const CheckIcon = () => ( diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index e69de29bb..33660e014 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -0,0 +1,420 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import { Frame, fadeUp, fadeLeft } from '../components/Layout.jsx'; +import { theme } from '../styles/theme'; + +// ─── Split card ─────────────────────────────────────────────── +const SplitCard = styled.div` + width: 100%; + min-height: 100vh; + position: relative; + display: flex; + flex-direction: column; + + /* mobil: ren gradient, inget split */ + background: linear-gradient(160deg, #1a6b76 0%, #0d4a52 100%); + + @media (min-width: 700px) { + min-height: 620px; + border-radius: 24px; + max-width: 1050px; + display: grid; + grid-template-columns: 1fr 1fr; + box-shadow: ${theme.shadow.card}; + } +`; + +// ─── Vänster panel — bara desktop ──────────────────────────── +const LeftPanel = styled.div` + display: none; + + @media (min-width: 700px) { + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 2.5rem; + background: linear-gradient(160deg, #1a6b76 0%, #1a6b76 20%, #4a9fa8 55%, #1a6b76 80%, #0d4a52 100%); + position: relative; + + &::after { + content: ''; + position: absolute; + inset: 0; + background: rgba(8,35,40,0.42); + pointer-events: none; + z-index: 1; + } + + &::before { + content: ''; + position: absolute; + inset: 0; + background-image: repeating-linear-gradient( + 90deg, transparent, transparent 3px, + rgba(255,255,255,0.025) 3px, rgba(255,255,255,0.025) 4px + ); + pointer-events: none; + z-index: 0; + } + } +`; + +const LeftInner = styled.div` + position: relative; + z-index: 5; +`; + +const LogoWrap = styled.a` + display: flex; + align-items: center; + gap: 0.6rem; + text-decoration: none; + position: relative; + z-index: 5; +`; + +const LogoIcon = styled.div` + width: 32px; height: 32px; + border-radius: 50%; + background: rgba(255,255,255,0.2); + border: 1.5px solid rgba(255,255,255,0.35); + display: flex; align-items: center; justify-content: center; +`; + +const LogoText = styled.span` + font-size: 0.9rem; + font-weight: 500; + color: rgba(255,255,255,0.9); +`; + +const LeftHeading = styled.h2` + font-family: ${theme.fonts.serif}; + font-size: 3rem; + font-weight: 300; + line-height: 1.15; + color: #fff; + margin-bottom: 1rem; + animation: ${fadeUp} 0.8s ease both; + + em { font-style: italic; color: rgba(255,255,255,0.78); } +`; + +const LeftSubtext = styled.p` + font-size: 0.88rem; + font-weight: 300; + color: rgba(255,255,255,0.85); + line-height: 1.7; + max-width: 280px; +`; + +const TrustBadges = styled.div` + position: relative; + z-index: 5; + display: flex; + flex-direction: column; + gap: 0.6rem; +`; + +const TrustItem = styled.div` + display: flex; + align-items: center; + gap: 0.7rem; + font-size: 0.75rem; + color: rgba(255,255,255,0.85); +`; + +const TrustIcon = styled.div` + width: 28px; height: 28px; + border-radius: 50%; + background: rgba(255,255,255,0.15); + border: 1px solid rgba(255,255,255,0.28); + display: flex; align-items: center; justify-content: center; + flex-shrink: 0; +`; + +// ─── Höger formpanel ────────────────────────────────────────── +const RightPanel = styled.div` + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + padding: 2.5rem 1.4rem; + + @media (min-width: 700px) { + background: rgba(10,40,46,0.88); + backdrop-filter: blur(20px); + border-left: 1px solid rgba(255,255,255,0.1); + padding: 3rem; + animation: ${fadeLeft} 0.8s 0.2s ease both; + } +`; + +// Mobil-logga — bara synlig på mobil ────────────────────────── +const MobileLogo = styled.div` + display: flex; + align-items: center; + gap: 0.6rem; + margin-bottom: 2rem; + z-index: 10; + position: relative; + + @media (min-width: 700px) { + display: none; + } +`; + +const FormHeading = styled.h3` + font-family: ${theme.fonts.serif}; + font-size: 2rem; + font-weight: 300; + color: #fff; + margin-bottom: 0.4rem; +`; + +const FormSubtext = styled.p` + font-size: 0.82rem; + color: rgba(255,255,255,0.65); + margin-bottom: 2rem; +`; + +const FormGroup = styled.div` + margin-bottom: 1.2rem; +`; + +const Label = styled.label` + display: block; + font-size: 0.75rem; + font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; + color: rgba(255,255,255,0.65); + margin-bottom: 0.5rem; +`; + +const Input = styled.input` + width: 100%; + background: rgba(255,255,255,0.1); + border: 1px solid rgba(255,255,255,0.2); + border-radius: 10px; + padding: 0.9rem 1.1rem; + font-size: 1rem; /* 16px — förhindrar zoom på iOS */ + font-weight: 400; + color: #fff; + outline: none; + transition: border-color 0.2s, background 0.2s; + -webkit-appearance: none; + + &::placeholder { color: rgba(255,255,255,0.35); } + &:focus { + border-color: rgba(125,255,212,0.5); + background: rgba(255,255,255,0.14); + } +`; + +const ForgotRow = styled.div` + display: flex; + justify-content: flex-end; + margin-top: -0.5rem; + margin-bottom: 1.5rem; +`; + +const ForgotLink = styled.a` + font-size: 0.75rem; + color: rgba(255,255,255,0.6); + text-decoration: none; + transition: color 0.2s; + &:hover { color: rgba(255,255,255,0.9); } +`; + +const SubmitBtn = styled.button` + width: 100%; + background: rgba(255,255,255,0.92); + color: ${theme.colors.tealDeep}; + border: none; + border-radius: 100px; + padding: 1rem; + font-size: 1rem; + font-weight: 500; + cursor: pointer; + transition: background 0.2s, transform 0.15s; + box-shadow: 0 8px 24px rgba(0,0,0,0.2); + + &:hover { background: #fff; transform: translateY(-1px); } + &:active { transform: scale(0.98); } +`; + +const Divider = styled.div` + display: flex; + align-items: center; + gap: 1rem; + margin: 1.4rem 0; +`; + +const DividerLine = styled.div` + flex: 1; + height: 1px; + background: rgba(255,255,255,0.15); +`; + +const DividerText = styled.span` + font-size: 0.72rem; + color: rgba(255,255,255,0.55); + letter-spacing: 0.04em; +`; + +const GhostBtn = styled.button` + width: 100%; + background: transparent; + color: rgba(255,255,255,0.8); + border: 1px solid rgba(255,255,255,0.25); + border-radius: 100px; + padding: 1rem; + font-size: 0.9rem; + font-weight: 400; + cursor: pointer; + transition: border-color 0.2s, color 0.2s; + &:hover { border-color: rgba(255,255,255,0.45); color: #fff; } + &:active { transform: scale(0.98); } +`; + +const RegisterLink = styled.p` + text-align: center; + margin-top: 1.5rem; + font-size: 0.8rem; + color: rgba(255,255,255,0.6); + + a { + color: ${theme.colors.mint}; + text-decoration: none; + &:hover { color: #fff; } + } +`; + +const CornerLabel = styled.span` + display: none; + + @media (min-width: 700px) { + display: block; + position: absolute; + color: rgba(255,255,255,0.45); + font-size: 0.62rem; + letter-spacing: 0.05em; + z-index: 10; + + ${({ pos }) => ({ + 'tl': 'top: 1rem; left: 1.2rem;', + 'tr': 'top: 1rem; right: 1.2rem;', + 'bl': 'bottom: 1rem; left: 1.2rem;', + 'br': 'bottom: 1rem; right: 1.2rem;', + }[pos])} + } +`; + +// ─── Page ───────────────────────────────────────────────────── +const LoginPage = ({ onLogin }) => { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + + const handleSubmit = (e) => { + e.preventDefault(); + if (onLogin) onLogin({ email }); + window.location.href = '/symptomkoll'; + }; + + return ( +
tillbaka + Din hälsojournal och symtomhistorik väntar. Logga in för att fortsätta din riskbedömning. + + + Evidensbaserad medicinsk information + Krypterad och säker datahantering + GDPR-säker hantering av persondata + + + + {/* Höger — formulär */} + + {/* Logga bara synlig på mobil */} + + + + + + + Stoppa Proppen + + + Logga in + Ange dina uppgifter för att fortsätta + +
+ + + setEmail(e.target.value)} required /> + + + + setPassword(e.target.value)} required /> + + + Glömt lösenordet? + + Logga in +
+ + + ELLER + + + window.location.href = '/symptomkoll'}> + Fortsätt som gäst + + + + Inget konto? Skapa ett här + +
+ + + ); +}; + +const StarIcon = () => ( + + + +); +const LockIcon = () => ( + + + + +); +const CheckCircleIcon = () => ( + + + + +); + +export default LoginPage; From 833b70b7794d16aa5aa6c330e6016d29781174e9 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Fri, 20 Feb 2026 11:28:03 +0100 Subject: [PATCH 04/48] deploy netlify --- frontend/public/_redirects | 1 + 1 file changed, 1 insertion(+) create mode 100644 frontend/public/_redirects diff --git a/frontend/public/_redirects b/frontend/public/_redirects new file mode 100644 index 000000000..50a463356 --- /dev/null +++ b/frontend/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 \ No newline at end of file From a84c4989d6e8575f9c555928d927105f301d1312 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Thu, 26 Feb 2026 11:24:51 +0100 Subject: [PATCH 05/48] added medicinpage, added tillbaka button, added remove and edit in medicin page --- frontend/index.html | 2 +- frontend/src/App.jsx | 2 + frontend/src/components/Navbar.jsx | 34 +- frontend/src/pages/MedicinPage.jsx | 1025 ++++++++++++++++++++++++++++ 4 files changed, 1061 insertions(+), 2 deletions(-) create mode 100644 frontend/src/pages/MedicinPage.jsx diff --git a/frontend/index.html b/frontend/index.html index 664410b5b..bc7675a9c 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - Technigo React Vite Boiler Plate + Stoppa Proppen
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 0078f70a0..7f79bbe9e 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -4,6 +4,7 @@ import GlobalStyles from "./styles/Globalstyles"; import { theme } from "./styles/theme"; import { BrowserRouter, Routes, Route } from "react-router-dom"; import LoginPage from "./pages/LoginPage"; +import MedicinPage from './pages/MedicinPage.jsx'; export const App = () => { @@ -15,6 +16,7 @@ export const App = () => { } /> } /> + } /> diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index fe6b3c80b..50cc2d60a 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -165,6 +165,24 @@ const MobileCta = styled.a` &:hover { background: #fff; } `; +// Tillbaka-knapp i app-varianten — tar användaren till startsidan +const BackBtn = styled.a` + display: flex; + align-items: center; + gap: 0.4rem; + font-size: 0.82rem; + font-weight: 400; + color: rgba(255,255,255,0.75); + text-decoration: none; + padding: 0.4rem 0.8rem; + border-radius: 100px; + border: 1px solid rgba(255,255,255,0.2); + transition: color 0.2s, border-color 0.2s; + + &:hover { color: #fff; border-color: rgba(255,255,255,0.4); } + &:active { transform: scale(0.97); } +`; + // ─── User pill (app variant) ────────────────────────────────── const UserPill = styled.div` display: flex; @@ -196,6 +214,9 @@ const Avatar = styled.div` const Navbar = ({ variant = 'default', user = null, logoHref = '/' }) => { const [menuOpen, setMenuOpen] = useState(false); + const toggleMenu = () => setMenuOpen(prev => !prev); + const closeMenu = () => setMenuOpen(false); + return ( <> {/* Hamburgare på mobil — outside Nav to avoid its stacking context */} {variant === 'default' && ( - setMenuOpen(o => !o)} aria-label="Meny"> + @@ -244,6 +275,7 @@ const Navbar = ({ variant = 'default', user = null, logoHref = '/' }) => { setMenuOpen(false)}>Om oss setMenuOpen(false)}>Hur det fungerar setMenuOpen(false)}>FAQ + setMenuOpen(false)}>Medicinkoll setMenuOpen(false)}>Kom igång gratis )} diff --git a/frontend/src/pages/MedicinPage.jsx b/frontend/src/pages/MedicinPage.jsx new file mode 100644 index 000000000..a56b5efcf --- /dev/null +++ b/frontend/src/pages/MedicinPage.jsx @@ -0,0 +1,1025 @@ +// ───────────────────────────────────────────────────────────────────────────── +// MedicinPage.jsx — MOBILE FIRST +// +// Alla styled-components är skrivna mobile-first: +// - Basstilarna (utanför @media) gäller för mobil +// - @media (min-width: Xpx) lägger till eller skriver över för större skärmar +// +// Brytpunkter som används: +// 480px = stora mobiltelefoner (iPhone Plus, Pixel XL) +// 768px = surfplatta och desktop +// ───────────────────────────────────────────────────────────────────────────── + +import React, { useState } from 'react'; +import styled, { keyframes, css } from 'styled-components'; + +// react-toastify: toast = triggar en notis, ToastContainer = måste finnas i trädet +import { toast, ToastContainer } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; + +// Frame = yttersta wrapper, Card = gradient-kortet, +// CornerLabel = dekorativa hörn-texter (bara desktop), fadeUp = animation +import { Frame, Card, CornerLabel, fadeUp } from '../components/Layout.jsx'; +import Navbar from '../components/Navbar.jsx'; +import { theme } from '../styles/theme'; + + +// ─── ANIMATIONER ───────────────────────────────────────────────────────────── + +// Glider upp från nedanför — används på medicin-korten och modal-sheeten +const slideUp = keyframes` + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +`; + +// Studs-animation när en medicin markeras som tagen +const checkPop = keyframes` + 0% { transform: scale(0.8); } + 60% { transform: scale(1.2); } + 100% { transform: scale(1); } +`; + +// Enkel fade-in för modal-bakgrunden +const fadeIn = keyframes` + from { opacity: 0; } + to { opacity: 1; } +`; + + +// ─── SCROLL-AREA ───────────────────────────────────────────────────────────── +// Innehåller hela medicin-listan och kan scrollas. +// padding-bottom är stor (7rem) så innehållet inte döljs bakom den fasta knappen. +// Scrollbaren döljs visuellt för ett renare mobilutseende. +// +// MOBIL (bas): full bredd, mindre sidpadding +// DESKTOP 768+: begränsad max-bredd och centreras med margin: auto +const ScrollArea = styled.div` + /* MOBIL: full bredd, lite luft på sidorna */ + position: relative; + z-index: 10; + flex: 1; + overflow-y: auto; + padding: 0.5rem 1.4rem 7rem; /* stor bottom-padding pga den fasta knappen */ + + /* Döljer scrollbaren i Firefox */ + scrollbar-width: none; + /* Döljer scrollbaren i Chrome/Safari */ + &::-webkit-scrollbar { display: none; } + + /* DESKTOP 768+: begränsa bredden och centrera */ + @media (min-width: 768px) { + padding: 0.5rem 2.5rem 5rem; + max-width: 640px; + margin: 0 auto; + width: 100%; + } +`; + + +// ─── DAGSSTATUS-SEKTION ─────────────────────────────────────────────────────── +// Visar datum, veckodagsnamn och hur många mediciner som är kvar. +// Inga responsiva skillnader här — fungerar lika bra på alla skärmar. + +// Wrapper för hela blocket, glider in vid sidladdning +const DayHeader = styled.div` + margin-bottom: 1.5rem; + animation: ${fadeUp} 0.6s ease both; +`; + +// Liten etikett ovanför, t.ex. "26 februari" +const DayLabel = styled.div` + font-size: 0.68rem; + font-weight: 500; + letter-spacing: 0.1em; + text-transform: uppercase; + color: rgba(255,255,255,0.55); + margin-bottom: 0.3rem; +`; + +// Stor serif-rubrik, t.ex. "Måndag — 2 kvar" +// clamp() gör fontstorleken responsiv utan media queries: +// minimum 1.6rem, ökar med 5% av viewportbredden, max 2.2rem +const DayTitle = styled.h2` + font-family: ${theme.fonts.serif}; + font-size: clamp(1.6rem, 5vw, 2.2rem); + font-weight: 300; + color: #fff; + line-height: 1.2; + em { font-style: italic; color: rgba(255,255,255,0.65); } +`; + +// Undertext, t.ex. "1 av 2 mediciner tagna idag" +const DaySubtext = styled.p` + font-size: 0.82rem; + color: rgba(255,255,255,0.6); + margin-top: 0.4rem; +`; + + +// ─── SEKTION-RUBRIK ─────────────────────────────────────────────────────────── +// Rubrikerna "Idag" och "Imorgon". +// ::after skapar den horisontella linjen till höger om texten. +// Inga responsiva skillnader. +const SectionTitle = styled.div` + font-size: 0.65rem; + font-weight: 500; + letter-spacing: 0.1em; + text-transform: uppercase; + color: rgba(255,255,255,0.45); + margin: 1.4rem 0 0.7rem; + display: flex; + align-items: center; + gap: 0.8rem; + + &::after { + content: ''; + flex: 1; + height: 1px; + background: rgba(255,255,255,0.1); + } +`; + + +// ─── MEDICIN-KORT ───────────────────────────────────────────────────────────── +// $taken (boolean): styr grön eller neutral färgton +// $delay (sträng): animationsfördröjning per kort för sekventiell inglid +// +// MOBIL (bas): kompaktare padding +// DESKTOP 768+: lite mer luft +const MedCard = styled.div` + /* MOBIL: kompakt padding */ + background: ${({ $taken }) => $taken ? 'rgba(125,255,212,0.08)' : 'rgba(0,0,0,0.25)'}; + border: 1px solid ${({ $taken }) => $taken ? 'rgba(125,255,212,0.3)' : 'rgba(255,255,255,0.15)'}; + border-radius: 14px; + padding: 0.85rem 1rem; + margin-bottom: 0.6rem; + display: flex; + align-items: center; + gap: 0.75rem; + transition: border-color 0.3s, background 0.3s; + animation: ${slideUp} 0.5s ${({ $delay }) => $delay || '0s'} ease both; + + /* DESKTOP 768+: mer luft och rundare hörn */ + @media (min-width: 768px) { + border-radius: 16px; + padding: 1rem 1.2rem; + gap: 0.9rem; + margin-bottom: 0.7rem; + } +`; + +// Rund checkknapp till vänster. +// $taken styr färg. checkPop-animationen spelas bara när $taken är true. +// Storleken är 40px på mobil (lättare att trycka) och 36px på desktop. +const CheckBtn = styled.button` + /* MOBIL: lite större för enklare touch-interaktion */ + width: 40px; + height: 40px; + border-radius: 50%; + border: 1.5px solid ${({ $taken }) => $taken ? 'rgba(125,255,212,0.8)' : 'rgba(255,255,255,0.3)'}; + background: ${({ $taken }) => $taken ? 'rgba(125,255,212,0.2)' : 'transparent'}; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + flex-shrink: 0; + transition: all 0.2s; + + ${({ $taken }) => $taken && css`animation: ${checkPop} 0.3s ease;`} + + &:active { transform: scale(0.92); } + + /* DESKTOP 768+: standard storlek */ + @media (min-width: 768px) { + width: 36px; + height: 36px; + } +`; + +// Mittencolumn med namn och dos — klickbar för att öppna redigera-modalen +const MedInfo = styled.div` + flex: 1; + min-width: 0; /* förhindrar text-overflow utanför kortet */ + cursor: pointer; +`; + +// Medicinnamn med genomstrykning och tonad färg när tagen +const MedName = styled.div` + /* MOBIL: standard storlek */ + font-size: 0.92rem; + font-weight: 500; + color: ${({ $taken }) => $taken ? 'rgba(255,255,255,0.45)' : '#fff'}; + text-decoration: ${({ $taken }) => $taken ? 'line-through' : 'none'}; + transition: color 0.3s; + + /* DESKTOP 768+: något större */ + @media (min-width: 768px) { + font-size: 0.95rem; + } +`; + +// Dos-text, t.ex. "5 mg" +const MedDose = styled.div` + font-size: 0.75rem; + color: rgba(255,255,255,0.4); + margin-top: 0.15rem; +`; + +// Höger kolumn: tid, tagen-klockslag och åtgärdsknappar +const MedRight = styled.div` + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 0.3rem; +`; + +// Tidtext, t.ex. "kl 20:00" (amber) eller "✓ Tagen" (grön) +const MedTime = styled.div` + font-size: 0.72rem; + font-weight: 500; + color: ${({ $taken }) => $taken ? 'rgba(125,255,212,0.7)' : 'rgba(255,217,125,0.9)'}; + white-space: nowrap; +`; + +// Klockslag för när medicinen faktiskt togs, t.ex. "08:14" +const TakenTime = styled.div` + font-size: 0.68rem; + color: rgba(125,255,212,0.55); +`; + + +// ─── ÅTGÄRDSKNAPPAR (REDIGERA / TA BORT) ───────────────────────────────────── +// $danger (boolean): false = neutral (penna), true = röd (papperskorg) +// +// MOBIL (bas): lite större (44px) för touch +// DESKTOP 768+: standard (30px) + +const ActionRow = styled.div` + display: flex; + gap: 0.35rem; + flex-shrink: 0; +`; + +const IconBtn = styled.button` + /* MOBIL: touch-vänlig storlek (44px rekommenderas av Apple/Google) */ + width: 34px; + height: 34px; + border-radius: 8px; + border: 1px solid ${({ $danger }) => $danger ? 'rgba(255,138,125,0.3)' : 'rgba(255,255,255,0.15)'}; + background: ${({ $danger }) => $danger ? 'rgba(255,138,125,0.1)' : 'rgba(255,255,255,0.06)'}; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: ${({ $danger }) => $danger ? '#ff8a7d' : 'rgba(255,255,255,0.6)'}; + transition: all 0.2s; + + &:active { transform: scale(0.92); } + + /* DESKTOP 768+: kompaktare storlek + hover-effekter (hover finns inte på touch) */ + @media (min-width: 768px) { + width: 30px; + height: 30px; + + &:hover { + background: ${({ $danger }) => $danger ? 'rgba(255,138,125,0.2)' : 'rgba(255,255,255,0.12)'}; + color: ${({ $danger }) => $danger ? '#ff8a7d' : '#fff'}; + border-color: ${({ $danger }) => $danger ? 'rgba(255,138,125,0.5)' : 'rgba(255,255,255,0.3)'}; + } + } +`; + + +// ─── IMORGON-KORT ───────────────────────────────────────────────────────────── +// Nedtonade, informativa kort utan åtgärdsknappar. +// Inga responsiva skillnader behövs här. +const TomorrowCard = styled.div` + background: rgba(0,0,0,0.15); + border: 1px solid rgba(255,255,255,0.08); + border-radius: 14px; + padding: 0.9rem 1.2rem; + margin-bottom: 0.5rem; + display: flex; + align-items: center; + gap: 0.8rem; + opacity: 0.6; +`; + +const TomorrowDot = styled.div` + width: 8px; height: 8px; + border-radius: 50%; + border: 1.5px solid rgba(255,255,255,0.35); + flex-shrink: 0; +`; + +const TomorrowName = styled.div` + font-size: 0.88rem; + color: rgba(255,255,255,0.65); + flex: 1; +`; + +const TomorrowTime = styled.div` + font-size: 0.72rem; + color: rgba(255,255,255,0.35); +`; + + +// ─── LÄGG TILL-KNAPP (FAST LÄNGST NER) ─────────────────────────────────────── +// Knappen är fixed längst ner hela tiden. +// Gradienten bakom gör att listan tonas bort naturligt. +// +// MOBIL (bas): sträcker sig full bredd +// DESKTOP 768+: centreras och begränsas till 640px (samma bredd som innehållet) +const AddBtnWrap = styled.div` + /* MOBIL: full bredd */ + position: fixed; + bottom: 2.5rem; left: 0; right: 0; + z-index: 50; + padding: 1rem 1.4rem 1.6rem; + + /* DESKTOP 768+: centrera med max-bredd */ + @media (min-width: 768px) { + max-width: 640px; + left: 50%; + transform: translateX(-50%); + padding: 1rem 2.5rem 1.4rem; + } +`; + +const AddBtn = styled.button` + width: 100%; + display: flex; + align-items: center; + justify-content: center; + gap: 0.6rem; + background: rgba(255,255,255,0.92); + color: ${theme.colors.tealDeep}; + border: none; + border-radius: 100px; + padding: 0.95rem; + font-size: 0.92rem; + font-weight: 500; + cursor: pointer; + box-shadow: 0 8px 30px rgba(0,0,0,0.3); + transition: background 0.2s, transform 0.15s; + + /* Hover finns bara på desktop — på mobil är det :active som gäller */ + @media (min-width: 768px) { + &:hover { background: #fff; transform: translateY(-1px); } + } + &:active { transform: scale(0.98); } +`; + + +// ─── MODAL BACKDROP ─────────────────────────────────────────────────────────── +// Täcker hela skärmen bakom modalen. +// Klick på backdropen (inte på Sheet) stänger modalen via e.target check i JSX. +// +// MOBIL (bas): align-items: flex-end → Sheet kommer nerifrån (bottom sheet) +// DESKTOP 768+: align-items: center → Sheet centreras på skärmen +const Backdrop = styled.div` + /* MOBIL: modal glider upp nerifrån */ + position: fixed; + inset: 0; + background: rgba(0,0,0,0.65); + backdrop-filter: blur(6px); + z-index: 200; + display: flex; + align-items: flex-end; /* bottom sheet på mobil */ + justify-content: center; + animation: ${fadeIn} 0.2s ease both; + + /* DESKTOP 768+: centrerad modal */ + @media (min-width: 768px) { + align-items: center; + } +`; + + +// ─── BEKRÄFTELSE-DIALOG (TA BORT) ───────────────────────────────────────────── +// Visas när användaren trycker papperskorgen. +// Kräver ett extra klick för att bekräfta — förhindrar misstag. +// +// MOBIL (bas): bottom sheet (inga rundade underhörn) +// DESKTOP 768+: centrerad dialog med rundade hörn runt om +const ConfirmSheet = styled.div` + /* MOBIL: bottom sheet stil */ + background: #0d3d45; + border: 1px solid rgba(255,138,125,0.25); + border-radius: 20px 20px 0 0; /* rundade hörn bara uppåt */ + padding: 1.6rem 1.4rem 2.2rem; + width: 100%; + max-width: 400px; + animation: ${slideUp} 0.3s ease both; + text-align: center; + + /* DESKTOP 768+: dialog centreras med rundade hörn runt om */ + @media (min-width: 768px) { + border-radius: 20px; + margin: 1rem; + padding: 2rem; + } +`; + +const ConfirmTitle = styled.h3` + font-family: ${theme.fonts.serif}; + font-size: 1.4rem; + font-weight: 300; + color: #fff; + margin-bottom: 0.5rem; +`; + +const ConfirmSub = styled.p` + font-size: 0.82rem; + color: rgba(255,255,255,0.5); + margin-bottom: 1.5rem; +`; + +const DeleteConfirmBtn = styled.button` + width: 100%; + background: rgba(255,138,125,0.2); + color: #ff8a7d; + border: 1px solid rgba(255,138,125,0.4); + border-radius: 100px; + padding: 0.9rem; + font-size: 0.9rem; + font-weight: 500; + cursor: pointer; + margin-bottom: 0.5rem; + transition: background 0.2s; + + @media (min-width: 768px) { + &:hover { background: rgba(255,138,125,0.3); } + } + &:active { transform: scale(0.98); } +`; + + +// ─── BOTTOM SHEET / MODAL (LÄGG TILL & REDIGERA) ───────────────────────────── +// Samma Sheet-komponent används för båda lägena. +// modalMode ('add' | 'edit') styr rubrik och knapp-text. +// +// MOBIL (bas): bottom sheet, glider upp nerifrån +// DESKTOP 768+: centrerad dialog +const Sheet = styled.div` + /* MOBIL: full bredd, bottom sheet */ + background: #0d3d45; + border: 1px solid rgba(255,255,255,0.15); + border-radius: 20px 20px 0 0; + padding: 1.6rem 1.4rem 2.2rem; + width: 100%; + max-width: 500px; + animation: ${slideUp} 0.3s ease both; + + /* DESKTOP 768+: centrerad modal */ + @media (min-width: 768px) { + border-radius: 24px; + margin: 1rem; + padding: 2rem; + } +`; + +// Litet handtag längst upp — konvention för bottom sheets på mobil. +// Döljs på desktop eftersom det inte är ett bottom sheet där. +const SheetHandle = styled.div` + /* MOBIL: synligt handtag */ + width: 36px; + height: 3px; + background: rgba(255,255,255,0.2); + border-radius: 2px; + margin: 0 auto 1.5rem; + + /* DESKTOP 768+: döljs */ + @media (min-width: 768px) { + display: none; + } +`; + +const SheetTitle = styled.h3` + font-family: ${theme.fonts.serif}; + font-size: 1.6rem; + font-weight: 300; + color: #fff; + margin-bottom: 1.5rem; +`; + +const FieldGroup = styled.div` + margin-bottom: 1.1rem; +`; + +const FieldLabel = styled.label` + display: block; + font-size: 0.7rem; + font-weight: 500; + letter-spacing: 0.07em; + text-transform: uppercase; + color: rgba(255,255,255,0.5); + margin-bottom: 0.45rem; +`; + +// Textfält och tidsfält i formuläret. +// VIKTIGT: font-size 1rem (= 16px) på mobil förhindrar att iOS zooomar in +// automatiskt vid fokus — under 16px triggas auto-zoom. +// -webkit-appearance: none tar bort iOS standardstilar (border, shadow etc.) +const FieldInput = styled.input` + /* MOBIL: stor nog för att undvika iOS auto-zoom */ + width: 100%; + background: rgba(255,255,255,0.08); + border: 1px solid rgba(255,255,255,0.15); + border-radius: 10px; + padding: 0.85rem 1rem; /* touch-vänlig höjd */ + font-size: 1rem; /* 16px = iOS-gränsen för auto-zoom */ + color: #fff; + outline: none; + transition: border-color 0.2s; + -webkit-appearance: none; /* tar bort iOS standardstilar */ + + &::placeholder { color: rgba(255,255,255,0.22); } + &:focus { border-color: rgba(125,255,212,0.5); } +`; + +// Rad med dag-knappar (M T O T F L S) +// Inga responsiva skillnader — fungerar bra på alla skärmar +const DayRow = styled.div` + display: flex; + gap: 0.4rem; +`; + +// Dag-knapp. $active styr grön (vald) eller grå (ej vald) stil. +// flex: 1 gör att alla sju knappar tar lika mycket plats. +// Lite större padding på mobil för enklare touch. +const DayToggle = styled.button` + /* MOBIL: generös touch-yta */ + flex: 1; + padding: 0.65rem 0; + border-radius: 8px; + font-size: 0.72rem; + font-weight: 500; + border: 1px solid ${({ $active }) => $active ? 'rgba(125,255,212,0.5)' : 'rgba(255,255,255,0.15)'}; + background: ${({ $active }) => $active ? 'rgba(125,255,212,0.15)' : 'transparent'}; + color: ${({ $active }) => $active ? 'rgba(125,255,212,0.95)' : 'rgba(255,255,255,0.45)'}; + cursor: pointer; + transition: all 0.15s; + + &:active { transform: scale(0.95); } + + /* DESKTOP 768+: kompaktare padding */ + @media (min-width: 768px) { + padding: 0.55rem 0; + } +`; + +// Spara-knapp längst ner i formuläret +const SaveBtn = styled.button` + width: 100%; + background: rgba(255,255,255,0.92); + color: ${theme.colors.tealDeep}; + border: none; + border-radius: 100px; + padding: 0.95rem; + font-size: 0.92rem; + font-weight: 500; + cursor: pointer; + margin-top: 1.4rem; + transition: background 0.2s; + + @media (min-width: 768px) { + &:hover { background: #fff; } + } + &:active { transform: scale(0.98); } +`; + +// Avbryt-knapp — diskret textstil +const CancelBtn = styled.button` + width: 100%; + background: transparent; + color: rgba(255,255,255,0.45); + border: none; + padding: 0.75rem; + font-size: 0.85rem; + cursor: pointer; + margin-top: 0.3rem; + + @media (min-width: 768px) { + &:hover { color: rgba(255,255,255,0.75); } + } +`; + + +// ─── TOM-STATE ──────────────────────────────────────────────────────────────── +// Visas när medicines-listan är tom. +const EmptyState = styled.div` + text-align: center; + padding: 3rem 1rem; + animation: ${fadeUp} 0.6s 0.2s ease both; +`; + +const EmptyIcon = styled.div` + width: 56px; height: 56px; + border-radius: 50%; + background: rgba(255,255,255,0.06); + border: 1px solid rgba(255,255,255,0.1); + display: flex; align-items: center; justify-content: center; + margin: 0 auto 1rem; +`; + +const EmptyText = styled.p` + font-family: ${theme.fonts.serif}; + font-size: 1.3rem; + font-weight: 300; + color: rgba(255,255,255,0.55); + margin-bottom: 0.4rem; +`; + +const EmptySub = styled.p` + font-size: 0.8rem; + color: rgba(255,255,255,0.3); +`; + + +// ─── KONSTANTER OCH HJÄLPFUNKTIONER ────────────────────────────────────────── + +// Svenska förkortningar för dag-toggle-knapparna +const DAY_NAMES = ['Mån', 'Tis', 'Ons', 'Tor', 'Fre', 'Lör', 'Sön']; + +// Returnerar dagens namn på svenska. getDay() ger 0 (sön) – 6 (lör). +const getTodayName = () => { + const names = ['Söndag', 'Måndag', 'Tisdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lördag']; + return names[new Date().getDay()]; +}; + +// Returnerar datumet formaterat på svenska, t.ex. "26 februari" +const getDateString = () => + new Date().toLocaleDateString('sv-SE', { day: 'numeric', month: 'long' }); + +// Tomt formulär — används för att återställa vid öppna/stäng +const EMPTY_FORM = { name: '', dose: '', time: '08:00' }; + +// Återanvändbar hjälpfunktion för toast-stilar. +// Tar emot valfri border-färg och textfärg så varje typ ser olika ut. +const toastStyle = ( + border = 'rgba(255,255,255,0.15)', + color = 'rgba(255,255,255,0.9)' +) => ({ + position: 'top-center', + autoClose: 3000, + style: { + background: '#0d3d45', + color, + border: `1px solid ${border}`, + borderRadius: '12px', + fontSize: '0.88rem', + }, +}); + + +// ─── HUVUDKOMPONENT ─────────────────────────────────────────────────────────── +const MedicinPage = () => { + + // ── STATE ──────────────────────────────────────────────────────────────── + // Listan med mediciner. Varje objekt: + // id, name, dose, time ("HH:MM"), days ([0-6]), taken (bool), takenAt (str|null) + const [medicines, setMedicines] = useState([ + /* { id: 1, name: 'Waran', dose: '5 mg', time: '08:00', days: [0,1,2,3,4,5,6], taken: true, takenAt: '08:14' }, + { id: 2, name: 'Xarelto', dose: '20 mg', time: '20:00', days: [0,1,2,3,4,5,6], taken: false, takenAt: null }, */ + ]); + + // null = stängd, 'add' = lägg till, 'edit' = redigera + const [modalMode, setModalMode] = useState(null); + + // ID för medicinen som redigeras (null om ingen redigeras) + const [editingId, setEditingId] = useState(null); + + // ID för medicinen vars ta bort-dialog visas (null om ingen) + const [confirmId, setConfirmId] = useState(null); + + // Valda veckodagar i formuläret (array med index 0-6) + const [activeDays, setActiveDays] = useState([0,1,2,3,4,5,6]); + + // Formulärdata + const [form, setForm] = useState(EMPTY_FORM); + + // ── BERÄKNADE VÄRDEN ───────────────────────────────────────────────────── + const takenCount = medicines.filter(m => m.taken).length; + const remainingCount = medicines.filter(m => !m.taken).length; + + + // ── ÖPPNA "LÄGG TILL"-MODAL ─────────────────────────────────────────────── + const openAdd = () => { + setForm(EMPTY_FORM); + setActiveDays([0,1,2,3,4,5,6]); + setEditingId(null); + setModalMode('add'); + }; + + // ── ÖPPNA "REDIGERA"-MODAL ──────────────────────────────────────────────── + // Fyller formuläret med befintliga värden och sätter editingId + const openEdit = (med) => { + setForm({ name: med.name, dose: med.dose, time: med.time }); + setActiveDays(med.days); + setEditingId(med.id); + setModalMode('edit'); + }; + + // ── STÄNG MODAL ─────────────────────────────────────────────────────────── + const closeModal = () => { + setModalMode(null); + setEditingId(null); + }; + + + // ── SPARA (NY ELLER UPPDATERING) ───────────────────────────────────────── + const saveMedicine = () => { + if (!form.name.trim()) return; + + if (modalMode === 'edit') { + // map() returnerar ny array där bara rätt medicin är ändrad + setMedicines(prev => prev.map(m => + m.id === editingId + ? { ...m, name: form.name, dose: form.dose, time: form.time, days: activeDays } + : m + )); + toast(`✏️ ${form.name} uppdaterad!`, + toastStyle('rgba(125,255,212,0.3)', '#7dffd4')); + + } else { + const newMed = { + id: Date.now(), // unikt ID via tidsstämpel + name: form.name, dose: form.dose, time: form.time, + days: activeDays, taken: false, takenAt: null, + }; + setMedicines(prev => [...prev, newMed]); + + // Schemalägg toast-påminnelse om vald tid är senare idag + const [h, min] = form.time.split(':').map(Number); + const triggerTime = new Date(); + triggerTime.setHours(h, min, 0, 0); + const msUntil = triggerTime - Date.now(); + + if (msUntil > 0 && msUntil < 24 * 60 * 60 * 1000) { + setTimeout(() => { + toast(`🔔 Dags att ta ${form.name}! (${form.dose})`, + toastStyle('rgba(255,217,125,0.4)', '#fff')); + }, msUntil); + } + + toast(`💊 ${form.name} tillagd! Påminnelse kl ${form.time}.`, toastStyle()); + } + + closeModal(); + }; + + + // ── MARKERA SOM TAGEN / OTAGEN ──────────────────────────────────────────── + const toggleTaken = (id) => { + setMedicines(prev => prev.map(m => { + if (m.id !== id) return m; + if (!m.taken) { + const timeStr = new Date().toLocaleTimeString('sv-SE', { hour: '2-digit', minute: '2-digit' }); + toast(`✓ ${m.name} markerad som tagen!`, + toastStyle('rgba(125,255,212,0.3)', '#7dffd4')); + return { ...m, taken: true, takenAt: timeStr }; + } + return { ...m, taken: false, takenAt: null }; + })); + }; + + + // ── TA BORT ─────────────────────────────────────────────────────────────── + // filter() returnerar alla mediciner UTOM den med confirmId + const deleteMedicine = () => { + const med = medicines.find(m => m.id === confirmId); + setMedicines(prev => prev.filter(m => m.id !== confirmId)); + setConfirmId(null); + toast(`🗑 ${med?.name} borttagen.`, + toastStyle('rgba(255,138,125,0.3)', '#ff8a7d')); + }; + + + // ── TOGGA VECKODAG ──────────────────────────────────────────────────────── + const toggleDay = (i) => + setActiveDays(prev => + prev.includes(i) ? prev.filter(d => d !== i) : [...prev, i] + ); + + + // ── JSX ────────────────────────────────────────────────────────────────── + return ( + + + Medicinlåda + Stoppa Proppen + + + + + + {/* Dagsstatus */} + + {getDateString()} + + {getTodayName()}{' '} + {remainingCount === 0 ? '— allt klart! 🎉' : `— ${remainingCount} kvar`} + + {medicines.length > 0 && ( + {takenCount} av {medicines.length} mediciner tagna idag + )} + + + Idag + + {/* Tom-state eller medicin-lista */} + {medicines.length === 0 ? ( + + + Inga mediciner tillagda + Tryck på knappen nedan för att lägga till din första medicin + + ) : ( + // $delay förskjuter animationen per kort → sekventiell inglid + medicines.map((med, i) => ( + + + {/* Checkknapp — togglar tagen/otagen */} + toggleTaken(med.id)}> + {med.taken + ? + :
+ } + + + {/* Namn + dos — klick öppnar redigera-modalen */} + openEdit(med)}> + {med.name} + {med.dose} + + + {/* Höger kolumn */} + + + {med.taken ? '✓ Tagen' : `kl ${med.time}`} + + {med.takenAt && {med.takenAt}} + + + openEdit(med)} title="Redigera"> + + + setConfirmId(med.id)} title="Ta bort"> + + + + + + + )) + )} + + {/* Imorgon-sektion */} + {medicines.length > 0 && ( + <> + Imorgon + {medicines.map(med => ( + + + {med.name} — {med.dose} + kl {med.time} + + ))} + + )} + + + + {/* Fast Lägg till-knapp */} + + + + Lägg till medicin + + + + + + + + {/* ── LÄGG TILL / REDIGERA MODAL ── */} + {modalMode && ( + e.target === e.currentTarget && closeModal()}> + + + + {modalMode === 'edit' ? 'Redigera medicin' : 'Lägg till medicin'} + + + + Namn på medicin + setForm(f => ({ ...f, name: e.target.value }))} + autoFocus + /> + + + + Dos + setForm(f => ({ ...f, dose: e.target.value }))} + /> + + + + Påminnelsetid + setForm(f => ({ ...f, time: e.target.value }))} + /> + + + + Dagar + + {DAY_NAMES.map((day, i) => ( + toggleDay(i)} + > + {day.charAt(0)} + + ))} + + + + + {modalMode === 'edit' ? 'Spara ändringar' : 'Spara medicin'} + + Avbryt + + + )} + + + {/* ── BEKRÄFTELSE-DIALOG (TA BORT) ── */} + {confirmId && ( + e.target === e.currentTarget && setConfirmId(null)}> + + + Ta bort medicin? + + {medicines.find(m => m.id === confirmId)?.name} kommer att tas bort permanent. + + Ja, ta bort + setConfirmId(null)}>Avbryt + + + )} + + + ); +}; + + +// ─── SVG-IKONER ─────────────────────────────────────────────────────────────── + +const CheckIcon = ({ color = 'white' }) => ( + + + +); + +const PlusIcon = () => ( + + + +); + +const EditIcon = () => ( + + + +); + +const TrashIcon = () => ( + + + +); + +const PillIcon = () => ( + + + + +); + +export default MedicinPage; From 25a73fd23a06a2c4cdd31ef85cece6ea8111d201 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Fri, 27 Feb 2026 17:01:46 +0100 Subject: [PATCH 06/48] added my render adress --- backend/package.json | 3 ++- backend/server.js | 13 ++++++++++++- frontend/src/App.jsx | 1 + package.json | 10 ---------- 4 files changed, 15 insertions(+), 12 deletions(-) delete mode 100644 package.json diff --git a/backend/package.json b/backend/package.json index 08f29f244..aaa1c11d2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -13,8 +13,9 @@ "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", "cors": "^2.8.5", + "dotenv": "^17.3.1", "express": "^4.17.3", "mongoose": "^8.4.0", "nodemon": "^3.0.1" } -} \ No newline at end of file +} diff --git a/backend/server.js b/backend/server.js index 070c87518..341a2fcc2 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,12 +1,16 @@ import express from "express"; import cors from "cors"; import mongoose from "mongoose"; +import dotenv from 'dotenv'; + +dotenv.config(); + const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/final-project"; mongoose.connect(mongoUrl); mongoose.Promise = Promise; -const port = process.env.PORT || 8080; +const port = process.env.PORT || 8081; const app = express(); app.use(cors()); @@ -20,3 +24,10 @@ app.get("/", (req, res) => { app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); +mongoose.connect(mongoUrl) + .then(() => { + console.log("✅ Succé! Vi har kontakt med MongoDB Atlas."); + }) + .catch((err) => { + console.error("❌ Aj då, kunde inte ansluta till databasen:", err); + }); diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 7f79bbe9e..dec69354a 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -6,6 +6,7 @@ import { BrowserRouter, Routes, Route } from "react-router-dom"; import LoginPage from "./pages/LoginPage"; import MedicinPage from './pages/MedicinPage.jsx'; +const API_URL = "https://project-final-vq1d.onrender.com"; export const App = () => { diff --git a/package.json b/package.json deleted file mode 100644 index 917ab571d..000000000 --- a/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "project-final-parent", - "version": "1.0.0", - "scripts": { - "postinstall": "npm install --prefix backend" - }, - "dependencies": { - "date-fns": "^4.1.0" - } -} From ff3c7d2b921174e2e682c0baaacbe9bee3e2e49d Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Mon, 2 Mar 2026 08:52:13 +0100 Subject: [PATCH 07/48] bux fixes for netlify deployment --- frontend/index.html | 3 +++ frontend/src/pages/MedicinPage.jsx | 12 ------------ frontend/src/styles/Globalstyles.js | 2 -- netlify.toml | 9 +++++++++ 4 files changed, 12 insertions(+), 14 deletions(-) create mode 100644 netlify.toml diff --git a/frontend/index.html b/frontend/index.html index bc7675a9c..d4e87152f 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -5,6 +5,9 @@ Stoppa Proppen + + +
diff --git a/frontend/src/pages/MedicinPage.jsx b/frontend/src/pages/MedicinPage.jsx index a56b5efcf..f77fb1b8c 100644 --- a/frontend/src/pages/MedicinPage.jsx +++ b/frontend/src/pages/MedicinPage.jsx @@ -1,15 +1,3 @@ -// ───────────────────────────────────────────────────────────────────────────── -// MedicinPage.jsx — MOBILE FIRST -// -// Alla styled-components är skrivna mobile-first: -// - Basstilarna (utanför @media) gäller för mobil -// - @media (min-width: Xpx) lägger till eller skriver över för större skärmar -// -// Brytpunkter som används: -// 480px = stora mobiltelefoner (iPhone Plus, Pixel XL) -// 768px = surfplatta och desktop -// ───────────────────────────────────────────────────────────────────────────── - import React, { useState } from 'react'; import styled, { keyframes, css } from 'styled-components'; diff --git a/frontend/src/styles/Globalstyles.js b/frontend/src/styles/Globalstyles.js index 931e0f0be..ad3e6c680 100644 --- a/frontend/src/styles/Globalstyles.js +++ b/frontend/src/styles/Globalstyles.js @@ -1,8 +1,6 @@ import { createGlobalStyle } from 'styled-components'; const GlobalStyles = createGlobalStyle` - @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;1,300;1,400&family=DM+Sans:wght@300;400;500&display=swap'); - *, *::before, *::after { box-sizing: border-box; margin: 0; diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 000000000..8658d775f --- /dev/null +++ b/netlify.toml @@ -0,0 +1,9 @@ +[build] + base = "frontend" + command = "npm run build" + publish = "dist" + +[[redirects]] + from = "/*" + to = "/index.html" + status = 200 From c0077be673d6a09d16d14e3a98622a92db06a973 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 3 Mar 2026 09:22:07 +0100 Subject: [PATCH 08/48] added .env in frontend, added a faqpage, and 404 that navigates to the laning page --- frontend/src/App.jsx | 7 +- frontend/src/components/BottomNav.jsx | 123 +++++++++ frontend/src/pages/FaqPage.jsx | 368 ++++++++++++++++++++++++++ frontend/src/pages/MedicinPage.jsx | 2 +- 4 files changed, 496 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/BottomNav.jsx create mode 100644 frontend/src/pages/FaqPage.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index dec69354a..f7ce21327 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -4,9 +4,8 @@ import GlobalStyles from "./styles/Globalstyles"; import { theme } from "./styles/theme"; import { BrowserRouter, Routes, Route } from "react-router-dom"; import LoginPage from "./pages/LoginPage"; -import MedicinPage from './pages/MedicinPage.jsx'; - -const API_URL = "https://project-final-vq1d.onrender.com"; +import MedicinPage from "./pages/MedicinPage"; +import FaqPage from "./pages/FaqPage"; export const App = () => { @@ -18,6 +17,8 @@ export const App = () => { } /> } /> } /> + } /> + } /> diff --git a/frontend/src/components/BottomNav.jsx b/frontend/src/components/BottomNav.jsx new file mode 100644 index 000000000..b46545485 --- /dev/null +++ b/frontend/src/components/BottomNav.jsx @@ -0,0 +1,123 @@ +// ───────────────────────────────────────────────────────────────────────────── +// BottomNav.jsx +// +// Bottom navigation för app-sidor på mobil. +// Visar tre stora knappar med ikon + text längst ner på skärmen. +// Döljs på desktop (768px+) eftersom man navigerar via toppmeny/tillbaka-knapp. +// +// Används på: MedicinPage, SymptomCheckPage +// Props: +// active = sträng med aktiv sida: 'hem' | 'medicin' | 'symptomkoll' +// ───────────────────────────────────────────────────────────────────────────── + +import React from 'react'; +import styled from 'styled-components'; +import { theme } from '../styles/theme'; + +// ─── Wrapper ────────────────────────────────────────────────────────────────── +// Sitter fast längst ner på skärmen (position: fixed). +// Döljs helt på desktop — där navigerar man via navbar istället. +const Bar = styled.nav` + /* MOBIL: synlig, fast längst ner */ + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: 100; + + display: flex; + align-items: stretch; + + background: rgba(8, 35, 40, 0.97); + backdrop-filter: blur(16px); + border-top: 1px solid rgba(255,255,255,0.1); + + /* Extra padding i botten för iPhone-notch/home indicator */ + padding-bottom: env(safe-area-inset-bottom, 0px); + + /* DESKTOP 768+: döljs */ + @media (min-width: 768px) { + display: none; + } +`; + +// ─── Enskild nav-knapp ──────────────────────────────────────────────────────── +// flex: 1 gör att alla tre knappar tar exakt lika stor bredd. +// $active styr om knappen är markerad (ljusgrön) eller inte (grå). +const NavItem = styled.a` + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.3rem; + padding: 0.75rem 0.5rem; + text-decoration: none; + color: ${({ $active }) => $active ? theme.colors.mint : 'rgba(255,255,255,0.45)'}; + transition: color 0.2s; + + /* Liten highlight-linje längst upp på aktiv knapp */ + border-top: 2px solid ${({ $active }) => $active ? theme.colors.mint : 'transparent'}; + + &:active { opacity: 0.7; } +`; + +// Ikonwrapper +const Icon = styled.div` + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; +`; + +// Text under ikonen +const Label = styled.span` + font-size: 0.68rem; + font-weight: ${({ $active }) => $active ? '500' : '400'}; + letter-spacing: 0.02em; +`; + +// ─── Komponent ──────────────────────────────────────────────────────────────── +const BottomNav = ({ active = '' }) => ( + + + {/* Hem */} + + + + + + + + + + + {/* Medicinlåda */} + + + + + + + + + + + {/* Symtomkoll */} + + + + + + + + + + +); + +export default BottomNav; \ No newline at end of file diff --git a/frontend/src/pages/FaqPage.jsx b/frontend/src/pages/FaqPage.jsx new file mode 100644 index 000000000..1cc5e1e64 --- /dev/null +++ b/frontend/src/pages/FaqPage.jsx @@ -0,0 +1,368 @@ +import React, { useState } from 'react'; +import styled, { keyframes } from 'styled-components'; +import { Frame, Card, CornerLabel, fadeUp } from '../components/Layout.jsx'; +import Navbar from '../components/Navbar.jsx'; +import { theme } from '../styles/theme.js'; + +// ─── ANIMATIONER ───────────────────────────────────────────── +const slideDown = keyframes` + from { opacity: 0; transform: translateY(-6px); } + to { opacity: 1; transform: translateY(0); } +`; + +// ─── SCROLL-AREA ────────────────────────────────────────────── +const ScrollArea = styled.div` + position: relative; + z-index: 10; + flex: 1; + overflow-y: auto; + padding: 1.5rem 1.4rem 4rem; + scrollbar-width: none; + &::-webkit-scrollbar { display: none; } + + @media (min-width: 768px) { + padding: 2rem 2.5rem 4rem; + max-width: 720px; + margin: 0 auto; + width: 100%; + } +`; + +// ─── HERO ───────────────────────────────────────────────────── +const Hero = styled.div` + margin-bottom: 2.5rem; + animation: ${fadeUp} 0.7s ease both; +`; + +const Badge = styled.div` + display: inline-flex; + align-items: center; + gap: 0.5rem; + background: rgba(0,0,0,0.25); + border: 1px solid rgba(255,255,255,0.2); + border-radius: 100px; + padding: 0.3rem 0.9rem; + font-size: 0.68rem; + font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; + color: rgba(255,255,255,0.75); + margin-bottom: 1rem; +`; + +const BadgeDot = styled.span` + width: 5px; height: 5px; + border-radius: 50%; + background: ${theme.colors.amber}; +`; + +const H1 = styled.h1` + font-family: ${theme.fonts.serif}; + font-size: clamp(2rem, 6vw, 3.2rem); + font-weight: 300; + color: #fff; + line-height: 1.2; + margin-bottom: 0.9rem; + em { font-style: italic; color: rgba(255,255,255,0.6); } +`; + +const HeroText = styled.p` + font-size: 0.9rem; + color: rgba(255,255,255,0.65); + line-height: 1.75; + max-width: 480px; +`; + +// ─── KATEGORI-ETIKETT ────────────────────────────────────────── +const CategoryLabel = styled.div` + font-size: 0.65rem; + font-weight: 500; + letter-spacing: 0.12em; + text-transform: uppercase; + color: rgba(255,255,255,0.35); + margin: 2rem 0 0.8rem; + display: flex; + align-items: center; + gap: 0.8rem; + + &::after { + content: ''; + flex: 1; + height: 1px; + background: rgba(255,255,255,0.08); + } +`; + +// ─── FAQ-ACCORDION-KORT ─────────────────────────────────────── +// $open styr om svaret är synligt eller inte. +const FaqCard = styled.div` + background: rgba(0,0,0,0.22); + border: 1px solid ${({ $open }) => $open ? 'rgba(255,255,255,0.2)' : 'rgba(255,255,255,0.1)'}; + border-radius: 14px; + margin-bottom: 0.6rem; + overflow: hidden; + transition: border-color 0.2s; + animation: ${fadeUp} 0.5s ${({ $delay }) => $delay || '0s'} ease both; +`; + +// Klickbar rubrikrad +const FaqQuestion = styled.button` + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 1.1rem 1.2rem; + background: transparent; + border: none; + cursor: pointer; + text-align: left; +`; + +const QuestionText = styled.span` + font-size: 0.9rem; + font-weight: 500; + color: #fff; + line-height: 1.4; + + @media (min-width: 768px) { + font-size: 0.95rem; + } +`; + +// Roterande pil-ikon +const Chevron = styled.span` + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 24px; height: 24px; + color: rgba(255,255,255,0.4); + transform: ${({ $open }) => $open ? 'rotate(180deg)' : 'rotate(0deg)'}; + transition: transform 0.25s ease; +`; + +// Svar-text — döljs/visas med en height-animation via max-height +const FaqAnswer = styled.div` + /* max-height trick: 0 = döljs, stort värde = visas */ + max-height: ${({ $open }) => $open ? '400px' : '0'}; + overflow: hidden; + transition: max-height 0.35s ease; +`; + +const AnswerInner = styled.div` + padding: 0 1.2rem 1.2rem; + font-size: 0.84rem; + font-weight: 300; + color: rgba(255,255,255,0.65); + line-height: 1.8; + border-top: 1px solid rgba(255,255,255,0.08); + padding-top: 0.9rem; + animation: ${({ $open }) => $open ? slideDown : 'none'} 0.3s ease both; +`; + +// ─── KONTAKT-KORT ───────────────────────────────────────────── +const ContactCard = styled.div` + background: rgba(255,217,125,0.07); + border: 1px solid rgba(255,217,125,0.2); + border-radius: 16px; + padding: 1.6rem 1.4rem; + margin-top: 2.5rem; + display: flex; + flex-direction: column; + gap: 0.5rem; + animation: ${fadeUp} 0.6s 0.3s ease both; + + @media (min-width: 600px) { + flex-direction: row; + align-items: center; + justify-content: space-between; + } +`; + +const ContactText = styled.div``; + +const ContactTitle = styled.div` + font-family: ${theme.fonts.serif}; + font-size: 1.2rem; + font-weight: 300; + color: #fff; + margin-bottom: 0.25rem; +`; + +const ContactSub = styled.div` + font-size: 0.78rem; + color: rgba(255,255,255,0.5); +`; + +const ContactBtn = styled.a` + display: inline-flex; + align-items: center; + gap: 0.4rem; + background: rgba(255,217,125,0.15); + border: 1px solid rgba(255,217,125,0.35); + color: ${theme.colors.amber}; + border-radius: 100px; + padding: 0.6rem 1.3rem; + font-size: 0.82rem; + font-weight: 500; + text-decoration: none; + white-space: nowrap; + transition: background 0.2s; + + &:hover { background: rgba(255,217,125,0.25); } + &:active { transform: scale(0.97); } +`; + +// ─── FAQ-DATA ───────────────────────────────────────────────── +// Kategorier med frågor och svar. +// Ingen data hämtas från API — allt är hårdkodat här i filen. +const FAQ_DATA = [ + { + category: 'Om appen', + items: [ + { + q: 'Vad är Stoppa Proppen?', + a: 'Stoppa Proppen är en gratis hälsoapp som hjälper dig förstå, förebygga och hantera blodpropp. Appen erbjuder en symtomkoll, medicinpåminnelser och ett community där du kan möta andra med liknande erfarenheter.', + }, + { + q: 'Kostar det något att använda appen?', + a: 'Nej, Stoppa Proppen är helt gratis att använda. Vi tar inga betalt för grundfunktionerna och visar inga annonser.', + }, + { + q: 'Är appen godkänd av sjukvården?', + a: 'Appen är inte ett medicintekniskt hjälpmedel och ersätter inte sjukvård. All medicinsk information är baserad på Socialstyrelsens och 1177:s riktlinjer. Vid akuta symtom ska du alltid ringa 112 eller kontakta sjukvården.', + }, + ], + }, + { + category: 'Symtomkollen', + items: [ + { + q: 'Hur fungerar symtomkollen?', + a: 'Du svarar på ett antal frågor om dina symtom och riskfaktorer. Baserat på dina svar ger appen en preliminär riskbedömning. Det är viktigt att förstå att detta inte är en medicinsk diagnos — kontakta alltid sjukvården om du är orolig.', + }, + { + q: 'Vilka symtom på blodpropp bör jag känna till?', + a: 'Vanliga symtom på djup ventrombos (DVT) inkluderar svullnad, värme och ömhet i ett ben. Lungembolism kan ge plötslig andnöd, bröstsmärta och hosta. Vid dessa symtom — ring 112 omedelbart.', + }, + { + q: 'Sparas mina svar från symtomkollen?', + a: 'Just nu sparas inga svar permanet mellan sessioner. I kommande versioner av appen planerar vi att låta inloggade användare se sin historik.', + }, + ], + }, + { + category: 'Medicinpåminnelser', + items: [ + { + q: 'Hur lägger jag till en medicin?', + a: 'Gå till Medicinlådan via menyn och tryck på "Lägg till medicin". Fyll i namn, dos, tid och vilka dagar du tar medicinen. Appen påminner dig när det är dags.', + }, + { + q: 'Vad händer om jag missar en påminnelse?', + a: 'Påminnelsen visas som en notis om appen är öppen. I en framtida version planerar vi push-notiser som fungerar även när appen är stängd. Kontakta alltid din läkare vid frågor om missade doser.', + }, + { + q: 'Kan jag lägga till flera mediciner?', + a: 'Ja, du kan lägga till hur många mediciner som helst. Varje medicin kan ha sin egen tid och sina egna veckodagar.', + }, + ], + }, + { + category: 'Integritet & säkerhet', + items: [ + { + q: 'Hur hanteras mina personuppgifter?', + a: 'Vi samlar bara in den information som krävs för att appen ska fungera. Vi säljer aldrig dina uppgifter till tredje part. Läs vår integritetspolicy för fullständig information.', + }, + { + q: 'Kan jag ta bort mitt konto?', + a: 'Ja, du kan när som helst begära att ditt konto och alla dina uppgifter raderas. Kontakta oss via e-post så hanterar vi det inom 30 dagar.', + }, + ], + }, +]; + +// ─── ACCORDION-KOMPONENT ────────────────────────────────────── +// En enkel accordion-komponent som hanterar sitt eget open/closed-state. +// Separerad från FaqPage för att hålla koden ren. +const FaqItem = ({ question, answer, delay }) => { + // open-state: styr om svaret är synligt + const [open, setOpen] = useState(false); + + return ( + + setOpen(o => !o)}> + {question} + + + + + + + + {answer} + + + ); +}; + +// ─── PAGE ───────────────────────────────────────────────────── +const FaqPage = () => ( + + + FAQ + {/* Stoppa Proppen */} + © 2025 + + + + + + {/* Hero */} + + Vanliga frågor +

+ Svar på dina
+ frågor +

+ + Här hittar du svar på de vanligaste frågorna om appen, + symtomkollen och medicinpåminnelser. Hittar du inte svar? + Hör gärna av dig till oss. + +
+ + {/* Accordion per kategori */} + {FAQ_DATA.map((cat, ci) => ( +
+ {cat.category} + {cat.items.map((item, ii) => ( + + ))} +
+ ))} + + {/* Kontakt-kort längst ner */} + + + Hittade du inte svar? + Vi svarar vanligtvis inom ett par dagar. + + + Kontakta oss → + + + +
+
+ +); + +export default FaqPage; \ No newline at end of file diff --git a/frontend/src/pages/MedicinPage.jsx b/frontend/src/pages/MedicinPage.jsx index f77fb1b8c..4abc8470c 100644 --- a/frontend/src/pages/MedicinPage.jsx +++ b/frontend/src/pages/MedicinPage.jsx @@ -796,7 +796,7 @@ const MedicinPage = () => { Medicinlåda - Stoppa Proppen + {/* Stoppa Proppen */} From 2e8379996f2c74032fdaabbb51c08936a243d953 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 3 Mar 2026 10:13:17 +0100 Subject: [PATCH 09/48] removed tillbaka button and just changed to the navbar as navigation, also added omossPage --- frontend/src/App.jsx | 4 +- frontend/src/components/Navbar.jsx | 8 +- frontend/src/pages/MedicinPage.jsx | 3 +- frontend/src/pages/OmossPage.jsx | 461 +++++++++++++++++++++++++++++ 4 files changed, 469 insertions(+), 7 deletions(-) create mode 100644 frontend/src/pages/OmossPage.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index f7ce21327..1cce1ed69 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -2,10 +2,11 @@ import LandingPage from "./pages/LandingPage"; import { ThemeProvider } from "styled-components"; import GlobalStyles from "./styles/Globalstyles"; import { theme } from "./styles/theme"; -import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import LoginPage from "./pages/LoginPage"; import MedicinPage from "./pages/MedicinPage"; import FaqPage from "./pages/FaqPage"; +import OmOssPage from "./pages/OmossPage"; export const App = () => { @@ -17,6 +18,7 @@ export const App = () => { } /> } /> } /> + } /> } /> } /> diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index 50cc2d60a..c091a97e1 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -232,11 +232,11 @@ const Navbar = ({ variant = 'default', user = null, logoHref = '/' }) => { {variant === 'default' && ( <> -
  • Hem
  • +
  • Hem
  • Medicinkoll
  • -
  • Om oss
  • +
  • Om oss
  • Hur det fungerar
  • -
  • FAQ
  • +
  • FAQ
  • Kom igång @@ -274,7 +274,7 @@ const Navbar = ({ variant = 'default', user = null, logoHref = '/' }) => { setMenuOpen(false)}>Hem setMenuOpen(false)}>Om oss setMenuOpen(false)}>Hur det fungerar - setMenuOpen(false)}>FAQ + setMenuOpen(false)}>FAQ setMenuOpen(false)}>Medicinkoll setMenuOpen(false)}>Kom igång gratis diff --git a/frontend/src/pages/MedicinPage.jsx b/frontend/src/pages/MedicinPage.jsx index 4abc8470c..9aa66f39f 100644 --- a/frontend/src/pages/MedicinPage.jsx +++ b/frontend/src/pages/MedicinPage.jsx @@ -506,7 +506,6 @@ const FieldLabel = styled.label` `; // Textfält och tidsfält i formuläret. -// VIKTIGT: font-size 1rem (= 16px) på mobil förhindrar att iOS zooomar in // automatiskt vid fokus — under 16px triggas auto-zoom. // -webkit-appearance: none tar bort iOS standardstilar (border, shadow etc.) const FieldInput = styled.input` @@ -798,7 +797,7 @@ const MedicinPage = () => { Medicinlåda {/* Stoppa Proppen */} - + diff --git a/frontend/src/pages/OmossPage.jsx b/frontend/src/pages/OmossPage.jsx new file mode 100644 index 000000000..f6dc7b9a3 --- /dev/null +++ b/frontend/src/pages/OmossPage.jsx @@ -0,0 +1,461 @@ +import React from 'react'; +import styled from 'styled-components'; +import { Frame, Card, CornerLabel, fadeUp } from '../components/Layout.jsx'; +import Navbar from '../components/Navbar.jsx'; +import { theme } from '../styles/theme'; + +// ─── SCROLL-AREA ────────────────────────────────────────────── +const ScrollArea = styled.div` + position: relative; + z-index: 10; + flex: 1; + overflow-y: auto; + padding: 1.5rem 1.4rem 4rem; + scrollbar-width: none; + &::-webkit-scrollbar { display: none; } + + @media (min-width: 768px) { + padding: 2rem 2.5rem 4rem; + max-width: 720px; + margin: 0 auto; + width: 100%; + } +`; + +// ─── HERO-SEKTION ───────────────────────────────────────────── +const Hero = styled.div` + margin-bottom: 3rem; + animation: ${fadeUp} 0.7s ease both; +`; + +const Badge = styled.div` + display: inline-flex; + align-items: center; + gap: 0.5rem; + background: rgba(0,0,0,0.25); + border: 1px solid rgba(255,255,255,0.2); + border-radius: 100px; + padding: 0.3rem 0.9rem; + font-size: 0.68rem; + font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; + color: rgba(255,255,255,0.75); + margin-bottom: 1rem; +`; + +const BadgeDot = styled.span` + width: 5px; height: 5px; + border-radius: 50%; + background: ${theme.colors.mint}; +`; + +const H1 = styled.h1` + font-family: ${theme.fonts.serif}; + font-size: clamp(2.2rem, 7vw, 3.8rem); + font-weight: 300; + line-height: 1.15; + color: #fff; + margin-bottom: 1.2rem; + + em { + font-style: italic; + color: rgba(255,255,255,0.65); + } +`; + +const HeroText = styled.p` + font-size: 0.95rem; + font-weight: 300; + color: rgba(255,255,255,0.75); + line-height: 1.8; + max-width: 520px; +`; + +// ─── AVDELARE ───────────────────────────────────────────────── +const Divider = styled.div` + height: 1px; + background: rgba(255,255,255,0.1); + margin: 2.5rem 0; +`; + +// ─── SEKTION-RUBRIK ─────────────────────────────────────────── +const SectionLabel = styled.div` + font-size: 0.65rem; + font-weight: 500; + letter-spacing: 0.12em; + text-transform: uppercase; + color: rgba(255,255,255,0.4); + margin-bottom: 1rem; +`; + +// ─── VÄRDE-KORT (de tre kort-kolumnerna) ───────────────────── +const CardGrid = styled.div` + display: flex; + flex-direction: column; + gap: 0.8rem; + margin-bottom: 2.5rem; + + @media (min-width: 600px) { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 1rem; + } +`; + +const ValueCard = styled.div` + background: rgba(0,0,0,0.25); + border: 1px solid rgba(255,255,255,0.12); + border-radius: 16px; + padding: 1.4rem; + animation: ${fadeUp} 0.6s ${({ $delay }) => $delay || '0s'} ease both; +`; + +const ValueIcon = styled.div` + width: 36px; height: 36px; + border-radius: 10px; + background: rgba(125,255,212,0.12); + border: 1px solid rgba(125,255,212,0.25); + display: flex; align-items: center; justify-content: center; + margin-bottom: 0.9rem; + color: ${theme.colors.mint}; +`; + +const ValueTitle = styled.div` + font-family: ${theme.fonts.serif}; + font-size: 1.1rem; + font-weight: 400; + color: #fff; + margin-bottom: 0.4rem; +`; + +const ValueText = styled.div` + font-size: 0.78rem; + color: rgba(255,255,255,0.55); + line-height: 1.65; +`; + +// ─── LÖPTEXT-SEKTION ────────────────────────────────────────── +const TextSection = styled.div` + margin-bottom: 2.5rem; + animation: ${fadeUp} 0.6s 0.2s ease both; +`; + +const SectionTitle = styled.h2` + font-family: ${theme.fonts.serif}; + font-size: clamp(1.5rem, 4vw, 2rem); + font-weight: 300; + color: #fff; + line-height: 1.2; + margin-bottom: 0.9rem; + + em { font-style: italic; color: rgba(255,255,255,0.6); } +`; + +const BodyText = styled.p` + font-size: 0.88rem; + font-weight: 300; + color: rgba(255,255,255,0.7); + line-height: 1.85; + margin-bottom: 0.9rem; +`; + +// ─── TEAM-SEKTION ───────────────────────────────────────────── +const TeamGrid = styled.div` + display: flex; + flex-direction: column; + gap: 0.8rem; + margin-bottom: 2.5rem; + + @media (min-width: 480px) { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + } +`; + +const TeamCard = styled.div` + background: rgba(0,0,0,0.2); + border: 1px solid rgba(255,255,255,0.1); + border-radius: 14px; + padding: 1.2rem; + display: flex; + align-items: center; + gap: 1rem; + animation: ${fadeUp} 0.6s ${({ $delay }) => $delay || '0s'} ease both; +`; + +const TeamAvatar = styled.div` + width: 44px; height: 44px; + border-radius: 50%; + background: ${({ $color }) => $color || 'rgba(125,255,212,0.15)'}; + border: 1px solid rgba(255,255,255,0.2); + display: flex; align-items: center; justify-content: center; + font-family: ${theme.fonts.serif}; + font-size: 1.1rem; + font-weight: 300; + color: #fff; + flex-shrink: 0; +`; + +const TeamInfo = styled.div``; + +const TeamName = styled.div` + font-size: 0.9rem; + font-weight: 500; + color: #fff; + margin-bottom: 0.15rem; +`; + +const TeamRole = styled.div` + font-size: 0.72rem; + color: rgba(255,255,255,0.45); +`; + +// ─── STATISTIK-RAD ──────────────────────────────────────────── +const StatsRow = styled.div` + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.8rem; + margin-bottom: 2.5rem; + + @media (min-width: 600px) { + grid-template-columns: repeat(4, 1fr); + } +`; + +const StatBox = styled.div` + background: rgba(0,0,0,0.2); + border: 1px solid rgba(255,255,255,0.1); + border-radius: 14px; + padding: 1.2rem 1rem; + text-align: center; + animation: ${fadeUp} 0.6s ${({ $delay }) => $delay || '0s'} ease both; +`; + +const StatNum = styled.div` + font-family: ${theme.fonts.serif}; + font-size: 1.8rem; + font-weight: 300; + color: ${theme.colors.mint}; + line-height: 1; + margin-bottom: 0.3rem; +`; + +const StatLabel = styled.div` + font-size: 0.68rem; + color: rgba(255,255,255,0.45); + letter-spacing: 0.04em; +`; + +// ─── CTA-KORT ───────────────────────────────────────────────── +const CtaCard = styled.div` + background: rgba(125,255,212,0.07); + border: 1px solid rgba(125,255,212,0.2); + border-radius: 18px; + padding: 1.8rem 1.4rem; + text-align: center; + animation: ${fadeUp} 0.6s 0.3s ease both; + + @media (min-width: 768px) { + padding: 2.5rem 2rem; + } +`; + +const CtaTitle = styled.h3` + font-family: ${theme.fonts.serif}; + font-size: 1.6rem; + font-weight: 300; + color: #fff; + margin-bottom: 0.6rem; +`; + +const CtaText = styled.p` + font-size: 0.82rem; + color: rgba(255,255,255,0.6); + margin-bottom: 1.4rem; + line-height: 1.7; +`; + +const CtaBtn = styled.a` + display: inline-flex; + align-items: center; + gap: 0.5rem; + background: rgba(255,255,255,0.92); + color: ${theme.colors.tealDeep}; + border-radius: 100px; + padding: 0.85rem 2rem; + font-size: 0.88rem; + font-weight: 500; + text-decoration: none; + transition: background 0.2s, transform 0.15s; + + &:hover { background: #fff; transform: translateY(-1px); } + &:active { transform: scale(0.98); } +`; + +// ─── PAGE ───────────────────────────────────────────────────── +const OmOssPage = () => ( + + + Om oss + © 2025 + + + + + + {/* Hero */} + + Vårt uppdrag +

    + Medicinsk trygghet
    + för alla — alltid +

    + + Stoppa Proppen är en svensk hälsoapp skapad för att hjälpa + människor förstå, förebygga och hantera blodpropp. Vi kombinerar + evidensbaserad medicinsk information med ett varmt community + där ingen behöver känna sig ensam. + +
    + + {/* Statistik */} + {/* Appen i siffror + + + 1 247 + Aktiva användare + + + 4.8 + Snittbetyg + + + 98% + Nöjda användare + + + 0 kr + Alltid gratis + + */} + + + + {/* Vad vi erbjuder */} + + Vad vi erbjuder + Tre pelare + + + + + + + + + Symtomkoll + Svara på några frågor och få en preliminär riskbedömning baserad på kliniska riktlinjer. + + + + + + + + Community + Möt andra med liknande erfarenheter. Dela, stödja och lära av varandra i en trygg miljö. + + + + + + + + + Påminnelser + Håll koll på dina mediciner med dagliga påminnelser direkt i appen — enkelt och pålitligt. + + + + + + {/* Vår historia */} + + Bakgrund + Varför vi byggde detta + + Blodpropp drabbar varje år tusentals svenskar — och många av dem + beskriver känslan av att stå ensam med sin oro och sina frågor. + Sjukvården gör ett fantastiskt jobb, men det finns ett gap mellan + läkarbesöken som Stoppa Proppen vill fylla. + + + Appen startade som ett examensarbete på Technigo bootcamp + och har växt till ett riktigt projekt med målet att bli ett + komplement till den svenska vården — aldrig en ersättning. + + + {/* All medicinsk information är granskad och baseras på Socialstyrelsens + och 1177:s riktlinjer. */} Vi är alltid transparenta med vad appen kan + och inte kan — den ställer inga diagnoser. + + + + + + {/* Teamet */} + Teamet + + Människorna bakom + + + + R + + Rebecca + Grundare & utvecklare + + + + M + + Medicinsk rådgivare + {/* Legitimerad sjuksköterska */} + + 1 + + UX + + Design & tillgänglighet + UX-specialist + + + + LTU + + Luleå tekniska univ. + Akademisk handledning + + + + + + + {/* CTA */} + + Redo att komma igång? + + Skapa ett gratis konto och få tillgång till symtomkoll, + medicinpåminnelser och vårt community idag. + + Kom igång gratis → + + +
    +
    + +); + +export default OmOssPage; \ No newline at end of file From b26c198ea05ad2a42f7c18d8a5ab40c613ca04d5 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 3 Mar 2026 10:14:35 +0100 Subject: [PATCH 10/48] removed missprint --- frontend/src/pages/OmossPage.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/OmossPage.jsx b/frontend/src/pages/OmossPage.jsx index f6dc7b9a3..05097419c 100644 --- a/frontend/src/pages/OmossPage.jsx +++ b/frontend/src/pages/OmossPage.jsx @@ -424,7 +424,7 @@ const OmOssPage = () => ( Medicinsk rådgivare {/* Legitimerad sjuksköterska */} - 1 + UX From cb8cf053b5b8d5e15055a113f3810dec61e242a8 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 3 Mar 2026 10:31:24 +0100 Subject: [PATCH 11/48] main landmark for lighthouse --- frontend/src/components/Layout.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 9336a8130..716473881 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -38,7 +38,7 @@ export const Frame = styled.div` `; // ─── Main card ──────────────────────────────────────────────── -export const Card = styled.div` +export const Card = styled.main` width: 100%; min-height: 100vh; position: relative; From 7fb62e74707ebcf5b66ccb1179c1313cd5798764 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 3 Mar 2026 11:37:15 +0100 Subject: [PATCH 12/48] fixed border on loginpage --- frontend/src/pages/FaqPage.jsx | 2 +- frontend/src/pages/LoginPage.jsx | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/FaqPage.jsx b/frontend/src/pages/FaqPage.jsx index 1cc5e1e64..85c01c06e 100644 --- a/frontend/src/pages/FaqPage.jsx +++ b/frontend/src/pages/FaqPage.jsx @@ -227,7 +227,7 @@ const FAQ_DATA = [ }, { q: 'Kostar det något att använda appen?', - a: 'Nej, Stoppa Proppen är helt gratis att använda. Vi tar inga betalt för grundfunktionerna och visar inga annonser.', + a: 'Nej, Stoppa Proppen är helt gratis att använda. Vi tar inget betalt för grundfunktionerna och visar inga annonser.', }, { q: 'Är appen godkänd av sjukvården?', diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index 33660e014..f1d725e2c 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -10,6 +10,8 @@ const SplitCard = styled.div` position: relative; display: flex; flex-direction: column; + border-radius: 24px; + overflow: hidden; /* mobil: ren gradient, inget split */ background: linear-gradient(160deg, #1a6b76 0%, #0d4a52 100%); From 446d5c39c6e4a6a20055dd6cc2bfbc6d97252ba1 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 3 Mar 2026 15:45:49 +0100 Subject: [PATCH 13/48] removed cornerlabels on every page --- frontend/src/App.jsx | 2 + frontend/src/components/Navbar.jsx | 2 +- frontend/src/pages/FaqPage.jsx | 2 +- frontend/src/pages/Hurdetfungerarpage.jsx | 455 ++++++++++++++++++++++ frontend/src/pages/LandingPage.jsx | 147 ++++--- frontend/src/pages/LoginPage.jsx | 4 +- frontend/src/pages/MedicinPage.jsx | 2 +- frontend/src/pages/OmossPage.jsx | 2 +- 8 files changed, 554 insertions(+), 62 deletions(-) create mode 100644 frontend/src/pages/Hurdetfungerarpage.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1cce1ed69..d4aecadc4 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -7,6 +7,7 @@ import LoginPage from "./pages/LoginPage"; import MedicinPage from "./pages/MedicinPage"; import FaqPage from "./pages/FaqPage"; import OmOssPage from "./pages/OmossPage"; +import HurdetFungerar from "./pages/Hurdetfungerarpage" export const App = () => { @@ -19,6 +20,7 @@ export const App = () => { } /> } /> } /> + } /> } /> } /> diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index c091a97e1..5d721b397 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -235,7 +235,7 @@ const Navbar = ({ variant = 'default', user = null, logoHref = '/' }) => {
  • Hem
  • Medicinkoll
  • Om oss
  • -
  • Hur det fungerar
  • +
  • Hur det fungerar
  • FAQ
  • Kom igång diff --git a/frontend/src/pages/FaqPage.jsx b/frontend/src/pages/FaqPage.jsx index 85c01c06e..1285f07d9 100644 --- a/frontend/src/pages/FaqPage.jsx +++ b/frontend/src/pages/FaqPage.jsx @@ -312,7 +312,7 @@ const FaqItem = ({ question, answer, delay }) => { const FaqPage = () => ( - FAQ + {/* FAQ */} {/* Stoppa Proppen */} © 2025 diff --git a/frontend/src/pages/Hurdetfungerarpage.jsx b/frontend/src/pages/Hurdetfungerarpage.jsx new file mode 100644 index 000000000..9b2c7e030 --- /dev/null +++ b/frontend/src/pages/Hurdetfungerarpage.jsx @@ -0,0 +1,455 @@ +import React from 'react'; +import styled from 'styled-components'; +import { Frame, Card, CornerLabel, fadeUp } from '../components/Layout.jsx'; +import Navbar from '../components/Navbar.jsx'; +import { theme } from '../styles/theme'; + + +// ─── SCROLL-AREA ────────────────────────────────────────────────────────────── +const ScrollArea = styled.div` + position: relative; + z-index: 10; + flex: 1; + overflow-y: auto; + padding: 1.5rem 1.4rem 4rem; + scrollbar-width: none; + &::-webkit-scrollbar { display: none; } + + @media (min-width: 768px) { + padding: 2rem 2.5rem 4rem; + max-width: 720px; + margin: 0 auto; + width: 100%; + } +`; + + +// ─── HERO ───────────────────────────────────────────────────────────────────── +const Hero = styled.div` + margin-bottom: 2.5rem; + animation: ${fadeUp} 0.7s ease both; +`; + +const Badge = styled.div` + display: inline-flex; + align-items: center; + gap: 0.5rem; + background: rgba(0,0,0,0.25); + border: 1px solid rgba(255,255,255,0.2); + border-radius: 100px; + padding: 0.3rem 0.9rem; + font-size: 0.68rem; + font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; + color: rgba(255,255,255,0.75); + margin-bottom: 1rem; +`; + +const BadgeDot = styled.span` + width: 5px; height: 5px; + border-radius: 50%; + background: ${theme.colors.mint}; +`; + +const H1 = styled.h1` + font-family: ${theme.fonts.serif}; + font-size: clamp(2rem, 6vw, 3.2rem); + font-weight: 300; + color: #fff; + line-height: 1.2; + margin-bottom: 0.9rem; + em { font-style: italic; color: rgba(255,255,255,0.6); } +`; + +const HeroText = styled.p` + font-size: 0.9rem; + color: rgba(255,255,255,0.65); + line-height: 1.75; + max-width: 480px; +`; + + +// ─── AVDELARE ───────────────────────────────────────────────────────────────── +const Divider = styled.div` + height: 1px; + background: rgba(255,255,255,0.1); + margin: 2.5rem 0; +`; + + +// ─── SEKTION-ETIKETT ────────────────────────────────────────────────────────── +const SectionLabel = styled.div` + font-size: 0.65rem; + font-weight: 500; + letter-spacing: 0.12em; + text-transform: uppercase; + color: rgba(255,255,255,0.35); + margin-bottom: 1rem; + display: flex; + align-items: center; + gap: 0.8rem; + + &::after { + content: ''; + flex: 1; + height: 1px; + background: rgba(255,255,255,0.08); + } +`; + +const SectionTitle = styled.h2` + font-family: ${theme.fonts.serif}; + font-size: clamp(1.5rem, 4vw, 2rem); + font-weight: 300; + color: #fff; + line-height: 1.2; + margin-bottom: 1.2rem; + em { font-style: italic; color: rgba(255,255,255,0.6); } +`; + + +// ─── STEG-KORT ──────────────────────────────────────────────────────────────── +const StepList = styled.div` + display: flex; + flex-direction: column; + gap: 0.8rem; + margin-bottom: 2rem; +`; + +const StepCard = styled.div` + background: rgba(0,0,0,0.22); + border: 1px solid rgba(255,255,255,0.1); + border-radius: 16px; + padding: 1.2rem 1.4rem; + display: flex; + gap: 1.1rem; + align-items: flex-start; + animation: ${fadeUp} 0.6s ${({ $delay }) => $delay || '0s'} ease both; +`; + +const StepNumber = styled.div` + font-family: ${theme.fonts.serif}; + font-size: 1.6rem; + font-weight: 300; + color: ${theme.colors.mint}; + line-height: 1; + flex-shrink: 0; + width: 28px; + margin-top: 0.1rem; +`; + +const StepContent = styled.div``; + +const StepTitle = styled.div` + font-size: 0.95rem; + font-weight: 500; + color: #fff; + margin-bottom: 0.3rem; +`; + +const StepText = styled.div` + font-size: 0.82rem; + font-weight: 300; + color: rgba(255,255,255,0.6); + line-height: 1.7; +`; + + +// ─── FUNKTIONS-KORT ─────────────────────────────────────────────────────────── +const FeatureGrid = styled.div` + display: flex; + flex-direction: column; + gap: 0.8rem; + margin-bottom: 2rem; + + @media (min-width: 600px) { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 1rem; + } +`; + +const FeatureCard = styled.div` + background: rgba(0,0,0,0.22); + border: 1px solid rgba(255,255,255,0.1); + border-radius: 16px; + padding: 1.4rem; + animation: ${fadeUp} 0.6s ${({ $delay }) => $delay || '0s'} ease both; +`; + +const FeatureIcon = styled.div` + width: 38px; height: 38px; + border-radius: 10px; + background: rgba(125,255,212,0.1); + border: 1px solid rgba(125,255,212,0.2); + display: flex; + align-items: center; + justify-content: center; + color: ${theme.colors.mint}; + margin-bottom: 0.9rem; +`; + +const FeatureTitle = styled.div` + font-family: ${theme.fonts.serif}; + font-size: 1.05rem; + font-weight: 400; + color: #fff; + margin-bottom: 0.4rem; +`; + +const FeatureText = styled.div` + font-size: 0.78rem; + color: rgba(255,255,255,0.55); + line-height: 1.65; +`; + + +// ─── INFO-RUTA ──────────────────────────────────────────────────────────────── +const InfoBox = styled.div` + background: rgba(255,217,125,0.07); + border: 1px solid rgba(255,217,125,0.25); + border-radius: 14px; + padding: 1.2rem 1.4rem; + display: flex; + gap: 0.9rem; + align-items: flex-start; + margin-bottom: 2rem; + animation: ${fadeUp} 0.6s 0.2s ease both; +`; + +const InfoIcon = styled.div` + font-size: 1.1rem; + flex-shrink: 0; + margin-top: 0.1rem; +`; + +const InfoText = styled.div` + font-size: 0.82rem; + color: rgba(255,217,125,0.85); + line-height: 1.7; + + strong { + color: rgba(255,217,125,1); + font-weight: 500; + } +`; + + +// ─── CTA-KORT ───────────────────────────────────────────────────────────────── +const CtaCard = styled.div` + background: rgba(125,255,212,0.07); + border: 1px solid rgba(125,255,212,0.2); + border-radius: 18px; + padding: 1.8rem 1.4rem; + text-align: center; + animation: ${fadeUp} 0.6s 0.3s ease both; + + @media (min-width: 768px) { + padding: 2.5rem 2rem; + } +`; + +const CtaTitle = styled.h3` + font-family: ${theme.fonts.serif}; + font-size: 1.6rem; + font-weight: 300; + color: #fff; + margin-bottom: 0.6rem; +`; + +const CtaText = styled.p` + font-size: 0.82rem; + color: rgba(255,255,255,0.6); + margin-bottom: 1.4rem; + line-height: 1.7; +`; + +const CtaBtn = styled.a` + display: inline-flex; + align-items: center; + gap: 0.5rem; + background: rgba(255,255,255,0.92); + color: ${theme.colors.tealDeep}; + border-radius: 100px; + padding: 0.85rem 2rem; + font-size: 0.88rem; + font-weight: 500; + text-decoration: none; + transition: background 0.2s, transform 0.15s; + + @media (min-width: 768px) { + &:hover { background: #fff; transform: translateY(-1px); } + } + &:active { transform: scale(0.98); } +`; + + +// ─── PAGE ───────────────────────────────────────────────────────────────────── +const HurDetFungerarPage = () => ( + + + {/* Hur det fungerar + Stoppa Proppen */} + © 2025 + + + + + + {/* Hero */} + + Guide +

    + Kom igång på
    + tre minuter +

    + + Stoppa Proppen är byggt för att vara enkelt att använda — + oavsett om du är van vid appar eller inte. Här går vi igenom + hur allt fungerar, steg för steg. + +
    + + + {/* Steg-för-steg */} + Steg-för-steg + Så här börjar du + + + + 1 + + Skapa ett gratis konto + + Tryck på "Kom igång gratis" på startsidan. Ange din + e-postadress och välj ett lösenord. Det tar mindre än en minut. + + + + + + 2 + + Gör en symtomkoll + + Svara på några enkla frågor om hur du mår. Appen ger dig + en preliminär riskbedömning och råd om vad du bör göra härnäst. + + + + + + 3 + + Lägg till dina mediciner + + Gå till Medicinlådan och lägg till dina mediciner med namn, + dos och tid. Appen påminner dig varje dag när det är dags. + + + + + + 4 + + Följ din hälsa över tid + + Markera mediciner som tagna, gör nya symtomkollar och håll + koll på ditt välmående — allt på ett och samma ställe. + + + + + + + + + + {/* Funktioner */} + Funktioner + Vad du kan göra + + + + + + + + + Symtomkoll + + Besvara frågor om dina symtom och få en riskbedömning + baserad på medicinska riktlinjer. + + + + + + + + + + + Medicinlådan + + Håll koll på dina dagliga mediciner med påminnelser + och en tydlig checklista. + + + + + + + + + + + + + Community + + Möt andra med liknande erfarenheter och dela tips + i en trygg och stöttande miljö. + + + + + + + + + {/* Viktig info-ruta */} + + ⚠️ + + Viktigt att veta: + Stoppa Proppen ersätter inte sjukvård och ställer inga diagnoser. + Vid akuta symtom som plötslig andnöd, bröstsmärta eller kraftig + svullnad i ett ben — ring 112 omedelbart. + + + + + {/* CTA */} + + Redo att testa? + + Skapa ett gratis konto och kom igång direkt. + Det tar mindre än en minut. + + Kom igång gratis → + + +
    +
    + +); + +export default HurDetFungerarPage; diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx index 2a7ae5201..60585f998 100644 --- a/frontend/src/pages/LandingPage.jsx +++ b/frontend/src/pages/LandingPage.jsx @@ -1,29 +1,37 @@ import React from 'react'; import styled, { keyframes } from 'styled-components'; -import { Frame, Card, CornerLabel, BtnPrimary, fadeUp, fadeLeft, pulse } from '../components/Layout.jsx'; +import { Frame, Card, CornerLabel, fadeUp, fadeLeft, pulse } from '../components/Layout.jsx'; import Navbar from '../components/Navbar.jsx'; import { theme } from '../styles/theme'; -// ─── Styles ─────────────────────────────────────────────────── +// ─── ANIMATIONER ───────────────────────────────────────────── +// Växer progressbaren från 0 till sin slutbredd const growBar = keyframes`from { width: 0; }`; + +// ─── HERO ───────────────────────────────────────────────────── +// MOBIL (bas): centrerat innehåll, mindre padding +// DESKTOP 768+: mer padding runt om const Hero = styled.div` + /* MOBIL */ position: relative; z-index: 10; - flex: 1; + flex: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; - padding: 3rem 3rem 5rem; - gap: 1.8rem; + padding: 1.8rem; + gap: 1rem; - @media (max-width: 768px) { - padding: 2rem 1.8rem 6rem; + /* DESKTOP 768+ */ + @media (min-width: 768px) { + padding: 3rem 3rem 5rem; } `; +// Liten pill-badge högst upp, t.ex. "Beta — Nu tillgänglig" const Badge = styled.div` display: inline-flex; align-items: center; @@ -40,6 +48,7 @@ const Badge = styled.div` animation: ${fadeUp} 0.8s ease both; `; +// Den pulserande gröna punkten i badge const BadgeDot = styled.span` width: 6px; height: 6px; @@ -49,9 +58,12 @@ const BadgeDot = styled.span` animation: ${pulse} 2s infinite; `; +// Stor rubrik +// clamp() gör storleken responsiv utan media query: +// min 2.4rem, ökar med skärmbredden, max 5.2rem const H1 = styled.h1` font-family: ${theme.fonts.serif}; - font-size: clamp(3rem, 6vw, 5.2rem); + font-size: clamp(2.4rem, 6vw, 5.2rem); font-weight: 300; line-height: 1.1; letter-spacing: -0.01em; @@ -66,16 +78,24 @@ const H1 = styled.h1` } `; +// Underrubrik / beskrivningstext const HeroSub = styled.p` - font-size: 1rem; + /* MOBIL */ + font-size: 0.92rem; font-weight: 300; color: rgba(255,255,255,0.88); max-width: 440px; line-height: 1.7; animation: ${fadeUp} 0.8s 0.2s ease both; text-shadow: 0 1px 4px rgba(0,0,0,0.3); + + /* DESKTOP 768+ */ + @media (min-width: 768px) { + font-size: 1rem; + } `; +// Wrapper för knappar/länkar under texten const HeroActions = styled.div` display: flex; gap: 1rem; @@ -85,6 +105,7 @@ const HeroActions = styled.div` justify-content: center; `; +// Diskret ghost-länk, t.ex. "Läs mer →" const BtnGhostLink = styled.a` font-size: 0.85rem; font-weight: 400; @@ -100,18 +121,34 @@ const BtnGhostLink = styled.a` &:hover { color: #fff; } `; + +// ─── STATISTIK-PILLS ────────────────────────────────────────── +// Tre pills längst ner i hero-sektionen. +// MOBIL (bas): döljs — tar för mycket plats på liten skärm +// DESKTOP 768+: visas längst ner på kortet const Stats = styled.div` - position: absolute; - bottom: 2.5rem; - left: 0; - right: 0; - z-index: 10; + /* MOBIL: döljs */ + /* display: none; */ display: flex; justify-content: center; - gap: 1rem; - padding: 0 3rem; - animation: ${fadeUp} 0.8s 0.4s ease both; + gap: 0.6rem; + padding: 0 1.4rem 2rem; flex-wrap: wrap; + animation: ${fadeUp} 0.8s 0.4s ease both; + + /* DESKTOP 768+: visas */ + @media (min-width: 768px) { + position: absolute; + bottom: 2.5rem; + left: 0; + right: 0; + z-index: 10; + justify-content: center; + gap: 1rem; + padding: 0 3rem; + animation: ${fadeUp} 0.8s 0.4s ease both; + flex-wrap: wrap; + } `; const StatPill = styled.div` @@ -135,22 +172,32 @@ const StatSep = styled.span` background: rgba(255,255,255,0.25); `; -const FloatCard = styled.div` - position: absolute; - top: 50%; - right: 3.5rem; - transform: translateY(-60%); - z-index: 20; - background: rgba(10,40,46,0.8); - backdrop-filter: blur(20px); - border: 1px solid rgba(255,255,255,0.2); - border-radius: 18px; - padding: 1.4rem 1.6rem; - min-width: 220px; - box-shadow: ${theme.shadow.float}; - animation: ${fadeLeft} 0.8s 0.5s ease both; - @media (max-width: 900px) { display: none; } +// ─── FLOAT-KORT ─────────────────────────────────────────────── +// Det svävande kortet med "riskbedömning"-animationen. +// MOBIL (bas): döljs — tar för mycket plats +// DESKTOP 900+: visas till höger om hero-texten +const FloatCard = styled.div` + /* MOBIL: döljs */ + display: none; + + /* DESKTOP 900+: visas som ett svävande kort */ + @media (min-width: 900px) { + display: block; + position: absolute; + top: 50%; + right: 3.5rem; + transform: translateY(-60%); + z-index: 20; + background: rgba(10,40,46,0.8); + backdrop-filter: blur(20px); + border: 1px solid rgba(255,255,255,0.2); + border-radius: 18px; + padding: 1.4rem 1.6rem; + min-width: 220px; + box-shadow: ${theme.shadow.float}; + animation: ${fadeLeft} 0.8s 0.5s ease both; + } `; const FloatLabel = styled.div` @@ -210,23 +257,17 @@ const FloatMeta = styled.div` color: rgba(255,255,255,0.65); `; -// ─── Page ───────────────────────────────────────────────────── + +// ─── PAGE ───────────────────────────────────────────────────── const LandingPage = () => ( - {/* Beta v0.1 */} - {/* Medicinsk App */} - Stoppa Proppen © - 2025 +{/* Stoppa Proppen © + */} 2025 - {/* - - Beta — Nu tillgänglig - */} -

    Skydda dig mot
    blodproppar @@ -238,22 +279,20 @@ const LandingPage = () => ( - {/* - - Kom igång gratis - */} - + Läs mer + {/* Stats döljs på mobil, visas på desktop */} Medicinsk vägledningGratis Community & stödAlltid tillgänglig Snabb hjälpSymtomkoll på under 60 sek + {/* FloatCard döljs på mobil, visas på desktop */} Din riskbedömning Symtomanalys
    pågår…
    @@ -265,18 +304,14 @@ const LandingPage = () => ( ); -// Inline SVG icons -const CheckIcon = () => ( - - - - -); +// ─── SVG-IKONER ─────────────────────────────────────────────── const ArrowIcon = () => ( - + ); -export default LandingPage; +export default LandingPage; \ No newline at end of file diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index f1d725e2c..1c1ac02c2 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -326,8 +326,8 @@ const LoginPage = ({ onLogin }) => { return ( - Inloggning - Stoppa Proppen + {/* Inloggning + Stoppa Proppen */} Säker anslutning v0.1 diff --git a/frontend/src/pages/MedicinPage.jsx b/frontend/src/pages/MedicinPage.jsx index 9aa66f39f..93f3de08d 100644 --- a/frontend/src/pages/MedicinPage.jsx +++ b/frontend/src/pages/MedicinPage.jsx @@ -794,7 +794,7 @@ const MedicinPage = () => { return ( - Medicinlåda + {/* Medicinlåda */} {/* Stoppa Proppen */} diff --git a/frontend/src/pages/OmossPage.jsx b/frontend/src/pages/OmossPage.jsx index 05097419c..271377ff1 100644 --- a/frontend/src/pages/OmossPage.jsx +++ b/frontend/src/pages/OmossPage.jsx @@ -298,7 +298,7 @@ const CtaBtn = styled.a` const OmOssPage = () => ( - Om oss + {/* Om oss */} © 2025 From 114baace47a245a574e832bbfc1273de18f3b1cb Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 3 Mar 2026 16:08:12 +0100 Subject: [PATCH 14/48] added the bottomnav on mobile - styling changes on mobile --- frontend/src/components/Navbar.jsx | 7 ++++--- frontend/src/pages/MedicinPage.jsx | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index 5d721b397..51201668d 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import styled from 'styled-components'; import { theme } from '../styles/theme'; +import { useLocation } from 'react-router-dom'; // ─── Styles ─────────────────────────────────────────────────── const Nav = styled.nav` @@ -271,9 +272,9 @@ const Navbar = ({ variant = 'default', user = null, logoHref = '/' }) => { {/* Mobile drawer */} {variant === 'default' && ( - setMenuOpen(false)}>Hem - setMenuOpen(false)}>Om oss - setMenuOpen(false)}>Hur det fungerar + setMenuOpen(false)}>Hem + setMenuOpen(false)}>Om oss + setMenuOpen(false)}>Hur det fungerar setMenuOpen(false)}>FAQ setMenuOpen(false)}>Medicinkoll setMenuOpen(false)}>Kom igång gratis diff --git a/frontend/src/pages/MedicinPage.jsx b/frontend/src/pages/MedicinPage.jsx index 93f3de08d..bc28a7b98 100644 --- a/frontend/src/pages/MedicinPage.jsx +++ b/frontend/src/pages/MedicinPage.jsx @@ -6,10 +6,10 @@ import { toast, ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; // Frame = yttersta wrapper, Card = gradient-kortet, -// CornerLabel = dekorativa hörn-texter (bara desktop), fadeUp = animation import { Frame, Card, CornerLabel, fadeUp } from '../components/Layout.jsx'; import Navbar from '../components/Navbar.jsx'; import { theme } from '../styles/theme'; +import BottomNav from "../components/BottomNav.jsx" // ─── ANIMATIONER ───────────────────────────────────────────────────────────── @@ -321,7 +321,8 @@ const TomorrowTime = styled.div` const AddBtnWrap = styled.div` /* MOBIL: full bredd */ position: fixed; - bottom: 2.5rem; left: 0; right: 0; + bottom: 5rem; + left: 0; right: 0; z-index: 50; padding: 1rem 1.4rem 1.6rem; @@ -968,7 +969,7 @@ const MedicinPage = () => { )} - + ); }; From e13e4af2ac863229bfe0e9ddada25dbae90e39e6 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 3 Mar 2026 16:14:37 +0100 Subject: [PATCH 15/48] styling changes for mobile --- frontend/src/pages/LandingPage.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx index 60585f998..1e13e04ce 100644 --- a/frontend/src/pages/LandingPage.jsx +++ b/frontend/src/pages/LandingPage.jsx @@ -22,6 +22,7 @@ const Hero = styled.div` align-items: center; justify-content: center; text-align: center; + margin-top: 2.5rem; padding: 1.8rem; gap: 1rem; From 357e28205a9aeb8dcce7d5668cc768090c92bb1b Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 4 Mar 2026 08:41:20 +0100 Subject: [PATCH 16/48] added close button on loginpage, and fontsize mobile --- frontend/src/assets/boiler-plate.svg | 18 ---------------- frontend/src/assets/react.svg | 1 - frontend/src/assets/technigo-logo.svg | 31 --------------------------- frontend/src/pages/LandingPage.jsx | 2 +- frontend/src/pages/LoginPage.jsx | 27 +++++++++++++++++++++++ frontend/src/styles/Globalstyles.js | 6 ++++++ 6 files changed, 34 insertions(+), 51 deletions(-) delete mode 100644 frontend/src/assets/boiler-plate.svg delete mode 100644 frontend/src/assets/react.svg delete mode 100644 frontend/src/assets/technigo-logo.svg diff --git a/frontend/src/assets/boiler-plate.svg b/frontend/src/assets/boiler-plate.svg deleted file mode 100644 index c9252833b..000000000 --- a/frontend/src/assets/boiler-plate.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg deleted file mode 100644 index 6c87de9bb..000000000 --- a/frontend/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/assets/technigo-logo.svg b/frontend/src/assets/technigo-logo.svg deleted file mode 100644 index 3f0da3e57..000000000 --- a/frontend/src/assets/technigo-logo.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx index 1e13e04ce..3104643f7 100644 --- a/frontend/src/pages/LandingPage.jsx +++ b/frontend/src/pages/LandingPage.jsx @@ -42,7 +42,7 @@ const Badge = styled.div` backdrop-filter: blur(10px); border-radius: 100px; padding: 0.35rem 1rem; - font-size: 0.75rem; + font-size: 0.8rem; font-weight: 500; color: rgba(255,255,255,0.9); letter-spacing: 0.03em; diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index 1c1ac02c2..abfc1f954 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -151,6 +151,27 @@ const RightPanel = styled.div` } `; +const CloseBtn = styled.a` + position: absolute; + top: 1.2rem; + right: 1.2rem; + z-index: 20; + width: 36px; + height: 36px; + border-radius: 50%; + background: rgba(255,255,255,0.1); + border: 1px solid rgba(255,255,255,0.2); + display: flex; + align-items: center; + justify-content: center; + color: rgba(255,255,255,0.75); + text-decoration: none; + transition: background 0.2s, color 0.2s; + + &:hover { background: rgba(255,255,255,0.18); color: #fff; } + &:active { transform: scale(0.95); } +`; + // Mobil-logga — bara synlig på mobil ────────────────────────── const MobileLogo = styled.div` display: flex; @@ -326,6 +347,12 @@ const LoginPage = ({ onLogin }) => { return ( + + + + + {/* Inloggning Stoppa Proppen */} Säker anslutning diff --git a/frontend/src/styles/Globalstyles.js b/frontend/src/styles/Globalstyles.js index ad3e6c680..455da9108 100644 --- a/frontend/src/styles/Globalstyles.js +++ b/frontend/src/styles/Globalstyles.js @@ -14,6 +14,12 @@ const GlobalStyles = createGlobalStyle` color: #ffffff; overflow-x: hidden; -webkit-font-smoothing: antialiased; + + font-size: 18px; + + @media (min-width: 768px) { + font-size: 16px; + } } a { text-decoration: none; color: inherit; } From 880477ea64e00344fc45ed9df7697b3fa27952dd Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 4 Mar 2026 08:43:20 +0100 Subject: [PATCH 17/48] styling fixes --- frontend/src/pages/MedicinPage.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/MedicinPage.jsx b/frontend/src/pages/MedicinPage.jsx index bc28a7b98..386510672 100644 --- a/frontend/src/pages/MedicinPage.jsx +++ b/frontend/src/pages/MedicinPage.jsx @@ -321,7 +321,7 @@ const TomorrowTime = styled.div` const AddBtnWrap = styled.div` /* MOBIL: full bredd */ position: fixed; - bottom: 5rem; + bottom: 7rem; left: 0; right: 0; z-index: 50; padding: 1rem 1.4rem 1.6rem; From 1c33d49442cefc1a6019e8d811b4aa67b553160c Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 4 Mar 2026 08:48:12 +0100 Subject: [PATCH 18/48] styling fixes --- frontend/src/pages/MedicinPage.jsx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/frontend/src/pages/MedicinPage.jsx b/frontend/src/pages/MedicinPage.jsx index 386510672..7b065cc13 100644 --- a/frontend/src/pages/MedicinPage.jsx +++ b/frontend/src/pages/MedicinPage.jsx @@ -312,22 +312,17 @@ const TomorrowTime = styled.div` `; -// ─── LÄGG TILL-KNAPP (FAST LÄNGST NER) ─────────────────────────────────────── -// Knappen är fixed längst ner hela tiden. -// Gradienten bakom gör att listan tonas bort naturligt. -// -// MOBIL (bas): sträcker sig full bredd -// DESKTOP 768+: centreras och begränsas till 640px (samma bredd som innehållet) +// Knappen är fixed längst ner. + const AddBtnWrap = styled.div` - /* MOBIL: full bredd */ position: fixed; - bottom: 7rem; + bottom: 4rem; left: 0; right: 0; z-index: 50; padding: 1rem 1.4rem 1.6rem; - /* DESKTOP 768+: centrera med max-bredd */ @media (min-width: 768px) { + bottom: 6rem; max-width: 640px; left: 50%; transform: translateX(-50%); From 313fcba54520a0aa047d22f1c45fa80a1647acc8 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 4 Mar 2026 08:48:49 +0100 Subject: [PATCH 19/48] styling fixes --- frontend/src/pages/MedicinPage.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/MedicinPage.jsx b/frontend/src/pages/MedicinPage.jsx index 7b065cc13..0445ea053 100644 --- a/frontend/src/pages/MedicinPage.jsx +++ b/frontend/src/pages/MedicinPage.jsx @@ -322,7 +322,7 @@ const AddBtnWrap = styled.div` padding: 1rem 1.4rem 1.6rem; @media (min-width: 768px) { - bottom: 6rem; + bottom: 9rem; max-width: 640px; left: 50%; transform: translateX(-50%); From b1e20827a63e210cb67178e6e0e633c1255b4dc9 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 4 Mar 2026 09:08:10 +0100 Subject: [PATCH 20/48] style changes --- frontend/src/styles/Globalstyles.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/styles/Globalstyles.js b/frontend/src/styles/Globalstyles.js index 455da9108..dd2e62cd7 100644 --- a/frontend/src/styles/Globalstyles.js +++ b/frontend/src/styles/Globalstyles.js @@ -15,7 +15,7 @@ const GlobalStyles = createGlobalStyle` overflow-x: hidden; -webkit-font-smoothing: antialiased; - font-size: 18px; + font-size: 19px; @media (min-width: 768px) { font-size: 16px; From 29ddec9488c2daa1e3a664806357d8b15237e56d Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 4 Mar 2026 09:31:32 +0100 Subject: [PATCH 21/48] styling fixes --- frontend/src/pages/LoginPage.jsx | 19 ++++++++++++++----- frontend/src/styles/Globalstyles.js | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index abfc1f954..ccff184d1 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -177,7 +177,7 @@ const MobileLogo = styled.div` display: flex; align-items: center; gap: 0.6rem; - margin-bottom: 2rem; + margin-bottom: 1rem; z-index: 10; position: relative; @@ -191,7 +191,8 @@ const FormHeading = styled.h3` font-size: 2rem; font-weight: 300; color: #fff; - margin-bottom: 0.4rem; + margin-bottom: 0.2rem; + `; const FormSubtext = styled.p` @@ -238,7 +239,7 @@ const ForgotRow = styled.div` display: flex; justify-content: flex-end; margin-top: -0.5rem; - margin-bottom: 1.5rem; + margin-bottom: 1rem; `; const ForgotLink = styled.a` @@ -270,13 +271,14 @@ const Divider = styled.div` display: flex; align-items: center; gap: 1rem; - margin: 1.4rem 0; + margin: 0.8rem 0; `; const DividerLine = styled.div` flex: 1; height: 1px; background: rgba(255,255,255,0.15); + margin: 0; `; const DividerText = styled.span` @@ -302,7 +304,7 @@ const GhostBtn = styled.button` const RegisterLink = styled.p` text-align: center; - margin-top: 1.5rem; + margin-top: 1rem; font-size: 0.8rem; color: rgba(255,255,255,0.6); @@ -333,6 +335,11 @@ const CornerLabel = styled.span` } `; +/* const LoginFrame = styled(Frame)` + @media (min-width: 768px) { + margin-top: 1rem; + } +`; */ // ─── Page ───────────────────────────────────────────────────── const LoginPage = ({ onLogin }) => { const [email, setEmail] = useState(''); @@ -346,6 +353,7 @@ const LoginPage = ({ onLogin }) => { return ( + @@ -424,6 +432,7 @@ const LoginPage = ({ onLogin }) => { + ); }; diff --git a/frontend/src/styles/Globalstyles.js b/frontend/src/styles/Globalstyles.js index dd2e62cd7..49cc86a66 100644 --- a/frontend/src/styles/Globalstyles.js +++ b/frontend/src/styles/Globalstyles.js @@ -18,7 +18,7 @@ const GlobalStyles = createGlobalStyle` font-size: 19px; @media (min-width: 768px) { - font-size: 16px; + font-size: 17px; } } From eebbec6c975c1873a47cade3c13dd23d8ee66ff6 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 4 Mar 2026 09:46:59 +0100 Subject: [PATCH 22/48] styling --- frontend/src/pages/LandingPage.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx index 3104643f7..fa8d9d26f 100644 --- a/frontend/src/pages/LandingPage.jsx +++ b/frontend/src/pages/LandingPage.jsx @@ -22,7 +22,6 @@ const Hero = styled.div` align-items: center; justify-content: center; text-align: center; - margin-top: 2.5rem; padding: 1.8rem; gap: 1rem; @@ -290,8 +289,8 @@ const LandingPage = () => ( Medicinsk vägledningGratis Community & stödAlltid tillgänglig - Snabb hjälpSymtomkoll på under 60 sek - +{/* Snabb hjälpSymtomkoll på under 60 sek + */} {/* FloatCard döljs på mobil, visas på desktop */} From 1f7b8064b55cb8160c01e5d077c846ca164fd91c Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 4 Mar 2026 09:54:00 +0100 Subject: [PATCH 23/48] layout landingpage fix --- frontend/src/components/Layout.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 716473881..d4bb504d7 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -40,19 +40,20 @@ export const Frame = styled.div` // ─── Main card ──────────────────────────────────────────────── export const Card = styled.main` width: 100%; - min-height: 100vh; + /* min-height: 100vh; */ position: relative; background: ${theme.gradients.card}; display: flex; flex-direction: column; /* mobil: inga rundade hörn, fyller hela skärmen */ + min-height: auto; border-radius: 0; box-shadow: none; @media (min-width: 768px) { - max-width: ${({ maxWidth }) => maxWidth || '1100px'}; min-height: ${({ minHeight }) => minHeight || '620px'}; + max-width: ${({ maxWidth }) => maxWidth || '1100px'}; border-radius: 24px; box-shadow: ${theme.shadow.card}, inset 0 1px 0 rgba(255,255,255,0.15); } From 28910059079828ca22e1b7a2db381ad8a5070fc2 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 4 Mar 2026 09:56:25 +0100 Subject: [PATCH 24/48] styling changes --- frontend/src/components/Layout.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index d4bb504d7..b81f54068 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -23,7 +23,6 @@ export const growBar = keyframes` // ─── Page frame ─────────────────────────────────────────────── export const Frame = styled.div` - min-height: 100vh; display: flex; align-items: center; justify-content: center; @@ -33,6 +32,7 @@ export const Frame = styled.div` background: ${theme.colors.dark}; @media (min-width: 768px) { + min-height: 100vh; padding: 2rem; } `; From cb7405a47e36ce71b7d19cc9be8b07004c8e4158 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 4 Mar 2026 10:02:25 +0100 Subject: [PATCH 25/48] changed back mobile layout --- frontend/src/components/Layout.jsx | 7 +++---- frontend/src/pages/LandingPage.jsx | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index b81f54068..716473881 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -23,6 +23,7 @@ export const growBar = keyframes` // ─── Page frame ─────────────────────────────────────────────── export const Frame = styled.div` + min-height: 100vh; display: flex; align-items: center; justify-content: center; @@ -32,7 +33,6 @@ export const Frame = styled.div` background: ${theme.colors.dark}; @media (min-width: 768px) { - min-height: 100vh; padding: 2rem; } `; @@ -40,20 +40,19 @@ export const Frame = styled.div` // ─── Main card ──────────────────────────────────────────────── export const Card = styled.main` width: 100%; - /* min-height: 100vh; */ + min-height: 100vh; position: relative; background: ${theme.gradients.card}; display: flex; flex-direction: column; /* mobil: inga rundade hörn, fyller hela skärmen */ - min-height: auto; border-radius: 0; box-shadow: none; @media (min-width: 768px) { - min-height: ${({ minHeight }) => minHeight || '620px'}; max-width: ${({ maxWidth }) => maxWidth || '1100px'}; + min-height: ${({ minHeight }) => minHeight || '620px'}; border-radius: 24px; box-shadow: ${theme.shadow.card}, inset 0 1px 0 rgba(255,255,255,0.15); } diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx index fa8d9d26f..2d02bcddb 100644 --- a/frontend/src/pages/LandingPage.jsx +++ b/frontend/src/pages/LandingPage.jsx @@ -289,8 +289,8 @@ const LandingPage = () => ( Medicinsk vägledningGratis Community & stödAlltid tillgänglig -{/* Snabb hjälpSymtomkoll på under 60 sek - */} + Snabb hjälpSymtomkoll på under 60 sek + {/* FloatCard döljs på mobil, visas på desktop */} From c6f98afb01532a0ee355752dee80e1d3a883a3af Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Thu, 5 Mar 2026 09:30:01 +0100 Subject: [PATCH 26/48] styling --- backend/package.json | 2 ++ backend/routes/moods.js | 0 backend/routes/symptoms.js | 0 backend/server.js | 23 ++++++++++++++++------- frontend/src/pages/MedicinPage.jsx | 2 +- frontend/src/styles/Globalstyles.js | 2 +- 6 files changed, 20 insertions(+), 9 deletions(-) delete mode 100644 backend/routes/moods.js delete mode 100644 backend/routes/symptoms.js diff --git a/backend/package.json b/backend/package.json index aaa1c11d2..266fe19c2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,9 +12,11 @@ "@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.3.1", "express": "^4.17.3", + "express-session": "^1.19.0", "mongoose": "^8.4.0", "nodemon": "^3.0.1" } diff --git a/backend/routes/moods.js b/backend/routes/moods.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/routes/symptoms.js b/backend/routes/symptoms.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/server.js b/backend/server.js index 341a2fcc2..4b931ac23 100644 --- a/backend/server.js +++ b/backend/server.js @@ -2,31 +2,40 @@ import express from "express"; import cors from "cors"; import mongoose from "mongoose"; import dotenv from 'dotenv'; +import session from 'express-session'; dotenv.config(); - const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/final-project"; -mongoose.connect(mongoUrl); -mongoose.Promise = Promise; - +/* mongoose.Promise = Promise; */ const port = process.env.PORT || 8081; const app = express(); +/* Middleware */ app.use(cors()); app.use(express.json()); +// Session-hantering +// secret: en hemlig nyckel som krypterar sessionen +// resave: spara inte sessionen om inget ändrats +// saveUninitialized: spara inte tomma sessioner +app.use(session({ + secret: process.env.SESSION_SECRET || 'secret-key', + resave: false, + saveUninitialized: false, +})); + app.get("/", (req, res) => { res.send("Hello Technigo!"); }); // Start the server -app.listen(port, () => { - console.log(`Server running on http://localhost:${port}`); -}); mongoose.connect(mongoUrl) .then(() => { console.log("✅ Succé! Vi har kontakt med MongoDB Atlas."); + app.listen(port, () => { + console.log(`Server running on http://localhost:${port}`); +}); }) .catch((err) => { console.error("❌ Aj då, kunde inte ansluta till databasen:", err); diff --git a/frontend/src/pages/MedicinPage.jsx b/frontend/src/pages/MedicinPage.jsx index 0445ea053..1db14b7fd 100644 --- a/frontend/src/pages/MedicinPage.jsx +++ b/frontend/src/pages/MedicinPage.jsx @@ -322,7 +322,7 @@ const AddBtnWrap = styled.div` padding: 1rem 1.4rem 1.6rem; @media (min-width: 768px) { - bottom: 9rem; + bottom: 90rem; max-width: 640px; left: 50%; transform: translateX(-50%); diff --git a/frontend/src/styles/Globalstyles.js b/frontend/src/styles/Globalstyles.js index 49cc86a66..96403ac1e 100644 --- a/frontend/src/styles/Globalstyles.js +++ b/frontend/src/styles/Globalstyles.js @@ -15,7 +15,7 @@ const GlobalStyles = createGlobalStyle` overflow-x: hidden; -webkit-font-smoothing: antialiased; - font-size: 19px; + font-size: 18px; @media (min-width: 768px) { font-size: 17px; From f45711b93e6737586f8b204d169ca2df8f5c4053 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Thu, 5 Mar 2026 09:56:10 +0100 Subject: [PATCH 27/48] bug fix --- backend/server.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/server.js b/backend/server.js index 4b931ac23..b6cdf1d5b 100644 --- a/backend/server.js +++ b/backend/server.js @@ -2,8 +2,8 @@ import express from "express"; import cors from "cors"; import mongoose from "mongoose"; import dotenv from 'dotenv'; -import session from 'express-session'; - +/* import session from 'express-session'; + */ dotenv.config(); const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/final-project"; @@ -19,11 +19,11 @@ app.use(express.json()); // secret: en hemlig nyckel som krypterar sessionen // resave: spara inte sessionen om inget ändrats // saveUninitialized: spara inte tomma sessioner -app.use(session({ +/* app.use(session({ secret: process.env.SESSION_SECRET || 'secret-key', resave: false, saveUninitialized: false, -})); +})); */ app.get("/", (req, res) => { res.send("Hello Technigo!"); From e9a206cfb04b142c570928ca70bf4d932dee5232 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Sat, 7 Mar 2026 15:11:02 +0100 Subject: [PATCH 28/48] created log in and sign up --- backend/models/User.js | 27 +++++----- backend/routes/auth.js | 74 +++++++++++++++++++++++++++ backend/server.js | 17 ++++--- frontend/src/pages/LoginPage.jsx | 87 +++++++++++++++++++++++++++++--- 4 files changed, 176 insertions(+), 29 deletions(-) diff --git a/backend/models/User.js b/backend/models/User.js index 99d991efc..f2b465c03 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -1,24 +1,21 @@ -import mongoose from "mongoose" +import mongoose from "mongoose"; const userSchema = new mongoose.Schema({ - firstname: { - type: String, - required: true - }, - lastname: { - type: String, - required: true - }, email: { type: String, required: true, - unique: true + unique: true, //ingen får ha samma email + lowercase: true, // sparas med små bokstäver }, - password:{ - type: String, - required: true + password:{ + type: String, + required: true } -}) +}, { timestamps: true} //lägger automatiskt till createdAT och updatedAt +); +//Skapar en user modell baserat på schemat +const User = mongoose.model("User", userSchema) + -export const User = mongoose.model('User', userSchema) +export default User diff --git a/backend/routes/auth.js b/backend/routes/auth.js index e69de29bb..995dc3936 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -0,0 +1,74 @@ +import express from "express" +import User from "../models/User.js" //denna behöver vi för att kunna spara och hämta användare i MongoDB +import bcrypt from "bcrypt"; + +//mini server - hanterar bara sina egna routes, istället för att ha alla routes i server.js - kopplar vi sen ihop den med routern i huvudappen +const router = express.Router(); + +//async - menas att routen innehåller kod som tar tid( prata med databasen ) +// utan async/await kan vi inte vänta på svar från MongoDB +router.post("/register", async (req, res) => { + + //body innehåller datan användaren skickade från formuläret + // destructuring = vi plockar ut email och password direkt istället för req.body.email + const { email, password } = req.body; + + //Kollar om användaren faktiskt har fyllt i fälten + //! betyder "inte" - så om email saknas + if (!email || !password) { + + //stoppa routern här så att resten av koden inte körs + //status 400 + return res.status(400).json({ message: "Email och lösenord krävs"}); + } + +//kollar om emailen redan finns i databasen, findOne letar efter ett dokument som matchar, returnerar null om inget hittas +const existingUser = await User.findOne({ email }); + +if (existingUser) { + return res.status(400).json({ message: "Email redan registrerad"}); +} +//10 betyder att lösenordet processas 10 gånger +//await vänta tills krypteringen är klar innan det skickas vidare +const hashedPassword = await bcrypt.hash(password, 10); + +//newUser skapar ett nytt användarobjekt med vårat schema +//vi skickar in email och det krypterade lösenordet +const newUser = new User({ + email: email, + password: hashedPassword, +}); +//sparar användaren till mongoDB +//await - vänta tills sparandet är klart +await newUser.save(); + +res.status(201).json({ message: "Konto skapat!" }); +}); + +router.post("/login", async (req, res) => { + + const { email, password } = req.body; + + if (!email || !password) { + return res.status(400).json({ message: 'Email och lösenord krävs' }); + } + + //letar efter användarens email i databasen + const user = await User.findOne({ email }); + //om null skickas tillbaka nekas inloggningen + if (!user) { + return res.status(401).json({ message: "Fel email eller lösenord"}); + } + + //compare jämför lösenordet med det krypterade, await vänta tills dom jämförts, returnerar true om dom matchar false anars + const isMatch = await bcrypt.compare(password, user.password); + //om lösenordet inte matchar nekas inlogg + if(!isMatch){ + return res.status(401).json({ message: "Fel email eller lösenord"}) + } + //sparar användarens id i sessionen + req.session.userId = user._id; + res.status(200).json({ message: 'Inloggad!' }); +}) + +export default router; diff --git a/backend/server.js b/backend/server.js index b6cdf1d5b..11e38b2b2 100644 --- a/backend/server.js +++ b/backend/server.js @@ -2,8 +2,9 @@ import express from "express"; import cors from "cors"; import mongoose from "mongoose"; import dotenv from 'dotenv'; -/* import session from 'express-session'; - */ +import authRoutes from "./routes/auth"; +import session from 'express-session'; + dotenv.config(); const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/final-project"; @@ -19,15 +20,17 @@ app.use(express.json()); // secret: en hemlig nyckel som krypterar sessionen // resave: spara inte sessionen om inget ändrats // saveUninitialized: spara inte tomma sessioner -/* app.use(session({ + app.use(session({ secret: process.env.SESSION_SECRET || 'secret-key', resave: false, saveUninitialized: false, -})); */ +})); -app.get("/", (req, res) => { - res.send("Hello Technigo!"); -}); +//berättar för express att alla routes i auth.js ska börja med api +//register bli /api/register +app.use("/api", authRoutes); + +app.use // Start the server mongoose.connect(mongoUrl) diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index ccff184d1..823cbcfdb 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -345,12 +345,59 @@ const LoginPage = ({ onLogin }) => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - const handleSubmit = (e) => { + const [isLogin, setIsLogin] = useState('true'); + const [error, setError] = useState(''); + +/* const [confirmPassword, setconfirmPassword] = useState('') + */ + const handleSubmit = async (e) => { e.preventDefault(); - if (onLogin) onLogin({ email }); - window.location.href = '/symptomkoll'; + + const response = await fetch("http://localhost:8081/api/login", { + + method: "POST", + headers: { + "Content-Type": "application/json" //vi säger att vi skickar json + }, + body: JSON.stringify({ + email: email, + password: password + }) + }); + + const data = await response.json() + + if (response.ok) { + window.location.href = "/medicin" + } else { + setError(data.message) + } }; + const handleRegister = async (e) => { + e.preventDefault(); + + const response = await fetch("http://localhost:8081/api/register", { + + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + email: email, + password: password + }) + }); + + const data = await response.json() + + if (response.ok){ + setIsLogin(true); + } else { + setError(data.message) + } + } + return ( @@ -399,6 +446,8 @@ const LoginPage = ({ onLogin }) => { Stoppa Proppen + {isLogin ? ( + <> Logga in Ange dina uppgifter för att fortsätta @@ -423,14 +472,38 @@ const LoginPage = ({ onLogin }) => { ELLER - window.location.href = '/symptomkoll'}> + window.location.href = '/medicin'}> Fortsätt som gäst - + - Inget konto? Skapa ett här + Inget konto? setIsLogin(false)}>Skapa ett här - + + ) : ( + <> + Skapa Konto + Ange dina uppgifter för att fortsätta + +
    + + + setEmail(e.target.value)} required /> + + + + setPassword(e.target.value)} required /> + + + Har du redan ett lösenord? setIsLogin(true)}>Logga in här + + Registrera +
    + + )} +
    From f3465a32d97db51bb4181e9b1189302d1ed2d1bd Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Sat, 7 Mar 2026 18:14:52 +0100 Subject: [PATCH 29/48] added a protectedroute around profilpage --- frontend/src/App.jsx | 4 ++++ frontend/src/components/ProtectedRoute.jsx | 19 +++++++++++++++++++ frontend/src/pages/LoginPage.jsx | 21 +++++++++++++++++---- frontend/src/pages/ProfilPage.jsx | 0 frontend/src/store/userStore.js | 14 ++++++++++++++ 5 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/ProtectedRoute.jsx create mode 100644 frontend/src/pages/ProfilPage.jsx create mode 100644 frontend/src/store/userStore.js diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index d4aecadc4..49ae8e778 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -8,6 +8,7 @@ import MedicinPage from "./pages/MedicinPage"; import FaqPage from "./pages/FaqPage"; import OmOssPage from "./pages/OmossPage"; import HurdetFungerar from "./pages/Hurdetfungerarpage" +import ProtectedRoute from "./components/ProtectedRoute"; export const App = () => { @@ -18,6 +19,9 @@ export const App = () => { } /> } /> + + } /> + } /> } /> } /> diff --git a/frontend/src/components/ProtectedRoute.jsx b/frontend/src/components/ProtectedRoute.jsx new file mode 100644 index 000000000..b31d75e4f --- /dev/null +++ b/frontend/src/components/ProtectedRoute.jsx @@ -0,0 +1,19 @@ +import { Navigate } from 'react-router-dom'; //checks if the user is logged in, otherwise sends you to login +import useUserStore from '../store/userStore'; // checks isLoggedIn from Zustand + +/* isLoggedIn = true → show children (show whats inside the component) +isLoggedIn = false → */ + +const ProtectedRoute = ({ children }) => { + + const isLoggedIn = useUserStore(state => state.isLoggedIn); + + if (isLoggedIn){ + return children //show the site (component) + } else { + return + } +}; + + +export default ProtectedRoute; \ No newline at end of file diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index 823cbcfdb..bfc95080c 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -2,6 +2,9 @@ import React, { useState } from 'react'; import styled from 'styled-components'; import { Frame, fadeUp, fadeLeft } from '../components/Layout.jsx'; import { theme } from '../styles/theme'; +import useUserStore from '../store/userStore.js'; +import { useNavigate } from 'react-router-dom'; + // ─── Split card ─────────────────────────────────────────────── const SplitCard = styled.div` @@ -312,6 +315,7 @@ const RegisterLink = styled.p` color: ${theme.colors.mint}; text-decoration: none; &:hover { color: #fff; } + cursor: pointer; } `; @@ -345,9 +349,15 @@ const LoginPage = ({ onLogin }) => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - const [isLogin, setIsLogin] = useState('true'); + const [isLogin, setIsLogin] = useState(true); const [error, setError] = useState(''); - + + const navigate = useNavigate(); + const login = useUserStore(state => state.login); + const isLoggedIn = useUserStore(state => state.isLoggedIn); + console.log('isLoggedIn:', isLoggedIn) + + /* const [confirmPassword, setconfirmPassword] = useState('') */ const handleSubmit = async (e) => { @@ -357,7 +367,7 @@ const LoginPage = ({ onLogin }) => { method: "POST", headers: { - "Content-Type": "application/json" //vi säger att vi skickar json + "Content-Type": "application/json" //we are saying that we are sending json }, body: JSON.stringify({ email: email, @@ -368,7 +378,8 @@ const LoginPage = ({ onLogin }) => { const data = await response.json() if (response.ok) { - window.location.href = "/medicin" + login(data) + navigate('/medicin'); } else { setError(data.message) } @@ -465,6 +476,7 @@ const LoginPage = ({ onLogin }) => { Glömt lösenordet? + {error &&

    {error}

    } Logga in @@ -499,6 +511,7 @@ const LoginPage = ({ onLogin }) => { Har du redan ett lösenord? setIsLogin(true)}>Logga in här + {error &&

    {error}

    } Registrera diff --git a/frontend/src/pages/ProfilPage.jsx b/frontend/src/pages/ProfilPage.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/store/userStore.js b/frontend/src/store/userStore.js new file mode 100644 index 000000000..0cd787993 --- /dev/null +++ b/frontend/src/store/userStore.js @@ -0,0 +1,14 @@ +import { create } from "zustand"; + +const useUserStore = create((set) => ({ + + user: null, //no user from the beginning + isLoggedIn: false, //not loged in from the start + + //function to log in + login: (userData) => set({ user: userData, isLoggedIn: true }), + + logout: () => set({ user: null, isLoggedIn: false }) +})); + +export default useUserStore \ No newline at end of file From 9d4ff288334ed7780d376b05d5f3f43397d63444 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Sat, 7 Mar 2026 19:39:51 +0100 Subject: [PATCH 30/48] user email shown when logged in --- backend/routes/auth.js | 6 +- frontend/src/App.jsx | 8 +- frontend/src/pages/LoginPage.jsx | 2 +- frontend/src/pages/ProfilPage.jsx | 418 ++++++++++++++++++++++++++++++ 4 files changed, 429 insertions(+), 5 deletions(-) diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 995dc3936..d344a3268 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -68,7 +68,11 @@ router.post("/login", async (req, res) => { } //sparar användarens id i sessionen req.session.userId = user._id; - res.status(200).json({ message: 'Inloggad!' }); + res.status(200).json({ + message: 'Inloggad!', + email: user.email, + id: user._id + }); }) export default router; diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 49ae8e778..bdafdb9e9 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -9,6 +9,7 @@ import FaqPage from "./pages/FaqPage"; import OmOssPage from "./pages/OmossPage"; import HurdetFungerar from "./pages/Hurdetfungerarpage" import ProtectedRoute from "./components/ProtectedRoute"; +import ProfilPage from "./pages/ProfilPage"; export const App = () => { @@ -19,9 +20,10 @@ export const App = () => { } /> } /> - - } /> - + + + } /> } /> } /> } /> diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index bfc95080c..a2576f98d 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -379,7 +379,7 @@ const LoginPage = ({ onLogin }) => { if (response.ok) { login(data) - navigate('/medicin'); + navigate('/profil'); } else { setError(data.message) } diff --git a/frontend/src/pages/ProfilPage.jsx b/frontend/src/pages/ProfilPage.jsx index e69de29bb..52cc25d74 100644 --- a/frontend/src/pages/ProfilPage.jsx +++ b/frontend/src/pages/ProfilPage.jsx @@ -0,0 +1,418 @@ +import React from 'react'; +import styled from 'styled-components'; +import { Frame, Card, CornerLabel, fadeUp } from '../components/Layout.jsx'; +import Navbar from '../components/Navbar.jsx'; +import { theme } from '../styles/theme'; +import { Link } from 'react-router-dom'; +import useUserStore from '../store/userStore.js'; + + +// ─── SCROLL-AREA ────────────────────────────────────────────────────────────── +const ScrollArea = styled.div` + position: relative; + z-index: 10; + flex: 1; + overflow-y: auto; + padding: 1.5rem 1.4rem 4rem; + scrollbar-width: none; + &::-webkit-scrollbar { display: none; } + + @media (min-width: 768px) { + padding: 2rem 2.5rem 4rem; + max-width: 720px; + margin: 0 auto; + width: 100%; + } +`; + + +// ─── HERO ───────────────────────────────────────────────────────────────────── +const Hero = styled.div` + margin-bottom: 2rem; + animation: ${fadeUp} 0.7s ease both; +`; + +const Badge = styled.div` + display: inline-flex; + align-items: center; + gap: 0.5rem; + background: rgba(0,0,0,0.25); + border: 1px solid rgba(255,255,255,0.2); + border-radius: 100px; + padding: 0.3rem 0.9rem; + font-size: 0.68rem; + font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; + color: rgba(255,255,255,0.75); + margin-bottom: 1rem; +`; + +const BadgeDot = styled.span` + width: 5px; height: 5px; + border-radius: 50%; + background: ${theme.colors.mint}; +`; + +const H1 = styled.h1` + font-family: ${theme.fonts.serif}; + font-size: clamp(2rem, 6vw, 3.2rem); + font-weight: 300; + color: #fff; + line-height: 1.2; + margin-bottom: 0.4rem; + em { font-style: italic; color: rgba(255,255,255,0.6); } +`; + +const HeroSub = styled.p` + font-size: 0.88rem; + color: rgba(255,255,255,0.55); +`; + + +// ─── AVDELARE ───────────────────────────────────────────────────────────────── +const Divider = styled.div` + height: 1px; + background: rgba(255,255,255,0.1); + margin: 2rem 0; +`; + + +// ─── SEKTION-ETIKETT ────────────────────────────────────────────────────────── +const SectionLabel = styled.div` + font-size: 0.65rem; + font-weight: 500; + letter-spacing: 0.12em; + text-transform: uppercase; + color: rgba(255,255,255,0.35); + margin-bottom: 0.8rem; + display: flex; + align-items: center; + gap: 0.8rem; + + &::after { + content: ''; + flex: 1; + height: 1px; + background: rgba(255,255,255,0.08); + } +`; + + +// ─── SNABBLÄNKS-KORT ────────────────────────────────────────────────────────── +// Två klickbara kort för Medicinlådan och Symtomkollen +const QuickGrid = styled.div` + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.8rem; + margin-bottom: 2rem; +`; + +const QuickCard = styled(Link)` + background: rgba(0,0,0,0.22); + border: 1px solid rgba(255,255,255,0.1); + border-radius: 16px; + padding: 1.2rem; + text-decoration: none; + display: flex; + flex-direction: column; + gap: 0.6rem; + transition: border-color 0.2s, background 0.2s; + animation: ${fadeUp} 0.6s ${({ $delay }) => $delay || '0s'} ease both; + + &:hover { + border-color: rgba(255,255,255,0.2); + background: rgba(0,0,0,0.3); + } + &:active { transform: scale(0.98); } +`; + +const QuickIcon = styled.div` + width: 36px; height: 36px; + border-radius: 10px; + background: rgba(125,255,212,0.1); + border: 1px solid rgba(125,255,212,0.2); + display: flex; + align-items: center; + justify-content: center; + color: ${theme.colors.mint}; +`; + +const QuickTitle = styled.div` + font-size: 0.9rem; + font-weight: 500; + color: #fff; +`; + +const QuickSub = styled.div` + font-size: 0.72rem; + color: rgba(255,255,255,0.45); +`; + + +// ─── KONTO-KORT ─────────────────────────────────────────────────────────────── +// Visar email och kontoinformation +const AccountCard = styled.div` + background: rgba(0,0,0,0.22); + border: 1px solid rgba(255,255,255,0.1); + border-radius: 16px; + padding: 1.2rem 1.4rem; + margin-bottom: 0.8rem; + animation: ${fadeUp} 0.6s 0.1s ease both; +`; + +const AccountRow = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; +`; + +const AccountLabel = styled.div` + font-size: 0.7rem; + font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; + color: rgba(255,255,255,0.4); + margin-bottom: 0.3rem; +`; + +const AccountValue = styled.div` + font-size: 0.92rem; + color: #fff; +`; + + +// ─── ÅTGÄRDS-KNAPPAR ────────────────────────────────────────────────────────── +// Knappar för logga ut, byta lösenord och radera konto +// Dessa är tomma onClick — du fyller i funktionaliteten! + +const ActionList = styled.div` + display: flex; + flex-direction: column; + gap: 0.6rem; + margin-bottom: 2rem; +`; + +const ActionBtn = styled.button` + width: 100%; + background: rgba(0,0,0,0.22); + border: 1px solid rgba(255,255,255,0.1); + border-radius: 14px; + padding: 1rem 1.4rem; + display: flex; + align-items: center; + gap: 0.9rem; + cursor: pointer; + text-align: left; + transition: background 0.2s, border-color 0.2s; + animation: ${fadeUp} 0.6s ${({ $delay }) => $delay || '0s'} ease both; + + &:hover { + background: rgba(0,0,0,0.3); + border-color: rgba(255,255,255,0.2); + } + &:active { transform: scale(0.98); } +`; + +const ActionIcon = styled.div` + width: 34px; height: 34px; + border-radius: 9px; + background: ${({ $color }) => $color || 'rgba(255,255,255,0.08)'}; + display: flex; + align-items: center; + justify-content: center; + color: ${({ $iconColor }) => $iconColor || 'rgba(255,255,255,0.6)'}; + flex-shrink: 0; +`; + +const ActionText = styled.div` + flex: 1; +`; + +const ActionTitle = styled.div` + font-size: 0.88rem; + font-weight: 500; + color: #fff; +`; + +const ActionSub = styled.div` + font-size: 0.72rem; + color: rgba(255,255,255,0.4); + margin-top: 0.1rem; +`; + +const ActionArrow = styled.div` + color: rgba(255,255,255,0.25); + font-size: 0.8rem; +`; + + +// ─── RADERA-KONTO-KNAPP ─────────────────────────────────────────────────────── +// Röd/destructive variant — avskiljs visuellt från resten +const DeleteBtn = styled.button` + width: 100%; + background: rgba(255,80,80,0.06); + border: 1px solid rgba(255,80,80,0.2); + border-radius: 14px; + padding: 1rem 1.4rem; + display: flex; + align-items: center; + gap: 0.9rem; + cursor: pointer; + text-align: left; + transition: background 0.2s, border-color 0.2s; + animation: ${fadeUp} 0.6s 0.3s ease both; + + &:hover { + background: rgba(255,80,80,0.1); + border-color: rgba(255,80,80,0.35); + } + &:active { transform: scale(0.98); } +`; + +const DeleteTitle = styled.div` + font-size: 0.88rem; + font-weight: 500; + color: rgba(255,120,120,0.9); +`; + +const DeleteSub = styled.div` + font-size: 0.72rem; + color: rgba(255,100,100,0.5); + margin-top: 0.1rem; +`; + + +// ─── PAGE ───────────────────────────────────────────────────────────────────── +const ProfilPage = () => { + const user = useUserStore(state => state.user); + return ( + + + Profil +{/* Stoppa Proppen + */} © 2025 + + + + + + {/* Hero */} + + Inloggad +

    + Mitt konto +

    + Hantera ditt konto och dina inställningar +
    + + + {/* Snabblänkar */} + Genvägar + + + + {/* Pill-ikon */} + + + + + + Medicinlådan + Dina dagliga mediciner + + + + + {/* Puls-ikon */} + + + + + Symtomkoll + Gör en ny bedömning + + + + + + + + {/* Kontoinformation */} + Kontoinformation + + +
    + E-postadress + {/* TODO: byt ut mot riktig email från Zustand-storen */} + {user?.email} +
    +
    +
    + + + + + + {/* Åtgärder */} + Inställningar + + + {/* Logga ut — fyll i onClick */} + {window.location.href = '/login'}}> + + + + + + + + Logga ut + Avslutar din session + + + + + {}}> {/* //will do later */} + + + + + + + + Byt lösenord + Uppdatera ditt lösenord + + + + + + + + {/* Radera konto — destructive action */} + Farlig zon + {}}> + + + + + + + Radera konto + Tar bort all din data permanent + + + + +
    +
    + +)}; + +export default ProfilPage; \ No newline at end of file From 3e1e9f5135ea244d83ba2d2a23173bdb7b845560 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Sat, 7 Mar 2026 19:42:11 +0100 Subject: [PATCH 31/48] comments --- frontend/src/pages/ProfilPage.jsx | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/frontend/src/pages/ProfilPage.jsx b/frontend/src/pages/ProfilPage.jsx index 52cc25d74..d0a5d7b64 100644 --- a/frontend/src/pages/ProfilPage.jsx +++ b/frontend/src/pages/ProfilPage.jsx @@ -70,7 +70,6 @@ const HeroSub = styled.p` `; -// ─── AVDELARE ───────────────────────────────────────────────────────────────── const Divider = styled.div` height: 1px; background: rgba(255,255,255,0.1); @@ -78,7 +77,6 @@ const Divider = styled.div` `; -// ─── SEKTION-ETIKETT ────────────────────────────────────────────────────────── const SectionLabel = styled.div` font-size: 0.65rem; font-weight: 500; @@ -98,9 +96,6 @@ const SectionLabel = styled.div` } `; - -// ─── SNABBLÄNKS-KORT ────────────────────────────────────────────────────────── -// Två klickbara kort för Medicinlådan och Symtomkollen const QuickGrid = styled.div` display: grid; grid-template-columns: 1fr 1fr; @@ -150,8 +145,6 @@ const QuickSub = styled.div` `; -// ─── KONTO-KORT ─────────────────────────────────────────────────────────────── -// Visar email och kontoinformation const AccountCard = styled.div` background: rgba(0,0,0,0.22); border: 1px solid rgba(255,255,255,0.1); @@ -182,11 +175,6 @@ const AccountValue = styled.div` color: #fff; `; - -// ─── ÅTGÄRDS-KNAPPAR ────────────────────────────────────────────────────────── -// Knappar för logga ut, byta lösenord och radera konto -// Dessa är tomma onClick — du fyller i funktionaliteten! - const ActionList = styled.div` display: flex; flex-direction: column; @@ -248,8 +236,6 @@ const ActionArrow = styled.div` `; -// ─── RADERA-KONTO-KNAPP ─────────────────────────────────────────────────────── -// Röd/destructive variant — avskiljs visuellt från resten const DeleteBtn = styled.button` width: 100%; background: rgba(255,80,80,0.06); @@ -308,12 +294,10 @@ const ProfilPage = () => { - {/* Snabblänkar */} Genvägar - {/* Pill-ikon */} @@ -327,7 +311,6 @@ const ProfilPage = () => { - {/* Puls-ikon */} { - {/* Kontoinformation */} Kontoinformation
    E-postadress - {/* TODO: byt ut mot riktig email från Zustand-storen */} + {/* email from Zustand-store */} {user?.email}
    @@ -359,11 +341,9 @@ const ProfilPage = () => { - {/* Åtgärder */} Inställningar - {/* Logga ut — fyll i onClick */} {window.location.href = '/login'}}> @@ -395,9 +375,8 @@ const ProfilPage = () => { - {/* Radera konto — destructive action */} Farlig zon - {}}> + {}}> {/* will do later */} From 7afb8ac872b8bd78f8ea6b22a4c06717cdda3a77 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Sun, 8 Mar 2026 11:20:05 +0100 Subject: [PATCH 32/48] added comments and my render url --- backend/models/User.js | 8 +- backend/routes/auth.js | 44 ++-- backend/server.js | 12 +- frontend/src/components/BottomNav.jsx | 28 +-- frontend/src/components/Layout.jsx | 6 +- frontend/src/components/Navbar.jsx | 6 +- frontend/src/pages/FaqPage.jsx | 32 +-- frontend/src/pages/Hurdetfungerarpage.jsx | 14 +- frontend/src/pages/LandingPage.jsx | 52 ++-- frontend/src/pages/LoginPage.jsx | 20 +- frontend/src/pages/MedicinPage.jsx | 286 +++++++++++----------- frontend/src/pages/OmossPage.jsx | 22 +- frontend/src/pages/ProfilPage.jsx | 5 - 13 files changed, 265 insertions(+), 270 deletions(-) diff --git a/backend/models/User.js b/backend/models/User.js index f2b465c03..d3b4b12db 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -5,16 +5,16 @@ const userSchema = new mongoose.Schema({ email: { type: String, required: true, - unique: true, //ingen får ha samma email - lowercase: true, // sparas med små bokstäver + unique: true, //noone gets the same email + lowercase: true, // saves with small letters }, password:{ type: String, required: true } -}, { timestamps: true} //lägger automatiskt till createdAT och updatedAt +}, { timestamps: true} //automatic adds createdAT and updatedAt ); -//Skapar en user modell baserat på schemat +//creates a user modell based on schema const User = mongoose.model("User", userSchema) diff --git a/backend/routes/auth.js b/backend/routes/auth.js index d344a3268..24ec6a76e 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -1,45 +1,45 @@ import express from "express" -import User from "../models/User.js" //denna behöver vi för att kunna spara och hämta användare i MongoDB +import User from "../models/User.js" // we need this to save and retrieve users in MongoDB import bcrypt from "bcrypt"; -//mini server - hanterar bara sina egna routes, istället för att ha alla routes i server.js - kopplar vi sen ihop den med routern i huvudappen +// mini server - only handles its own routes, instead of having all routes in server.js - we then connect it with the router in the main app const router = express.Router(); -//async - menas att routen innehåller kod som tar tid( prata med databasen ) -// utan async/await kan vi inte vänta på svar från MongoDB +// async - means the route contains code that takes time (talking to the database) +// without async/await we cannot wait for a response from MongoDB router.post("/register", async (req, res) => { - //body innehåller datan användaren skickade från formuläret - // destructuring = vi plockar ut email och password direkt istället för req.body.email + // body contains the data the user submitted from the form + // destructuring = we extract email and password directly instead of req.body.email const { email, password } = req.body; - //Kollar om användaren faktiskt har fyllt i fälten - //! betyder "inte" - så om email saknas + // Checks if the user has actually filled in the fields + // ! means "not" - so if email is missing if (!email || !password) { - //stoppa routern här så att resten av koden inte körs - //status 400 + // stop the router here so the rest of the code does not run + // status 400 return res.status(400).json({ message: "Email och lösenord krävs"}); } -//kollar om emailen redan finns i databasen, findOne letar efter ett dokument som matchar, returnerar null om inget hittas +// checks if the email already exists in the database, findOne looks for a matching document, returns null if nothing found const existingUser = await User.findOne({ email }); if (existingUser) { return res.status(400).json({ message: "Email redan registrerad"}); } -//10 betyder att lösenordet processas 10 gånger -//await vänta tills krypteringen är klar innan det skickas vidare +// 10 means the password is hashed 10 times +// await wait until encryption is complete before proceeding const hashedPassword = await bcrypt.hash(password, 10); -//newUser skapar ett nytt användarobjekt med vårat schema -//vi skickar in email och det krypterade lösenordet +// newUser creates a new user object with our schema +// we pass in email and the hashed password const newUser = new User({ email: email, password: hashedPassword, }); -//sparar användaren till mongoDB -//await - vänta tills sparandet är klart +// saves the user to MongoDB +// await - wait until saving is complete await newUser.save(); res.status(201).json({ message: "Konto skapat!" }); @@ -53,20 +53,20 @@ router.post("/login", async (req, res) => { return res.status(400).json({ message: 'Email och lösenord krävs' }); } - //letar efter användarens email i databasen + // looks for the user's email in the database const user = await User.findOne({ email }); - //om null skickas tillbaka nekas inloggningen + // if null is returned the login is denied if (!user) { return res.status(401).json({ message: "Fel email eller lösenord"}); } - //compare jämför lösenordet med det krypterade, await vänta tills dom jämförts, returnerar true om dom matchar false anars + // compare compares the password with the hashed one, await wait until compared, returns true if they match false otherwise const isMatch = await bcrypt.compare(password, user.password); - //om lösenordet inte matchar nekas inlogg + // if the password does not match login is denied if(!isMatch){ return res.status(401).json({ message: "Fel email eller lösenord"}) } - //sparar användarens id i sessionen + // saves the user's id in the session req.session.userId = user._id; res.status(200).json({ message: 'Inloggad!', diff --git a/backend/server.js b/backend/server.js index 11e38b2b2..27ed0c02a 100644 --- a/backend/server.js +++ b/backend/server.js @@ -16,18 +16,18 @@ const app = express(); app.use(cors()); app.use(express.json()); -// Session-hantering -// secret: en hemlig nyckel som krypterar sessionen -// resave: spara inte sessionen om inget ändrats -// saveUninitialized: spara inte tomma sessioner +// Session management +// secret: a secret key that encrypts the session +// resave: do not save the session if nothing has changed +// saveUninitialized: do not save empty sessions app.use(session({ secret: process.env.SESSION_SECRET || 'secret-key', resave: false, saveUninitialized: false, })); -//berättar för express att alla routes i auth.js ska börja med api -//register bli /api/register +// tells express that all routes in auth.js should start with /api +// register becomes /api/register app.use("/api", authRoutes); app.use diff --git a/frontend/src/components/BottomNav.jsx b/frontend/src/components/BottomNav.jsx index b46545485..db2b9ba94 100644 --- a/frontend/src/components/BottomNav.jsx +++ b/frontend/src/components/BottomNav.jsx @@ -1,13 +1,13 @@ // ───────────────────────────────────────────────────────────────────────────── // BottomNav.jsx // -// Bottom navigation för app-sidor på mobil. -// Visar tre stora knappar med ikon + text längst ner på skärmen. -// Döljs på desktop (768px+) eftersom man navigerar via toppmeny/tillbaka-knapp. +// Bottom navigation for app pages on mobile. +// Shows three large buttons with icon + text at the bottom of the screen. +// Hidden on desktop (768px+) since navigation is done via the top menu/back button. // // Används på: MedicinPage, SymptomCheckPage // Props: -// active = sträng med aktiv sida: 'hem' | 'medicin' | 'symptomkoll' +// active = string with active page: 'hem' | 'medicin' | 'symptomkoll' // ───────────────────────────────────────────────────────────────────────────── import React from 'react'; @@ -15,8 +15,8 @@ import styled from 'styled-components'; import { theme } from '../styles/theme'; // ─── Wrapper ────────────────────────────────────────────────────────────────── -// Sitter fast längst ner på skärmen (position: fixed). -// Döljs helt på desktop — där navigerar man via navbar istället. +// Fixed at the bottom of the screen (position: fixed). +// Hidden entirely on desktop — navigation is done via the navbar instead. const Bar = styled.nav` /* MOBIL: synlig, fast längst ner */ position: fixed; @@ -32,7 +32,7 @@ const Bar = styled.nav` backdrop-filter: blur(16px); border-top: 1px solid rgba(255,255,255,0.1); - /* Extra padding i botten för iPhone-notch/home indicator */ + /* Extra padding at the bottom for iPhone notch/home indicator */ padding-bottom: env(safe-area-inset-bottom, 0px); /* DESKTOP 768+: döljs */ @@ -41,9 +41,9 @@ const Bar = styled.nav` } `; -// ─── Enskild nav-knapp ──────────────────────────────────────────────────────── -// flex: 1 gör att alla tre knappar tar exakt lika stor bredd. -// $active styr om knappen är markerad (ljusgrön) eller inte (grå). +// ─── Individual nav button ──────────────────────────────────────────────────── +// flex: 1 makes all three buttons take exactly equal width. +// $active controls whether the button is highlighted (light green) or not (grey). const NavItem = styled.a` flex: 1; display: flex; @@ -56,13 +56,13 @@ const NavItem = styled.a` color: ${({ $active }) => $active ? theme.colors.mint : 'rgba(255,255,255,0.45)'}; transition: color 0.2s; - /* Liten highlight-linje längst upp på aktiv knapp */ + /* Small highlight line at the top of the active button */ border-top: 2px solid ${({ $active }) => $active ? theme.colors.mint : 'transparent'}; &:active { opacity: 0.7; } `; -// Ikonwrapper +// Icon wrapper const Icon = styled.div` width: 24px; height: 24px; @@ -71,14 +71,14 @@ const Icon = styled.div` justify-content: center; `; -// Text under ikonen +// Text below the icon const Label = styled.span` font-size: 0.68rem; font-weight: ${({ $active }) => $active ? '500' : '400'}; letter-spacing: 0.02em; `; -// ─── Komponent ──────────────────────────────────────────────────────────────── +// ─── Component ──────────────────────────────────────────────────────────────── const BottomNav = ({ active = '' }) => ( diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 716473881..0292720ac 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -28,7 +28,7 @@ export const Frame = styled.div` align-items: center; justify-content: center; - /* mobil: ingen padding, kortet fyller hela skärmen */ + /* mobile: no padding, the card fills the entire screen */ padding: 0; background: ${theme.colors.dark}; @@ -46,7 +46,7 @@ export const Card = styled.main` display: flex; flex-direction: column; - /* mobil: inga rundade hörn, fyller hela skärmen */ + /* mobile: no rounded corners, fills the entire screen */ border-radius: 0; box-shadow: none; @@ -82,7 +82,7 @@ export const Card = styled.main` } `; -// ─── Corner labels — bara synliga på desktop ────────────────── +// ─── Corner labels — only visible on desktop ────────────────── export const CornerLabel = styled.span` display: none; diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index 51201668d..ac36765cd 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -83,7 +83,7 @@ const NavCta = styled.a` } `; -// ─── Hamburger button (bara mobil) ──────────────────────────── +// ─── Hamburger button (mobile only) ─────────────────────────── const HamburgerBtn = styled.button` display: flex; flex-direction: column; @@ -166,7 +166,7 @@ const MobileCta = styled.a` &:hover { background: #fff; } `; -// Tillbaka-knapp i app-varianten — tar användaren till startsidan +// Back button in the app variant — takes the user to the home page const BackBtn = styled.a` display: flex; align-items: center; @@ -259,7 +259,7 @@ const Navbar = ({ variant = 'default', user = null, logoHref = '/' }) => { )} - {/* Hamburgare på mobil — outside Nav to avoid its stacking context */} + {/* Hamburger on mobile — outside Nav to avoid its stacking context */} {variant === 'default' && ( diff --git a/frontend/src/pages/FaqPage.jsx b/frontend/src/pages/FaqPage.jsx index 1285f07d9..c41c09999 100644 --- a/frontend/src/pages/FaqPage.jsx +++ b/frontend/src/pages/FaqPage.jsx @@ -4,7 +4,7 @@ import { Frame, Card, CornerLabel, fadeUp } from '../components/Layout.jsx'; import Navbar from '../components/Navbar.jsx'; import { theme } from '../styles/theme.js'; -// ─── ANIMATIONER ───────────────────────────────────────────── +// ─── ANIMATIONS ────────────────────────────────────────────── const slideDown = keyframes` from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } @@ -73,7 +73,7 @@ const HeroText = styled.p` max-width: 480px; `; -// ─── KATEGORI-ETIKETT ────────────────────────────────────────── +// ─── CATEGORY LABEL ──────────────────────────────────────────── const CategoryLabel = styled.div` font-size: 0.65rem; font-weight: 500; @@ -93,8 +93,8 @@ const CategoryLabel = styled.div` } `; -// ─── FAQ-ACCORDION-KORT ─────────────────────────────────────── -// $open styr om svaret är synligt eller inte. +// ─── FAQ ACCORDION CARD ─────────────────────────────────────── +// $open controls whether the answer is visible or not. const FaqCard = styled.div` background: rgba(0,0,0,0.22); border: 1px solid ${({ $open }) => $open ? 'rgba(255,255,255,0.2)' : 'rgba(255,255,255,0.1)'}; @@ -105,7 +105,7 @@ const FaqCard = styled.div` animation: ${fadeUp} 0.5s ${({ $delay }) => $delay || '0s'} ease both; `; -// Klickbar rubrikrad +// Clickable header row const FaqQuestion = styled.button` width: 100%; display: flex; @@ -130,7 +130,7 @@ const QuestionText = styled.span` } `; -// Roterande pil-ikon +// Rotating arrow icon const Chevron = styled.span` display: flex; align-items: center; @@ -142,9 +142,9 @@ const Chevron = styled.span` transition: transform 0.25s ease; `; -// Svar-text — döljs/visas med en height-animation via max-height +// Answer text — hidden/shown with a height animation via max-height const FaqAnswer = styled.div` - /* max-height trick: 0 = döljs, stort värde = visas */ + /* max-height trick: 0 = hidden, large value = visible */ max-height: ${({ $open }) => $open ? '400px' : '0'}; overflow: hidden; transition: max-height 0.35s ease; @@ -161,7 +161,7 @@ const AnswerInner = styled.div` animation: ${({ $open }) => $open ? slideDown : 'none'} 0.3s ease both; `; -// ─── KONTAKT-KORT ───────────────────────────────────────────── +// ─── CONTACT CARD ───────────────────────────────────────────── const ContactCard = styled.div` background: rgba(255,217,125,0.07); border: 1px solid rgba(255,217,125,0.2); @@ -214,9 +214,9 @@ const ContactBtn = styled.a` &:active { transform: scale(0.97); } `; -// ─── FAQ-DATA ───────────────────────────────────────────────── -// Kategorier med frågor och svar. -// Ingen data hämtas från API — allt är hårdkodat här i filen. +// ─── FAQ DATA ───────────────────────────────────────────────── +// Categories with questions and answers. +// No data is fetched from an API — everything is hardcoded in this file. const FAQ_DATA = [ { category: 'Om appen', @@ -284,11 +284,11 @@ const FAQ_DATA = [ }, ]; -// ─── ACCORDION-KOMPONENT ────────────────────────────────────── -// En enkel accordion-komponent som hanterar sitt eget open/closed-state. -// Separerad från FaqPage för att hålla koden ren. +// ─── ACCORDION COMPONENT ────────────────────────────────────── +// A simple accordion component that manages its own open/closed state. +// Separated from FaqPage to keep the code clean. const FaqItem = ({ question, answer, delay }) => { - // open-state: styr om svaret är synligt + // open state: controls whether the answer is visible const [open, setOpen] = useState(false); return ( diff --git a/frontend/src/pages/Hurdetfungerarpage.jsx b/frontend/src/pages/Hurdetfungerarpage.jsx index 9b2c7e030..eff9539de 100644 --- a/frontend/src/pages/Hurdetfungerarpage.jsx +++ b/frontend/src/pages/Hurdetfungerarpage.jsx @@ -70,7 +70,7 @@ const HeroText = styled.p` `; -// ─── AVDELARE ───────────────────────────────────────────────────────────────── +// ─── DIVIDER ────────────────────────────────────────────────────────────────── const Divider = styled.div` height: 1px; background: rgba(255,255,255,0.1); @@ -78,7 +78,7 @@ const Divider = styled.div` `; -// ─── SEKTION-ETIKETT ────────────────────────────────────────────────────────── +// ─── SECTION LABEL ──────────────────────────────────────────────────────────── const SectionLabel = styled.div` font-size: 0.65rem; font-weight: 500; @@ -109,7 +109,7 @@ const SectionTitle = styled.h2` `; -// ─── STEG-KORT ──────────────────────────────────────────────────────────────── +// ─── STEP CARDS ─────────────────────────────────────────────────────────────── const StepList = styled.div` display: flex; flex-direction: column; @@ -156,7 +156,7 @@ const StepText = styled.div` `; -// ─── FUNKTIONS-KORT ─────────────────────────────────────────────────────────── +// ─── FEATURE CARDS ──────────────────────────────────────────────────────────── const FeatureGrid = styled.div` display: flex; flex-direction: column; @@ -205,7 +205,7 @@ const FeatureText = styled.div` `; -// ─── INFO-RUTA ──────────────────────────────────────────────────────────────── +// ─── INFO BOX ───────────────────────────────────────────────────────────────── const InfoBox = styled.div` background: rgba(255,217,125,0.07); border: 1px solid rgba(255,217,125,0.25); @@ -236,7 +236,7 @@ const InfoText = styled.div` `; -// ─── CTA-KORT ───────────────────────────────────────────────────────────────── +// ─── CTA CARD ───────────────────────────────────────────────────────────────── const CtaCard = styled.div` background: rgba(125,255,212,0.07); border: 1px solid rgba(125,255,212,0.2); @@ -312,7 +312,7 @@ const HurDetFungerarPage = () => ( - {/* Steg-för-steg */} + {/* Step-by-step */} Steg-för-steg Så här börjar du diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx index 2d02bcddb..3ee35a735 100644 --- a/frontend/src/pages/LandingPage.jsx +++ b/frontend/src/pages/LandingPage.jsx @@ -4,14 +4,14 @@ import { Frame, Card, CornerLabel, fadeUp, fadeLeft, pulse } from '../components import Navbar from '../components/Navbar.jsx'; import { theme } from '../styles/theme'; -// ─── ANIMATIONER ───────────────────────────────────────────── -// Växer progressbaren från 0 till sin slutbredd +// ─── ANIMATIONS ────────────────────────────────────────────── +// Grows the progress bar from 0 to its final width const growBar = keyframes`from { width: 0; }`; // ─── HERO ───────────────────────────────────────────────────── -// MOBIL (bas): centrerat innehåll, mindre padding -// DESKTOP 768+: mer padding runt om +// MOBILE (base): centered content, less padding +// DESKTOP 768+: more padding all around const Hero = styled.div` /* MOBIL */ position: relative; @@ -31,7 +31,7 @@ const Hero = styled.div` } `; -// Liten pill-badge högst upp, t.ex. "Beta — Nu tillgänglig" +// Small pill badge at the top, e.g. "Beta — Now available" const Badge = styled.div` display: inline-flex; align-items: center; @@ -48,7 +48,7 @@ const Badge = styled.div` animation: ${fadeUp} 0.8s ease both; `; -// Den pulserande gröna punkten i badge +// The pulsing green dot in the badge const BadgeDot = styled.span` width: 6px; height: 6px; @@ -58,9 +58,9 @@ const BadgeDot = styled.span` animation: ${pulse} 2s infinite; `; -// Stor rubrik -// clamp() gör storleken responsiv utan media query: -// min 2.4rem, ökar med skärmbredden, max 5.2rem +// Large heading +// clamp() makes the size responsive without media queries: +// min 2.4rem, grows with screen width, max 5.2rem const H1 = styled.h1` font-family: ${theme.fonts.serif}; font-size: clamp(2.4rem, 6vw, 5.2rem); @@ -78,7 +78,7 @@ const H1 = styled.h1` } `; -// Underrubrik / beskrivningstext +// Subtitle / description text const HeroSub = styled.p` /* MOBIL */ font-size: 0.92rem; @@ -95,7 +95,7 @@ const HeroSub = styled.p` } `; -// Wrapper för knappar/länkar under texten +// Wrapper for buttons/links below the text const HeroActions = styled.div` display: flex; gap: 1rem; @@ -105,7 +105,7 @@ const HeroActions = styled.div` justify-content: center; `; -// Diskret ghost-länk, t.ex. "Läs mer →" +// Subtle ghost link, e.g. "Read more →" const BtnGhostLink = styled.a` font-size: 0.85rem; font-weight: 400; @@ -122,10 +122,10 @@ const BtnGhostLink = styled.a` `; -// ─── STATISTIK-PILLS ────────────────────────────────────────── -// Tre pills längst ner i hero-sektionen. -// MOBIL (bas): döljs — tar för mycket plats på liten skärm -// DESKTOP 768+: visas längst ner på kortet +// ─── STATS PILLS ────────────────────────────────────────────── +// Three pills at the bottom of the hero section. +// MOBILE (base): hidden — takes too much space on small screen +// DESKTOP 768+: shown at the bottom of the card const Stats = styled.div` /* MOBIL: döljs */ /* display: none; */ @@ -136,7 +136,7 @@ const Stats = styled.div` flex-wrap: wrap; animation: ${fadeUp} 0.8s 0.4s ease both; - /* DESKTOP 768+: visas */ + /* DESKTOP 768+: shown */ @media (min-width: 768px) { position: absolute; bottom: 2.5rem; @@ -173,15 +173,15 @@ const StatSep = styled.span` `; -// ─── FLOAT-KORT ─────────────────────────────────────────────── -// Det svävande kortet med "riskbedömning"-animationen. -// MOBIL (bas): döljs — tar för mycket plats -// DESKTOP 900+: visas till höger om hero-texten +// ─── FLOAT CARD ─────────────────────────────────────────────── +// The floating card with the "risk assessment" animation. +// MOBILE (base): hidden — takes too much space +// DESKTOP 900+: shown to the right of the hero text const FloatCard = styled.div` - /* MOBIL: döljs */ + /* MOBILE: hidden */ display: none; - /* DESKTOP 900+: visas som ett svävande kort */ + /* DESKTOP 900+: shown as a floating card */ @media (min-width: 900px) { display: block; position: absolute; @@ -285,14 +285,14 @@ const LandingPage = () => ( - {/* Stats döljs på mobil, visas på desktop */} + {/* Stats hidden on mobile, shown on desktop */} Medicinsk vägledningGratis Community & stödAlltid tillgänglig Snabb hjälpSymtomkoll på under 60 sek - {/* FloatCard döljs på mobil, visas på desktop */} + {/* FloatCard hidden on mobile, shown on desktop */} Din riskbedömning Symtomanalys
    pågår…
    @@ -305,7 +305,7 @@ const LandingPage = () => ( ); -// ─── SVG-IKONER ─────────────────────────────────────────────── +// ─── SVG ICONS ──────────────────────────────────────────────── const ArrowIcon = () => ( { const handleSubmit = async (e) => { e.preventDefault(); - const response = await fetch("http://localhost:8081/api/login", { + const response = await fetch(`${import.meta.env.VITE_API_URL}/api/login`, { method: "POST", headers: { @@ -388,7 +388,7 @@ const LoginPage = ({ onLogin }) => { const handleRegister = async (e) => { e.preventDefault(); - const response = await fetch("http://localhost:8081/api/register", { + const response = await fetch(`${import.meta.env.VITE_API_URL}/api/register`, { method: "POST", headers: { @@ -424,7 +424,7 @@ const LoginPage = ({ onLogin }) => { Säker anslutning v0.1 - {/* Vänster — bara desktop */} + {/* Left — desktop only */} @@ -445,9 +445,9 @@ const LoginPage = ({ onLogin }) => { - {/* Höger — formulär */} + {/* Right — form */} - {/* Logga bara synlig på mobil */} + {/* Logo only visible on mobile */} diff --git a/frontend/src/pages/MedicinPage.jsx b/frontend/src/pages/MedicinPage.jsx index 1db14b7fd..46069560a 100644 --- a/frontend/src/pages/MedicinPage.jsx +++ b/frontend/src/pages/MedicinPage.jsx @@ -1,60 +1,60 @@ import React, { useState } from 'react'; import styled, { keyframes, css } from 'styled-components'; -// react-toastify: toast = triggar en notis, ToastContainer = måste finnas i trädet +// react-toastify: toast = triggers a notification, ToastContainer = must exist in the tree import { toast, ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; -// Frame = yttersta wrapper, Card = gradient-kortet, +// Frame = outermost wrapper, Card = gradient card, import { Frame, Card, CornerLabel, fadeUp } from '../components/Layout.jsx'; import Navbar from '../components/Navbar.jsx'; import { theme } from '../styles/theme'; import BottomNav from "../components/BottomNav.jsx" -// ─── ANIMATIONER ───────────────────────────────────────────────────────────── +// ─── ANIMATIONS ────────────────────────────────────────────────────────────── -// Glider upp från nedanför — används på medicin-korten och modal-sheeten +// Slides up from below — used on the medicine cards and modal sheet const slideUp = keyframes` from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } `; -// Studs-animation när en medicin markeras som tagen +// Bounce animation when a medicine is marked as taken const checkPop = keyframes` 0% { transform: scale(0.8); } 60% { transform: scale(1.2); } 100% { transform: scale(1); } `; -// Enkel fade-in för modal-bakgrunden +// Simple fade-in for the modal background const fadeIn = keyframes` from { opacity: 0; } to { opacity: 1; } `; -// ─── SCROLL-AREA ───────────────────────────────────────────────────────────── -// Innehåller hela medicin-listan och kan scrollas. -// padding-bottom är stor (7rem) så innehållet inte döljs bakom den fasta knappen. -// Scrollbaren döljs visuellt för ett renare mobilutseende. +// ─── SCROLL AREA ───────────────────────────────────────────────────────────── +// Contains the entire medicine list and is scrollable. +// padding-bottom is large (7rem) so content is not hidden behind the fixed button. +// The scrollbar is hidden visually for a cleaner mobile appearance. // -// MOBIL (bas): full bredd, mindre sidpadding -// DESKTOP 768+: begränsad max-bredd och centreras med margin: auto +// MOBILE (base): full width, less side padding +// DESKTOP 768+: limited max-width and centered with margin: auto const ScrollArea = styled.div` - /* MOBIL: full bredd, lite luft på sidorna */ + /* MOBILE: full width, some padding on the sides */ position: relative; z-index: 10; flex: 1; overflow-y: auto; - padding: 0.5rem 1.4rem 7rem; /* stor bottom-padding pga den fasta knappen */ + padding: 0.5rem 1.4rem 7rem; /* large bottom padding due to the fixed button */ - /* Döljer scrollbaren i Firefox */ + /* Hides the scrollbar in Firefox */ scrollbar-width: none; - /* Döljer scrollbaren i Chrome/Safari */ + /* Hides the scrollbar in Chrome/Safari */ &::-webkit-scrollbar { display: none; } - /* DESKTOP 768+: begränsa bredden och centrera */ + /* DESKTOP 768+: limit width and center */ @media (min-width: 768px) { padding: 0.5rem 2.5rem 5rem; max-width: 640px; @@ -64,17 +64,17 @@ const ScrollArea = styled.div` `; -// ─── DAGSSTATUS-SEKTION ─────────────────────────────────────────────────────── -// Visar datum, veckodagsnamn och hur många mediciner som är kvar. -// Inga responsiva skillnader här — fungerar lika bra på alla skärmar. +// ─── DAY STATUS SECTION ─────────────────────────────────────────────────────── +// Shows date, weekday name and how many medicines are left. +// No responsive differences here — works equally well on all screens. -// Wrapper för hela blocket, glider in vid sidladdning +// Wrapper for the whole block, slides in on page load const DayHeader = styled.div` margin-bottom: 1.5rem; animation: ${fadeUp} 0.6s ease both; `; -// Liten etikett ovanför, t.ex. "26 februari" +// Small label above, e.g. "26 February" const DayLabel = styled.div` font-size: 0.68rem; font-weight: 500; @@ -84,9 +84,9 @@ const DayLabel = styled.div` margin-bottom: 0.3rem; `; -// Stor serif-rubrik, t.ex. "Måndag — 2 kvar" -// clamp() gör fontstorleken responsiv utan media queries: -// minimum 1.6rem, ökar med 5% av viewportbredden, max 2.2rem +// Large serif heading, e.g. "Monday — 2 left" +// clamp() makes the font size responsive without media queries: +// minimum 1.6rem, grows by 5% of the viewport width, max 2.2rem const DayTitle = styled.h2` font-family: ${theme.fonts.serif}; font-size: clamp(1.6rem, 5vw, 2.2rem); @@ -96,7 +96,7 @@ const DayTitle = styled.h2` em { font-style: italic; color: rgba(255,255,255,0.65); } `; -// Undertext, t.ex. "1 av 2 mediciner tagna idag" +// Subtext, e.g. "1 of 2 medicines taken today" const DaySubtext = styled.p` font-size: 0.82rem; color: rgba(255,255,255,0.6); @@ -104,10 +104,10 @@ const DaySubtext = styled.p` `; -// ─── SEKTION-RUBRIK ─────────────────────────────────────────────────────────── -// Rubrikerna "Idag" och "Imorgon". -// ::after skapar den horisontella linjen till höger om texten. -// Inga responsiva skillnader. +// ─── SECTION HEADING ─────────────────────────────────────────────────────────── +// The headings "Today" and "Tomorrow". +// ::after creates the horizontal line to the right of the text. +// No responsive differences. const SectionTitle = styled.div` font-size: 0.65rem; font-weight: 500; @@ -128,14 +128,14 @@ const SectionTitle = styled.div` `; -// ─── MEDICIN-KORT ───────────────────────────────────────────────────────────── -// $taken (boolean): styr grön eller neutral färgton -// $delay (sträng): animationsfördröjning per kort för sekventiell inglid +// ─── MEDICINE CARD ───────────────────────────────────────────────────────────── +// $taken (boolean): controls green or neutral colour +// $delay (string): animation delay per card for sequential slide-in // -// MOBIL (bas): kompaktare padding -// DESKTOP 768+: lite mer luft +// MOBILE (base): more compact padding +// DESKTOP 768+: a little more space const MedCard = styled.div` - /* MOBIL: kompakt padding */ + /* MOBILE: compact padding */ background: ${({ $taken }) => $taken ? 'rgba(125,255,212,0.08)' : 'rgba(0,0,0,0.25)'}; border: 1px solid ${({ $taken }) => $taken ? 'rgba(125,255,212,0.3)' : 'rgba(255,255,255,0.15)'}; border-radius: 14px; @@ -147,7 +147,7 @@ const MedCard = styled.div` transition: border-color 0.3s, background 0.3s; animation: ${slideUp} 0.5s ${({ $delay }) => $delay || '0s'} ease both; - /* DESKTOP 768+: mer luft och rundare hörn */ + /* DESKTOP 768+: more space and rounder corners */ @media (min-width: 768px) { border-radius: 16px; padding: 1rem 1.2rem; @@ -156,11 +156,11 @@ const MedCard = styled.div` } `; -// Rund checkknapp till vänster. -// $taken styr färg. checkPop-animationen spelas bara när $taken är true. -// Storleken är 40px på mobil (lättare att trycka) och 36px på desktop. +// Round check button on the left. +// $taken controls colour. The checkPop animation only plays when $taken is true. +// Size is 40px on mobile (easier to tap) and 36px on desktop. const CheckBtn = styled.button` - /* MOBIL: lite större för enklare touch-interaktion */ + /* MOBILE: slightly larger for easier touch interaction */ width: 40px; height: 40px; border-radius: 50%; @@ -177,43 +177,43 @@ const CheckBtn = styled.button` &:active { transform: scale(0.92); } - /* DESKTOP 768+: standard storlek */ + /* DESKTOP 768+: standard size */ @media (min-width: 768px) { width: 36px; height: 36px; } `; -// Mittencolumn med namn och dos — klickbar för att öppna redigera-modalen +// Middle column with name and dose — clickable to open the edit modal const MedInfo = styled.div` flex: 1; - min-width: 0; /* förhindrar text-overflow utanför kortet */ + min-width: 0; /* prevents text overflow outside the card */ cursor: pointer; `; -// Medicinnamn med genomstrykning och tonad färg när tagen +// Medicine name with strikethrough and faded colour when taken const MedName = styled.div` - /* MOBIL: standard storlek */ + /* MOBILE: standard size */ font-size: 0.92rem; font-weight: 500; color: ${({ $taken }) => $taken ? 'rgba(255,255,255,0.45)' : '#fff'}; text-decoration: ${({ $taken }) => $taken ? 'line-through' : 'none'}; transition: color 0.3s; - /* DESKTOP 768+: något större */ + /* DESKTOP 768+: slightly larger */ @media (min-width: 768px) { font-size: 0.95rem; } `; -// Dos-text, t.ex. "5 mg" +// Dose text, e.g. "5 mg" const MedDose = styled.div` font-size: 0.75rem; color: rgba(255,255,255,0.4); margin-top: 0.15rem; `; -// Höger kolumn: tid, tagen-klockslag och åtgärdsknappar +// Right column: time, taken-time and action buttons const MedRight = styled.div` display: flex; flex-direction: column; @@ -221,7 +221,7 @@ const MedRight = styled.div` gap: 0.3rem; `; -// Tidtext, t.ex. "kl 20:00" (amber) eller "✓ Tagen" (grön) +// Time text, e.g. "kl 20:00" (amber) or "✓ Tagen" (green) const MedTime = styled.div` font-size: 0.72rem; font-weight: 500; @@ -229,17 +229,17 @@ const MedTime = styled.div` white-space: nowrap; `; -// Klockslag för när medicinen faktiskt togs, t.ex. "08:14" +// Timestamp for when the medicine was actually taken, e.g. "08:14" const TakenTime = styled.div` font-size: 0.68rem; color: rgba(125,255,212,0.55); `; -// ─── ÅTGÄRDSKNAPPAR (REDIGERA / TA BORT) ───────────────────────────────────── -// $danger (boolean): false = neutral (penna), true = röd (papperskorg) +// ─── ACTION BUTTONS (EDIT / DELETE) ───────────────────────────────────────── +// $danger (boolean): false = neutral (pencil), true = red (trash) // -// MOBIL (bas): lite större (44px) för touch +// MOBILE (base): slightly larger (44px) for touch // DESKTOP 768+: standard (30px) const ActionRow = styled.div` @@ -249,7 +249,7 @@ const ActionRow = styled.div` `; const IconBtn = styled.button` - /* MOBIL: touch-vänlig storlek (44px rekommenderas av Apple/Google) */ + /* MOBILE: touch-friendly size (44px recommended by Apple/Google) */ width: 34px; height: 34px; border-radius: 8px; @@ -264,7 +264,7 @@ const IconBtn = styled.button` &:active { transform: scale(0.92); } - /* DESKTOP 768+: kompaktare storlek + hover-effekter (hover finns inte på touch) */ + /* DESKTOP 768+: more compact size + hover effects (hover does not exist on touch) */ @media (min-width: 768px) { width: 30px; height: 30px; @@ -278,9 +278,9 @@ const IconBtn = styled.button` `; -// ─── IMORGON-KORT ───────────────────────────────────────────────────────────── -// Nedtonade, informativa kort utan åtgärdsknappar. -// Inga responsiva skillnader behövs här. +// ─── TOMORROW CARD ───────────────────────────────────────────────────────────── +// Dimmed, informative cards without action buttons. +// No responsive differences needed here. const TomorrowCard = styled.div` background: rgba(0,0,0,0.15); border: 1px solid rgba(255,255,255,0.08); @@ -312,7 +312,7 @@ const TomorrowTime = styled.div` `; -// Knappen är fixed längst ner. +// The button is fixed at the bottom. const AddBtnWrap = styled.div` position: fixed; @@ -347,7 +347,7 @@ const AddBtn = styled.button` box-shadow: 0 8px 30px rgba(0,0,0,0.3); transition: background 0.2s, transform 0.15s; - /* Hover finns bara på desktop — på mobil är det :active som gäller */ + /* Hover only exists on desktop — on mobile :active is used instead */ @media (min-width: 768px) { &:hover { background: #fff; transform: translateY(-1px); } } @@ -356,48 +356,48 @@ const AddBtn = styled.button` // ─── MODAL BACKDROP ─────────────────────────────────────────────────────────── -// Täcker hela skärmen bakom modalen. -// Klick på backdropen (inte på Sheet) stänger modalen via e.target check i JSX. +// Covers the entire screen behind the modal. +// Clicking the backdrop (not the Sheet) closes the modal via e.target check in JSX. // -// MOBIL (bas): align-items: flex-end → Sheet kommer nerifrån (bottom sheet) -// DESKTOP 768+: align-items: center → Sheet centreras på skärmen +// MOBILE (base): align-items: flex-end → Sheet slides in from below (bottom sheet) +// DESKTOP 768+: align-items: center → Sheet is centered on screen const Backdrop = styled.div` - /* MOBIL: modal glider upp nerifrån */ + /* MOBILE: modal slides up from below */ position: fixed; inset: 0; background: rgba(0,0,0,0.65); backdrop-filter: blur(6px); z-index: 200; display: flex; - align-items: flex-end; /* bottom sheet på mobil */ + align-items: flex-end; /* bottom sheet on mobile */ justify-content: center; animation: ${fadeIn} 0.2s ease both; - /* DESKTOP 768+: centrerad modal */ + /* DESKTOP 768+: centered modal */ @media (min-width: 768px) { align-items: center; } `; -// ─── BEKRÄFTELSE-DIALOG (TA BORT) ───────────────────────────────────────────── -// Visas när användaren trycker papperskorgen. -// Kräver ett extra klick för att bekräfta — förhindrar misstag. +// ─── CONFIRM DIALOG (DELETE) ───────────────────────────────────────────────── +// Shown when the user presses the trash icon. +// Requires an extra click to confirm — prevents mistakes. // -// MOBIL (bas): bottom sheet (inga rundade underhörn) -// DESKTOP 768+: centrerad dialog med rundade hörn runt om +// MOBILE (base): bottom sheet (no rounded lower corners) +// DESKTOP 768+: centered dialog with rounded corners all around const ConfirmSheet = styled.div` - /* MOBIL: bottom sheet stil */ + /* MOBILE: bottom sheet style */ background: #0d3d45; border: 1px solid rgba(255,138,125,0.25); - border-radius: 20px 20px 0 0; /* rundade hörn bara uppåt */ + border-radius: 20px 20px 0 0; /* rounded corners only at the top */ padding: 1.6rem 1.4rem 2.2rem; width: 100%; max-width: 400px; animation: ${slideUp} 0.3s ease both; text-align: center; - /* DESKTOP 768+: dialog centreras med rundade hörn runt om */ + /* DESKTOP 768+: dialog centered with rounded corners all around */ @media (min-width: 768px) { border-radius: 20px; margin: 1rem; @@ -439,14 +439,14 @@ const DeleteConfirmBtn = styled.button` `; -// ─── BOTTOM SHEET / MODAL (LÄGG TILL & REDIGERA) ───────────────────────────── -// Samma Sheet-komponent används för båda lägena. -// modalMode ('add' | 'edit') styr rubrik och knapp-text. +// ─── BOTTOM SHEET / MODAL (ADD & EDIT) ─────────────────────────────────────── +// The same Sheet component is used for both modes. +// modalMode ('add' | 'edit') controls heading and button text. // -// MOBIL (bas): bottom sheet, glider upp nerifrån -// DESKTOP 768+: centrerad dialog +// MOBILE (base): bottom sheet, slides up from below +// DESKTOP 768+: centered dialog const Sheet = styled.div` - /* MOBIL: full bredd, bottom sheet */ + /* MOBILE: full width, bottom sheet */ background: #0d3d45; border: 1px solid rgba(255,255,255,0.15); border-radius: 20px 20px 0 0; @@ -455,7 +455,7 @@ const Sheet = styled.div` max-width: 500px; animation: ${slideUp} 0.3s ease both; - /* DESKTOP 768+: centrerad modal */ + /* DESKTOP 768+: centered modal */ @media (min-width: 768px) { border-radius: 24px; margin: 1rem; @@ -463,17 +463,17 @@ const Sheet = styled.div` } `; -// Litet handtag längst upp — konvention för bottom sheets på mobil. -// Döljs på desktop eftersom det inte är ett bottom sheet där. +// Small handle at the top — convention for bottom sheets on mobile. +// Hidden on desktop since it is not a bottom sheet there. const SheetHandle = styled.div` - /* MOBIL: synligt handtag */ + /* MOBILE: visible handle */ width: 36px; height: 3px; background: rgba(255,255,255,0.2); border-radius: 2px; margin: 0 auto 1.5rem; - /* DESKTOP 768+: döljs */ + /* DESKTOP 768+: hidden */ @media (min-width: 768px) { display: none; } @@ -501,38 +501,38 @@ const FieldLabel = styled.label` margin-bottom: 0.45rem; `; -// Textfält och tidsfält i formuläret. -// automatiskt vid fokus — under 16px triggas auto-zoom. -// -webkit-appearance: none tar bort iOS standardstilar (border, shadow etc.) +// Text fields and time fields in the form. +// automatically on focus — below 16px auto-zoom is triggered. +// -webkit-appearance: none removes iOS default styles (border, shadow etc.) const FieldInput = styled.input` - /* MOBIL: stor nog för att undvika iOS auto-zoom */ + /* MOBILE: large enough to avoid iOS auto-zoom */ width: 100%; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.15); border-radius: 10px; - padding: 0.85rem 1rem; /* touch-vänlig höjd */ - font-size: 1rem; /* 16px = iOS-gränsen för auto-zoom */ + padding: 0.85rem 1rem; /* touch-friendly height */ + font-size: 1rem; /* 16px = iOS threshold for auto-zoom */ color: #fff; outline: none; transition: border-color 0.2s; - -webkit-appearance: none; /* tar bort iOS standardstilar */ + -webkit-appearance: none; /* removes iOS default styles */ &::placeholder { color: rgba(255,255,255,0.22); } &:focus { border-color: rgba(125,255,212,0.5); } `; -// Rad med dag-knappar (M T O T F L S) -// Inga responsiva skillnader — fungerar bra på alla skärmar +// Row of day buttons (M T W T F S S) +// No responsive differences — works well on all screens const DayRow = styled.div` display: flex; gap: 0.4rem; `; -// Dag-knapp. $active styr grön (vald) eller grå (ej vald) stil. -// flex: 1 gör att alla sju knappar tar lika mycket plats. -// Lite större padding på mobil för enklare touch. +// Day button. $active controls green (selected) or grey (unselected) style. +// flex: 1 makes all seven buttons take equal space. +// Slightly larger padding on mobile for easier touch. const DayToggle = styled.button` - /* MOBIL: generös touch-yta */ + /* MOBILE: generous touch area */ flex: 1; padding: 0.65rem 0; border-radius: 8px; @@ -546,13 +546,13 @@ const DayToggle = styled.button` &:active { transform: scale(0.95); } - /* DESKTOP 768+: kompaktare padding */ + /* DESKTOP 768+: more compact padding */ @media (min-width: 768px) { padding: 0.55rem 0; } `; -// Spara-knapp längst ner i formuläret +// Save button at the bottom of the form const SaveBtn = styled.button` width: 100%; background: rgba(255,255,255,0.92); @@ -572,7 +572,7 @@ const SaveBtn = styled.button` &:active { transform: scale(0.98); } `; -// Avbryt-knapp — diskret textstil +// Cancel button — subtle text style const CancelBtn = styled.button` width: 100%; background: transparent; @@ -589,8 +589,8 @@ const CancelBtn = styled.button` `; -// ─── TOM-STATE ──────────────────────────────────────────────────────────────── -// Visas när medicines-listan är tom. +// ─── EMPTY STATE ────────────────────────────────────────────────────────────── +// Shown when the medicines list is empty. const EmptyState = styled.div` text-align: center; padding: 3rem 1rem; @@ -620,26 +620,26 @@ const EmptySub = styled.p` `; -// ─── KONSTANTER OCH HJÄLPFUNKTIONER ────────────────────────────────────────── +// ─── CONSTANTS AND HELPER FUNCTIONS ────────────────────────────────────────── -// Svenska förkortningar för dag-toggle-knapparna +// Swedish abbreviations for the day toggle buttons const DAY_NAMES = ['Mån', 'Tis', 'Ons', 'Tor', 'Fre', 'Lör', 'Sön']; -// Returnerar dagens namn på svenska. getDay() ger 0 (sön) – 6 (lör). +// Returns today's name in Swedish. getDay() returns 0 (Sun) – 6 (Sat). const getTodayName = () => { const names = ['Söndag', 'Måndag', 'Tisdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lördag']; return names[new Date().getDay()]; }; -// Returnerar datumet formaterat på svenska, t.ex. "26 februari" +// Returns the date formatted in Swedish, e.g. "26 februari" const getDateString = () => new Date().toLocaleDateString('sv-SE', { day: 'numeric', month: 'long' }); -// Tomt formulär — används för att återställa vid öppna/stäng +// Empty form — used to reset on open/close const EMPTY_FORM = { name: '', dose: '', time: '08:00' }; -// Återanvändbar hjälpfunktion för toast-stilar. -// Tar emot valfri border-färg och textfärg så varje typ ser olika ut. +// Reusable helper function for toast styles. +// Accepts an optional border colour and text colour so each type looks different. const toastStyle = ( border = 'rgba(255,255,255,0.15)', color = 'rgba(255,255,255,0.9)' @@ -656,38 +656,38 @@ const toastStyle = ( }); -// ─── HUVUDKOMPONENT ─────────────────────────────────────────────────────────── +// ─── MAIN COMPONENT ─────────────────────────────────────────────────────────── const MedicinPage = () => { // ── STATE ──────────────────────────────────────────────────────────────── - // Listan med mediciner. Varje objekt: + // The list of medicines. Each object: // id, name, dose, time ("HH:MM"), days ([0-6]), taken (bool), takenAt (str|null) const [medicines, setMedicines] = useState([ /* { id: 1, name: 'Waran', dose: '5 mg', time: '08:00', days: [0,1,2,3,4,5,6], taken: true, takenAt: '08:14' }, { id: 2, name: 'Xarelto', dose: '20 mg', time: '20:00', days: [0,1,2,3,4,5,6], taken: false, takenAt: null }, */ ]); - // null = stängd, 'add' = lägg till, 'edit' = redigera + // null = closed, 'add' = add, 'edit' = edit const [modalMode, setModalMode] = useState(null); - // ID för medicinen som redigeras (null om ingen redigeras) + // ID of the medicine being edited (null if none being edited) const [editingId, setEditingId] = useState(null); - // ID för medicinen vars ta bort-dialog visas (null om ingen) + // ID of the medicine whose delete dialog is shown (null if none) const [confirmId, setConfirmId] = useState(null); - // Valda veckodagar i formuläret (array med index 0-6) + // Selected weekdays in the form (array with indices 0-6) const [activeDays, setActiveDays] = useState([0,1,2,3,4,5,6]); - // Formulärdata + // Form data const [form, setForm] = useState(EMPTY_FORM); - // ── BERÄKNADE VÄRDEN ───────────────────────────────────────────────────── + // ── COMPUTED VALUES ───────────────────────────────────────────────────── const takenCount = medicines.filter(m => m.taken).length; const remainingCount = medicines.filter(m => !m.taken).length; - // ── ÖPPNA "LÄGG TILL"-MODAL ─────────────────────────────────────────────── + // ── OPEN "ADD" MODAL ─────────────────────────────────────────────────── const openAdd = () => { setForm(EMPTY_FORM); setActiveDays([0,1,2,3,4,5,6]); @@ -695,8 +695,8 @@ const MedicinPage = () => { setModalMode('add'); }; - // ── ÖPPNA "REDIGERA"-MODAL ──────────────────────────────────────────────── - // Fyller formuläret med befintliga värden och sätter editingId + // ── OPEN "EDIT" MODAL ────────────────────────────────────────────────── + // Fills the form with existing values and sets editingId const openEdit = (med) => { setForm({ name: med.name, dose: med.dose, time: med.time }); setActiveDays(med.days); @@ -704,19 +704,19 @@ const MedicinPage = () => { setModalMode('edit'); }; - // ── STÄNG MODAL ─────────────────────────────────────────────────────────── + // ── CLOSE MODAL ─────────────────────────────────────────────────────────── const closeModal = () => { setModalMode(null); setEditingId(null); }; - // ── SPARA (NY ELLER UPPDATERING) ───────────────────────────────────────── + // ── SAVE (NEW OR UPDATE) ───────────────────────────────────────────────── const saveMedicine = () => { if (!form.name.trim()) return; if (modalMode === 'edit') { - // map() returnerar ny array där bara rätt medicin är ändrad + // map() returns a new array where only the right medicine is changed setMedicines(prev => prev.map(m => m.id === editingId ? { ...m, name: form.name, dose: form.dose, time: form.time, days: activeDays } @@ -727,13 +727,13 @@ const MedicinPage = () => { } else { const newMed = { - id: Date.now(), // unikt ID via tidsstämpel + id: Date.now(), // unique ID via timestamp name: form.name, dose: form.dose, time: form.time, days: activeDays, taken: false, takenAt: null, }; setMedicines(prev => [...prev, newMed]); - // Schemalägg toast-påminnelse om vald tid är senare idag + // Schedule toast reminder if selected time is later today const [h, min] = form.time.split(':').map(Number); const triggerTime = new Date(); triggerTime.setHours(h, min, 0, 0); @@ -753,7 +753,7 @@ const MedicinPage = () => { }; - // ── MARKERA SOM TAGEN / OTAGEN ──────────────────────────────────────────── + // ── MARK AS TAKEN / UNTAKEN ────────────────────────────────────────────── const toggleTaken = (id) => { setMedicines(prev => prev.map(m => { if (m.id !== id) return m; @@ -768,8 +768,8 @@ const MedicinPage = () => { }; - // ── TA BORT ─────────────────────────────────────────────────────────────── - // filter() returnerar alla mediciner UTOM den med confirmId + // ── DELETE ─────────────────────────────────────────────────────────────── + // filter() returns all medicines EXCEPT the one with confirmId const deleteMedicine = () => { const med = medicines.find(m => m.id === confirmId); setMedicines(prev => prev.filter(m => m.id !== confirmId)); @@ -779,7 +779,7 @@ const MedicinPage = () => { }; - // ── TOGGA VECKODAG ──────────────────────────────────────────────────────── + // ── TOGGLE WEEKDAY ──────────────────────────────────────────────────────── const toggleDay = (i) => setActiveDays(prev => prev.includes(i) ? prev.filter(d => d !== i) : [...prev, i] @@ -797,7 +797,7 @@ const MedicinPage = () => { - {/* Dagsstatus */} + {/* Day status */} {getDateString()} @@ -811,7 +811,7 @@ const MedicinPage = () => { Idag - {/* Tom-state eller medicin-lista */} + {/* Empty state or medicine list */} {medicines.length === 0 ? ( @@ -819,11 +819,11 @@ const MedicinPage = () => { Tryck på knappen nedan för att lägga till din första medicin ) : ( - // $delay förskjuter animationen per kort → sekventiell inglid + // $delay staggers the animation per card → sequential slide-in medicines.map((med, i) => ( - {/* Checkknapp — togglar tagen/otagen */} + {/* Check button — toggles taken/untaken */} toggleTaken(med.id)}> {med.taken ? @@ -831,13 +831,13 @@ const MedicinPage = () => { } - {/* Namn + dos — klick öppnar redigera-modalen */} + {/* Name + dose — click opens the edit modal */} openEdit(med)}> {med.name} {med.dose} - {/* Höger kolumn */} + {/* Right column */} {med.taken ? '✓ Tagen' : `kl ${med.time}`} @@ -858,7 +858,7 @@ const MedicinPage = () => { )) )} - {/* Imorgon-sektion */} + {/* Tomorrow section */} {medicines.length > 0 && ( <> Imorgon @@ -874,7 +874,7 @@ const MedicinPage = () => { - {/* Fast Lägg till-knapp */} + {/* Fixed Add button */} @@ -886,7 +886,7 @@ const MedicinPage = () => {
    - {/* ── LÄGG TILL / REDIGERA MODAL ── */} + {/* ── ADD / EDIT MODAL ── */} {modalMode && ( e.target === e.currentTarget && closeModal()}> @@ -950,7 +950,7 @@ const MedicinPage = () => { )} - {/* ── BEKRÄFTELSE-DIALOG (TA BORT) ── */} + {/* ── CONFIRM DIALOG (DELETE) ── */} {confirmId && ( e.target === e.currentTarget && setConfirmId(null)}> @@ -970,7 +970,7 @@ const MedicinPage = () => { }; -// ─── SVG-IKONER ─────────────────────────────────────────────────────────────── +// ─── SVG ICONS ─────────────────────────────────────────────────────────────── const CheckIcon = ({ color = 'white' }) => ( diff --git a/frontend/src/pages/OmossPage.jsx b/frontend/src/pages/OmossPage.jsx index 271377ff1..984d963dc 100644 --- a/frontend/src/pages/OmossPage.jsx +++ b/frontend/src/pages/OmossPage.jsx @@ -22,7 +22,7 @@ const ScrollArea = styled.div` } `; -// ─── HERO-SEKTION ───────────────────────────────────────────── +// ─── HERO SECTION ───────────────────────────────────────────── const Hero = styled.div` margin-bottom: 3rem; animation: ${fadeUp} 0.7s ease both; @@ -72,14 +72,14 @@ const HeroText = styled.p` max-width: 520px; `; -// ─── AVDELARE ───────────────────────────────────────────────── +// ─── DIVIDER ────────────────────────────────────────────────── const Divider = styled.div` height: 1px; background: rgba(255,255,255,0.1); margin: 2.5rem 0; `; -// ─── SEKTION-RUBRIK ─────────────────────────────────────────── +// ─── SECTION HEADING ────────────────────────────────────────── const SectionLabel = styled.div` font-size: 0.65rem; font-weight: 500; @@ -89,7 +89,7 @@ const SectionLabel = styled.div` margin-bottom: 1rem; `; -// ─── VÄRDE-KORT (de tre kort-kolumnerna) ───────────────────── +// ─── VALUE CARDS (the three card columns) ──────────────────── const CardGrid = styled.div` display: flex; flex-direction: column; @@ -135,7 +135,7 @@ const ValueText = styled.div` line-height: 1.65; `; -// ─── LÖPTEXT-SEKTION ────────────────────────────────────────── +// ─── BODY TEXT SECTION ──────────────────────────────────────── const TextSection = styled.div` margin-bottom: 2.5rem; animation: ${fadeUp} 0.6s 0.2s ease both; @@ -160,7 +160,7 @@ const BodyText = styled.p` margin-bottom: 0.9rem; `; -// ─── TEAM-SEKTION ───────────────────────────────────────────── +// ─── TEAM SECTION ───────────────────────────────────────────── const TeamGrid = styled.div` display: flex; flex-direction: column; @@ -212,7 +212,7 @@ const TeamRole = styled.div` color: rgba(255,255,255,0.45); `; -// ─── STATISTIK-RAD ──────────────────────────────────────────── +// ─── STATS ROW ──────────────────────────────────────────────── const StatsRow = styled.div` display: grid; grid-template-columns: 1fr 1fr; @@ -248,7 +248,7 @@ const StatLabel = styled.div` letter-spacing: 0.04em; `; -// ─── CTA-KORT ───────────────────────────────────────────────── +// ─── CTA CARD ───────────────────────────────────────────────── const CtaCard = styled.div` background: rgba(125,255,212,0.07); border: 1px solid rgba(125,255,212,0.2); @@ -343,7 +343,7 @@ const OmOssPage = () => ( - {/* Vad vi erbjuder */} + {/* What we offer */} Vad vi erbjuder Tre pelare @@ -397,8 +397,8 @@ const OmOssPage = () => ( komplement till den svenska vården — aldrig en ersättning. - {/* All medicinsk information är granskad och baseras på Socialstyrelsens - och 1177:s riktlinjer. */} Vi är alltid transparenta med vad appen kan + {/* All medical information is reviewed and based on the National Board of Health's + and 1177's guidelines. */} Vi är alltid transparenta med vad appen kan och inte kan — den ställer inga diagnoser. diff --git a/frontend/src/pages/ProfilPage.jsx b/frontend/src/pages/ProfilPage.jsx index d0a5d7b64..279170a58 100644 --- a/frontend/src/pages/ProfilPage.jsx +++ b/frontend/src/pages/ProfilPage.jsx @@ -322,10 +322,8 @@ const ProfilPage = () => { - - Kontoinformation @@ -337,10 +335,8 @@ const ProfilPage = () => { - - Inställningar @@ -374,7 +370,6 @@ const ProfilPage = () => { - Farlig zon {}}> {/* will do later */} From 3a920f869be02d1c86e7360157368cc9064fe260 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Sun, 8 Mar 2026 12:11:23 +0100 Subject: [PATCH 33/48] added comments --- backend/models/Moodentry.js | 0 backend/routes/medications.js | 1 + frontend/public/vite.svg | 1 - frontend/src/components/BottomNav.jsx | 3 -- frontend/src/data/questions.js | 1 + frontend/src/pages/FaqPage.jsx | 3 +- frontend/src/pages/Hurdetfungerarpage.jsx | 7 ---- frontend/src/pages/LandingPage.jsx | 2 +- frontend/src/pages/LoginPage.jsx | 2 +- frontend/src/pages/MedicinPage.jsx | 46 ++--------------------- frontend/src/pages/OmossPage.jsx | 1 - frontend/src/pages/SymptomCheckPage.jsx | 1 + 12 files changed, 10 insertions(+), 58 deletions(-) delete mode 100644 backend/models/Moodentry.js delete mode 100644 frontend/public/vite.svg diff --git a/backend/models/Moodentry.js b/backend/models/Moodentry.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/routes/medications.js b/backend/routes/medications.js index e69de29bb..5623b177d 100644 --- a/backend/routes/medications.js +++ b/backend/routes/medications.js @@ -0,0 +1 @@ +/* will do later */ \ No newline at end of file diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg deleted file mode 100644 index e7b8dfb1b..000000000 --- a/frontend/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/components/BottomNav.jsx b/frontend/src/components/BottomNav.jsx index db2b9ba94..6ef8242a4 100644 --- a/frontend/src/components/BottomNav.jsx +++ b/frontend/src/components/BottomNav.jsx @@ -4,8 +4,6 @@ // Bottom navigation for app pages on mobile. // Shows three large buttons with icon + text at the bottom of the screen. // Hidden on desktop (768px+) since navigation is done via the top menu/back button. -// -// Används på: MedicinPage, SymptomCheckPage // Props: // active = string with active page: 'hem' | 'medicin' | 'symptomkoll' // ───────────────────────────────────────────────────────────────────────────── @@ -42,7 +40,6 @@ const Bar = styled.nav` `; // ─── Individual nav button ──────────────────────────────────────────────────── -// flex: 1 makes all three buttons take exactly equal width. // $active controls whether the button is highlighted (light green) or not (grey). const NavItem = styled.a` flex: 1; diff --git a/frontend/src/data/questions.js b/frontend/src/data/questions.js index e69de29bb..5623b177d 100644 --- a/frontend/src/data/questions.js +++ b/frontend/src/data/questions.js @@ -0,0 +1 @@ +/* will do later */ \ No newline at end of file diff --git a/frontend/src/pages/FaqPage.jsx b/frontend/src/pages/FaqPage.jsx index c41c09999..1ab4af48a 100644 --- a/frontend/src/pages/FaqPage.jsx +++ b/frontend/src/pages/FaqPage.jsx @@ -334,7 +334,7 @@ const FaqPage = () => ( - {/* Accordion per kategori */} + {/* Accordion per category */} {FAQ_DATA.map((cat, ci) => (
    {cat.category} @@ -349,7 +349,6 @@ const FaqPage = () => (
    ))} - {/* Kontakt-kort längst ner */} Hittade du inte svar? diff --git a/frontend/src/pages/Hurdetfungerarpage.jsx b/frontend/src/pages/Hurdetfungerarpage.jsx index eff9539de..697c49673 100644 --- a/frontend/src/pages/Hurdetfungerarpage.jsx +++ b/frontend/src/pages/Hurdetfungerarpage.jsx @@ -362,11 +362,8 @@ const HurDetFungerarPage = () => ( - - - {/* Funktioner */} Funktioner Vad du kan göra @@ -421,11 +418,8 @@ const HurDetFungerarPage = () => ( - - - {/* Viktig info-ruta */} ⚠️ @@ -436,7 +430,6 @@ const HurDetFungerarPage = () => ( - {/* CTA */} Redo att testa? diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx index 3ee35a735..e59cb7e5d 100644 --- a/frontend/src/pages/LandingPage.jsx +++ b/frontend/src/pages/LandingPage.jsx @@ -127,7 +127,7 @@ const BtnGhostLink = styled.a` // MOBILE (base): hidden — takes too much space on small screen // DESKTOP 768+: shown at the bottom of the card const Stats = styled.div` - /* MOBIL: döljs */ + /* MOBIL: hidden */ /* display: none; */ display: flex; justify-content: center; diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index e1165149c..3eacb3759 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -367,7 +367,7 @@ const LoginPage = ({ onLogin }) => { method: "POST", headers: { - "Content-Type": "application/json" //we are saying that we are sending json + "Content-Type": "application/json" //we sending json }, body: JSON.stringify({ email: email, diff --git a/frontend/src/pages/MedicinPage.jsx b/frontend/src/pages/MedicinPage.jsx index 46069560a..3e4654d05 100644 --- a/frontend/src/pages/MedicinPage.jsx +++ b/frontend/src/pages/MedicinPage.jsx @@ -1,17 +1,13 @@ import React, { useState } from 'react'; import styled, { keyframes, css } from 'styled-components'; - -// react-toastify: toast = triggers a notification, ToastContainer = must exist in the tree import { toast, ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; - -// Frame = outermost wrapper, Card = gradient card, import { Frame, Card, CornerLabel, fadeUp } from '../components/Layout.jsx'; import Navbar from '../components/Navbar.jsx'; import { theme } from '../styles/theme'; import BottomNav from "../components/BottomNav.jsx" - +// react-toastify: toast = triggers a notification, ToastContainer = must exist in the tree // ─── ANIMATIONS ────────────────────────────────────────────────────────────── // Slides up from below — used on the medicine cards and modal sheet @@ -38,9 +34,6 @@ const fadeIn = keyframes` // Contains the entire medicine list and is scrollable. // padding-bottom is large (7rem) so content is not hidden behind the fixed button. // The scrollbar is hidden visually for a cleaner mobile appearance. -// -// MOBILE (base): full width, less side padding -// DESKTOP 768+: limited max-width and centered with margin: auto const ScrollArea = styled.div` /* MOBILE: full width, some padding on the sides */ position: relative; @@ -67,8 +60,6 @@ const ScrollArea = styled.div` // ─── DAY STATUS SECTION ─────────────────────────────────────────────────────── // Shows date, weekday name and how many medicines are left. // No responsive differences here — works equally well on all screens. - -// Wrapper for the whole block, slides in on page load const DayHeader = styled.div` margin-bottom: 1.5rem; animation: ${fadeUp} 0.6s ease both; @@ -107,7 +98,6 @@ const DaySubtext = styled.p` // ─── SECTION HEADING ─────────────────────────────────────────────────────────── // The headings "Today" and "Tomorrow". // ::after creates the horizontal line to the right of the text. -// No responsive differences. const SectionTitle = styled.div` font-size: 0.65rem; font-weight: 500; @@ -131,9 +121,7 @@ const SectionTitle = styled.div` // ─── MEDICINE CARD ───────────────────────────────────────────────────────────── // $taken (boolean): controls green or neutral colour // $delay (string): animation delay per card for sequential slide-in -// -// MOBILE (base): more compact padding -// DESKTOP 768+: a little more space + const MedCard = styled.div` /* MOBILE: compact padding */ background: ${({ $taken }) => $taken ? 'rgba(125,255,212,0.08)' : 'rgba(0,0,0,0.25)'}; @@ -158,7 +146,6 @@ const MedCard = styled.div` // Round check button on the left. // $taken controls colour. The checkPop animation only plays when $taken is true. -// Size is 40px on mobile (easier to tap) and 36px on desktop. const CheckBtn = styled.button` /* MOBILE: slightly larger for easier touch interaction */ width: 40px; @@ -238,10 +225,6 @@ const TakenTime = styled.div` // ─── ACTION BUTTONS (EDIT / DELETE) ───────────────────────────────────────── // $danger (boolean): false = neutral (pencil), true = red (trash) -// -// MOBILE (base): slightly larger (44px) for touch -// DESKTOP 768+: standard (30px) - const ActionRow = styled.div` display: flex; gap: 0.35rem; @@ -277,10 +260,8 @@ const IconBtn = styled.button` } `; - // ─── TOMORROW CARD ───────────────────────────────────────────────────────────── // Dimmed, informative cards without action buttons. -// No responsive differences needed here. const TomorrowCard = styled.div` background: rgba(0,0,0,0.15); border: 1px solid rgba(255,255,255,0.08); @@ -311,9 +292,7 @@ const TomorrowTime = styled.div` color: rgba(255,255,255,0.35); `; - // The button is fixed at the bottom. - const AddBtnWrap = styled.div` position: fixed; bottom: 4rem; @@ -354,13 +333,9 @@ const AddBtn = styled.button` &:active { transform: scale(0.98); } `; - // ─── MODAL BACKDROP ─────────────────────────────────────────────────────────── // Covers the entire screen behind the modal. -// Clicking the backdrop (not the Sheet) closes the modal via e.target check in JSX. -// -// MOBILE (base): align-items: flex-end → Sheet slides in from below (bottom sheet) -// DESKTOP 768+: align-items: center → Sheet is centered on screen + const Backdrop = styled.div` /* MOBILE: modal slides up from below */ position: fixed; @@ -379,13 +354,9 @@ const Backdrop = styled.div` } `; - // ─── CONFIRM DIALOG (DELETE) ───────────────────────────────────────────────── // Shown when the user presses the trash icon. // Requires an extra click to confirm — prevents mistakes. -// -// MOBILE (base): bottom sheet (no rounded lower corners) -// DESKTOP 768+: centered dialog with rounded corners all around const ConfirmSheet = styled.div` /* MOBILE: bottom sheet style */ background: #0d3d45; @@ -442,9 +413,7 @@ const DeleteConfirmBtn = styled.button` // ─── BOTTOM SHEET / MODAL (ADD & EDIT) ─────────────────────────────────────── // The same Sheet component is used for both modes. // modalMode ('add' | 'edit') controls heading and button text. -// -// MOBILE (base): bottom sheet, slides up from below -// DESKTOP 768+: centered dialog + const Sheet = styled.div` /* MOBILE: full width, bottom sheet */ background: #0d3d45; @@ -522,7 +491,6 @@ const FieldInput = styled.input` `; // Row of day buttons (M T W T F S S) -// No responsive differences — works well on all screens const DayRow = styled.div` display: flex; gap: 0.4rem; @@ -588,7 +556,6 @@ const CancelBtn = styled.button` } `; - // ─── EMPTY STATE ────────────────────────────────────────────────────────────── // Shown when the medicines list is empty. const EmptyState = styled.div` @@ -619,7 +586,6 @@ const EmptySub = styled.p` color: rgba(255,255,255,0.3); `; - // ─── CONSTANTS AND HELPER FUNCTIONS ────────────────────────────────────────── // Swedish abbreviations for the day toggle buttons @@ -785,8 +751,6 @@ const MedicinPage = () => { prev.includes(i) ? prev.filter(d => d !== i) : [...prev, i] ); - - // ── JSX ────────────────────────────────────────────────────────────────── return ( @@ -885,7 +849,6 @@ const MedicinPage = () => { - {/* ── ADD / EDIT MODAL ── */} {modalMode && ( e.target === e.currentTarget && closeModal()}> @@ -949,7 +912,6 @@ const MedicinPage = () => { )} - {/* ── CONFIRM DIALOG (DELETE) ── */} {confirmId && ( e.target === e.currentTarget && setConfirmId(null)}> diff --git a/frontend/src/pages/OmossPage.jsx b/frontend/src/pages/OmossPage.jsx index 984d963dc..9233c7621 100644 --- a/frontend/src/pages/OmossPage.jsx +++ b/frontend/src/pages/OmossPage.jsx @@ -422,7 +422,6 @@ const OmOssPage = () => ( M Medicinsk rådgivare - {/* Legitimerad sjuksköterska */} diff --git a/frontend/src/pages/SymptomCheckPage.jsx b/frontend/src/pages/SymptomCheckPage.jsx index e69de29bb..5623b177d 100644 --- a/frontend/src/pages/SymptomCheckPage.jsx +++ b/frontend/src/pages/SymptomCheckPage.jsx @@ -0,0 +1 @@ +/* will do later */ \ No newline at end of file From 527c6a3fdd1d35b1f716722f47cb82fded26ea63 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Sun, 8 Mar 2026 12:39:28 +0100 Subject: [PATCH 34/48] funktionality change in profilpage --- backend/models/Symptom.js | 0 frontend/src/data/questions.js | 1 - frontend/src/pages/LoginPage.jsx | 7 ++++--- frontend/src/pages/ProfilPage.jsx | 2 +- frontend/src/pages/SymptomCheckPage.jsx | 1 - frontend/src/store/userStore.js | 20 +++++++++++++------- 6 files changed, 18 insertions(+), 13 deletions(-) delete mode 100644 backend/models/Symptom.js delete mode 100644 frontend/src/data/questions.js delete mode 100644 frontend/src/pages/SymptomCheckPage.jsx diff --git a/backend/models/Symptom.js b/backend/models/Symptom.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/src/data/questions.js b/frontend/src/data/questions.js deleted file mode 100644 index 5623b177d..000000000 --- a/frontend/src/data/questions.js +++ /dev/null @@ -1 +0,0 @@ -/* will do later */ \ No newline at end of file diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index 3eacb3759..603641e82 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -5,6 +5,7 @@ import { theme } from '../styles/theme'; import useUserStore from '../store/userStore.js'; import { useNavigate } from 'react-router-dom'; +const BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8081'; //if the production doesnt work, use an env variabel // ─── Split card ─────────────────────────────────────────────── const SplitCard = styled.div` @@ -355,7 +356,7 @@ const LoginPage = ({ onLogin }) => { const navigate = useNavigate(); const login = useUserStore(state => state.login); const isLoggedIn = useUserStore(state => state.isLoggedIn); - console.log('isLoggedIn:', isLoggedIn) + /* console.log('isLoggedIn:', isLoggedIn) */ /* const [confirmPassword, setconfirmPassword] = useState('') @@ -363,7 +364,7 @@ const LoginPage = ({ onLogin }) => { const handleSubmit = async (e) => { e.preventDefault(); - const response = await fetch(`${import.meta.env.VITE_API_URL}/api/login`, { + const response = await fetch(`${BASE_URL}/api/login`, { method: "POST", headers: { @@ -388,7 +389,7 @@ const LoginPage = ({ onLogin }) => { const handleRegister = async (e) => { e.preventDefault(); - const response = await fetch(`${import.meta.env.VITE_API_URL}/api/register`, { + const response = await fetch(`${BASE_URL}/api/register`, { method: "POST", headers: { diff --git a/frontend/src/pages/ProfilPage.jsx b/frontend/src/pages/ProfilPage.jsx index 279170a58..bb9f99331 100644 --- a/frontend/src/pages/ProfilPage.jsx +++ b/frontend/src/pages/ProfilPage.jsx @@ -340,7 +340,7 @@ const ProfilPage = () => { Inställningar - {window.location.href = '/login'}}> + {logout(); navigate("/login")}}> diff --git a/frontend/src/pages/SymptomCheckPage.jsx b/frontend/src/pages/SymptomCheckPage.jsx deleted file mode 100644 index 5623b177d..000000000 --- a/frontend/src/pages/SymptomCheckPage.jsx +++ /dev/null @@ -1 +0,0 @@ -/* will do later */ \ No newline at end of file diff --git a/frontend/src/store/userStore.js b/frontend/src/store/userStore.js index 0cd787993..a5ab51d3f 100644 --- a/frontend/src/store/userStore.js +++ b/frontend/src/store/userStore.js @@ -1,14 +1,20 @@ import { create } from "zustand"; +import { persist } from "zustand/middleware" //saves the zustand storen in localstorage -const useUserStore = create((set) => ({ +const useUserStore = create( + persist( + (set) => ({ - user: null, //no user from the beginning - isLoggedIn: false, //not loged in from the start + user: null, //no user from the beginning + isLoggedIn: false, //not loged in from the start - //function to log in - login: (userData) => set({ user: userData, isLoggedIn: true }), + //function to log in + login: (userData) => set({ user: userData, isLoggedIn: true }), - logout: () => set({ user: null, isLoggedIn: false }) -})); + logout: () => set({ user: null, isLoggedIn: false }) + }), + { name: "user-store" } + ) +); export default useUserStore \ No newline at end of file From af24814b0745edfec79e17161989a8002b90f997 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Sun, 8 Mar 2026 14:13:41 +0100 Subject: [PATCH 35/48] added useWindowSize hook --- frontend/src/components/Navbar.jsx | 49 ++++++++++++++++-------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index ac36765cd..45aa540ea 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import styled from 'styled-components'; import { theme } from '../styles/theme'; import { useLocation } from 'react-router-dom'; +import { useWindowSize } from "usehooks-ts"; // ─── Styles ─────────────────────────────────────────────────── const Nav = styled.nav` @@ -218,6 +219,9 @@ const Navbar = ({ variant = 'default', user = null, logoHref = '/' }) => { const toggleMenu = () => setMenuOpen(prev => !prev); const closeMenu = () => setMenuOpen(false); + const { width } = useWindowSize(); + const isMobile = width < 768; + return ( <>
  • Hem
  • -
  • Medicinkoll
  • -
  • Om oss
  • -
  • Hur det fungerar
  • -
  • FAQ
  • - - Kom igång - - )} + {/* NavLinks visas bara på desktop — inuti Nav */} + {!isMobile && variant === 'default' && ( + +
  • Hem
  • +
  • Medicinkoll
  • +
  • Om oss
  • +
  • Hur det fungerar
  • +
  • FAQ
  • +
    + )} + + {!isMobile && variant === 'default' && ( + Kom igång + )} {variant === 'app' && user && ( <> @@ -259,18 +265,17 @@ const Navbar = ({ variant = 'default', user = null, logoHref = '/' }) => { )} - {/* Hamburger on mobile — outside Nav to avoid its stacking context */} - {variant === 'default' && ( - - - - - - )} + {isMobile && variant === 'default' && ( + + + + + + )} + {/* Mobile drawer */} - {variant === 'default' && ( + {isMobile && variant === 'default' && ( setMenuOpen(false)}>Hem setMenuOpen(false)}>Om oss From 6c3e8f1f84901a6de7021fb6ce0cf43810197011 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Sun, 8 Mar 2026 14:21:37 +0100 Subject: [PATCH 36/48] functionality changes in navbar --- frontend/src/components/Navbar.jsx | 2 +- frontend/src/pages/ProfilPage.jsx | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index 45aa540ea..a59cace89 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -234,7 +234,7 @@ const Navbar = ({ variant = 'default', user = null, logoHref = '/' }) => { Stoppa Proppen - {/* NavLinks visas bara på desktop — inuti Nav */} + {/* if its not mobile, show navlinks */} {!isMobile && variant === 'default' && (
  • Hem
  • diff --git a/frontend/src/pages/ProfilPage.jsx b/frontend/src/pages/ProfilPage.jsx index bb9f99331..1743bd38f 100644 --- a/frontend/src/pages/ProfilPage.jsx +++ b/frontend/src/pages/ProfilPage.jsx @@ -5,6 +5,7 @@ import Navbar from '../components/Navbar.jsx'; import { theme } from '../styles/theme'; import { Link } from 'react-router-dom'; import useUserStore from '../store/userStore.js'; +import { useNavigate } from "react-router-dom"; // ─── SCROLL-AREA ────────────────────────────────────────────────────────────── @@ -273,6 +274,9 @@ const DeleteSub = styled.div` // ─── PAGE ───────────────────────────────────────────────────────────────────── const ProfilPage = () => { const user = useUserStore(state => state.user); + const logout = useUserStore(state => state.logout); + const navigate = useNavigate(); + return ( From 197f098f2b2d869c0c5551493c9223a1af4fb935 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Sun, 8 Mar 2026 14:25:35 +0100 Subject: [PATCH 37/48] changed to button for accessibility --- frontend/src/pages/LoginPage.jsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index 603641e82..123f7880d 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -312,9 +312,13 @@ const RegisterLink = styled.p` font-size: 0.8rem; color: rgba(255,255,255,0.6); - a { + button { + background: none; + border: none; + padding: 0; color: ${theme.colors.mint}; text-decoration: none; + font-size: inherit; &:hover { color: #fff; } cursor: pointer; } @@ -490,7 +494,7 @@ const LoginPage = ({ onLogin }) => { - Inget konto? setIsLogin(false)}>Skapa ett här + Inget konto? ) : ( @@ -510,7 +514,7 @@ const LoginPage = ({ onLogin }) => { value={password} onChange={e => setPassword(e.target.value)} required /> - Har du redan ett lösenord? setIsLogin(true)}>Logga in här + Har du redan ett lösenord? {error &&

    {error}

    } Registrera From 38f4dd06ec56efdd36d4cf2b3768f242423a20af Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Sun, 8 Mar 2026 14:27:18 +0100 Subject: [PATCH 38/48] bug fixes --- backend/server.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/server.js b/backend/server.js index 27ed0c02a..111e1d580 100644 --- a/backend/server.js +++ b/backend/server.js @@ -30,8 +30,6 @@ app.use(express.json()); // register becomes /api/register app.use("/api", authRoutes); -app.use - // Start the server mongoose.connect(mongoUrl) .then(() => { From ea966adf668e367f57056c85a1d398b94a8cd3bd Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Sun, 8 Mar 2026 14:44:43 +0100 Subject: [PATCH 39/48] added netlify to read me --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 31466b54c..82a0f134c 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,6 @@ Describe how you approached to problem, and what tools and techniques you used t ## 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. \ No newline at end of file +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. + +https://stoppa-proppen.netlify.app/ \ No newline at end of file From 6667c2e3c164d21d36add74b1b2f7802d7527c26 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Sun, 8 Mar 2026 15:07:28 +0100 Subject: [PATCH 40/48] added user email with zustand in profilpage --- frontend/src/components/Navbar.jsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index a59cace89..576fd2dac 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -3,6 +3,7 @@ import styled from 'styled-components'; import { theme } from '../styles/theme'; import { useLocation } from 'react-router-dom'; import { useWindowSize } from "usehooks-ts"; +import useUserStore from '../store/userStore'; // ─── Styles ─────────────────────────────────────────────────── const Nav = styled.nav` @@ -222,6 +223,9 @@ const Navbar = ({ variant = 'default', user = null, logoHref = '/' }) => { const { width } = useWindowSize(); const isMobile = width < 768; + const isLoggedIn = useUserStore(state => state.isLoggedIn); + const storeUser = useUserStore(state => state.user); + return ( <> {isMobile && variant === 'default' && ( @@ -295,7 +257,7 @@ const Navbar = ({ variant = 'default', user = null, logoHref = '/' }) => { setMenuOpen(false)}>Hem setMenuOpen(false)}>Min Profil setMenuOpen(false)}>Om oss - setMenuOpen(false)}>Hur det fungerar + setMenuOpen(false)}>Hur det fungerar setMenuOpen(false)}>FAQ setMenuOpen(false)}>Medicinkoll {!isLoggedIn && ( diff --git a/frontend/src/pages/FaqPage.jsx b/frontend/src/pages/FaqPage.jsx index 1ab4af48a..2b0a347f4 100644 --- a/frontend/src/pages/FaqPage.jsx +++ b/frontend/src/pages/FaqPage.jsx @@ -312,8 +312,6 @@ const FaqItem = ({ question, answer, delay }) => { const FaqPage = () => ( - {/* FAQ */} - {/* Stoppa Proppen */} © 2025 diff --git a/frontend/src/pages/Hurdetfungerarpage.jsx b/frontend/src/pages/Hurdetfungerarpage.jsx index 697c49673..9519526e0 100644 --- a/frontend/src/pages/Hurdetfungerarpage.jsx +++ b/frontend/src/pages/Hurdetfungerarpage.jsx @@ -289,8 +289,6 @@ const CtaBtn = styled.a` const HurDetFungerarPage = () => ( - {/* Hur det fungerar - Stoppa Proppen */} © 2025 diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx index e59cb7e5d..44da02416 100644 --- a/frontend/src/pages/LandingPage.jsx +++ b/frontend/src/pages/LandingPage.jsx @@ -31,33 +31,6 @@ const Hero = styled.div` } `; -// Small pill badge at the top, e.g. "Beta — Now available" -const Badge = styled.div` - display: inline-flex; - align-items: center; - gap: 0.5rem; - background: rgba(0,0,0,0.3); - border: 1px solid rgba(255,255,255,0.25); - backdrop-filter: blur(10px); - border-radius: 100px; - padding: 0.35rem 1rem; - font-size: 0.8rem; - font-weight: 500; - color: rgba(255,255,255,0.9); - letter-spacing: 0.03em; - animation: ${fadeUp} 0.8s ease both; -`; - -// The pulsing green dot in the badge -const BadgeDot = styled.span` - width: 6px; - height: 6px; - border-radius: 50%; - background: ${theme.colors.mint}; - box-shadow: 0 0 8px ${theme.colors.mint}; - animation: ${pulse} 2s infinite; -`; - // Large heading // clamp() makes the size responsive without media queries: // min 2.4rem, grows with screen width, max 5.2rem @@ -262,8 +235,7 @@ const FloatMeta = styled.div` const LandingPage = () => ( -{/* Stoppa Proppen © - */} 2025 + 2025 diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index 123f7880d..396456e5f 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import styled from 'styled-components'; +import styled, { keyframes } from 'styled-components'; import { Frame, fadeUp, fadeLeft } from '../components/Layout.jsx'; import { theme } from '../styles/theme'; import useUserStore from '../store/userStore.js'; @@ -266,9 +266,28 @@ const SubmitBtn = styled.button` cursor: pointer; transition: background 0.2s, transform 0.15s; box-shadow: 0 8px 24px rgba(0,0,0,0.2); + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; - &:hover { background: #fff; transform: translateY(-1px); } - &:active { transform: scale(0.98); } + &:hover:not(:disabled) { background: #fff; transform: translateY(-1px); } + &:active:not(:disabled) { transform: scale(0.98); } + &:disabled { opacity: 0.7; cursor: not-allowed; } +`; + +const spin = keyframes` + to { transform: rotate(360deg); } +`; + +const Spinner = styled.span` + display: inline-block; + width: 1rem; + height: 1rem; + border: 2px solid ${theme.colors.tealMid}; + border-top-color: transparent; + border-radius: 50%; + animation: ${spin} 0.7s linear infinite; `; const Divider = styled.div` @@ -344,35 +363,28 @@ const CornerLabel = styled.span` } `; -/* const LoginFrame = styled(Frame)` - @media (min-width: 768px) { - margin-top: 1rem; - } -`; */ // ─── Page ───────────────────────────────────────────────────── -const LoginPage = ({ onLogin }) => { +const LoginPage = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [isLogin, setIsLogin] = useState(true); const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); const navigate = useNavigate(); const login = useUserStore(state => state.login); - const isLoggedIn = useUserStore(state => state.isLoggedIn); - /* console.log('isLoggedIn:', isLoggedIn) */ - -/* const [confirmPassword, setconfirmPassword] = useState('') - */ const handleSubmit = async (e) => { e.preventDefault(); + setLoading(true); + setError(''); const response = await fetch(`${BASE_URL}/api/login`, { - - method: "POST", + + method: "POST", headers: { - "Content-Type": "application/json" //we sending json + "Content-Type": "application/json" //we sending json }, body: JSON.stringify({ email: email, @@ -387,11 +399,14 @@ const LoginPage = ({ onLogin }) => { navigate('/profil'); } else { setError(data.message) + setLoading(false); } }; const handleRegister = async (e) => { e.preventDefault(); + setLoading(true); + setError(''); const response = await fetch(`${BASE_URL}/api/register`, { @@ -408,9 +423,11 @@ const LoginPage = ({ onLogin }) => { const data = await response.json() if (response.ok){ + setLoading(false); setIsLogin(true); } else { setError(data.message) + setLoading(false); } } @@ -424,8 +441,6 @@ const LoginPage = ({ onLogin }) => { stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/> - {/* Inloggning - Stoppa Proppen */} Säker anslutning v0.1 @@ -482,7 +497,9 @@ const LoginPage = ({ onLogin }) => { Glömt lösenordet? {error &&

    {error}

    } - Logga in + + {loading ? <>Laddar... : 'Logga in'} + @@ -517,7 +534,9 @@ const LoginPage = ({ onLogin }) => { Har du redan ett lösenord? {error &&

    {error}

    } - Registrera + + {loading ? <>Laddar... : 'Registrera'} + )} diff --git a/frontend/src/pages/MedicinPage.jsx b/frontend/src/pages/MedicinPage.jsx index feb007202..a280664bf 100644 --- a/frontend/src/pages/MedicinPage.jsx +++ b/frontend/src/pages/MedicinPage.jsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import styled, { keyframes, css } from 'styled-components'; import { toast, ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; -import { Frame, Card, CornerLabel, fadeUp } from '../components/Layout.jsx'; +import { Frame, Card, fadeUp } from '../components/Layout.jsx'; import Navbar from '../components/Navbar.jsx'; import { theme } from '../styles/theme'; import BottomNav from "../components/BottomNav.jsx" diff --git a/frontend/src/pages/OmossPage.jsx b/frontend/src/pages/OmossPage.jsx index 9233c7621..321cf0e3c 100644 --- a/frontend/src/pages/OmossPage.jsx +++ b/frontend/src/pages/OmossPage.jsx @@ -212,42 +212,6 @@ const TeamRole = styled.div` color: rgba(255,255,255,0.45); `; -// ─── STATS ROW ──────────────────────────────────────────────── -const StatsRow = styled.div` - display: grid; - grid-template-columns: 1fr 1fr; - gap: 0.8rem; - margin-bottom: 2.5rem; - - @media (min-width: 600px) { - grid-template-columns: repeat(4, 1fr); - } -`; - -const StatBox = styled.div` - background: rgba(0,0,0,0.2); - border: 1px solid rgba(255,255,255,0.1); - border-radius: 14px; - padding: 1.2rem 1rem; - text-align: center; - animation: ${fadeUp} 0.6s ${({ $delay }) => $delay || '0s'} ease both; -`; - -const StatNum = styled.div` - font-family: ${theme.fonts.serif}; - font-size: 1.8rem; - font-weight: 300; - color: ${theme.colors.mint}; - line-height: 1; - margin-bottom: 0.3rem; -`; - -const StatLabel = styled.div` - font-size: 0.68rem; - color: rgba(255,255,255,0.45); - letter-spacing: 0.04em; -`; - // ─── CTA CARD ───────────────────────────────────────────────── const CtaCard = styled.div` background: rgba(125,255,212,0.07); @@ -298,7 +262,6 @@ const CtaBtn = styled.a` const OmOssPage = () => ( - {/* Om oss */} © 2025 @@ -320,27 +283,6 @@ const OmOssPage = () => ( - {/* Statistik */} - {/* Appen i siffror - - - 1 247 - Aktiva användare - - - 4.8 - Snittbetyg - - - 98% - Nöjda användare - - - 0 kr - Alltid gratis - - */} - {/* What we offer */} @@ -397,8 +339,7 @@ const OmOssPage = () => ( komplement till den svenska vården — aldrig en ersättning. - {/* All medical information is reviewed and based on the National Board of Health's - and 1177's guidelines. */} Vi är alltid transparenta med vad appen kan + Vi är alltid transparenta med vad appen kan och inte kan — den ställer inga diagnoser. diff --git a/frontend/src/pages/ProfilPage.jsx b/frontend/src/pages/ProfilPage.jsx index 1743bd38f..d1122a9f0 100644 --- a/frontend/src/pages/ProfilPage.jsx +++ b/frontend/src/pages/ProfilPage.jsx @@ -281,8 +281,7 @@ const ProfilPage = () => { Profil -{/* Stoppa Proppen - */} © 2025 + © 2025 @@ -313,7 +312,7 @@ const ProfilPage = () => { Dina dagliga mediciner - + {
    - {}}> {/* //will do later */} + {}}> @@ -375,7 +374,7 @@ const ProfilPage = () => {
    Farlig zon - {}}> {/* will do later */} + {}}> From 748736b3572c36a9adb7c0ed59a7c8f33108000c Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 24 Mar 2026 13:34:15 +0100 Subject: [PATCH 46/48] changed all the href to links with react-router-dom --- frontend/src/components/BottomNav.jsx | 9 ++--- frontend/src/components/Navbar.jsx | 43 ++++++++++++----------- frontend/src/pages/Hurdetfungerarpage.jsx | 5 +-- frontend/src/pages/LandingPage.jsx | 7 ++-- frontend/src/pages/LoginPage.jsx | 14 ++++---- frontend/src/pages/OmossPage.jsx | 5 +-- 6 files changed, 44 insertions(+), 39 deletions(-) diff --git a/frontend/src/components/BottomNav.jsx b/frontend/src/components/BottomNav.jsx index 6ef8242a4..25568751c 100644 --- a/frontend/src/components/BottomNav.jsx +++ b/frontend/src/components/BottomNav.jsx @@ -10,6 +10,7 @@ import React from 'react'; import styled from 'styled-components'; +import { Link } from 'react-router-dom'; import { theme } from '../styles/theme'; // ─── Wrapper ────────────────────────────────────────────────────────────────── @@ -41,7 +42,7 @@ const Bar = styled.nav` // ─── Individual nav button ──────────────────────────────────────────────────── // $active controls whether the button is highlighted (light green) or not (grey). -const NavItem = styled.a` +const NavItem = styled(Link)` flex: 1; display: flex; flex-direction: column; @@ -80,7 +81,7 @@ const BottomNav = ({ active = '' }) => ( {/* Hem */} - + ( {/* Medicinlåda */} - + ( {/* Symtomkoll */} - + { return ( <>
  • Hem
  • -
  • Medicinkoll
  • -
  • Om oss
  • -
  • Hur det fungerar
  • -
  • FAQ
  • +
  • Hem
  • +
  • Medicinkoll
  • +
  • Om oss
  • +
  • Hur det fungerar
  • +
  • FAQ
  • )} {!isMobile && variant === 'default' && !isLoggedIn && ( - Kom igång + Kom igång )} {!isMobile && variant === 'default' && isLoggedIn && ( - + 👤 {storeUser?.email} @@ -254,14 +255,14 @@ const Navbar = ({ variant = 'default', logoHref = '/' }) => { {/* Mobile drawer */} {isMobile && variant === 'default' && ( - setMenuOpen(false)}>Hem - setMenuOpen(false)}>Min Profil - setMenuOpen(false)}>Om oss - setMenuOpen(false)}>Hur det fungerar - setMenuOpen(false)}>FAQ - setMenuOpen(false)}>Medicinkoll - {!isLoggedIn && ( - setMenuOpen(false)}>Kom igång gratis + setMenuOpen(false)}>Hem + setMenuOpen(false)}>Min Profil + setMenuOpen(false)}>Om oss + setMenuOpen(false)}>Hur det fungerar + setMenuOpen(false)}>FAQ + setMenuOpen(false)}>Medicinkoll + {!isLoggedIn && ( + setMenuOpen(false)}>Kom igång gratis )} )} diff --git a/frontend/src/pages/Hurdetfungerarpage.jsx b/frontend/src/pages/Hurdetfungerarpage.jsx index 9519526e0..5dae9b339 100644 --- a/frontend/src/pages/Hurdetfungerarpage.jsx +++ b/frontend/src/pages/Hurdetfungerarpage.jsx @@ -1,5 +1,6 @@ import React from 'react'; import styled from 'styled-components'; +import { Link } from 'react-router-dom'; import { Frame, Card, CornerLabel, fadeUp } from '../components/Layout.jsx'; import Navbar from '../components/Navbar.jsx'; import { theme } from '../styles/theme'; @@ -265,7 +266,7 @@ const CtaText = styled.p` line-height: 1.7; `; -const CtaBtn = styled.a` +const CtaBtn = styled(Link)` display: inline-flex; align-items: center; gap: 0.5rem; @@ -435,7 +436,7 @@ const HurDetFungerarPage = () => ( Skapa ett gratis konto och kom igång direkt. Det tar mindre än en minut. - Kom igång gratis → + Kom igång gratis →
    diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx index 44da02416..043694df8 100644 --- a/frontend/src/pages/LandingPage.jsx +++ b/frontend/src/pages/LandingPage.jsx @@ -1,6 +1,7 @@ import React from 'react'; import styled, { keyframes } from 'styled-components'; -import { Frame, Card, CornerLabel, fadeUp, fadeLeft, pulse } from '../components/Layout.jsx'; +import { Link } from 'react-router-dom'; +import { Frame, Card, CornerLabel, fadeUp, fadeLeft } from '../components/Layout.jsx'; import Navbar from '../components/Navbar.jsx'; import { theme } from '../styles/theme'; @@ -79,7 +80,7 @@ const HeroActions = styled.div` `; // Subtle ghost link, e.g. "Read more →" -const BtnGhostLink = styled.a` +const BtnGhostLink = styled(Link)` font-size: 0.85rem; font-weight: 400; color: rgba(255,255,255,0.88); @@ -251,7 +252,7 @@ const LandingPage = () => ( - + Läs mer diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index 396456e5f..117e4b38c 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -3,7 +3,7 @@ import styled, { keyframes } from 'styled-components'; import { Frame, fadeUp, fadeLeft } from '../components/Layout.jsx'; import { theme } from '../styles/theme'; import useUserStore from '../store/userStore.js'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, Link } from 'react-router-dom'; const BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8081'; //if the production doesnt work, use an env variabel @@ -70,7 +70,7 @@ const LeftInner = styled.div` z-index: 5; `; -const LogoWrap = styled.a` +const LogoWrap = styled(Link)` display: flex; align-items: center; gap: 0.6rem; @@ -155,7 +155,7 @@ const RightPanel = styled.div` } `; -const CloseBtn = styled.a` +const CloseBtn = styled(Link)` position: absolute; top: 1.2rem; right: 1.2rem; @@ -246,7 +246,7 @@ const ForgotRow = styled.div` margin-bottom: 1rem; `; -const ForgotLink = styled.a` +const ForgotLink = styled(Link)` font-size: 0.75rem; color: rgba(255,255,255,0.6); text-decoration: none; @@ -435,7 +435,7 @@ const LoginPage = () => { - + @@ -446,7 +446,7 @@ const LoginPage = () => { {/* Left — desktop only */} - + @@ -494,7 +494,7 @@ const LoginPage = () => { value={password} onChange={e => setPassword(e.target.value)} required /> - Glömt lösenordet? + Glömt lösenordet? {error &&

    {error}

    } diff --git a/frontend/src/pages/OmossPage.jsx b/frontend/src/pages/OmossPage.jsx index 321cf0e3c..1afb11786 100644 --- a/frontend/src/pages/OmossPage.jsx +++ b/frontend/src/pages/OmossPage.jsx @@ -1,5 +1,6 @@ import React from 'react'; import styled from 'styled-components'; +import { Link } from 'react-router-dom'; import { Frame, Card, CornerLabel, fadeUp } from '../components/Layout.jsx'; import Navbar from '../components/Navbar.jsx'; import { theme } from '../styles/theme'; @@ -241,7 +242,7 @@ const CtaText = styled.p` line-height: 1.7; `; -const CtaBtn = styled.a` +const CtaBtn = styled(Link)` display: inline-flex; align-items: center; gap: 0.5rem; @@ -390,7 +391,7 @@ const OmOssPage = () => ( Skapa ett gratis konto och få tillgång till symtomkoll, medicinpåminnelser och vårt community idag. - Kom igång gratis → + Kom igång gratis → From 8612aa762384325520e5c3cc634645c73d960902 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 24 Mar 2026 13:55:00 +0100 Subject: [PATCH 47/48] adding jwt token in auth --- backend/models/User.js | 16 ++++++++++------ backend/routes/auth.js | 26 +++++++++++++++++++------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/backend/models/User.js b/backend/models/User.js index d3b4b12db..2ee12dbc2 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -1,19 +1,23 @@ import mongoose from "mongoose"; +import crypto from "crypto"; const userSchema = new mongoose.Schema({ - email: { + email: { type: String, required: true, - unique: true, //noone gets the same email - lowercase: true, // saves with small letters + unique: true, + lowercase: true, }, - password:{ + password: { type: String, required: true + }, + accessToken: { + type: String, + default: () => crypto.randomUUID() } -}, { timestamps: true} //automatic adds createdAT and updatedAt -); +}, { timestamps: true }); //creates a user modell based on schema const User = mongoose.model("User", userSchema) diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 24ec6a76e..c0945477c 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -1,10 +1,23 @@ import express from "express" -import User from "../models/User.js" // we need this to save and retrieve users in MongoDB +7import User from "../models/User.js" // we need this to save and retrieve users in MongoDB import bcrypt from "bcrypt"; -// mini server - only handles its own routes, instead of having all routes in server.js - we then connect it with the router in the main app const router = express.Router(); +// Middleware to protect routes — checks the Authorization header for a valid token +export const authenticateUser = async (req, res, next) => { + const token = req.headers.authorization?.replace("Bearer ", ""); + if (!token) { + return res.status(401).json({ message: "Ingen token angiven" }); + } + const user = await User.findOne({ accessToken: token }); + if (!user) { + return res.status(401).json({ message: "Ogiltig token" }); + } + req.user = user; + next(); +}; + // async - means the route contains code that takes time (talking to the database) // without async/await we cannot wait for a response from MongoDB router.post("/register", async (req, res) => { @@ -66,12 +79,11 @@ router.post("/login", async (req, res) => { if(!isMatch){ return res.status(401).json({ message: "Fel email eller lösenord"}) } - // saves the user's id in the session - req.session.userId = user._id; - res.status(200).json({ - message: 'Inloggad!', + res.status(200).json({ + message: 'Inloggad!', email: user.email, - id: user._id + id: user._id, + accessToken: user.accessToken }); }) From cf43131994d4e0b275eda08735902f6227f1940d Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 24 Mar 2026 17:35:53 +0100 Subject: [PATCH 48/48] removed the express-session, implemented access token --- backend/routes/auth.js | 4 ++-- backend/server.js | 11 ----------- frontend/src/store/userStore.js | 16 ++++++++++------ 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/backend/routes/auth.js b/backend/routes/auth.js index c0945477c..c0ad2927f 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -1,5 +1,5 @@ -import express from "express" -7import User from "../models/User.js" // we need this to save and retrieve users in MongoDB +import express from "express"; +import User from "../models/User.js"; import bcrypt from "bcrypt"; const router = express.Router(); diff --git a/backend/server.js b/backend/server.js index bbcb16aa4..50d4b3380 100644 --- a/backend/server.js +++ b/backend/server.js @@ -3,7 +3,6 @@ import cors from "cors"; import mongoose from "mongoose"; import dotenv from 'dotenv'; import authRoutes from "./routes/auth"; -import session from 'express-session'; dotenv.config(); @@ -16,16 +15,6 @@ const app = express(); app.use(cors()); app.use(express.json()); -// Session management -// secret: a secret key that encrypts the session -// resave: do not save the session if nothing has changed -// saveUninitialized: do not save empty sessions - app.use(session({ - secret: process.env.SESSION_SECRET || 'secret-key', - resave: false, - saveUninitialized: false, -})); - // tells express that all routes in auth.js should start with /api // register becomes /api/register app.use("/api", authRoutes); diff --git a/frontend/src/store/userStore.js b/frontend/src/store/userStore.js index a5ab51d3f..21beda5cd 100644 --- a/frontend/src/store/userStore.js +++ b/frontend/src/store/userStore.js @@ -5,13 +5,17 @@ const useUserStore = create( persist( (set) => ({ - user: null, //no user from the beginning - isLoggedIn: false, //not loged in from the start - - //function to log in - login: (userData) => set({ user: userData, isLoggedIn: true }), + user: null, + accessToken: null, + isLoggedIn: false, - logout: () => set({ user: null, isLoggedIn: false }) + login: (userData) => set({ + user: { email: userData.email, id: userData.id }, + accessToken: userData.accessToken, + isLoggedIn: true + }), + + logout: () => set({ user: null, accessToken: null, isLoggedIn: false }) }), { name: "user-store" } )