login form

This commit is contained in:
Kirill Ivlev 2024-12-18 16:03:45 +04:00
parent 9d39ae2de3
commit 5c78c45879
8 changed files with 222 additions and 29 deletions

81
package-lock.json generated
View file

@ -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",

View file

@ -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
View 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;

View file

@ -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>
</>

View 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;

View file

@ -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}

View file

@ -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;

View file

@ -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>