diff --git a/.gitignore b/.gitignore index 3d70248ba2..22a1c967e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,31 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +anteckningar node_modules -.DS_Store +dist +dist-ssr +*.local +package-lock.json .env .env.local .env.development.local .env.test.local .env.production.local -build - -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -package-lock.json \ No newline at end of file +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/PLANNING.md b/PLANNING.md new file mode 100644 index 0000000000..18fbf2d082 --- /dev/null +++ b/PLANNING.md @@ -0,0 +1,203 @@ +> 📚 **Navigation:** [README](README.md) | [TODO](TODO.md) +# 📐 Planning Document +> **Open in preview** +`Shift + Command + V on Mac` +`Shift + Control + V on Windows` + +> 💡 **Tip:** Convert planning items to tasks in TODO.md as you finalize decisions + +## 📋 Table of Contents +- [Project Overview](#1) +- [Goals & Requirements](#2) +- [Design & Wireframes](#3) +- [Technical Planning](#4) +- [API Planning](#5) +- [State Management](#6) +- [Accessibility Plan](#7) +- [Timeline & Milestones](#8) +- [Risks & Challenges](#9) +- [Resources & References](#10) +- [Notes & Ideas](#11) +- [Next Steps](#12) + + +## 📌 Project Overview +**Project Name:** Womenation Membership +**Timeline:** 17/3 - 24/4 + +### Project Description + + + +## 🎯 Goals & Requirements + +### Core Requirements (Must-Have) +- Requirement 1 +- Requirement 2 +- Requirement 3 + +### Stretch Goals (Nice-to-Have) +- Feature 1 +- Feature 2 + +### Learning Objectives +What do you want to learn/practice in this project? +- Technology, concept, or skill to practice + + +## 🎹 Design & Wireframes + +In graphical design file. + +### Wireframes +[Figmadesign WIP](https://www.figma.com/design/TZNZ0U28L0Z7IOS1g4oUOR/womenation--design?node-id=0-1&p=f&t=ggM79gvBe0FchJft-0) + + +## đŸ—ïž Technical Planning +### Tech Stack Decision +| Technology | Purpose | Why chosen? | +|------------|---------|-------------| +| Framework/Library | Frontend/Backend | Reasoning | +| Tool/Service | Specific use case | Reasoning | +| React | Frontend | | +| MongoDB | | | +| | | | + +### Components Breakdown +List all planned components: + +**Reusable Components:** +- `ComponentName` - Purpose and functionality +- `ComponentName` - Purpose and functionality + +**Page Components:** +- `PageName` - What the page displays/does +- `PageName` - What the page displays/does + +**Layout Components:** +- `LayoutElement` - Purpose and location +- `LayoutElement` - Purpose and location + + +## 🔌 API Planning +### Endpoints to Use +| Endpoint | Method | Purpose | Response | +|----------|--------|---------|----------| +| `/api/resource` | GET | Description of what it fetches | Data type returned | +| `/api/resource/:id` | GET | Description of specific fetch | Data type returned | + +### Data Structure +```json +{ + "id": 1, + "property": "value", + "nested": { + "key": "value" + } +} +``` + +### Error Handling Strategy +- Network errors: Show error message +- 404: Display "Not found" page +- 500: Display "Server error" message + + +## 📊 State Management +### State Planning +What state do you need to manage? + +**Global State:** +- User authentication status +- Theme preference + +**Component State:** +- Loading states +- Error states +- Local state item and scope + + +### Data Flow +Describe how data moves through your app: +1. User action triggers... +2. Component updates state... +3. API call is made... +4. Response updates UI... + + +## ♿ Accessibility Plan (example) +### Checklist +- [ ] Semantic HTML elements +- [ ] Alt text for all images +- [ ] Keyboard navigation +- [ ] ARIA labels where needed +- [ ] Color contrast ratio (WCAG AA) +- [ ] Focus indicators +- [ ] Screen reader testing + + +## 📅 Timeline & Milestones +### Week 1: Planning & Setup +- [ ] Project planning +- [ ] Setup repository +- [ ] Create basic structure + +### Week 2: Core Features +- [ ] Implement feature A +- [ ] Implement feature B + +### Week 3: Styling & Polish +- [ ] Responsive design +- [ ] Cross-browser testing + +### Week 4: Final Touches +- [ ] Bug fixes +- [ ] Documentation +- [ ] Deployment + + +## 🚹 Risks & Challenges +### Potential Blockers +| Risk | Impact | Mitigation | +|------|--------|------------| +| API downtime | High | Implement error handling, mock data | +| Time constraint | Medium | Prioritize core features first | + +### Questions to Resolve +- How should pagination work? +- What's the fallback for missing images? +- How to handle expired tokens? + + + +## 📚 Resources & References +### Tutorials +- [Tutorial name](link) + +### Documentation +- [Library docs](link) + +### Inspiration +- [Similar project](link) + +### Tools +- [Design tool](link) +- [Icon library](link) + + + +## 📝 Notes & Ideas +Random thoughts, ideas, or things to remember: +- +- + + +## 🔄 Next Steps +Once planning is complete: +1. ✅ Review and finalize all sections above +2. 📋 Create actionable tasks in [TODO](TODO.md) based on this plan +3. 🚀 Begin implementation following the timeline +4. 📝 Update [README](README.md) as features are completed + +**Last Updated:** [Date] +**Next Review:** [Date] \ No newline at end of file diff --git a/README.md b/README.md index 31466b54c2..0aa3464b66 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,300 @@ -# Final Project +> 📚 **Navigation:** [PLANNING](PLANNING.md) | [TODO](TODO.md) -Replace this readme with your own information about your project. +# Womenation Membership +> **Open in preview** +`Shift + Command + V on Mac` +`Shift + Control + V on Windows` -Start by briefly describing the assignment in a sentence or two. Keep it short and to the point. +Womenation Membership is a fullstack membership platform where users can sign up, log in, and access a protected member portal with personal information, membership content, events, and offers. -## The problem +## 📋 Table of Contents +- [Live Demo](#1) +- [Screenshots](#2) +- [About Project](#3) +- [Tech Stack](#4) +- [Features](#5) +- [Project Objectives](#6) +- [Accessibility](#7) +- [Architecture](#8) + - [Folder Structure](#s1) + - [Pages & Routes](#s2) + - [Data Flow Overview](#s3) + - [API Usage](#s4) +- [Run Locally](#9) +- [Roadmap](#10) +- [Author](#11) +- [License](#12) -Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next? + +## 🚀 Live Demo +- [Frontend](https://project-final-medlemsportal.netlify.app/) +- [Backend API](https://womenation-membership.onrender.com) -## View it live + +## đŸ“· Screenshots +*To be edited* -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 +Add screenshots that show: +- Public membership page (`/om-medlemskap`) +- Login / signup flow +- Member portal pages (`/konto/*`) + + +## 🔎 About Project +The project was built to create a complete membership experience from public onboarding to protected account pages. + +The platform includes: +- Public pages for membership information, login and signup +- Authentication with protected routes +- A member portal with personal data and content pages +- Backend integration with MongoDB and Mailchimp sync on signup + + +## 📩 Tech Stack +- **Frontend:** React 19 + Vite +- **Routing:** React Router +- **Styling:** styled-components +- **State Management:** Zustand +- **Backend:** Node.js + Express +- **Database:** MongoDB + Mongoose +- **Authentication:** Email/password login with bcrypt + bearer accessToken validation in `authenticateUser` middleware +- **External Service:** Mailchimp Marketing API +- **Linting/Tooling:** ESLint, Nodemon, Babel Node + + +## 🧠 Features +- User signup flow with backend validation and duplicate-email handling +- Login flow with bcrypt credential verification and access token response +- Protected nested routes under `/konto` using `ProtectedRoute` +- Authenticated dashboard fetch (`/dashboard/:id`) via bearer token +- Zustand stores for user state, form state, and content state +- Member portal pages: Mina Sidor, Medlemskap, Events, Erbjudanden +- Public pages: Om Medlemskap, Logga In, Bli Medlem +- Responsive navigation with two hamburger menus (public + portal) +- Auto-close behavior on hamburger menu links +- Theming system with centralized color tokens and shared UI variants +- Signup integration with Mailchimp (contact creation + merge fields) +- API endpoint discovery on root route (`GET /` with endpoint list) + + +## 🎯 Project Objectives +Main objectives for the project: +- Build and connect a real frontend + backend flow +- Practice auth, protected routing, and API integration +- Work with reusable UI components and shared theme system +- Build responsive pages for both public and member sections + +### Required conditions +- React frontend + Node.js/Express backend + MongoDB +- Authentication +- React Router navigation +- Global state management +- At least two external libraries beyond core stack +- A React hook not covered in curriculum +- Cross-browser support: Chrome, Firefox, Safari +- Fully responsive: 320px–1600px +- 100% Lighthouse Accessibility score +- Clean Code practices +- Mobile-first design with cohesive colour scheme and consistent typography +- Submission of: full-project GitHub PR, deployed backend & frontend + +### Stretch Goals (optional) +- Go beyond Grade G with clear technical depth and project ambition +- Integrate meaningful tools/APIs/frameworks beyond course content +- Show strong frontend/backend decision-making and collaboration +- Polish UX with micro-interactions, loading states, and accessibility extras +- Provide strong docs: README, API docs, and visual walkthroughs +- Demonstrate iteration and thoughtful problem-solving in code and design + + + +## ♿ Accessibility +Accessibility work includes: +- Semantic HTML in reusable components +- Visible focus styles in form controls +- Keyboard-accessible navigation and buttons +- Color tokens centralized in theme for contrast tuning + +Planned improvements: +- Additional ARIA labels for complex interactions +- Broader screen reader flow testing +- Lighthouse 100% +- Real-time validation feedback in signup and login forms + + +## đŸ—ïž Architecture +The app is split into clear frontend and backend modules, with shared responsibilities separated by route, component type, and state domain. + + +### 🛠 Folder Structure +```text +womenation-membership/ +├── backend/ +│ ├── config/ +│ ├── middleware/ +│ ├── models/ +│ ├── routes/ +│ ├── seedingDatabase/ +│ └── server.js +├── frontend/ +│ ├── index.html +│ ├── public/ +│ │ └── media/ +│ │ ├── icons/ +│ │ ├── images/ +│ │ └── logo/ +│ ├── src/ +│ │ ├── App.jsx +│ │ ├── components/ +│ │ │ ├── navigation/ +│ │ │ ├── pages/ +│ │ │ ├── typography/ +│ │ │ └── ui/ +│ │ ├── constants/ +│ │ ├── data/ +│ │ ├── layouts/ +│ │ ├── main.jsx +│ │ ├── pages/ +│ │ │ └── portal-pages/ +│ │ ├── routes/ +│ │ ├── store/ +│ │ ├── styles/ +│ │ └── themes/ +│ └── vite.config.js +├── PLANNING.md +├── README.md +└── TODO.md +``` + + +### 🧭 Pages & Routes +| Route | Description | +|-------|-------------| +| `/` | Redirects to `/om-medlemskap` | +| `/om-medlemskap` | Public membership information | +| `/logga-in` | Login page | +| `/bli-medlem` | Signup page | +| `/konto` | Protected account entry | +| `/konto/mina-sidor` | User information page | +| `/konto/medlemskap` | Membership content page | +| `/konto/events` | Member events page | +| `/konto/erbjudanden` | Member offers page | + + +### 🔄 Data Flow Overview +#### Signup Flow +1. User submits signup form in frontend. +2. Frontend posts to `POST /users/signup`. +3. Backend validates and stores user in MongoDB. +4. Backend attempts Mailchimp sync. +5. Frontend updates user state and navigates to account flow. + +#### Login + Protected Flow +1. User submits login form. +2. Frontend posts to `POST /users/login`. +3. Backend validates password and returns `accessToken`. +4. Frontend stores user in Zustand. +5. Protected routes allow access via `ProtectedRoute`. +6. Frontend fetches dashboard data with bearer token. + + +### 🔑 API Usage +Base URL (production): +- `https://womenation-membership.onrender.com` + +Main endpoints: +- `GET /` - API welcome + endpoint listing +- `GET /users` - List users +- `POST /users/signup` - Create user +- `POST /users/login` - Authenticate user +- `GET /dashboard/:id` - Authenticated user dashboard data + +Auth: +- Header format: `Authorization: Bearer ` + +Error handling: +- `400` for invalid request / failed signup validation flow +- `409` for duplicate email on signup +- `401` for invalid login or missing/invalid auth token +- `404` for user not found (`/dashboard/:id`) +- `500` for internal server errors +- `502` for Mailchimp integration failure + + +## ▶ Run Locally +### 1. Clone repository +```bash +git clone https://github.com/mikaelasturk/womenation-membership +cd womenation-membership +``` + +### 2. Start backend +```bash +cd backend +npm install +npm run dev +``` + +Optional environment variables for backend integrations: +- `MONGO_URL` +- `MAILCHIMP_API_KEY` +- `MAILCHIMP_SERVER_PREFIX` +- `MAILCHIMP_LIST_ID` + +### 3. Start frontend +```bash +cd ../frontend +npm install +npm run dev +``` + + +## 🚗 Roadmap +Describe the planning process, implementation phase, and future improvements. + +### 📂 Project Documentation +This project uses a structured workflow with interconnected documentation: + +- **[📄 PLANNING.md](PLANNING.md)** - Detailed planning document with design decisions, tech stack, wireframes, and timelines +- **[✅ TODO.md](TODO.md)** - Active task tracking with priorities, status updates, and development progress + +**Workflow:** +1. **Plan** → Use PLANNING.md to define goals, design, and architecture +2. **Break Down** → Convert plan into actionable tasks in TODO.md +3. **Execute** → Work through TODO items, updating status as you go +4. **Document** → Update this README with completed features and learnings + +### Planning +Brief description of initial planning and design decisions. +Optional link to a planning document. +[📄 PLANNING.md](PLANNING.md) + +### Execution +Describe how the project was implemented, including architectural decisions, state management, and challenges encountered. + +### Retrospect +Summarize reflections, lessons learned, technical insights, and ideas for improvement. + + +## đŸ§‘â€đŸ’» Authors + +### Carolina Oldertz +- [GitHub](https://www.github.com/carro-barro) +- [LinkedIn](https://www.linkedin.com/in/carolina-oldertz-a875601b8/) + +### Mikaela Sturk +- [GitHub](https://www.github.com/mikaelasturk) +- [LinkedIn](https://www.linkedin.com/in/mikaelasturk) + + +## 📄 License +This project was created as part of a Technigo course assignment and not to be shared outside of school. +For educational purposes only. + + +--- + +
+
+ Made with ❀ by Carolina Oldertz & Mikaela Sturk +
\ No newline at end of file diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000000..92b0321ca5 --- /dev/null +++ b/TODO.md @@ -0,0 +1,78 @@ +> 📚 **Navigation:** [README](README.md) | [PLANNING](PLANNING.md) + +# TODO +> **Open in preview** +`Shift + Command + V on Mac` +`Shift + Control + V on Windows` + +> 💡 **Workflow:** Plan → Break Down → Execute → Document + +## 📋 Table of Contents +- [High Priority](#1) +- [Medium Priority](#2) +- [Low Priority](#3) +- [In Progress](#4) +- [Blocked](#5) +- [Completed](#6) +- [By Category](#7) +- [Notes](#8) + + +## 🔮 High Priority +- [ ] Feature or critical bug description (Due: YYYY-MM-DD) +- [ ] Feature or critical bug description + + +## 🟡 Medium Priority +- [ ] Enhancement or improvement description +- [ ] Enhancement or improvement description + + +## 🟱 Low Priority +- [ ] Nice-to-have feature or refactor description +- [ ] Nice-to-have feature or refactor description + + +## 🚧 In Progress +- [ ] Currently working on this feature/task +- [ ] Currently working on this feature/task + + +## đŸš« Blocked +- [ ] Blocked task with reason [#issue-number](link-to-issue) +- [ ] Waiting for external dependency or decision + + +## ✅ Completed +- [x] Completed feature or task description +- [x] Completed feature or task description + + +## 📂 By Category +### Frontend +- [ ] Component or UI-related task +- [ ] Component or UI-related task + +### Backend +- [ ] API, database, or server-related task +- [ ] API, database, or server-related task + +### Testing +- [ ] Unit, integration, or E2E test task +- [ ] Unit, integration, or E2E test task + +### Documentation +- [ ] README, comments, or docs update +- [ ] README, comments, or docs update + + + +## 📝 Notes +- Important reminder or consideration for the project +- Browser compatibility note or technical constraint +- Design decision or architectural choice to remember + +### 🔄 Don't Forget +- Update [PLANNING](PLANNING.md) when making architectural changes +- Update [README](README.md) when completing major features +- Move completed items to ✅ Completed section regularly \ No newline at end of file diff --git a/backend/config/googleOauthConfig.js b/backend/config/googleOauthConfig.js new file mode 100644 index 0000000000..190820b259 --- /dev/null +++ b/backend/config/googleOauthConfig.js @@ -0,0 +1,7 @@ +// [ ] todo: implement Google OAuth configuration and add secrets in env file + +// cloudinary.config({ +// cloud_name: process.env.CLOUDINARY_NAME, +// api_key: process.env.CLOUDINARY_KEY, +// api_secret: process.env.CLOUDINARY_SECRET +// }) \ No newline at end of file diff --git a/backend/config/mailchimpConfig.js b/backend/config/mailchimpConfig.js new file mode 100644 index 0000000000..4734f61bd8 --- /dev/null +++ b/backend/config/mailchimpConfig.js @@ -0,0 +1,55 @@ +import mailchimp from "@mailchimp/mailchimp_marketing" + +// [x] set up env +// [x] hĂ€mta server prefix +// [ ] skapa validering/error i ui för om emailadressen redan finns i mailchimp (Mailchimp returnerar 400 med "Member Exists" i body) Är detta en sĂ€kerhetssĂ„rbarhet? +// [x] Kolla om premiumStartDate och premiumEndDate funkar och skickas med i Mailchimp +// [ ] Skapa logik som taggar ny anvĂ€ndare med "Vill bli medlem" i Mailchimp vid POST +// [ ] Ta bort required pĂ„ alla fĂ€lt i Mailchimp, backend sköter validering sĂ„ det inte blir krock + +mailchimp.setConfig({ + apiKey: process.env.MAILCHIMP_API_KEY, + server: process.env.MAILCHIMP_SERVER_PREFIX +}) + +const toMailchimpMember = (user, cityLabel) => ({ + email_address: user.email, + status: "subscribed", //vi har single opt-in, sĂ„ vi kan ha "subscribed" och inte "pending" + merge_fields: { + FNAME: user.firstName, + LNAME: user.lastName, + CITY: cityLabel, + MOTIVE: user.justifyMembership, + // [ ] WORKSTATUS: user.workStatus, + MMERGE15: user.isPremium ? "Premium" : "Gratis", + PREM_START: user.premiumStartDate, + PREM_END: user.premiumEndDate, + CREATEDAT: user.userCreatedAt + } +}) + +export const addContactToMailchimp = async (user, cityLabel) => { + + // Stoppa tidigt om input saknas. + if (!user?.email) { + throw new Error("Missing user email for Mailchimp") + } + + // SĂ€kerstĂ€ll att Mailchimp-konfig finns innan API-anrop. + if (!process.env.MAILCHIMP_API_KEY || !process.env.MAILCHIMP_SERVER_PREFIX || !process.env.MAILCHIMP_LIST_ID) { + throw new Error("Missing Mailchimp environment variables") + } + + try { + const response = await mailchimp.lists.addListMember( + process.env.MAILCHIMP_LIST_ID, + toMailchimpMember(user, cityLabel) + ) + + return response + + } catch (error) { + console.error("Mailchimp error:", error.response?.body || error.message) + throw error + } +} \ No newline at end of file diff --git a/backend/middleware/authMiddleware.js b/backend/middleware/authMiddleware.js new file mode 100644 index 0000000000..f0cdc9580a --- /dev/null +++ b/backend/middleware/authMiddleware.js @@ -0,0 +1,25 @@ +// [ ] todo: implement middleware for user authentication and for premium content access which is then used in dashboardRoutes +// [ ] note: do we need to add replace bearer in req.header and test loggedOut: true, + +import { User } from '../models/User' + +export const authenticateUser = async (request, response, next) => { + try { + const user = await User.findOne({ accessToken: request.header("Authorization").replace("Bearer ", "") }) + + if (user) { + request.user = user + next() + } else { + response.status(401).json({ + message: "Authentication missing or invalid", + loggedOut: true + }) + } + } catch (error) { + response.status(500).json({ + message: "Internal Server Error", + error: error.message + }) + } +} \ No newline at end of file diff --git a/backend/models/User.js b/backend/models/User.js new file mode 100644 index 0000000000..5426cc5a1c --- /dev/null +++ b/backend/models/User.js @@ -0,0 +1,88 @@ +// [x] todo: implement user schema and model with Mongoose +// [ ] todo: look into adding enum for city if neccessary +// [ ] note: do we need ispremium in schema? + +import crypto from "crypto" +import mongoose from "mongoose" + +const formatStockholm = (date) => date ? new Intl.DateTimeFormat("sv-SE", { + timeZone: "Europe/Stockholm", + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }).format(date) +: undefined; + +const userSchema = new mongoose.Schema({ + + firstName: { + type: String, + required: true, + minlength: 2 + }, + lastName: { + type: String, + required: true, + minlength: 2 + }, + email: { + type: String, + required: true, + unique: true, + lowercase: true + }, + password: { + type: String, + required: true + }, + accessToken: { + type: String, + default: () => crypto.randomBytes(128).toString("hex") + }, + city: { + type: String, + required: true + }, + justifyMembership: { + type: String, + required: true, + minlength: 10 + }, + isPremium: { + type: Boolean, + required: true, + default: false + }, + premiumStartDate: { + type: Date, + default: function() { + return this.isPremium === true ? new Date() : undefined + } + }, + premiumEndDate: { + type: Date, + default: function() { + return this.isPremium === true ? new Date(new Date().setFullYear(new Date().getFullYear() + 1)) : undefined + } + }, + userCreatedAt: { + type: Date, + default: Date.now + } +}, + +{ + toJSON: { + transform(doc, ret) { + ret.userCreatedAt = formatStockholm(ret.userCreatedAt); + ret.premiumStartDate = formatStockholm(ret.premiumStartDate); + ret.premiumEndDate = formatStockholm(ret.premiumEndDate); + } + } +} +) + +export const User = mongoose.model("User", userSchema) \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index 08f29f2448..9aed807425 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,9 +12,13 @@ "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", - "cors": "^2.8.5", - "express": "^4.17.3", - "mongoose": "^8.4.0", - "nodemon": "^3.0.1" + "@mailchimp/mailchimp_marketing": "^3.0.80", + "bcrypt": "^6.0.0", + "cors": "^2.8.6", + "express": "^4.22.1", + "express-list-endpoints": "^7.1.1", + "mongodb": "^7.1.1", + "mongoose": "^8.23.0", + "nodemon": "^3.1.14" } -} \ No newline at end of file +} diff --git a/backend/routes/dashboardRoutes.js b/backend/routes/dashboardRoutes.js new file mode 100644 index 0000000000..0425aacf1d --- /dev/null +++ b/backend/routes/dashboardRoutes.js @@ -0,0 +1,38 @@ +// [ ] todo: implement dashboard routes and connect middleware for user authentication and for premium content access (is some content visable to free users or is the whole "page" restricted?) +// [ ] todo: implement patch request and get request to /mina-sidor +// [ ] Skapa admin route. HĂ€r eller egen route? +// [ ] Flytta ut controllers? + +import express from "express" +//import { getDashboard } from "../controllers/dashboardController.js" +import { User } from "../models/User" +import { seedingUsers } from "../seedingDatabase/seedingUsers" + +const router = express.Router() +seedingUsers() + +// Dashboard route - protected, only accessible to authenticated users +router.get("/:id", async (request, response) => { + try { + const { id } = request.params + const user = await User.findById(id) + + if (user) { + response.status(200).json({ + message: "Hej!!!! VĂ€lkommen till din hemliga dashboard, " + user.firstName + "!!!", + user + }) + } else { + response.status(404).json({ + error: "user not found" + }) + } + + } catch (error) { + response.status(400).json({ + error: "invalid request" + }) + } +}) + +export default router \ No newline at end of file diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js new file mode 100644 index 0000000000..2677c6bdb0 --- /dev/null +++ b/backend/routes/userRoutes.js @@ -0,0 +1,114 @@ +// [x] todo: implement user routes for signup and login locally +// [ ] todo: make route for admin to view members and filter on premium(query) + +import express from "express" +import bcrypt from "bcrypt" +import { User } from "../models/User" +import { addContactToMailchimp } from "../config/mailchimpConfig"; +import { seedingUsers } from "../seedingDatabase/seedingUsers"; + +const router = express.Router() + +seedingUsers() + +router.get("/", async (request, response) => { + + const user = await User.find().sort({ userCreatedAt: "desc"}) + response.json(user) +}) + +// Signup route +router.post("/signup", async (request, response) => { + try { + const { email, password, firstName, lastName, cityValue, cityLabel, justifyMembership, isPremium } = request.body + const existingUser = await User.findOne({ email: email.toLowerCase()}) + + if (existingUser) { + return response.status(409).json({ + success: false, + message: "Email address already exists" + // [ ] Är detta en GDPR frĂ„ga? FĂ„r vi sĂ€ga att emailadressen redan finns? + }) + } + + const salt = bcrypt.genSaltSync() + const hashedPassword = bcrypt.hashSync(password, salt) + + const user = new User({ + email, + password: hashedPassword, + firstName, + lastName, + city: cityValue, + // [ ] lĂ€gg till cityLabel hĂ€r ocksĂ„? Isf blir det cityLabel och cityValue i User schema istĂ€llet + // [ ] lĂ€gg till workStatus + justifyMembership, + isPremium: isPremium || false + }) + + const savedUser = await user.save() + + try { + await addContactToMailchimp(savedUser, cityLabel) + } catch (error) { + const mailchimpError = error.response?.body || error.message + + console.error("Kunde inte lĂ€gga till anvĂ€ndare i Mailchimp:", mailchimpError) + + return response.status(502).json({ + success: false, + message: "User created, but Mailchimp sync failed", + response: mailchimpError + }) + } + + response.status(201).json({ + success: true, + message: "User created successfully and added to Mailchimp", + response: savedUser + }) + + } catch (error) { + response.status(400).json({ + success: false, + message: "Failed to create user", + response: error + }) + } +}) + +// Login route +router.post("/login", async (request, response) => { + + try { + const { email, password } = request.body + + const user = await User.findOne({email:email.toLowerCase()}) + + if (user && bcrypt.compareSync(password, user.password)) + { + response.status(200).json({ + success: true, + message: "Login successful", + response: { + email: user.email, + id: user._id, + accessToken: user.accessToken } + }) + } else { + response.status(401).json({ + success: false, + message: "Invalid email or password", + response: null + }) + } + } catch(error) { + response.status(500).json({ + success: false, + message: "Something went wrong during login", + response: error + }) + } +}) + +export default router \ No newline at end of file diff --git a/backend/seedingDatabase/seedingUsers.js b/backend/seedingDatabase/seedingUsers.js new file mode 100644 index 0000000000..f6d79fecab --- /dev/null +++ b/backend/seedingDatabase/seedingUsers.js @@ -0,0 +1,41 @@ +import { User } from "../models/User" +import bcrypt from "bcrypt" + +export const seedingUsers = async () => { + const salt = 10 + + if (process.env.RESET_SEED_DB === "true") { + console.log("Resetting seeding database..."); + await User.deleteMany() + } + + // To check if Carolina exists + const carolinaExists = await User.findOne({ email: "carolina.oldertz@gmail.com" }) + if (!carolinaExists) { + await new User ({ + firstName: "Carolina", + lastName: "Oldertz", + email: "carolina.oldertz@gmail.com", + password: bcrypt.hashSync("carolina", salt), + city: "Stockholm", + justifyMembership: "Hej pĂ„ dig!!!!!!!!!!", + isPremium: true, + premiumStartDate: new Date(), + premiumEndDate: new Date(new Date().setFullYear(new Date().getFullYear() + 1)), + }).save(); + } + + // To check if Mikaela exists + const mikaelaExists = await User.findOne({ email: "mikaelasturk@gmail.com" }) + if (!mikaelaExists) { + await new User ({ + firstName: "Mikaela", + lastName: "Sturk", + email: "mikaelasturk@gmail.com", + password: bcrypt.hashSync("mikaela", salt), + city: "Stockholm", + justifyMembership: "Hej pĂ„ digSAN!", + isPremium: false + }).save() + } +} \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 070c875189..75a23234e5 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,8 +1,17 @@ +// [ ] todo: connect user routes, premium user routes, and dashboard routes to server +// [x] todo: implement listEndpoints from express-list-endpoints to "/" + import express from "express"; import cors from "cors"; import mongoose from "mongoose"; +import listEndpoints from "express-list-endpoints"; +import userRoutes from "./routes/userRoutes" +import { authenticateUser } from "./middleware/authMiddleware"; +import dashboardRoutes from "./routes/dashboardRoutes" + -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/final-project"; +// [ ] Make new connection in compass! +const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/membership"; mongoose.connect(mongoUrl); mongoose.Promise = Promise; @@ -13,10 +22,19 @@ app.use(cors()); app.use(express.json()); app.get("/", (req, res) => { - res.send("Hello Technigo!"); + const endpoints = listEndpoints(app) + res.json({ + message: "Welcome to Womenation API", + endpoints: endpoints + }) }); + +app.use("/users", userRoutes) +app.use("/dashboard", authenticateUser, dashboardRoutes) + + // Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); -}); +}); \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000000..2fd24fdb30 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,33 @@ +import js from '@eslint/js' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import globals from 'globals' + +export default [ + { ignores: ['dist'] }, + { + files: ['**/*.{js,jsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module' + } + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh + }, + rules: { + ...js.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true } + ] + } + } +] diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000000..2fd24fdb30 --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,33 @@ +import js from '@eslint/js' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import globals from 'globals' + +export default [ + { ignores: ['dist'] }, + { + files: ['**/*.{js,jsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module' + } + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh + }, + rules: { + ...js.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true } + ] + } + } +] diff --git a/frontend/index.html b/frontend/index.html index 664410b5b9..6d0820371a 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,12 +2,19 @@ - + + + + + - Technigo React Vite Boiler Plate + Womenation
- + diff --git a/frontend/package.json b/frontend/package.json index 7b2747e949..297b19a865 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,26 +1,32 @@ { - "name": "project-final-backend", - "description": "Client part of final project", - "version": "1.0.0", + "name": "technigo-react-vite-boiler", + "private": true, + "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host", "build": "vite build", - "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", + "lint": "eslint .", "preview": "vite preview" }, "dependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router": "^7.13.2", + "react-router-dom": "^7.13.2", + "react-select": "^5.10.2", + "styled-components": "^6.3.12", + "zustand": "^5.0.12" }, "devDependencies": { - "@types/react": "^18.2.15", - "@types/react-dom": "^18.2.7", - "@vitejs/plugin-react": "^4.0.3", - "eslint": "^8.45.0", - "eslint-plugin-react": "^7.32.2", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.3", - "vite": "^6.3.5" + "@eslint/js": "^9.21.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.21.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.15.0", + "vite": "^6.2.0" } } diff --git a/frontend/public/_redirects b/frontend/public/_redirects new file mode 100644 index 0000000000..7797f7c6a7 --- /dev/null +++ b/frontend/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 diff --git a/frontend/public/favicon.png b/frontend/public/favicon.png new file mode 100644 index 0000000000..2f55027fe1 Binary files /dev/null and b/frontend/public/favicon.png differ diff --git a/frontend/public/media/icons/checkbox.png b/frontend/public/media/icons/checkbox.png new file mode 100644 index 0000000000..975446c9f2 Binary files /dev/null and b/frontend/public/media/icons/checkbox.png differ diff --git a/frontend/public/media/icons/checkbox2.png b/frontend/public/media/icons/checkbox2.png new file mode 100644 index 0000000000..0fc03dcb59 Binary files /dev/null and b/frontend/public/media/icons/checkbox2.png differ diff --git a/frontend/public/media/images/1 (254).jpg b/frontend/public/media/images/1 (254).jpg new file mode 100644 index 0000000000..e2caa9a5b1 Binary files /dev/null and b/frontend/public/media/images/1 (254).jpg differ diff --git a/frontend/public/media/images/1 (258).jpg b/frontend/public/media/images/1 (258).jpg new file mode 100644 index 0000000000..296a786b6b Binary files /dev/null and b/frontend/public/media/images/1 (258).jpg differ diff --git a/frontend/public/media/images/1 (284).jpg b/frontend/public/media/images/1 (284).jpg new file mode 100644 index 0000000000..e00ee173b0 Binary files /dev/null and b/frontend/public/media/images/1 (284).jpg differ diff --git a/frontend/public/media/images/1 (285) (1).jpg b/frontend/public/media/images/1 (285) (1).jpg new file mode 100644 index 0000000000..a317fee20a Binary files /dev/null and b/frontend/public/media/images/1 (285) (1).jpg differ diff --git a/frontend/public/media/images/1 (285).jpg b/frontend/public/media/images/1 (285).jpg new file mode 100644 index 0000000000..a317fee20a Binary files /dev/null and b/frontend/public/media/images/1 (285).jpg differ diff --git a/frontend/public/media/images/1 (286).jpg b/frontend/public/media/images/1 (286).jpg new file mode 100644 index 0000000000..9d4d78a10c Binary files /dev/null and b/frontend/public/media/images/1 (286).jpg differ diff --git a/frontend/public/media/images/396725506_266163596405153_7816128484953626918_n.jpg b/frontend/public/media/images/396725506_266163596405153_7816128484953626918_n.jpg new file mode 100644 index 0000000000..a7b6afcc00 Binary files /dev/null and b/frontend/public/media/images/396725506_266163596405153_7816128484953626918_n.jpg differ diff --git a/frontend/public/media/images/396727846_266161843071995_5188809347353672283_n.jpg b/frontend/public/media/images/396727846_266161843071995_5188809347353672283_n.jpg new file mode 100644 index 0000000000..6c1b23da70 Binary files /dev/null and b/frontend/public/media/images/396727846_266161843071995_5188809347353672283_n.jpg differ diff --git a/frontend/public/media/images/396728014_266161709738675_2522972426941273959_n (1).jpg b/frontend/public/media/images/396728014_266161709738675_2522972426941273959_n (1).jpg new file mode 100644 index 0000000000..59cc93252e Binary files /dev/null and b/frontend/public/media/images/396728014_266161709738675_2522972426941273959_n (1).jpg differ diff --git a/frontend/public/media/images/397877626_266163639738482_4804133712260471633_n.jpg b/frontend/public/media/images/397877626_266163639738482_4804133712260471633_n.jpg new file mode 100644 index 0000000000..1c40175056 Binary files /dev/null and b/frontend/public/media/images/397877626_266163639738482_4804133712260471633_n.jpg differ diff --git a/frontend/public/media/images/397969742_266161883071991_1986506717576720906_n.jpg b/frontend/public/media/images/397969742_266161883071991_1986506717576720906_n.jpg new file mode 100644 index 0000000000..2506eb00cb Binary files /dev/null and b/frontend/public/media/images/397969742_266161883071991_1986506717576720906_n.jpg differ diff --git a/frontend/public/media/images/Fotograf-linkoping_SugarCrispPictures_Womenation-12.jpg b/frontend/public/media/images/Fotograf-linkoping_SugarCrispPictures_Womenation-12.jpg new file mode 100644 index 0000000000..dda185510c Binary files /dev/null and b/frontend/public/media/images/Fotograf-linkoping_SugarCrispPictures_Womenation-12.jpg differ diff --git a/frontend/public/media/images/Fotograf-linkoping_SugarCrispPictures_Womenation-15.jpg b/frontend/public/media/images/Fotograf-linkoping_SugarCrispPictures_Womenation-15.jpg new file mode 100644 index 0000000000..43acafafc4 Binary files /dev/null and b/frontend/public/media/images/Fotograf-linkoping_SugarCrispPictures_Womenation-15.jpg differ diff --git a/frontend/public/media/images/Fotograf-linkoping_SugarCrispPictures_Womenation-17.jpg b/frontend/public/media/images/Fotograf-linkoping_SugarCrispPictures_Womenation-17.jpg new file mode 100644 index 0000000000..ab6b4220c4 Binary files /dev/null and b/frontend/public/media/images/Fotograf-linkoping_SugarCrispPictures_Womenation-17.jpg differ diff --git a/frontend/public/media/images/Fotograf-linkoping_SugarCrispPictures_Womenation-20.jpg b/frontend/public/media/images/Fotograf-linkoping_SugarCrispPictures_Womenation-20.jpg new file mode 100644 index 0000000000..c8dce72f8a Binary files /dev/null and b/frontend/public/media/images/Fotograf-linkoping_SugarCrispPictures_Womenation-20.jpg differ diff --git a/frontend/public/media/images/Gemini_Generated_Image_5znreq5znreq5znr.png b/frontend/public/media/images/Gemini_Generated_Image_5znreq5znreq5znr.png new file mode 100644 index 0000000000..113e2c6d09 Binary files /dev/null and b/frontend/public/media/images/Gemini_Generated_Image_5znreq5znreq5znr.png differ diff --git a/frontend/public/media/images/IMG_2162.HEIC b/frontend/public/media/images/IMG_2162.HEIC new file mode 100644 index 0000000000..b963908e86 Binary files /dev/null and b/frontend/public/media/images/IMG_2162.HEIC differ diff --git a/frontend/public/media/images/IMG_2874.HEIC b/frontend/public/media/images/IMG_2874.HEIC new file mode 100644 index 0000000000..6256d20677 Binary files /dev/null and b/frontend/public/media/images/IMG_2874.HEIC differ diff --git a/frontend/public/media/images/IMG_2877.HEIC b/frontend/public/media/images/IMG_2877.HEIC new file mode 100644 index 0000000000..5d4107ee98 Binary files /dev/null and b/frontend/public/media/images/IMG_2877.HEIC differ diff --git a/frontend/public/media/images/IMG_8886.JPG b/frontend/public/media/images/IMG_8886.JPG new file mode 100644 index 0000000000..34eecac10f Binary files /dev/null and b/frontend/public/media/images/IMG_8886.JPG differ diff --git a/frontend/public/media/images/Womenation networking 6 (2).jpg b/frontend/public/media/images/Womenation networking 6 (2).jpg new file mode 100644 index 0000000000..5c4923c5e5 Binary files /dev/null and b/frontend/public/media/images/Womenation networking 6 (2).jpg differ diff --git a/frontend/public/media/images/done (44).jpg b/frontend/public/media/images/done (44).jpg new file mode 100644 index 0000000000..811bf7446b Binary files /dev/null and b/frontend/public/media/images/done (44).jpg differ diff --git a/frontend/public/media/images/done (45).jpg b/frontend/public/media/images/done (45).jpg new file mode 100644 index 0000000000..71b05908e9 Binary files /dev/null and b/frontend/public/media/images/done (45).jpg differ diff --git a/frontend/public/media/images/done (5).jpg b/frontend/public/media/images/done (5).jpg new file mode 100644 index 0000000000..44965af778 Binary files /dev/null and b/frontend/public/media/images/done (5).jpg differ diff --git a/frontend/public/media/images/done (6).jpg b/frontend/public/media/images/done (6).jpg new file mode 100644 index 0000000000..00aa19bb81 Binary files /dev/null and b/frontend/public/media/images/done (6).jpg differ diff --git a/frontend/public/media/logo/Black logo - no background.png b/frontend/public/media/logo/Black logo - no background.png new file mode 100644 index 0000000000..be2926ddfa Binary files /dev/null and b/frontend/public/media/logo/Black logo - no background.png differ diff --git a/frontend/public/media/logo/Color logo - no background.png b/frontend/public/media/logo/Color logo - no background.png new file mode 100644 index 0000000000..7214fb310e Binary files /dev/null and b/frontend/public/media/logo/Color logo - no background.png differ diff --git a/frontend/public/media/logo/Color logo with background.png b/frontend/public/media/logo/Color logo with background.png new file mode 100644 index 0000000000..586c15c5c9 Binary files /dev/null and b/frontend/public/media/logo/Color logo with background.png differ diff --git a/frontend/public/media/logo/White logo - no background.png b/frontend/public/media/logo/White logo - no background.png new file mode 100644 index 0000000000..48b16526b8 Binary files /dev/null and b/frontend/public/media/logo/White logo - no background.png differ diff --git a/frontend/public/media/logo/icon.png b/frontend/public/media/logo/icon.png new file mode 100644 index 0000000000..2f55027fe1 Binary files /dev/null and b/frontend/public/media/logo/icon.png differ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 0a24275e6e..cddb8dad16 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,8 +1,46 @@ -export const App = () => { +// [ ] Skapa premium route + +import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom" +import styled, { ThemeProvider } from "styled-components" +import { Theme } from "./themes/Theme" +import { GlobalStyle } from "./styles/GlobalStyle" +import { MainPageLayout, MedlemsportalLayout } from "./layouts" +import { ProtectedRoute } from "./routes" +import { OmMedlemskap, LoggaIn, MinaSidor, Medlemskap, Events, BliMedlem, Erbjudanden } from "./pages" +const AccountHomeTitle = styled.h1` + color: ${({ theme }) => theme.konto.txtClrH1}; + text-transform: uppercase; + letter-spacing: 10px; + text-align: center; + font-size: 28px; + font-weight: 400; + margin-bottom: 48px; +` + +export const App = () => { return ( - <> -

Welcome to Final Project!

- - ); -}; + + + + + }> + }/> + }/> + }/> + }/> + }> + }> + VÀlkommen till ditt konto!} /> + } /> + } /> + } /> + } /> + + + + + + + ) +} diff --git a/frontend/src/components/ComponentTemplate.jsx b/frontend/src/components/ComponentTemplate.jsx new file mode 100644 index 0000000000..ab42218206 --- /dev/null +++ b/frontend/src/components/ComponentTemplate.jsx @@ -0,0 +1,13 @@ +import styled from 'styled-components' + +const StyledComponent = styled.div` + +` + +export const ComponentTemplate = () => { + return ( + <> + + ) +} + diff --git a/frontend/src/components/navigation/HamburgerMenu.jsx b/frontend/src/components/navigation/HamburgerMenu.jsx new file mode 100644 index 0000000000..f3b9e80e3c --- /dev/null +++ b/frontend/src/components/navigation/HamburgerMenu.jsx @@ -0,0 +1,108 @@ +// [ ] Ska vi lÀgga auto-stÀngning av hamburgermenyn? +// [ ] Vill vi att HamburgerMenu ska hantera sin egen state för expanded, eller ska det hanteras i Navbar och skickas ner som prop? +// [ ] Se till sÄ att contentStore och Theme anvÀnder samma sid-struktur + +import styled from "styled-components" +import { NavLink } from "react-router-dom" +import { useContentStore } from "../../store" + + +const StyledButton = styled.button` + color: ${({ theme }) => theme.navbar.txtClr}; + background-color: transparent; + padding: 12px; + border: none; + z-index: 4001; + position: relative; + margin-left: 20px; + + @media (min-width: ${({ theme }) => theme.breakpoints.tablet}) { + display: none; + } +` + +const StyledNavContent = styled.div` + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 4000; + padding: 0 24px 42px 24px; + display: flex; + flex-direction: column; + background: ${({ theme }) => theme.navbar.bgClr}; + align-items: center; + width: 100%; + box-sizing: border-box; + height: 50vh; + transition: transform 0.3s ease-in-out, opacity 0.5s ease; + transform: ${({ $expanded }) => ($expanded ? "translateY(0)" : "translateY(-100%)")}; + opacity: ${({ $expanded }) => ($expanded ? "1" : "0")}; + pointer-events: ${({ $expanded }) => ($expanded ? "all" : "none")}; + visibility: ${({ $expanded }) => ($expanded ? "visible" : "hidden")}; // För att förhindra att tabba genom menyn nÀr den Àr stÀngd +` + +const StyledSpan = styled.span` + display: block; + height: 4px; + width: 40px; + background: ${({ theme }) => theme.navbar.spanClr}; + margin: 8px 0; + border-radius: 2px; + transition: transform 0.4s ease, opacity 0.3s ease; +` + +const StyledFirstSpan = styled(StyledSpan)` + transform: ${({ $expanded }) => ($expanded ? "translateY(12px) rotate(45deg)" : "initial")}; +` + +const StyledSecondSpan = styled(StyledSpan)` + opacity: ${({ $expanded }) => ($expanded ? "0" : "initial")}; +` + +const StyledThirdSpan = styled(StyledSpan)` + transform: ${({ $expanded }) => ($expanded ? "translateY(-12px) rotate(-45deg)" : "initial")}; +` + +const StyledHamList = styled.ul` + list-style: none; + padding-top: 100px; + margin: 0; + display: flex; + flex-direction: column; + gap: 10px; +` + +const StyledNavLink = styled(NavLink)` + text-decoration: none; + font-size: 18px; + color: ${({ theme }) => theme.navbar.txtClr}; +` + +export const HamburgerMenu = ({ expanded, onToggle }) => { + const { navbarContent } = useContentStore() + const { links } = navbarContent + const { omMedlemskap, konto } = links + + return ( + <> + + + + +
  • onToggle(false)}>{omMedlemskap}
  • +
  • onToggle(false)}>{konto}
  • +
    +
    + + ) +} diff --git a/frontend/src/components/navigation/Navbar.jsx b/frontend/src/components/navigation/Navbar.jsx new file mode 100644 index 0000000000..efc49c8d5f --- /dev/null +++ b/frontend/src/components/navigation/Navbar.jsx @@ -0,0 +1,105 @@ +// [ ] Flytta handleLogout till userStore +// [ ] Ska vi flytta usestate för hamburger-menu till store? +// [ ] StÀda upp stylingen hÀr + +import styled from "styled-components" +import { NavLink, useNavigate } from "react-router-dom" +import { Button } from "../ui" +import { useContentStore, useUserStore } from "../../store" +import { useState } from "react" +import { HamburgerMenu } from "./HamburgerMenu" + +const StyledNavbar = styled.nav` + background: ${({theme}) => theme.navbar.bgClr}; + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +` + +const StyledNavContentContainer = styled.div` + display: flex; + flex-direction: row; +` + +const StyledLogo = styled.img` + height: 10vh; + display: flex; + align-items: center; + justify-items: center; + padding: 10px 0px 10px 20px; +` + +const StyledUl = styled.ul` + display: none; + + @media (min-width: ${({theme})=> theme.breakpoints.tablet}) { + display: flex; + flex-direction: row; + gap: 30px; + align-items: center; + margin-left: 40px; + } +` + +const StyledItems = styled.li` + color: ${({theme})=> theme.navbar.txtClr} +` + +const StyledButtonWrapper = styled.div` + margin: 35px 35px 30px 0; +` + +export const Navbar = () => { + const { navbarContent } = useContentStore() + const { buttons, logoHref, links } = navbarContent + const { user, resetUserData } = useUserStore() + const [expanded, setExpanded] = useState(false) + const navigate = useNavigate() + const isLoggedIn = !!user?.accessToken + + const handleLogout = () => { + resetUserData() + navigate("/logga-in") + } + + const handleClick = (nextState) => { + if (typeof nextState === 'boolean') { + setExpanded(nextState) + return + } + + setExpanded(prev => !prev) + } + + return ( + + + + + + + {links.omMedlemskap} + {links.konto} + + + + + {isLoggedIn + ?