1
+ import useInterval from '@use-it/interval'
1
2
import format from 'format-duration'
2
3
import he from 'he'
3
4
import fetch from 'isomorphic-unfetch'
4
5
import { GetServerSideProps , NextComponentType , NextPageContext } from 'next'
5
6
import absoluteUrl from 'next-absolute-url'
6
- import React , { useCallback , useEffect , useMemo , useState } from 'react'
7
+ import React , { FunctionComponent , useState } from 'react'
7
8
import useSWR from 'swr'
8
9
import { fetcherFn } from 'swr/dist/types'
9
- import Timer from 'tiny-timer'
10
10
11
11
import FIVE_MINUTES_IN_MS from '~/constants/five-minutes-in-ms'
12
12
import EbayItem from '~/typings/ebay-item'
@@ -15,6 +15,88 @@ const fetcher: fetcherFn<EbayItem & { bigImageUrl: string | null }> = (
15
15
url : string
16
16
) => fetch ( url ) . then ( r => r . json ( ) )
17
17
18
+ const BuyButton : FunctionComponent < { item : EbayItem } > = ( { item } ) => (
19
+ < a
20
+ className = "btn btn-lg btn-success"
21
+ href = { item . viewItemURL }
22
+ target = "_blank"
23
+ rel = "noopener noreferrer"
24
+ >
25
+ Buy for ${ item . sellingStatus . convertedCurrentPrice . toFixed ( 2 ) } { ' ' }
26
+ { item . shippingInfo . shippingServiceCost === 0 ? (
27
+ < > with free shipping</ >
28
+ ) : (
29
+ < > plus ${ item . shippingInfo . shippingServiceCost . toFixed ( 2 ) } shipping</ >
30
+ ) }
31
+ </ a >
32
+ )
33
+ BuyButton . displayName = 'BuyButton'
34
+
35
+ const ProgressBar : FunctionComponent < {
36
+ now : number
37
+ min : number
38
+ max : number
39
+ } > = ( { now, min, max } ) => (
40
+ < div className = "progress" >
41
+ < div
42
+ className = "progress-bar progress-bar-striped bg-info"
43
+ role = "progressbar"
44
+ aria-valuenow = { now }
45
+ aria-valuemin = { min }
46
+ aria-valuemax = { max }
47
+ style = { {
48
+ width : `${ ( now / max ) * 100 } %`
49
+ } }
50
+ />
51
+ </ div >
52
+ )
53
+ ProgressBar . displayName = 'ProgressBar'
54
+
55
+ const CardBody : FunctionComponent < {
56
+ item : EbayItem & { bigImageUrl : string | null }
57
+ } > = ( { item } ) => (
58
+ < div className = "card-body" >
59
+ < img
60
+ className = "rounded mx-auto d-block"
61
+ src = { item . bigImageUrl || item . galleryURL }
62
+ />
63
+ < div className = "text-center my-2" >
64
+ ({ he . decode ( item . primaryCategory . categoryName ) } )
65
+ </ div >
66
+ < div className = "text-center mt-4" >
67
+ < BuyButton item = { item } />
68
+ </ div >
69
+ </ div >
70
+ )
71
+ CardBody . displayName = 'CardBody'
72
+
73
+ const CardFooter : FunctionComponent < {
74
+ now : number
75
+ min : number
76
+ max : number
77
+ } > = ( { now, min, max } ) => (
78
+ < div className = "card-footer text-muted" >
79
+ < div className = "text-center" > { format ( max - now ) } until the next item</ div >
80
+ < ProgressBar now = { now } min = { min } max = { max } />
81
+ </ div >
82
+ )
83
+ CardFooter . displayName = 'CardBody'
84
+
85
+ const FooterText : FunctionComponent < { } > = ( ) => (
86
+ < div className = "text-center mt-4" >
87
+ (put together from a box of scraps by{ ' ' }
88
+ < a
89
+ href = "https://github.com/icopp"
90
+ target = "_blank"
91
+ rel = "noopener noreferrer"
92
+ >
93
+ icopp
94
+ </ a >
95
+ )
96
+ </ div >
97
+ )
98
+ FooterText . displayName = 'FooterText'
99
+
18
100
const Index : NextComponentType <
19
101
NextPageContext ,
20
102
{ initialData : EbayItem & { bigImageUrl : string | null } } ,
@@ -24,92 +106,36 @@ const Index: NextComponentType<
24
106
'/api/random-ebay-result' ,
25
107
fetcher ,
26
108
{
27
- initialData
109
+ initialData,
110
+ revalidateOnFocus : false
28
111
}
29
112
)
30
113
31
- const [ tick , setTick ] = useState < number > ( 0 )
32
- const timer = useMemo ( ( ) => new Timer ( { interval : 500 , stopwatch : true } ) , [ ] )
33
- const finishTimer = useCallback ( ( ) => {
34
- mutate ( )
35
- timer . stop ( )
36
- timer . start ( FIVE_MINUTES_IN_MS )
37
- } , [ ] )
38
- useEffect ( ( ) => {
39
- timer . addListener ( 'tick' , setTick )
40
- timer . addListener ( 'done' , finishTimer )
41
- timer . start ( FIVE_MINUTES_IN_MS )
42
-
43
- return function ( ) : void {
44
- timer . removeListener ( 'tick' , setTick )
45
- timer . removeListener ( 'done' , finishTimer )
46
- timer . stop ( )
114
+ const [ timePassed , setTimePassed ] = useState ( 0 )
115
+ useInterval ( ( ) => {
116
+ if ( timePassed >= FIVE_MINUTES_IN_MS ) {
117
+ mutate ( )
118
+ setTimePassed ( 0 )
119
+ return
47
120
}
48
- } , [ ] )
121
+
122
+ setTimePassed ( timePassed => timePassed + 1000 )
123
+ } , 1000 )
49
124
50
125
return (
51
126
< div className = "container" >
52
127
{ data && (
53
128
< div className = "card my-4" >
54
129
< h3 className = "card-header text-center" > { he . decode ( data . title ) } </ h3 >
55
- < div className = "card-body" >
56
- < img
57
- className = "rounded mx-auto d-block"
58
- src = { data . bigImageUrl || data . galleryURL }
59
- />
60
- < div className = "text-center my-2" >
61
- ({ he . decode ( data . primaryCategory . categoryName ) } )
62
- </ div >
63
- < div className = "text-center mt-4" >
64
- < a
65
- className = "btn btn-lg btn-success"
66
- href = { data . viewItemURL }
67
- target = "_blank"
68
- rel = "noopener noreferrer"
69
- >
70
- Buy for ${ data . sellingStatus . convertedCurrentPrice . toFixed ( 2 ) } { ' ' }
71
- { data . shippingInfo . shippingServiceCost === 0 ? (
72
- < > with free shipping</ >
73
- ) : (
74
- < >
75
- plus ${ data . shippingInfo . shippingServiceCost . toFixed ( 2 ) } { ' ' }
76
- shipping
77
- </ >
78
- ) }
79
- </ a >
80
- </ div >
81
- </ div >
82
- < div className = "card-footer text-muted" >
83
- < div className = "text-center" >
84
- { format ( FIVE_MINUTES_IN_MS - tick ) } until the next item
85
- </ div >
86
- < div className = "progress" >
87
- < div
88
- className = "progress-bar progress-bar-striped progress-bar-animated bg-info"
89
- role = "progressbar"
90
- aria-valuenow = { tick }
91
- aria-valuemin = { 0 }
92
- aria-valuemax = { FIVE_MINUTES_IN_MS }
93
- style = { { width : `${ ( tick / FIVE_MINUTES_IN_MS ) * 100 } %` } }
94
- />
95
- </ div >
96
- </ div >
130
+ < CardBody item = { data } />
131
+ < CardFooter now = { timePassed } min = { 0 } max = { FIVE_MINUTES_IN_MS } />
97
132
</ div >
98
133
) }
99
- < div className = "text-center mt-4" >
100
- (put together from a box of scraps by{ ' ' }
101
- < a
102
- href = "https://github.com/icopp"
103
- target = "_blank"
104
- rel = "noopener noreferrer"
105
- >
106
- icopp
107
- </ a >
108
- )
109
- </ div >
134
+ < FooterText />
110
135
</ div >
111
136
)
112
137
}
138
+ Index . displayName = 'Index'
113
139
export default Index
114
140
115
141
export const getServerSideProps : GetServerSideProps = async ( { req } ) => {
0 commit comments