add login
This commit is contained in:
parent
d86738dccf
commit
3e810c80e9
@ -1,13 +1,16 @@
|
|||||||
import React from "preact/compat";
|
import React from "preact/compat";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import './style.css';
|
import './style.css';
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { UserRootState } from "../../store/type";
|
||||||
|
|
||||||
|
|
||||||
function Header() {
|
function Header() {
|
||||||
|
|
||||||
const [searchVal, setSearchVal] = useState('');
|
const [searchVal, setSearchVal] = useState('');
|
||||||
const [dropdown, setDropdown] = useState(false);
|
const [dropdown, setDropdown] = useState(false);
|
||||||
|
|
||||||
|
const user = useSelector((state: UserRootState) => state.auth)
|
||||||
|
|
||||||
const onInput = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
const onInput = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
const val = e.target as HTMLInputElement;
|
const val = e.target as HTMLInputElement;
|
||||||
setSearchVal(val.value)
|
setSearchVal(val.value)
|
||||||
@ -33,18 +36,25 @@ function Header() {
|
|||||||
<a href={"/"}>
|
<a href={"/"}>
|
||||||
<h1 className={`title ${dropdown ? 'title-dropdown' : ""}`}>Hilingin</h1>
|
<h1 className={`title ${dropdown ? 'title-dropdown' : ""}`}>Hilingin</h1>
|
||||||
</a>
|
</a>
|
||||||
<form onSubmit={onSearchSubmit} className={`search-input ${dropdown ? "search-input-dropdown" : ""}`}>
|
<div className={'user-img self-center mr-5'} style={dropdown ? { display: 'none'} : ''}>
|
||||||
<label>
|
<img
|
||||||
<input
|
loading={'lazy'}
|
||||||
type="text"
|
style={{ width: 40, borderRadius: 15 }}
|
||||||
value={searchVal}
|
src={'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'}
|
||||||
onInput={onInput}
|
/>
|
||||||
placeholder="Yogyakarta, Pantai Cidaun ..."
|
</div>
|
||||||
class="text-input-search"
|
<form onSubmit={onSearchSubmit} className={`search-input ${dropdown ? "search-input-dropdown" : ""}`}>
|
||||||
/>
|
<label>
|
||||||
</label>
|
<input
|
||||||
</form>
|
type="text"
|
||||||
<button onClick={onDropdown} className="dropdown-menu bg-secondary" style={{ padding: 5, margin: 'auto 0', borderRadius: 10, marginLeft: 'auto' }}>
|
value={searchVal}
|
||||||
|
onInput={onInput}
|
||||||
|
placeholder="Yogyakarta, Pantai Cidaun ..."
|
||||||
|
class="text-input-search"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
<button onClick={onDropdown} className={`dropdown-menu bg-secondary ${dropdown ? 'ml-auto' : ''}`} style={{ padding: 5, borderRadius: 10}}>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="30" fill="white" viewBox="0 -960 960 960" width="30"><path d="M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z" /></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" height="30" fill="white" viewBox="0 -960 960 960" width="30"><path d="M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z" /></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -58,7 +68,7 @@ function Header() {
|
|||||||
<a href="/stories" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Stories</a>
|
<a href="/stories" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Stories</a>
|
||||||
<a href="/news-events" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>News / Events</a>
|
<a href="/news-events" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>News / Events</a>
|
||||||
<a href="#" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Forum</a>
|
<a href="#" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Forum</a>
|
||||||
<a href="#" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Sign in</a>
|
<a href={user.username ? '#' : '/login'} className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>{user.username ? user.username : 'Sign in'}</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
|
@ -37,6 +37,11 @@
|
|||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.user-img {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
button.dropdown-menu {
|
button.dropdown-menu {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@ -66,9 +71,6 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
label {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -114,4 +116,12 @@ label:before {
|
|||||||
/* display: 'inline-block';
|
/* display: 'inline-block';
|
||||||
max-width: '100%';
|
max-width: '100%';
|
||||||
text-align: 'center'; */
|
text-align: 'center'; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-img {
|
||||||
|
display: none;
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
const BASE_URL = "http://localhost:8888"
|
const BASE_URL = "http://localhost:8888"
|
||||||
|
|
||||||
const SIGNUP_URI = `${BASE_URL}/user/signup`
|
const SIGNUP_URI = `${BASE_URL}/user/signup`
|
||||||
|
const LOGIN_URI = `${BASE_URL}/user/login`
|
||||||
|
|
||||||
|
|
||||||
const GET_LIST_LOCATIONS_URI = `${BASE_URL}/locations`;
|
const GET_LIST_LOCATIONS_URI = `${BASE_URL}/locations`;
|
||||||
@ -14,6 +15,7 @@ const GET_IMAGES_BY_LOCATION_URI = `${BASE_URL}/images/location`
|
|||||||
export {
|
export {
|
||||||
BASE_URL,
|
BASE_URL,
|
||||||
SIGNUP_URI,
|
SIGNUP_URI,
|
||||||
|
LOGIN_URI,
|
||||||
GET_LIST_RECENT_LOCATIONS_RATING_URI,
|
GET_LIST_RECENT_LOCATIONS_RATING_URI,
|
||||||
GET_LIST_TOP_LOCATIONS,
|
GET_LIST_TOP_LOCATIONS,
|
||||||
GET_LIST_LOCATIONS_URI,
|
GET_LIST_LOCATIONS_URI,
|
||||||
|
@ -18,6 +18,13 @@
|
|||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
/* a {
|
/* a {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #646cff;
|
color: #646cff;
|
||||||
|
4
src/pages/Login/index.css
Normal file
4
src/pages/Login/index.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
input {
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
104
src/pages/Login/index.tsx
Normal file
104
src/pages/Login/index.tsx
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { ChangeEvent, TargetedEvent, useState } from "preact/compat";
|
||||||
|
import { createAccountService, loginService } from "../../services";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { authAdded } from "../../features/auth/authSlice/authSlice";
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
function Login() {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [form, setFrom] = useState({
|
||||||
|
username: '',
|
||||||
|
password: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const [errorMsg, setErrorMsg] = useState([{
|
||||||
|
field: '',
|
||||||
|
msg: ''
|
||||||
|
}])
|
||||||
|
|
||||||
|
const navigation = useNavigate()
|
||||||
|
|
||||||
|
async function handleSignIn(e: TargetedEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
const res = await loginService({username: form.username, password: form.password})
|
||||||
|
if (res.error) {
|
||||||
|
setErrorMsg(res.error.response.data.errors)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dispatch(authAdded(res.data))
|
||||||
|
// localStorage.setItem("user", JSON.stringify(res.data))
|
||||||
|
navigation("/")
|
||||||
|
} catch(err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSignUp(e: TargetedEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
const res = await createAccountService({
|
||||||
|
username: form.username,
|
||||||
|
password: form.password
|
||||||
|
})
|
||||||
|
|
||||||
|
if(res.error) {
|
||||||
|
setErrorMsg(res.error.response.data.errors)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(res.data)
|
||||||
|
} catch (err) {
|
||||||
|
alert(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChangeInput(e: ChangeEvent<HTMLInputElement>) {
|
||||||
|
const val = e.target as HTMLInputElement;
|
||||||
|
setFrom({ ...form, [val.placeholder]: val.value })
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'p-2'}>
|
||||||
|
<h1 className={'text-2xl mb-2'}>Sign in</h1>
|
||||||
|
<form onSubmit={handleSignIn}>
|
||||||
|
<table style={{ borderSpacing: '0 0.5em', borderCollapse: 'separate' }}>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>username: </td>
|
||||||
|
<td><input value={form.username} onChange={onChangeInput} placeholder={'username'} className={'bg-secondary'} /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>password: </td>
|
||||||
|
<td><input value={form.password} onChange={onChangeInput} type={'password'} placeholder={'password'} className={'bg-secondary'} /></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<a className={'block'}>Forgout account ?</a>
|
||||||
|
<input type={'submit'} value={'sign in'} className={'p-1 text-sm text-primary mt-4'} style={{ backgroundColor: '#a8adb3', borderRadius: 7 }} />
|
||||||
|
</form>
|
||||||
|
<h1 className={'text-2xl mt-10'}>Create Account</h1>
|
||||||
|
<form onSubmit={handleSignUp} >
|
||||||
|
<table style={{ borderSpacing: '0 0.5em', borderCollapse: 'separate' }}>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>username: </td>
|
||||||
|
<td><input value={form.username} onChange={onChangeInput} placeholder={'username'} className={'bg-secondary'} /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>password: </td>
|
||||||
|
<td><input value={form.password} onChange={onChangeInput} type={'password'} placeholder={'password'} className={'bg-secondary'} /></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{errorMsg.map(x => (
|
||||||
|
<p>{x.msg}</p>
|
||||||
|
))}
|
||||||
|
<input type={'submit'} value={'create account'} className={'p-1 text-sm text-primary mt-4'} style={{ backgroundColor: '#a8adb3', borderRadius: 7 }} />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login;
|
@ -4,8 +4,11 @@ import Discovery from "./Discovery";
|
|||||||
import Story from "./Stories";
|
import Story from "./Stories";
|
||||||
import NewsEvent from "./NewsEvents";
|
import NewsEvent from "./NewsEvents";
|
||||||
import LocationDetail from "./LocationDetail";
|
import LocationDetail from "./LocationDetail";
|
||||||
|
import Login from './Login';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
Login,
|
||||||
|
|
||||||
Home,
|
Home,
|
||||||
|
|
||||||
BestLocation,
|
BestLocation,
|
||||||
|
47
src/services/auth.ts
Normal file
47
src/services/auth.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { AxiosError } from "axios";
|
||||||
|
import { LOGIN_URI, SIGNUP_URI } from "../constants/api";
|
||||||
|
import { client } from "./config";
|
||||||
|
|
||||||
|
const initialState: IEmptyResponseState = {
|
||||||
|
data: null,
|
||||||
|
error: AxiosError
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAuthentication {
|
||||||
|
username: String
|
||||||
|
password: String
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createAccountService({ username, password }: IAuthentication) {
|
||||||
|
const newState = { ...initialState };
|
||||||
|
const url = `${SIGNUP_URI}`
|
||||||
|
try {
|
||||||
|
const response = await client({ method: 'POST', url: url, data: { username, password } })
|
||||||
|
newState.data = response.data
|
||||||
|
newState.error = null
|
||||||
|
return newState
|
||||||
|
} catch (error) {
|
||||||
|
newState.error = error
|
||||||
|
return newState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loginService({ username, password }: IAuthentication) {
|
||||||
|
const newState = { ...initialState };
|
||||||
|
const url = `${LOGIN_URI}`
|
||||||
|
try {
|
||||||
|
const response = await client({ method: 'POST', url: url, data: { username, password } })
|
||||||
|
newState.data = response.data
|
||||||
|
newState.error = null
|
||||||
|
return newState
|
||||||
|
} catch (error) {
|
||||||
|
newState.error = error
|
||||||
|
return newState
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
loginService,
|
||||||
|
createAccountService
|
||||||
|
};
|
@ -7,8 +7,12 @@ import {
|
|||||||
} from "./locations";
|
} from "./locations";
|
||||||
|
|
||||||
import { getImagesByLocationService } from "./images"
|
import { getImagesByLocationService } from "./images"
|
||||||
|
import { createAccountService, loginService } from "./auth";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
createAccountService,
|
||||||
|
loginService,
|
||||||
|
|
||||||
getListLocationsService,
|
getListLocationsService,
|
||||||
getListRecentLocationsRatingsService,
|
getListRecentLocationsRatingsService,
|
||||||
getListTopLocationsService,
|
getListTopLocationsService,
|
||||||
|
@ -5,3 +5,9 @@ interface GetRequestPagination {
|
|||||||
page: number,
|
page: number,
|
||||||
page_size: number,
|
page_size: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface IEmptyResponseState {
|
||||||
|
data: unknown,
|
||||||
|
error: any,
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user