From 871efad30f92a043a88afaee4a670553e2856472 Mon Sep 17 00:00:00 2001 From: Moueed Ali Date: Wed, 3 Sep 2025 09:56:13 +0200 Subject: [PATCH 01/59] feat/added feature for email and password validaion --- src/pages/register/index.js | 36 +++++++++++++++++++++++++++++++++++- src/service/apiClient.js | 2 +- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/pages/register/index.js b/src/pages/register/index.js index 5cc70e32..6947030d 100644 --- a/src/pages/register/index.js +++ b/src/pages/register/index.js @@ -14,6 +14,30 @@ const Register = () => { setFormData({ ...formData, [name]: value }); }; + const validateEmail = (email) => { + const mailFormat = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/; + if (email.match(mailFormat)) { + return true; // return true if email is in valid format + } + else { + alert("You have entered an invalid email address"); // generic error message for now, needs refactoring + return false; + } + + console.log(onRegister) + } + + const validatePassword = (password) => { + const passwordFormat = /^(?=.*?[A-Z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/; + if (password.match(passwordFormat)) { + return true; + } + else { + alert("Your password is not in the right format"); // generic error message for now, needs refactoring + return false; + } + } + return (
{ type="email" name="email" label={'Email *'} + required /> { name="password" label={'Password *'} type={'password'} + required />
@@ -52,3 +83,6 @@ const Register = () => { }; export default Register; + +// email: dave@email.com +// password: @Qwert123456 \ No newline at end of file diff --git a/src/service/apiClient.js b/src/service/apiClient.js index 5f3cdbcf..0effc0ca 100644 --- a/src/service/apiClient.js +++ b/src/service/apiClient.js @@ -5,7 +5,7 @@ async function login(email, password) { } async function register(email, password) { - await post('users', { email, password }, false); + await post('signup', { email, password }, false); return await login(email, password); } From 1619055c1a2fbf7a11520a2fe907dbd77adaa319 Mon Sep 17 00:00:00 2001 From: Moueed Ali Date: Wed, 3 Sep 2025 11:38:39 +0200 Subject: [PATCH 02/59] fix/removed_email_and_password --- src/pages/register/index.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pages/register/index.js b/src/pages/register/index.js index 6947030d..42efbb99 100644 --- a/src/pages/register/index.js +++ b/src/pages/register/index.js @@ -83,6 +83,3 @@ const Register = () => { }; export default Register; - -// email: dave@email.com -// password: @Qwert123456 \ No newline at end of file From d45d3451a332979afd1656d3c51f87ef4a59b32b Mon Sep 17 00:00:00 2001 From: Moueed Ali Date: Wed, 3 Sep 2025 13:44:27 +0200 Subject: [PATCH 03/59] feat/added new feature to perform checks against DB for existing email --- .prettierignore | 2 ++ package-lock.json | 18 ++++++++++++---- package.json | 3 ++- src/pages/register/index.js | 42 ++++++++++++++++++++++++++++++------- 4 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..08fe6302 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +src/* +src/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6891fbef..efeadd9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,13 +15,14 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-modal": "^3.16.1", + "react-password-checklist": "^1.8.1", "react-router-dom": "^6.8.0", "react-scripts": "5.0.1", "web-vitals": "^3.1.1" }, "devDependencies": { "eslint": "^8.57.1", - "eslint-config-prettier": "^9.1.0", + "eslint-config-prettier": "^9.1.2", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-n": "^16.6.2", @@ -7360,9 +7361,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", + "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, "license": "MIT", "bin": { @@ -14974,6 +14975,15 @@ "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18" } }, + "node_modules/react-password-checklist": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/react-password-checklist/-/react-password-checklist-1.8.1.tgz", + "integrity": "sha512-QHIU/OejxoH4/cIfYLHaHLb+yYc8mtL0Vr4HTmULxQg3ZNdI9Ni/yYf7pwLBgsUh4sseKCV/GzzYHWpHqejTGw==", + "license": "MIT", + "peerDependencies": { + "react": ">16.0.0-alpha || >17.0.0-alpha || >18.0.0-alpha" + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", diff --git a/package.json b/package.json index 249b6b05..320f92bc 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-modal": "^3.16.1", + "react-password-checklist": "^1.8.1", "react-router-dom": "^6.8.0", "react-scripts": "5.0.1", "web-vitals": "^3.1.1" @@ -43,7 +44,7 @@ }, "devDependencies": { "eslint": "^8.57.1", - "eslint-config-prettier": "^9.1.0", + "eslint-config-prettier": "^9.1.2", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-n": "^16.6.2", diff --git a/src/pages/register/index.js b/src/pages/register/index.js index 42efbb99..0efe356c 100644 --- a/src/pages/register/index.js +++ b/src/pages/register/index.js @@ -4,6 +4,7 @@ import TextInput from '../../components/form/textInput'; import useAuth from '../../hooks/useAuth'; import CredentialsCard from '../../components/credentials'; import './register.css'; +import ReactPasswordChecklist from 'react-password-checklist'; const Register = () => { const { onRegister } = useAuth(); @@ -17,14 +18,13 @@ const Register = () => { const validateEmail = (email) => { const mailFormat = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/; if (email.match(mailFormat)) { - return true; // return true if email is in valid format + return true; } else { - alert("You have entered an invalid email address"); // generic error message for now, needs refactoring + alert("You have entered an invalid email address"); return false; } - console.log(onRegister) } const validatePassword = (password) => { @@ -33,10 +33,25 @@ const Register = () => { return true; } else { - alert("Your password is not in the right format"); // generic error message for now, needs refactoring + alert("Your password is not in the right format"); return false; } } + + const checkEmailExists = async (email) => { + try { + const response = await fetch('/api/check-email', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email }), + }); + const data = await response.json(); + return data.exists; + } + catch (error) { + return true; + } + }; return (
@@ -65,15 +80,28 @@ const Register = () => { type={'password'} required /> +
From 676d083378b4ebbef5a1898562de225d06021895 Mon Sep 17 00:00:00 2001 From: Linda Do Date: Wed, 3 Sep 2025 13:53:58 +0200 Subject: [PATCH 04/59] updated step one and two --- src/pages/welcome/index.js | 7 ++++++- src/pages/welcome/stepFour/index.js | 19 +++++++++++++++++++ src/pages/welcome/stepOne/index.js | 12 ++++-------- src/pages/welcome/stepTwo/index.js | 25 +++++++++++++++++++++++-- 4 files changed, 52 insertions(+), 11 deletions(-) create mode 100644 src/pages/welcome/stepFour/index.js diff --git a/src/pages/welcome/index.js b/src/pages/welcome/index.js index 85af11ab..377418ad 100644 --- a/src/pages/welcome/index.js +++ b/src/pages/welcome/index.js @@ -3,6 +3,7 @@ import Stepper from '../../components/stepper'; import useAuth from '../../hooks/useAuth'; import StepOne from './stepOne'; import StepTwo from './stepTwo'; +import StepFour from './stepFour'; import './style.css'; const Welcome = () => { @@ -11,7 +12,9 @@ const Welcome = () => { const [profile, setProfile] = useState({ firstName: '', lastName: '', + username: '', githubUsername: '', + mobile: '', bio: '' }); @@ -23,9 +26,10 @@ const Welcome = () => { [name]: value }); }; + const onComplete = () => { - onCreateProfile(profile.firstName, profile.lastName, profile.githubUsername, profile.bio); + onCreateProfile(profile.firstName, profile.lastName, profile.username, profile.githubUsername, profile.mobile, profile.bio); }; return ( @@ -38,6 +42,7 @@ const Welcome = () => { } onComplete={onComplete}> + ); diff --git a/src/pages/welcome/stepFour/index.js b/src/pages/welcome/stepFour/index.js new file mode 100644 index 00000000..8395955c --- /dev/null +++ b/src/pages/welcome/stepFour/index.js @@ -0,0 +1,19 @@ +import Form from '../../../components/form'; + +const StepThree = ({ data, setData }) => { + return ( + <> +
+

Bio

+
+
+
+ +

*Required

+
+
+ + ); +}; + +export default StepThree; diff --git a/src/pages/welcome/stepOne/index.js b/src/pages/welcome/stepOne/index.js index 317940f8..33e5d3de 100644 --- a/src/pages/welcome/stepOne/index.js +++ b/src/pages/welcome/stepOne/index.js @@ -22,15 +22,11 @@ const StepOne = ({ data, setData }) => { onChange={setData} value={data.firstName} name="firstName" - label={'First name'} - /> - - + +

*Required

diff --git a/src/pages/welcome/stepTwo/index.js b/src/pages/welcome/stepTwo/index.js index f40dad3e..3d02952c 100644 --- a/src/pages/welcome/stepTwo/index.js +++ b/src/pages/welcome/stepTwo/index.js @@ -1,14 +1,35 @@ + import Form from '../../../components/form'; +import TextInput from '../../../components/form/textInput'; const StepTwo = ({ data, setData }) => { return ( <>
-

Bio

+

Basic info

- + + +

*Required

From 6687919e96d592b406927abdaf0d4d219d12e607 Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Wed, 3 Sep 2025 14:22:37 +0200 Subject: [PATCH 05/59] Added password feature and fixed prettier --- .gitignore | 4 +++- .prettierignore | 4 ++++ package-lock.json | 8 ++++---- package.json | 2 +- src/components/form/textInput/index.js | 8 ++++---- 5 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 .prettierignore diff --git a/.gitignore b/.gitignore index 2cd0c01c..c094933b 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -.vscode \ No newline at end of file +.vscode +.idea + diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..a276e2d6 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +# Example .prettierignore content + +src/* +src/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6891fbef..450e1030 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ }, "devDependencies": { "eslint": "^8.57.1", - "eslint-config-prettier": "^9.1.0", + "eslint-config-prettier": "^9.1.2", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-n": "^16.6.2", @@ -7360,9 +7360,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", + "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, "license": "MIT", "bin": { diff --git a/package.json b/package.json index 249b6b05..9f4dcf98 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "eslint": "^8.57.1", - "eslint-config-prettier": "^9.1.0", + "eslint-config-prettier": "^9.1.2", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-n": "^16.6.2", diff --git a/src/components/form/textInput/index.js b/src/components/form/textInput/index.js index 39da3cae..d85a67e1 100644 --- a/src/components/form/textInput/index.js +++ b/src/components/form/textInput/index.js @@ -1,27 +1,27 @@ import { useState } from 'react'; const TextInput = ({ value, onChange, name, label, icon, type = 'text' }) => { - const [input, setInput] = useState(''); const [showpassword, setShowpassword] = useState(false); + const [typ, setTyp] = useState(type); if (type === 'password') { return (
{ onChange(e); - setInput(e.target.value); }} /> - {showpassword && } + + {isOpen && ( +
    + {normalizedOptions.map((option) => ( +
  • handleOptionClick(option.value)} + style={{ + padding: "0.5rem 1rem", + cursor: "pointer", + backgroundColor: value === option.value ? "#f0f0f0" : "#fff", + }} + > + {option.label} +
  • + ))} +
+ )} +
+ ); +} + +export default DropdownMenu; diff --git a/src/components/dropdown/style.css b/src/components/dropdown/style.css new file mode 100644 index 00000000..9619e527 --- /dev/null +++ b/src/components/dropdown/style.css @@ -0,0 +1,45 @@ +/* Dropdown Button */ +.dropbtn { + background-color: #3498DB; + color: white; + padding: 16px; + font-size: 16px; + border: none; + cursor: pointer; +} + +/* Dropdown button on hover & focus */ +.dropbtn:hover, .dropbtn:focus { + background-color: #2980B9; +} + +/* The container
- needed to position the dropdown content */ +.dropdown { + position: relative; + left: 0; + display: inline-block; +} + +/* Dropdown Content (Hidden by Default) */ +.dropdown-content { + display: none; + position: absolute; + background-color: #f1f1f1; + min-width: 160px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 1; +} + +/* Links inside the dropdown */ +.dropdown-content a { + color: black; + padding: 12px 16px; + text-decoration: none; + display: block; +} + +/* Change color of dropdown links on hover */ +.dropdown-content a:hover {background-color: #ddd;} + +/* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */ +.show {display:block;} \ No newline at end of file diff --git a/src/components/stepper/index.js b/src/components/stepper/index.js index c9e5f259..17281815 100644 --- a/src/components/stepper/index.js +++ b/src/components/stepper/index.js @@ -34,7 +34,7 @@ const Stepper = ({ header, children, onComplete }) => {
); - } else { + } else { return (
{label && } @@ -45,7 +45,7 @@ const TextInput = ({ value, onChange, name, label, icon, type = 'text', placehol
); } -}; +} const EyeLogo = () => { return ( diff --git a/src/components/stepper/index.js b/src/components/stepper/index.js index c9e5f259..17a79006 100644 --- a/src/components/stepper/index.js +++ b/src/components/stepper/index.js @@ -4,7 +4,7 @@ import Button from '../button'; import './style.css'; import { useState } from 'react'; -const Stepper = ({ header, children, onComplete }) => { +const Stepper = ({ header, children, onComplete, data }) => { const [currentStep, setCurrentStep] = useState(0); const onBackClick = () => { @@ -22,6 +22,34 @@ const Stepper = ({ header, children, onComplete }) => { setCurrentStep(currentStep + 1); }; + const validateName = (data) => { + if(!data) { + alert("OBSS!!! Please write Firstname and Lastname") + return false + } else { + return true + } + } + + const validateUsername = (data) => { + if(data.username.length < 7) { + alert("Username is too short. Input must be at least 7 characters long") + return false + } else { + return true + } + } + + const validateMobile = (data) => { + if(data.length < 8) { + alert("Mobile number is too short. Input must be at least 8 characters long") + return false + } else { + return true + } + } + + return ( {header} @@ -33,11 +61,31 @@ const Stepper = ({ header, children, onComplete }) => {
); diff --git a/src/pages/welcome/index.js b/src/pages/welcome/index.js index 377418ad..4b660ff6 100644 --- a/src/pages/welcome/index.js +++ b/src/pages/welcome/index.js @@ -25,9 +25,9 @@ const Welcome = () => { ...profile, [name]: value }); + }; - const onComplete = () => { onCreateProfile(profile.firstName, profile.lastName, profile.username, profile.githubUsername, profile.mobile, profile.bio); }; @@ -39,7 +39,7 @@ const Welcome = () => {

Create your profile to get started

- } onComplete={onComplete}> + } onComplete={onComplete}> From f08502bfcd0e7a486b464a1f10a27029bd0803a8 Mon Sep 17 00:00:00 2001 From: Moueed Ali Date: Thu, 4 Sep 2025 19:27:29 +0200 Subject: [PATCH 11/59] feat/added feature for email and password check against database for signup and login --- src/pages/login/index.js | 11 ++++++++++- src/pages/register/index.js | 33 +++++++++++---------------------- src/service/apiClient.js | 6 ++++++ 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/pages/login/index.js b/src/pages/login/index.js index 08df7d5a..1dcb7382 100644 --- a/src/pages/login/index.js +++ b/src/pages/login/index.js @@ -36,7 +36,16 @@ const Login = () => { ); - } else { + } else { return (
{label && } diff --git a/src/context/form.js b/src/context/form.js new file mode 100644 index 00000000..c602786e --- /dev/null +++ b/src/context/form.js @@ -0,0 +1,15 @@ +import React, { createContext, useContext, useState } from 'react'; + +const FormContext = createContext(); + +export const FormProvider = ({ children }) => { + const [formData, setFormData] = useState({ email: '', password: '' }); + + return ( + + {children} + + ); +}; + +export const useFormData = () => useContext(FormContext); diff --git a/src/pages/welcome/index.js b/src/pages/welcome/index.js index 4b660ff6..aaf3eb2a 100644 --- a/src/pages/welcome/index.js +++ b/src/pages/welcome/index.js @@ -5,9 +5,11 @@ import StepOne from './stepOne'; import StepTwo from './stepTwo'; import StepFour from './stepFour'; import './style.css'; +import { useFormData } from '../../context/form'; const Welcome = () => { const { onCreateProfile } = useAuth(); + const { formData } = useFormData(); const [profile, setProfile] = useState({ firstName: '', @@ -25,11 +27,17 @@ const Welcome = () => { ...profile, [name]: value }); - }; - + const onComplete = () => { - onCreateProfile(profile.firstName, profile.lastName, profile.username, profile.githubUsername, profile.mobile, profile.bio); + onCreateProfile( + profile.firstName, + profile.lastName, + profile.username, + profile.mobile, + profile.githubUsername, + profile.bio + ); }; return ( @@ -41,7 +49,7 @@ const Welcome = () => { } onComplete={onComplete}> - + diff --git a/src/pages/welcome/stepTwo/index.js b/src/pages/welcome/stepTwo/index.js index 763fc391..c4f66c35 100644 --- a/src/pages/welcome/stepTwo/index.js +++ b/src/pages/welcome/stepTwo/index.js @@ -3,7 +3,7 @@ import Form from '../../../components/form'; import NumberInput from '../../../components/form/numberInput'; import TextInput from '../../../components/form/textInput'; -const StepTwo = ({ data, setData }) => { +const StepTwo = ({ data, setData, formData }) => { return ( <>
@@ -12,12 +12,12 @@ const StepTwo = ({ data, setData }) => {
{ required /> + value={formData.password} + placeholder={formData.password} + name="password" + label={'Password *'} + type={'password'} + /> + {console.log(formData.password)}

*Required

From 2a8d2c7576ac6e2b82fb26b7799cd49d9893bbb2 Mon Sep 17 00:00:00 2001 From: Linda Do Date: Tue, 9 Sep 2025 15:50:05 +0200 Subject: [PATCH 14/59] fixed missing steps from merge --- src/pages/welcome/index.js | 18 +++++++++-- src/pages/welcome/stepThree/index.js | 45 ++++++++++----------------- src/pages/welcome/stepTwo/index.js | 46 ++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 31 deletions(-) create mode 100644 src/pages/welcome/stepTwo/index.js diff --git a/src/pages/welcome/index.js b/src/pages/welcome/index.js index 1cae5cae..34f44d0a 100644 --- a/src/pages/welcome/index.js +++ b/src/pages/welcome/index.js @@ -18,7 +18,12 @@ const Welcome = () => { username: '', githubUsername: '', mobile: '', - bio: '' + bio: '', + role: '', + specialism: '', + cohort: '', + startDate: '', + endDate: '' }); const onChange = (event) => { @@ -37,10 +42,19 @@ const Welcome = () => { profile.username, profile.mobile, profile.githubUsername, - profile.bio + profile.bio, + profile.role, + profile.specialism, + profile.cohort, + profile.startDate, + profile.endDate ); }; + + + + return (
diff --git a/src/pages/welcome/stepThree/index.js b/src/pages/welcome/stepThree/index.js index 8d63811e..ec5b8da4 100644 --- a/src/pages/welcome/stepThree/index.js +++ b/src/pages/welcome/stepThree/index.js @@ -1,17 +1,9 @@ -import { useState } from 'react'; import Form from '../../../components/form'; import TextInput from '../../../components/form/textInput'; -import DropdownMenu from '../../../components/dropdown'; -import { getRoles } from '@testing-library/react'; const StepThree = ({ data, setData }) => { - const [selectedRole, setSelectedRole] = useState(""); - const [selectedSpecialism, setSelectedSpecialism] = useState(""); - const [selectedCohort, setSelectedCohort] = useState(""); - const cohorts = [{value:"c1", label:"c1"}, {value:"c2", label:"c2"}]; - const specialisms = [{value:"s1", label:"s1"}, {value:"s2", label:"s2"}]; return ( <>
@@ -19,35 +11,30 @@ const StepThree = ({ data, setData }) => {
- - -

*Required

diff --git a/src/pages/welcome/stepTwo/index.js b/src/pages/welcome/stepTwo/index.js new file mode 100644 index 00000000..2351e44e --- /dev/null +++ b/src/pages/welcome/stepTwo/index.js @@ -0,0 +1,46 @@ + +import Form from '../../../components/form'; +import NumberInput from '../../../components/form/numberInput'; +import TextInput from '../../../components/form/textInput'; + +const StepTwo = ({ data, setData, formData }) => { + return ( + <> +
+

Basic info

+
+ +
+ + + + {console.log(formData.password)} + +

*Required

+
+ + + ); +}; + +export default StepTwo; \ No newline at end of file From 98b4a9f32383213ad06a351c1e25c6523b46e4b4 Mon Sep 17 00:00:00 2001 From: Linda Do Date: Wed, 10 Sep 2025 13:56:30 +0200 Subject: [PATCH 15/59] Upload profile picture ok --- src/pages/welcome/index.js | 22 ++++++++-- src/pages/welcome/stepOne/index.js | 70 ++++++++++++++++++++++++++++-- src/pages/welcome/style.css | 20 ++++++++- 3 files changed, 105 insertions(+), 7 deletions(-) diff --git a/src/pages/welcome/index.js b/src/pages/welcome/index.js index 34f44d0a..838e37b5 100644 --- a/src/pages/welcome/index.js +++ b/src/pages/welcome/index.js @@ -23,7 +23,8 @@ const Welcome = () => { specialism: '', cohort: '', startDate: '', - endDate: '' + endDate: '', + photo: '' }); const onChange = (event) => { @@ -47,11 +48,26 @@ const Welcome = () => { profile.specialism, profile.cohort, profile.startDate, - profile.endDate + profile.endDate, + profile.photo ); }; + const handleFileChange = (event, close) => { + + const file = event.target.files[0]; + if (file) { + const url = URL.createObjectURL(file) + setProfile(prevProfile => ({ + ...prevProfile, + photo: url + })); + close() + console.log("profile:" + profile.photo) + } + } + @@ -63,7 +79,7 @@ const Welcome = () => {
} onComplete={onComplete}> - + diff --git a/src/pages/welcome/stepOne/index.js b/src/pages/welcome/stepOne/index.js index 5518d4a1..d07a608e 100644 --- a/src/pages/welcome/stepOne/index.js +++ b/src/pages/welcome/stepOne/index.js @@ -1,8 +1,17 @@ +import Popup from 'reactjs-popup'; import ProfileIcon from '../../../assets/icons/profileIcon'; + import Form from '../../../components/form'; import TextInput from '../../../components/form/textInput'; +import Card from '../../../components/card'; + + + + -const StepOne = ({ data, setData }) => { + + +const StepOne = ({ data, setData, handleFileChange }) => { return ( <>
@@ -11,10 +20,65 @@ const StepOne = ({ data, setData }) => {

Photo

+
- -

Add headshot

+ {data.photo ? ( + profile photo + ) : ( )} + + {data.photo ? ( + Replace headshot} modal> + {close => ( + +
+

Upload Photo

+

Choose a file to upload your headshot

+
+ + handleFileChange(e, close)}/> + +
+
+
+ )} +
) : ( + Add headshot} modal> + {close => ( + +
+

Upload Photo

+

Choose a file to upload your headshot

+
+ + handleFileChange(e, close)}/> + +
+ +
+
+ )} +
+ )}
+

Please upload a valid image file

diff --git a/src/pages/welcome/style.css b/src/pages/welcome/style.css index 9f0647cb..798591f7 100644 --- a/src/pages/welcome/style.css +++ b/src/pages/welcome/style.css @@ -55,4 +55,22 @@ .bio-heading { font-size: 25px; -} \ No newline at end of file +} + +.addHeadshot { + height: 55px; + color:#64648c; + display: flex; +} + + + +.upload-label { + background-color: var(--color-blue); + color: white; + padding: 14px 24px; + border-radius: 4px; + cursor: pointer; + text-align: center; + font-size: 20px; +} From 17837c53ab60562107dd47d865feeed5dcfdd6cb Mon Sep 17 00:00:00 2001 From: Linda Do Date: Wed, 10 Sep 2025 13:57:35 +0200 Subject: [PATCH 16/59] missing form context --- src/App.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/App.js b/src/App.js index 136c3a15..5c2221ed 100644 --- a/src/App.js +++ b/src/App.js @@ -8,11 +8,13 @@ import Verification from './pages/verification'; import { AuthProvider, ProtectedRoute } from './context/auth'; import { ModalProvider } from './context/modal'; import Welcome from './pages/welcome'; +import { FormProvider } from './context/form'; const App = () => { return ( <> + } /> @@ -38,6 +40,7 @@ const App = () => { /> + ); From 0ddf2e0b10c66053545dec3f8d8ef499349ccdcc Mon Sep 17 00:00:00 2001 From: Linda Do Date: Wed, 10 Sep 2025 14:03:48 +0200 Subject: [PATCH 17/59] refactor --- src/pages/welcome/stepOne/index.js | 79 ++++++++++++------------------ 1 file changed, 32 insertions(+), 47 deletions(-) diff --git a/src/pages/welcome/stepOne/index.js b/src/pages/welcome/stepOne/index.js index d07a608e..0d5daaff 100644 --- a/src/pages/welcome/stepOne/index.js +++ b/src/pages/welcome/stepOne/index.js @@ -29,54 +29,39 @@ const StepOne = ({ data, setData, handleFileChange }) => { className="welcome-form-profileimg-input" style={{ width: '80px', marginTop: '10px'}}/> ) : ( )} + + {data.photo ? "Replace headshot" : "Add headshot"} + } modal> + {close => ( + +
+

+ {data.photo ? "Replace Photo" : "Upload Photo"} +

+

Choose a file to upload your headshot

+ +
+ + + handleFileChange(e, close)} + /> + +
+
+
+ )} +
- {data.photo ? ( - Replace headshot} modal> - {close => ( - -
-

Upload Photo

-

Choose a file to upload your headshot

-
- - handleFileChange(e, close)}/> - -
-
-
- )} -
) : ( - Add headshot} modal> - {close => ( - -
-

Upload Photo

-

Choose a file to upload your headshot

-
- - handleFileChange(e, close)}/> - -
- -
-
- )} -
- )}

Please upload a valid image file

From 1c8613bb7eabd1c82a05697e8751d02f0c689234 Mon Sep 17 00:00:00 2001 From: Marie Helene Hansen Date: Wed, 10 Sep 2025 14:36:24 +0200 Subject: [PATCH 18/59] add file structure and route for Student View Cohort page --- src/App.js | 10 +++++++++- src/components/navigation/index.js | 2 +- src/context/auth.js | 2 +- src/pages/cohort/cohort.css | 0 src/pages/cohort/exercises/index.js | 7 +++++++ src/pages/cohort/index.js | 7 +++++++ src/pages/cohort/students/index.js | 7 +++++++ src/pages/cohort/teachers/index.js | 7 +++++++ src/pages/register/index.js | 2 +- 9 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 src/pages/cohort/cohort.css create mode 100644 src/pages/cohort/exercises/index.js create mode 100644 src/pages/cohort/index.js create mode 100644 src/pages/cohort/students/index.js create mode 100644 src/pages/cohort/teachers/index.js diff --git a/src/App.js b/src/App.js index 136c3a15..46f9b549 100644 --- a/src/App.js +++ b/src/App.js @@ -8,6 +8,7 @@ import Verification from './pages/verification'; import { AuthProvider, ProtectedRoute } from './context/auth'; import { ModalProvider } from './context/modal'; import Welcome from './pages/welcome'; +import Cohort from './pages/cohort'; const App = () => { return ( @@ -19,7 +20,6 @@ const App = () => { } /> } /> } /> - { } /> + + + + } + /> diff --git a/src/components/navigation/index.js b/src/components/navigation/index.js index b31393a8..10eec500 100644 --- a/src/components/navigation/index.js +++ b/src/components/navigation/index.js @@ -28,7 +28,7 @@ const Navigation = () => {
  • - +

    Cohort

    diff --git a/src/context/auth.js b/src/context/auth.js index 47cd66c9..a64a46ab 100644 --- a/src/context/auth.js +++ b/src/context/auth.js @@ -19,7 +19,7 @@ const AuthProvider = ({ children }) => { useEffect(() => { const storedToken = localStorage.getItem('token'); - if (storedToken) { + if (storedToken && !token) { setToken(storedToken); navigate(location.state?.from?.pathname || '/'); } diff --git a/src/pages/cohort/cohort.css b/src/pages/cohort/cohort.css new file mode 100644 index 00000000..e69de29b diff --git a/src/pages/cohort/exercises/index.js b/src/pages/cohort/exercises/index.js new file mode 100644 index 00000000..d3dc4109 --- /dev/null +++ b/src/pages/cohort/exercises/index.js @@ -0,0 +1,7 @@ +function Exercises() { + return ( + <> + ) +} + +export default Exercises; \ No newline at end of file diff --git a/src/pages/cohort/index.js b/src/pages/cohort/index.js new file mode 100644 index 00000000..6093e0b4 --- /dev/null +++ b/src/pages/cohort/index.js @@ -0,0 +1,7 @@ +const Cohort = () => { + return ( +

    + ) +} + +export default Cohort; \ No newline at end of file diff --git a/src/pages/cohort/students/index.js b/src/pages/cohort/students/index.js new file mode 100644 index 00000000..1a0b5886 --- /dev/null +++ b/src/pages/cohort/students/index.js @@ -0,0 +1,7 @@ +function Students() { + return ( + <> + ) +} + +export default Students; \ No newline at end of file diff --git a/src/pages/cohort/teachers/index.js b/src/pages/cohort/teachers/index.js new file mode 100644 index 00000000..7c0e3373 --- /dev/null +++ b/src/pages/cohort/teachers/index.js @@ -0,0 +1,7 @@ +function Teachers() { + return ( + <> + ) +} + +export default Teachers; \ No newline at end of file diff --git a/src/pages/register/index.js b/src/pages/register/index.js index 14d3bac5..c434a916 100644 --- a/src/pages/register/index.js +++ b/src/pages/register/index.js @@ -16,7 +16,7 @@ const Register = () => { }; const validateEmail = (email) => { - const mailFormat = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/; + const mailFormat = /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/; if (email.match(mailFormat)) { return true; } From 9bebb8aa940db5bf784e2716328a6964631eb0b3 Mon Sep 17 00:00:00 2001 From: Linda Do Date: Thu, 11 Sep 2025 09:33:19 +0200 Subject: [PATCH 19/59] fixed issues from merge --- package-lock.json | 14 ++++++++++++++ package.json | 1 + src/components/form/textInput/index.js | 9 +++++---- src/pages/register/index.js | 4 ++-- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index efeadd9c..d9caa348 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "react-password-checklist": "^1.8.1", "react-router-dom": "^6.8.0", "react-scripts": "5.0.1", + "reactjs-popup": "^2.0.6", "web-vitals": "^3.1.1" }, "devDependencies": { @@ -15094,6 +15095,19 @@ } } }, + "node_modules/reactjs-popup": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/reactjs-popup/-/reactjs-popup-2.0.6.tgz", + "integrity": "sha512-A+tt+x9wdgZiZjv0e2WzYLD3IfFwJALaRaqwrCSXGjo0iQdsry/EtBEbQXRSmQs7cHmOi5eytCiSlOm8k4C+dg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index 320f92bc..1825e548 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "react-password-checklist": "^1.8.1", "react-router-dom": "^6.8.0", "react-scripts": "5.0.1", + "reactjs-popup": "^2.0.6", "web-vitals": "^3.1.1" }, "scripts": { diff --git a/src/components/form/textInput/index.js b/src/components/form/textInput/index.js index 9ae3516b..06d7feb5 100644 --- a/src/components/form/textInput/index.js +++ b/src/components/form/textInput/index.js @@ -2,27 +2,28 @@ import { useState } from 'react'; const TextInput = ({ value, onChange, name, label, icon, type = 'text', placeholder }) => { const [showpassword, setShowpassword] = useState(false); - const [typ, setTyp] = useState(type); + const [input, setInput] = useState(value); if (type === 'password') { return (
    { onChange(e); + setInput(e.target.value) }} /> + {showpassword && } + + ) } diff --git a/src/pages/cohort/index.js b/src/pages/cohort/index.js index 6093e0b4..c26be881 100644 --- a/src/pages/cohort/index.js +++ b/src/pages/cohort/index.js @@ -1,6 +1,15 @@ +import Exercises from "./exercises"; + const Cohort = () => { return ( -

    + <> +
    + +
    + + ) } From ee6799a4373d964ded0bd02c9c0aed19f3e725e2 Mon Sep 17 00:00:00 2001 From: Isabell Date: Thu, 11 Sep 2025 11:04:57 +0200 Subject: [PATCH 22/59] Added component for view all teachers --- src/App.css | 5 ++++ src/components/profile-icon/index.js | 24 ++++++++++++++++++ src/components/profile-icon/style.css | 35 +++++++++++++++++++++++++++ src/pages/cohort/index.js | 12 ++++++--- src/pages/cohort/teachers/index.js | 21 +++++++++++++--- src/pages/cohort/teachers/style.css | 8 ++++++ 6 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 src/components/profile-icon/index.js create mode 100644 src/components/profile-icon/style.css create mode 100644 src/pages/cohort/teachers/style.css diff --git a/src/App.css b/src/App.css index ec62bd1a..1222108c 100644 --- a/src/App.css +++ b/src/App.css @@ -14,3 +14,8 @@ .ReactModal__Html--open { overflow: hidden; } + +.border-line { + border-bottom: 1px solid var(--color-blue5); + padding: 20px 10px; +} diff --git a/src/components/profile-icon/index.js b/src/components/profile-icon/index.js new file mode 100644 index 00000000..d09ee716 --- /dev/null +++ b/src/components/profile-icon/index.js @@ -0,0 +1,24 @@ +import './style.css'; + +const UserIcon = ({initials, name, role}) => { + + return ( +
    +
    +
    +

    {initials}

    +
    +
    +
    +

    {name}

    +

    {role}

    +
    +
    +

    ...

    +
    +
    + + ); +} + +export default UserIcon; \ No newline at end of file diff --git a/src/components/profile-icon/style.css b/src/components/profile-icon/style.css new file mode 100644 index 00000000..52767713 --- /dev/null +++ b/src/components/profile-icon/style.css @@ -0,0 +1,35 @@ +.user { + display: flex; + flex-direction: row; + align-items: center; + gap: 12px; + width: 100%; + padding: 20px 10px; +} + +/* sirkel */ +.profile-circle{ + min-width: 40px; + height: 40px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + background: #4cc0e5; +} + + +.user-name { + margin: 0; + white-space: nowrap; /* hindrer at navnet brytes om du vil */ + padding-top: 4px; + font-weight: 600; + font-size: 1.1rem; +} + +/* tre prikker helt til høyre */ +.edit-name { + margin-left: auto; /* skyver dette elementet til høyre */ +} + + diff --git a/src/pages/cohort/index.js b/src/pages/cohort/index.js index 6093e0b4..2763ded6 100644 --- a/src/pages/cohort/index.js +++ b/src/pages/cohort/index.js @@ -1,7 +1,11 @@ +import Teachers from './teachers'; + const Cohort = () => { - return ( -

    - ) + return( + <> + + + ); } -export default Cohort; \ No newline at end of file +export default Cohort; diff --git a/src/pages/cohort/teachers/index.js b/src/pages/cohort/teachers/index.js index 7c0e3373..b5c04927 100644 --- a/src/pages/cohort/teachers/index.js +++ b/src/pages/cohort/teachers/index.js @@ -1,7 +1,20 @@ -function Teachers() { +import Card from "../../../components/card"; +import './style.css'; +import UserIcon from "../../../components/profile-icon"; + +const Teachers = () => { + return ( - <> - ) + <> + +

    Teachers

    +
    + + +
    +
    + + ); } -export default Teachers; \ No newline at end of file +export default Teachers; diff --git a/src/pages/cohort/teachers/style.css b/src/pages/cohort/teachers/style.css new file mode 100644 index 00000000..9323386a --- /dev/null +++ b/src/pages/cohort/teachers/style.css @@ -0,0 +1,8 @@ +.card { + background: white; + padding: 24px; + border-radius: 8px; + width: 50%; + margin-bottom: 25px; + border: 1px #e6ebf5 solid; +} \ No newline at end of file From afbbbaafa3a76c603ead5155f253c685f720590b Mon Sep 17 00:00:00 2001 From: Moueed Ali Date: Thu, 11 Sep 2025 11:15:48 +0200 Subject: [PATCH 23/59] fix/fixed issue from Daves last comment --- src/pages/cohort/exercises/exercises.css | 23 ++++++++++++++++++++-- src/pages/cohort/exercises/index.js | 25 ++++++++++++++++++------ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/pages/cohort/exercises/exercises.css b/src/pages/cohort/exercises/exercises.css index 37f3357f..e8764745 100644 --- a/src/pages/cohort/exercises/exercises.css +++ b/src/pages/cohort/exercises/exercises.css @@ -1,8 +1,27 @@ -.paragraph-text { +.value { + color: var(--color-blue1); + margin-bottom: 15px; +} + +.label { color: var(--color-blue1); margin-bottom: 15px; } .see-more-button { background-color: var(--color-blue5); -} \ No newline at end of file +} + +.exercise-row { + display: flex; + justify-content: space-between; + margin-bottom: 8px; +} + +.label { + font-weight: 500; +} + +.value { + color: var(--color-blue1); +} diff --git a/src/pages/cohort/exercises/index.js b/src/pages/cohort/exercises/index.js index 569953c7..e1b6a573 100644 --- a/src/pages/cohort/exercises/index.js +++ b/src/pages/cohort/exercises/index.js @@ -5,12 +5,25 @@ const Exercises = () => { return ( <> -

    My Exercises

    -
    -

    Modules:     2/7 completed

    -

    Units:          4/10 completed

    -

    Exercise:     34/58 completed

    - +

    My Exercises

    +
    + +
    + Modules: + 2/7 completed +
    + +
    + Units: + 4/10 completed +
    + +
    + Exercise: + 34/58 completed +
    + +
    ) From 3e710c1842660ac9eec820db57fc02b5b7614609 Mon Sep 17 00:00:00 2001 From: Isabell Date: Thu, 11 Sep 2025 13:55:31 +0200 Subject: [PATCH 24/59] Added function to three dots --- src/components/profile-icon/index.js | 25 +++++++++++----- src/components/profile-icon/style.css | 43 +++++++++++++++------------ src/components/seeProfile/index.js | 37 +++++++++++++++++++++++ src/components/seeProfile/style.css | 5 ++++ src/pages/cohort/teachers/index.js | 3 +- src/pages/welcome/stepOne/index.js | 5 ---- 6 files changed, 84 insertions(+), 34 deletions(-) create mode 100644 src/components/seeProfile/index.js create mode 100644 src/components/seeProfile/style.css diff --git a/src/components/profile-icon/index.js b/src/components/profile-icon/index.js index d09ee716..02eaf3cf 100644 --- a/src/components/profile-icon/index.js +++ b/src/components/profile-icon/index.js @@ -1,6 +1,8 @@ import './style.css'; +import SeeProfile from '../seeProfile'; +import Popup from 'reactjs-popup'; -const UserIcon = ({initials, name, role}) => { +const UserIcon = ({initials, firstname, lastname, role}) => { return (
    @@ -10,15 +12,22 @@ const UserIcon = ({initials, name, role}) => {
    -

    {name}

    +

    {firstname} {lastname}

    {role}

    -
    -

    ...

    -
    -
  • - - ); + +

    ...

    } position="right center" + closeOnDocumentClick + arrow={false}> + + +
    + ) } export default UserIcon; \ No newline at end of file diff --git a/src/components/profile-icon/style.css b/src/components/profile-icon/style.css index 52767713..a0dcbd6c 100644 --- a/src/components/profile-icon/style.css +++ b/src/components/profile-icon/style.css @@ -1,13 +1,17 @@ .user { - display: flex; - flex-direction: row; - align-items: center; - gap: 12px; - width: 100%; - padding: 20px 10px; + display: flex; + align-items: center; + padding: 8px 12px; + gap: 12px; +} + +.user-info { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-width: 0; } -/* sirkel */ .profile-circle{ min-width: 40px; height: 40px; @@ -20,16 +24,17 @@ .user-name { - margin: 0; - white-space: nowrap; /* hindrer at navnet brytes om du vil */ - padding-top: 4px; - font-weight: 600; - font-size: 1.1rem; -} - -/* tre prikker helt til høyre */ -.edit-name { - margin-left: auto; /* skyver dette elementet til høyre */ -} - + margin: 0; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.pop-up { + position: absolute; + top: 100%; /* under .edit-icon */ + right: 0; /* høyrejustert med ikonet */ + margin-top: 8px; + z-index: 100; +} \ No newline at end of file diff --git a/src/components/seeProfile/index.js b/src/components/seeProfile/index.js new file mode 100644 index 00000000..bc8888df --- /dev/null +++ b/src/components/seeProfile/index.js @@ -0,0 +1,37 @@ +import Card from '../card'; +import './style.css'; +import { NavLink } from 'react-router-dom'; +import ProfileIcon from '../../assets/icons/profileIcon'; + +const SeeProfile = ({initials, firstname, lastname, role}) => { + + return ( +
    + +
    +
    +

    {initials}

    +
    + +
    +

    {firstname} {lastname}

    + {role}, Cohort 3 +
    +
    + +
    +
      +
    • + +

      Profile

      +
      +
    • +
    +
    +
    +
    + ) + +} + +export default SeeProfile; \ No newline at end of file diff --git a/src/components/seeProfile/style.css b/src/components/seeProfile/style.css new file mode 100644 index 00000000..0b4797a9 --- /dev/null +++ b/src/components/seeProfile/style.css @@ -0,0 +1,5 @@ + + +.card { + width: 450px; +} \ No newline at end of file diff --git a/src/pages/cohort/teachers/index.js b/src/pages/cohort/teachers/index.js index b5c04927..ea621275 100644 --- a/src/pages/cohort/teachers/index.js +++ b/src/pages/cohort/teachers/index.js @@ -9,8 +9,7 @@ const Teachers = () => {

    Teachers

    - - +
    diff --git a/src/pages/welcome/stepOne/index.js b/src/pages/welcome/stepOne/index.js index 0d5daaff..bc39174b 100644 --- a/src/pages/welcome/stepOne/index.js +++ b/src/pages/welcome/stepOne/index.js @@ -6,11 +6,6 @@ import TextInput from '../../../components/form/textInput'; import Card from '../../../components/card'; - - - - - const StepOne = ({ data, setData, handleFileChange }) => { return ( <> From 61fa1deb6301efc942b2e1e98499cb4c4585fb68 Mon Sep 17 00:00:00 2001 From: Linda Do Date: Fri, 12 Sep 2025 10:02:23 +0200 Subject: [PATCH 25/59] fixed issues with submit profile --- src/components/posts/index.js | 2 +- src/components/stepper/index.js | 9 ++++----- src/context/auth.js | 8 +++++--- src/pages/welcome/index.js | 26 +++++++++++++------------- src/pages/welcome/stepOne/index.js | 12 ++++++------ src/pages/welcome/stepThree/index.js | 4 ++-- src/pages/welcome/stepTwo/index.js | 2 -- src/service/apiClient.js | 10 ++++++++-- src/service/mockData.js | 12 ++++++------ 9 files changed, 45 insertions(+), 40 deletions(-) diff --git a/src/components/posts/index.js b/src/components/posts/index.js index 79756c41..675ca64b 100644 --- a/src/components/posts/index.js +++ b/src/components/posts/index.js @@ -15,7 +15,7 @@ const Posts = () => { return ( { const validateName = (data) => { if(!data) { - alert("OBSS!!! Please write Firstname and Lastname") + alert("OBSS!!! Please write first_name and last_name") return false } else { return true } } - + const validateUsername = (data) => { if(data.username.length < 7) { alert("Username is too short. Input must be at least 7 characters long") @@ -49,7 +49,6 @@ const Stepper = ({ header, children, onComplete, data }) => { } } - return ( {header} @@ -66,7 +65,7 @@ const Stepper = ({ header, children, onComplete, data }) => { text={currentStep === children.length - 1 ? 'Submit' : 'Next'} classes="blue" onClick={() => { - if (validateName(data.firstName) && validateName(data.lastName) &&validateUsername(data)) { + if (validateName(data.first_name) && validateName(data.last_name) &&validateUsername(data)) { onNextClick(); } }} @@ -91,4 +90,4 @@ const Stepper = ({ header, children, onComplete, data }) => { ); }; -export default Stepper; +export default Stepper; \ No newline at end of file diff --git a/src/context/auth.js b/src/context/auth.js index d1becd97..94f6fe94 100644 --- a/src/context/auth.js +++ b/src/context/auth.js @@ -45,15 +45,17 @@ const AuthProvider = ({ children }) => { const handleRegister = async (email, password) => { const res = await register(email, password); - setToken(res.data.token); + + localStorage.setItem('token', res.data.token); + setToken(res.data.token); navigate('/verification'); }; - const handleCreateProfile = async (firstName, lastName, githubUrl, bio) => { + const handleCreateProfile = async (first_name, last_name, username, github_username, mobile, bio, role, specialism, cohort, start_date, end_date, photo) => { const { userId } = jwt_decode(token); - await createProfile(userId, firstName, lastName, githubUrl, bio); + await createProfile(userId, first_name, last_name, username, github_username, mobile, bio, role, specialism, cohort, start_date, end_date, photo); localStorage.setItem('token', token); navigate('/'); diff --git a/src/pages/welcome/index.js b/src/pages/welcome/index.js index 838e37b5..cd6175ff 100644 --- a/src/pages/welcome/index.js +++ b/src/pages/welcome/index.js @@ -13,17 +13,17 @@ const Welcome = () => { const { formData } = useFormData(); const [profile, setProfile] = useState({ - firstName: '', - lastName: '', + first_name: '', + last_name: '', username: '', - githubUsername: '', + github_username: '', mobile: '', bio: '', - role: '', - specialism: '', - cohort: '', - startDate: '', - endDate: '', + role: 'ROLE_STUDENT', + specialism: 'Software Development', + cohort: 1, + start_date: '2025-09-14', + end_date: '2025-10-15', photo: '' }); @@ -38,17 +38,17 @@ const Welcome = () => { const onComplete = () => { onCreateProfile( - profile.firstName, - profile.lastName, + profile.first_name, + profile.last_name, profile.username, profile.mobile, - profile.githubUsername, + profile.github_username, profile.bio, profile.role, profile.specialism, profile.cohort, - profile.startDate, - profile.endDate, + profile.start_date, + profile.end_date, profile.photo ); }; diff --git a/src/pages/welcome/stepOne/index.js b/src/pages/welcome/stepOne/index.js index 0d5daaff..f2f00bbc 100644 --- a/src/pages/welcome/stepOne/index.js +++ b/src/pages/welcome/stepOne/index.js @@ -69,16 +69,16 @@ const StepOne = ({ data, setData, handleFileChange }) => {
    @@ -92,8 +92,8 @@ const StepOne = ({ data, setData, handleFileChange }) => { /> diff --git a/src/pages/welcome/stepThree/index.js b/src/pages/welcome/stepThree/index.js index ec5b8da4..de8daf58 100644 --- a/src/pages/welcome/stepThree/index.js +++ b/src/pages/welcome/stepThree/index.js @@ -27,12 +27,12 @@ const StepThree = ({ data, setData }) => { value={'Cohort 4'} /> diff --git a/src/pages/welcome/stepTwo/index.js b/src/pages/welcome/stepTwo/index.js index 2351e44e..e6073fb7 100644 --- a/src/pages/welcome/stepTwo/index.js +++ b/src/pages/welcome/stepTwo/index.js @@ -34,8 +34,6 @@ const StepTwo = ({ data, setData, formData }) => { label={'Password *'} type={'password'} /> - {console.log(formData.password)} -

    *Required

    diff --git a/src/service/apiClient.js b/src/service/apiClient.js index df36acac..c5870203 100644 --- a/src/service/apiClient.js +++ b/src/service/apiClient.js @@ -9,8 +9,14 @@ async function register(email, password) { return await login(email, password); } -async function createProfile(userId, firstName, lastName, githubUrl, bio) { - return await patch(`users/${userId}`, { firstName, lastName, githubUrl, bio }); +async function createProfile(userId, first_name, last_name, username, github_username, mobile, bio, role, specialism, cohort, start_date, end_date, photo) { + console.log(userId, first_name, last_name, username, github_username, mobile, bio, role, specialism, cohort, start_date, end_date, photo) + + cohort = parseInt(cohort) + photo = JSON.stringify(photo) + + await post(`profiles`, { userId, first_name, last_name, username, github_username, mobile, bio, role, specialism, cohort, start_date, end_date, photo }); + return await patch(`users/${userId}`, {}) } async function getPosts() { diff --git a/src/service/mockData.js b/src/service/mockData.js index d49e98a4..6b93d5f8 100644 --- a/src/service/mockData.js +++ b/src/service/mockData.js @@ -5,8 +5,8 @@ const user = { email: 'test@email.com', cohortId: 1, role: 'STUDENT', - firstName: 'Joe', - lastName: 'Bloggs', + first_name: 'Joe', + last_name: 'Bloggs', bio: 'Lorem ipsum dolor sit amet.', githubUrl: 'https://github.com/vherus' } @@ -23,8 +23,8 @@ const posts = [ id: 1, cohortId: 1, role: 'STUDENT', - firstName: 'Sam', - lastName: 'Fletcher', + first_name: 'Sam', + last_name: 'Fletcher', bio: 'Lorem ipsum dolor sit amet.', githubUrl: 'https://github.com/vherus' } @@ -39,8 +39,8 @@ const posts = [ id: 2, cohortId: 1, role: 'STUDENT', - firstName: 'Dolor', - lastName: 'Lobortis', + first_name: 'Dolor', + last_name: 'Lobortis', bio: 'Lorem ipsum dolor sit amet.', githubUrl: 'https://github.com/vherus' }, From b84e7d83003eebfe69085117baf5bf01cafe316d Mon Sep 17 00:00:00 2001 From: Marie Helene Hansen Date: Fri, 12 Sep 2025 11:21:01 +0200 Subject: [PATCH 26/59] feat/student page cohort view --- src/pages/cohort/cohort.css | 0 src/pages/cohort/index.js | 10 +++- src/pages/cohort/students/index.js | 48 +++++++++++++++- src/pages/cohort/students/student/index.js | 22 +++++++ src/pages/cohort/students/students.css | 67 ++++++++++++++++++++++ 5 files changed, 145 insertions(+), 2 deletions(-) delete mode 100644 src/pages/cohort/cohort.css create mode 100644 src/pages/cohort/students/student/index.js create mode 100644 src/pages/cohort/students/students.css diff --git a/src/pages/cohort/cohort.css b/src/pages/cohort/cohort.css deleted file mode 100644 index e69de29b..00000000 diff --git a/src/pages/cohort/index.js b/src/pages/cohort/index.js index 6093e0b4..89716318 100644 --- a/src/pages/cohort/index.js +++ b/src/pages/cohort/index.js @@ -1,6 +1,14 @@ +import Students from "./students"; +// import Teachers from "./teachers"; +// import Exercises from "./exercises"; + const Cohort = () => { return ( -

    + <> +
    + +
    + ) } diff --git a/src/pages/cohort/students/index.js b/src/pages/cohort/students/index.js index 1a0b5886..24d39e46 100644 --- a/src/pages/cohort/students/index.js +++ b/src/pages/cohort/students/index.js @@ -1,6 +1,52 @@ +import Card from "../../../components/card"; +import Student from "./student"; +import './students.css'; + function Students() { + const students = [ + { id: 1, name: 'Alice Johnson' }, + { id: 2, name: 'Bob Smith' }, + { id: 3, name: 'Charlie Brown' }, + { id: 4, name: 'Diana Prince' }, + { id: 5, name: 'Ethan Hunt' }, + { id: 6, name: 'Fiona Gallagher' }, + { id: 7, name: 'George Martin' }, + { id: 8, name: 'Hannah Baker' }, + { id: 9, name: 'Ian Fleming' }, + { id: 10, name: 'Jane Doe' } + ]; + + /* + const [students, setStudents] = useState([]); + useEffect(() => { + getStudents().then(setStudents); + }, []); + + // need: backend getStudents: users with role = student + // add getStudents(endpoint, data, auth = true) to apiClient.js + + // in cohort-course-date: getCohortsForStudent() or something, make it a dropdown to select which subject to render!!!! we do NOT want to scroll through lots of students to view the next subject's students + */ return ( - <> + +
    +
    +

    My cohort

    +
    + +
    +

    Software Development, Cohort 4

    + January 2023 - June 2023 +
    + +
    + {students.map((student) => ( + + ))} +
    +
    + +
    ) } diff --git a/src/pages/cohort/students/student/index.js b/src/pages/cohort/students/student/index.js new file mode 100644 index 00000000..9e205510 --- /dev/null +++ b/src/pages/cohort/students/student/index.js @@ -0,0 +1,22 @@ +const Student = ({ icon, name }) => { + + return ( + <> +
    +
    +

    {icon}

    +
    +
    +

    {name}

    +
    +
    + +
    +
    + + ); +}; + +export default Student; diff --git a/src/pages/cohort/students/students.css b/src/pages/cohort/students/students.css new file mode 100644 index 00000000..257243ee --- /dev/null +++ b/src/pages/cohort/students/students.css @@ -0,0 +1,67 @@ +.cohort { + display: grid; + row-gap: 20px; +} + +.cohort-details { + display: grid; + grid-template-columns: 56px auto 48px; + column-gap: 20px; +} + +.cohort-course-date, .student-name { + padding-top: 4px; +} + +.cohort-course-date, p { + font-weight: 600; + font-size: 1.1rem; +} + +.cohort-course-date small { + color: #64648c; +} + +.edit-icon { + border-radius: 50%; + width: 40px; + height: 40px; + background: #f0f5fa; +} + +.edit-icon p { + text-align: center; + font-size: 20px; +} + +.cohort-students-container { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; +} + +.cohort-course { + display: grid; + grid-template-columns: 1fr 1fr; +} + +.cohort-students-container p { + text-align: left; +} + +.student-details { + display: grid; + grid-template-columns: 56px 1fr 48px; + align-items: center; + column-gap: 20px; + padding: 10px; + background: #fff; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); +} + +.border-top { + border-top: 1px solid #ccc; + padding-top: 20px; + padding-bottom: 10px; +} \ No newline at end of file From 95546fdd704b278765dd170891f9959e4f977adf Mon Sep 17 00:00:00 2001 From: Marie Helene Hansen Date: Fri, 12 Sep 2025 12:45:58 +0200 Subject: [PATCH 27/59] change app title and minor fix in students.css --- public/index.html | 2 +- src/pages/cohort/students/students.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/index.html b/public/index.html index aa069f27..b74868f3 100644 --- a/public/index.html +++ b/public/index.html @@ -24,7 +24,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - React App + Cohort Manager diff --git a/src/pages/cohort/students/students.css b/src/pages/cohort/students/students.css index 257243ee..4d1e8ac6 100644 --- a/src/pages/cohort/students/students.css +++ b/src/pages/cohort/students/students.css @@ -13,7 +13,7 @@ padding-top: 4px; } -.cohort-course-date, p { +.cohort-course-date p { font-weight: 600; font-size: 1.1rem; } From a298ae2fc5535867048a456a30eb049319675fe1 Mon Sep 17 00:00:00 2001 From: Marie Helene Hansen Date: Fri, 12 Sep 2025 15:26:35 +0200 Subject: [PATCH 28/59] add cohort manager favicon --- public/faviconsvg.svg | 4 ++++ public/favicontransparent.ico | Bin 0 -> 188582 bytes public/faviconwhite.ico | Bin 0 -> 12806 bytes public/index.html | 2 +- 4 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 public/faviconsvg.svg create mode 100644 public/favicontransparent.ico create mode 100644 public/faviconwhite.ico diff --git a/public/faviconsvg.svg b/public/faviconsvg.svg new file mode 100644 index 00000000..03d77140 --- /dev/null +++ b/public/faviconsvg.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/public/favicontransparent.ico b/public/favicontransparent.ico new file mode 100644 index 0000000000000000000000000000000000000000..d73f75c4f38bfea3a3998598c5bc15286e9f6409 GIT binary patch literal 188582 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

    g03>%&$;F=a4fXhzfl3H$N1d+ZP$}L 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�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 z%669E X6z$L?A-Dps2!fKAR+g%gFbet~g`G3+ literal 0 HcmV?d00001 diff --git a/public/faviconwhite.ico b/public/faviconwhite.ico new file mode 100644 index 0000000000000000000000000000000000000000..b7340a080fccf2bb1407c39d7b6d67aab0dc879a GIT binary patch literal 12806 zcmeI2TTB#J7{?clw-=*E@wE?LnrPAxQ6KtXNEK1=Qd>~tgIK|YYBA9T8m)ZF@$L}p&E_mO=981QiM|t)J2~%rL4aBTk1(Ad&7?_OXEPVA&%uu`o|#&ySdtSl(T;2C8y%;fJy^h~KnHwvV{r2TV#VM+r-9VIkPb+#8R! z#zsY^7e4AEcv);#tEe}jOq(~m)agZAo9G#}FOcP`i+n*{>WD7REUaE_O-&H*Crn%{ zMMbhK+)Ux&pteq9uU^5`VowbwB^K&O~GE?-#P z<@{nYjVi<#vfz5hQx?X!7);zOug911*)gaeu7HLlz zZZj#etX(S^W4}*4COjVy7CzH<#Y4mB_aYv=@FtTY3+78n3E=*r^D$!4lQ_EtrMl}} zRxxlBbki@Li=o%vU{Yj3U1V5TrEFk>S-XYUtb$pBKKkqRWS3-8EvLdD*T(5h=HM>;YIE;*m0K-72{z#QpM*BO_sa zco;_d`yp}LHrf8MBqc>yzhI-GF36&N9?#v6@%DG@z7*2$U4NMgM(cz~r>3eZIJkWI zyfrSi`_a{_>H2C+uZN_tuz7oBxE{u}@QuVom@%7$a*^lj{u_knG}jozI~Fguib8knt}p|TDDA*=NQTFKU!L7+uzO0qxVUz@1s|)gm1F5MNH49{)F%J z-$M0ZxjLpVhxW-QB(mqm1Y~Kdtc19Y8ztin+x^p1r@%Bh$B(|~(j`eIWIVWH#XKgV z;Z#0hEU}@XvN@3%9S!=P9#Kuqe#qo03j;G7jkK@B7rcJF(wq? zbb?I9WK42br{5-8AeGEVagL4iQrC7Cv}At$aO9ro?Ud!t9hWRwF){P z#-7O}Jl$!03hCYFC=+ri8S?&YcqlwCux!{MoBN)yuoqA!t=6SZ<(ZiR6Xwp7@t>e^ zw3b+%g4M!fLj%n-d>>pg_{Cyjb#hlH_8gB#mWGN7P+J%0x~Rd#&C=WJ zTph1mGo}MB#;5X(3?ZLfy_AdP&>_&P#KP)^OgQIu#Z;!iq{M<_clIK%c!PB3l;H&0DLNouNOAHmkSxaL9)*!RH9$PvHSg|RR%Fp;?t>cK2009hQF ss`K*!_hS>tp5@Jp7wP^b`+J~z1KG30eb|M5C&|Ol{_p>PA6zZ)AET`f3IG5A literal 0 HcmV?d00001 diff --git a/public/index.html b/public/index.html index b74868f3..dc1bfc18 100644 --- a/public/index.html +++ b/public/index.html @@ -2,7 +2,7 @@ - + Date: Fri, 12 Sep 2025 15:58:43 +0200 Subject: [PATCH 29/59] fixed so that we see posts --- src/components/posts/index.js | 21 ++++++++++++++++++--- src/service/apiClient.js | 1 + 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/components/posts/index.js b/src/components/posts/index.js index 675ca64b..f68a3450 100644 --- a/src/components/posts/index.js +++ b/src/components/posts/index.js @@ -6,17 +6,32 @@ const Posts = () => { const [posts, setPosts] = useState([]); useEffect(() => { - getPosts().then(setPosts); + async function fetchPosts() { + try { + const posts = await getPosts(); + setPosts(posts); + } catch (error) { + console.error('Error fetching posts:', error); + setPosts([]); + } + } + fetchPosts(); }, []); + return ( <> {posts.map((post) => { + // Handle missing author gracefully + const authorName = post.author + ? `${post.author.first_name || 'Unknown'} ${post.author.last_name || 'User'}` + : 'Unknown User'; + return ( diff --git a/src/service/apiClient.js b/src/service/apiClient.js index c5870203..bd58f1e1 100644 --- a/src/service/apiClient.js +++ b/src/service/apiClient.js @@ -21,6 +21,7 @@ async function createProfile(userId, first_name, last_name, username, github_use async function getPosts() { const res = await get('posts'); + console.log(res.data.posts + " <- this is from apiClient.js"); return res.data.posts; } From a25a2d01dfbacd5c507dcda6d2fde913e38aa634 Mon Sep 17 00:00:00 2001 From: Linda Do Date: Mon, 15 Sep 2025 14:59:46 +0200 Subject: [PATCH 30/59] added-azure-logo-removed-the-rest --- public/microsoft-azure-logo.svg | 15 +++++ src/components/card/style.css | 2 +- src/components/profile-icon/index.js | 2 +- src/components/socialLinks/index.js | 86 +++++++++------------------- 4 files changed, 43 insertions(+), 62 deletions(-) create mode 100644 public/microsoft-azure-logo.svg diff --git a/public/microsoft-azure-logo.svg b/public/microsoft-azure-logo.svg new file mode 100644 index 00000000..d321d23a --- /dev/null +++ b/public/microsoft-azure-logo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/card/style.css b/src/components/card/style.css index c1f0e8b2..b2e829e2 100644 --- a/src/components/card/style.css +++ b/src/components/card/style.css @@ -2,7 +2,7 @@ background: white; padding: 24px; border-radius: 8px; - width: 100%; + width: 100% !important; margin-bottom: 25px; border: 1px #e6ebf5 solid; } diff --git a/src/components/profile-icon/index.js b/src/components/profile-icon/index.js index 02eaf3cf..c5134bf0 100644 --- a/src/components/profile-icon/index.js +++ b/src/components/profile-icon/index.js @@ -16,7 +16,7 @@ const UserIcon = ({initials, firstname, lastname, role}) => {

    {role}

    -

    ...

    } position="right center" +

    ...

    } position="left center" closeOnDocumentClick arrow={false}> { }} className="socialbutton" > - - - - - - - - - - + + + + ); }; export default SocialLinks; + + + + From eb4b3aa2a4149e2421798fb74460b98b5940d18f Mon Sep 17 00:00:00 2001 From: Moueed Ali Date: Tue, 16 Sep 2025 08:48:56 +0200 Subject: [PATCH 31/59] minor change in welcome/index.js to send correct data to DB --- src/pages/welcome/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/welcome/index.js b/src/pages/welcome/index.js index cd6175ff..0d522d6c 100644 --- a/src/pages/welcome/index.js +++ b/src/pages/welcome/index.js @@ -41,8 +41,8 @@ const Welcome = () => { profile.first_name, profile.last_name, profile.username, - profile.mobile, profile.github_username, + profile.mobile, profile.bio, profile.role, profile.specialism, From 7e28c491abd4f9eb7a16674ec2a4899d92ffc4be Mon Sep 17 00:00:00 2001 From: Linda Do Date: Tue, 16 Sep 2025 09:52:28 +0200 Subject: [PATCH 32/59] updated icons on sidebar for each page --- src/assets/icons/homeIcon.js | 8 +++---- src/assets/icons/homeIconFilled.js | 7 ++++++ src/assets/icons/profileIcon.js | 20 +++------------- src/assets/icons/profileIconFilled.js | 17 ++++++++++++++ src/components/card/style.css | 2 +- src/components/navigation/index.js | 33 ++++++++++++++++++--------- src/components/navigation/style.css | 15 ++++++++++++ 7 files changed, 68 insertions(+), 34 deletions(-) create mode 100644 src/assets/icons/homeIconFilled.js create mode 100644 src/assets/icons/profileIconFilled.js diff --git a/src/assets/icons/homeIcon.js b/src/assets/icons/homeIcon.js index 235fcfdf..a5dc71c6 100644 --- a/src/assets/icons/homeIcon.js +++ b/src/assets/icons/homeIcon.js @@ -1,9 +1,7 @@ -const HomeIcon = ({ colour = '#64648C' }) => { +const HomeIcon = ({colour}) => { return ( - - - - ); + + ); }; export default HomeIcon; diff --git a/src/assets/icons/homeIconFilled.js b/src/assets/icons/homeIconFilled.js new file mode 100644 index 00000000..78c4998a --- /dev/null +++ b/src/assets/icons/homeIconFilled.js @@ -0,0 +1,7 @@ +const HomeIconFilled = ({colour}) => { + return ( + + ); +}; + +export default HomeIconFilled; diff --git a/src/assets/icons/profileIcon.js b/src/assets/icons/profileIcon.js index 9f57f958..462e7410 100644 --- a/src/assets/icons/profileIcon.js +++ b/src/assets/icons/profileIcon.js @@ -1,21 +1,7 @@ -const ProfileIcon = ({ colour = '#64648C', background = 'transparent' }) => { +const ProfileIcon = () => { return ( - - + + ); }; diff --git a/src/assets/icons/profileIconFilled.js b/src/assets/icons/profileIconFilled.js new file mode 100644 index 00000000..3f9fb82f --- /dev/null +++ b/src/assets/icons/profileIconFilled.js @@ -0,0 +1,17 @@ +const ProfileIconFilled = () => { + return ( + + + ); +}; + +export default ProfileIconFilled; + + + + + diff --git a/src/components/card/style.css b/src/components/card/style.css index c1f0e8b2..b2e829e2 100644 --- a/src/components/card/style.css +++ b/src/components/card/style.css @@ -2,7 +2,7 @@ background: white; padding: 24px; border-radius: 8px; - width: 100%; + width: 100% !important; margin-bottom: 25px; border: 1px #e6ebf5 solid; } diff --git a/src/components/navigation/index.js b/src/components/navigation/index.js index 10eec500..b1469ce8 100644 --- a/src/components/navigation/index.js +++ b/src/components/navigation/index.js @@ -1,13 +1,18 @@ import { NavLink } from 'react-router-dom'; import CohortIcon from '../../assets/icons/cohortIcon'; -import HomeIcon from '../../assets/icons/homeIcon'; import ProfileIcon from '../../assets/icons/profileIcon'; import useAuth from '../../hooks/useAuth'; import './style.css'; +import { useState } from 'react'; +import ProfileIconFilled from '../../assets/icons/profileIconFilled'; +import HomeIconFilled from '../../assets/icons/homeIconFilled'; +import HomeIcon from '../../assets/icons/homeIcon'; +import CohortIconFill from '../../assets/icons/cohortIcon-fill'; const Navigation = () => { const { token } = useAuth(); - + const [active, setActive] = useState(1) + if (!token) { return null; } @@ -16,20 +21,26 @@ const Navigation = () => {
    • - - + active === 1 ? "nav-item active" : "nav-item"} + onClick={() => setActive(1)}> + {active === 1 ? () : ()}

      Home

      -
      +
    • - - -

      Profile

      -
      + active === 2 ? "nav-item active" : "nav-item"} + onClick={() => setActive(2)}> + {active === 2 ? () : ()} +

      Profile

      +
    • - - + active === 3 ? "nav-item active" : "nav-item"} + onClick={() => setActive(3)}> + {active === 3 ? () : ()}

      Cohort

    • diff --git a/src/components/navigation/style.css b/src/components/navigation/style.css index 91849135..79ede16f 100644 --- a/src/components/navigation/style.css +++ b/src/components/navigation/style.css @@ -27,3 +27,18 @@ nav svg { nav p { line-height: 24px; } + +.nav-item { + display: flex; + flex-direction: column; + align-items: center; + padding: 10px; + background-color: white; + border-radius: 8px; + text-decoration: none; + transition: background-color 0.3s ease; +} + +.nav-item.active { + background-color: #d3d3d3b2; /* Grå bakgrunn når aktiv */ +} From f91e8baa0916470841daf7c81b3f198bfe92df20 Mon Sep 17 00:00:00 2001 From: Marie Helene Hansen Date: Tue, 16 Sep 2025 10:11:23 +0200 Subject: [PATCH 33/59] fix input fields in welcome components --- src/components/form/textInput/index.js | 4 +++- src/pages/welcome/stepFour/index.js | 1 - src/pages/welcome/stepThree/index.js | 16 ++++++++++------ src/pages/welcome/stepTwo/index.js | 6 ++++-- src/pages/welcome/style.css | 2 +- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/components/form/textInput/index.js b/src/components/form/textInput/index.js index 06d7feb5..5a918c03 100644 --- a/src/components/form/textInput/index.js +++ b/src/components/form/textInput/index.js @@ -1,6 +1,6 @@ import { useState } from 'react'; -const TextInput = ({ value, onChange, name, label, icon, type = 'text', placeholder }) => { +const TextInput = ({ value, onChange, name, label, icon, type = 'text', placeholder, readOnly = false }) => { const [showpassword, setShowpassword] = useState(false); const [input, setInput] = useState(value); if (type === 'password') { @@ -16,6 +16,7 @@ const TextInput = ({ value, onChange, name, label, icon, type = 'text', placehol onChange(e); setInput(e.target.value) }} + readOnly={readOnly} /> {showpassword && } @@ -70,17 +103,23 @@ const Post = ({ post }) => {
      - -
      -

      {!post.likes && 'Be the first to like this'}

      +

      + {likeCount === 0 ? 'Be the first to like this' : `${likeCount} ${likeCount === 1 ? 'like' : 'likes'}`} +

      {comments.length > 2 && ( @@ -88,18 +127,22 @@ const Post = ({ post }) => { )}
      - {comments.map((comment, idx) => ( - - ))} - + {comments.map((comment, idx) => { + const commentAuthorName = comment.user?.profile + ? `${comment.user.profile.firstName || 'Unknown'} ${comment.user.profile.lastName || 'User'}` + : 'Unknown User'; + + return ( + + ); + })} +
      diff --git a/src/components/post/style.css b/src/components/post/style.css index ec295b44..9f3517c2 100644 --- a/src/components/post/style.css +++ b/src/components/post/style.css @@ -68,6 +68,35 @@ color: var(--color-blue1); padding: 8px 12px; border-radius: 999px; + cursor: pointer; + transition: all 0.2s ease; +} + +.pill:hover { + background: #d9e7f4; +} + +.pill svg { + transition: all 0.2s ease; +} + +.pill--animating svg { + animation: heartBounce 0.4s ease-out; +} + +@keyframes heartBounce { + 0% { + transform: translateY(0) scale(1); + } + 30% { + transform: translateY(-3px) scale(1.1); + } + 60% { + transform: translateY(1px) scale(1.05); + } + 100% { + transform: translateY(0) scale(1); + } } .pill svg { diff --git a/src/components/posts/index.js b/src/components/posts/index.js index 7e093de6..ac3a9d1c 100644 --- a/src/components/posts/index.js +++ b/src/components/posts/index.js @@ -2,16 +2,14 @@ import { useEffect, useState } from 'react'; import Post from '../post'; import { getPosts } from '../../service/apiClient'; -const Posts = () => { +const Posts = ({ onPostAdded }) => { const [posts, setPosts] = useState([]); - - useEffect(() => { async function fetchPosts() { try { const fetchedPosts = await getPosts(); - setPosts(fetchedPosts); + setPosts(fetchedPosts.reverse()); // Reverse fetched posts so newest are first } catch (error) { console.error('Error fetching posts:', error); setPosts([]); @@ -20,12 +18,21 @@ const Posts = () => { fetchPosts(); }, []); + // Expose the function to add a new post + useEffect(() => { + if (onPostAdded) { + onPostAdded.current = (newPost) => { + setPosts(prevPosts => [newPost, ...prevPosts]); + }; + } + }, [onPostAdded]); + return ( <> - {posts.map((post) => ( - - ))} + {posts.map((post) => ( + + ))} ); }; diff --git a/src/components/posts/style.css b/src/components/posts/style.css new file mode 100644 index 00000000..e69de29b diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 4960df6d..028a873b 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useRef } from 'react'; import SearchIcon from '../../assets/icons/searchIcon'; import Button from '../../components/button'; import Card from '../../components/card'; @@ -8,10 +8,17 @@ import Posts from '../../components/posts'; import useModal from '../../hooks/useModal'; import './style.css'; import Cohorts from './cohorts'; +import useAuth from '../../hooks/useAuth'; +import jwtDecode from 'jwt-decode'; const Dashboard = () => { const [searchVal, setSearchVal] = useState(''); const [role, setRole] = useState("teacher") + const onPostAddedRef = useRef(null); + const { token } = useAuth(); + const decodedToken = jwtDecode(token || localStorage.getItem('token')) || {}; + const fullName = `${decodedToken.firstName || decodedToken.first_name || 'Current'} ${decodedToken.lastName || decodedToken.last_name || 'User'}`; + const initials = fullName?.match(/\b(\w)/g)?.join('') || 'NO'; const onChange = (e) => { setRole("teacher") // midlertidig for å unngå kompileringsfeil @@ -24,25 +31,56 @@ const Dashboard = () => { // Create a function to run on user interaction const showModal = () => { // Use setModal to set the header of the modal and the component the modal should render - setModal('Create a post', ); // CreatePostModal is just a standard React component, nothing special + setModal('Create a post', ); // CreatePostModal is just a standard React component, nothing special // Open the modal! openModal(); }; + const handlePostAdded = (newPost) => { + // Call the Posts component's add function + if (onPostAddedRef.current) { + onPostAddedRef.current(newPost); + } + }; + +/* TODO TRIED ADDING CORRECT INITALS TO PROFILE CIRCLE, DIDN'T WORK +useEffect(() => { + async function fetchUser() { + try { + const { userId } = jwt_decode(token || localStorage.getItem('token')) || {}; + if (!userId) { + console.log('Could not determine user. Please log in again.'); + return; + } + const fetchedUser = await get(`users/${userId}`); + setUser(fetchedUser); + } catch (error) { + console.error('Error fetching user:', error); + setUser([]); + } + const authorName = post.user.profile + ? `${post.user.profile.firstName || 'Unknown'} ${post.user.profile.lastName || 'User'}` + : 'Unknown User'; + setUserInitials(authorName.match(/\b(\w)/g)); + } + fetchUser(); + }, []); */ + return ( <>
      -

      AJ

      +

      {initials}

      +
      - +