1
1
/* eslint-disable @next/next/no-img-element */
2
2
3
3
'use client' ;
4
- import { Send } from '@mui/icons-material' ;
4
+ import styled from '@emotion/styled' ;
5
+ import { Autorenew , Send } from '@mui/icons-material' ;
6
+ import { css , keyframes } from '@mui/material' ;
5
7
import Avatar from '@mui/material/Avatar' ;
6
8
import Button from '@mui/material/Button' ;
9
+ import { purple } from '@mui/material/colors' ;
7
10
import Stack from '@mui/material/Stack' ;
8
11
import React , { useEffect , useState } from 'react' ;
9
12
@@ -12,42 +15,60 @@ import { useClientContext } from '@/hooks/useClientContext';
12
15
13
16
import SubmitButton from '@/components/shared/SubmitButton' ;
14
17
18
+ import { getApiResponse } from '@/utils/shared/get-api-response' ;
19
+
15
20
const DisplayRandomPicture = ( ) => {
16
21
const [ imageUrl , setImageUrl ] = useState ( '' ) ;
17
- const [ loading , setLoading ] = useState ( true ) ;
22
+ const [ loading , setLoading ] = useState ( false ) ;
18
23
const [ error , setError ] = useState ( '' ) ;
19
24
const { fetchCount, updateClientCtx } = useClientContext ( ) ;
20
25
const { setAlertBarProps, renderAlertBar } = useAlertBar ( ) ;
21
26
const renderCountRef = React . useRef ( 0 ) ;
22
27
23
28
const fetchRandomPicture = async ( ) => {
29
+ if ( loading ) {
30
+ setAlertBarProps ( {
31
+ message : 'Please wait for the current fetch to complete' ,
32
+ severity : 'warning' ,
33
+ } ) ;
34
+ return ;
35
+ }
24
36
setLoading ( true ) ;
25
37
setError ( '' ) ;
26
38
27
39
try {
28
- const response = await fetch ( 'https://picsum.photos/300/150' ) ;
29
- if ( ! response . ok ) {
30
- throw new Error ( 'Error fetching the image' ) ;
40
+ const response = await getApiResponse < Response & { url : string } > ( {
41
+ apiEndpoint : 'https://picsum.photos/300/160' ,
42
+ timeout : 5001 ,
43
+ } ) ;
44
+
45
+ if ( ! response ?. url ) {
46
+ throw new Error ( 'Error fetching the image, no response url' ) ;
31
47
}
48
+
32
49
setImageUrl ( response . url ) ;
33
50
updateClientCtx ( { fetchCount : fetchCount + 1 } ) ;
34
51
setAlertBarProps ( {
35
52
message : 'A random picture fetched successfully' ,
36
53
severity : 'info' ,
37
54
} ) ;
38
55
} catch ( error ) {
39
- setError ( 'Error fetching the image' ) ;
56
+ const errorMsg =
57
+ error instanceof Error ? error . message : 'Error fetching the image' ;
58
+
59
+ setError ( errorMsg ) ;
40
60
setAlertBarProps ( {
41
- message : 'Error fetching the image' ,
61
+ message : errorMsg ,
42
62
severity : 'error' ,
43
63
} ) ;
64
+ setLoading ( false ) ;
44
65
} finally {
45
66
setLoading ( false ) ;
46
67
}
47
68
} ;
48
69
49
70
useEffect ( ( ) => {
50
- if ( renderCountRef . current === 0 ) {
71
+ if ( renderCountRef . current === 0 && ! loading ) {
51
72
fetchRandomPicture ( ) ;
52
73
}
53
74
renderCountRef . current += 1 ;
@@ -59,6 +80,7 @@ const DisplayRandomPicture = () => {
59
80
justifyContent = 'center'
60
81
alignItems = 'center'
61
82
spacing = { 2 }
83
+ sx = { { position : 'relative' , width : '300px' , margin : '0 auto' } }
62
84
>
63
85
{ error && < p > { error } </ p > }
64
86
{ imageUrl && (
@@ -71,7 +93,7 @@ const DisplayRandomPicture = () => {
71
93
) }
72
94
< div >
73
95
{ loading && < span > Loading...</ span > } Component Render Count:{ ' ' }
74
- { renderCountRef . current }
96
+ { renderCountRef . current + 1 }
75
97
</ div >
76
98
77
99
< SubmitButton
@@ -88,9 +110,54 @@ const DisplayRandomPicture = () => {
88
110
Get Another Picture
89
111
</ Button >
90
112
</ SubmitButton >
113
+ { imageUrl && (
114
+ < StyledRefreshButton onClick = { fetchRandomPicture } loading = { loading } >
115
+ < Avatar sx = { { width : 24 , height : 24 } } >
116
+ < Autorenew />
117
+ </ Avatar >
118
+ </ StyledRefreshButton >
119
+ ) }
91
120
{ renderAlertBar ( ) }
92
121
</ Stack >
93
122
) ;
94
123
} ;
95
124
125
+ const spin = keyframes `
126
+ from {
127
+ transform : rotate (0deg );
128
+ }
129
+ to {
130
+ transform : rotate (360deg );
131
+ }
132
+ ` ;
133
+ const StyledRefreshButton = styled . div < { loading ?: boolean } > `
134
+ position: absolute;
135
+ right: 0;
136
+ top: 0;
137
+ margin: 0.5rem !important;
138
+ pointer-events: ${ ( { loading } ) => ( loading ? 'none' : 'auto' ) } ;
139
+ opacity: ${ ( { loading } ) => ( loading ? '0.6' : '1' ) } ;
140
+ cursor: ${ ( { loading } ) => ( loading ? 'not-allowed' : 'pointer' ) } ;
141
+ svg {
142
+ width: 20px;
143
+ height: 20px;
144
+ animation: ${ ( { loading } ) =>
145
+ loading
146
+ ? css `
147
+ ${ spin } 2s linear infinite
148
+ `
149
+ : 'none' } ;
150
+ }
151
+ :hover {
152
+ svg {
153
+ path {
154
+ fill: ${ purple [ 500 ] } ;
155
+ }
156
+ }
157
+ .MuiAvatar-circular {
158
+ background-color: ${ purple [ 50 ] } ;
159
+ }
160
+ }
161
+ ` ;
162
+
96
163
export default DisplayRandomPicture ;
0 commit comments