feat(forms & pages & services): add a lot of stuff
improve login & register (integrate that with redux and api) + improve user settings dashboard (divide forms to particular files as form components) + improve formGenerator (catching refs and etc) + morefeat/x_gpu/chat_gpt_new_version
parent
ae781652de
commit
afe0b88808
|
|
@ -13,6 +13,7 @@
|
||||||
"axios": "^1.8.1",
|
"axios": "^1.8.1",
|
||||||
"gatsby": "^5.13.3",
|
"gatsby": "^5.13.3",
|
||||||
"gatsby-plugin-sass": "^6.14.0",
|
"gatsby-plugin-sass": "^6.14.0",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
|
|
@ -11751,6 +11752,15 @@
|
||||||
"@sideway/pinpoint": "^2.0.0"
|
"@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": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
|
@ -25860,6 +25870,11 @@
|
||||||
"@sideway/pinpoint": "^2.0.0"
|
"@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": {
|
"js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
"axios": "^1.8.1",
|
"axios": "^1.8.1",
|
||||||
"gatsby": "^5.13.3",
|
"gatsby": "^5.13.3",
|
||||||
"gatsby-plugin-sass": "^6.14.0",
|
"gatsby-plugin-sass": "^6.14.0",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
|
|
||||||
import passwordVisibleImg from '../../images/password-visible.png'
|
import passwordVisibleImg from '../../images/password-visible.png'
|
||||||
import passwordHiddenImg from '../../images/password-hidden.png'
|
import passwordHiddenImg from '../../images/password-hidden.png'
|
||||||
|
|
@ -6,43 +6,28 @@ import passwordHiddenImg from '../../images/password-hidden.png'
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param { [ {}, {}, ...{} ] } inputList - list of dicts with info about input
|
* @param { [ {}, {}, ...{} ] } inputList - list of dicts with info about input
|
||||||
* @param { [] } refList - react ref objects list for handler validation
|
|
||||||
* @param { } action - fetch method
|
* @param { } action - fetch method
|
||||||
*/
|
*/
|
||||||
export const FormGenerator = ({
|
export const FormGenerator = ({
|
||||||
inputList, refList,
|
inputList,
|
||||||
action
|
action
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const handler = async (event) => {
|
const handleSubmit = (e) => {
|
||||||
event.preventDefault()
|
e.preventDefault();
|
||||||
|
const formData = {};
|
||||||
if ( inputList[0].action === 'Async' ) {
|
inputList.forEach((singleInput, index) => {
|
||||||
await action(refList)
|
if (typeof singleInput.type !== 'undefined' && singleInput.ref?.current) {
|
||||||
} else if (
|
formData[singleInput.name] = singleInput.ref.current.value;
|
||||||
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
|
let info
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={event => handler(event)} className="form">
|
<form onSubmit={handleSubmit} className="form">
|
||||||
{
|
{
|
||||||
inputList.map((input, key) => {
|
inputList.map((input, key) => {
|
||||||
|
|
||||||
|
|
@ -104,6 +89,13 @@ export const FormGenerator = ({
|
||||||
key={key}
|
key={key}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
} else if (input.type === 'label') {
|
||||||
|
return (
|
||||||
|
<div className="form-field">
|
||||||
|
<label>{input.name}</label>
|
||||||
|
<div className="label-value">{input.value}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -112,10 +104,10 @@ export const FormGenerator = ({
|
||||||
? <></>
|
? <></>
|
||||||
: <button
|
: <button
|
||||||
type='submit'
|
type='submit'
|
||||||
disabled={ info.allowButtonAction }
|
disabled={!info.allowButtonAction}
|
||||||
className={ info.allowButtonAction === false ? "button-disabled" : "" }
|
className={!info.allowButtonAction ? "button-disabled" : ""}
|
||||||
>
|
>
|
||||||
{ info.button_value }
|
{info.button_value}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,6 +148,7 @@ const TextInputGenerator = ({
|
||||||
} else {
|
} else {
|
||||||
setTextInputValidationInfo("Success")
|
setTextInputValidationInfo("Success")
|
||||||
}
|
}
|
||||||
|
input.validationInfo = textInputValidationInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -167,12 +160,13 @@ const TextInputGenerator = ({
|
||||||
id={input.name + info.action + info.endpoint + 'Input'}
|
id={input.name + info.action + info.endpoint + 'Input'}
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
ref={input.ref}
|
ref={input.ref}
|
||||||
onChange={ input.onChange === null ? defaultValidation : input.onChange}
|
onChange={input.onChange === null ? defaultValidation : input.onChange}
|
||||||
className={
|
className={
|
||||||
[ "Empty", "Success"].includes(
|
[ "Empty", "Success"].includes(
|
||||||
input.validationInfo === null ? textInputValidationInfo : input.validationInfo
|
input.validationInfo === null ? textInputValidationInfo : input.validationInfo
|
||||||
) ? "" : "input-incorrect"
|
) ? "" : "input-incorrect"
|
||||||
}
|
}
|
||||||
|
placeholder={input.placeholder === null ? "" : input.placeholder}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="popup"
|
className="popup"
|
||||||
|
|
@ -238,7 +232,6 @@ const PasswordInputGenerator = ({
|
||||||
<div className="popup-content">
|
<div className="popup-content">
|
||||||
{ input.validationInfo }
|
{ input.validationInfo }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -465,7 +458,6 @@ const UploadInputGenerator = ({
|
||||||
const onLoadFile = async (event) => {
|
const onLoadFile = async (event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
let data = event.target.files[0]
|
let data = event.target.files[0]
|
||||||
// input.setFile(await toBase64(data))
|
|
||||||
input.setFile( data )
|
input.setFile( data )
|
||||||
setDropInfos(data.name, data.size)
|
setDropInfos(data.name, data.size)
|
||||||
}
|
}
|
||||||
|
|
@ -474,7 +466,6 @@ const UploadInputGenerator = ({
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.persist()
|
event.persist()
|
||||||
let data = event.dataTransfer.files[0]
|
let data = event.dataTransfer.files[0]
|
||||||
// input.setFile(await toBase64(data))
|
|
||||||
input.setFile( data )
|
input.setFile( data )
|
||||||
setDropInfos(data.name, data.size)
|
setDropInfos(data.name, data.size)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,121 +1,106 @@
|
||||||
import React, { useState, useEffect } from 'react'
|
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 { useSelector, useDispatch } from 'react-redux'
|
const UserLogin = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const error = useSelector(selectAuthError);
|
||||||
|
const loading = useSelector(selectAuthLoading);
|
||||||
|
const [infoMessage, setInfoMessage] = useState('');
|
||||||
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
|
|
||||||
// import { userAuthSelector } from '../../../redux/slices/userAuthSlice'
|
const usernameInput = createRef();
|
||||||
// import userAuthAsyncThunk from '../../../redux/asyncThunks/userAuthAsyncThunk'
|
const passwordInput = createRef();
|
||||||
|
|
||||||
import FormGenerator from '../formGenerator'
|
const [usernameValidationInfo, setUsernameValidationInfo] = useState("Empty");
|
||||||
|
const [passwordValidationInfo, setPasswordValidationInfo] = useState("Empty");
|
||||||
|
|
||||||
|
const [allowButtonAction, setAllowButtonAction] = useState(false);
|
||||||
|
|
||||||
const UserLoginForm = () => {
|
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,24}$/;
|
||||||
|
|
||||||
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) => {
|
const usernameValidation = (event) => {
|
||||||
|
|
||||||
if (event.target.value === "") {
|
if (event.target.value === "") {
|
||||||
setUsernameValidationInfo("Email is required.")
|
setUsernameValidationInfo("Login jest wymagany");
|
||||||
} else if(!emailRegex.test(event.target.value)) {
|
|
||||||
setUsernameValidationInfo("Please provide correct email")
|
|
||||||
} else {
|
} else {
|
||||||
setUsernameValidationInfo("Success")
|
setUsernameValidationInfo("Success");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const passwordValidation = (event) => {
|
const passwordValidation = (event) => {
|
||||||
|
|
||||||
setPassword(event.target.value)
|
|
||||||
|
|
||||||
if (event.target.value === "") {
|
if (event.target.value === "") {
|
||||||
setPasswordValidationInfo("Password is required.")
|
setPasswordValidationInfo("Hasło jest wymagane");
|
||||||
} else if(!passwordRegex.test(event.target.value)) {
|
} 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.")
|
setPasswordValidationInfo("Hasło nie spełnia wymagań tej witryny");
|
||||||
} else {
|
} else {
|
||||||
setPasswordValidationInfo("Success")
|
setPasswordValidationInfo("Success");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAllowButtonAction(
|
setAllowButtonAction(
|
||||||
usernameValidationInfo === "Success"
|
usernameValidationInfo === "Success" &&
|
||||||
&& passwordValidationInfo === "Success"
|
passwordValidationInfo === "Success"
|
||||||
)
|
);
|
||||||
}, [
|
}, [
|
||||||
allowButtonAction,
|
usernameValidationInfo,
|
||||||
usernameValidationInfo,
|
passwordValidationInfo,
|
||||||
passwordValidationInfo
|
]);
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
// const dispatch = useDispatch()
|
const inputList = [
|
||||||
// const { info } = useSelector( userAuthSelector )
|
|
||||||
const info = "" // if redux is integrated - delete this line
|
|
||||||
|
|
||||||
let refList = [
|
|
||||||
usernameInput,
|
|
||||||
passwordInput
|
|
||||||
]
|
|
||||||
|
|
||||||
let inputList = [
|
|
||||||
{
|
{
|
||||||
type: 'info',
|
type: 'info',
|
||||||
action: 'Create',
|
action: 'Login',
|
||||||
endpint: 'user/auth',
|
endpoint: 'auth',
|
||||||
button_value: 'SIGN IN'
|
button_value: loading ? 'LOGOWANIE...' : 'ZALOGUJ',
|
||||||
|
allowButtonAction: allowButtonAction
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
name: 'EMAIL',
|
name: 'LOGIN',
|
||||||
ref: usernameInput,
|
ref: usernameInput,
|
||||||
onChange: usernameValidation,
|
onChange: usernameValidation,
|
||||||
validationInfo: usernameValidationInfo
|
validationInfo: usernameValidationInfo
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'password',
|
type: 'password',
|
||||||
name: 'PASSWORD',
|
name: 'HASŁO',
|
||||||
ref: passwordInput,
|
ref: passwordInput,
|
||||||
onChange: passwordValidation,
|
onChange: passwordValidation,
|
||||||
validationInfo: passwordValidationInfo
|
validationInfo: passwordValidationInfo
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
|
||||||
const login = async ( refs ) => {
|
const login = async (formData) => {
|
||||||
let pass = {
|
try {
|
||||||
username: refs[0].current.value,
|
const credentials = {
|
||||||
password: refs[1].current.value
|
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 + ")");
|
||||||
}
|
}
|
||||||
// dispatch(
|
};
|
||||||
// userAuthAsyncThunk.fetchLogin(
|
|
||||||
// pass
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className='form-container'>
|
||||||
<FormGenerator
|
<FormGenerator
|
||||||
inputList={ inputList }
|
inputList={inputList}
|
||||||
refList={ refList }
|
action={login}
|
||||||
action={ login }
|
|
||||||
/>
|
/>
|
||||||
<div className='form_info'>
|
<div className='form_info'>
|
||||||
{ info }
|
{infoMessage && <div className="success-message">{infoMessage}</div>}
|
||||||
|
{errorMessage && <div className="error-message">{errorMessage}</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
|
};
|
||||||
|
|
||||||
}
|
export default UserLogin;
|
||||||
|
|
||||||
export default UserLoginForm
|
|
||||||
|
|
|
||||||
|
|
@ -1,152 +1,161 @@
|
||||||
import React, { useState, useEffect } from 'react'
|
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 { useSelector, useDispatch } from 'react-redux'
|
const UserRegister = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const error = useSelector(selectAuthError);
|
||||||
|
const loading = useSelector(selectAuthLoading);
|
||||||
|
const [infoMessage, setInfoMessage] = useState('');
|
||||||
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
|
|
||||||
// import { userCrudSelector } from '../../../redux/slices/userCrudSlice'
|
const usernameInput = createRef();
|
||||||
// import userCrudAsyncThunk from '../../../redux/asyncThunks/userCrudAsyncThunk'
|
const emailInput = createRef();
|
||||||
|
const passwordInput = createRef();
|
||||||
|
const confirmPasswordInput = createRef();
|
||||||
|
|
||||||
import FormGenerator from '../formGenerator'
|
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}$/;
|
||||||
|
|
||||||
|
const [usernameValidationInfo, setUsernameValidationInfo] = useState("Empty");
|
||||||
|
const [emailValidationInfo, setEmailValidationInfo] = useState("Empty");
|
||||||
|
const [passwordValidationInfo, setPasswordValidationInfo] = useState("Empty");
|
||||||
|
const [confirmPasswordValidationInfo, setConfirmPasswordValidationInfo] = useState("Empty");
|
||||||
|
|
||||||
const UserRegisterForm = () => {
|
const [password, setPassword] = useState("");
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState("");
|
||||||
const usernameInput = React.createRef()
|
const [allowButtonAction, setAllowButtonAction] = useState(false);
|
||||||
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) => {
|
const usernameValidation = (event) => {
|
||||||
if (event.target.value === "") {
|
if (event.target.value === "") {
|
||||||
setUsernameValidationInfo("Email is required.")
|
setUsernameValidationInfo("Login jest wymagany");
|
||||||
} else if(!emailRegex.test(event.target.value)) {
|
|
||||||
setUsernameValidationInfo("Please provide correct email")
|
|
||||||
} else {
|
} 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) => {
|
const passwordValidation = (event) => {
|
||||||
|
setPassword(event.target.value);
|
||||||
setPassword(event.target.value)
|
|
||||||
|
|
||||||
if (event.target.value === "") {
|
if (event.target.value === "") {
|
||||||
setPasswordValidationInfo("Password is required.")
|
setPasswordValidationInfo("Hasło jest wymagane");
|
||||||
} else if(!passwordRegex.test(event.target.value)) {
|
} 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.")
|
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");
|
||||||
} else {
|
} else {
|
||||||
setPasswordValidationInfo("Success")
|
setPasswordValidationInfo("Success");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(event.target.value !== confirmPassword) {
|
if (event.target.value !== confirmPassword) {
|
||||||
setConfirmPasswordValidationInfo("Passwords are different.")
|
setConfirmPasswordValidationInfo("Hasła nie są identyczne");
|
||||||
} else {
|
} else {
|
||||||
setConfirmPasswordValidationInfo("Success")
|
setConfirmPasswordValidationInfo("Success");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const confirmPasswordValidation = (event) => {
|
const confirmPasswordValidation = (event) => {
|
||||||
|
setConfirmPassword(event.target.value);
|
||||||
|
|
||||||
setConfirmPassword(event.target.value)
|
if (event.target.value !== password) {
|
||||||
|
setConfirmPasswordValidationInfo("Hasła nie są identyczne");
|
||||||
if(event.target.value !== password) {
|
|
||||||
setConfirmPasswordValidationInfo("Passwords are different.")
|
|
||||||
} else {
|
} else {
|
||||||
setConfirmPasswordValidationInfo("Success")
|
setConfirmPasswordValidationInfo("Success");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAllowButtonAction(
|
setAllowButtonAction(
|
||||||
usernameValidationInfo === "Success"
|
usernameValidationInfo === "Success" &&
|
||||||
&& passwordValidationInfo === "Success"
|
emailValidationInfo === "Success" &&
|
||||||
&& confirmPasswordValidationInfo === "Success"
|
passwordValidationInfo === "Success" &&
|
||||||
)
|
confirmPasswordValidationInfo === "Success"
|
||||||
}, [
|
);
|
||||||
allowButtonAction,
|
}, [
|
||||||
usernameValidationInfo,
|
usernameValidationInfo,
|
||||||
passwordValidationInfo,
|
emailValidationInfo,
|
||||||
confirmPasswordValidationInfo
|
passwordValidationInfo,
|
||||||
]
|
confirmPasswordValidationInfo
|
||||||
)
|
]);
|
||||||
|
|
||||||
|
|
||||||
// const dispatch = useDispatch()
|
const inputList = [
|
||||||
// const { info } = useSelector( userCrudSelector )
|
|
||||||
const info = "" // if redux is integrated - delete this line
|
|
||||||
|
|
||||||
let refList = [
|
|
||||||
usernameInput,
|
|
||||||
passwordInput,
|
|
||||||
confirmPasswordInput
|
|
||||||
]
|
|
||||||
|
|
||||||
let inputList = [
|
|
||||||
{
|
{
|
||||||
type: 'info',
|
type: 'info',
|
||||||
action: 'Create',
|
action: 'Register',
|
||||||
endpint: 'user/auth/register',
|
endpoint: 'auth',
|
||||||
button_value: 'SIGN UP',
|
button_value: loading ? 'REJESTRACJA...' : 'ZAREJESTRUJ',
|
||||||
allowButtonAction: allowButtonAction
|
allowButtonAction: allowButtonAction
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
name: 'EMAIL',
|
name: 'LOGIN',
|
||||||
ref: usernameInput,
|
ref: usernameInput,
|
||||||
onChange: usernameValidation,
|
onChange: usernameValidation,
|
||||||
validationInfo: usernameValidationInfo
|
validationInfo: usernameValidationInfo
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'EMAIL',
|
||||||
|
ref: emailInput,
|
||||||
|
onChange: emailValidation,
|
||||||
|
validationInfo: emailValidationInfo
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'password',
|
type: 'password',
|
||||||
name: 'PASSWORD',
|
name: 'HASŁO',
|
||||||
ref: passwordInput,
|
ref: passwordInput,
|
||||||
onChange: passwordValidation,
|
onChange: passwordValidation,
|
||||||
validationInfo: passwordValidationInfo
|
validationInfo: passwordValidationInfo
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'password',
|
type: 'password',
|
||||||
name: 'CONFIRM PASSWORD',
|
name: 'POTWIERDŹ HASŁO',
|
||||||
ref: confirmPasswordInput,
|
ref: confirmPasswordInput,
|
||||||
onChange: confirmPasswordValidation,
|
onChange: confirmPasswordValidation,
|
||||||
validationInfo: confirmPasswordValidationInfo
|
validationInfo: confirmPasswordValidationInfo
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
|
||||||
const register = async ( refs ) => {
|
const register = async (formData) => {
|
||||||
let pass = {
|
try {
|
||||||
username: refs[0].current.value,
|
const userData = {
|
||||||
password: refs[1].current.value
|
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 + ")");
|
||||||
}
|
}
|
||||||
// dispatch(
|
};
|
||||||
// userCrudAsyncThunk.fetchRegister(
|
|
||||||
// pass
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className='form-container'>
|
||||||
<FormGenerator
|
<FormGenerator
|
||||||
inputList={ inputList }
|
inputList={inputList}
|
||||||
refList={ refList }
|
action={register}
|
||||||
action={ register }
|
|
||||||
/>
|
/>
|
||||||
<div className='form_info'>
|
<div className='form_info'>
|
||||||
{ info }
|
{infoMessage && <div className="success-message">{infoMessage}</div>}
|
||||||
|
{errorMessage && <div className="error-message">{errorMessage}</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
|
};
|
||||||
|
|
||||||
}
|
export default UserRegister;
|
||||||
|
|
||||||
export default UserRegisterForm
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
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;
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
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;
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
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;
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// 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: '/'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export const useDashboardList = (initialItems = []) => {
|
||||||
|
const [selectedItem, setSelectedItem] = useState(null);
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [items, setItems] = useState(initialItems);
|
||||||
|
const [isFormVisible, setIsFormVisible] = useState(false);
|
||||||
|
const [formMode, setFormMode] = useState('create');
|
||||||
|
const [message, setMessage] = useState({ type: '', text: '' });
|
||||||
|
|
||||||
|
const handleSearch = (query) => {
|
||||||
|
setSearchQuery(query);
|
||||||
|
// Implementacja wyszukiwania w zależności od typu elementów
|
||||||
|
const filteredItems = initialItems.filter(item =>
|
||||||
|
Object.values(item).some(value =>
|
||||||
|
String(value).toLowerCase().includes(query.toLowerCase())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setItems(filteredItems);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleItemSelect = (item) => {
|
||||||
|
setSelectedItem(item);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFormToggle = (mode = 'create', item = null) => {
|
||||||
|
setFormMode(mode);
|
||||||
|
setSelectedItem(item);
|
||||||
|
setIsFormVisible(!isFormVisible);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMessage = (type, text) => {
|
||||||
|
setMessage({ type, text });
|
||||||
|
setTimeout(() => {
|
||||||
|
setMessage({ type: '', text: '' });
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedItem,
|
||||||
|
searchQuery,
|
||||||
|
items,
|
||||||
|
isFormVisible,
|
||||||
|
formMode,
|
||||||
|
message,
|
||||||
|
handleSearch,
|
||||||
|
handleItemSelect,
|
||||||
|
handleFormToggle,
|
||||||
|
handleMessage,
|
||||||
|
setItems
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -1,21 +1,36 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import '../styles/general.scss';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import '@fortawesome/fontawesome-free/css/all.min.css';
|
import { navigate } from 'gatsby';
|
||||||
|
import { logoutUser, checkAuth } from '../redux/asyncThunks/userAuthAsyncThunk';
|
||||||
|
import { selectIsAuthenticated, selectUserPermissions } from '../redux/slices/userAuthSlice';
|
||||||
|
|
||||||
import FootComponent from '../components/foot.js';
|
|
||||||
import NavBarComponent from '../components/navbar.js';
|
import NavBarComponent from '../components/navbar.js';
|
||||||
|
import FootComponent from '../components/foot.js';
|
||||||
import ThreeDModelsDashboard from './dashboards/3d-models.js';
|
|
||||||
import AIModelsDashboard from './dashboards/ai.models.js';
|
import AIModelsDashboard from './dashboards/ai.models.js';
|
||||||
import AITasksDashboard from './dashboards/ai.tasks.js';
|
import AITasksDashboard from './dashboards/ai.tasks.js';
|
||||||
import RendersDashboard from './dashboards/renders.js';
|
import RendersDashboard from './dashboards/renders.js';
|
||||||
import ServersDashboard from './dashboards/servers.js';
|
import ServersDashboard from './dashboards/servers.js';
|
||||||
import UserSettings from './dashboards/user.js';
|
import UserSettingsDashboard from './dashboards/user.js';
|
||||||
|
import ThreeDModelsDashboard from './dashboards/3d-models.js';
|
||||||
|
|
||||||
const DashboardPage = () => {
|
const DashboardPage = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const isAuthenticated = useSelector(selectIsAuthenticated);
|
||||||
|
const permissions = useSelector(selectUserPermissions);
|
||||||
|
|
||||||
const icons_size = "fa-2x";
|
const icons_size = "fa-2x";
|
||||||
const [activeComponent, setActiveComponent] = useState('3d-models');
|
const [activeComponent, setActiveComponent] = useState('3d-models');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
dispatch(checkAuth())
|
||||||
|
.unwrap()
|
||||||
|
.catch(() => {
|
||||||
|
navigate('/auth/login');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [dispatch, isAuthenticated]);
|
||||||
|
|
||||||
const handleNavigation = (path) => {
|
const handleNavigation = (path) => {
|
||||||
setActiveComponent(path);
|
setActiveComponent(path);
|
||||||
};
|
};
|
||||||
|
|
@ -24,10 +39,13 @@ const DashboardPage = () => {
|
||||||
return activeComponent === path;
|
return activeComponent === path;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = async () => {
|
||||||
// TODO: Implement proper logout logic (clear tokens, session, etc.)
|
try {
|
||||||
console.log('Logging out...');
|
await dispatch(logoutUser()).unwrap();
|
||||||
window.location.href = '/login';
|
navigate('/auth/login');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Błąd podczas wylogowywania:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
|
|
@ -41,7 +59,7 @@ const DashboardPage = () => {
|
||||||
case 'servers':
|
case 'servers':
|
||||||
return <ServersDashboard />;
|
return <ServersDashboard />;
|
||||||
case 'settings':
|
case 'settings':
|
||||||
return <UserSettings />;
|
return <UserSettingsDashboard />;
|
||||||
case '3d-models':
|
case '3d-models':
|
||||||
default:
|
default:
|
||||||
return <ThreeDModelsDashboard />;
|
return <ThreeDModelsDashboard />;
|
||||||
|
|
|
||||||
|
|
@ -1,193 +1,42 @@
|
||||||
import React, { useState, useRef } from 'react';
|
import React from 'react';
|
||||||
import FormGenerator from '../../components/forms/formGenerator';
|
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';
|
||||||
|
|
||||||
const UserSettings = () => {
|
const UserSettingsDashboard = () => {
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const user = useSelector(selectCurrentUser);
|
||||||
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 (
|
return (
|
||||||
<div className="list-container">
|
<div className="dashboard-container">
|
||||||
<div className="user-settings">
|
<div className="dashboard-header">
|
||||||
<div className="settings-header">
|
<h1>Panel Użytkownika</h1>
|
||||||
<h2>User Settings</h2>
|
<div className="user-info">
|
||||||
<button
|
<span>Zalogowany jako: {user?.login}</span>
|
||||||
className={`edit-button ${isEditing ? 'cancel' : ''}`}
|
|
||||||
onClick={() => setIsEditing(!isEditing)}
|
|
||||||
>
|
|
||||||
{isEditing ? 'Cancel' : 'Edit'}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{message.text && (
|
<div className="dashboard-content">
|
||||||
<div className={`message ${message.type}`}>
|
<div className="user-settings">
|
||||||
{message.text}
|
<div className="settings-section">
|
||||||
|
<h2>Aktualizacja profilu</h2>
|
||||||
|
<UserUpdateProfileForm />
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{isEditing && (
|
<div className="settings-section">
|
||||||
<div className="form-overlay">
|
<h2>Zmiana hasła</h2>
|
||||||
<div className="form-container">
|
<UserChangePasswordForm />
|
||||||
<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>
|
||||||
)}
|
|
||||||
|
<div className="settings-section">
|
||||||
|
<h2>Usuwanie konta</h2>
|
||||||
|
<UserDeleteAccountForm />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UserSettings;
|
export default UserSettingsDashboard;
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,179 @@
|
||||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
import axios from 'axios';
|
import { setCredentials, setLoading, setError, logout } from '../slices/userAuthSlice';
|
||||||
|
import { API_URL, AUTH_CONFIG } from '../../config';
|
||||||
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
|
import { cookieService } from '../../services/cookieService';
|
||||||
|
|
||||||
export const loginUser = createAsyncThunk(
|
export const loginUser = createAsyncThunk(
|
||||||
'userAuth/login',
|
'userAuth/login',
|
||||||
async (credentials) => {
|
async (credentials, { dispatch }) => {
|
||||||
const formData = new FormData();
|
try {
|
||||||
formData.append('username', credentials.username);
|
dispatch(setLoading(true));
|
||||||
formData.append('password', credentials.password);
|
const response = await fetch(`${API_URL}/auth/login`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(credentials),
|
||||||
|
});
|
||||||
|
|
||||||
const response = await axios.post(`${API_URL}/auth`, formData);
|
const data = await response.json();
|
||||||
return response.data;
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const registerUser = createAsyncThunk(
|
export const registerUser = createAsyncThunk(
|
||||||
'userAuth/register',
|
'userAuth/register',
|
||||||
async (userData) => {
|
async (userData, { dispatch }) => {
|
||||||
const response = await axios.post(`${API_URL}/register`, userData);
|
try {
|
||||||
return response.data;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const changePassword = createAsyncThunk(
|
export const changePassword = createAsyncThunk(
|
||||||
'userAuth/changePassword',
|
'userAuth/changePassword',
|
||||||
async (passwordData) => {
|
async (passwordData) => {
|
||||||
const response = await axios.post(`${API_URL}/change-password`, passwordData);
|
const response = await fetch(`${API_URL}/change-password`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(passwordData),
|
||||||
|
});
|
||||||
return response.data;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -1,72 +1,54 @@
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import { loginUser, registerUser, changePassword } from '../asyncThunks/userAuthAsyncThunk';
|
import { cookieService } from '../../services/cookieService';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
user: null,
|
user: cookieService.getUserData() || null,
|
||||||
token: null,
|
token: cookieService.getToken() || null,
|
||||||
isLoading: false,
|
isAuthenticated: !!cookieService.getToken(),
|
||||||
error: null
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
permissions: []
|
||||||
};
|
};
|
||||||
|
|
||||||
const userAuthSlice = createSlice({
|
const userAuthSlice = createSlice({
|
||||||
name: 'userAuth',
|
name: 'userAuth',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
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) => {
|
logout: (state) => {
|
||||||
state.user = null;
|
state.user = null;
|
||||||
state.token = null;
|
state.token = null;
|
||||||
state.error = null;
|
state.isAuthenticated = false;
|
||||||
|
state.permissions = [];
|
||||||
|
cookieService.clearAll();
|
||||||
|
},
|
||||||
|
setLoading: (state, { payload }) => {
|
||||||
|
state.loading = payload;
|
||||||
|
},
|
||||||
|
setError: (state, { payload }) => {
|
||||||
|
state.error = payload;
|
||||||
},
|
},
|
||||||
clearError: (state) => {
|
clearError: (state) => {
|
||||||
state.error = null;
|
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 { logout, clearError } = userAuthSlice.actions;
|
export const { setCredentials, logout, setLoading, setError, clearError } = userAuthSlice.actions;
|
||||||
export const userAuthSelector = (state) => state.userAuth;
|
|
||||||
|
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 default userAuthSlice.reducer;
|
export default userAuthSlice.reducer;
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
@import '@fortawesome/fontawesome-free/css/all.min.css';
|
||||||
|
|
||||||
// colors
|
// colors
|
||||||
|
|
||||||
$first-color: rgba(0, 90, 25, 1);
|
$first-color: rgba(0, 90, 25, 1);
|
||||||
|
|
@ -7,6 +9,7 @@ $error-color: #dc3545;
|
||||||
$in-progress-color: #ffa500;
|
$in-progress-color: #ffa500;
|
||||||
$queued-color: #fbff00;
|
$queued-color: #fbff00;
|
||||||
$success-color: $first-color;
|
$success-color: $first-color;
|
||||||
|
$card-background: rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
$title-color: white;
|
$title-color: white;
|
||||||
$subtitle-color: #a6a6a6;
|
$subtitle-color: #a6a6a6;
|
||||||
|
|
@ -805,6 +808,13 @@ body, html {
|
||||||
filter: invert(8%) sepia(63%) saturate(4888%) hue-rotate(8deg) brightness(113%) contrast(112%);
|
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 {
|
.popup {
|
||||||
|
|
@ -980,12 +990,6 @@ body, html {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form_info {
|
|
||||||
float: float;
|
|
||||||
position: absolute;
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.float_form_render_sync {
|
.float_form_render_sync {
|
||||||
|
|
@ -1146,119 +1150,108 @@ body, html {
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-settings {
|
.user-settings {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
color: $secondary-color;
|
max-width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
.settings-header {
|
.settings-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 30px;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
color: $title-color;
|
margin: 0;
|
||||||
margin: 0;
|
color: $title-color;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-button {
|
.settings-actions {
|
||||||
background: $first-color;
|
display: flex;
|
||||||
color: white;
|
gap: 16px;
|
||||||
border: none;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
&.cancel {
|
.edit-button {
|
||||||
background: #dc3545;
|
padding: 8px 16px;
|
||||||
}
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: $first-color;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-2px);
|
background: darken($first-color, 10%);
|
||||||
&.cancel {
|
}
|
||||||
background: darken(#dc3545, 10%);
|
|
||||||
|
&.cancel {
|
||||||
|
background: $error-color;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: darken($error-color, 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&:not(.cancel) {
|
|
||||||
background: darken($first-color, 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%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message {
|
.settings-sections-container {
|
||||||
padding: 10px;
|
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;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border-radius: 5px;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
&.success {
|
&.success {
|
||||||
background: rgba(0, 120, 0, 0.2);
|
background: rgba($success-color, 0.1);
|
||||||
color: #00ff00;
|
color: $success-color;
|
||||||
|
border: 1px solid $success-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.error {
|
&.error {
|
||||||
background: rgba(220, 53, 69, 0.2);
|
background: rgba($error-color, 0.1);
|
||||||
color: #ff4444;
|
color: $error-color;
|
||||||
|
border: 1px solid $error-color;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.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 {
|
.task-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -1557,3 +1550,63 @@ body, html {
|
||||||
.dashboard-content {
|
.dashboard-content {
|
||||||
min-height: 100%; /* Wymusza pojawienie się suwaka */
|
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%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue