add create new locations and get indonesia regions
This commit is contained in:
parent
0b0c2c0db6
commit
40b8b5f112
@ -4,11 +4,16 @@ const SIGNUP_URI = `${BASE_URL}/user/signup`
|
||||
const LOGIN_URI = `${BASE_URL}/user/login`
|
||||
const LOGOUT_URI = `${BASE_URL}/user/logout`
|
||||
|
||||
const GET_REGIONS = `${BASE_URL}/regions`;
|
||||
const GET_REGENCIES = `${BASE_URL}/region/regencies`;
|
||||
const GET_PROVINCES = `${BASE_URL}/region/provinces`;
|
||||
|
||||
const GET_LIST_LOCATIONS_URI = `${BASE_URL}/locations`;
|
||||
const GET_LIST_TOP_LOCATIONS = `${BASE_URL}/locations/top-ratings`
|
||||
const GET_LIST_RECENT_LOCATIONS_RATING_URI = `${BASE_URL}/locations/recent`
|
||||
const GET_LOCATION_URI = `${BASE_URL}/location`;
|
||||
const GET_LOCATION_TAGS_URI = `${BASE_URL}/location/tags`
|
||||
const POST_CREATE_LOCATION = GET_LIST_LOCATIONS_URI;
|
||||
|
||||
const GET_IMAGES_BY_LOCATION_URI = `${BASE_URL}/images/location`
|
||||
|
||||
@ -20,9 +25,13 @@ export {
|
||||
SIGNUP_URI,
|
||||
LOGIN_URI,
|
||||
LOGOUT_URI,
|
||||
GET_REGIONS,
|
||||
GET_PROVINCES,
|
||||
GET_REGENCIES,
|
||||
GET_LIST_RECENT_LOCATIONS_RATING_URI,
|
||||
GET_LIST_TOP_LOCATIONS,
|
||||
GET_LIST_LOCATIONS_URI,
|
||||
POST_CREATE_LOCATION,
|
||||
GET_LOCATION_URI,
|
||||
GET_LOCATION_TAGS_URI,
|
||||
GET_IMAGES_BY_LOCATION_URI,
|
||||
|
243
src/pages/AddLocation/index.tsx
Normal file
243
src/pages/AddLocation/index.tsx
Normal file
@ -0,0 +1,243 @@
|
||||
import { ChangeEvent, TargetedEvent, useEffect, useState } from "preact/compat";
|
||||
import { getListRecentLocationsRatingsService, getProvincesService, getRegenciesService } from "../../services";
|
||||
import { LocationInfo } from "../../domains/LocationInfo";
|
||||
import { DropdownInput } from "../../components";
|
||||
import { IndonesiaRegionsInfo, LocationType } from "../../types/common";
|
||||
import { Regency, emptyRegency } from "../../domains";
|
||||
import { Form } from './types';
|
||||
import { enumKeys } from "../../utils";
|
||||
import './style.css';
|
||||
import { createLocationService } from "../../services/locations";
|
||||
import { useSelector } from "react-redux";
|
||||
import { UserRootState } from "../../store/type";
|
||||
|
||||
function AddLocation() {
|
||||
const [recentLocations, setRecentLocations] = useState<Array<LocationInfo>>()
|
||||
const [idnRegions, setIdnRegions] = useState<IndonesiaRegionsInfo>({
|
||||
regencies: Array<Regency>(),
|
||||
})
|
||||
const [form, setForm] = useState<Form>({
|
||||
name: '',
|
||||
address: '',
|
||||
google_maps_link: '',
|
||||
location_type: LocationType.Beach,
|
||||
regency: emptyRegency(),
|
||||
thumbnails: [],
|
||||
})
|
||||
|
||||
const user = useSelector((state: UserRootState) => state.auth)
|
||||
|
||||
const [pageState, setPageState] = useState({
|
||||
regency_form_error: false,
|
||||
})
|
||||
|
||||
async function getRecentLocations() {
|
||||
try {
|
||||
const locations = await getListRecentLocationsRatingsService(9)
|
||||
setRecentLocations(locations.data)
|
||||
// setIsLoading(false)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function getRegions() {
|
||||
try {
|
||||
const provinces = await getProvincesService();
|
||||
const regencies = await getRegenciesService();
|
||||
|
||||
setIdnRegions({
|
||||
provinces: provinces.data,
|
||||
regencies: regencies.data
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function onSubmitForm(e: TargetedEvent) {
|
||||
e.preventDefault();
|
||||
|
||||
if(form.regency.regency_name === '') {
|
||||
setPageState({ regency_form_error: true })
|
||||
return
|
||||
}
|
||||
|
||||
let tempThumbnailArr: Array<File> = [];
|
||||
let formData = new FormData();
|
||||
|
||||
form.thumbnails.forEach(x => {
|
||||
tempThumbnailArr.push(x.file)
|
||||
})
|
||||
|
||||
formData.append("address", form.address);
|
||||
formData.append("location_type", form.location_type);
|
||||
formData.append("name", form.name);
|
||||
formData.append("regency_id", form.regency.id.toString());
|
||||
formData.append("submitted_by", user.id.toString());
|
||||
formData.append("google_maps_link", form.google_maps_link);
|
||||
|
||||
for(let i = 0; i < tempThumbnailArr.length; i++) {
|
||||
formData.append("thumbnail", tempThumbnailArr[i])
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await createLocationService(formData)
|
||||
alert("Location Added")
|
||||
|
||||
} catch(error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
function onChangeUploadImage(e: TargetedEvent) {
|
||||
e.preventDefault()
|
||||
let event = e.target as HTMLInputElement;
|
||||
const files = Array.from(event.files as ArrayLike<File>);
|
||||
|
||||
const result = files.filter((x) => {
|
||||
if (x.type === "image/jpg" || x.type === "image/png" || x.type === "image/jpeg") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}).map(v => {
|
||||
let ret = {
|
||||
file: v,
|
||||
url: URL.createObjectURL(v)
|
||||
}
|
||||
return ret
|
||||
})
|
||||
|
||||
setForm({ ...form, thumbnails: result })
|
||||
}
|
||||
|
||||
function onDeleteSelectedThumbnail(e: TargetedEvent, idx: number) {
|
||||
e.preventDefault();
|
||||
|
||||
// remove image from FileList
|
||||
const dt = new DataTransfer();
|
||||
const tempInput = document.getElementById('imageUpload')!;
|
||||
const input = tempInput as HTMLInputElement;
|
||||
const { files } = input;
|
||||
|
||||
for (let i = 0; i < files!.length; i++) {
|
||||
const file = files![i]
|
||||
if (idx !== i)
|
||||
dt.items.add(file)
|
||||
}
|
||||
|
||||
input.files = dt.files
|
||||
|
||||
let thumbnails = form.thumbnails.filter((_, index) => index != idx);
|
||||
setForm({ ...form, thumbnails: thumbnails })
|
||||
}
|
||||
|
||||
function onChangeFormInput(e: ChangeEvent) {
|
||||
let event = e.target as HTMLInputElement;
|
||||
setForm({ ...form, [event.name]: event.value })
|
||||
}
|
||||
|
||||
function onDeleteAllThumbnails(e: TargetedEvent) {
|
||||
e.preventDefault();
|
||||
|
||||
const dt = new DataTransfer();
|
||||
const tempInput = document.getElementById('imageUpload');
|
||||
const input = tempInput as HTMLInputElement;
|
||||
|
||||
dt.items.clear();
|
||||
input.files = dt.files
|
||||
|
||||
setForm({ ...form, thumbnails: [] })
|
||||
}
|
||||
|
||||
function onChangeRegencyDropdownInput(val: Regency) {
|
||||
setPageState({ ...pageState, regency_form_error: false })
|
||||
setForm({ ...form, regency: val })
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
getRecentLocations()
|
||||
getRegions()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={'content main-content'}>
|
||||
<div style={{ tableLayout: 'fixed', display: 'table', width: '100%' }}>
|
||||
<div style={{ textAlign: 'left', padding: '20px 30px', maxWidth: 1096, minWidth: 680, display: 'table-cell', position: 'relative', verticalAlign: 'top', width: '100%' }}>
|
||||
<h1 className={'text-3xl mb-5 font-bold'}>Add New Location</h1>
|
||||
<div style={{ backgroundColor: '#2f3136', padding: 10, margin: '0 0 25px' }}>
|
||||
<form onSubmit={onSubmitForm}>
|
||||
<span className={'block mt-2 text-sm mb-2'}>Location Name <span className={'text-error'}>*</span></span>
|
||||
<input className={'bg-primary text-sm input-text'} type={'text'} onChange={onChangeFormInput} name={'name'} value={form.name} required />
|
||||
<span className={'block mt-2 text-sm mb-2'}>Address <span className={'text-error'}>*</span></span>
|
||||
<input className={'bg-primary text-sm input-text'} type={'text'} onChange={onChangeFormInput} name={'address'} value={form.address} required />
|
||||
<label className={'block text-sm mt-2 mb-2'} for="location_type">Location Category</label>
|
||||
<select className={'bg-primary p-1'} name="location_type" id="location_type">
|
||||
{ enumKeys(LocationType).map(x => (
|
||||
<option value={LocationType[x]}>{LocationType[x]}</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
<span className={'block mt-2 text-sm mb-2'}>Kota / Kabupaten <span className={'text-error'}>*</span> <span className={`text-xs text-error ${!pageState.regency_form_error && 'hidden'}`}> (regency mustn't be empty)</span></span>
|
||||
<DropdownInput
|
||||
isSearchable={true}
|
||||
onChange={(val: Regency) => onChangeRegencyDropdownInput(val)}
|
||||
labelPropsName={"regency_name"}
|
||||
options={idnRegions.regencies!}
|
||||
placeholder={''}
|
||||
/>
|
||||
<span className={'block mt-2 text-sm mb-2'}>Google Maps Link <span className={'text-error'}>*</span></span>
|
||||
<input className={'bg-primary text-sm input-text'} type={'text'} onChange={onChangeFormInput} name={'google_maps_link'} value={form.google_maps_link} required />
|
||||
<span className={'block mt-2 text-sm mb-2'}>Thumbnails</span>
|
||||
<input id={'imageUpload'} className={'inputfile'} type="file" accept={'image/*'} multiple onChange={onChangeUploadImage} />
|
||||
<label for={'imageUpload'}>Choose File(s)</label>
|
||||
<div style={{ clear: 'both' }} />
|
||||
{form.thumbnails.length > 0 &&
|
||||
form.thumbnails.map((x, idx) => (
|
||||
<div className={'mr-3 font-bold'} style={{ width: 150, display: 'inline-block', textAlign: 'right', fontSize: 20 }}>
|
||||
<a onClick={(e) => onDeleteSelectedThumbnail(e, idx)}>
|
||||
x
|
||||
</a>
|
||||
<img
|
||||
loading={'lazy'}
|
||||
src={x.url}
|
||||
className={'mt-1'}
|
||||
style={{ aspectRatio: '1/1', objectFit: 'cover' }}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
{form.thumbnails.length > 0 &&
|
||||
<a className={'block mt-2'} onClick={onDeleteAllThumbnails}>Delete All Thumbnails</a>
|
||||
}
|
||||
|
||||
<input type={'submit'} value={'Submit'} className={'block p-1 text-sm text-primary mt-4'} style={{ backgroundColor: '#a8adb3', letterSpacing: .5, width: 75 }} />
|
||||
</form>
|
||||
<span className={'mt-5 text-sm font-bold block'}>NOTE: LOCATION SUBMISSION MAY BE EDITED BY MODERATOR SO DON'T PUT STUPID ASS THUMBNAILS YOU 1 CENT DOORKNOB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'table-cell', position: 'relative', verticalAlign: 'top', width: 345, textAlign: 'left', padding: '15px 0' }}>
|
||||
<p>Recently added locations</p>
|
||||
{recentLocations?.map(x => (
|
||||
<div style={{ width: '32%', display: 'inline-block', padding: '1px 1%' }}>
|
||||
<a href={'#'} title={x.name}>
|
||||
<img
|
||||
loading={'lazy'}
|
||||
src={x.thumbnail.String.toString()}
|
||||
alt={x.name}
|
||||
style={{ aspectRatio: '1/1' }}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddLocation;
|
33
src/pages/AddLocation/style.css
Normal file
33
src/pages/AddLocation/style.css
Normal file
@ -0,0 +1,33 @@
|
||||
.input-text {
|
||||
border-radius: 7px;
|
||||
min-width: 325px;
|
||||
max-width: 450px;
|
||||
width: 100%;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.inputfile {
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.inputfile + label {
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
background-color: #555555;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.inputfile + label:hover {
|
||||
background-color: #202225;
|
||||
}
|
||||
|
||||
.inputfile + label {
|
||||
cursor: pointer; /* "hand" cursor */
|
||||
}
|
16
src/pages/AddLocation/types.ts
Normal file
16
src/pages/AddLocation/types.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { LocationType } from "src/types/common"
|
||||
import { Regency } from "../../domains"
|
||||
|
||||
export interface Thumbnail {
|
||||
file: File,
|
||||
url: string
|
||||
}
|
||||
|
||||
export interface Form {
|
||||
name: string,
|
||||
address: string,
|
||||
regency: Regency,
|
||||
location_type: LocationType,
|
||||
google_maps_link: string,
|
||||
thumbnails: Array<Thumbnail>
|
||||
}
|
@ -6,6 +6,8 @@ import NewsEvent from "./NewsEvents";
|
||||
import LocationDetail from "./LocationDetail";
|
||||
import Login from './Login';
|
||||
import NotFound from "./NotFound";
|
||||
import AddLocation from "./AddLocation";
|
||||
import Submissions from "./Submissions";
|
||||
|
||||
export {
|
||||
Login,
|
||||
@ -15,7 +17,9 @@ export {
|
||||
NotFound,
|
||||
|
||||
BestLocation,
|
||||
AddLocation,
|
||||
LocationDetail,
|
||||
Submissions,
|
||||
|
||||
Discovery,
|
||||
Story,
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
LocationDetail,
|
||||
NewsEvent,
|
||||
Story,
|
||||
Login
|
||||
AddLocation
|
||||
} from '../pages';
|
||||
|
||||
const routes = [
|
||||
@ -38,6 +38,11 @@ const routes = [
|
||||
path: "/location/:id",
|
||||
name: "LocationDetail",
|
||||
element: <LocationDetail />
|
||||
},
|
||||
{
|
||||
path: "/add-location",
|
||||
name: "AddLocation",
|
||||
element: <AddLocation />
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -8,12 +8,17 @@ import {
|
||||
import { getImagesByLocationService } from "./images"
|
||||
import { createAccountService, loginService, logoutService } from "./auth";
|
||||
import { postReviewLocation, getCurrentUserLocationReviewService } from "./review";
|
||||
import { getRegionsService, getProvincesService, getRegenciesService} from "./regions";
|
||||
|
||||
export {
|
||||
createAccountService,
|
||||
loginService,
|
||||
logoutService,
|
||||
|
||||
getRegionsService,
|
||||
getProvincesService,
|
||||
getRegenciesService,
|
||||
|
||||
getListLocationsService,
|
||||
getListRecentLocationsRatingsService,
|
||||
getListTopLocationsService,
|
||||
|
@ -1,18 +1,27 @@
|
||||
import { GET_LIST_LOCATIONS_URI, GET_LIST_RECENT_LOCATIONS_RATING_URI, GET_LIST_TOP_LOCATIONS, GET_LOCATION_TAGS_URI, GET_LOCATION_URI } from "../constants/api";
|
||||
import { GetRequestPagination, IHttpResponse } from "../types/common";
|
||||
import {
|
||||
GET_LIST_LOCATIONS_URI,
|
||||
GET_LIST_RECENT_LOCATIONS_RATING_URI,
|
||||
GET_LIST_TOP_LOCATIONS,
|
||||
GET_LOCATION_TAGS_URI,
|
||||
GET_LOCATION_URI,
|
||||
POST_CREATE_LOCATION
|
||||
} from "../constants/api";
|
||||
import { client } from "./config";
|
||||
import statusCode from "./status-code";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
const initialState: any = {
|
||||
data: null,
|
||||
error: null
|
||||
}
|
||||
|
||||
interface getListLocationsArg extends GetRequestPagination {
|
||||
interface GetListLocationsArg extends GetRequestPagination {
|
||||
order_by?: number,
|
||||
region_type?: number
|
||||
}
|
||||
|
||||
async function getListLocationsService({ page, page_size }: getListLocationsArg) {
|
||||
async function getListLocationsService({ page, page_size }: GetListLocationsArg) {
|
||||
const newState = { ...initialState };
|
||||
const url = `${GET_LIST_LOCATIONS_URI}?page=${page}&page_size=${page_size}`
|
||||
try {
|
||||
@ -48,7 +57,7 @@ async function getListRecentLocationsRatingsService(page_size: Number) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getListTopLocationsService({ page, page_size, order_by, region_type }: getListLocationsArg) {
|
||||
async function getListTopLocationsService({ page, page_size, order_by, region_type }: GetListLocationsArg) {
|
||||
const newState = { ...initialState };
|
||||
const url = `${GET_LIST_TOP_LOCATIONS}?page=${page}&page_size=${page_size}&order_by=${order_by}®ion_type=${region_type}`
|
||||
try {
|
||||
@ -102,10 +111,26 @@ async function getLocationTagsService(id: Number) {
|
||||
}
|
||||
}
|
||||
|
||||
async function createLocationService(data: FormData): Promise<IHttpResponse> {
|
||||
const newState: IHttpResponse = { data: null, error: null};
|
||||
try {
|
||||
const response = await client({ method: 'POST', url: POST_CREATE_LOCATION, data: data})
|
||||
newState.data = response.data;
|
||||
newState.status = response.status
|
||||
return newState;
|
||||
} catch (error) {
|
||||
let err = error as AxiosError;
|
||||
newState.error = err;
|
||||
newState.status = err.status;
|
||||
return newState;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
getListLocationsService,
|
||||
getListRecentLocationsRatingsService,
|
||||
getListTopLocationsService,
|
||||
getLocationTagsService,
|
||||
getLocationService
|
||||
getLocationService,
|
||||
createLocationService,
|
||||
}
|
46
src/services/regions.ts
Normal file
46
src/services/regions.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { client } from "./config";
|
||||
import { GET_PROVINCES, GET_REGENCIES, GET_REGIONS } from "../constants/api";
|
||||
import { IHttpResponse } from "src/types/common";
|
||||
|
||||
async function getRegionsService(): Promise<IHttpResponse> {
|
||||
const newState: IHttpResponse = {data: null, error: null}
|
||||
try {
|
||||
const response = await client({ method: 'GET', url: GET_REGIONS})
|
||||
newState.data = response.data;
|
||||
return newState
|
||||
} catch(err) {
|
||||
newState.error = err
|
||||
throw (newState)
|
||||
}
|
||||
}
|
||||
|
||||
async function getProvincesService(): Promise<IHttpResponse> {
|
||||
const newState: IHttpResponse = { data: null, error: null}
|
||||
try {
|
||||
const response = await client({ method: 'GET', url: GET_PROVINCES})
|
||||
newState.data = response.data;
|
||||
return newState
|
||||
} catch(err) {
|
||||
newState.error = err
|
||||
throw (newState)
|
||||
}
|
||||
}
|
||||
|
||||
async function getRegenciesService(): Promise<IHttpResponse> {
|
||||
const newState: IHttpResponse = { data: null, error: null};
|
||||
try {
|
||||
const response = await client({ method: 'GET', url: GET_REGENCIES})
|
||||
newState.data = response.data;
|
||||
newState.status = response.status
|
||||
return newState
|
||||
} catch(err) {
|
||||
newState.error = err
|
||||
throw (newState)
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
getRegionsService,
|
||||
getProvincesService,
|
||||
getRegenciesService,
|
||||
}
|
Loading…
Reference in New Issue
Block a user