login form
This commit is contained in:
parent
9d39ae2de3
commit
5c78c45879
8 changed files with 222 additions and 29 deletions
81
package-lock.json
generated
81
package-lock.json
generated
|
|
@ -8,6 +8,7 @@
|
|||
"name": "wokapp-ui",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.9",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^7.0.2"
|
||||
|
|
@ -3309,6 +3310,12 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
|
|
@ -3373,6 +3380,17 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.9",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-polyfill-corejs2": {
|
||||
"version": "0.4.12",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz",
|
||||
|
|
@ -3784,6 +3802,18 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
|
|
@ -4084,6 +4114,15 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
|
|
@ -4713,6 +4752,26 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
||||
|
|
@ -4753,6 +4812,20 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
|
||||
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||
|
|
@ -6019,7 +6092,6 @@
|
|||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
|
|
@ -6032,7 +6104,6 @@
|
|||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
|
|
@ -6642,6 +6713,12 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.9",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^7.0.2"
|
||||
|
|
|
|||
19
src/api.tsx
Normal file
19
src/api.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import axios from "axios";
|
||||
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: "http://localhost:5079/api",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
api.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
}, (err) => Promise.reject(err));
|
||||
|
||||
export default api;
|
||||
|
|
@ -2,13 +2,18 @@ import {FC, ReactNode} from "react";
|
|||
|
||||
interface ButtonProps {
|
||||
children: ReactNode | null;
|
||||
onClick: () => void;
|
||||
onClick?: () => void;
|
||||
className: string | "";
|
||||
disabled?: boolean;
|
||||
type?: "submit"|"button";
|
||||
}
|
||||
|
||||
const Button: FC<ButtonProps> = ({children, onClick, className}) => {
|
||||
const Button: FC<ButtonProps> = ({children, onClick, className, disabled, type}) => {
|
||||
return <>
|
||||
<button className={"bg-blue-500 text-white p-2 rounded block " + className} onClick={onClick}>
|
||||
<button className={"bg-blue-500 text-white p-2 rounded block disabled:bg-gray-300 disabled:cursor-not-allowed " + className}
|
||||
onClick={onClick}
|
||||
type={ type ? type : 'button'}
|
||||
disabled={disabled}>
|
||||
{ children }
|
||||
</button>
|
||||
</>
|
||||
|
|
|
|||
23
src/components/UI/HeaderMenu.tsx
Normal file
23
src/components/UI/HeaderMenu.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import {useAuth} from "../../context/AuthContext.tsx";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
|
||||
const HeaderMenu = () => {
|
||||
const auth = useAuth()
|
||||
const navigate = useNavigate();
|
||||
function logout() {
|
||||
auth.logout()
|
||||
navigate('/');
|
||||
}
|
||||
return <>
|
||||
<div className={"flex p-1"}>
|
||||
<div className={"grow"}>
|
||||
Menu will be placed here
|
||||
</div>
|
||||
<div>
|
||||
<a onClick={logout} className={"underline cursor-pointer"}>Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
export default HeaderMenu;
|
||||
|
|
@ -2,16 +2,21 @@ import React, { createContext, useState, useContext } from 'react';
|
|||
|
||||
const AuthContext = createContext({
|
||||
isAuthenticated: false,
|
||||
login: () => {},
|
||||
login: (_token: string) => {},
|
||||
logout: () => {}
|
||||
});
|
||||
|
||||
export const AuthProvider= ({ children }: { children: React.ReactNode }) => {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
|
||||
const login = () => setIsAuthenticated(true);
|
||||
const logout = () => setIsAuthenticated(false);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(!!localStorage.getItem('token'));
|
||||
|
||||
const login = (token: string) => {
|
||||
localStorage.setItem('token', token)
|
||||
setIsAuthenticated(true);
|
||||
}
|
||||
const logout = () => {
|
||||
localStorage.removeItem('token');
|
||||
setIsAuthenticated(false);
|
||||
}
|
||||
return (
|
||||
<AuthContext.Provider value={{ isAuthenticated, login, logout }}>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import HeaderMenu from "../components/UI/HeaderMenu.tsx";
|
||||
|
||||
function Home() {
|
||||
return <><h1 className="font-bold underline">Home</h1></>
|
||||
return <>
|
||||
<HeaderMenu />
|
||||
<h1 className="font-bold underline">Home</h1>
|
||||
</>
|
||||
}
|
||||
|
||||
export default Home;
|
||||
|
|
@ -1,11 +1,49 @@
|
|||
import Input from "../components/UI/Input.tsx";
|
||||
import Button from "../components/UI/Button.tsx";
|
||||
import {useState} from "react";
|
||||
import React, {useState} from "react";
|
||||
import api from "../api.tsx";
|
||||
import {useAuth} from "../context/AuthContext.tsx";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
|
||||
|
||||
export function LoginPage() {
|
||||
const [login,setLogin] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [showWrongUserNameAndPasswordError, setShowWrongUserNameAndPasswordError] = useState(false);
|
||||
const [registerResultMessage, setRegisterResultMessage] = useState('');
|
||||
const auth = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
function loginFormSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
api.post('user/login', {
|
||||
username: login,
|
||||
password: password,
|
||||
}).then((response) => {
|
||||
auth.login(response.data.token);
|
||||
setShowWrongUserNameAndPasswordError(false);
|
||||
navigate('/');
|
||||
}).catch((err) => {
|
||||
if(err.status === 401) {
|
||||
setShowWrongUserNameAndPasswordError(true);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function registerBtnClick() {
|
||||
api.post('user/register', {
|
||||
username: login,
|
||||
password: password,
|
||||
}).then((response) => {
|
||||
if(response.status === 201) {
|
||||
setRegisterResultMessage('User registered successfully. You can now log in.');
|
||||
setShowWrongUserNameAndPasswordError(false);
|
||||
}
|
||||
}).catch(() => {
|
||||
setRegisterResultMessage('Something went wrong');
|
||||
});
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className={"flex flex-col items-center mb-4 pt-4"}>
|
||||
<div className={"lg:w-80 sm:w-3/4"}>
|
||||
|
|
@ -17,24 +55,44 @@ export function LoginPage() {
|
|||
</div>
|
||||
<div className={"flex flex-col items-center"}>
|
||||
<div className={"lg:w-80 sm:w-3/4"}>
|
||||
<Input type="text"
|
||||
label="Login"
|
||||
placeholder="Enter your login"
|
||||
value={login}
|
||||
name={"login"}
|
||||
onChange={(e) => setLogin(e.target.value) }
|
||||
error={false} />
|
||||
{ showWrongUserNameAndPasswordError && (
|
||||
<div className={"p-4 rounded bg-red-200 text-black"}>
|
||||
Wrong username or password
|
||||
</div>
|
||||
)}
|
||||
{ registerResultMessage && (
|
||||
<div className={"p-4 rounded bg-blue-800 text-white"}>
|
||||
{registerResultMessage}
|
||||
</div>
|
||||
)}
|
||||
<form onSubmit={loginFormSubmit}>
|
||||
<Input type="text"
|
||||
label="Login"
|
||||
placeholder="Enter your login"
|
||||
value={login}
|
||||
name={"login"}
|
||||
onChange={(e) => setLogin(e.target.value) }
|
||||
error={false} />
|
||||
|
||||
<Input type={"password"}
|
||||
label={"Password"}
|
||||
value={password}
|
||||
placeholder={"Enter your password"}
|
||||
error={false}
|
||||
name={"password"}
|
||||
onChange={(e)=>setPassword(e.target.value) } />
|
||||
<Button type={"submit"} className={"w-full mt-8"}
|
||||
disabled={login.length < 3 || password.length < 3}>
|
||||
Login
|
||||
</Button>
|
||||
<p className={"text-center"}>- or -</p>
|
||||
<Button className={"w-full bg-gray-500"}
|
||||
disabled={login.length < 3 || password.length < 3}
|
||||
onClick={() => registerBtnClick()}>
|
||||
Create account
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<Input type={"password"}
|
||||
label={"Password"}
|
||||
value={password}
|
||||
placeholder={"Enter your password"}
|
||||
error={false}
|
||||
name={"password"}
|
||||
onChange={(e)=>setPassword(e.target.value) } />
|
||||
<Button className={"w-full mt-8"} onClick={() => {}}>Login</Button>
|
||||
<p className={"text-center"}>- or -</p>
|
||||
<Button className={"w-full bg-gray-500"} onClick={() => {}}>Create account</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue