add login

This commit is contained in:
NCanggoro 2023-09-23 15:20:47 +07:00
parent d86738dccf
commit 3e810c80e9
10 changed files with 214 additions and 17 deletions

View File

@ -1,13 +1,16 @@
import React from "preact/compat";
import { useState } from "preact/hooks";
import './style.css';
import { useSelector } from "react-redux";
import { UserRootState } from "../../store/type";
function Header() {
const [searchVal, setSearchVal] = useState('');
const [dropdown, setDropdown] = useState(false);
const user = useSelector((state: UserRootState) => state.auth)
const onInput = (e: React.ChangeEvent<HTMLInputElement>): void => {
const val = e.target as HTMLInputElement;
setSearchVal(val.value)
@ -33,18 +36,25 @@ function Header() {
<a href={"/"}>
<h1 className={`title ${dropdown ? 'title-dropdown' : ""}`}>Hilingin</h1>
</a>
<form onSubmit={onSearchSubmit} className={`search-input ${dropdown ? "search-input-dropdown" : ""}`}>
<label>
<input
type="text"
value={searchVal}
onInput={onInput}
placeholder="Yogyakarta, Pantai Cidaun ..."
class="text-input-search"
/>
</label>
</form>
<button onClick={onDropdown} className="dropdown-menu bg-secondary" style={{ padding: 5, margin: 'auto 0', borderRadius: 10, marginLeft: 'auto' }}>
<div className={'user-img self-center mr-5'} style={dropdown ? { display: 'none'} : ''}>
<img
loading={'lazy'}
style={{ width: 40, borderRadius: 15 }}
src={'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'}
/>
</div>
<form onSubmit={onSearchSubmit} className={`search-input ${dropdown ? "search-input-dropdown" : ""}`}>
<label>
<input
type="text"
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>
</button>
</div>
@ -58,7 +68,7 @@ function Header() {
<a href="/stories" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Stories</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" : ""}`}>Sign in</a>
<a href={user.username ? '#' : '/login'} className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>{user.username ? user.username : 'Sign in'}</a>
</div>
</header>
)

View File

@ -37,6 +37,11 @@
margin-left: 0;
}
div.user-img {
display: block;
margin-left: auto;
}
button.dropdown-menu {
display: block;
}
@ -66,9 +71,6 @@
display: none;
}
.search-input {
margin-left: auto;
}
label {
position: relative;
@ -115,3 +117,11 @@ label:before {
max-width: '100%';
text-align: 'center'; */
}
.search-input {
margin-left: auto;
}
.user-img {
display: none;
}

View File

@ -1,6 +1,7 @@
const BASE_URL = "http://localhost:8888"
const SIGNUP_URI = `${BASE_URL}/user/signup`
const LOGIN_URI = `${BASE_URL}/user/login`
const GET_LIST_LOCATIONS_URI = `${BASE_URL}/locations`;
@ -14,6 +15,7 @@ const GET_IMAGES_BY_LOCATION_URI = `${BASE_URL}/images/location`
export {
BASE_URL,
SIGNUP_URI,
LOGIN_URI,
GET_LIST_RECENT_LOCATIONS_RATING_URI,
GET_LIST_TOP_LOCATIONS,
GET_LIST_LOCATIONS_URI,

View File

@ -18,6 +18,13 @@
-webkit-text-size-adjust: 100%;
}
input:focus {
outline: none;
}
a:hover {
cursor: pointer;
}
/* a {
font-weight: 500;
color: #646cff;

View File

@ -0,0 +1,4 @@
input {
padding: 5px;
font-size: 14px;
}

104
src/pages/Login/index.tsx Normal file
View 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;

View File

@ -4,8 +4,11 @@ import Discovery from "./Discovery";
import Story from "./Stories";
import NewsEvent from "./NewsEvents";
import LocationDetail from "./LocationDetail";
import Login from './Login';
export {
Login,
Home,
BestLocation,

47
src/services/auth.ts Normal file
View 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
};

View File

@ -7,8 +7,12 @@ import {
} from "./locations";
import { getImagesByLocationService } from "./images"
import { createAccountService, loginService } from "./auth";
export {
createAccountService,
loginService,
getListLocationsService,
getListRecentLocationsRatingsService,
getListTopLocationsService,

View File

@ -5,3 +5,9 @@ interface GetRequestPagination {
page: number,
page_size: number,
}
interface IEmptyResponseState {
data: unknown,
error: any,
};