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"; ...@@ -5,6 +5,8 @@ import ProductsPage from "./containers/ProductsPage/ProductsPage";
import Login from "./containers/Login/Login"; import Login from "./containers/Login/Login";
import Register from "./containers/Register/Register"; import Register from "./containers/Register/Register";
import PrivateRoute from "./utils/PrivateRoute"; import PrivateRoute from "./utils/PrivateRoute";
import UserForm from "./components/UserForm/UserForm";
import ForgotPasswordForm from "./components/ForgotPasswordForm/ForgotPasswordForm";
const App = () => { const App = () => {
return ( return (
...@@ -14,10 +16,12 @@ const App = () => { ...@@ -14,10 +16,12 @@ const App = () => {
<Route path={"/"} element={<h1>HOME</h1>} /> <Route path={"/"} element={<h1>HOME</h1>} />
<Route path={"/products"} element={<ProductsPage />} /> <Route path={"/products"} element={<ProductsPage />} />
<Route path={"/add-product"} element={<ProductFormPage />} /> <Route path={"/add-product"} element={<ProductFormPage />} />
<Route path={"/edit-user"} element={<UserForm />} />
</Route> </Route>
</Route> </Route>
<Route path={"/login"} element={<Login />} /> <Route path={"/login"} element={<Login />} />
<Route path={"/register"} element={<Register />} /> <Route path={"/register"} element={<Register />} />
<Route path={"/forgot-password"} element={<ForgotPasswordForm />} />
</Routes> </Routes>
); );
}; };
......
...@@ -50,6 +50,35 @@ class UserApi { ...@@ -50,6 +50,35 @@ class UserApi {
return response 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() 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 { .Header {
padding: 10px 0; padding: 10px 0;
display: flex;
justify-content: space-between;
align-items: center;
} }
.Header__link { .Header__link {
......
import React, { FunctionComponent, ReactElement } from "react"; import React, { FunctionComponent, ReactElement } from "react";
import { shallowEqual, useSelector } from "react-redux"; import { shallowEqual, useDispatch, useSelector } from "react-redux";
import { NavLink } from "react-router-dom"; import { NavLink, useNavigate } from "react-router-dom";
import { AppState } from "../../store/store"; import { AppDispatch, AppState } from "../../store/store";
import { initState } from "../../store/users/users.slice";
import styles from "./Header.module.css"; import styles from "./Header.module.css";
const Header: FunctionComponent = (): ReactElement => { 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 ( return (
<header className={styles.Header}> <header className={styles.Header}>
<>
<NavLink className={styles.Header__link} to={"/add-product"}> <NavLink className={styles.Header__link} to={"/add-product"}>
Add product Add product
</NavLink> </NavLink>
<NavLink className={styles.Header__link} to={"/products"}> <NavLink className={styles.Header__link} to={"/products"}>
Products Products
</NavLink> </NavLink>
</> <NavLink className={styles.Header__link} to={"/edit-user"}>
Edit user
</NavLink>
<div>
{user?.username}
</div>
<button onClick={logoutHandler}>Logout</button>
</header> </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 => { ...@@ -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 /> <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> <button className={styles.Login__button}>Sign in</button>
<Link to={'/register'} className={styles.Login__button}>Sign up now</Link> <Link to={'/register'} className={styles.Login__button}>Sign up now</Link>
<Link to={'/forgot-password'} className={styles.Login__button}>Forgot password</Link>
</form> </form>
</div> </div>
</> </>
......
...@@ -29,16 +29,35 @@ export const checkToken = createAppAsyncThunk( ...@@ -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({ export const sendEmailPassword = createAppAsyncThunk(
name: namespace, `${namespace}/sendEmailPassword`,
initialState: { async (email: string) => {
return userApi.sendEmailPassword(email)
}
)
const initialState: IUsersState = {
user: {} as IUser, user: {} as IUser,
isAuth: false, isAuth: false,
loadingUser: false, loadingUser: false,
messageUser: '' messageUser: ''
} as IUsersState, }
reducers: {},
export const usersSlice = createSlice({
name: namespace,
initialState: initialState,
reducers: {
initState(state) {
state = initialState
}
},
extraReducers: (builder) => { extraReducers: (builder) => {
builder builder
.addCase(login.rejected, (state) => { .addCase(login.rejected, (state) => {
...@@ -90,7 +109,29 @@ export const usersSlice = createSlice({ ...@@ -90,7 +109,29 @@ export const usersSlice = createSlice({
state.isAuth = false 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 export const {initState} = usersSlice.actions
\ No newline at end of file \ No newline at end of file
...@@ -5,6 +5,6 @@ import react from '@vitejs/plugin-react' ...@@ -5,6 +5,6 @@ import react from '@vitejs/plugin-react'
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
server: { 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