Compare commits

...

2 Commits

Author SHA1 Message Date
7e152c1532 fix returned pgx data type 2024-03-04 16:08:47 +07:00
42777b4e35 add search 2024-03-03 20:39:33 +07:00
19 changed files with 2432 additions and 69 deletions

4
TODO Normal file
View File

@ -0,0 +1,4 @@
SITE DESIGN:
- Discover / Cards
https://www.looria.com/category/backpack

View File

@ -16,11 +16,10 @@
"interweave": "^13.1.0", "interweave": "^13.1.0",
"interweave-autolink": "^5.1.0", "interweave-autolink": "^5.1.0",
"interweave-emoji": "^7.0.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-redux": "^8.1.2",
"react-router-dom": "^6.16.0", "react-router-dom": "^6.16.0",
"react-select": "^5.8.0",
"react-textarea-autosize": "^8.5.3", "react-textarea-autosize": "^8.5.3",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"redux-thunk": "^2.4.2", "redux-thunk": "^2.4.2",

2273
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@ import { BrowserRouter as Router } from 'react-router-dom'
import './app.css' import './app.css'
import { DefaultLayout } from './layouts' import { DefaultLayout } from './layouts'
import "yet-another-react-lightbox/styles.css"; import "yet-another-react-lightbox/styles.css";
import { Login, NotFound, Submissions } from './pages' import { Login, NotFound } from './pages'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { persistore, store } from './store/config' import { persistore, store } from './store/config'
import { PersistGate } from 'redux-persist/integration/react' import { PersistGate } from 'redux-persist/integration/react'

View File

@ -35,13 +35,13 @@ export default function CustomInterweave({
instagram, instagram,
...props ...props
}: Props) { }: Props) {
let hashtagUrl = ''; // let hashtagUrl = '';
if (twitter) { // if (twitter) {
hashtagUrl = 'https://twitter.com/hashtag/{{hashtag}}'; // hashtagUrl = 'https://twitter.com/hashtag/{{hashtag}}';
} else if (instagram) { // } else if (instagram) {
hashtagUrl = 'https://instagram.com/explore/tags/{{hashtag}}'; // hashtagUrl = 'https://instagram.com/explore/tags/{{hashtag}}';
} // }
return ( return (
<Interweave <Interweave

View File

@ -3,8 +3,10 @@ import { useState } from "preact/hooks";
import { useSelector, useDispatch } from "react-redux"; 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 './style.css'; import './style.css';
import { logoutService } from "../../services"; import { logoutService } from "../../services";
import { getSearchLocationService } from "../../services/locations";
function Header() { function Header() {
@ -17,9 +19,9 @@ function Header() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const user = useSelector((state: UserRootState) => state.auth) const user = useSelector((state: UserRootState) => state.auth)
const onInput = (e: React.ChangeEvent<HTMLInputElement>): void => { const onInput = (val: string): void => {
const val = e.target as HTMLInputElement; // const val = e.target as HTMLInputElement;
setSearchVal(val.value) setSearchVal(val.toLowerCase())
} }
const handleLogout = async (): Promise<void> => { const handleLogout = async (): Promise<void> => {
@ -36,6 +38,29 @@ function Header() {
e.preventDefault(); e.preventDefault();
} }
const onSelectedSearchOption = (val) => {
console.log(val)
}
const onLoadSelectOptions = async (inputValue: string) => {
try {
const results = await getSearchLocationService({
name: inputValue,
page: 1,
page_size: 7
})
const resultData = results.data.map((x: any) => {
return {
value: x.id,
label: x.name
}
})
return resultData
} catch (err) {
alert(err)
}
}
const onDropdown = (): void => { const onDropdown = (): void => {
document.body.style.overflow = "hidden" document.body.style.overflow = "hidden"
@ -73,15 +98,57 @@ function Header() {
} }
</div> </div>
<form onSubmit={onSearchSubmit} className={`header-search-input ${dropdown ? "search-input-dropdown" : ""}`}> <form onSubmit={onSearchSubmit} className={`header-search-input ${dropdown ? "search-input-dropdown" : ""}`}>
<label> <AsyncSelect
<input onInputChange={onInput}
type="text" inputValue={searchVal}
value={searchVal} isSearchable
onInput={onInput} placeholder={"Candi Borobudur, Tunjungan Plaza, ...."}
placeholder="Yogyakarta, Pantai Cidaun ..."
class="text-input-search" components={{
DropdownIndicator: () => null,
NoOptionsMessage: () => null,
IndicatorSeparator: () => null
}}
loadOptions={onLoadSelectOptions}
cacheOptions
classNames={{
control: () => "bg-secondary text-input-search",
menuList: () => "bg-secondary text-sm text-left",
}}
styles={{
singleValue: (base, _props) => ({
color: '#797979',
textTransform: 'capitalize',
...base,
}),
input: (base, _props) => ({
...base,
width: 325,
color: 'white',
padding: 0,
}),
control: (base, _props) => ({
...base,
backgroundColor: '#red',
color: 'white',
border: 0,
padding: 0,
boxShadow: 'none',
textAlign: 'left',
minHeight: 45
}),
option: (base, {isFocused}) => ({
...base,
backgroundColor: isFocused ? '#202225' : 'none',
}),
// container: (base, props) => ({
// ...base,
// border: 0,
// color: "white"
// })
}}
onChange={onSelectedSearchOption}
/> />
</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 ${dropdown ? 'ml-auto' : ''}`} style={{ padding: 5, borderRadius: 10 }}>
<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>

View File

@ -1,8 +1,8 @@
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 LOGIN_URI = `${BASE_URL}/user/login`;
const LOGOUT_URI = `${BASE_URL}/user/logout` const LOGOUT_URI = `${BASE_URL}/user/logout`;
const GET_REGIONS = `${BASE_URL}/regions`; const GET_REGIONS = `${BASE_URL}/regions`;
const GET_REGENCIES = `${BASE_URL}/region/regencies`; const GET_REGENCIES = `${BASE_URL}/region/regencies`;
@ -11,22 +11,23 @@ const GET_PROVINCES = `${BASE_URL}/region/provinces`;
const GET_CURRENT_USER_STATS = `${BASE_URL}/user/profile`; const GET_CURRENT_USER_STATS = `${BASE_URL}/user/profile`;
const PATCH_USER_AVATAR = `${BASE_URL}/user/avatar`; const PATCH_USER_AVATAR = `${BASE_URL}/user/avatar`;
const PATCH_USER_INFO = `${BASE_URL}/user`; const PATCH_USER_INFO = `${BASE_URL}/user`;
const DELETE_USER_AVATAR = `${BASE_URL}/user/avatar` const DELETE_USER_AVATAR = `${BASE_URL}/user/avatar`;
const GET_NEWS_EVENTS_URI = `${BASE_URL}/news-events`; const GET_NEWS_EVENTS_URI = `${BASE_URL}/news-events`;
const POST_NEWS_EVENTS_URI = GET_NEWS_EVENTS_URI const POST_NEWS_EVENTS_URI = GET_NEWS_EVENTS_URI;
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_SEARCH_LOCATIONS_URI = `${BASE_URL}/locations/search`;
const GET_LIST_RECENT_LOCATIONS_RATING_URI = `${BASE_URL}/locations/recent` const GET_LIST_TOP_LOCATIONS = `${BASE_URL}/locations/top-ratings`;
const GET_LIST_RECENT_LOCATIONS_RATING_URI = `${BASE_URL}/locations/recent`;
const GET_LOCATION_URI = `${BASE_URL}/location`; const GET_LOCATION_URI = `${BASE_URL}/location`;
const GET_LOCATION_TAGS_URI = `${BASE_URL}/location/tags` const GET_LOCATION_TAGS_URI = `${BASE_URL}/location/tags`;
const POST_CREATE_LOCATION = GET_LIST_LOCATIONS_URI; const POST_CREATE_LOCATION = GET_LIST_LOCATIONS_URI;
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` const POST_REVIEW_LOCATION_URI = `${BASE_URL}/review/location`;
const GET_CURRENT_USER_REVIEW_LOCATION_URI = `${BASE_URL}/user/review/location` const GET_CURRENT_USER_REVIEW_LOCATION_URI = `${BASE_URL}/user/review/location`;
export { export {
BASE_URL, BASE_URL,
@ -43,6 +44,7 @@ export {
GET_LIST_TOP_LOCATIONS, GET_LIST_TOP_LOCATIONS,
GET_LIST_LOCATIONS_URI, GET_LIST_LOCATIONS_URI,
GET_LOCATION_URI, GET_LOCATION_URI,
GET_SEARCH_LOCATIONS_URI,
GET_LOCATION_TAGS_URI, GET_LOCATION_TAGS_URI,
GET_IMAGES_BY_LOCATION_URI, GET_IMAGES_BY_LOCATION_URI,
GET_CURRENT_USER_REVIEW_LOCATION_URI, GET_CURRENT_USER_REVIEW_LOCATION_URI,

View File

@ -1,9 +1,7 @@
import { NullValueRes } from '../types/common';
export type LocationInfo = { export type LocationInfo = {
id: Number, id: Number,
name: string, name: string,
thumbnail: NullValueRes<'String', string>, thumbnail: string | null,
regency_name: String, regency_name: String,
province_name: String, province_name: String,
critic_score: Number, critic_score: Number,

View File

@ -227,7 +227,7 @@ function AddLocation() {
<a href={'#'} title={x.name}> <a href={'#'} title={x.name}>
<img <img
loading={'lazy'} loading={'lazy'}
src={x.thumbnail.String.toString()} src={x.thumbnail ? x.thumbnail : ""}
alt={x.name} alt={x.name}
style={{ aspectRatio: '1/1' }} style={{ aspectRatio: '1/1' }}
/> />

View File

@ -9,7 +9,7 @@ interface TopLocation {
row_number: Number, row_number: Number,
id: Number, id: Number,
name: String, name: String,
thumbnail: NullValueRes<"String", String>, thumbnail: string | null,
address: String, address: String,
google_maps_link: string, google_maps_link: string,
regency_name: string, regency_name: string,
@ -145,7 +145,7 @@ function BestLocation() {
</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}`} >
<img src={x.thumbnail.String.toString()} 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> </a>
</div> </div>
<div className={'text-md font-bold'}>{x.regency_name}</div> <div className={'text-md font-bold'}>{x.regency_name}</div>

View File

@ -53,16 +53,8 @@ function Home() {
} }
} }
function onNavigateToDetail( function onNavigateToDetail(id: Number,) {
id: Number, navigate(`/location/${id}`)
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(() => {
@ -79,9 +71,9 @@ function Home() {
<SeparatorWithAnchor pageLink='#' pageName='recently added' secondLink='#' /> <SeparatorWithAnchor pageLink='#' pageName='recently added' secondLink='#' />
{recentLocations.map((x) => ( {recentLocations.map((x) => (
<div className={"recently-added-section-card"}> <div className={"recently-added-section-card"}>
<a onClick={() => onNavigateToDetail(x.id, x.critic_count, x.critic_score, x.user_count, x.user_score)}> <a onClick={() => onNavigateToDetail(x.id)}>
<div className={'border-secondary recently-img-container'}> <div className={'border-secondary recently-img-container'}>
<img alt={x.name} src={x.thumbnail.String.toString()} loading="lazy" style={{ width: '100%', height: '100%' }} /> <img alt={x.name} src={x.thumbnail ? x.thumbnail : ''} loading="lazy" style={{ width: '100%', height: '100%' }} />
</div> </div>
</a> </a>
<div className={"border-primary pb-2 location-container text-sm mb-2 mt-2"}> <div className={"border-primary pb-2 location-container text-sm mb-2 mt-2"}>
@ -199,7 +191,7 @@ function Home() {
<div className={"pt-2 text-sm top-location-container"}> <div className={"pt-2 text-sm top-location-container"}>
<div className={'mr-2 critics-users-image'}> <div className={'mr-2 critics-users-image'}>
<img <img
src={x.thumbnail.Valid ? x.thumbnail.String.toString() : 'https://i.ytimg.com/vi/0DY1WSk8B9o/maxresdefault.jpg'} src={x.thumbnail ? x.thumbnail : 'https://i.ytimg.com/vi/0DY1WSk8B9o/maxresdefault.jpg'}
loading={'lazy'} loading={'lazy'}
style={{ height: '100%', width: '100%', borderRadius: 3}} style={{ height: '100%', width: '100%', borderRadius: 3}}
/> />
@ -223,7 +215,7 @@ function Home() {
<div className={"pt-2 text-sm top-location-container"}> <div className={"pt-2 text-sm top-location-container"}>
<div className={'mr-2 critics-users-image'}> <div className={'mr-2 critics-users-image'}>
<img <img
src={x.thumbnail.Valid ? x.thumbnail.String.toString() : 'https://i.ytimg.com/vi/0DY1WSk8B9o/maxresdefault.jpg'} src={x.thumbnail ? x.thumbnail : 'https://i.ytimg.com/vi/0DY1WSk8B9o/maxresdefault.jpg'}
style={{ height: '100%', width: '100%', borderRadius: 3}} style={{ height: '100%', width: '100%', borderRadius: 3}}
/> />
</div> </div>

View File

@ -61,7 +61,9 @@ function LocationDetail() {
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()) if(val.detail.thumbnail) {
getImage(val.detail.thumbnail)
}
}) })
} catch (error) { } catch (error) {
let err = error as AxiosError; let err = error as AxiosError;
@ -223,7 +225,7 @@ function LocationDetail() {
style={{ display: 'grid', position: 'relative', gridTemplateColumns: 'repeat(12,1fr)', cursor: 'zoom-in' }} style={{ display: 'grid', position: 'relative', gridTemplateColumns: 'repeat(12,1fr)', cursor: 'zoom-in' }}
>{Number(locationImages?.total_image) > 0 && >{Number(locationImages?.total_image) > 0 &&
<div class="image-stack__item image-stack__item--top"> <div class="image-stack__item image-stack__item--top">
<img src={locationDetail.detail.thumbnail.String.toString()} alt="" style={{ aspectRatio: '1/1' }} /> <img src={locationDetail.detail.thumbnail ? locationDetail.detail.thumbnail : ""} alt="" style={{ aspectRatio: '1/1' }} />
{locationImages?.images.length > 1 && {locationImages?.images.length > 1 &&
<div className={'text-xs p-2 bg-primary'} style={{ position: 'absolute', bottom: 0, right: 0 }}> <div className={'text-xs p-2 bg-primary'} style={{ position: 'absolute', bottom: 0, right: 0 }}>
Total images ({locationImages?.images.length}) Total images ({locationImages?.images.length})
@ -237,7 +239,7 @@ function LocationDetail() {
</div> </div>
} }
<div class="image-stack__item image-stack__item--bottom" style={Number(locationImages?.total_image) > 1 ? {} : { gridColumn: '13/1' }}> <div class="image-stack__item image-stack__item--bottom" style={Number(locationImages?.total_image) > 1 ? {} : { gridColumn: '13/1' }}>
<img src={Number(locationImages?.total_image) > 1 ? locationImages?.images[1].src.toString() : locationDetail.detail.thumbnail.String.toString()} alt="" style={{ aspectRatio: '1/1' }} /> <img src={Number(locationImages?.total_image) > 1 ? locationImages?.images[1].src.toString() : locationDetail.detail.thumbnail!} alt="" style={{ aspectRatio: '1/1' }} />
</div> </div>
</a> </a>
</div> </div>
@ -433,7 +435,7 @@ 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={x.user_avatar ? x.user_avatar : 'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'}
/> />
</a> </a>
</div> </div>
@ -486,7 +488,7 @@ 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={x.user_avatar ? x.user_avatar : 'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'}
/> />
</a> </a>
</div> </div>

View File

@ -9,7 +9,7 @@ export interface ILocationDetail {
province_name: String, province_name: String,
region_name: String, region_name: String,
google_maps_link: String, google_maps_link: String,
thumbnail: NullValueRes<"String", String>, thumbnail: string | null,
submitted_by: Number, submitted_by: Number,
critic_score: Number, critic_score: Number,
critic_count: Number, critic_count: Number,
@ -22,7 +22,7 @@ export function emptyLocationDetail(): ILocationDetail {
id: 0, id: 0,
address: '', address: '',
google_maps_link: '', google_maps_link: '',
thumbnail: { String: '', Valid: false }, thumbnail: "",
name: '', name: '',
province_name: '', province_name: '',
regency_name: '', regency_name: '',
@ -41,7 +41,7 @@ export interface LocationReviewsResponse {
comments: string, comments: string,
user_id: number, user_id: number,
username: string, username: string,
user_avatar: NullValueRes<"String", string>, user_avatar: string | null,
created_at: string, created_at: string,
updated_at: string updated_at: string
} }

View File

@ -10,7 +10,6 @@ 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 useCallbackState from "../../../src/types/state-callback";
function NewsEvent() { function NewsEvent() {
const [news, setNews] = useState<Array<News>>([]); const [news, setNews] = useState<Array<News>>([]);

View File

@ -3,7 +3,7 @@ import { DefaultButton, TitleSeparator, WarningButton } from "../../components";
import { UserRootState } from "../../store/type"; import { UserRootState } from "../../store/type";
import { DEFAULT_AVATAR_IMG } from "../../constants/default"; import { DEFAULT_AVATAR_IMG } from "../../constants/default";
import { ChangeEvent, TargetedEvent, useRef } from "preact/compat"; import { ChangeEvent, TargetedEvent, useRef } from "preact/compat";
import { useState } from "react"; import { useState } from "preact/compat";
import { enumKeys, useAutosizeTextArea } from "../../utils"; import { enumKeys, useAutosizeTextArea } from "../../utils";
import { SocialMediaEnum } from "../../types/common"; import { SocialMediaEnum } from "../../types/common";
import { SocialMedia, UserInfo } from "../../../src/domains/User"; import { SocialMedia, UserInfo } from "../../../src/domains/User";

View File

@ -5,6 +5,7 @@ import {
GET_LIST_TOP_LOCATIONS, GET_LIST_TOP_LOCATIONS,
GET_LOCATION_TAGS_URI, GET_LOCATION_TAGS_URI,
GET_LOCATION_URI, GET_LOCATION_URI,
GET_SEARCH_LOCATIONS_URI,
POST_CREATE_LOCATION POST_CREATE_LOCATION
} from "../constants/api"; } from "../constants/api";
import { client } from "./config"; import { client } from "./config";
@ -21,6 +22,10 @@ interface GetListLocationsArg extends GetRequestPagination {
region_type?: number region_type?: number
} }
interface GetSearchLocations extends GetRequestPagination {
name: string
}
async function getListLocationsService({ page, page_size }: GetListLocationsArg) { async function getListLocationsService({ page, page_size }: GetListLocationsArg) {
const newState = { ...initialState }; const newState = { ...initialState };
const url = `${GET_LIST_LOCATIONS_URI}?page=${page}&page_size=${page_size}` const url = `${GET_LIST_LOCATIONS_URI}?page=${page}&page_size=${page_size}`
@ -126,8 +131,29 @@ async function createLocationService(data: FormData): Promise<IHttpResponse> {
} }
} }
async function getSearchLocationService(arg: GetSearchLocations): Promise<IHttpResponse> {
const newState: IHttpResponse = { data: null, error: null};
try {
const response = await client({
method: 'GET',
url: `${GET_SEARCH_LOCATIONS_URI}?name=${arg.name}&limit=${arg.page_size}&offset=${arg.page}`
})
newState.data = response.data;
newState.status = response.status;
return newState;
} catch(error) {
const err = error as AxiosError;
newState.error = err;
newState.status = err.status;
return newState;
}
}
export { export {
getListLocationsService, getListLocationsService,
getSearchLocationService,
getListRecentLocationsRatingsService, getListRecentLocationsRatingsService,
getListTopLocationsService, getListTopLocationsService,
getLocationTagsService, getLocationTagsService,

View File

@ -1,6 +1,7 @@
import { AxiosError } from "axios" import { AxiosError } from "axios"
import { client } from "./config"; import { client } from "./config";
import { GET_CURRENT_USER_REVIEW_LOCATION_URI, POST_REVIEW_LOCATION_URI } from "../constants/api"; import { GET_CURRENT_USER_REVIEW_LOCATION_URI, POST_REVIEW_LOCATION_URI } from "../constants/api";
import { IHttpResponse } from "src/types/common";
const initialState: IHttpResponse = { const initialState: IHttpResponse = {
data: null, data: null,

View File

@ -1,5 +1,5 @@
// https://medium.com/geekculture/usecallbackstate-the-hook-that-let-you-run-code-after-a-setstate-operation-finished-25f40db56661 // https://medium.com/geekculture/usecallbackstate-the-hook-that-let-you-run-code-after-a-setstate-operation-finished-25f40db56661
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "preact/compat";
type CallBackType<T> = (updatedValue: T) => void; type CallBackType<T> = (updatedValue: T) => void;

View File

@ -1,4 +1,4 @@
import { useEffect } from "react"; import { useEffect } from "preact/compat";
// Updates the height of a <textarea> when the value changes. // Updates the height of a <textarea> when the value changes.
const useAutosizeTextArea = ( const useAutosizeTextArea = (