Compare commits

..

No commits in common. "0c08a4d942cefbcd11b7cbfc9f46040b5913a680" and "ae781652de2f839380c5b2ecdfb53dbead17d107" have entirely different histories.

15 changed files with 561 additions and 979 deletions

15
package-lock.json generated
View File

@ -13,7 +13,6 @@
"axios": "^1.8.1",
"gatsby": "^5.13.3",
"gatsby-plugin-sass": "^6.14.0",
"js-cookie": "^3.0.5",
"react": "^18",
"react-dom": "^18",
"react-redux": "^9.2.0",
@ -11752,15 +11751,6 @@
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -25870,11 +25860,6 @@
"@sideway/pinpoint": "^2.0.0"
}
},
"js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw=="
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",

View File

@ -20,7 +20,6 @@
"axios": "^1.8.1",
"gatsby": "^5.13.3",
"gatsby-plugin-sass": "^6.14.0",
"js-cookie": "^3.0.5",
"react": "^18",
"react-dom": "^18",
"react-redux": "^9.2.0",

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'
import React, { useState } from 'react'
import passwordVisibleImg from '../../images/password-visible.png'
import passwordHiddenImg from '../../images/password-hidden.png'
@ -6,28 +6,43 @@ import passwordHiddenImg from '../../images/password-hidden.png'
/**
*
* @param { [ {}, {}, ...{} ] } inputList - list of dicts with info about input
* @param { [] } refList - react ref objects list for handler validation
* @param { } action - fetch method
*/
export const FormGenerator = ({
inputList,
inputList, refList,
action
}) => {
const handleSubmit = (e) => {
e.preventDefault();
const formData = {};
inputList.forEach((singleInput, index) => {
if (typeof singleInput.type !== 'undefined' && singleInput.ref?.current) {
formData[singleInput.name] = singleInput.ref.current.value;
const handler = async (event) => {
event.preventDefault()
if ( inputList[0].action === 'Async' ) {
await action(refList)
} else if (
inputList[0].action === 'Download'
|| inputList[0].action === 'Upload'
) {
await action()
} else {
for (let i = 0; i < refList.length; i++) {
if (refList[i].current.value === ''
&& inputList[0].action !== 'Update'
|| i === 0
&& refList.length !== 1
) {
refList[i].current.focus()
} else if (i === refList.length - 1) {
await action(refList)
}
}
});
action(formData);
};
}
}
let info
return (
<form onSubmit={handleSubmit} className="form">
<form onSubmit={event => handler(event)} className="form">
{
inputList.map((input, key) => {
@ -89,13 +104,6 @@ export const FormGenerator = ({
key={key}
/>
)
} else if (input.type === 'label') {
return (
<div className="form-field">
<label>{input.name}</label>
<div className="label-value">{input.value}</div>
</div>
)
}
})
}
@ -104,10 +112,10 @@ export const FormGenerator = ({
? <></>
: <button
type='submit'
disabled={!info.allowButtonAction}
className={!info.allowButtonAction ? "button-disabled" : ""}
disabled={ info.allowButtonAction }
className={ info.allowButtonAction === false ? "button-disabled" : "" }
>
{info.button_value}
{ info.button_value }
</button>
}
@ -148,7 +156,6 @@ const TextInputGenerator = ({
} else {
setTextInputValidationInfo("Success")
}
input.validationInfo = textInputValidationInfo
}
return (
@ -160,13 +167,12 @@ const TextInputGenerator = ({
id={input.name + info.action + info.endpoint + 'Input'}
autoComplete='off'
ref={input.ref}
onChange={input.onChange === null ? defaultValidation : input.onChange}
onChange={ input.onChange === null ? defaultValidation : input.onChange}
className={
[ "Empty", "Success"].includes(
input.validationInfo === null ? textInputValidationInfo : input.validationInfo
) ? "" : "input-incorrect"
}
placeholder={input.placeholder === null ? "" : input.placeholder}
/>
<div
className="popup"
@ -232,6 +238,7 @@ const PasswordInputGenerator = ({
<div className="popup-content">
{ input.validationInfo }
</div>
</div>
</div>
)
@ -458,6 +465,7 @@ const UploadInputGenerator = ({
const onLoadFile = async (event) => {
event.preventDefault()
let data = event.target.files[0]
// input.setFile(await toBase64(data))
input.setFile( data )
setDropInfos(data.name, data.size)
}
@ -466,6 +474,7 @@ const UploadInputGenerator = ({
event.preventDefault()
event.persist()
let data = event.dataTransfer.files[0]
// input.setFile(await toBase64(data))
input.setFile( data )
setDropInfos(data.name, data.size)
}

View File

@ -1,106 +1,121 @@
import React, { useState, createRef, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { navigate } from 'gatsby';
import { loginUser } from '../../../redux/asyncThunks/userAuthAsyncThunk';
import { selectAuthError, selectAuthLoading, setError } from '../../../redux/slices/userAuthSlice';
import FormGenerator from '../formGenerator';
import React, { useState, useEffect } from 'react'
const UserLogin = () => {
const dispatch = useDispatch();
const error = useSelector(selectAuthError);
const loading = useSelector(selectAuthLoading);
const [infoMessage, setInfoMessage] = useState('');
const [errorMessage, setErrorMessage] = useState('');
// import { useSelector, useDispatch } from 'react-redux'
const usernameInput = createRef();
const passwordInput = createRef();
// import { userAuthSelector } from '../../../redux/slices/userAuthSlice'
// import userAuthAsyncThunk from '../../../redux/asyncThunks/userAuthAsyncThunk'
const [usernameValidationInfo, setUsernameValidationInfo] = useState("Empty");
const [passwordValidationInfo, setPasswordValidationInfo] = useState("Empty");
import FormGenerator from '../formGenerator'
const [allowButtonAction, setAllowButtonAction] = useState(false);
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,24}$/;
const UserLoginForm = () => {
const usernameInput = React.createRef()
const passwordInput = React.createRef()
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/
const [usernameValidationInfo, setUsernameValidationInfo] = useState("Empty")
const [passwordValidationInfo, setPasswordValidationInfo] = useState("Empty")
const [password, setPassword] = useState("")
const [allowButtonAction, setAllowButtonAction] = useState(false)
const usernameValidation = (event) => {
if (event.target.value === "") {
setUsernameValidationInfo("Login jest wymagany");
setUsernameValidationInfo("Email is required.")
} else if(!emailRegex.test(event.target.value)) {
setUsernameValidationInfo("Please provide correct email")
} else {
setUsernameValidationInfo("Success");
setUsernameValidationInfo("Success")
}
};
}
const passwordValidation = (event) => {
setPassword(event.target.value)
if (event.target.value === "") {
setPasswordValidationInfo("Hasło jest wymagane");
} else if (!passwordRegex.test(event.target.value)) {
setPasswordValidationInfo("Hasło nie spełnia wymagań tej witryny");
setPasswordValidationInfo("Password is required.")
} else if(!passwordRegex.test(event.target.value)) {
setPasswordValidationInfo("Password require:\n - At least 8 characters,\n - At least one uppercase letter,\n - At least one lowercase letter,\n - At least one digit,\n - At least one special character.")
} else {
setPasswordValidationInfo("Success");
setPasswordValidationInfo("Success")
}
};
}
useEffect(() => {
setAllowButtonAction(
usernameValidationInfo === "Success" &&
passwordValidationInfo === "Success"
);
}, [
usernameValidationInfo,
passwordValidationInfo,
]);
setAllowButtonAction(
usernameValidationInfo === "Success"
&& passwordValidationInfo === "Success"
)
}, [
allowButtonAction,
usernameValidationInfo,
passwordValidationInfo
]
)
const inputList = [
// const dispatch = useDispatch()
// const { info } = useSelector( userAuthSelector )
const info = "" // if redux is integrated - delete this line
let refList = [
usernameInput,
passwordInput
]
let inputList = [
{
type: 'info',
action: 'Login',
endpoint: 'auth',
button_value: loading ? 'LOGOWANIE...' : 'ZALOGUJ',
allowButtonAction: allowButtonAction
action: 'Create',
endpint: 'user/auth',
button_value: 'SIGN IN'
},
{
type: 'text',
name: 'LOGIN',
name: 'EMAIL',
ref: usernameInput,
onChange: usernameValidation,
validationInfo: usernameValidationInfo
},
{
type: 'password',
name: 'HASŁO',
name: 'PASSWORD',
ref: passwordInput,
onChange: passwordValidation,
validationInfo: passwordValidationInfo
}
];
]
const login = async (formData) => {
try {
const credentials = {
username: formData.LOGIN,
password: formData.HASŁO
};
await dispatch(loginUser(credentials)).unwrap();
setInfoMessage("Logowanie zakończone sukcesem!");
navigate('/dashboard');
} catch (error) {
setErrorMessage("Wystąpił błąd podczas logowania (" + error.message + ")");
const login = async ( refs ) => {
let pass = {
username: refs[0].current.value,
password: refs[1].current.value
}
};
// dispatch(
// userAuthAsyncThunk.fetchLogin(
// pass
// )
// )
}
return (
<div className='form-container'>
<div>
<FormGenerator
inputList={inputList}
action={login}
inputList={ inputList }
refList={ refList }
action={ login }
/>
<div className='form_info'>
{infoMessage && <div className="success-message">{infoMessage}</div>}
{errorMessage && <div className="error-message">{errorMessage}</div>}
{ info }
</div>
</div>
);
};
)
export default UserLogin;
}
export default UserLoginForm

View File

@ -1,161 +1,152 @@
import React, { useState, useEffect, createRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { navigate } from 'gatsby';
import { registerUser } from '../../../redux/asyncThunks/userAuthAsyncThunk';
import { selectAuthError, selectAuthLoading } from '../../../redux/slices/userAuthSlice';
import FormGenerator from '../formGenerator';
import React, { useState, useEffect } from 'react'
const UserRegister = () => {
const dispatch = useDispatch();
const error = useSelector(selectAuthError);
const loading = useSelector(selectAuthLoading);
const [infoMessage, setInfoMessage] = useState('');
const [errorMessage, setErrorMessage] = useState('');
// import { useSelector, useDispatch } from 'react-redux'
const usernameInput = createRef();
const emailInput = createRef();
const passwordInput = createRef();
const confirmPasswordInput = createRef();
// import { userCrudSelector } from '../../../redux/slices/userCrudSlice'
// import userCrudAsyncThunk from '../../../redux/asyncThunks/userCrudAsyncThunk'
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,24}$/;
import FormGenerator from '../formGenerator'
const [usernameValidationInfo, setUsernameValidationInfo] = useState("Empty");
const [emailValidationInfo, setEmailValidationInfo] = useState("Empty");
const [passwordValidationInfo, setPasswordValidationInfo] = useState("Empty");
const [confirmPasswordValidationInfo, setConfirmPasswordValidationInfo] = useState("Empty");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [allowButtonAction, setAllowButtonAction] = useState(false);
const UserRegisterForm = () => {
const usernameInput = React.createRef()
const passwordInput = React.createRef()
const confirmPasswordInput = React.createRef()
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/
const [usernameValidationInfo, setUsernameValidationInfo] = useState("Empty")
const [passwordValidationInfo, setPasswordValidationInfo] = useState("Empty")
const [confirmPasswordValidationInfo, setConfirmPasswordValidationInfo] = useState("Empty")
const [password, setPassword] = useState("")
const [confirmPassword, setConfirmPassword] = useState("")
const [allowButtonAction, setAllowButtonAction] = useState(false)
const usernameValidation = (event) => {
if (event.target.value === "") {
setUsernameValidationInfo("Login jest wymagany");
setUsernameValidationInfo("Email is required.")
} else if(!emailRegex.test(event.target.value)) {
setUsernameValidationInfo("Please provide correct email")
} else {
setUsernameValidationInfo("Success");
setUsernameValidationInfo("Success")
}
};
const emailValidation = (event) => {
if (event.target.value === "") {
setEmailValidationInfo("Email jest wymagany");
} else if (!emailRegex.test(event.target.value)) {
setEmailValidationInfo("Nieprawidłowy format emaila");
} else {
setEmailValidationInfo("Success");
}
};
}
const passwordValidation = (event) => {
setPassword(event.target.value);
setPassword(event.target.value)
if (event.target.value === "") {
setPasswordValidationInfo("Hasło jest wymagane");
} else if (!passwordRegex.test(event.target.value)) {
setPasswordValidationInfo("Hasło musi zawierać:\n - Minimum 8 znaków\n - Maksimum 24 znaki\n - Minimum jedną wielką literę\n - Minimum jedną małą literę\n - Minimum jedną cyfrę\n - Minimum jeden znak specjalny");
setPasswordValidationInfo("Password is required.")
} else if(!passwordRegex.test(event.target.value)) {
setPasswordValidationInfo("Password require:\n - At least 8 characters,\n - At least one uppercase letter,\n - At least one lowercase letter,\n - At least one digit,\n - At least one special character.")
} else {
setPasswordValidationInfo("Success");
setPasswordValidationInfo("Success")
}
if (event.target.value !== confirmPassword) {
setConfirmPasswordValidationInfo("Hasła nie są identyczne");
if(event.target.value !== confirmPassword) {
setConfirmPasswordValidationInfo("Passwords are different.")
} else {
setConfirmPasswordValidationInfo("Success");
setConfirmPasswordValidationInfo("Success")
}
};
}
const confirmPasswordValidation = (event) => {
setConfirmPassword(event.target.value);
if (event.target.value !== password) {
setConfirmPasswordValidationInfo("Hasła nie są identyczne");
setConfirmPassword(event.target.value)
if(event.target.value !== password) {
setConfirmPasswordValidationInfo("Passwords are different.")
} else {
setConfirmPasswordValidationInfo("Success");
setConfirmPasswordValidationInfo("Success")
}
};
}
useEffect(() => {
setAllowButtonAction(
usernameValidationInfo === "Success" &&
emailValidationInfo === "Success" &&
passwordValidationInfo === "Success" &&
confirmPasswordValidationInfo === "Success"
);
}, [
usernameValidationInfo,
emailValidationInfo,
passwordValidationInfo,
confirmPasswordValidationInfo
]);
setAllowButtonAction(
usernameValidationInfo === "Success"
&& passwordValidationInfo === "Success"
&& confirmPasswordValidationInfo === "Success"
)
}, [
allowButtonAction,
usernameValidationInfo,
passwordValidationInfo,
confirmPasswordValidationInfo
]
)
const inputList = [
// const dispatch = useDispatch()
// const { info } = useSelector( userCrudSelector )
const info = "" // if redux is integrated - delete this line
let refList = [
usernameInput,
passwordInput,
confirmPasswordInput
]
let inputList = [
{
type: 'info',
action: 'Register',
endpoint: 'auth',
button_value: loading ? 'REJESTRACJA...' : 'ZAREJESTRUJ',
action: 'Create',
endpint: 'user/auth/register',
button_value: 'SIGN UP',
allowButtonAction: allowButtonAction
},
{
type: 'text',
name: 'LOGIN',
name: 'EMAIL',
ref: usernameInput,
onChange: usernameValidation,
validationInfo: usernameValidationInfo
},
{
type: 'text',
name: 'EMAIL',
ref: emailInput,
onChange: emailValidation,
validationInfo: emailValidationInfo
},
{
type: 'password',
name: 'HASŁO',
name: 'PASSWORD',
ref: passwordInput,
onChange: passwordValidation,
validationInfo: passwordValidationInfo
},
{
type: 'password',
name: 'POTWIERDŹ HASŁO',
name: 'CONFIRM PASSWORD',
ref: confirmPasswordInput,
onChange: confirmPasswordValidation,
validationInfo: confirmPasswordValidationInfo
}
];
]
const register = async (formData) => {
try {
const userData = {
username: formData.LOGIN,
email: formData.EMAIL,
password: formData.HASŁO
};
await dispatch(registerUser(userData)).unwrap();
setInfoMessage("Rejestracja zakończona sukcesem!");
navigate('/dashboard');
} catch (error) {
setErrorMessage("Wystąpił błąd podczas rejestracji (" + error.massage + ")");
const register = async ( refs ) => {
let pass = {
username: refs[0].current.value,
password: refs[1].current.value
}
};
// dispatch(
// userCrudAsyncThunk.fetchRegister(
// pass
// )
// )
}
return (
<div className='form-container'>
<div>
<FormGenerator
inputList={inputList}
action={register}
inputList={ inputList }
refList={ refList }
action={ register }
/>
<div className='form_info'>
{infoMessage && <div className="success-message">{infoMessage}</div>}
{errorMessage && <div className="error-message">{errorMessage}</div>}
{ info }
</div>
</div>
);
};
)
export default UserRegister;
}
export default UserRegisterForm

View File

@ -1,137 +0,0 @@
import React, { useState, createRef, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { changePassword } from '../../../redux/asyncThunks/userAuthAsyncThunk';
import { selectAuthError, selectAuthLoading } from '../../../redux/slices/userAuthSlice';
import FormGenerator from '../formGenerator';
const UserChangePasswordForm = () => {
const dispatch = useDispatch();
const loading = useSelector(selectAuthLoading);
const error = useSelector(selectAuthError);
const [infoMessage, setInfoMessage] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const currentPasswordInput = createRef();
const newPasswordInput = createRef();
const confirmPasswordInput = createRef();
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,24}$/;
const [currentPasswordValidationInfo, setCurrentPasswordValidationInfo] = useState("Empty");
const [newPasswordValidationInfo, setNewPasswordValidationInfo] = useState("Empty");
const [confirmPasswordValidationInfo, setConfirmPasswordValidationInfo] = useState("Empty");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [allowButtonAction, setAllowButtonAction] = useState(false);
const currentPasswordValidation = (event) => {
if (event.target.value === "") {
setCurrentPasswordValidationInfo("Hasło jest wymagane");
} else if (event.target.value === newPassword) {
setCurrentPasswordValidationInfo("Nowe hasło nie może być takie samo jak aktualne");
} else {
setCurrentPasswordValidationInfo("Success");
}
};
const newPasswordValidation = (event) => {
setNewPassword(event.target.value);
if (event.target.value === "") {
setNewPasswordValidationInfo("Hasło jest wymagane");
} else if (!passwordRegex.test(event.target.value)) {
setNewPasswordValidationInfo("Hasło musi zawierać:\n - Minimum 8 znaków\n - Maksimum 24 znaki\n - Minimum jedną wielką literę\n - Minimum jedną małą literę\n - Minimum jedną cyfrę\n - Minimum jeden znak specjalny");
} else {
setNewPasswordValidationInfo("Success");
}
if (event.target.value !== confirmPassword) {
setConfirmPasswordValidationInfo("Hasła nie są identyczne");
} else {
setConfirmPasswordValidationInfo("Success");
}
};
const confirmPasswordValidation = (event) => {
setConfirmPassword(event.target.value);
if (event.target.value !== newPassword) {
setConfirmPasswordValidationInfo("Hasła nie są identyczne");
} else {
setConfirmPasswordValidationInfo("Success");
}
};
useEffect(() => {
setAllowButtonAction(
currentPasswordValidationInfo === "Success" &&
newPasswordValidationInfo === "Success" &&
confirmPasswordValidationInfo === "Success"
);
}, [
currentPasswordValidationInfo,
newPasswordValidationInfo,
confirmPasswordValidationInfo
]);
const inputList = [
{
type: 'info',
action: 'Update',
endpoint: 'User',
button_value: loading ? 'AKTUALIZACJA...' : 'AKTUALIZUJ',
allowButtonAction: allowButtonAction
},
{
type: 'password',
name: 'AKTUALNE HASŁO',
ref: currentPasswordInput,
onChange: currentPasswordValidation,
validationInfo: currentPasswordValidationInfo
},
{
type: 'password',
name: 'NOWE HASŁO',
ref: newPasswordInput,
onChange: newPasswordValidation,
validationInfo: newPasswordValidationInfo
},
{
type: 'password',
name: 'POTWIERDŹ NOWE HASŁO',
ref: confirmPasswordInput,
onChange: confirmPasswordValidation,
validationInfo: confirmPasswordValidationInfo
}
];
const update = async (formData) => {
try {
const userData = {
currentPassword: formData['AKTUALNE HASŁO'],
newPassword: formData['NOWE HASŁO']
};
await dispatch(changePassword(userData)).unwrap();
setInfoMessage("Hasło zostało zmienione!");
} catch (error) {
setErrorMessage("Wystąpił błąd podczas zmiany hasła (" + error.message + ")");
}
};
return (
<div className='form-container'>
<FormGenerator
inputList={inputList}
action={update}
/>
<div className='form_info'>
{infoMessage && <div className="success-message">{infoMessage}</div>}
{errorMessage && <div className="error-message">{errorMessage}</div>}
</div>
</div>
);
};
export default UserChangePasswordForm;

View File

@ -1,81 +0,0 @@
import React, { useState, createRef, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { navigate } from 'gatsby';
import { deleteAccount } from '../../../redux/asyncThunks/userAuthAsyncThunk';
import { selectAuthError, selectAuthLoading } from '../../../redux/slices/userAuthSlice';
import FormGenerator from '../formGenerator';
const UserDeleteAccountForm = () => {
const dispatch = useDispatch();
const loading = useSelector(selectAuthLoading);
const error = useSelector(selectAuthError);
const [infoMessage, setInfoMessage] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const deleteConfirmationInput = createRef();
const [deleteConfirmationValidationInfo, setDeleteConfirmationValidationInfo] = useState("Empty");
const [allowButtonAction, setAllowButtonAction] = useState(false);
const deleteConfirmationValidation = (event) => {
if (event.target.value === "") {
setDeleteConfirmationValidationInfo("Wpisz 'DELETE' aby potwierdzić usunięcie konta");
} else if (event.target.value !== "DELETE") {
setDeleteConfirmationValidationInfo("Wpisz dokładnie 'DELETE' aby potwierdzić usunięcie konta");
} else {
setDeleteConfirmationValidationInfo("Success");
}
};
useEffect(() => {
setAllowButtonAction(deleteConfirmationValidationInfo === "Success");
}, [deleteConfirmationValidationInfo]);
const inputList = [
{
type: 'info',
action: 'Delete',
endpoint: 'User',
button_value: loading ? 'USUWANIE...' : 'USUŃ KONTO',
allowButtonAction: allowButtonAction
},
{
type: 'text',
name: 'POTWIERDŹ USUNIĘCIE',
ref: deleteConfirmationInput,
onChange: deleteConfirmationValidation,
validationInfo: deleteConfirmationValidationInfo,
placeholder: "Wpisz 'DELETE' aby potwierdzić usunięcie konta"
}
];
const deleteAccountHandler = async (formData) => {
try {
if (formData['POTWIERDŹ USUNIĘCIE'] === 'DELETE') {
await dispatch(deleteAccount()).unwrap();
setInfoMessage("Konto zostało usunięte!");
setTimeout(() => {
navigate('/');
}, 2000);
} else {
setErrorMessage("Wpisz 'DELETE' aby potwierdzić usunięcie konta");
}
} catch (error) {
setErrorMessage("Wystąpił błąd podczas usuwania konta (" + error.message + ")");
}
};
return (
<div className='form-container'>
<FormGenerator
inputList={inputList}
action={deleteAccountHandler}
/>
<div className='form_info'>
{infoMessage && <div className="success-message">{infoMessage}</div>}
{errorMessage && <div className="error-message">{errorMessage}</div>}
</div>
</div>
);
};
export default UserDeleteAccountForm;

View File

@ -1,84 +0,0 @@
import React, { useState, createRef, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { updateProfile } from '../../../redux/asyncThunks/userAuthAsyncThunk';
import { selectAuthError, selectAuthLoading, selectCurrentUser } from '../../../redux/slices/userAuthSlice';
import FormGenerator from '../formGenerator';
const UserUpdateProfileForm = () => {
const dispatch = useDispatch();
const user = useSelector(selectCurrentUser);
const loading = useSelector(selectAuthLoading);
const error = useSelector(selectAuthError);
const [infoMessage, setInfoMessage] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const emailInput = createRef();
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const [emailValidationInfo, setEmailValidationInfo] = useState("Empty");
const [allowButtonAction, setAllowButtonAction] = useState(false);
const emailValidation = (event) => {
if (event.target.value === "") {
setEmailValidationInfo("Email jest wymagany");
} else if (!emailRegex.test(event.target.value)) {
setEmailValidationInfo("Nieprawidłowy format emaila");
} else {
setEmailValidationInfo("Success");
}
};
useEffect(() => {
setAllowButtonAction(emailValidationInfo === "Success");
}, [emailValidationInfo]);
const inputList = [
{
type: 'info',
action: 'Update',
endpoint: 'User',
button_value: loading ? 'AKTUALIZACJA...' : 'AKTUALIZUJ',
allowButtonAction: allowButtonAction
},
{
type: 'label',
name: 'LOGIN',
value: user?.login || ''
},
{
type: 'text',
name: 'EMAIL',
ref: emailInput,
onChange: emailValidation,
validationInfo: emailValidationInfo,
value: user?.email || ''
}
];
const update = async (formData) => {
try {
const userData = {
email: formData.EMAIL
};
await dispatch(updateProfile(userData)).unwrap();
setInfoMessage("Profil został zaktualizowany!");
} catch (error) {
setErrorMessage("Wystąpił błąd podczas aktualizacji (" + error.message + ")");
}
};
return (
<div className='form-container'>
<FormGenerator
inputList={inputList}
action={update}
/>
<div className='form_info'>
{infoMessage && <div className="success-message">{infoMessage}</div>}
{errorMessage && <div className="error-message">{errorMessage}</div>}
</div>
</div>
);
};
export default UserUpdateProfileForm;

View File

@ -1,20 +0,0 @@
// Konfiguracja API
export const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:3001/api';
// Konfiguracja aplikacji
export const APP_CONFIG = {
name: 'Render App',
version: '1.0.0',
environment: process.env.NODE_ENV || 'development'
};
// Konfiguracja autentykacji
export const AUTH_CONFIG = {
tokenExpiry: 7, // dni
refreshTokenExpiry: 30, // dni
cookieOptions: {
secure: true,
sameSite: 'strict',
path: '/'
}
};

View File

@ -1,36 +1,21 @@
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { navigate } from 'gatsby';
import { logoutUser, checkAuth } from '../redux/asyncThunks/userAuthAsyncThunk';
import { selectIsAuthenticated, selectUserPermissions } from '../redux/slices/userAuthSlice';
import React, { useState } from 'react';
import '../styles/general.scss';
import '@fortawesome/fontawesome-free/css/all.min.css';
import NavBarComponent from '../components/navbar.js';
import FootComponent from '../components/foot.js';
import NavBarComponent from '../components/navbar.js';
import ThreeDModelsDashboard from './dashboards/3d-models.js';
import AIModelsDashboard from './dashboards/ai.models.js';
import AITasksDashboard from './dashboards/ai.tasks.js';
import RendersDashboard from './dashboards/renders.js';
import ServersDashboard from './dashboards/servers.js';
import UserSettingsDashboard from './dashboards/user.js';
import ThreeDModelsDashboard from './dashboards/3d-models.js';
import UserSettings from './dashboards/user.js';
const DashboardPage = () => {
const dispatch = useDispatch();
const isAuthenticated = useSelector(selectIsAuthenticated);
const permissions = useSelector(selectUserPermissions);
const icons_size = "fa-2x";
const [activeComponent, setActiveComponent] = useState('3d-models');
useEffect(() => {
if (!isAuthenticated) {
dispatch(checkAuth())
.unwrap()
.catch(() => {
navigate('/auth/login');
});
}
}, [dispatch, isAuthenticated]);
const handleNavigation = (path) => {
setActiveComponent(path);
};
@ -39,13 +24,10 @@ const DashboardPage = () => {
return activeComponent === path;
};
const handleLogout = async () => {
try {
await dispatch(logoutUser()).unwrap();
navigate('/auth/login');
} catch (error) {
console.error('Błąd podczas wylogowywania:', error);
}
const handleLogout = () => {
// TODO: Implement proper logout logic (clear tokens, session, etc.)
console.log('Logging out...');
window.location.href = '/login';
};
const renderContent = () => {
@ -59,7 +41,7 @@ const DashboardPage = () => {
case 'servers':
return <ServersDashboard />;
case 'settings':
return <UserSettingsDashboard />;
return <UserSettings />;
case '3d-models':
default:
return <ThreeDModelsDashboard />;

View File

@ -1,42 +1,193 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { selectCurrentUser } from '../../redux/slices/userAuthSlice';
import UserUpdateProfileForm from '../../components/forms/user_settings/userUpdateProfileForm';
import UserChangePasswordForm from '../../components/forms/user_settings/userChangePasswordForm';
import UserDeleteAccountForm from '../../components/forms/user_settings/userDeleteAccountForm';
import React, { useState, useRef } from 'react';
import FormGenerator from '../../components/forms/formGenerator';
const UserSettingsDashboard = () => {
const user = useSelector(selectCurrentUser);
const UserSettings = () => {
const [isEditing, setIsEditing] = useState(false);
const [message, setMessage] = useState({ type: '', text: '' });
const usernameInput = useRef();
const emailInput = useRef();
const currentPasswordInput = useRef();
const newPasswordInput = useRef();
const confirmPasswordInput = useRef();
const [usernameValidationInfo, setUsernameValidationInfo] = useState("Empty");
const [emailValidationInfo, setEmailValidationInfo] = useState("Empty");
const [currentPasswordValidationInfo, setCurrentPasswordValidationInfo] = useState("Empty");
const [newPasswordValidationInfo, setNewPasswordValidationInfo] = useState("Empty");
const [confirmPasswordValidationInfo, setConfirmPasswordValidationInfo] = useState("Empty");
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
const usernameValidation = (event) => {
if (event.target.value === "") {
setUsernameValidationInfo("Username is required.");
} else {
setUsernameValidationInfo("Success");
}
};
const emailValidation = (event) => {
if (event.target.value === "") {
setEmailValidationInfo("Email is required.");
} else if (!emailRegex.test(event.target.value)) {
setEmailValidationInfo("Please provide correct email");
} else {
setEmailValidationInfo("Success");
}
};
const currentPasswordValidation = (event) => {
if (event.target.value === "") {
setCurrentPasswordValidationInfo("Current password is required.");
} else {
setCurrentPasswordValidationInfo("Success");
}
};
const newPasswordValidation = (event) => {
if (event.target.value === "") {
setNewPasswordValidationInfo("New password is required.");
} else if (!passwordRegex.test(event.target.value)) {
setNewPasswordValidationInfo("Password require:\n - At least 8 characters,\n - At least one uppercase letter,\n - At least one lowercase letter,\n - At least one digit,\n - At least one special character.");
} else {
setNewPasswordValidationInfo("Success");
}
};
const confirmPasswordValidation = (event) => {
if (event.target.value === "") {
setConfirmPasswordValidationInfo("Please confirm your new password.");
} else if (event.target.value !== newPasswordInput.current.value) {
setConfirmPasswordValidationInfo("Passwords do not match.");
} else {
setConfirmPasswordValidationInfo("Success");
}
};
const handleSubmit = async (refs) => {
try {
// TODO: Implement API call to update user data
console.log('Updating user data:', {
username: refs[0].current.value,
email: refs[1].current.value,
currentPassword: refs[2].current.value,
newPassword: refs[3].current.value
});
setMessage({ type: 'success', text: 'Settings updated successfully!' });
setIsEditing(false);
} catch (error) {
setMessage({ type: 'error', text: 'Failed to update settings. Please try again.' });
}
};
const getInputList = () => {
const baseInputs = [
{
type: 'info',
action: 'Update',
endpoint: 'user/settings',
button_value: isEditing ? 'SAVE CHANGES' : ''
},
{
type: 'text',
name: 'USERNAME',
ref: usernameInput,
onChange: usernameValidation,
validationInfo: usernameValidationInfo,
disabled: !isEditing
},
{
type: 'text',
name: 'EMAIL',
ref: emailInput,
onChange: emailValidation,
validationInfo: emailValidationInfo,
disabled: !isEditing
}
];
if (isEditing) {
baseInputs.push(
{
type: 'password',
name: 'CURRENT PASSWORD',
ref: currentPasswordInput,
onChange: currentPasswordValidation,
validationInfo: currentPasswordValidationInfo
},
{
type: 'password',
name: 'NEW PASSWORD',
ref: newPasswordInput,
onChange: newPasswordValidation,
validationInfo: newPasswordValidationInfo
},
{
type: 'password',
name: 'CONFIRM NEW PASSWORD',
ref: confirmPasswordInput,
onChange: confirmPasswordValidation,
validationInfo: confirmPasswordValidationInfo
}
);
}
return baseInputs;
};
const formRefs = [usernameInput, emailInput, currentPasswordInput, newPasswordInput, confirmPasswordInput];
const handleFormAction = (refs) => {
const formData = {};
formRefs.forEach((ref, index) => {
formData[getInputList()[index].name] = ref.current.value;
});
handleSubmit(formRefs);
};
return (
<div className="dashboard-container">
<div className="dashboard-header">
<h1>Panel Użytkownika</h1>
<div className="user-info">
<span>Zalogowany jako: {user?.login}</span>
<div className="list-container">
<div className="user-settings">
<div className="settings-header">
<h2>User Settings</h2>
<button
className={`edit-button ${isEditing ? 'cancel' : ''}`}
onClick={() => setIsEditing(!isEditing)}
>
{isEditing ? 'Cancel' : 'Edit'}
</button>
</div>
</div>
<div className="dashboard-content">
<div className="user-settings">
<div className="settings-section">
<h2>Aktualizacja profilu</h2>
<UserUpdateProfileForm />
{message.text && (
<div className={`message ${message.type}`}>
{message.text}
</div>
)}
<div className="settings-section">
<h2>Zmiana hasła</h2>
<UserChangePasswordForm />
{isEditing && (
<div className="form-overlay">
<div className="form-container">
<FormGenerator
inputList={getInputList().map((field, index) => ({
...field,
ref: formRefs[index],
type: field.type,
name: field.name,
onChange: field.onChange,
validationInfo: field.validationInfo
}))}
refList={formRefs}
action={handleFormAction}
/>
</div>
</div>
<div className="settings-section">
<h2>Usuwanie konta</h2>
<UserDeleteAccountForm />
</div>
</div>
)}
</div>
</div>
);
};
export default UserSettingsDashboard;
export default UserSettings;

View File

@ -1,179 +1,32 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import { setCredentials, setLoading, setError, logout } from '../slices/userAuthSlice';
import { API_URL, AUTH_CONFIG } from '../../config';
import { cookieService } from '../../services/cookieService';
import axios from 'axios';
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
export const loginUser = createAsyncThunk(
'userAuth/login',
async (credentials, { dispatch }) => {
try {
dispatch(setLoading(true));
const response = await fetch(`${API_URL}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(credentials),
});
async (credentials) => {
const formData = new FormData();
formData.append('username', credentials.username);
formData.append('password', credentials.password);
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || 'Błąd logowania');
}
dispatch(setCredentials({
user: data.user,
token: data.token,
permissions: data.permissions
}));
return data;
} catch (error) {
dispatch(setError(error.message));
throw error;
} finally {
dispatch(setLoading(false));
}
const response = await axios.post(`${API_URL}/auth`, formData);
return response.data;
}
);
export const registerUser = createAsyncThunk(
'userAuth/register',
async (userData, { dispatch }) => {
try {
dispatch(setLoading(true));
const response = await fetch(`${API_URL}/auth/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || 'Błąd rejestracji');
}
dispatch(setCredentials({
user: data.user,
token: data.token,
permissions: data.permissions
}));
return data;
} catch (error) {
dispatch(setError(error.message));
throw error;
} finally {
dispatch(setLoading(false));
}
}
);
export const logoutUser = createAsyncThunk(
'userAuth/logout',
async (_, { dispatch }) => {
try {
dispatch(setLoading(true));
const token = cookieService.getToken();
if (token) {
await fetch(`${API_URL}/auth/logout`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
},
});
}
dispatch(logout());
} catch (error) {
console.error('Błąd podczas wylogowywania:', error);
} finally {
dispatch(setLoading(false));
}
}
);
export const checkAuth = createAsyncThunk(
'userAuth/checkAuth',
async (_, { dispatch }) => {
try {
const token = cookieService.getToken();
if (!token) {
throw new Error('Brak tokenu');
}
const response = await fetch(`${API_URL}/auth/verify`, {
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error('Token nieprawidłowy');
}
const data = await response.json();
return data;
} catch (error) {
dispatch(logout());
throw error;
}
async (userData) => {
const response = await axios.post(`${API_URL}/register`, userData);
return response.data;
}
);
export const changePassword = createAsyncThunk(
'userAuth/changePassword',
async (passwordData) => {
const response = await fetch(`${API_URL}/change-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(passwordData),
});
const response = await axios.post(`${API_URL}/change-password`, passwordData);
return response.data;
}
);
export const updateProfile = createAsyncThunk(
'userAuth/updateProfile',
async (profileData, { rejectWithValue }) => {
try {
const response = await fetch(`${API_URL}/user/profile`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${cookieService.getToken()}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(profileData),
});
return response.data;
} catch (error) {
return rejectWithValue(error.response?.data);
}
}
);
export const deleteAccount = createAsyncThunk(
'userAuth/deleteAccount',
async (_, { rejectWithValue }) => {
try {
const response = await fetch(`${API_URL}/user/delete`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${cookieService.getToken()}`,
},
});
cookieService.clearAll();
return response.data;
} catch (error) {
return rejectWithValue(error.response?.data);
}
}
);

View File

@ -1,54 +1,72 @@
import { createSlice } from '@reduxjs/toolkit';
import { cookieService } from '../../services/cookieService';
import { loginUser, registerUser, changePassword } from '../asyncThunks/userAuthAsyncThunk';
const initialState = {
user: cookieService.getUserData() || null,
token: cookieService.getToken() || null,
isAuthenticated: !!cookieService.getToken(),
loading: false,
error: null,
permissions: []
user: null,
token: null,
isLoading: false,
error: null
};
const userAuthSlice = createSlice({
name: 'userAuth',
initialState,
reducers: {
setCredentials: (state, { payload }) => {
const { user, token, permissions } = payload;
state.user = user;
state.token = token;
state.isAuthenticated = true;
state.permissions = permissions;
cookieService.setToken(token);
cookieService.setUserData({ ...user, permissions });
},
logout: (state) => {
state.user = null;
state.token = null;
state.isAuthenticated = false;
state.permissions = [];
cookieService.clearAll();
},
setLoading: (state, { payload }) => {
state.loading = payload;
},
setError: (state, { payload }) => {
state.error = payload;
state.error = null;
},
clearError: (state) => {
state.error = null;
}
},
extraReducers: (builder) => {
// Login
builder.addCase(loginUser.pending, (state) => {
state.isLoading = true;
state.error = null;
});
builder.addCase(loginUser.fulfilled, (state, action) => {
state.isLoading = false;
state.user = action.payload.user;
state.token = action.payload.token;
});
builder.addCase(loginUser.rejected, (state, action) => {
state.isLoading = false;
state.error = action.error.message;
});
// Register
builder.addCase(registerUser.pending, (state) => {
state.isLoading = true;
state.error = null;
});
builder.addCase(registerUser.fulfilled, (state, action) => {
state.isLoading = false;
state.user = action.payload.user;
state.token = action.payload.token;
});
builder.addCase(registerUser.rejected, (state, action) => {
state.isLoading = false;
state.error = action.error.message;
});
// Change Password
builder.addCase(changePassword.pending, (state) => {
state.isLoading = true;
state.error = null;
});
builder.addCase(changePassword.fulfilled, (state) => {
state.isLoading = false;
});
builder.addCase(changePassword.rejected, (state, action) => {
state.isLoading = false;
state.error = action.error.message;
});
}
});
export const { setCredentials, logout, setLoading, setError, clearError } = userAuthSlice.actions;
export const selectCurrentUser = (state) => state.userAuth.user;
export const selectCurrentToken = (state) => state.userAuth.token;
export const selectIsAuthenticated = (state) => state.userAuth.isAuthenticated;
export const selectUserPermissions = (state) => state.userAuth.permissions;
export const selectAuthLoading = (state) => state.userAuth.loading;
export const selectAuthError = (state) => state.userAuth.error;
export const { logout, clearError } = userAuthSlice.actions;
export const userAuthSelector = (state) => state.userAuth;
export default userAuthSlice.reducer;

View File

@ -1,46 +0,0 @@
import Cookies from 'js-cookie';
const TOKEN_COOKIE_NAME = 'auth_token';
const USER_COOKIE_NAME = 'user_data';
export const cookieService = {
setToken: (token) => {
Cookies.set(TOKEN_COOKIE_NAME, token, {
expires: 7, // 7 dni
secure: true, // tylko HTTPS
sameSite: 'strict', // ochrona przed CSRF
path: '/'
});
},
getToken: () => {
return Cookies.get(TOKEN_COOKIE_NAME);
},
removeToken: () => {
Cookies.remove(TOKEN_COOKIE_NAME, { path: '/' });
},
setUserData: (userData) => {
Cookies.set(USER_COOKIE_NAME, JSON.stringify(userData), {
expires: 7,
secure: true,
sameSite: 'strict',
path: '/'
});
},
getUserData: () => {
const userData = Cookies.get(USER_COOKIE_NAME);
return userData ? JSON.parse(userData) : null;
},
removeUserData: () => {
Cookies.remove(USER_COOKIE_NAME, { path: '/' });
},
clearAll: () => {
this.removeToken();
this.removeUserData();
}
};

View File

@ -1,5 +1,3 @@
@import '@fortawesome/fontawesome-free/css/all.min.css';
// colors
$first-color: rgba(0, 90, 25, 1);
@ -9,7 +7,6 @@ $error-color: #dc3545;
$in-progress-color: #ffa500;
$queued-color: #fbff00;
$success-color: $first-color;
$card-background: rgba(0, 0, 0, 0.2);
$title-color: white;
$subtitle-color: #a6a6a6;
@ -808,13 +805,6 @@ body, html {
filter: invert(8%) sepia(63%) saturate(4888%) hue-rotate(8deg) brightness(113%) contrast(112%);
}
}
.label-value {
width: 90%;
padding: 5%;
color: $subtitle-color;
font-size: 14px;
}
}
.popup {
@ -990,6 +980,12 @@ body, html {
margin-top: 30px;
}
.form_info {
float: float;
position: absolute;
margin-top: 0px;
}
}
.float_form_render_sync {
@ -1150,108 +1146,119 @@ body, html {
}
.user-settings {
padding: 20px;
max-width: 100%;
margin: 0 auto;
padding: 20px;
color: $secondary-color;
.settings-header {
.settings-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
margin-bottom: 20px;
h2 {
margin: 0;
color: $title-color;
font-size: 24px;
color: $title-color;
margin: 0;
font-size: 24px;
}
.settings-actions {
display: flex;
gap: 16px;
.edit-button {
background: $first-color;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
.edit-button {
padding: 8px 16px;
border: none;
border-radius: 4px;
background: $first-color;
color: white;
cursor: pointer;
transition: background-color 0.3s;
&.cancel {
background: #dc3545;
}
&:hover {
background: darken($first-color, 10%);
}
&.cancel {
background: $error-color;
&:hover {
background: darken($error-color, 10%);
}
}
&:hover {
transform: translateY(-2px);
&.cancel {
background: darken(#dc3545, 10%);
}
.delete-button {
padding: 8px 16px;
border: none;
border-radius: 4px;
background: $error-color;
color: white;
cursor: pointer;
transition: background-color 0.3s;
&:hover {
background: darken($error-color, 10%);
}
&:not(.cancel) {
background: darken($first-color, 10%);
}
}
}
}
}
.settings-sections-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
.settings-section {
flex: 1;
width: 450px;
background: $card-background;
border-radius: 8px;
padding: 20px;
h3 {
margin: 0 0 20px 0;
color: $title-color;
font-size: 18px;
}
}
}
.settings-section.delete-section {
border: 1px solid $error-color;
background: rgba($error-color, 0.1);
}
.message {
padding: 12px;
border-radius: 4px;
.message {
padding: 10px;
margin-bottom: 20px;
border-radius: 5px;
text-align: center;
font-weight: 500;
&.success {
background: rgba($success-color, 0.1);
color: $success-color;
border: 1px solid $success-color;
background: rgba(0, 120, 0, 0.2);
color: #00ff00;
}
&.error {
background: rgba($error-color, 0.1);
color: $error-color;
border: 1px solid $error-color;
background: rgba(220, 53, 69, 0.2);
color: #ff4444;
}
}
.settings-form {
max-width: 500px;
margin: 0 auto;
.form-group {
margin-bottom: 20px;
label {
display: block;
margin-bottom: 8px;
color: $title-color;
font-weight: 600;
}
input {
width: 100%;
padding: 10px;
background: rgba(0, 0, 0, 0.2);
border: 1px solid $border-color;
border-radius: 5px;
color: $secondary-color;
transition: all 0.3s ease;
&:disabled {
background: rgba(0, 0, 0, 0.1);
cursor: not-allowed;
}
&:focus {
outline: none;
border-color: $first-color;
box-shadow: 0 0 0 2px rgba($first-color, 0.2);
}
}
}
.save-button {
width: 100%;
padding: 12px;
background: $first-color;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
&:hover {
background: darken($first-color, 10%);
transform: translateY(-2px);
}
}
}
}
}
.task-row {
display: flex;
@ -1550,63 +1557,3 @@ body, html {
.dashboard-content {
min-height: 100%; /* Wymusza pojawienie się suwaka */
}
.form-container {
margin: 0 auto;
padding: 20px;
border-radius: 8px;
h2 {
color: $title-color;
text-align: center;
margin-bottom: 20px;
}
.form_info {
float: float;
width: 400px;
height: 100px;
margin-top: 10px;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
.success-message {
background: rgba($success-color, 0.1);
color: $success-color;
padding: 10px;
border-radius: 4px;
margin-bottom: 20px;
text-align: center;
border: 1px solid $success-color;
}
.error-message {
background: rgba($error-color, 0.1);
color: $error-color;
padding: 10px;
border-radius: 4px;
margin-bottom: 20px;
text-align: center;
border: 1px solid $error-color;
}
}
}
.logout-button {
width: 100%;
padding: 10px;
background: $error-color;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: background-color 0.2s;
&:hover {
background: darken($error-color, 10%);
}
}