Commit a5376c4c authored by Pavel Mishakov's avatar Pavel Mishakov

lesson 91 done

parent 880b9f02
......@@ -5,6 +5,8 @@ import ProductsPage from "./containers/ProductsPage/ProductsPage";
import Login from "./containers/Login/Login";
import Register from "./containers/Register/Register";
import PrivateRoute from "./utils/PrivateRoute";
import UserForm from "./components/UserForm/UserForm";
import ForgotPasswordForm from "./components/ForgotPasswordForm/ForgotPasswordForm";
const App = () => {
return (
......@@ -14,10 +16,12 @@ const App = () => {
<Route path={"/"} element={<h1>HOME</h1>} />
<Route path={"/products"} element={<ProductsPage />} />
<Route path={"/add-product"} element={<ProductFormPage />} />
<Route path={"/edit-user"} element={<UserForm />} />
</Route>
</Route>
<Route path={"/login"} element={<Login />} />
<Route path={"/register"} element={<Register />} />
<Route path={"/forgot-password"} element={<ForgotPasswordForm />} />
</Routes>
);
};
......
......@@ -50,6 +50,35 @@ class UserApi {
return response
}
}
public editUser = async (user: IUserCreateDto): Promise<IResponse<IUserGetDto | undefined>> => {
try {
const response = await instance.put(`/users`, user)
return response.data
} catch (err: unknown) {
const error = err as Error
const response: IResponse<undefined> = {
status: EStatuses.NOT_OK,
result: undefined,
message: error.message
}
return response
}
}
public sendEmailPassword = async (email: string): Promise<IResponse<undefined>> => {
try {
const response = await instance.post(`/users/forgot-password`, {email})
return response.data
} catch (err: unknown) {
const error = err as Error
const response: IResponse<undefined> = {
status: EStatuses.NOT_OK,
result: undefined,
message: error.message
}
return response
}
}
}
export const userApi = new UserApi()
\ No newline at end of file
.ForgotPasswordForm {
width: 80%;
margin: 20px auto;
display: flex;
align-items: center;
flex-direction: column;
}
.ForgotPasswordForm__input {
margin: 10px 0;
min-height: 20px;
border-radius: 7px;
padding: 10px;
min-width: 400px;
}
.ForgotPasswordForm__button {
background: inherit;
border: 1px solid blueviolet;
padding: 10px 15px;
border-radius: 5px;
}
.ForgotPasswordForm__fileInput {
display: none;
}
\ No newline at end of file
import React, { ChangeEvent, FormEvent, FunctionComponent, ReactElement, useState } from "react";
import { shallowEqual, useDispatch, useSelector } from "react-redux";
import IProductDto from "../../interfaces/IProductDto";
import IUserCreateDto from "../../interfaces/IUserCreateDto";
import { createProduct } from "../../store/products/products.slice";
import { AppDispatch, AppState } from "../../store/store";
import { editUser, sendEmailPassword } from "../../store/users/users.slice";
import styles from './ForgotPasswordForm.module.css'
const ForgotPasswordForm: FunctionComponent = (): ReactElement => {
const dispatch: AppDispatch = useDispatch()
const [errorMessage, setErrorMessage] = useState<string>('')
const [email, setEmail] = useState<string>('')
const inputHandler = (e: ChangeEvent<HTMLInputElement>): void => {
setEmail(e.target.value)
}
const submitHandler = (e: FormEvent) => {
e.preventDefault()
dispatch(sendEmailPassword(email))
}
return (
<div className={styles.ForgotPasswordForm__frame}>
<form className={styles.ForgotPasswordForm} onSubmit={submitHandler}>
<h6>{errorMessage}</h6>
<input
name={'Email'}
type="email"
placeholder="Email"
value={email}
onChange={inputHandler}
className={styles.ForgotPasswordForm__input}
/>
<button className={styles.ForgotPasswordForm__button}>SEND</button>
</form>
</div>
)
}
export default ForgotPasswordForm
\ No newline at end of file
.Header {
padding: 10px 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.Header__link {
......
import React, { FunctionComponent, ReactElement } from "react";
import { shallowEqual, useSelector } from "react-redux";
import { NavLink } from "react-router-dom";
import { AppState } from "../../store/store";
import { shallowEqual, useDispatch, useSelector } from "react-redux";
import { NavLink, useNavigate } from "react-router-dom";
import { AppDispatch, AppState } from "../../store/store";
import { initState } from "../../store/users/users.slice";
import styles from "./Header.module.css";
const Header: FunctionComponent = (): ReactElement => {
const {user} = useSelector((state: AppState) => state.users, shallowEqual)
const navigate = useNavigate()
const dispatch: AppDispatch = useDispatch()
const logoutHandler = () => {
localStorage.removeItem('token')
dispatch(initState())
navigate('/login')
}
return (
<header className={styles.Header}>
<>
<NavLink className={styles.Header__link} to={"/add-product"}>
Add product
</NavLink>
<NavLink className={styles.Header__link} to={"/products"}>
Products
</NavLink>
</>
<NavLink className={styles.Header__link} to={"/edit-user"}>
Edit user
</NavLink>
<div>
{user?.username}
</div>
<button onClick={logoutHandler}>Logout</button>
</header>
);
};
......
.UserForm {
width: 80%;
margin: 20px auto;
display: flex;
align-items: center;
flex-direction: column;
}
.UserForm__input {
margin: 10px 0;
min-height: 20px;
border-radius: 7px;
padding: 10px;
min-width: 400px;
}
.UserForm__button {
background: inherit;
border: 1px solid blueviolet;
padding: 10px 15px;
border-radius: 5px;
}
.UserForm__fileInput {
display: none;
}
\ No newline at end of file
import React, { ChangeEvent, FormEvent, FunctionComponent, ReactElement, useState } from "react";
import { shallowEqual, useDispatch, useSelector } from "react-redux";
import IProductDto from "../../interfaces/IProductDto";
import IUserCreateDto from "../../interfaces/IUserCreateDto";
import { createProduct } from "../../store/products/products.slice";
import { AppDispatch, AppState } from "../../store/store";
import { editUser } from "../../store/users/users.slice";
import styles from './UserForm.module.css'
const UserForm: FunctionComponent = (): ReactElement => {
const dispatch: AppDispatch = useDispatch()
const [isChangingPassword, setIsChangingPassword] = useState<boolean>(false)
const [errorMessage, setErrorMessage] = useState<string>('')
const {user} = useSelector((state: AppState) => state.users, shallowEqual)
const [userValues, setUserValues] = useState<IUserCreateDto & {password_repeat: string}>({
username: user?.username || '',
password: '',
password_repeat: ''
})
const inputHandler = (e: ChangeEvent<HTMLInputElement>): void => {
setUserValues(prevState => {
return {...prevState, [e.target.name]: e.target.value}
})
}
const submitHandler = (e: FormEvent) => {
e.preventDefault()
console.log('isChangingPassword', isChangingPassword)
if (isChangingPassword && userValues.password !== userValues.password_repeat) {
setErrorMessage('Passwords are different')
return
}
if (userValues.username.trim() === '') {
setErrorMessage('Username cannot be empty')
return
}
dispatch(editUser({
username: userValues.username,
password: isChangingPassword ? userValues.password : ''
}))
}
const toggleChangePassword = () => {
setIsChangingPassword(!isChangingPassword)
}
return (
<div className={styles.UserForm__frame}>
<form className={styles.UserForm} onSubmit={submitHandler}>
<h6>{errorMessage}</h6>
<input
name={'username'}
type="text"
placeholder="Username"
value={userValues.username}
onChange={inputHandler}
className={styles.UserForm__input}
/>
<label>
<input type="checkbox"
onChange={toggleChangePassword}
checked={isChangingPassword} />
Change password?
</label>
<input
name={'password'}
type="text"
disabled={!isChangingPassword}
placeholder="Password"
value={userValues.password}
onChange={inputHandler}
className={styles.UserForm__input}
/>
<input
name={'password_repeat'}
type="text"
disabled={!isChangingPassword}
placeholder="Repeat password"
value={userValues.password_repeat}
onChange={inputHandler}
className={styles.UserForm__input}
/>
<button className={styles.UserForm__button}>SEND</button>
</form>
</div>
)
}
export default UserForm
\ No newline at end of file
......@@ -40,6 +40,8 @@ const Login: React.FunctionComponent = (): React.ReactElement => {
<input onChange={inputHandler} value={values.password} placeholder='Password' className={styles.Login__input} type="password" name={'password'} required />
<button className={styles.Login__button}>Sign in</button>
<Link to={'/register'} className={styles.Login__button}>Sign up now</Link>
<Link to={'/forgot-password'} className={styles.Login__button}>Forgot password</Link>
</form>
</div>
</>
......
......@@ -29,16 +29,35 @@ export const checkToken = createAppAsyncThunk(
}
)
export const editUser = createAppAsyncThunk(
`${namespace}/editUser`,
async (user: IUserCreateDto) => {
return userApi.editUser(user)
}
)
export const usersSlice = createSlice({
name: namespace,
initialState: {
export const sendEmailPassword = createAppAsyncThunk(
`${namespace}/sendEmailPassword`,
async (email: string) => {
return userApi.sendEmailPassword(email)
}
)
const initialState: IUsersState = {
user: {} as IUser,
isAuth: false,
loadingUser: false,
messageUser: ''
} as IUsersState,
reducers: {},
}
export const usersSlice = createSlice({
name: namespace,
initialState: initialState,
reducers: {
initState(state) {
state = initialState
}
},
extraReducers: (builder) => {
builder
.addCase(login.rejected, (state) => {
......@@ -90,7 +109,29 @@ export const usersSlice = createSlice({
state.isAuth = false
}
})
.addCase(editUser.rejected, (state) => {
state.loadingUser = false
})
.addCase(editUser.pending, (state) => {
state.loadingUser = true
})
.addCase(editUser.fulfilled, (state, action) => {
state.loadingUser = false
const user = action.payload.result
state.user = user
state.messageUser = action.payload.message
})
.addCase(sendEmailPassword.rejected, (state) => {
state.loadingUser = false
})
.addCase(sendEmailPassword.pending, (state) => {
state.loadingUser = true
})
.addCase(sendEmailPassword.fulfilled, (state, action) => {
state.loadingUser = false
state.messageUser = action.payload.message
})
}
})
export const {} = usersSlice.actions
\ No newline at end of file
export const {initState} = usersSlice.actions
\ No newline at end of file
......@@ -5,6 +5,6 @@ import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
port: 3000
port: 3001
}
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment