Compare commits

...

2 Commits
master ... dev

Author SHA1 Message Date
f1fd9bcbb9 adjust location state with new response 2024-08-19 07:41:35 +07:00
5183a90b59 dev 2024-05-22 11:25:20 +07:00
121 changed files with 2843 additions and 2088 deletions

0
.gitignore vendored Normal file → Executable file
View File

0
README.md Normal file → Executable file
View File

0
TODO Normal file → Executable file
View File

0
index.html Normal file → Executable file
View File

2
package.json Normal file → Executable file
View File

@ -9,8 +9,8 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@floating-ui/react": "^0.26.9",
"@reduxjs/toolkit": "^1.9.5", "@reduxjs/toolkit": "^1.9.5",
"@types/react-redux": "^7.1.26",
"axios": "^1.5.0", "axios": "^1.5.0",
"emojibase": "^15.0.0", "emojibase": "^15.0.0",
"interweave": "^13.1.0", "interweave": "^13.1.0",

2769
pnpm-lock.yaml Normal file → Executable file

File diff suppressed because it is too large Load Diff

0
postcss.config.js Normal file → Executable file
View File

0
public/vite.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

0
src/actions/LogoutAction.ts Normal file → Executable file
View File

0
src/actions/index.ts Normal file → Executable file
View File

4
src/app.css Normal file → Executable file
View File

@ -60,6 +60,10 @@
line-height: 1.25rem; line-height: 1.25rem;
} }
.input-checkbox:hover {
cursor: pointer;
}
@media screen and(max-width: 425px) { @media screen and(max-width: 425px) {
.users-score { .users-score {
font-size: .75rem; font-size: .75rem;

10
src/app.tsx Normal file → Executable file
View File

@ -14,7 +14,6 @@ import { getRoutes } from './routes';
export function App() { export function App() {
const { routes } = getRoutes(); const { routes } = getRoutes();
return ( return (
<>
<Provider store={store}> <Provider store={store}>
<PersistGate persistor={persistore}> <PersistGate persistor={persistore}>
<Router> <Router>
@ -25,7 +24,6 @@ export function App() {
let Element = element as any let Element = element as any
if (protectedRoute === "user") { if (protectedRoute === "user") {
return ( return (
<>
<Route <Route
path={path} path={path}
id={name} id={name}
@ -35,14 +33,11 @@ export function App() {
</UserProtectedRoute> </UserProtectedRoute>
} }
/> />
</>
) )
} }
if (protectedRoute === "admin") { if (protectedRoute === "admin") {
return ( return (
<>
<>
<Route <Route
path={path} path={path}
id={name} id={name}
@ -52,18 +47,14 @@ export function App() {
</AdminProtectedRoute> </AdminProtectedRoute>
} }
/> />
</>
</>
) )
} }
return ( return (
<>
<Route <Route
path={path} path={path}
id={name} id={name}
element={element} element={element}
/> />
</>
) )
})} })}
<Route path="*" element={<NotFound />} /> <Route path="*" element={<NotFound />} />
@ -72,6 +63,5 @@ export function App() {
</Router> </Router>
</PersistGate> </PersistGate>
</Provider> </Provider>
</>
) )
} }

0
src/assets/preact.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

0
src/components/Button/DefaultButton/index.tsx Normal file → Executable file
View File

0
src/components/Button/DefaultButton/style.css Normal file → Executable file
View File

View File

@ -0,0 +1,14 @@
import "./style.css";
interface ComponentProps {
label: string
onClick: () => void;
}
const FilterButton = (props: ComponentProps) => (
<button onClick={props.onClick} className="button-container">
<div className="button-label">{props.label}</div>
</button>
)
export default FilterButton;

View File

@ -0,0 +1,20 @@
.button-label {
text-align: center;
/* color: #85CE73; */
}
.button-container {
align-self: stretch;
padding-left: 12px;
padding-right: 12px;
padding-top: 10px;
padding-bottom: 10px;
border-radius: 6px;
border: 1px #58874d solid;
color: #85CE73;
}
.button-container:hover {
background-color: #58874d;
color: #FFF;
}

0
src/components/Button/WarningButton/index.tsx Normal file → Executable file
View File

0
src/components/Button/WarningButton/style.css Normal file → Executable file
View File

View File

@ -0,0 +1,47 @@
import { JSXInternal } from "node_modules/preact/src/jsx";
import { LocationInfo } from "../../../domains";
interface ComponentProps {
onCardClick: (id: Number) => void,
data: LocationInfo,
containerClass?: string,
containerStyle?: JSXInternal.CSSProperties
}
const LocationCard = (props: ComponentProps) => (
<div className={props.containerClass} style={props.containerStyle}>
<a onClick={() => props.onCardClick(props.data.id)}>
<div className={'border-secondary recently-img-container'}>
<img alt={props.data.name} src={props.data.thumbnail ? props.data.thumbnail : ''} loading="lazy" style={{ width: '100%', height: '100%' }} />
</div>
</a>
<div className={"border-primary pb-2 location-container text-sm mb-2 mt-2"}>
<p className={'location-title'}>{props.data.name}</p>
<p className={'text-xs mt-1'}>{props.data.regency_name}, {props.data.province_name}</p>
</div>
{props.data.critic_count !== 0 &&
<div className={"flex flex-row items-center mb-3"}>
<div className={'mr-3 users-score-bar'}>
<p className={'text-sm text-center'}>{props.data.critic_score}</p>
<div style={{ height: 4, width: 30, backgroundColor: "#72767d" }}>
<div style={{ height: 4, width: `${props.data.critic_score}%`, backgroundColor: 'green' }} />
</div>
</div>
<p className={"users-score"}>critic score ({props.data.critic_count})</p>
</div>
}
{props.data.user_score !== 0 &&
<div className={"flex flex-row items-center"}>
<div className={'mr-3 users-score-bar'}>
<p className={'text-sm text-center'}>{props.data.user_score}</p>
<div style={{ height: 4, width: 30, backgroundColor: "#72767d" }}>
<div style={{ height: 4, width: ` ${props.data.user_score}%`, backgroundColor: 'green' }} />
</div>
</div>
<p className={'users-score'}>user score ({props.data.user_count})</p>
</div>
}
</div>
)
export default LocationCard;

0
src/components/CustomInterweave/index.tsx Normal file → Executable file
View File

0
src/components/DropdownInput/index.tsx Normal file → Executable file
View File

0
src/components/DropdownInput/style.css Normal file → Executable file
View File

0
src/components/Footer/index.tsx Normal file → Executable file
View File

0
src/components/Footer/style.css Normal file → Executable file
View File

6
src/components/Header/index.tsx Normal file → Executable file
View File

@ -8,6 +8,7 @@ import './style.css';
import { logoutService } from "../../services"; import { logoutService } from "../../services";
import { getSearchLocationService } from "../../services/locations"; import { getSearchLocationService } from "../../services/locations";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { ReactSelectData } from "src/types/common";
function Header() { function Header() {
@ -40,7 +41,8 @@ function Header() {
e.preventDefault(); e.preventDefault();
} }
const onSelectedSearchOption = (val: any) => { const onSelectedSearchOption = (v: ReactSelectData | unknown) => {
const val = v as ReactSelectData
navigate(`/location/${val.value}`) navigate(`/location/${val.value}`)
} }
@ -149,7 +151,7 @@ function Header() {
// color: "white" // color: "white"
// }) // })
}} }}
onChange={onSelectedSearchOption} onChange={(v: ReactSelectData | unknown, _) => onSelectedSearchOption(v)}
/> />
</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 ${dropdown ? 'ml-auto' : ''}`} style={{ padding: 5, borderRadius: 10 }}>

0
src/components/Header/style.css Normal file → Executable file
View File

View File

@ -0,0 +1,24 @@
import { capitalize } from "../../../types/common"
interface ComponentProps {
value: string,
isSelected?: boolean
onChange: () => void,
inputName: string
}
const CheckboxInput = (props: ComponentProps) => (
<div style="align-self: stretch; justify-content: flex-start; align-items: center; gap: 12px; display: inline-flex">
<input checked={props.isSelected}
type={"checkbox"}
style={{ width: 16, height: 16 }}
className={"input-checkbox"}
value={props.value}
name={props.inputName}
onChange={props.onChange}
/>
<div style="color: white; font-size: 14px; line-height: 21px; letter-spacing: 0.56px; word-wrap: break-word">{capitalize(props.value)}</div>
</div>
)
export default CheckboxInput

0
src/components/Loading/Spinner/index.tsx Normal file → Executable file
View File

0
src/components/Loading/Spinner/style.css Normal file → Executable file
View File

0
src/components/RefOutsideClick/index.tsx Normal file → Executable file
View File

0
src/components/Separator/Default/index.tsx Normal file → Executable file
View File

0
src/components/Separator/NavigationSeparator/index.tsx Normal file → Executable file
View File

0
src/components/Separator/NavigationSeparator/style.css Normal file → Executable file
View File

0
src/components/Separator/TitleSeparator/index.tsx Normal file → Executable file
View File

0
src/components/Separator/WithAnchor/index.tsx Normal file → Executable file
View File

0
src/components/Separator/WithAnchor/style.css Normal file → Executable file
View File

12
src/components/index.ts Normal file → Executable file
View File

@ -15,6 +15,12 @@ import DropdownInput from "./DropdownInput";
import SpinnerLoading from "./Loading/Spinner"; import SpinnerLoading from "./Loading/Spinner";
import FilterButton from "./Button/FilterButton";
import LocationCard from "./Card/LocationCard";
import CheckboxInput from "./Input/CheckboxInput";
export { export {
Header, Header,
WarningButton, WarningButton,
@ -30,5 +36,11 @@ export {
CustomInterweave, CustomInterweave,
DropdownInput, DropdownInput,
CheckboxInput,
SpinnerLoading, SpinnerLoading,
LocationCard,
FilterButton,
} }

0
src/constants/actions.ts Normal file → Executable file
View File

0
src/constants/api.ts Normal file → Executable file
View File

0
src/constants/default.ts Normal file → Executable file
View File

0
src/datas/critics_users_best_pick.json Normal file → Executable file
View File

0
src/datas/home.json Normal file → Executable file
View File

0
src/datas/popular.json Normal file → Executable file
View File

0
src/datas/popular_user_reviews.json Normal file → Executable file
View File

0
src/datas/recent_news_event.json Normal file → Executable file
View File

0
src/domains/LocationInfo.ts Normal file → Executable file
View File

0
src/domains/NewsEvent.ts Normal file → Executable file
View File

0
src/domains/Province.ts Normal file → Executable file
View File

0
src/domains/Regency.ts Normal file → Executable file
View File

0
src/domains/Region.ts Normal file → Executable file
View File

0
src/domains/User.ts Normal file → Executable file
View File

0
src/domains/index.ts Normal file → Executable file
View File

0
src/features/auth/authSlice/authSlice.ts Normal file → Executable file
View File

0
src/features/index.ts Normal file → Executable file
View File

0
src/fonts/Lato-Black.ttf Normal file → Executable file
View File

0
src/fonts/Lato-BlackItalic.ttf Normal file → Executable file
View File

0
src/fonts/Lato-Bold.ttf Normal file → Executable file
View File

0
src/fonts/Lato-BoldItalic.ttf Normal file → Executable file
View File

0
src/fonts/Lato-Italic.ttf Normal file → Executable file
View File

0
src/fonts/Lato-Light.ttf Normal file → Executable file
View File

0
src/fonts/Lato-LightItalic.ttf Normal file → Executable file
View File

0
src/fonts/Lato-Regular.ttf Normal file → Executable file
View File

0
src/fonts/Lato-Thin.ttf Normal file → Executable file
View File

0
src/fonts/Lato-ThinItalic.ttf Normal file → Executable file
View File

0
src/index.css Normal file → Executable file
View File

0
src/lato.css Normal file → Executable file
View File

0
src/layouts/Default/Default.tsx Normal file → Executable file
View File

0
src/layouts/index.ts Normal file → Executable file
View File

0
src/main.tsx Normal file → Executable file
View File

0
src/pages/AddLocation/index.tsx Normal file → Executable file
View File

0
src/pages/AddLocation/style.css Normal file → Executable file
View File

0
src/pages/AddLocation/types.ts Normal file → Executable file
View File

7
src/pages/BestLocations/index.tsx Normal file → Executable file
View File

@ -2,8 +2,8 @@ import { TargetedEvent } from "preact/compat";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { getListTopLocationsService } from "../../services"; import { getListTopLocationsService } from "../../services";
import { DefaultSeparator } from "../../components"; import { DefaultSeparator } from "../../components";
import { NullValueRes } from "../../types/common";
import './style.css'; import './style.css';
import { useClick, useFloating, useInteractions } from "@floating-ui/react";
interface TopLocation { interface TopLocation {
row_number: Number, row_number: Number,
@ -47,7 +47,7 @@ const MIN_REVIEWS = [
] ]
function BestLocation() { function BestLocation() {
const [page, setPage] = useState<number>(1); const [page, _setPage] = useState<number>(1);
const [topLocations, setTopLocations] = useState<Array<TopLocation>>([]) const [topLocations, setTopLocations] = useState<Array<TopLocation>>([])
const [pageState, setPageState] = useState({ const [pageState, setPageState] = useState({
filterScoreType: 'all', filterScoreType: 'all',
@ -84,9 +84,7 @@ function BestLocation() {
getTopLocations() getTopLocations()
}, [pageState]) }, [pageState])
return ( return (
<>
<div className={'content main-content mt-3'}> <div className={'content main-content mt-3'}>
<section name={"Top locations header"}> <section name={"Top locations header"}>
<h1 className={'text-3xl mb-5 font-bold'}>Top Locations</h1> <h1 className={'text-3xl mb-5 font-bold'}>Top Locations</h1>
@ -198,7 +196,6 @@ function BestLocation() {
</div> </div>
</section> </section>
</div> </div>
</>
) )
} }

0
src/pages/BestLocations/style.css Normal file → Executable file
View File

302
src/pages/Discovery/index.tsx Normal file → Executable file
View File

@ -1,8 +1,304 @@
import { useEffect, useState } from "preact/hooks";
import { CheckboxInput, DefaultSeparator, FilterButton, LocationCard } from "../../components";
import { useClick, useFloating, useInteractions, Side, AlignedPlacement, flip, shift, useDismiss } from "@floating-ui/react";
import { JSXInternal } from "node_modules/preact/src/jsx";
import { getListRecentLocationsRatingsService, getRegenciesService, } from "../../services";
import { LocationInfo, Regency } from "../../domains";
import { ChangeEvent, TargetedEvent } from "preact/compat";
import { LocationType, capitalize } from "../../types/common";
import './style.css';
import { enumKeys } from "../../utils";
import { getSearchLocationService } from "../../services/locations";
interface floatFilterArgs<T> {
isOpen: boolean,
placement: Side | AlignedPlacement,
outsidePress?: () => void,
onChecked: (e: TargetedEvent<HTMLInputElement, Event>, id: number) => void,
onClearFilter?: () => void,
onInputChange: (val: string) => void,
listData?: T[];
}
function FloatFilter(props: floatFilterArgs<Regency & { isSelected?: boolean }>) {
const [floatState, setFloatState] = useState({
searchValue: "",
})
const { refs, floatingStyles, context } = useFloating({
placement: props.placement,
open: props.isOpen,
middleware: [
flip({ fallbackAxisSideDirection: "end" }),
shift()
]
});
const click = useClick(context);
const dismiss = useDismiss(context, {
outsidePress: () => {
props.outsidePress && props.outsidePress()
return false
}
});
const { getReferenceProps, getFloatingProps } = useInteractions([
click,
dismiss
]);
const style: JSXInternal.CSSProperties = {
...floatingStyles,
top: 210,
left: 'none',
marginLeft: 150,
width: 355,
zIndex: 1,
}
function onInputChange(e: ChangeEvent<HTMLInputElement>): void {
const input = e.target as HTMLInputElement;
setFloatState({ searchValue: input.value.toLowerCase() })
props.onInputChange(input.value)
}
if (props.isOpen) return (
<div
ref={refs.setFloating} {...getReferenceProps()}
style={style}
className={'bg-secondary float-filter-scroll'}
{...getFloatingProps()}
>
<div style={{ padding: 10 }}>
<input
type="text"
value={floatState.searchValue}
onChange={onInputChange}
placeholder={"Search ..."}
className={'bg-quartenary text-sm input-text mb-3'}
/>
</div>
<div style={{ overflowY: 'scroll', height: 315, padding: 10 }} className={'float-filter-scroll'}>
{props.listData ?
<ul>
{props.listData.map(x => (
<div style={{ display: 'flex', alignItems: 'center', marginBottom: 5 }}>
<input
type={"checkbox"}
id={x.id.toString()}
checked={x.isSelected}
value={x.id}
name={x.regency_name}
className={'input-checkbox'}
style={{ marginRight: 10, width: 18, height: 18 }}
onChange={(e) => props.onChecked(e, x.id)}
/>
<label htmlFor={x.id.toString()}>{x.regency_name}</label>
</div>
))}
</ul>
: null}
</div>
<div style={{ display: 'flex', justifyContent: 'space-around', paddingBottom: 10, paddingTop: 10 }}>
<button onClick={props.onClearFilter}>Clear</button>
<button onClick={props.outsidePress} >Close</button>
</div>
</div>
)
return null
}
function Discovery() { function Discovery() {
interface DiscoveryState {
filterQ: string,
regencies: any[],
searchRegencies: any[],
locations: LocationInfo[],
locationType: Array<{value: string, isSelected?: boolean}>
}
const [data, setData] = useState<DiscoveryState>({
filterQ: '',
regencies: [],
searchRegencies: [],
locations: [],
locationType: []
});
const [isFloatFilterOpen, setFloatFilterOpen] = useState(false)
useEffect(() => {
getLocationType()
getRecentLocations()
getRegion()
}, [])
async function getRegion() {
try {
const res = await getRegenciesService();
setData((prevState) => ({ ...prevState, regencies: res.data, searchRegencies: res.data }))
} catch (err) {
alert(err)
}
}
async function getRecentLocations() {
try {
const locations = await getListRecentLocationsRatingsService(15)
setData((prevState) => ({ ...prevState, locations: locations.data.locations }))
} catch (error) {
console.log(error)
}
}
function onCheckedRegencyFilter(_val: any, id: number) {
// const value = val as HTMLInputElement;
const dataRegencies = data.searchRegencies as (Regency & { isSelected?: boolean })[];
const regencyIdx = dataRegencies.findIndex(x => x.id == id);
dataRegencies[regencyIdx].isSelected = !dataRegencies[regencyIdx].isSelected
setData({
...data,
regencies: dataRegencies,
searchRegencies: dataRegencies
})
}
function onClearFilter() {
const dataRegencies = data.searchRegencies as (Regency & { isSelected?: boolean })[];
const regencies = dataRegencies.map(x => ({ ...x, isSelected: false }))
setData({
...data,
regencies: regencies,
searchRegencies: regencies
})
}
function onInputChange(search: string) {
const dataRegencies = data.regencies as (Regency & { isSelected?: boolean })[];
// console.log(dataRegencies.filter(x => x.regency_name.toLowerCase().includes(search)))
setData({
...data,
searchRegencies: dataRegencies.filter(x => x.regency_name.toLowerCase().includes(search))
})
}
function onClickedTypeLocations(idx: number) {
const locType = data.locationType
locType[idx].isSelected = !locType[idx].isSelected
setData({
...data,
locationType: locType
})
}
function onApplyFilter() {
const dataRegencies = data.regencies as (Regency & { isSelected?: boolean})[];
const selectedRegencies = dataRegencies.filter(x => x.isSelected)
const selectedLocType = data.locationType.filter(x => x.isSelected)
let regenciesQ = "";
let locTypeQ = ""
if(selectedRegencies.length > 0) {
regenciesQ = selectedRegencies.map(x => x.id).join(" OR ").replace(/\s+/g, '%20')
}
if(selectedLocType.length > 0) {
const onJoin = selectedRegencies.length > 0 ? " AND " : " OR "
locTypeQ = selectedLocType.map(x => x.value).join(onJoin).replace(/\s+/g, '%20')
if(selectedRegencies.length > 0) locTypeQ = `%20AND%20${locTypeQ}`
}
const searchQ = `${regenciesQ}${locTypeQ}`
}
function getLocationType() {
const type: Array<{value: string, isSelected?: boolean}> = []
for (const lt of enumKeys(LocationType)) {
type.push({value: LocationType[lt]});
}
setData((prevState) => ({ ...prevState, locationType: type }))
}
return ( return (
<> <div className="content main-content mt-3">
<h1>Discovery</h1> <section name="header">
</> <h1 className={'text-3xl mb-5 font-bold'}>Discovery</h1>
<DefaultSeparator />
</section>
<section name="content">
<div style="padding-bottom: 24px; justify-content: flex-start; align-items: flex-start; display: inline-flex">
{/* FILTER */}
<section name="discovery filter">
<div className="filter-container">
<div style="align-self: stretch; padding-left: 12px; padding-right: 12px; padding-top: 8px; padding-bottom: 8px; background: #36383B; border-radius: 8px; justify-content: flex-start; align-items: center; gap: 10px; display: inline-flex">
<div style="flex: 1 1 0; color: white; font-size: 16px; line-height: 24px; letter-spacing: 0.64px; word-wrap: break-word">Filter</div>
</div>
<div style="align-self: stretch; padding-left: 12px; padding-right: 12px; flex-direction: column; justify-content: flex-start; align-items: flex-start; gap: 16px; display: flex">
<div style="align-self: stretch; color: white; font-size: 14px; font-weight: 700; line-height: 21px; letter-spacing: 0.56px; word-wrap: break-word">Kota / Kabupaten </div>
<div style="align-self: stretch; flex-direction: column; justify-content: flex-start; align-items: flex-start; gap: 12px; display: flex">
{data.regencies.filter(x => x.isSelected).map(x => (
<div style="align-self: stretch; justify-content: flex-start; align-items: center; gap: 12px; display: inline-flex">
<div style="flex: 1 1 0; color: white; font-size: 14px; font-family: Poppins; font-weight: 500; line-height: 21px; letter-spacing: 0.56px; word-wrap: break-word">{x.regency_name}</div>
</div>
))}
</div>
<div style="align-self: stretch; padding: 8px; border-radius: 6px; justify-content: center; align-items: center; gap: 4px; display: inline-flex">
<button onClick={() => setFloatFilterOpen(!isFloatFilterOpen)} style="text-align: center; color: #85CE73; font-size: 14px; font-weight: 600; line-height: 21px; letter-spacing: 0.10px; word-wrap: break-word">Lihat Selengkapnya</button>
<FloatFilter
isOpen={isFloatFilterOpen}
placement="right"
listData={data.searchRegencies}
outsidePress={() => setFloatFilterOpen(false)}
onClearFilter={onClearFilter}
onChecked={onCheckedRegencyFilter}
onInputChange={onInputChange}
/>
</div>
</div>
<div style="align-self: stretch; height: 2px; background: #36383B; border-radius: 2px"></div>
<div style="align-self: stretch; height: 190px; padding-left: 12px; padding-right: 12px; flex-direction: column; justify-content: flex-start; align-items: flex-start; gap: 16px; display: flex">
<div style="align-self: stretch; color: white; font-size: 14px; font-family: Poppins; font-weight: 700; line-height: 21px; letter-spacing: 0.56px; word-wrap: break-word">Tipe</div>
<div style="align-self: stretch; height: 153px; flex-direction: column; justify-content: flex-start; align-items: flex-start; gap: 12px; display: flex">
{data.locationType.map((x, i) => (
<CheckboxInput
inputName="location_type"
onChange={() => onClickedTypeLocations(i)}
value={x.value}
isSelected={x.isSelected}
/>
))}
</div>
</div>
<div style="align-self: stretch; height: 2px; background: #36383B; border-radius: 2px"></div>
<FilterButton onClick={onApplyFilter} label="Apply" />
</div>
</section>
{/* FILTER END */}
<div>
{data.locations.map(x => (
<LocationCard
containerClass='card-list-container'
onCardClick={(id) => console.log(id)}
data={x}
/>
))}
</div>
</div>
</section>
</div>
) )
} }

112
src/pages/Discovery/style.css Executable file
View File

@ -0,0 +1,112 @@
.float-filter-scroll::-webkit-scrollbar-track {
background: #e0e0e0;
}
/* Customizing the scrollbar handle */
.float-filter-scroll::-webkit-scrollbar-thumb {
background: #888;
}
.float-filter-scroll::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Customizing the scrollbar width */
.float-filter-scroll::-webkit-scrollbar {
width: 12px;
}
/* For Firefox */
.float-filter-scroll {
scrollbar-width: thin;
scrollbar-color: #a8adb3 #2f3136;
}
.card-list-container {
padding: 10px 1% 15px;
display: inline-block;
margin: 0 0 15px;
vertical-align: top;
width: 20%;
}
.filter-container {
width: 195px;
flex-direction: column;
justify-content:
flex-start;
align-items: flex-start;
gap: 24px;
display: inline-flex;
margin-right: 20px;
}
@media screen and (max-width: 1300px) {
.card-list-container {
width: 24.3%;
}
}
@media screen and (max-width: 1135px) {
.card-list-container {
width: 25%;
}
}
@media screen and (max-width: 1100px) {
.card-list-container {
width: 33%;
}
}
@media screen and (max-width: 920px) {
.card-list-container {
width: 33%;
}
.filter-container {
display: none;
}
}
@media screen and (max-width: 625px) {
.card-list-container {
width: 50%;
}
}
/*
@media screen and (max-width: 425px) {
.news-card {
width: 100%;
}
.news-link {
font-size: 0.75rem;
line-height: 1rem;
}
.recently-img-container {
height: 120px;
width: 100%;
}
.critics-users-image {
height: 60px;
width: 60px;
}
.location-province {
font-size: 10px !important;
}
.trending-image-container {
overflow: hidden;
border-radius: 7px;
height: 100px;
}
.top-location-container .location-title {
font-size: 0.685rem;
}
} */

77
src/pages/Home/index.tsx Normal file → Executable file
View File

@ -1,4 +1,4 @@
import { SeparatorWithAnchor } from '../../components'; import { LocationCard, SeparatorWithAnchor } from '../../components';
import news from '../../datas/recent_news_event.json'; import news from '../../datas/recent_news_event.json';
import popular from '../../datas/popular.json'; import popular from '../../datas/popular.json';
import popular_user_review from '../../datas/popular_user_reviews.json'; import popular_user_review from '../../datas/popular_user_reviews.json';
@ -28,7 +28,7 @@ function Home() {
async function getRecentLocations() { async function getRecentLocations() {
try { try {
const locations = await getListRecentLocationsRatingsService(12) const locations = await getListRecentLocationsRatingsService(12)
setRecentLocations(locations.data) setRecentLocations(locations.data.locations)
// setIsLoading(false) // setIsLoading(false)
} catch (error) { } catch (error) {
console.log(error) console.log(error)
@ -63,46 +63,50 @@ function Home() {
getUsersBestLocations() getUsersBestLocations()
}, []) }, [])
return ( return (
<>
<div className="content main-content mt-3"> <div className="content main-content mt-3">
{/* RECENTLY ADDED SECTION */} {/* RECENTLY ADDED SECTION */}
<section about={"Recently added places"} className={'mt-3'}> <section about={"Recently added places"} className={'mt-3'}>
<SeparatorWithAnchor pageLink='#' pageName='recently added' secondLink='#' /> <SeparatorWithAnchor pageLink='#' pageName='recently added' secondLink='#' />
{recentLocations.map((x) => ( {recentLocations.map((x) => (
<div className={"recently-added-section-card"}> <LocationCard
<a onClick={() => onNavigateToDetail(x.id)}> containerClass='recently-added-section-card'
<div className={'border-secondary recently-img-container'}> onCardClick={onNavigateToDetail}
<img alt={x.name} src={x.thumbnail ? x.thumbnail : ''} loading="lazy" style={{ width: '100%', height: '100%' }} /> data={x}
</div> />
</a> // <div className={"recently-added-section-card"}>
<div className={"border-primary pb-2 location-container text-sm mb-2 mt-2"}> // <a onClick={() => onNavigateToDetail(x.id)}>
<p className={'location-title'}>{x.name}</p> // <div className={'border-secondary recently-img-container'}>
<p className={'text-xs mt-1'}>{x.regency_name}, {x.province_name}</p> // <img alt={x.name} src={x.thumbnail ? x.thumbnail : ''} loading="lazy" style={{ width: '100%', height: '100%' }} />
</div> // </div>
{ x.critic_count !== 0 && // </a>
<div className={"flex flex-row items-center mb-3"}> // <div className={"border-primary pb-2 location-container text-sm mb-2 mt-2"}>
<div className={'mr-3 users-score-bar'}> // <p className={'location-title'}>{x.name}</p>
<p className={'text-sm text-center'}>{x.critic_score}</p> // <p className={'text-xs mt-1'}>{x.regency_name}, {x.province_name}</p>
<div style={{ height: 4, width: 30, backgroundColor: "#72767d"}}> // </div>
<div style={{ height: 4, width: `${x.critic_score}%`, backgroundColor: 'green' }} /> // {x.critic_count !== 0 &&
</div> // <div className={"flex flex-row items-center mb-3"}>
</div> // <div className={'mr-3 users-score-bar'}>
<p className={"users-score"}>critic score ({x.critic_count})</p> // <p className={'text-sm text-center'}>{x.critic_score}</p>
</div> // <div style={{ height: 4, width: 30, backgroundColor: "#72767d" }}>
} // <div style={{ height: 4, width: `${x.critic_score}%`, backgroundColor: 'green' }} />
{ x.user_score !== 0 && // </div>
<div className={"flex flex-row items-center"}> // </div>
<div className={'mr-3 users-score-bar'}> // <p className={"users-score"}>critic score ({x.critic_count})</p>
<p className={'text-sm text-center'}>{x.user_score}</p> // </div>
<div style={{ height: 4, width: 30, backgroundColor: "#72767d" }}> // }
<div style={{ height: 4, width: ` ${x.user_score}%`, backgroundColor: 'green' }} /> // {x.user_score !== 0 &&
</div> // <div className={"flex flex-row items-center"}>
</div> // <div className={'mr-3 users-score-bar'}>
<p className={'users-score'}>user score ({x.user_count})</p> // <p className={'text-sm text-center'}>{x.user_score}</p>
</div> // <div style={{ height: 4, width: 30, backgroundColor: "#72767d" }}>
} // <div style={{ height: 4, width: ` ${x.user_score}%`, backgroundColor: 'green' }} />
</div> // </div>
// </div>
// <p className={'users-score'}>user score ({x.user_count})</p>
// </div>
// }
// </div>
))} ))}
</section> </section>
{/* END RECENTLY ADDED SECTION */} {/* END RECENTLY ADDED SECTION */}
@ -235,7 +239,6 @@ function Home() {
</div> </div>
</section> </section>
</div> </div>
</>
) )
} }

0
src/pages/Home/style.css Normal file → Executable file
View File

0
src/pages/LocationDetail/index.css Normal file → Executable file
View File

106
src/pages/LocationDetail/index.tsx Normal file → Executable file
View File

@ -193,13 +193,12 @@ function LocationDetail() {
}, []) }, [])
useEffect(() => { useEffect(() => {
if(updatePage) { if (updatePage || id) {
getLocationDetail() getLocationDetail()
} }
}, [updatePage, id]) }, [updatePage, id])
return ( return (
<>
<div className={'content main-content mt-3'}> <div className={'content main-content mt-3'}>
<section name={"HEADER LINK"}> <section name={"HEADER LINK"}>
<div className={'header-link text-tertiary'}> <div className={'header-link text-tertiary'}>
@ -470,10 +469,7 @@ function LocationDetail() {
</> </>
: :
<>
<span className={'text-sm italic'}>No Critics review to display</span> <span className={'text-sm italic'}>No Critics review to display</span>
</>
} }
</div> </div>
@ -540,111 +536,12 @@ function LocationDetail() {
} }
</div> </div>
{/* <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="#">
<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={''} 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>
</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 &&
<div className={'bg-secondary'} style={{ display: 'table-cell', position: 'relative', verticalAlign: 'top', width: 330, textAlign: 'left', padding: 15, boxSizing: 'border-box', height: 1080 }}>
// ADD USER DISTRIBUTION SOMETHING LIKE THIS
// https://www.w3schools.com/howto/tryit.asp?filename=tryhow_css_user_rating
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 style={{ clear: 'both' }} /> <div style={{ clear: 'both' }} />
</section> </section>
@ -660,7 +557,6 @@ function LocationDetail() {
slides={locationImages?.images} slides={locationImages?.images}
/> />
</div> </div>
</>
) )
} }

0
src/pages/LocationDetail/types.ts Normal file → Executable file
View File

0
src/pages/Login/index.css Normal file → Executable file
View File

0
src/pages/Login/index.tsx Normal file → Executable file
View File

0
src/pages/NewsEvent/index.tsx Normal file → Executable file
View File

0
src/pages/NewsEvent/style.css Normal file → Executable file
View File

0
src/pages/NotFound/index.tsx Normal file → Executable file
View File

2
src/pages/Stories/index.tsx Normal file → Executable file
View File

@ -1,8 +1,6 @@
function Story() { function Story() {
return( return(
<>
<h1>Best PLaces</h1> <h1>Best PLaces</h1>
</>
) )
} }

0
src/pages/Submissions/index.tsx Normal file → Executable file
View File

0
src/pages/UserFeed/index.tsx Normal file → Executable file
View File

2
src/pages/UserProfile/index.tsx Normal file → Executable file
View File

@ -188,9 +188,7 @@ function UserProfile() {
))} ))}
</> </>
: :
<>
<span className={'text-sm italic'}>No users review to display</span> <span className={'text-sm italic'}>No users review to display</span>
</>
} }
</div> </div>
</div> </div>

0
src/pages/UserProfile/style.css Normal file → Executable file
View File

0
src/pages/UserSettings/index.tsx Normal file → Executable file
View File

0
src/pages/UserSettings/styles.css Normal file → Executable file
View File

0
src/pages/index.tsx Normal file → Executable file
View File

0
src/reducers/index.ts Normal file → Executable file
View File

0
src/routes/ProtectedRoute.tsx Normal file → Executable file
View File

0
src/routes/index.tsx Normal file → Executable file
View File

0
src/services/auth.ts Normal file → Executable file
View File

0
src/services/config.ts Normal file → Executable file
View File

Some files were not shown because too many files have changed in this diff Show More