adjust location detail response with reviews

This commit is contained in:
NCanggoro 2023-09-27 21:55:51 +07:00
parent cd680de68b
commit 00c8831dc9
7 changed files with 357 additions and 345 deletions

View File

@ -3,8 +3,6 @@ import { getListTopLocationsService } from "../../services";
import { DefaultSeparator } from "../../components";
import { TargetedEvent } from "preact/compat";
import './style.css';
import { useNavigate } from "react-router-dom";
interface TopLocation {
row_number: Number,
@ -57,8 +55,6 @@ function BestLocation() {
filterRegionType: 0,
})
const navigate = useNavigate()
async function getTopLocations() {
try {
const res = await getListTopLocationsService({ page: page, page_size: 20, order_by: pageState.filterScoreTypeidx, region_type: pageState.filterRegionType })
@ -79,18 +75,6 @@ function BestLocation() {
setPageState({ ...pageState, filterRegionTypeName: region_name, filterRegionType: type})
}
function onNavigateToDetail(
id: Number,
critic_count: Number,
critic_score: Number,
user_count: Number,
user_score: Number,
) {
navigate(`/location/${id}`, { state: { user_score: user_score, user_count: user_count, critic_score: critic_score, critic_count: critic_count }})
}
useEffect(() => {
getTopLocations()
}, [pageState])
@ -152,10 +136,10 @@ function BestLocation() {
</a>
</div> */}
<div className={'mb-2 best-locations-title'}>
<a className={'text-xl'} href={`/location/${x.id}`} onClick={() => onNavigateToDetail(x.id, x.critic_count, x.critic_score, x.user_count, x.user_score)}>{x.row_number}.{x.name}</a>
<a className={'text-xl'} href={`/location/${x.id}`}>{x.row_number}.{x.name}</a>
</div>
<div style={{ maxWidth: 200, maxHeight: 200, margin: '0 30px 30px 10px', float: 'left' }}>
<a href={`/location/${x.id}`} onClick={() => onNavigateToDetail(x.id, x.critic_count, x.critic_score, x.user_count, x.user_score)}>
<a href={`/location/${x.id}`} >
<img src={x.thumbnail.String.toString()} loading={'lazy'} style={{ width: '100%', objectFit: 'cover', height: '100%', aspectRatio: '1/1' }} />
</a>
</div>

View File

@ -67,6 +67,11 @@ img {
outline: none;
}
.text-area-button {
background-color: gray;
letter-spacing: 1px;
}
.criticSortFilter {
float: right;
}

View File

@ -1,24 +1,43 @@
import { useLocation, useParams } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router-dom';
import { ChangeEvent, TargetedEvent } from 'preact/compat';
import { useEffect, useRef, useState } from 'preact/hooks';
import './index.css';
import Lightbox from 'yet-another-react-lightbox';
import useCallbackState from '../../types/state-callback';
import { EmptyLocationDetailResponse, LocationDetailResponse, LocationResponse, emptyLocationResponse } from './types';
import { useAutosizeTextArea } from '../../utils';
import { getImagesByLocationService, getLocationService } from "../../services";
import { DefaultSeparator, SeparatorWithAnchor, CustomInterweave } from '../../components';
import {
EmptyLocationDetailResponse,
LocationDetailResponse,
LocationResponse,
emptyLocationResponse,
CurrentUserLocationReviews,
} from './types';
import { handleAxiosError, useAutosizeTextArea } from '../../utils';
import { getImagesByLocationService, getLocationService, postReviewLocation } from "../../services";
import { DefaultSeparator, SeparatorWithAnchor, CustomInterweave, SpinnerLoading } from '../../components';
import { useSelector } from 'react-redux';
import { UserRootState } from '../../store/type';
import { DEFAULT_AVATAR_IMG } from '../../constants/default';
import './index.css';
import { AxiosError } from 'axios';
const SORT_TYPE = [
'highest rated',
'lowest rated',
'newest',
'oldest'
]
function LocationDetail() {
const [locationDetail, setLocationDetail] = useCallbackState<LocationDetailResponse>(EmptyLocationDetailResponse)
const [locationImages, setLocationImages] = useState<LocationResponse>(emptyLocationResponse())
const [currentUserReview, setCurrentUserReview] = useState<CurrentUserLocationReviews>()
const [lightboxOpen, setLightboxOpen] = useState<boolean>(false)
const [pageState, setPageState] = useState({
critic_filter_name: 'highest rated',
critic_filter_type: 0,
show_sort: false,
enable_post: true,
on_submit_loading: false,
is_score_rating_panic_msg: '',
temp: ''
})
const [reviewValue, setReviewValue] = useState({
review_textArea: '',
@ -26,28 +45,27 @@ function LocationDetail() {
})
const [isLoading, setIsLoading] = useState<boolean>(true)
const navigate = useNavigate();
const user = useSelector((state: UserRootState) => state.auth)
const textAreaRef = useRef<HTMLTextAreaElement>(null);
useAutosizeTextArea(textAreaRef.current, reviewValue.review_textArea);
const { state } = useLocation();
const { id } = useParams()
const SORT_TYPE = [
'highest rated',
'lowest rated',
'newest',
'oldest'
]
async function getLocationDetail(): Promise<void> {
try {
const res = await getLocationService(Number(id))
setLocationDetail(res.data, (val) => {
getImage(val.detail.thumbnail.String.toString())
})
} catch (err) {
console.log(err)
} catch (error) {
let err = error as AxiosError;
if (err.response?.status == 404) {
navigate("/")
}
alert(error)
}
}
@ -90,22 +108,63 @@ function LocationDetail() {
setPageState({ ...pageState, show_sort: false, critic_filter_name: sort_name, critic_filter_type: sort_type })
}
function handleSubmitReview(e: TargetedEvent<HTMLAnchorElement>) {
async function handleSubmitReview(e: TargetedEvent<HTMLAnchorElement>) {
e.preventDefault();
setPageState({ ...pageState, on_submit_loading: true })
if (isNaN(Number(reviewValue.score_input))) {
setPageState({ ...pageState, is_score_rating_panic_msg: "SCORE MUST BE A NUMBER" })
return
}
if (Number(reviewValue.score_input) > 100) {
setPageState({ ...pageState, is_score_rating_panic_msg: "SCORE MUST BE LESS OR EQUAL THAN 100" })
return
}
if (reviewValue.score_input === '') {
setPageState({ ...pageState, is_score_rating_panic_msg: "SCORE MUSTN'T BE EMPTY" })
return
}
try {
const { data } = await postReviewLocation({
is_hided: false,
location_id: Number(id),
score: Number(reviewValue.score_input),
submitted_by: Number(user.id),
is_from_critic: user.is_critics,
comments: reviewValue.review_textArea,
})
setPageState({ ...pageState, enable_post: false, on_submit_loading: false })
setReviewValue({ review_textArea: '', score_input: '' })
setCurrentUserReview({
id: data.id,
comments: data.comments,
is_from_critic: data.is_from_critic,
is_hided: data.is_hided,
location_id: data.location_id,
score: data.score,
submitted_by: data.submitted_by,
created_at: data.created_at,
updated_at: data.updated_at
})
} catch (error) {
let err = error as AxiosError;
console.log(err)
const str = handleAxiosError(err)
alert(str)
setPageState({ ...pageState, on_submit_loading: false })
}
}
function handleSignInNavigation(e: TargetedEvent<HTMLAnchorElement>) {
e.preventDefault();
if(Number(reviewValue.score_input) > 100) {
setPageState({ ...pageState, is_score_rating_panic_msg: "SCORE MUST BE LESS OR EQUAL THAN 100"})
return
}
if(reviewValue.score_input === '') {
setPageState({ ...pageState, is_score_rating_panic_msg: "SCORE MUSTN'T BE EMPTY"})
return
}
const temp = reviewValue.review_textArea.replace(/\n/g, "<br>")
setPageState({ ...pageState, temp: temp })
navigate('/login', { state: { from: `/location/${id}` } })
}
useEffect(() => {
@ -162,36 +221,36 @@ function LocationDetail() {
<div className={'p-4 bg-secondary mt-3'} style={{ width: '100%', height: 120, borderTopLeftRadius: 10, borderTopRightRadius: 10 }}>
<div className={'font-bold ml-1 text-xs'}>CRITICS SCORE</div>
<div className={'text-4xl text-center mt-2 mr-4'} style={{ width: 95, float: 'left' }}>
{state.critic_count !== 0 ? state.critic_score : "NR"}
{locationDetail.detail.critic_count !== 0 ? Math.floor(Number(locationDetail.detail.critic_score) / Number(locationDetail.detail.critic_count)) : "NR"}
<div className={"items-center p-2"}>
<div className={'mr-3 users-score-bar'}>
<div className={"mt-1"} style={{ height: 4, width: 80, backgroundColor: "#72767d" }}>
<div style={{ height: 4, width: ` ${state.critic_count !== 0 ? Number(state.critic_score) / Number(state.critic_count) * 10 : 0}%`, backgroundColor: 'green' }} />
<div style={{ height: 4, width: ` ${locationDetail.detail.critic_count !== 0 ? Number(locationDetail.detail.critic_score) : 0}%`, backgroundColor: 'green' }} />
</div>
</div>
</div>
</div>
{state.critic_count !== 0 &&
{locationDetail.detail.critic_count !== 0 &&
<div className={'ml-14 text-sm'}>
Based on {state.critic_count} reviews
Based on {locationDetail.detail.critic_count} reviews
</div>
}
</div>
<div className={'p-4 bg-secondary mt-1 inline-block'} style={{ width: '100%', height: 120, borderBottomLeftRadius: 10, borderBottomRightRadius: 10 }}>
<div className={'font-bold ml-2 text-xs'}>USERS SCORE</div>
<div className={'text-4xl text-center mt-2'} style={{ width: 95, float: 'left' }}>
{state.user_count !== 0 ? state.user_score : "NR"}
<div className={'text-4xl text-center mt-2 mr-4'} style={{ width: 95, float: 'left' }}>
{locationDetail.detail.user_count !== 0 ? Math.floor(Number(locationDetail.detail.user_score) / Number(locationDetail.detail.user_count)) : "NR"}
<div className={"items-center p-2"}>
<div className={'mr-3 users-score-bar'}>
<div className={"mt-1"} style={{ height: 4, width: 80, backgroundColor: "#72767d" }}>
<div style={{ height: 4, width: ` ${state.user_count !== 0 ? Number(state.user_score) / Number(state.user_score) * 10 : 0}%`, backgroundColor: 'green' }} />
<div style={{ height: 4, width: ` ${locationDetail.detail.user_count !== 0 ? Number(locationDetail.detail.user_score) / Number(locationDetail.detail.user_count) : 0}%`, backgroundColor: 'green' }} />
</div>
</div>
</div>
</div>
{state.user_count !== 0 &&
<div className={'ml-4'}>
Based on {state.user_count} reviews
{locationDetail.detail.user_count !== 0 &&
<div className={'ml-14 text-sm'}>
Based on {locationDetail.detail.user_count} reviews
</div>
}
</div>
@ -237,67 +296,87 @@ function LocationDetail() {
<section name={'REVIEWS SECTION'}>
<div className={'mt-5'} style={{ tableLayout: 'fixed', display: 'table', width: '100%' }}>
<div className={'wideLeft'} style={{ textAlign: 'left', paddingRight: 20, maxWidth: 1096, minWidth: 680, display: 'table-cell', position: 'relative', verticalAlign: 'top', width: '100%', boxSizing: 'border-box' }}>
{!user.username ?
<div className={'bg-secondary text-center p-3'} style={{ width: '100%' }}><a href={'#'} onClick={handleSignInNavigation} style={{ borderBottom: '1px solid white' }}>SIGN IN</a> TO REVIEW</div>
:
<div name="REVIEW INPUT TEXTAREA" className={'reviewContainer p-4'} style={{ backgroundColor: '#2f3136' }}>
<div className={'reviewBoxContent'} style={{ width: '75%', margin: '0 auto' }}>
{/* <div className={'bg-secondary text-center p-3'} style={{ width: '100%'}}><a href={'#'}>SIGN IN</a> TO REVIEW</div> */}
<div name="REVIEW INPUT TEXTAREA" className={'reviewContainer p-4'} style={{ backgroundColor: '#2f3136' }}>
<div className={'reviewBoxContent'} style={{ width: '75%', margin: '0 auto' }}>
<div className={'userImage mr-3'} style={{ width: 55, float: 'left' }}>
<a href={'#'}>
<img loading={'lazy'} src={user.avatar_picture != '' ? user.avatar_picture.toString() : DEFAULT_AVATAR_IMG} style={{ aspectRatio: '1/1' }} />
</a>
</div>
<div className={'userImage mr-3'} style={{ width: 55, float: 'left' }}>
<a href={'#'}>
<img loading={'lazy'} src={'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'} />
</a>
</div>
<div style={{ display: 'block' }}>
<a href={'#'}>{user.username}</a>
</div>
<div style={{ display: 'block' }}>
<a href={'#'}>user</a>
</div>
<div className={'ratingInput'} style={currentUserReview ? { margin: '0 0 10px' } : { margin: '5px 0 10px' }}>
{currentUserReview ?
<div style={{ display: 'inline-block' }}>
{console.log(currentUserReview)}
<p className={'ml-2'}>{currentUserReview.score}</p>
<div style={{ height: 4, width: 35, backgroundColor: "#72767d" }}>
<div style={{ height: 4, width: `${currentUserReview.score}%`, backgroundColor: 'green' }} />
</div>
</div>
:
<>
<input
type={'text'}
pattern={"\d*"}
style={{ fontSize: 12, backgroundColor: '#40444b', textAlign: 'center', width: 40, height: 20, lineHeight: 18, border: '1px solid #38444d' }}
maxLength={3}
value={reviewValue.score_input}
onChange={handleScoreInputChange}
placeholder={"0-100"}
autoComplete={'off'}
/>
<div className={'inline-block text-xs ml-2 text-tertiary'}>/ score</div>
{pageState.is_score_rating_panic_msg &&
<div className={'inline-block text-xs ml-2 text-error'}>{pageState.is_score_rating_panic_msg}</div>
}
</>
<div className={'ratingInput'} style={{ margin: '5px 0 10px' }}>
<div style={{ float: 'left' }}>
<input
type={'text'}
pattern={"\d*"}
style={{ fontSize: 12, backgroundColor: '#40444b', textAlign: 'center', width: 40, height: 20, lineHeight: 18, border: '1px solid #38444d' }}
maxLength={3}
value={reviewValue.score_input}
onChange={handleScoreInputChange}
placeholder={"0-100"}
autoComplete={'off'}
/>
<div className={'inline-block text-xs ml-2 text-tertiary'}>/ score</div>
{pageState.is_score_rating_panic_msg &&
<div className={'inline-block text-xs ml-2 text-error'}>{pageState.is_score_rating_panic_msg}</div>
}
<div style={{ clear: 'both' }} />
</div>
<div className={'mt-3'} style={{ width: '100%' }}>
{currentUserReview ?
<CustomInterweave
content={currentUserReview.comments}
/>
:
<textarea
onChange={handleTextAreaChange}
ref={textAreaRef}
className={'p-2'}
value={reviewValue.review_textArea}
style={{ border: 'none', overflow: 'auto', outline: 'none', boxShadow: 'none', backgroundColor: '#40444b', width: '100%', minHeight: 100, overflowY: 'hidden' }}
/>
}
</div>
<div style={{ clear: 'both' }} />
</div>
<div className={'mt-3'} style={{ width: '100%' }}>
<textarea
onChange={handleTextAreaChange}
ref={textAreaRef}
className={'p-2'}
value={reviewValue.review_textArea}
style={{ border: 'none', overflow: 'auto', outline: 'none', boxShadow: 'none', backgroundColor: '#40444b', width: '100%', minHeight: 100, overflowY: 'hidden' }}
/>
</div>
<div style={{ textAlign: 'right', width: "100%" }}>
<div style={{ display: 'inline-block', fontSize: 11, verticalAlign: 'middle', margin: '0 10px 0 0', letterSpacing: .5 }}>
<a>Review Guidelines</a>
<div style={{ textAlign: 'right', width: "100%" }}>
<div style={{ display: 'inline-block', fontSize: 11, verticalAlign: 'middle', margin: '0 10px 0 0', letterSpacing: .5 }}>
<a>Review Guidelines</a>
</div>
{pageState.on_submit_loading ?
<SpinnerLoading />
:
<span className={'text-xxs p-1 text-area-button'} style={pageState.enable_post ? '' : { display: 'none'}}>
<a href={'#'} onClick={handleSubmitReview}>
POST
</a>
</span>
}
</div>
<span className={'text-xxs p-1'} style={{ backgroundColor: 'gray', letterSpacing: 1 }}>
<a href={'#'} onClick={handleSubmitReview}>
POST
</a>
</span>
</div>
</div>
<CustomInterweave
content={pageState.temp}
/>
</div>
}
<div name={'CRTICITS REVIEW'} style={{ margin: '50px 0', textAlign: 'left' }}>
<SeparatorWithAnchor pageName={"critic's review"} pageLink='#' />
<div className={'criticSortFilter'}>
@ -314,236 +393,121 @@ function LocationDetail() {
</div>
<div style={{ clear: 'both' }} />
<div className={''} style={{ padding: '15px 0' }}>
<div style={{ float: 'left' }}>
<div style={{ fontSize: 20, marginRight: 20, textAlign: 'center', width: 55, marginBottom: 3 }}>
90
{locationDetail.critics_review.map(x => (
<div className={''} style={{ padding: '15px 0' }}>
<div style={{ float: 'left' }}>
<div style={{ fontSize: 20, marginRight: 20, textAlign: 'center', width: 55, marginBottom: 3 }}>
{x.score}
</div>
<div style={{ height: 4, width: 55, position: 'relative', backgroundColor: '#d8d8d8' }}>
<div style={{ height: 4, backgroundColor: '#85ce73', width: `${x.score}%` }} />
</div>
</div>
<div style={{ height: 4, width: 55, position: 'relative', backgroundColor: '#d8d8d8' }}>
<div style={{ height: 4, backgroundColor: '#85ce73', width: '90%' }} />
<div className={'mr-3'} style={{ display: 'inline-block', width: 40 }}>
<a href="#">
<img
loading={'lazy'}
style={{ width: '100%' }}
src={x.user_avatar.Valid ? x.user_avatar.String.toString() : 'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'}
/>
</a>
</div>
</div>
<div className={'mr-3'} style={{ display: 'inline-block', width: 40 }}>
<a href="#">
<img
loading={'lazy'}
style={{ width: '100%' }}
src={'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'}
<div style={{ display: 'inline-block', verticalAlign: 'top' }}>
<div style={{ fontWeight: 700, fontSize: 16, lineHeight: 'initial' }}>
<a>
<span>{x.username}</span>
</a>
</div>
</div>
<div style={{ fontSize: 15, lineHeight: '24px', margin: '5px 75px 1px' }}>
<CustomInterweave
content={x.comments}
/>
</a>
</div>
<div style={{ display: 'inline-block', verticalAlign: 'top' }}>
<div style={{ fontWeight: 700, fontSize: 16, lineHeight: 'initial' }}>
<a>
<span>Benito Mussolini</span>
</a>
</div>
<div className={'reviewLinks'} style={{ marginLeft: 72 }}>
<div className={'mr-2'} style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
<a className={'text-sm'} href={'#'}>
<svg className={'inline-block mr-1'} fill={'currentColor'} xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" /></svg>
<div className={'inline-block'}>Video</div>
</a>
</div>
<div style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
<a className={'text-sm'} href={'#'}>
<svg className={'inline-block mr-1'} fill={'currentColor'} xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" /></svg>
<div className={'inline-block'}>Instagram</div>
</a>
</div>
</div>
</div>
<div style={{ fontSize: 15, lineHeight: '24px', margin: '5px 75px 1px', wordWrap: 'break-word' }}>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro nihil dolor delectus ex minima aliquid quidem veniam officiis temporibus ipsum ea incidunt voluptatum a, repellat illum, cumque consequatur saepe assumenda.</p>
</div>
<div className={'reviewLinks'} style={{ marginLeft: 72 }}>
<div className={'mr-2'} style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
<a className={'text-sm'} href={'#'}>
<svg className={'inline-block mr-1'} fill={'currentColor'} xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" /></svg>
<div className={'inline-block'}>Video</div>
</a>
</div>
<div style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
<a className={'text-sm'} href={'#'}>
<svg className={'inline-block mr-1'} fill={'currentColor'} xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" /></svg>
<div className={'inline-block'}>Instagram</div>
</a>
</div>
</div>
</div>
<div className={''} style={{ padding: '15px 0', borderTop: '1px solid #38444d' }}>
<div style={{ float: 'left' }}>
<div style={{ fontSize: 20, marginRight: 20, textAlign: 'center', width: 55, marginBottom: 3 }}>
90
</div>
<div style={{ height: 4, width: 55, position: 'relative', backgroundColor: '#d8d8d8' }}>
<div style={{ height: 4, backgroundColor: '#85ce73', width: '90%' }} />
</div>
</div>
<div className={'mr-3'} style={{ display: 'inline-block', width: 40 }}>
<a href="#">
<img
loading={'lazy'}
style={{ width: '100%' }}
src={'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'}
/>
</a>
</div>
<div style={{ display: 'inline-block', verticalAlign: 'top' }}>
<div style={{ fontWeight: 700, fontSize: 16, lineHeight: 'initial' }}>
<a>
<span>Benito Mussolini</span>
</a>
</div>
</div>
<div style={{ fontSize: 15, lineHeight: '24px', margin: '5px 75px 1px', wordWrap: 'break-word' }}>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro nihil dolor delectus ex minima aliquid quidem veniam officiis temporibus ipsum ea incidunt voluptatum a, repellat illum, cumque consequatur saepe assumenda.</p>
</div>
<div className={'reviewLinks'} style={{ marginLeft: 72 }}>
<div className={'mr-2'} style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
<a className={'text-sm'} href={'#'}>
<svg className={'inline-block mr-1'} fill={'currentColor'} xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" /></svg>
<div className={'inline-block'}>Video</div>
</a>
</div>
<div style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
<a className={'text-sm'} href={'#'}>
<svg className={'inline-block mr-1'} fill={'currentColor'} xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" /></svg>
<div className={'inline-block'}>Instagram</div>
</a>
</div>
</div>
</div>
<div className={''} style={{ padding: '15px 0', borderTop: '1px solid #38444d' }}>
<div style={{ float: 'left' }}>
<div style={{ fontSize: 20, marginRight: 20, textAlign: 'center', width: 55, marginBottom: 3 }}>
90
</div>
<div style={{ height: 4, width: 55, position: 'relative', backgroundColor: '#d8d8d8' }}>
<div style={{ height: 4, backgroundColor: '#85ce73', width: '90%' }} />
</div>
</div>
<div className={'mr-3'} style={{ display: 'inline-block', width: 40 }}>
<a href="#">
<img
loading={'lazy'}
style={{ width: '100%' }}
src={'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'}
/>
</a>
</div>
<div style={{ display: 'inline-block', verticalAlign: 'top' }}>
<div style={{ fontWeight: 700, fontSize: 16, lineHeight: 'initial' }}>
<a>
<span>Benito Mussolini</span>
</a>
</div>
</div>
<div style={{ fontSize: 15, lineHeight: '24px', margin: '5px 75px 1px', wordWrap: 'break-word' }}>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro nihil dolor delectus ex minima aliquid quidem veniam officiis temporibus ipsum ea incidunt voluptatum a, repellat illum, cumque consequatur saepe assumenda.</p>
</div>
<div className={'reviewLinks'} style={{ marginLeft: 72 }}>
<div className={'mr-2'} style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
<a className={'text-sm'} href={'#'}>
<svg className={'inline-block mr-1'} fill={'currentColor'} xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" /></svg>
<div className={'inline-block'}>Video</div>
</a>
</div>
<div style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
<a className={'text-sm'} href={'#'}>
<svg className={'inline-block mr-1'} fill={'currentColor'} xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" /></svg>
<div className={'inline-block'}>Instagram</div>
</a>
</div>
</div>
</div>
))}
</div>
<div name={'USERS REVIEW'} style={{ margin: '50px 0', textAlign: 'left' }}>
<SeparatorWithAnchor pageName={"Popular User's review"} pageLink='#' secondLink='#'/>
<SeparatorWithAnchor pageName={"User's review"} pageLink='#' secondLink={locationDetail.users_review.length > 0 ? '#' : ''} />
{ locationDetail.users_review.length > 0 ?
<>
{locationDetail.users_review.map(x => (
<div style={{ padding: '15px 0' }}>
<div className={'mr-5'} style={{ width: 45, float: 'left' }}>
<a href="#">
<img
loading={'lazy'}
style={{ width: '100%' }}
src={x.user_avatar.Valid ? x.user_avatar.String.toString() : 'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'}
/>
</a>
</div>
<div>
<div style={{ fontWeight: 700, fontSize: 16, lineHeight: 'initial' }}>
<a>
<span>{x.username}</span>
</a>
</div>
</div>
<div className={'inline-block'}>
<div className={'text-sm text-center'} >{x.score}</div>
<div style={{ height: 4, width: 25, position: 'relative', backgroundColor: '#d8d8d8' }}>
<div style={{ height: 4, backgroundColor: '#85ce73', width: `${x.score}%` }} />
</div>
</div>
<div style={{ fontSize: 15, lineHeight: '24px', margin: '10px 65px 1px', wordWrap: 'break-word' }}>
<CustomInterweave
content={x.comments}
/>
</div>
<div className={'reviewLinks'} style={{ marginLeft: 63 }}>
<div className={'mr-2'} style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
<a className={'text-sm'} href={'#'}>
<svg className={'inline-block mr-1'} fill={'currentColor'} xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" /></svg>
<div className={'inline-block'}>Video</div>
</a>
</div>
<div style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
<a className={'text-sm'} href={'#'}>
<svg className={'inline-block mr-1'} fill={'currentColor'} xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" /></svg>
<div className={'inline-block'}>Instagram</div>
</a>
</div>
</div>
</div>
))}
<div className={''} style={{ padding: '15px 0' }}>
<div className={'mr-5'} style={{ width: 45, float: 'left' }}>
<a href="#">
<img
loading={'lazy'}
style={{ width: '100%' }}
src={'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'}
/>
</a>
</div>
<div>
<div style={{ fontWeight: 700, fontSize: 16, lineHeight: 'initial' }}>
<a>
<span>Benito Mussolini</span>
<div className={'review-more-button text-center text-sm mt-5'}>
<a style={{ borderRadius: 15, padding: '8px 32px', border: '1px solid #d8d8d8' }}>
More
</a>
</div>
</div>
<div className={'inline-block'}>
<div className={'text-sm text-center'} >80</div>
<div style={{ height: 4, width: 25, position: 'relative', backgroundColor: '#d8d8d8' }}>
<div style={{ height: 4, backgroundColor: '#85ce73', width: '90%' }} />
</div>
</div>
<div style={{ fontSize: 15, lineHeight: '24px', margin: '10px 65px 1px', wordWrap: 'break-word' }}>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro nihil dolor delectus ex minima aliquid quidem veniam officiis temporibus ipsum ea incidunt voluptatum a, repellat illum, cumque consequatur saepe assumenda.</p>
</div>
<div className={'reviewLinks'} style={{ marginLeft: 63 }}>
<div className={'mr-2'} style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
<a className={'text-sm'} href={'#'}>
<svg className={'inline-block mr-1'} fill={'currentColor'} xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" /></svg>
<div className={'inline-block'}>Video</div>
</a>
</div>
<div style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
<a className={'text-sm'} href={'#'}>
<svg className={'inline-block mr-1'} fill={'currentColor'} xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" /></svg>
<div className={'inline-block'}>Instagram</div>
</a>
</div>
</div>
</div>
<div className={''} style={{ padding: '15px 0', borderTop: '1px solid #38444d' }}>
<div className={'mr-5'} style={{ width: 45, float: 'left' }}>
<a href="#">
<img
loading={'lazy'}
style={{ width: '100%' }}
src={'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'}
/>
</a>
</div>
<div>
<div style={{ fontWeight: 700, fontSize: 16, lineHeight: 'initial' }}>
<a>
<span>Benito Mussolini</span>
</a>
</div>
</div>
<div className={'inline-block'}>
<div className={'text-sm text-center'} >80</div>
<div style={{ height: 4, width: 25, position: 'relative', backgroundColor: '#d8d8d8' }}>
<div style={{ height: 4, backgroundColor: '#85ce73', width: '90%' }} />
</div>
</div>
<div style={{ fontSize: 15, lineHeight: '24px', margin: '10px 65px 1px', wordWrap: 'break-word' }}>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro nihil dolor delectus ex minima aliquid quidem veniam officiis temporibus ipsum ea incidunt voluptatum a, repellat illum, cumque consequatur saepe assumenda.</p>
</div>
<div className={'reviewLinks'} style={{ marginLeft: 63 }}>
<div className={'mr-2'} style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
<a className={'text-sm'} href={'#'}>
<svg className={'inline-block mr-1'} fill={'currentColor'} xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" /></svg>
<div className={'inline-block'}>Video</div>
</a>
</div>
<div style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
<a className={'text-sm'} href={'#'}>
<svg className={'inline-block mr-1'} fill={'currentColor'} xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" /></svg>
<div className={'inline-block'}>Instagram</div>
</a>
</div>
</div>
</div>
<div className={'review-more-button text-center text-sm mt-5'}>
<a style={{ borderRadius: 15, padding: '8px 32px', border: '1px solid #d8d8d8'}}>
More
</a>
</div>
</>
:
<>
<span className={'text-sm italic'}>No users review to display</span>
</>
}
</div>
<div name={'USERS REVIEW'} style={{ margin: '50px 0', textAlign: 'left' }}>
<SeparatorWithAnchor pageName={"Recent User's review"} pageLink='#' secondLink='#'/>
{/* <div name={'USERS REVIEW'} style={{ margin: '50px 0', textAlign: 'left' }}>
<SeparatorWithAnchor pageName={"Popular User's review"} pageLink='#' secondLink='#' />
<div className={''} style={{ padding: '15px 0' }}>
<div className={'mr-5'} style={{ width: 45, float: 'left' }}>
<a href="#">
@ -628,11 +592,11 @@ function LocationDetail() {
</div>
</div>
<div className={'review-more-button text-center text-sm mt-5'}>
<a style={{ borderRadius: 15, padding: '8px 32px', border: '1px solid #d8d8d8'}}>
<a style={{ borderRadius: 15, padding: '8px 32px', border: '1px solid #d8d8d8' }}>
More
</a>
</div>
</div>
</div> */}
<div className={'mb-5'}>
CONTRUBITION
<DefaultSeparator />
@ -645,7 +609,7 @@ function LocationDetail() {
</div>
} */}
</div>
<div style={{ clear: 'both'}} />
<div style={{ clear: 'both' }} />
</section>
<section>

View File

@ -4,13 +4,16 @@ export interface ILocationDetail {
id: Number,
name: String,
address: String,
regency_name: String,
province_name: String,
region_name: String,
google_maps_link: String,
thumbnail: NullValueRes<"String", String>,
submitted_by: Number,
regency_name: String,
province_name: String,
region_name: String,
submitted_by_user: String
critic_score: Number,
critic_count: Number,
user_score: Number,
user_count: Number
}
export function emptyLocationDetail(): ILocationDetail {
@ -24,19 +27,38 @@ export function emptyLocationDetail(): ILocationDetail {
regency_name: '',
region_name: '',
submitted_by: 0,
submitted_by_user: ''
critic_score: 0,
critic_count: 0,
user_score: 0,
user_count: 0,
}
}
export interface LocationReviewsResponse {
id: number,
score: number,
comments: string,
user_id: number,
username: string,
user_avatar: NullValueRes<"String", string>,
created_at: string,
updated_at: string
}
export interface LocationDetailResponse {
detail: ILocationDetail,
tags: Array<String>
users_review: Array<LocationReviewsResponse>,
critics_review: Array<LocationReviewsResponse>
}
export function EmptyLocationDetailResponse(): LocationDetailResponse {
return {
detail: emptyLocationDetail(),
tags: []
tags: [],
critics_review: Array<LocationReviewsResponse>(),
users_review: Array<LocationReviewsResponse>()
}
}
@ -47,15 +69,6 @@ export interface LocationImage extends SlideImage {
uploaded_by: String
}
export function emptyLocationImage(): LocationImage {
return {
id: 0,
src: '',
created_at: '',
uploaded_by: ''
}
}
export interface LocationResponse {
total_image: Number,
images: Array<LocationImage>
@ -64,6 +77,18 @@ export interface LocationResponse {
export function emptyLocationResponse(): LocationResponse {
return {
total_image: 0,
images: [emptyLocationImage()]
images: Array<LocationImage>()
}
}
export type CurrentUserLocationReviews = {
id: Number,
comments: string,
is_from_critic: boolean,
is_hided: boolean,
location_id: Number,
score: Number,
submitted_by: Number,
created_at: NullValueRes<"Time", string>,
updated_at: NullValueRes<"Time", string>,
}

View File

@ -80,7 +80,7 @@ async function getLocationService(id: Number) {
return newState;
}
} catch (error) {
console.log(error)
throw(error)
}
}

34
src/services/review.ts Normal file
View File

@ -0,0 +1,34 @@
import { AxiosError } from "axios"
import { client } from "./config";
import { POST_REVIEW_LOCATION_URI } from "../constants/api";
const initialState: IEmptyResponseState = {
data: null,
error: AxiosError
}
interface postReviewLocationReq {
submitted_by: number,
comments: string,
score: number,
is_from_critic: boolean,
is_hided: boolean,
location_id: number
}
async function postReviewLocation(req: postReviewLocationReq) {
const newState = { ...initialState };
try {
const response = await client({ method: 'POST', url: POST_REVIEW_LOCATION_URI, data: req, withCredentials: true})
newState.data = response.data
newState.error = null
return newState
} catch (error) {
newState.error = error
throw(error)
}
}
export {
postReviewLocation
}

View File

@ -8,6 +8,6 @@ interface GetRequestPagination {
interface IEmptyResponseState {
data: unknown,
data: any,
error: any,
};