This commit is contained in:
nochill 2025-04-11 06:46:12 +07:00
parent f1fd9bcbb9
commit 8a5281a0f2
17 changed files with 356 additions and 116 deletions

View File

@ -12,6 +12,7 @@
"@floating-ui/react": "^0.26.9", "@floating-ui/react": "^0.26.9",
"@reduxjs/toolkit": "^1.9.5", "@reduxjs/toolkit": "^1.9.5",
"axios": "^1.5.0", "axios": "^1.5.0",
"class-variance-authority": "^0.7.0",
"emojibase": "^15.0.0", "emojibase": "^15.0.0",
"interweave": "^13.1.0", "interweave": "^13.1.0",
"interweave-autolink": "^5.1.0", "interweave-autolink": "^5.1.0",
@ -27,6 +28,7 @@
}, },
"devDependencies": { "devDependencies": {
"@preact/preset-vite": "^2.5.0", "@preact/preset-vite": "^2.5.0",
"@types/react-redux": "^7.1.34",
"autoprefixer": "^10.4.15", "autoprefixer": "^10.4.15",
"postcss": "^8.4.28", "postcss": "^8.4.28",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",

View File

@ -10,13 +10,16 @@ importers:
dependencies: dependencies:
'@floating-ui/react': '@floating-ui/react':
specifier: ^0.26.9 specifier: ^0.26.9
version: 0.26.9(react-dom@18.2.0)(react@18.2.0) version: 0.26.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@reduxjs/toolkit': '@reduxjs/toolkit':
specifier: ^1.9.5 specifier: ^1.9.5
version: 1.9.7(react-redux@8.1.3)(react@18.2.0) version: 1.9.7(react-redux@8.1.3(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(redux@4.2.1))(react@18.2.0)
axios: axios:
specifier: ^1.5.0 specifier: ^1.5.0
version: 1.6.7 version: 1.6.7
class-variance-authority:
specifier: ^0.7.0
version: 0.7.0
emojibase: emojibase:
specifier: ^15.0.0 specifier: ^15.0.0
version: 15.3.0 version: 15.3.0
@ -25,25 +28,25 @@ importers:
version: 13.1.0(react@18.2.0) version: 13.1.0(react@18.2.0)
interweave-autolink: interweave-autolink:
specifier: ^5.1.0 specifier: ^5.1.0
version: 5.1.1(interweave@13.1.0)(react@18.2.0) version: 5.1.1(interweave@13.1.0(react@18.2.0))(react@18.2.0)
interweave-emoji: interweave-emoji:
specifier: ^7.0.0 specifier: ^7.0.0
version: 7.0.0(interweave@13.1.0)(react@18.2.0) version: 7.0.0(interweave@13.1.0(react@18.2.0))(react@18.2.0)
preact: preact:
specifier: ^10.16.0 specifier: ^10.16.0
version: 10.19.3 version: 10.19.3
react-redux: react-redux:
specifier: ^8.1.2 specifier: ^8.1.2
version: 8.1.3(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) version: 8.1.3(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(redux@4.2.1)
react-router-dom: react-router-dom:
specifier: ^6.16.0 specifier: ^6.16.0
version: 6.21.3(react-dom@18.2.0)(react@18.2.0) version: 6.21.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react-select: react-select:
specifier: ^5.8.0 specifier: ^5.8.0
version: 5.8.0(react-dom@18.2.0)(react@18.2.0) version: 5.8.0(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react-textarea-autosize: react-textarea-autosize:
specifier: ^8.5.3 specifier: ^8.5.3
version: 8.5.3(react@18.2.0) version: 8.5.3(@types/react@18.2.48)(react@18.2.0)
redux-persist: redux-persist:
specifier: ^6.0.0 specifier: ^6.0.0
version: 6.0.0(react@18.2.0)(redux@4.2.1) version: 6.0.0(react@18.2.0)(redux@4.2.1)
@ -52,11 +55,14 @@ importers:
version: 2.4.2(redux@4.2.1) version: 2.4.2(redux@4.2.1)
yet-another-react-lightbox: yet-another-react-lightbox:
specifier: ^3.12.2 specifier: ^3.12.2
version: 3.16.0(react-dom@18.2.0)(react@18.2.0) version: 3.16.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
devDependencies: devDependencies:
'@preact/preset-vite': '@preact/preset-vite':
specifier: ^2.5.0 specifier: ^2.5.0
version: 2.8.1(@babel/core@7.23.9)(preact@10.19.3)(vite@4.5.2) version: 2.8.1(@babel/core@7.23.9)(preact@10.19.3)(vite@4.5.2)
'@types/react-redux':
specifier: ^7.1.34
version: 7.1.34
autoprefixer: autoprefixer:
specifier: ^10.4.15 specifier: ^10.4.15
version: 10.4.17(postcss@8.4.33) version: 10.4.17(postcss@8.4.33)
@ -486,6 +492,9 @@ packages:
'@types/prop-types@15.7.11': '@types/prop-types@15.7.11':
resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==} resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
'@types/react-redux@7.1.34':
resolution: {integrity: sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==}
'@types/react-transition-group@4.4.10': '@types/react-transition-group@4.4.10':
resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==} resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==}
@ -591,6 +600,13 @@ packages:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'} engines: {node: '>= 8.10.0'}
class-variance-authority@0.7.0:
resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==}
clsx@2.0.0:
resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==}
engines: {node: '>=6'}
color-convert@1.9.3: color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
@ -1557,7 +1573,7 @@ snapshots:
'@emotion/memoize@0.8.1': {} '@emotion/memoize@0.8.1': {}
'@emotion/react@11.11.3(react@18.2.0)': '@emotion/react@11.11.3(@types/react@18.2.48)(react@18.2.0)':
dependencies: dependencies:
'@babel/runtime': 7.23.9 '@babel/runtime': 7.23.9
'@emotion/babel-plugin': 11.11.0 '@emotion/babel-plugin': 11.11.0
@ -1568,6 +1584,8 @@ snapshots:
'@emotion/weak-memoize': 0.3.1 '@emotion/weak-memoize': 0.3.1
hoist-non-react-statics: 3.3.2 hoist-non-react-statics: 3.3.2
react: 18.2.0 react: 18.2.0
optionalDependencies:
'@types/react': 18.2.48
'@emotion/serialize@1.1.3': '@emotion/serialize@1.1.3':
dependencies: dependencies:
@ -1669,15 +1687,15 @@ snapshots:
'@floating-ui/core': 1.6.0 '@floating-ui/core': 1.6.0
'@floating-ui/utils': 0.2.1 '@floating-ui/utils': 0.2.1
'@floating-ui/react-dom@2.0.8(react-dom@18.2.0)(react@18.2.0)': '@floating-ui/react-dom@2.0.8(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies: dependencies:
'@floating-ui/dom': 1.6.3 '@floating-ui/dom': 1.6.3
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
'@floating-ui/react@0.26.9(react-dom@18.2.0)(react@18.2.0)': '@floating-ui/react@0.26.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies: dependencies:
'@floating-ui/react-dom': 2.0.8(react-dom@18.2.0)(react@18.2.0) '@floating-ui/react-dom': 2.0.8(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@floating-ui/utils': 0.2.1 '@floating-ui/utils': 0.2.1
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
@ -1764,14 +1782,15 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@reduxjs/toolkit@1.9.7(react-redux@8.1.3)(react@18.2.0)': '@reduxjs/toolkit@1.9.7(react-redux@8.1.3(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(redux@4.2.1))(react@18.2.0)':
dependencies: dependencies:
immer: 9.0.21 immer: 9.0.21
react: 18.2.0
react-redux: 8.1.3(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1)
redux: 4.2.1 redux: 4.2.1
redux-thunk: 2.4.2(redux@4.2.1) redux-thunk: 2.4.2(redux@4.2.1)
reselect: 4.1.8 reselect: 4.1.8
optionalDependencies:
react: 18.2.0
react-redux: 8.1.3(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(redux@4.2.1)
'@remix-run/router@1.14.2': {} '@remix-run/router@1.14.2': {}
@ -1789,6 +1808,13 @@ snapshots:
'@types/prop-types@15.7.11': {} '@types/prop-types@15.7.11': {}
'@types/react-redux@7.1.34':
dependencies:
'@types/hoist-non-react-statics': 3.3.5
'@types/react': 18.2.48
hoist-non-react-statics: 3.3.2
redux: 4.2.1
'@types/react-transition-group@4.4.10': '@types/react-transition-group@4.4.10':
dependencies: dependencies:
'@types/react': 18.2.48 '@types/react': 18.2.48
@ -1901,6 +1927,12 @@ snapshots:
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
class-variance-authority@0.7.0:
dependencies:
clsx: 2.0.0
clsx@2.0.0: {}
color-convert@1.9.3: color-convert@1.9.3:
dependencies: dependencies:
color-name: 1.1.3 color-name: 1.1.3
@ -2116,12 +2148,12 @@ snapshots:
parent-module: 1.0.1 parent-module: 1.0.1
resolve-from: 4.0.0 resolve-from: 4.0.0
interweave-autolink@5.1.1(interweave@13.1.0)(react@18.2.0): interweave-autolink@5.1.1(interweave@13.1.0(react@18.2.0))(react@18.2.0):
dependencies: dependencies:
interweave: 13.1.0(react@18.2.0) interweave: 13.1.0(react@18.2.0)
react: 18.2.0 react: 18.2.0
interweave-emoji@7.0.0(interweave@13.1.0)(react@18.2.0): interweave-emoji@7.0.0(interweave@13.1.0(react@18.2.0))(react@18.2.0):
dependencies: dependencies:
emojibase: 6.1.0 emojibase: 6.1.0
emojibase-regex: 6.0.1 emojibase-regex: 6.0.1
@ -2288,8 +2320,9 @@ snapshots:
postcss-load-config@4.0.2(postcss@8.4.33): postcss-load-config@4.0.2(postcss@8.4.33):
dependencies: dependencies:
lilconfig: 3.0.0 lilconfig: 3.0.0
postcss: 8.4.33
yaml: 2.3.4 yaml: 2.3.4
optionalDependencies:
postcss: 8.4.33
postcss-nested@6.0.1(postcss@8.4.33): postcss-nested@6.0.1(postcss@8.4.33):
dependencies: dependencies:
@ -2331,19 +2364,21 @@ snapshots:
react-is@18.2.0: {} react-is@18.2.0: {}
react-redux@8.1.3(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1): react-redux@8.1.3(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(redux@4.2.1):
dependencies: dependencies:
'@babel/runtime': 7.23.9 '@babel/runtime': 7.23.9
'@types/hoist-non-react-statics': 3.3.5 '@types/hoist-non-react-statics': 3.3.5
'@types/use-sync-external-store': 0.0.3 '@types/use-sync-external-store': 0.0.3
hoist-non-react-statics: 3.3.2 hoist-non-react-statics: 3.3.2
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-is: 18.2.0 react-is: 18.2.0
redux: 4.2.1
use-sync-external-store: 1.2.0(react@18.2.0) use-sync-external-store: 1.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.48
react-dom: 18.2.0(react@18.2.0)
redux: 4.2.1
react-router-dom@6.21.3(react-dom@18.2.0)(react@18.2.0): react-router-dom@6.21.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies: dependencies:
'@remix-run/router': 1.14.2 '@remix-run/router': 1.14.2
react: 18.2.0 react: 18.2.0
@ -2355,32 +2390,32 @@ snapshots:
'@remix-run/router': 1.14.2 '@remix-run/router': 1.14.2
react: 18.2.0 react: 18.2.0
react-select@5.8.0(react-dom@18.2.0)(react@18.2.0): react-select@5.8.0(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies: dependencies:
'@babel/runtime': 7.23.9 '@babel/runtime': 7.23.9
'@emotion/cache': 11.11.0 '@emotion/cache': 11.11.0
'@emotion/react': 11.11.3(react@18.2.0) '@emotion/react': 11.11.3(@types/react@18.2.48)(react@18.2.0)
'@floating-ui/dom': 1.6.0 '@floating-ui/dom': 1.6.0
'@types/react-transition-group': 4.4.10 '@types/react-transition-group': 4.4.10
memoize-one: 6.0.0 memoize-one: 6.0.0
prop-types: 15.8.1 prop-types: 15.8.1
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) react-transition-group: 4.4.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
use-isomorphic-layout-effect: 1.1.2(react@18.2.0) use-isomorphic-layout-effect: 1.1.2(@types/react@18.2.48)(react@18.2.0)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/react' - '@types/react'
react-textarea-autosize@8.5.3(react@18.2.0): react-textarea-autosize@8.5.3(@types/react@18.2.48)(react@18.2.0):
dependencies: dependencies:
'@babel/runtime': 7.23.9 '@babel/runtime': 7.23.9
react: 18.2.0 react: 18.2.0
use-composed-ref: 1.3.0(react@18.2.0) use-composed-ref: 1.3.0(react@18.2.0)
use-latest: 1.2.1(react@18.2.0) use-latest: 1.2.1(@types/react@18.2.48)(react@18.2.0)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/react' - '@types/react'
react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): react-transition-group@4.4.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies: dependencies:
'@babel/runtime': 7.23.9 '@babel/runtime': 7.23.9
dom-helpers: 5.2.1 dom-helpers: 5.2.1
@ -2403,8 +2438,9 @@ snapshots:
redux-persist@6.0.0(react@18.2.0)(redux@4.2.1): redux-persist@6.0.0(react@18.2.0)(redux@4.2.1):
dependencies: dependencies:
react: 18.2.0
redux: 4.2.1 redux: 4.2.1
optionalDependencies:
react: 18.2.0
redux-thunk@2.4.2(redux@4.2.1): redux-thunk@2.4.2(redux@4.2.1):
dependencies: dependencies:
@ -2549,14 +2585,18 @@ snapshots:
dependencies: dependencies:
react: 18.2.0 react: 18.2.0
use-isomorphic-layout-effect@1.1.2(react@18.2.0): use-isomorphic-layout-effect@1.1.2(@types/react@18.2.48)(react@18.2.0):
dependencies: dependencies:
react: 18.2.0 react: 18.2.0
optionalDependencies:
'@types/react': 18.2.48
use-latest@1.2.1(react@18.2.0): use-latest@1.2.1(@types/react@18.2.48)(react@18.2.0):
dependencies: dependencies:
react: 18.2.0 react: 18.2.0
use-isomorphic-layout-effect: 1.1.2(react@18.2.0) use-isomorphic-layout-effect: 1.1.2(@types/react@18.2.48)(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.48
use-sync-external-store@1.2.0(react@18.2.0): use-sync-external-store@1.2.0(react@18.2.0):
dependencies: dependencies:
@ -2594,7 +2634,7 @@ snapshots:
yaml@2.3.4: {} yaml@2.3.4: {}
yet-another-react-lightbox@3.16.0(react-dom@18.2.0)(react@18.2.0): yet-another-react-lightbox@3.16.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies: dependencies:
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)

View File

@ -2,7 +2,7 @@ import { JSXInternal } from "node_modules/preact/src/jsx";
import { LocationInfo } from "../../../domains"; import { LocationInfo } from "../../../domains";
interface ComponentProps { interface ComponentProps {
onCardClick: (id: Number) => void, onCardClick: (id: number) => void,
data: LocationInfo, data: LocationInfo,
containerClass?: string, containerClass?: string,
containerStyle?: JSXInternal.CSSProperties containerStyle?: JSXInternal.CSSProperties

View File

@ -1,14 +1,14 @@
import React from "preact/compat"; import React from "preact/compat";
import { useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { useSelector, useDispatch } from "react-redux";
import { UserRootState } from "../../store/type"; import { UserRootState } from "../../store/type";
import { logout } from '../../actions'; import { logout } from '../../actions';
import AsyncSelect from 'react-select/async'; import AsyncSelect from 'react-select/async';
import './style.css'; 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 { Link, useNavigate } from "react-router-dom";
import { ReactSelectData } from "src/types/common"; import { ReactSelectData } from "src/types/common";
import { useDispatch, useSelector } from "react-redux";
function Header() { function Header() {
@ -17,16 +17,37 @@ function Header() {
const [pageState, setPageState] = useState({ const [pageState, setPageState] = useState({
profileMenu: false profileMenu: false
}) })
const [searchResult, setSearchResult] = useState([])
const dispatch = useDispatch(); const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const user = useSelector((state: UserRootState) => state.auth) const user = useSelector((state: UserRootState) => state.auth)
const onInput = (val: string): void => { const onInput = async (val: any) => {
// const val = e.target as HTMLInputElement;
setSearchVal(val.toLowerCase()) setSearchVal(val.toLowerCase())
} }
useEffect(() => {
try {
setTimeout(async () => {
const results = await getSearchLocationService({
name: searchVal,
page: 1,
page_size: 7
})
const resultData = results.data.map((x: any) => {
return {
value: x.id,
label: x.name
}
})
setSearchResult(resultData)
}, 900)
} catch (err) {
alert(err)
}
}, [searchVal])
const handleLogout = async (): Promise<void> => { const handleLogout = async (): Promise<void> => {
try { try {
await logoutService() await logoutService()
@ -59,7 +80,13 @@ function Header() {
label: x.name label: x.name
} }
}) })
return resultData const firstObj = {
value: 0,
label: `search: ${inputValue}`
}
const result = [firstObj, ...resultData]
setSearchResult(resultData)
return result
} catch (err) { } catch (err) {
alert(err) alert(err)
} }
@ -75,39 +102,41 @@ function Header() {
setDropdown(!dropdown) setDropdown(!dropdown)
} }
return ( return (
<header> <header>
<div className="flex flex-row content"> <div className="flex flex-row content">
<a href={"/"}> <Link to={"/"}>
<h1 className={`title ${dropdown ? 'title-dropdown' : ""}`}>Hilingin</h1> <h1 className={`title ${dropdown ? 'title-dropdown' : ""}`}>Hilingin</h1>
</a> </Link>
<div className={'user-img self-center mr-5'} style={dropdown ? { display: 'none' } : ''}> <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 }) : ''}> <Link to={user.username ? '#' : '/login'} onClick={() => user.username ? setPageState({ ...pageState, profileMenu: !pageState.profileMenu }) : ''}>
<img <img
loading={'lazy'} loading={'lazy'}
style={{ width: 40, borderRadius: 15 }} style={{ width: 40, borderRadius: 15 }}
src={'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> </Link>
{user.username && {user.username &&
<div className={'profile-dropdown-img bg-secondary text-left'} style={pageState.profileMenu ? { display: 'block' } : { display: 'none' }}> <div className={'profile-dropdown-img bg-secondary text-left'} style={pageState.profileMenu ? { display: 'block' } : { display: 'none' }}>
<a href={'/user/profile'}><div className={'p-2'}>Profile</div></a> <Link to={'/user/profile'}><div className={'p-2'}>Profile</div></Link>
<a href={'#'}><div className={'p-2'}>Feed</div></a> <Link to={'#'}><div className={'p-2'}>Feed</div></Link>
<a href={'/add-location'}><div className={'p-2'}>Add location</div></a> <Link to={'/add-location'}><div className={'p-2'}>Add location</div></Link>
<a href={'/add-location'}><div className={'p-2'}>Settings</div></a> <Link to={'/add-location'}><div className={'p-2'}>Settings</div></Link>
<a href={'#'} onClick={handleLogout}><div className={'p-2'}>Logout</div></a> <Link to={'#'} onClick={handleLogout}><div className={'p-2'}>Logout</div></Link>
{/* <div className={'p-2'}><a href={'#'}>Halo</a></div> */} {/* <div className={'p-2'}><a href={'#'}>Halo</a></div> */}
{/* <div className={'p-2'}><a href={'#'}>Halo</a></div> */} {/* <div className={'p-2'}><a href={'#'}>Halo</a></div> */}
</div> </div>
} }
</div> </div>
<form onSubmit={onSearchSubmit} className={`header-search-input ${dropdown ? "search-input-dropdown" : ""}`}> <form onSubmit={onSearchSubmit} style={{ marginTop: 'auto', marginBottom: 'auto' }} className={`header-search-input ${dropdown ? "search-input-dropdown" : ""}`}>
<AsyncSelect <AsyncSelect
onInputChange={onInput} onInputChange={onInput}
inputValue={searchVal} inputValue={searchVal}
isSearchable isSearchable
isClearable
menuIsOpen={searchVal.length > 1}
placeholder={"Candi Borobudur, Tunjungan Plaza, ...."} placeholder={"Candi Borobudur, Tunjungan Plaza, ...."}
components={{ components={{
DropdownIndicator: () => null, DropdownIndicator: () => null,
NoOptionsMessage: () => null, NoOptionsMessage: () => null,
@ -145,11 +174,6 @@ function Header() {
...base, ...base,
backgroundColor: isFocused ? '#202225' : 'none', backgroundColor: isFocused ? '#202225' : 'none',
}), }),
// container: (base, props) => ({
// ...base,
// border: 0,
// color: "white"
// })
}} }}
onChange={(v: ReactSelectData | unknown, _) => onSelectedSearchOption(v)} onChange={(v: ReactSelectData | unknown, _) => onSelectedSearchOption(v)}
/> />
@ -162,23 +186,23 @@ function Header() {
{dropdown && {dropdown &&
<a href="/" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Home</a> <a href="/" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Home</a>
} }
<a href="/best-places" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Top Places</a> <Link to="/best-places" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Top Places</Link>
<a href="/discover" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Discover</a> <Link to="/discover" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Discover</Link>
<a href="/stories" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Stories</a> <Link to="/stories" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Stories</Link>
<a href="/news-events" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>News / Events</a> <Link to="/news-events" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>News / Events</Link>
<a href="#" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Forum</a> <Link to="#" className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>Forum</Link>
<div className={'profile-container'}> <div className={'profile-container'}>
<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> <Link to={user.username ? '#' : '/login'} onClick={() => user.username ? setPageState({ ...pageState, profileMenu: !pageState.profileMenu }) : ''} className={`navLink ${!dropdown ? "navLink-disabled" : ""}`}>{user.username ? user.username : 'Sign in'}</Link>
{user && screen.width > 600 && {user && screen.width > 600 &&
<div className={'profile-dropdown bg-secondary ml-6'} style={pageState.profileMenu ? { display: 'block' } : { display: 'none' }}> <div className={'profile-dropdown bg-secondary ml-6'} style={pageState.profileMenu ? { display: 'block' } : { display: 'none' }}>
<a href={'/add-location'}><div className={'p-1'}>Add location</div></a> <Link to={'/add-location'} onClick={() => setPageState({ ...pageState, profileMenu: !pageState.profileMenu })}><div className={'p-1'}>Add location</div></Link>
<a href={'/user/profile'}><div className={'p-1'}>Profile</div></a> <Link to={'/user/profile'}><div className={'p-1'}>Profile</div></Link>
<a href={'#'}><div className={'p-1'}>Feed</div></a> <Link to={'#'}><div className={'p-1'}>Feed</div></Link>
<a href={'/user/settings'}><div className={'p-1'}>Settings</div></a> <Link to={'/user/settings'}><div className={'p-1'}>Settings</div></Link>
{user.is_admin && {user.is_admin &&
<a href={'/submissions'} ><div className={'p-1'}>Submissions</div></a> <Link to={'/submissions'} ><div className={'p-1'}>Submissions</div></Link>
} }
<a href={'#'} onClick={handleLogout}><div className={'p-1'}>Logout</div></a> <Link to={'#'} onClick={handleLogout}><div className={'p-1'}>Logout</div></Link>
{/* <div className={'p-2'}><a href={'#'}>Halo</a></div> */} {/* <div className={'p-2'}><a href={'#'}>Halo</a></div> */}
{/* <div className={'p-2'}><a href={'#'}>Halo</a></div> */} {/* <div className={'p-2'}><a href={'#'}>Halo</a></div> */}
</div> </div>

View File

@ -0,0 +1,12 @@
import { JSXInternal } from "node_modules/preact/src/jsx";
import "../style.css"
type DefaultLoadingAnimationProps = {
style: JSXInternal.CSSProperties
}
const DefaultLoadingAnimation = (props: DefaultLoadingAnimationProps) => (
<div style={props.style} class="loader"></div>
)
export default DefaultLoadingAnimation;

View File

@ -0,0 +1,21 @@
.loader {
width: 30px;
padding: 8px;
aspect-ratio: 1;
border-radius: 50%;
background: #a8adb3;
--_m:
conic-gradient(#0000 10%, #000),
linear-gradient(#000 0 0) content-box;
-webkit-mask: var(--_m);
mask: var(--_m);
-webkit-mask-composite: source-out;
mask-composite: subtract;
animation: l3 1s infinite linear;
}
@keyframes l3 {
to {
transform: rotate(1turn)
}
}

113
src/components/Select/Default.tsx Executable file
View File

@ -0,0 +1,113 @@
// import { cx } from "class-variance-authority";
// import Downshift from "downshift";
// import "../Header/style.css";
// type DefaultSelectProps = {
// data: any,
// onInput: (val: string) => void;
// inputValue: string
// }
// function DefaultSelect(props: DefaultSelectProps) {
// const items = [
// { author: 'Harper Lee', title: 'To Kill a Mockingbird' },
// { author: 'Lev Tolstoy', title: 'War and Peace' },
// { author: 'Fyodor Dostoyevsy', title: 'The Idiot' },
// { author: 'Oscar Wilde', title: 'A Picture of Dorian Gray' },
// { author: 'George Orwell', title: '1984' },
// { author: 'Jane Austen', title: 'Pride and Prejudice' },
// { author: 'Marcus Aurelius', title: 'Meditations' },
// { author: 'Fyodor Dostoevsky', title: 'The Brothers Karamazov' },
// { author: 'Lev Tolstoy', title: 'Anna Karenina' },
// { author: 'Fyodor Dostoevsky', title: 'Crime and Punishment' },
// ];
// return (
// <Downshift
// onChange={selection =>
// alert(
// selection
// ? `You selected "${selection.title}" by ${selection.author}. Great Choice!`
// : 'Selection Cleared',
// )
// }
// itemToString={item => (item ? item.title : '')}
// >
// {({
// getInputProps,
// getItemProps,
// getLabelProps,
// getMenuProps,
// getToggleButtonProps,
// isOpen,
// inputValue,
// highlightedIndex,
// selectedItem,
// closeMenu,
// }) => {
// inputValue = props.inputValue
// return (
// <div>
// <div className="w-72 flex flex-col gap-1">
// <div style={{ borderRadius: 2 }}>
// <input
// placeholder="Candi Borobudur, Tunjungan Plaza ..."
// style={{ minHeight: 45 }}
// className="w-full bg-secondary p-1.5"
// {...getInputProps({
// onChange: val => props.onInput(val),
// onKeyDown: event => {
// if (event.key === 'ArrowUp' && highlightedIndex === 0) {
// // If at the top, close the menu
// event.preventDefault();
// closeMenu();
// }
// if(event.key === 'Enter') {
// alert(props.inputValue)
// }
// },
// })}
// />
// </div>
// </div>
// <ul
// className={`absolute w-72 bg-secondary mt-1 shadow-md max-h-80 overflow-scroll p-0 z-10 ${!(isOpen && items.length) && 'hidden'
// }`}
// {...getMenuProps()}
// >
// {isOpen
// ? items
// .filter(
// item =>
// !inputValue ||
// item.title.toLowerCase().includes(inputValue.toLowerCase()) ||
// item.author.toLowerCase().includes(inputValue.toLowerCase()),
// )
// .map((item, index) => (
// <li
// className={cx(
// highlightedIndex === index && 'bg-tertiary',
// 'py-2 px-3 flex flex-col',
// )}
// {...getItemProps({
// key: item.title,
// index,
// item,
// })}
// >
// <span>{item.title}</span>
// <span className="text-sm text-gray-700">{item.author}</span>
// </li>
// ))
// : null}
// </ul>
// </div>
// )
// }}
// </Downshift>
// );
// }
// export default DefaultSelect;

View File

@ -1,11 +1,11 @@
export type LocationInfo = { export type LocationInfo = {
id: Number, id: number,
name: string, name: string,
thumbnail: string | null, thumbnail: string | null,
regency_name: String, regency_name: String,
province_name: String, province_name: String,
critic_score: Number, critic_score: number,
critic_count: Number, critic_count: number,
user_score: Number, user_score: number,
user_count: Number user_count: number
} }

View File

@ -10,6 +10,7 @@ import './style.css';
import { createLocationService } from "../../services/locations"; import { createLocationService } from "../../services/locations";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { UserRootState } from "../../store/type"; import { UserRootState } from "../../store/type";
import DefaultLoadingAnimation from "../../components/LoadingAnimation/Default";
function AddLocation() { function AddLocation() {
const [recentLocations, setRecentLocations] = useState<Array<LocationInfo>>() const [recentLocations, setRecentLocations] = useState<Array<LocationInfo>>()
@ -24,6 +25,7 @@ function AddLocation() {
regency: emptyRegency(), regency: emptyRegency(),
thumbnails: [], thumbnails: [],
}) })
const [submitLoading, setSubmitLoading ] = useState<boolean>(false)
const user = useSelector((state: UserRootState) => state.auth) const user = useSelector((state: UserRootState) => state.auth)
@ -33,7 +35,7 @@ function AddLocation() {
async function getRecentLocations() { async function getRecentLocations() {
try { try {
const locations = await getListRecentLocationsRatingsService(9) const locations = await getListRecentLocationsRatingsService(10, 1)
setRecentLocations(locations.data) setRecentLocations(locations.data)
// setIsLoading(false) // setIsLoading(false)
} catch (error) { } catch (error) {
@ -61,6 +63,8 @@ function AddLocation() {
async function onSubmitForm(e: TargetedEvent) { async function onSubmitForm(e: TargetedEvent) {
e.preventDefault(); e.preventDefault();
setSubmitLoading(true)
if(form.regency.regency_name === '') { if(form.regency.regency_name === '') {
setPageState({ regency_form_error: true }) setPageState({ regency_form_error: true })
return return
@ -86,8 +90,8 @@ function AddLocation() {
try { try {
await createLocationService(formData) await createLocationService(formData)
alert("Location Added") setSubmitLoading(false)
alert("Location Added\nModerator will check the submission because we don't trust you")
} catch(error) { } catch(error) {
console.log(error) console.log(error)
} }
@ -214,8 +218,11 @@ function AddLocation() {
{form.thumbnails.length > 0 && {form.thumbnails.length > 0 &&
<a className={'block mt-2'} onClick={onDeleteAllThumbnails}>Delete All Thumbnails</a> <a className={'block mt-2'} onClick={onDeleteAllThumbnails}>Delete All Thumbnails</a>
} }
{submitLoading ?
<DefaultLoadingAnimation style={{ marginTop: 20 }} />
:
<input type={'submit'} value={'Submit'} className={'block p-1 text-sm text-primary mt-4'} style={{ backgroundColor: '#a8adb3', letterSpacing: .5, width: 75 }} /> <input type={'submit'} value={'Submit'} className={'block p-1 text-sm text-primary mt-4'} style={{ backgroundColor: '#a8adb3', letterSpacing: .5, width: 75 }} />
}
</form> </form>
<span className={'mt-5 text-sm font-bold block'}>NOTE: LOCATION SUBMISSION MAY BE EDITED BY MODERATOR SO DON'T PUT STUPID ASS THUMBNAILS YOU 1 CENT DOORKNOB</span> <span className={'mt-5 text-sm font-bold block'}>NOTE: LOCATION SUBMISSION MAY BE EDITED BY MODERATOR SO DON'T PUT STUPID ASS THUMBNAILS YOU 1 CENT DOORKNOB</span>
</div> </div>

View File

@ -4,6 +4,7 @@ import { getListTopLocationsService } from "../../services";
import { DefaultSeparator } from "../../components"; import { DefaultSeparator } from "../../components";
import './style.css'; import './style.css';
import { useClick, useFloating, useInteractions } from "@floating-ui/react"; import { useClick, useFloating, useInteractions } from "@floating-ui/react";
import { Link } from "react-router-dom";
interface TopLocation { interface TopLocation {
row_number: Number, row_number: Number,
@ -139,12 +140,12 @@ 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> <Link className={'text-xl'} to={`/location/${x.id}`}>{x.row_number}.{x.name}</Link>
</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}`} > <Link to={`/location/${x.id}`} >
<img src={x.thumbnail ? x.thumbnail : ""} loading={'lazy'} style={{ width: '100%', objectFit: 'cover', height: '100%', aspectRatio: '1/1' }} /> <img src={x.thumbnail ? x.thumbnail : ""} loading={'lazy'} style={{ width: '100%', objectFit: 'cover', height: '100%', aspectRatio: '1/1' }} />
</a> </Link>
</div> </div>
<div className={'text-md font-bold'}>{x.regency_name}</div> <div className={'text-md font-bold'}>{x.regency_name}</div>
<div className={'text-xs mb-2'}>{x.address}</div> <div className={'text-xs mb-2'}>{x.address}</div>

View File

@ -5,10 +5,10 @@ import { JSXInternal } from "node_modules/preact/src/jsx";
import { getListRecentLocationsRatingsService, getRegenciesService, } from "../../services"; import { getListRecentLocationsRatingsService, getRegenciesService, } from "../../services";
import { LocationInfo, Regency } from "../../domains"; import { LocationInfo, Regency } from "../../domains";
import { ChangeEvent, TargetedEvent } from "preact/compat"; import { ChangeEvent, TargetedEvent } from "preact/compat";
import { LocationType, capitalize } from "../../types/common"; import { LocationType } from "../../types/common";
import './style.css';
import { enumKeys } from "../../utils"; import { enumKeys } from "../../utils";
import { getSearchLocationService } from "../../services/locations"; import { useNavigate } from "react-router-dom";
import './style.css';
interface floatFilterArgs<T> { interface floatFilterArgs<T> {
@ -129,6 +129,7 @@ function Discovery() {
locationType: [] locationType: []
}); });
const [isFloatFilterOpen, setFloatFilterOpen] = useState(false) const [isFloatFilterOpen, setFloatFilterOpen] = useState(false)
const navigate = useNavigate()
useEffect(() => { useEffect(() => {
getLocationType() getLocationType()
@ -147,8 +148,8 @@ function Discovery() {
async function getRecentLocations() { async function getRecentLocations() {
try { try {
const locations = await getListRecentLocationsRatingsService(15) const locations = await getListRecentLocationsRatingsService(15, 1)
setData((prevState) => ({ ...prevState, locations: locations.data.locations })) setData((prevState) => ({ ...prevState, locations: locations.data }))
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
@ -232,6 +233,21 @@ function Discovery() {
} }
function onClickLocation(id: number) {
navigate(`/location/${id}`)
}
function onDeleteByCityFilter(value: any) {
const dataRegencies = data.searchRegencies as (Regency & { isSelected?: boolean })[];
const regencyIdx = dataRegencies.findIndex(x => x.id == value.id);
dataRegencies[regencyIdx].isSelected = !dataRegencies[regencyIdx].isSelected
setData({
...data,
regencies: dataRegencies,
searchRegencies: dataRegencies
})
}
return ( return (
<div className="content main-content mt-3"> <div className="content main-content mt-3">
<section name="header"> <section name="header">
@ -252,6 +268,7 @@ function Discovery() {
{data.regencies.filter(x => x.isSelected).map(x => ( {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="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 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>
<button onClick={() => onDeleteByCityFilter(x)} style={{ color: "white"}}>x</button>
</div> </div>
))} ))}
</div> </div>
@ -291,7 +308,7 @@ function Discovery() {
{data.locations.map(x => ( {data.locations.map(x => (
<LocationCard <LocationCard
containerClass='card-list-container' containerClass='card-list-container'
onCardClick={(id) => console.log(id)} onCardClick={onClickLocation}
data={x} data={x}
/> />
))} ))}

View File

@ -27,8 +27,8 @@ function Home() {
async function getRecentLocations() { async function getRecentLocations() {
try { try {
const locations = await getListRecentLocationsRatingsService(12) const locations = await getListRecentLocationsRatingsService(12, 1)
setRecentLocations(locations.data.locations) setRecentLocations(locations.data)
// setIsLoading(false) // setIsLoading(false)
} catch (error) { } catch (error) {
console.log(error) console.log(error)

View File

@ -10,6 +10,7 @@ import { IHttpResponse } from "../../../src/types/common";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { UserRootState } from "src/store/type"; import { UserRootState } from "src/store/type";
import "./style.css" import "./style.css"
import { Link } from "react-router-dom";
function NewsEvent() { function NewsEvent() {
const [news, setNews] = useState<Array<News>>([]); const [news, setNews] = useState<Array<News>>([]);
@ -170,7 +171,7 @@ function NewsEvent() {
</div> </div>
<div className={'submitted-info'} style={{ display: 'inline-block' }}> <div className={'submitted-info'} style={{ display: 'inline-block' }}>
1d ago by 1d ago by
<a> {x.submitted_by}</a> <Link to={'#'}> {x.submitted_by}</Link>
</div> </div>
</div> </div>

View File

@ -5,6 +5,7 @@ import "./style.css"
import { useEffect, useState } from "preact/compat"; import { useEffect, useState } from "preact/compat";
import { getUserStatsService } from "../../services"; import { getUserStatsService } from "../../services";
import { CustomInterweave, SeparatorWithAnchor } from "../../components"; import { CustomInterweave, SeparatorWithAnchor } from "../../components";
import { Link } from "react-router-dom";
interface UserStats { interface UserStats {
followers: number, followers: number,
@ -105,31 +106,31 @@ function UserProfile() {
<section name={"profile-navigation"}> <section name={"profile-navigation"}>
<div className={'bg-secondary profile-nav'}> <div className={'bg-secondary profile-nav'}>
<a> <Link to='#'>
<div> <div>
summary summary
</div> </div>
</a> </Link>
<a> <Link to='#'>
<div> <div>
reviews reviews
</div> </div>
</a> </Link>
<a> <Link to='#'>
<div> <div>
likes likes
</div> </div>
</a> </Link>
<a> <Link to='#'>
<div> <div>
stories stories
</div> </div>
</a> </Link>
<a> <Link to='#'>
<div> <div>
tags tags
</div> </div>
</a> </Link>
</div> </div>
</section> </section>

View File

@ -36,7 +36,7 @@ function UserSettings() {
const [form, setForm] = useState<UserInfo>({ const [form, setForm] = useState<UserInfo>({
about: user.about, about: user.about,
website: user.website, website: user.website,
social_media: user.social_media social_media: user.social_media ?? []
}) })
@ -218,7 +218,7 @@ function UserSettings() {
value={form.about} value={form.about}
style={{ border: 'none', overflow: 'auto', outline: 'none', boxShadow: 'none', width: '100%', minHeight: 100, overflowY: 'hidden' }} style={{ border: 'none', overflow: 'auto', outline: 'none', boxShadow: 'none', width: '100%', minHeight: 100, overflowY: 'hidden' }}
/> />
<div className={'mt-2 mb-1'}> <div className={'mt-2 mb-5'}>
<h2 className={'font-bold text-sm mb-1'}>WEBSITE</h2> <h2 className={'font-bold text-sm mb-1'}>WEBSITE</h2>
<input className={'bg-secondary text-sm'} placeholder={'www.mywebsite.com'} style={{ width: '100%', borderRadius: 7, padding: '5px 10px' }} type={'text'} onChange={onChangeFormInput} name={'website'} value={form.website} /> <input className={'bg-secondary text-sm'} placeholder={'www.mywebsite.com'} style={{ width: '100%', borderRadius: 7, padding: '5px 10px' }} type={'text'} onChange={onChangeFormInput} name={'website'} value={form.website} />
</div> </div>

View File

@ -45,9 +45,9 @@ async function getListLocationsService({ page, page_size }: GetListLocationsArg)
} }
} }
async function getListRecentLocationsRatingsService(page_size: Number) { async function getListRecentLocationsRatingsService(page_size: number, page: number) {
const newState = { ...initialState }; const newState = { ...initialState };
const url = `${GET_LIST_RECENT_LOCATIONS_RATING_URI}?page_size=${page_size}` const url = `${GET_LIST_RECENT_LOCATIONS_RATING_URI}?page_size=${page_size}&page=${page}`
try { try {
const response = await client({ method: 'GET', url: url }) const response = await client({ method: 'GET', url: url })
switch (response.request.status) { switch (response.request.status) {

View File

@ -7,4 +7,5 @@
"allowSyntheticDefaultImports": true "allowSyntheticDefaultImports": true
}, },
"include": ["vite.config.ts"] "include": ["vite.config.ts"]
} }