Compare commits

...

2 Commits

Author SHA1 Message Date
58d1996ce3
Added story index management via query on homepage
Related to #27
2023-07-20 13:14:04 +03:00
bc154f8b6b
Fixed useFetch and useUser typing 2023-07-20 00:55:12 +03:00
6 changed files with 82 additions and 32 deletions

View File

@ -1,8 +1,8 @@
import { initialUser } from '../../api/user' import { initialUser } from '../../api/user'
import { User } from '../../api/user/types' import { User } from '../../api/user/types'
import { UseFetchErrored, UseFetchSucced } from '../useFetch' import { UseFetchReturn } from '../useFetch'
const useUser = (): UseFetchSucced<User> | UseFetchErrored => ( const useUser = (): UseFetchReturn<User> => (
// useFetch( // useFetch(
// composeUserUrl(getToken()), // composeUserUrl(getToken()),
// 'GET', // 'GET',
@ -16,6 +16,7 @@ const useUser = (): UseFetchSucced<User> | UseFetchErrored => (
data: initialUser, data: initialUser,
loading: false, loading: false,
error: null, error: null,
setData: () => {0}
} }
) )

View File

@ -1,3 +1,4 @@
export { default as useStoryDimensions } from './useStoryDimensions' export { default as useStoryDimensions } from './useStoryDimensions'
export { default as useSend } from './useSend' export { default as useSend } from './useSend'
export { default as useFetch } from './useFetch' export { default as useFetch } from './useFetch'
export { default as UseStoryIndex } from './useStoryIndex'

View File

@ -3,40 +3,31 @@ import { useEffect, useState } from 'react'
import { SetState } from '../utils/types' import { SetState } from '../utils/types'
import useSend from './useSend' import useSend from './useSend'
type UseFetchShared = { type UseFetchShared<T> = {
loading: boolean, loading: boolean,
abort?: () => void, abort?: () => void,
setData: SetState<T | undefined>
} }
type UseFetchSucced<T> = { type UseFetchSucced<T> = {
error: null, error: null,
data: T, data: T,
} & UseFetchShared } & UseFetchShared<T>
type UseFetchErrored = { type UseFetchErrored<T> = {
error: string, error: string,
data: undefined data: undefined
} & UseFetchShared } & UseFetchShared<T>
const gotError = <T>(res: UseFetchErrored | UseFetchSucced<T>): res is UseFetchErrored => ( const gotError = <T>(res: UseFetchErrored<T> | UseFetchSucced<T>): res is UseFetchErrored<T> => (
typeof res.error === 'string' typeof res.error === 'string'
) )
const fallbackError = <T>(res: UseFetchSucced<T> | UseFetchErrored) => ( const fallbackError = <T>(res: UseFetchSucced<T> | UseFetchErrored<T>) => (
gotError(res) ? res.error : res.data gotError(res) ? res.error : res.data
) )
type UseFetchReturn<T> = ({ type UseFetchReturn<T> = UseFetchSucced<T> | UseFetchErrored<T>
error: null,
data: T
} | {
error: string,
data: undefined
}) & {
loading: boolean,
setData: SetState<T | undefined>
abort?: (() => void)
}
function useFetch<R, T>( function useFetch<R, T>(
url: string, url: string,
@ -70,7 +61,7 @@ function useFetch<R, T>(
} }
} }
export type { UseFetchErrored, UseFetchSucced } export type { UseFetchReturn }
export default useFetch export default useFetch

View File

@ -14,12 +14,14 @@ function useStoryDimensions(maxRatio = 16 / 9) {
function handleResize() { function handleResize() {
setWindowDimensions(getWindowDimensions()); setWindowDimensions(getWindowDimensions());
} }
window.addEventListener('resize', handleResize); window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize);
}, []); }, []);
const height = windowDimensions.height - 56 const bottomBarHeight = 56
const height = windowDimensions.height - bottomBarHeight
const ratio = Math.max(maxRatio, height / windowDimensions.width) const ratio = Math.max(maxRatio, height / windowDimensions.width)

View File

@ -0,0 +1,47 @@
import { useEffect, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { SetState } from '../utils/types'
function useStoryIndex(annLength: number | undefined) {
const [index, setIndex] = useState(0)
const [searchParams, setSearchParams] = useSearchParams()
const withReset = <T>(f: SetState<T>) => (...args: Parameters<SetState<T>>) => {
console.log('resetting index')
setIndex(0)
setSearchParams(prev => ({ ...prev, storyIndex: '0' }), { replace: true })
f(...args)
}
useEffect(() => {
setIndex(annLength ?
Number.parseInt(searchParams.get('storyIndex') || '0') :
0)
// searchParams have actual query string at first render
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [annLength])
const increment = () => setIndex(prev => {
const newIndex = (prev + 1) % (annLength || 1)
setSearchParams(prev => ({ ...prev, storyIndex: newIndex.toString() }), { replace: true })
return newIndex
})
const decrement = () => setIndex(prev => {
const newIndex = prev > 0 ? (prev - 1) : 0
setSearchParams(prev => ({ ...prev, storyIndex: newIndex.toString() }), { replace: true })
return newIndex
})
return {
n: index,
withReset,
increment,
decrement
}
}
export default useStoryIndex

View File

@ -1,4 +1,4 @@
import { CSSProperties, useEffect, useState } from 'react' import { CSSProperties, useEffect, useMemo, useState } from 'react'
import Stories from 'react-insta-stories' import Stories from 'react-insta-stories'
import { Story } from 'react-insta-stories/dist/interfaces' import { Story } from 'react-insta-stories/dist/interfaces'
@ -10,7 +10,8 @@ import { Announcement } from '../api/announcement/types'
import { categoryGraphics } from '../assets/category' import { categoryGraphics } from '../assets/category'
import puffSpinner from '../assets/puff.svg' import puffSpinner from '../assets/puff.svg'
import { gotError } from '../hooks/useFetch' import { UseFetchReturn, gotError } from '../hooks/useFetch'
import useStoryIndex from '../hooks/useStoryIndex'
function generateStories(announcements: Announcement[]): Story[] { function generateStories(announcements: Announcement[]): Story[] {
return announcements.map(announcement => { return announcements.map(announcement => {
@ -23,14 +24,14 @@ function generateStories(announcements: Announcement[]): Story[] {
}) })
} }
function fallbackGenerateStories(announcementsFetch: ReturnType<typeof useAnnouncements>) { function fallbackGenerateStories(announcements: UseFetchReturn<Announcement[]>) {
if (announcementsFetch.loading) if (announcements.loading)
return fallbackStory() return fallbackStory()
if (gotError(announcementsFetch)) if (gotError(announcements))
return fallbackStory(announcementsFetch.error, true) return fallbackStory(announcements.error, true)
const stories = generateStories(announcementsFetch.data) const stories = generateStories(announcements.data)
if (stories.length === 0) if (stories.length === 0)
return fallbackStory('Здесь пока пусто') return fallbackStory('Здесь пока пусто')
@ -68,14 +69,21 @@ function HomePage() {
const [filterShown, setFilterShown] = useState(false) const [filterShown, setFilterShown] = useState(false)
const [filter, setFilter] = useState(defaultFilters) const [filter, setFilter] = useState(defaultFilters)
const announcementsFetch = useAnnouncements(filter)
const stories = fallbackGenerateStories(announcementsFetch) const announcements = useAnnouncements(filter)
const stories = useMemo(() => fallbackGenerateStories(announcements), [announcements])
const index = useStoryIndex(announcements.data?.length)
return (<> return (<>
<Filters filter={filter} setFilter={setFilter} filterShown={filterShown} setFilterShown={setFilterShown} /> <Filters filter={filter} setFilter={index.withReset(setFilter)} filterShown={filterShown} setFilterShown={setFilterShown} />
<div style={styles.container}> <div style={styles.container}>
<Stories <Stories
currentIndex={index.n}
onStoryEnd={index.increment}
onNext={index.increment}
onPrevious={index.decrement}
stories={stories} stories={stories}
defaultInterval={11000} defaultInterval={11000}
height={height} height={height}