Compare commits

..

No commits in common. "00c8831dc92b8215decee2863bcc2ad16c2ac277" and "0295d18574c47152b3883bc739248f31c741fd12" have entirely different histories.

39 changed files with 433 additions and 1205 deletions

View File

@ -9,20 +9,9 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "^1.9.5",
"@types/react-redux": "^7.1.26",
"axios": "^1.5.0", "axios": "^1.5.0",
"emojibase": "^15.0.0",
"interweave": "^13.1.0",
"interweave-autolink": "^5.1.0",
"interweave-emoji": "^7.0.0",
"moment": "^2.29.4",
"preact": "^10.16.0", "preact": "^10.16.0",
"react": "^18.2.0",
"react-redux": "^8.1.2",
"react-router-dom": "^6.16.0", "react-router-dom": "^6.16.0",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.4.2",
"yet-another-react-lightbox": "^3.12.2" "yet-another-react-lightbox": "^3.12.2"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,5 +0,0 @@
import { LOGOUT } from "../constants/actions";
export const logout = () => ({
type: LOGOUT
});

View File

@ -1 +0,0 @@
export * from "./LogoutAction";

View File

@ -4,20 +4,13 @@ import './app.css'
import { DefaultLayout } from './layouts' import { DefaultLayout } from './layouts'
import routes from './routes' import routes from './routes'
import "yet-another-react-lightbox/styles.css"; import "yet-another-react-lightbox/styles.css";
import { Login, NotFound } from './pages'
import { Provider } from 'react-redux'
import { persistore, store } from './store/config'
import { PersistGate } from 'redux-persist/integration/react'
export function App() { export function App() {
return ( return (
<> <>
<Provider store={store}>
<PersistGate persistor={persistore}>
<Router> <Router>
<Routes> <Routes>
<Route path='/login' element={<Login />} />
<Route element={<DefaultLayout />}> <Route element={<DefaultLayout />}>
{routes.map(({ path, name, element}) => ( {routes.map(({ path, name, element}) => (
<> <>
@ -29,11 +22,8 @@ export function App() {
</> </>
))} ))}
</Route> </Route>
<Route path="*" element={<NotFound />} />
</Routes> </Routes>
</Router> </Router>
</PersistGate>
</Provider>
</> </>
) )
} }

View File

@ -1,64 +0,0 @@
import { stripHexcode } from 'emojibase';
import { InterweaveProps, FilterInterface, MatcherInterface, Interweave } from 'interweave';
import { IpMatcher, UrlMatcher, EmailMatcher, HashtagMatcher } from 'interweave-autolink';
import { EmojiMatcher, PathConfig } from 'interweave-emoji';
const globalFilters: FilterInterface[] = [];
const globalMatchers: MatcherInterface[] = [
new EmailMatcher('email'),
new IpMatcher('ip'),
new UrlMatcher('url'),
new HashtagMatcher('hashtag'),
new EmojiMatcher('emoji', {
convertEmoticon: true,
convertShortcode: true,
convertUnicode: true,
}),
];
function getEmojiPath(hexcode: string, { enlarged }: PathConfig): string {
return `//cdn.jsdelivr.net/emojione/assets/3.1/png/${enlarged ? 64 : 32}/${stripHexcode(
hexcode,
).toLowerCase()}.png`;
}
interface Props extends InterweaveProps {
instagram?: boolean;
twitter?: boolean;
}
export default function CustomInterweave({
filters = [],
matchers = [],
twitter,
instagram,
...props
}: Props) {
let hashtagUrl = '';
if (twitter) {
hashtagUrl = 'https://twitter.com/hashtag/{{hashtag}}';
} else if (instagram) {
hashtagUrl = 'https://instagram.com/explore/tags/{{hashtag}}';
}
return (
<Interweave
filters={[...globalFilters, ...filters]}
matchers={[...globalMatchers, ...matchers]}
// hashtagUrl={hashtagUrl}
emojiSource={getEmojiPath}
// newWindow
{...props}
/>
// <BaseInterweave
// filters={[...globalFilters, ...filters]}
// matchers={[...globalMatchers, ...matchers]}
// hashtagUrl={hashtagUrl}
// emojiPath={getEmojiPath}
// newWindow
// {...props}
// />
);
}

View File

@ -1,37 +1,18 @@
import React, { TargetedEvent } from "preact/compat"; import React from "preact/compat";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useSelector, useDispatch } from "react-redux";
import { UserRootState } from "../../store/type";
import { logout } from '../../actions';
import './style.css'; import './style.css';
import { logoutService } from "../../services";
function Header() { function Header() {
const [searchVal, setSearchVal] = useState(''); const [searchVal, setSearchVal] = useState('');
const [dropdown, setDropdown] = useState(false); const [dropdown, setDropdown] = useState(false);
const [pageState, setPageState] = useState({
profileMenu: false
})
const dispatch = useDispatch();
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)
} }
const handleLogout = async (): Promise<void> => {
try {
await logoutService()
dispatch(logout())
location.reload()
} catch (error) {
alert(error)
}
}
const onSearchSubmit = (e: React.TargetedEvent<HTMLFormElement>): void => { const onSearchSubmit = (e: React.TargetedEvent<HTMLFormElement>): void => {
e.preventDefault(); e.preventDefault();
} }
@ -52,25 +33,6 @@ function Header() {
<a href={"/"}> <a href={"/"}>
<h1 className={`title ${dropdown ? 'title-dropdown' : ""}`}>Hilingin</h1> <h1 className={`title ${dropdown ? 'title-dropdown' : ""}`}>Hilingin</h1>
</a> </a>
<div className={'user-img self-center mr-5'} style={dropdown ? { display: 'none' } : ''}>
<a href={user.username ? '#' : '/login'} onClick={() => user.username ? setPageState({ ...pageState, profileMenu: !pageState.profileMenu }) : ''}>
<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'}
/>
</a>
{user.username &&
<div className={'profile-dropdown-img bg-secondary text-left'} style={pageState.profileMenu ? { display: 'block'} : { display: 'none'}}>
<a href={'#'}><div className={'p-2'}>Profile</div></a>
<a href={'#'}><div className={'p-2'}>Feed</div></a>
<a href={'#'}><div className={'p-2'}>Add location</div></a>
<a href={'#'} onClick={handleLogout}><div className={'p-2'}>Logout</div></a>
{/* <div className={'p-2'}><a href={'#'}>Halo</a></div> */}
{/* <div className={'p-2'}><a href={'#'}>Halo</a></div> */}
</div>
}
</div>
<form onSubmit={onSearchSubmit} className={`search-input ${dropdown ? "search-input-dropdown" : ""}`}> <form onSubmit={onSearchSubmit} className={`search-input ${dropdown ? "search-input-dropdown" : ""}`}>
<label> <label>
<input <input
@ -82,7 +44,7 @@ function Header() {
/> />
</label> </label>
</form> </form>
<button onClick={onDropdown} className={`dropdown-menu bg-secondary ${dropdown ? 'ml-auto' : ''}`} style={{ padding: 5, borderRadius: 10 }}> <button onClick={onDropdown} className="dropdown-menu bg-secondary" style={{ padding: 5, margin: 'auto 0', borderRadius: 10, marginLeft: 'auto' }}>
<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>
@ -92,22 +54,11 @@ function Header() {
} }
<a href="/best-places" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Top Places</a> <a href="/best-places" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Top Places</a>
<a href="/discover" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Discover</a> <a href="/discover" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Discover</a>
<a href="#" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Trending Places</a>
<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>
<div className={'profile-container'}> <a href="#" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Sign in</a>
<a href={user.username ? '#' : '/login'} onClick={() => user.username ? setPageState({ ...pageState, profileMenu: !pageState.profileMenu }) : ''} className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>{user.username ? user.username : 'Sign in'}</a>
{user && screen.width > 600 &&
<div className={'profile-dropdown bg-secondary ml-6'} style={pageState.profileMenu ? { display: 'block' } : {display: 'none'}}>
<a href={'#'}><div className={'p-1'}>Profile</div></a>
<a href={'#'}><div className={'p-1'}>Feed</div></a>
<a href={'#'}><div className={'p-1'}>Add location</div></a>
<a href={'#'} onClick={handleLogout}><div className={'p-1'}>Logout</div></a>
{/* <div className={'p-2'}><a href={'#'}>Halo</a></div> */}
{/* <div className={'p-2'}><a href={'#'}>Halo</a></div> */}
</div>
}
</div>
</div> </div>
</header> </header>
) )

View File

@ -1,7 +1,74 @@
@media screen and (max-width: 768px) {
a.navLink {
font-size: 12px;
padding: 0px 20px;
}
}
@media screen and (max-width: 488px) {
a.navLink{
/* display: none; */
margin-top: 10px;
width: 100%;
padding: 0;
border-bottom: 1px solid white;
padding: 10px 0
}
div.nav-container {
padding: 0 20px;
height: 100vh;
/* align-items: start; */
justify-content: start;
flex-direction: column;
}
div.nav-container-disabled {
padding: 4px;
height: 0;
}
a.navLink-disabled {
display: none;
}
form.search-input {
display: none;
margin-left: 0;
}
button.dropdown-menu {
display: block;
}
.search-input-dropdown {
margin-right: 10px;
display: block !important;
}
h1.title-dropdown {
display: none;
}
a.navLink:hover{
border-bottom-width: 1px;
margin-bottom: 0px;
}
}
@media screen and (max-width: 390px){
input.text-input-search {
width: 230px;
}
}
.dropdown-menu { .dropdown-menu {
display: none; display: none;
} }
.search-input {
margin-left: auto;
}
label { label {
position: relative; position: relative;
@ -35,33 +102,6 @@ label:before {
font-size: 0.8em; font-size: 0.8em;
} }
.profile-dropdown {
display: block;
position: absolute;
padding: 5px;
width: 135px;
font-size: 13px;
}
.profile-dropdown a div:hover {
background-color: #a8adb3;
color: white;
}
.profile-dropdown-img {
position: absolute;
font-size: 13px;
padding: 5px;
margin-top: 5px;
right: 70px;
width: 130px;
z-index: 9999;
}
.profile-dropdown-img a div:hover {
background-color: #a8adb3;
color: white;
}
.nav-container { .nav-container {
background-color: #2f3136; background-color: #2f3136;
@ -75,90 +115,3 @@ label:before {
max-width: '100%'; max-width: '100%';
text-align: 'center'; */ text-align: 'center'; */
} }
.search-input {
margin-left: auto;
}
.user-img {
display: none;
}
@media screen and (max-width: 768px) {
a.navLink {
font-size: 12px;
padding: 0px 20px;
}
}
@media screen and (max-width: 488px) {
a.navLink{
/* display: none; */
margin-top: 10px;
width: 100%;
padding: 0;
border-bottom: 1px solid white;
padding: 10px 0
}
.profile-container {
display: none;
}
div.nav-container {
padding: 0 20px;
height: 100vh;
/* align-items: start; */
justify-content: start;
flex-direction: column;
}
div.nav-container-disabled {
padding: 4px;
height: 0;
}
a.navLink-disabled {
display: none;
}
form.search-input {
display: none;
margin-left: 0;
}
div.user-img {
display: block;
margin-left: auto;
}
button.dropdown-menu {
display: block;
}
.search-input-dropdown {
margin-right: 10px;
display: block !important;
}
h1.title-dropdown {
display: none;
}
a.navLink:hover{
border-bottom-width: 1px;
margin-bottom: 0px;
}
.user-img .profile-dropdown-img {
display: block;
}
}
@media screen and (max-width: 390px){
input.text-input-search {
width: 230px;
}
}

View File

@ -1,6 +0,0 @@
import './style.css'
export default function SpinnerLoading() {
return (
<div className={'spinner'} style={{ display: 'inline-block'}}></div>
)
}

View File

@ -1,16 +0,0 @@
@keyframes spinner {
to {transform: rotate(360deg);}
}
.spinner:before {
content: '';
box-sizing: border-box;
position: relative;
display: inline-block;
width: 16px;
height: 16px;
border-radius: 50%;
border: 2px solid #ccc;
border-top-color: #000;
animation: spinner .7s linear infinite;
}

View File

@ -2,16 +2,10 @@ import Header from "./Header";
import SeparatorWithAnchor from "./Separator/WithAnchor"; import SeparatorWithAnchor from "./Separator/WithAnchor";
import DefaultSeparator from "./Separator/Default"; import DefaultSeparator from "./Separator/Default";
import Footer from './Footer/'; import Footer from './Footer/';
import CustomInterweave from "./CustomInterweave";
import SpinnerLoading from "./Loading/Spinner";
export { export {
Header, Header,
SeparatorWithAnchor, SeparatorWithAnchor,
DefaultSeparator, DefaultSeparator,
Footer, Footer
CustomInterweave,
SpinnerLoading,
} }

View File

@ -1 +0,0 @@
export const LOGOUT = 'LOGOUT';

View File

@ -1,8 +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 LOGOUT_URI = `${BASE_URL}/user/logout`
const GET_LIST_LOCATIONS_URI = `${BASE_URL}/locations`; const GET_LIST_LOCATIONS_URI = `${BASE_URL}/locations`;
const GET_LIST_TOP_LOCATIONS = `${BASE_URL}/locations/top-ratings` const GET_LIST_TOP_LOCATIONS = `${BASE_URL}/locations/top-ratings`
@ -12,18 +11,13 @@ const GET_LOCATION_TAGS_URI = `${BASE_URL}/location/tags`
const GET_IMAGES_BY_LOCATION_URI = `${BASE_URL}/images/location` const GET_IMAGES_BY_LOCATION_URI = `${BASE_URL}/images/location`
const POST_REVIEW_LOCATION_URI = `${BASE_URL}/review/location`
export { export {
BASE_URL, BASE_URL,
SIGNUP_URI, SIGNUP_URI,
LOGIN_URI,
LOGOUT_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,
GET_LOCATION_URI, GET_LOCATION_URI,
GET_LOCATION_TAGS_URI, GET_LOCATION_TAGS_URI,
GET_IMAGES_BY_LOCATION_URI, GET_IMAGES_BY_LOCATION_URI,
POST_REVIEW_LOCATION_URI
} }

View File

@ -1,2 +0,0 @@
export const DEFAULT_AVATAR_IMG = 'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png';

View File

@ -1,20 +0,0 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = {}
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
authAdded(state, action) {
const current_user = action.payload;
let newState = { ...state }
newState = current_user
return newState
}
}
})
export const { authAdded } = authSlice.actions;
export default authSlice.reducer;

View File

@ -1,14 +0,0 @@
export interface IUser {
id: Number,
email: String,
username: String,
avatar_picture: String,
banned_at: NullValueRes<"Time", String>,
banned_until: NullValueRes<"Time", String>,
ban_reason: String,
is_permaban: boolean,
is_admin: boolean,
is_critics: boolean,
is_verfied: boolean,
social_media: NullValueRes<"RawMessage", any>
}

View File

@ -1,5 +0,0 @@
import authSlice from "./auth/authSlice/authSlice";
export {
authSlice
}

View File

@ -18,13 +18,6 @@
-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;

View File

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

View File

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

View File

@ -1,23 +1,35 @@
import { useNavigate, useParams } from 'react-router-dom'; import { useLocation, useParams } from 'react-router-dom';
import { ChangeEvent, TargetedEvent } from 'preact/compat'; import { ChangeEvent, TargetedEvent } from 'preact/compat';
import { useEffect, useRef, useState } from 'preact/hooks'; import { useEffect, useRef, useState } from 'preact/hooks';
import './index.css';
import Lightbox from 'yet-another-react-lightbox'; import Lightbox from 'yet-another-react-lightbox';
import useCallbackState from '../../types/state-callback'; import useCallbackState from '../../types/state-callback';
import { import { EmptyLocationDetailResponse, LocationDetailResponse, LocationResponse, emptyLocationResponse } from './types';
EmptyLocationDetailResponse, import { useAutosizeTextArea } from '../../utils';
LocationDetailResponse, import { getImagesByLocationService, getLocationService } from "../../services";
LocationResponse, import { DefaultSeparator, SeparatorWithAnchor } from '../../components';
emptyLocationResponse,
CurrentUserLocationReviews, function LocationDetail() {
} from './types'; const [locationDetail, setLocationDetail] = useCallbackState<LocationDetailResponse>(EmptyLocationDetailResponse)
import { handleAxiosError, useAutosizeTextArea } from '../../utils'; const [locationImages, setLocationImages] = useState<LocationResponse>(emptyLocationResponse())
import { getImagesByLocationService, getLocationService, postReviewLocation } from "../../services"; const [lightboxOpen, setLightboxOpen] = useState<boolean>(false)
import { DefaultSeparator, SeparatorWithAnchor, CustomInterweave, SpinnerLoading } from '../../components'; const [pageState, setPageState] = useState({
import { useSelector } from 'react-redux'; critic_filter_name: 'highest rated',
import { UserRootState } from '../../store/type'; critic_filter_type: 0,
import { DEFAULT_AVATAR_IMG } from '../../constants/default'; show_sort: false
import './index.css'; })
import { AxiosError } from 'axios'; const [reviewValue, setReviewValue] = useState({
review_textArea: '',
score_input: '',
})
const [isLoading, setIsLoading] = useState<boolean>(true)
const textAreaRef = useRef<HTMLTextAreaElement>(null);
useAutosizeTextArea(textAreaRef.current, reviewValue.review_textArea);
const { state } = useLocation();
const { id } = useParams()
const SORT_TYPE = [ const SORT_TYPE = [
'highest rated', 'highest rated',
@ -26,46 +38,14 @@ const SORT_TYPE = [
'oldest' '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: '',
})
const [reviewValue, setReviewValue] = useState({
review_textArea: '',
score_input: '',
})
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 { id } = useParams()
async function getLocationDetail(): Promise<void> { async function getLocationDetail(): Promise<void> {
try { try {
const res = await getLocationService(Number(id)) const res = await getLocationService(Number(id))
setLocationDetail(res.data, (val) => { setLocationDetail(res.data, (val) => {
getImage(val.detail.thumbnail.String.toString()) getImage(val.detail.thumbnail.String.toString())
}) })
} catch (error) { } catch (err) {
let err = error as AxiosError; console.log(err)
if (err.response?.status == 404) {
navigate("/")
}
alert(error)
} }
} }
@ -96,75 +76,11 @@ function LocationDetail() {
...reviewValue, ...reviewValue,
score_input: val.value score_input: val.value
}) })
setPageState({
...pageState,
is_score_rating_panic_msg: ''
})
} }
function onChangeCriticsSort(e: TargetedEvent<HTMLAnchorElement>, sort_name: string, sort_type: number): void { function onChangeCriticsSort(e: TargetedEvent<HTMLAnchorElement>, sort_name: string, sort_type: number): void {
e.preventDefault() e.preventDefault()
setPageState({ ...pageState, show_sort: false, critic_filter_name: sort_name, critic_filter_type: sort_type }) setPageState({ show_sort: false, critic_filter_name: sort_name, critic_filter_type: sort_type })
}
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();
navigate('/login', { state: { from: `/location/${id}` } })
} }
useEffect(() => { useEffect(() => {
@ -221,36 +137,36 @@ function LocationDetail() {
<div className={'p-4 bg-secondary mt-3'} style={{ width: '100%', height: 120, borderTopLeftRadius: 10, borderTopRightRadius: 10 }}> <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={'font-bold ml-1 text-xs'}>CRITICS SCORE</div>
<div className={'text-4xl text-center mt-2 mr-4'} style={{ width: 95, float: 'left' }}> <div className={'text-4xl text-center mt-2 mr-4'} style={{ width: 95, float: 'left' }}>
{locationDetail.detail.critic_count !== 0 ? Math.floor(Number(locationDetail.detail.critic_score) / Number(locationDetail.detail.critic_count)) : "NR"} {state.critic_count !== 0 ? state.critic_score : "NR"}
<div className={"items-center p-2"}> <div className={"items-center p-2"}>
<div className={'mr-3 users-score-bar'}> <div className={'mr-3 users-score-bar'}>
<div className={"mt-1"} style={{ height: 4, width: 80, backgroundColor: "#72767d" }}> <div className={"mt-1"} style={{ height: 4, width: 80, backgroundColor: "#72767d" }}>
<div style={{ height: 4, width: ` ${locationDetail.detail.critic_count !== 0 ? Number(locationDetail.detail.critic_score) : 0}%`, backgroundColor: 'green' }} /> <div style={{ height: 4, width: ` ${state.critic_count !== 0 ? Number(state.critic_score) / Number(state.critic_count) * 10 : 0}%`, backgroundColor: 'green' }} />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{locationDetail.detail.critic_count !== 0 && {state.critic_count !== 0 &&
<div className={'ml-14 text-sm'}> <div className={'ml-14 text-sm'}>
Based on {locationDetail.detail.critic_count} reviews Based on {state.critic_count} reviews
</div> </div>
} }
</div> </div>
<div className={'p-4 bg-secondary mt-1 inline-block'} style={{ width: '100%', height: 120, borderBottomLeftRadius: 10, borderBottomRightRadius: 10 }}> <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={'font-bold ml-2 text-xs'}>USERS SCORE</div>
<div className={'text-4xl text-center mt-2 mr-4'} style={{ width: 95, float: 'left' }}> <div className={'text-4xl text-center mt-2'} style={{ width: 95, float: 'left' }}>
{locationDetail.detail.user_count !== 0 ? Math.floor(Number(locationDetail.detail.user_score) / Number(locationDetail.detail.user_count)) : "NR"} {state.user_count !== 0 ? state.user_score : "NR"}
<div className={"items-center p-2"}> <div className={"items-center p-2"}>
<div className={'mr-3 users-score-bar'}> <div className={'mr-3 users-score-bar'}>
<div className={"mt-1"} style={{ height: 4, width: 80, backgroundColor: "#72767d" }}> <div className={"mt-1"} style={{ height: 4, width: 80, backgroundColor: "#72767d" }}>
<div style={{ height: 4, width: ` ${locationDetail.detail.user_count !== 0 ? Number(locationDetail.detail.user_score) / Number(locationDetail.detail.user_count) : 0}%`, backgroundColor: 'green' }} /> <div style={{ height: 4, width: ` ${state.user_count !== 0 ? Number(state.user_score) / Number(state.user_score) * 10 : 0}%`, backgroundColor: 'green' }} />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{locationDetail.detail.user_count !== 0 && {state.user_count !== 0 &&
<div className={'ml-14 text-sm'}> <div className={'ml-4'}>
Based on {locationDetail.detail.user_count} reviews Based on {state.user_count} reviews
</div> </div>
} }
</div> </div>
@ -296,33 +212,23 @@ function LocationDetail() {
<section name={'REVIEWS SECTION'}> <section name={'REVIEWS SECTION'}>
<div className={'mt-5'} style={{ tableLayout: 'fixed', display: 'table', width: '100%' }}> <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' }}> <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 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 name="REVIEW INPUT TEXTAREA" className={'reviewContainer p-4'} style={{ backgroundColor: '#2f3136' }}>
<div className={'reviewBoxContent'} style={{ width: '75%', margin: '0 auto' }}> <div className={'reviewBoxContent'} style={{ width: '75%', margin: '0 auto' }}>
<div className={'userImage mr-3'} style={{ width: 55, float: 'left' }}> <div className={'userImage mr-3'} style={{ width: 55, float: 'left' }}>
<a href={'#'}> <a href={'#'}>
<img loading={'lazy'} src={user.avatar_picture != '' ? user.avatar_picture.toString() : DEFAULT_AVATAR_IMG} style={{ aspectRatio: '1/1' }} /> <img loading={'lazy'} src={'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'} />
</a> </a>
</div> </div>
<div style={{ display: 'block' }}> <div style={{ display: 'block' }}>
<a href={'#'}>{user.username}</a> <a href={'#'}>user</a>
</div> </div>
<div className={'ratingInput'} style={currentUserReview ? { margin: '0 0 10px' } : { margin: '5px 0 10px' }}> <div className={'ratingInput'} style={{ margin: '5px 0 10px' }}>
{currentUserReview ? <div style={{ float: 'left' }}>
<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 <input
type={'text'} type={'text'}
pattern={"\d*"} pattern={"\d*"}
@ -334,21 +240,11 @@ function LocationDetail() {
autoComplete={'off'} autoComplete={'off'}
/> />
<div className={'inline-block text-xs ml-2 text-tertiary'}>/ score</div> <div className={'inline-block text-xs ml-2 text-tertiary'}>/ score</div>
{pageState.is_score_rating_panic_msg && </div>
<div className={'inline-block text-xs ml-2 text-error'}>{pageState.is_score_rating_panic_msg}</div>
}
</>
}
<div style={{ clear: 'both' }} /> <div style={{ clear: 'both' }} />
</div> </div>
<div className={'mt-3'} style={{ width: '100%' }}> <div className={'mt-3'} style={{ width: '100%' }}>
{currentUserReview ?
<CustomInterweave
content={currentUserReview.comments}
/>
:
<textarea <textarea
onChange={handleTextAreaChange} onChange={handleTextAreaChange}
ref={textAreaRef} ref={textAreaRef}
@ -356,27 +252,21 @@ function LocationDetail() {
value={reviewValue.review_textArea} value={reviewValue.review_textArea}
style={{ border: 'none', overflow: 'auto', outline: 'none', boxShadow: 'none', backgroundColor: '#40444b', width: '100%', minHeight: 100, overflowY: 'hidden' }} style={{ border: 'none', overflow: 'auto', outline: 'none', boxShadow: 'none', backgroundColor: '#40444b', width: '100%', minHeight: 100, overflowY: 'hidden' }}
/> />
}
</div> </div>
<div style={{ textAlign: 'right', width: "100%" }}> <div style={{ textAlign: 'right', width: "100%" }}>
<div style={{ display: 'inline-block', fontSize: 11, verticalAlign: 'middle', margin: '0 10px 0 0', letterSpacing: .5 }}> <div style={{ display: 'inline-block', fontSize: 11, verticalAlign: 'middle', margin: '0 10px 0 0', letterSpacing: .5 }}>
<a>Review Guidelines</a> <a>Review Guidelines</a>
</div> </div>
{pageState.on_submit_loading ? <span className={'text-xxs p-1'} style={{ backgroundColor: 'gray', letterSpacing: 1 }}>
<SpinnerLoading /> <a href={'#'}>
:
<span className={'text-xxs p-1 text-area-button'} style={pageState.enable_post ? '' : { display: 'none'}}>
<a href={'#'} onClick={handleSubmitReview}>
POST POST
</a> </a>
</span> </span>
}
</div> </div>
</div> </div>
</div> </div>
}
<div name={'CRTICITS REVIEW'} style={{ margin: '50px 0', textAlign: 'left' }}> <div name={'CRTICITS REVIEW'} style={{ margin: '50px 0', textAlign: 'left' }}>
<SeparatorWithAnchor pageName={"critic's review"} pageLink='#' /> <SeparatorWithAnchor pageName={"critic's review"} pageLink='#' />
<div className={'criticSortFilter'}> <div className={'criticSortFilter'}>
@ -393,14 +283,13 @@ function LocationDetail() {
</div> </div>
<div style={{ clear: 'both' }} /> <div style={{ clear: 'both' }} />
{locationDetail.critics_review.map(x => (
<div className={''} style={{ padding: '15px 0' }}> <div className={''} style={{ padding: '15px 0' }}>
<div style={{ float: 'left' }}> <div style={{ float: 'left' }}>
<div style={{ fontSize: 20, marginRight: 20, textAlign: 'center', width: 55, marginBottom: 3 }}> <div style={{ fontSize: 20, marginRight: 20, textAlign: 'center', width: 55, marginBottom: 3 }}>
{x.score} 90
</div> </div>
<div style={{ height: 4, width: 55, position: 'relative', backgroundColor: '#d8d8d8' }}> <div style={{ height: 4, width: 55, position: 'relative', backgroundColor: '#d8d8d8' }}>
<div style={{ height: 4, backgroundColor: '#85ce73', width: `${x.score}%` }} /> <div style={{ height: 4, backgroundColor: '#85ce73', width: '90%' }} />
</div> </div>
</div> </div>
<div className={'mr-3'} style={{ display: 'inline-block', width: 40 }}> <div className={'mr-3'} style={{ display: 'inline-block', width: 40 }}>
@ -408,21 +297,19 @@ function LocationDetail() {
<img <img
loading={'lazy'} loading={'lazy'}
style={{ width: '100%' }} 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'} src={'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'}
/> />
</a> </a>
</div> </div>
<div style={{ display: 'inline-block', verticalAlign: 'top' }}> <div style={{ display: 'inline-block', verticalAlign: 'top' }}>
<div style={{ fontWeight: 700, fontSize: 16, lineHeight: 'initial' }}> <div style={{ fontWeight: 700, fontSize: 16, lineHeight: 'initial' }}>
<a> <a>
<span>{x.username}</span> <span>Benito Mussolini</span>
</a> </a>
</div> </div>
</div> </div>
<div style={{ fontSize: 15, lineHeight: '24px', margin: '5px 75px 1px' }}> <div style={{ fontSize: 15, lineHeight: '24px', margin: '5px 75px 1px', wordWrap: 'break-word' }}>
<CustomInterweave <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>
content={x.comments}
/>
</div> </div>
<div className={'reviewLinks'} style={{ marginLeft: 72 }}> <div className={'reviewLinks'} style={{ marginLeft: 72 }}>
<div className={'mr-2'} style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}> <div className={'mr-2'} style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
@ -439,41 +326,166 @@ function LocationDetail() {
</div> </div>
</div> </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>
<div name={'USERS REVIEW'} style={{ margin: '50px 0', textAlign: 'left' }}> <div name={'USERS REVIEW'} style={{ margin: '50px 0', textAlign: 'left' }}>
<SeparatorWithAnchor pageName={"User's review"} pageLink='#' secondLink={locationDetail.users_review.length > 0 ? '#' : ''} /> <SeparatorWithAnchor pageName={"Popular User's review"} pageLink='#' secondLink='#'/>
{ locationDetail.users_review.length > 0 ?
<> <div className={''} style={{ padding: '15px 0' }}>
{locationDetail.users_review.map(x => (
<div style={{ padding: '15px 0' }}>
<div className={'mr-5'} style={{ width: 45, float: 'left' }}> <div className={'mr-5'} style={{ width: 45, float: 'left' }}>
<a href="#"> <a href="#">
<img <img
loading={'lazy'} loading={'lazy'}
style={{ width: '100%' }} 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'} src={'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'}
/> />
</a> </a>
</div> </div>
<div> <div>
<div style={{ fontWeight: 700, fontSize: 16, lineHeight: 'initial' }}> <div style={{ fontWeight: 700, fontSize: 16, lineHeight: 'initial' }}>
<a> <a>
<span>{x.username}</span> <span>Benito Mussolini</span>
</a> </a>
</div> </div>
</div> </div>
<div className={'inline-block'}> <div className={'inline-block'}>
<div className={'text-sm text-center'} >{x.score}</div> <div className={'text-sm text-center'} >80</div>
<div style={{ height: 4, width: 25, position: 'relative', backgroundColor: '#d8d8d8' }}> <div style={{ height: 4, width: 25, position: 'relative', backgroundColor: '#d8d8d8' }}>
<div style={{ height: 4, backgroundColor: '#85ce73', width: `${x.score}%` }} /> <div style={{ height: 4, backgroundColor: '#85ce73', width: '90%' }} />
</div> </div>
</div> </div>
<div style={{ fontSize: 15, lineHeight: '24px', margin: '10px 65px 1px', wordWrap: 'break-word' }}> <div style={{ fontSize: 15, lineHeight: '24px', margin: '10px 65px 1px', wordWrap: 'break-word' }}>
<CustomInterweave <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>
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', 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>
<div className={'reviewLinks'} style={{ marginLeft: 63 }}> <div className={'reviewLinks'} style={{ marginLeft: 63 }}>
<div className={'mr-2'} style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}> <div className={'mr-2'} style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
@ -490,24 +502,17 @@ function LocationDetail() {
</div> </div>
</div> </div>
</div> </div>
))}
<div className={'review-more-button text-center text-sm mt-5'}> <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 More
</a> </a>
</div> </div>
</>
:
<>
<span className={'text-sm italic'}>No users review to display</span>
</>
}
</div> </div>
{/* <div name={'USERS REVIEW'} style={{ margin: '50px 0', textAlign: 'left' }}> <div name={'USERS REVIEW'} style={{ margin: '50px 0', textAlign: 'left' }}>
<SeparatorWithAnchor pageName={"Popular User's review"} pageLink='#' secondLink='#' /> <SeparatorWithAnchor pageName={"Recent User's review"} pageLink='#' secondLink='#'/>
<div className={''} style={{ padding: '15px 0' }}> <div className={''} style={{ padding: '15px 0' }}>
<div className={'mr-5'} style={{ width: 45, float: 'left' }}> <div className={'mr-5'} style={{ width: 45, float: 'left' }}>
<a href="#"> <a href="#">
@ -596,18 +601,18 @@ function LocationDetail() {
More More
</a> </a>
</div> </div>
</div> */} </div>
<div className={'mb-5'}> <div className={'mb-5'}>
CONTRUBITION CONTRUBITION
<DefaultSeparator /> <DefaultSeparator />
anoeantoeh aoenthaoe aoenth aot anoeantoeh aoenthaoe aoenth aot
</div> </div>
</div> </div>
{/* {screen.width >= 1024 && {screen.width >= 1024 &&
<div className={'bg-secondary'} style={{ display: 'table-cell', position: 'relative', verticalAlign: 'top', width: 330, textAlign: 'left', padding: 15, boxSizing: 'border-box', height: 1080 }}> <div className={'bg-secondary'} style={{ display: 'table-cell', position: 'relative', verticalAlign: 'top', width: 330, textAlign: 'left', padding: 15, boxSizing: 'border-box', height: 1080 }}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Reprehenderit cumque aliquam doloribus in reiciendis? Laborum, ea assumenda, tempora dolore placeat aspernatur, cumque totam sequi debitis dolor nam eligendi suscipit aliquid? Lorem ipsum dolor sit amet consectetur adipisicing elit. Reprehenderit cumque aliquam doloribus in reiciendis? Laborum, ea assumenda, tempora dolore placeat aspernatur, cumque totam sequi debitis dolor nam eligendi suscipit aliquid?
</div> </div>
} */} }
</div> </div>
<div style={{ clear: 'both'}} /> <div style={{ clear: 'both'}} />
</section> </section>

View File

@ -4,16 +4,13 @@ export interface ILocationDetail {
id: Number, id: Number,
name: String, name: String,
address: String, address: String,
regency_name: String,
province_name: String,
region_name: String,
google_maps_link: String, google_maps_link: String,
thumbnail: NullValueRes<"String", String>, thumbnail: NullValueRes<"String", String>,
submitted_by: Number, submitted_by: Number,
critic_score: Number, regency_name: String,
critic_count: Number, province_name: String,
user_score: Number, region_name: String,
user_count: Number submitted_by_user: String
} }
export function emptyLocationDetail(): ILocationDetail { export function emptyLocationDetail(): ILocationDetail {
@ -27,38 +24,19 @@ export function emptyLocationDetail(): ILocationDetail {
regency_name: '', regency_name: '',
region_name: '', region_name: '',
submitted_by: 0, submitted_by: 0,
critic_score: 0, submitted_by_user: ''
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 { export interface LocationDetailResponse {
detail: ILocationDetail, detail: ILocationDetail,
tags: Array<String> tags: Array<String>
users_review: Array<LocationReviewsResponse>,
critics_review: Array<LocationReviewsResponse>
} }
export function EmptyLocationDetailResponse(): LocationDetailResponse { export function EmptyLocationDetailResponse(): LocationDetailResponse {
return { return {
detail: emptyLocationDetail(), detail: emptyLocationDetail(),
tags: [], tags: []
critics_review: Array<LocationReviewsResponse>(),
users_review: Array<LocationReviewsResponse>()
} }
} }
@ -69,6 +47,15 @@ export interface LocationImage extends SlideImage {
uploaded_by: String uploaded_by: String
} }
export function emptyLocationImage(): LocationImage {
return {
id: 0,
src: '',
created_at: '',
uploaded_by: ''
}
}
export interface LocationResponse { export interface LocationResponse {
total_image: Number, total_image: Number,
images: Array<LocationImage> images: Array<LocationImage>
@ -77,18 +64,6 @@ export interface LocationResponse {
export function emptyLocationResponse(): LocationResponse { export function emptyLocationResponse(): LocationResponse {
return { return {
total_image: 0, total_image: 0,
images: Array<LocationImage>() images: [emptyLocationImage()]
} }
} }
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

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

View File

@ -1,120 +0,0 @@
import { ChangeEvent, TargetedEvent, useState } from "preact/compat";
import { createAccountService, loginService } from "../../services";
import { useLocation, 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 { state } = useLocation()
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))
if (state) {
navigation(state.from)
return
}
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;
}
dispatch(authAdded(res.data))
if (state) {
navigation(state.from)
return
}
navigation("/")
} 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

@ -1,9 +0,0 @@
const NotFound = () => (
<div class={'main-content content'}>
<img
src={'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fmedia.tenor.com%2FUWHgJ8QRcZsAAAAi%2Fturtle-huh-meme.gif&f=1&nofb=1&ipt=64af045b4bd24f01c00528f14b53fb17c49ed5b603fc58332e8a5de65a1b89ef&ipo=images'}
/>
</div>
)
export default NotFound;

View File

@ -4,16 +4,10 @@ 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';
import NotFound from "./NotFound";
export { export {
Login,
Home, Home,
NotFound,
BestLocation, BestLocation,
LocationDetail, LocationDetail,

View File

@ -1,18 +0,0 @@
import { combineReducers } from "@reduxjs/toolkit";
import { LOGOUT } from "../constants/actions";
import { authSlice } from "../features";
const appReducer = combineReducers({
auth: authSlice
});
const rootReducer = (state: any, action: any) => {
if (action.type === LOGOUT) {
// remove token
state = undefined
}
return appReducer(state, action);
}
export default rootReducer;

View File

@ -4,8 +4,7 @@ import {
Home, Home,
LocationDetail, LocationDetail,
NewsEvent, NewsEvent,
Story, Story
Login
} from '../pages'; } from '../pages';
const routes = [ const routes = [

View File

@ -1,59 +0,0 @@
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 };
try {
const response = await client({ method: 'POST', url: SIGNUP_URI, 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 };
try {
const response = await client({ method: 'POST', url: LOGIN_URI, data: { username, password }, withCredentials: true })
newState.data = response.data
newState.error = null
return newState
} catch (error) {
newState.error = error
return newState
}
}
async function logoutService() {
const newState = { ...initialState };
try {
const response = await client({ method: 'POST', url: LOGIN_URI})
newState.data = response.data
newState.error = null
return newState
} catch (error) {
newState.error = error
return newState
}
}
export {
loginService,
createAccountService,
logoutService
};

View File

@ -6,8 +6,7 @@ export const client = (props: AxiosRequestConfig): AxiosPromise => axios({
baseURL: `${BASE_URL}`, baseURL: `${BASE_URL}`,
url: props.url, url: props.url,
headers: props.headers, headers: props.headers,
data: props.data, data: props.data
...props
}) })
// export const authClient = (props: AxiosRequestConfig) => axios({ // export const authClient = (props: AxiosRequestConfig) => axios({

View File

@ -5,21 +5,14 @@ import {
getLocationService, getLocationService,
getLocationTagsService, getLocationTagsService,
} from "./locations"; } from "./locations";
import { getImagesByLocationService } from "./images" import { getImagesByLocationService } from "./images"
import { createAccountService, loginService, logoutService } from "./auth";
import { postReviewLocation } from "./review";
export { export {
createAccountService,
loginService,
logoutService,
getListLocationsService, getListLocationsService,
getListRecentLocationsRatingsService, getListRecentLocationsRatingsService,
getListTopLocationsService, getListTopLocationsService,
getLocationService, getLocationService,
getLocationTagsService, getLocationTagsService,
getImagesByLocationService, getImagesByLocationService,
postReviewLocation
} }

View File

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

View File

@ -1,34 +0,0 @@
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

@ -1,25 +0,0 @@
import { applyMiddleware, createStore, compose } from '@reduxjs/toolkit'
import storage from 'redux-persist/lib/storage'
import persistReducer from 'redux-persist/es/persistReducer';
import rootReducer from '../reducers';
import thunk from 'redux-thunk';
import { persistStore } from 'redux-persist';
const composeEnhancers = (window as any)['__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'] as typeof compose || compose;
const persistConfig = {
key: 'root',
blacklist: [],
whitelist: [
"auth"
],
storage
}
const persistedReducer = persistReducer(persistConfig, rootReducer);
export type RootState = ReturnType<typeof store.getState>
export const store = createStore(persistedReducer, composeEnhancers(applyMiddleware(thunk)))
export const persistore = persistStore(store)

View File

@ -1,6 +0,0 @@
import { IUser } from "../features/auth/authSlice/type";
import { RootState } from "./config";
export interface UserRootState extends RootState {
auth: IUser
}

View File

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

View File

@ -1,5 +0,0 @@
import { AxiosError } from "axios";
export function handleAxiosError(error: AxiosError) {
return error.response?.data
}

View File

@ -1,7 +1,5 @@
import useAutosizeTextArea from "./useAutosizeTextArea"; import useAutosizeTextArea from "./useAutosizeTextArea";
import { handleAxiosError } from "./common";
export { export {
useAutosizeTextArea, useAutosizeTextArea
handleAxiosError
} }

View File

@ -7,8 +7,7 @@ export default {
current: 'currentColor', current: 'currentColor',
primary: '#202225', primary: '#202225',
secondary: '#2f3136', secondary: '#2f3136',
tertiary: '#a8adb3', tertiary: '#a8adb3'
error: '#ff5454',
}, },
borderColor: { borderColor: {
primary: '#38444d', primary: '#38444d',

191
yarn.lock
View File

@ -197,13 +197,6 @@
"@babel/plugin-syntax-jsx" "^7.22.5" "@babel/plugin-syntax-jsx" "^7.22.5"
"@babel/types" "^7.22.5" "@babel/types" "^7.22.5"
"@babel/runtime@^7.12.1", "@babel/runtime@^7.9.2":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8"
integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/template@^7.22.5": "@babel/template@^7.22.5":
version "7.22.5" version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec"
@ -441,16 +434,6 @@
"@prefresh/utils" "^1.2.0" "@prefresh/utils" "^1.2.0"
"@rollup/pluginutils" "^4.2.1" "@rollup/pluginutils" "^4.2.1"
"@reduxjs/toolkit@^1.9.5":
version "1.9.5"
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.5.tgz#d3987849c24189ca483baa7aa59386c8e52077c4"
integrity sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==
dependencies:
immer "^9.0.21"
redux "^4.2.1"
redux-thunk "^2.4.2"
reselect "^4.1.8"
"@remix-run/router@1.9.0": "@remix-run/router@1.9.0":
version "1.9.0" version "1.9.0"
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.9.0.tgz#9033238b41c4cbe1e961eccb3f79e2c588328cf6" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.9.0.tgz#9033238b41c4cbe1e961eccb3f79e2c588328cf6"
@ -464,48 +447,6 @@
estree-walker "^2.0.1" estree-walker "^2.0.1"
picomatch "^2.2.2" picomatch "^2.2.2"
"@types/hoist-non-react-statics@^3.3.0", "@types/hoist-non-react-statics@^3.3.1":
version "3.3.2"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#dc1e9ded53375d37603c479cc12c693b0878aa2a"
integrity sha512-YIQtIg4PKr7ZyqNPZObpxfHsHEmuB8dXCxd6qVcGuQVDK2bpsF7bYNnBJ4Nn7giuACZg+WewExgrtAJ3XnA4Xw==
dependencies:
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
"@types/prop-types@*":
version "15.7.6"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.6.tgz#bbf819813d6be21011b8f5801058498bec555572"
integrity sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg==
"@types/react-redux@^7.1.26":
version "7.1.26"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.26.tgz#84149f5614e40274bb70fcbe8f7cae6267d548b1"
integrity sha512-UKPo7Cm7rswYU6PH6CmTNCRv5NYF3HrgKuHEYTK8g/3czYLrUux50gQ2pkxc9c7ZpQZi+PNhgmI8oNIRoiVIxg==
dependencies:
"@types/hoist-non-react-statics" "^3.3.0"
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
redux "^4.0.0"
"@types/react@*":
version "18.2.22"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.22.tgz#abe778a1c95a07fa70df40a52d7300a40b949ccb"
integrity sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.3"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==
"@types/use-sync-external-store@^0.0.3":
version "0.0.3"
resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43"
integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==
ansi-styles@^3.2.1: ansi-styles@^3.2.1:
version "3.2.1" version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@ -670,11 +611,6 @@ cssesc@^3.0.0:
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
csstype@^3.0.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
debug@^4.1.0, debug@^4.3.1: debug@^4.1.0, debug@^4.3.1:
version "4.3.4" version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
@ -702,21 +638,6 @@ electron-to-chromium@^1.4.477:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.499.tgz#dc36b67f4c8e273524e8d2080c5203a6a76987b6" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.499.tgz#dc36b67f4c8e273524e8d2080c5203a6a76987b6"
integrity sha512-0NmjlYBLKVHva4GABWAaHuPJolnDuL0AhV3h1hES6rcLCWEIbRL6/8TghfsVwkx6TEroQVdliX7+aLysUpKvjw== integrity sha512-0NmjlYBLKVHva4GABWAaHuPJolnDuL0AhV3h1hES6rcLCWEIbRL6/8TghfsVwkx6TEroQVdliX7+aLysUpKvjw==
emojibase-regex@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/emojibase-regex/-/emojibase-regex-6.0.1.tgz#dc0b33d05c02f045ea44795d453698b205d41f0f"
integrity sha512-Mj1UT6IIk4j91yMFE0QetpUYcmsr5ZDkkOIMSGafhIgC086mBMaCh2Keaykx8YEllmV7hmx5zdANDzCYBYAVDw==
emojibase@^15.0.0:
version "15.0.0"
resolved "https://registry.yarnpkg.com/emojibase/-/emojibase-15.0.0.tgz#f41b7773ec9a8a332373c18628ff4471255bd769"
integrity sha512-bvSIs98sHaVnyKPmW+obRjo49MFx0g+rhfSz6mTePAagEZSlDPosq0b6AcSJa5gt48z3VP2ooXclyBs8vIkpGA==
emojibase@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/emojibase/-/emojibase-6.1.0.tgz#c3bc281e998a0e06398416090c23bac8c5ed3ee8"
integrity sha512-1GkKJPXP6tVkYJHOBSJHoGOr/6uaDxZ9xJ6H7m6PfdGXTmQgbALHLWaVRY4Gi/qf5x/gT/NUXLPuSHYLqtLtrQ==
esbuild@^0.18.10: esbuild@^0.18.10:
version "0.18.20" version "0.18.20"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6"
@ -750,11 +671,6 @@ escalade@^3.1.1:
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
escape-html@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
escape-string-regexp@^1.0.5: escape-string-regexp@^1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@ -872,18 +788,6 @@ has@^1.0.3:
dependencies: dependencies:
function-bind "^1.1.1" function-bind "^1.1.1"
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
dependencies:
react-is "^16.7.0"
immer@^9.0.21:
version "9.0.21"
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176"
integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==
inflight@^1.0.4: inflight@^1.0.4:
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@ -897,26 +801,6 @@ inherits@2:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
interweave-autolink@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/interweave-autolink/-/interweave-autolink-5.1.0.tgz#a5a5438c45c5e4d631838473845be1fdd38a664b"
integrity sha512-WOEakAdwqv/W2H85cLdigkpMM7o6qVg4CWM6iO5cHrFCywwUh+ILVmZgX1tHphEpa55sFdzpKNO2EHhAjbR4GA==
interweave-emoji@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/interweave-emoji/-/interweave-emoji-7.0.0.tgz#937455fcc616121761034d0c120edec4af8586c6"
integrity sha512-3yFBreW2h+I/Tjf9LpF/bKKdjGSi5DT1RxrRFibmmDjTB1tCyPe5X3XFNdglwoRPErgFL1qqFpQWQJKUlUwARg==
dependencies:
emojibase "^6.1.0"
emojibase-regex "^6.0.1"
interweave@^13.1.0:
version "13.1.0"
resolved "https://registry.yarnpkg.com/interweave/-/interweave-13.1.0.tgz#4b7a0a87a7eb32001bef64525f68d95296dee03c"
integrity sha512-JIDq0+2NYg0cgL7AB26fBcV0yZdiJvPDBp+aF6k8gq6Cr1kH5Gd2/Xqn7j8z+TGb8jCWZn739jzalCz+nPYwcA==
dependencies:
escape-html "^1.0.3"
is-binary-path@~2.1.0: is-binary-path@~2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
@ -953,7 +837,7 @@ jiti@^1.18.2:
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.19.3.tgz#ef554f76465b3c2b222dc077834a71f0d4a37569" resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.19.3.tgz#ef554f76465b3c2b222dc077834a71f0d4a37569"
integrity sha512-5eEbBDQT/jF1xg6l36P+mWGGoH9Spuy0PCdSr2dtWRDGC6ph/w9ZCL4lmESW8f8F7MwT3XKescfP0wnZWAKL9w== integrity sha512-5eEbBDQT/jF1xg6l36P+mWGGoH9Spuy0PCdSr2dtWRDGC6ph/w9ZCL4lmESW8f8F7MwT3XKescfP0wnZWAKL9w==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: js-tokens@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
@ -983,13 +867,6 @@ lines-and-columns@^1.1.6:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
loose-envify@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
lru-cache@^5.1.1: lru-cache@^5.1.1:
version "5.1.1" version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@ -1029,11 +906,6 @@ minimatch@^3.0.4:
dependencies: dependencies:
brace-expansion "^1.1.7" brace-expansion "^1.1.7"
moment@^2.29.4:
version "2.29.4"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
ms@2.1.2: ms@2.1.2:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
@ -1183,28 +1055,6 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-is@^18.0.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
react-redux@^8.1.2:
version "8.1.2"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.2.tgz#9076bbc6b60f746659ad6d51cb05de9c5e1e9188"
integrity sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==
dependencies:
"@babel/runtime" "^7.12.1"
"@types/hoist-non-react-statics" "^3.3.1"
"@types/use-sync-external-store" "^0.0.3"
hoist-non-react-statics "^3.3.2"
react-is "^18.0.0"
use-sync-external-store "^1.0.0"
react-router-dom@^6.16.0: react-router-dom@^6.16.0:
version "6.16.0" version "6.16.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.16.0.tgz#86f24658da35eb66727e75ecbb1a029e33ee39d9" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.16.0.tgz#86f24658da35eb66727e75ecbb1a029e33ee39d9"
@ -1220,13 +1070,6 @@ react-router@6.16.0:
dependencies: dependencies:
"@remix-run/router" "1.9.0" "@remix-run/router" "1.9.0"
react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
dependencies:
loose-envify "^1.1.0"
read-cache@^1.0.0: read-cache@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
@ -1241,33 +1084,6 @@ readdirp@~3.6.0:
dependencies: dependencies:
picomatch "^2.2.1" picomatch "^2.2.1"
redux-persist@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8"
integrity sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==
redux-thunk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b"
integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==
redux@^4.0.0, redux@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
dependencies:
"@babel/runtime" "^7.9.2"
regenerator-runtime@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45"
integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==
reselect@^4.1.8:
version "4.1.8"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524"
integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==
resolve@^1.1.7, resolve@^1.20.0, resolve@^1.22.2: resolve@^1.1.7, resolve@^1.20.0, resolve@^1.22.2:
version "1.22.4" version "1.22.4"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34"
@ -1403,11 +1219,6 @@ update-browserslist-db@^1.0.11:
escalade "^3.1.1" escalade "^3.1.1"
picocolors "^1.0.0" picocolors "^1.0.0"
use-sync-external-store@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
util-deprecate@^1.0.2: util-deprecate@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"