11/* eslint-disable @next/next/no-img-element */
22
33'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' ;
57import Avatar from '@mui/material/Avatar' ;
68import Button from '@mui/material/Button' ;
9+ import { purple } from '@mui/material/colors' ;
710import Stack from '@mui/material/Stack' ;
811import React , { useEffect , useState } from 'react' ;
912
@@ -12,42 +15,60 @@ import { useClientContext } from '@/hooks/useClientContext';
1215
1316import SubmitButton from '@/components/shared/SubmitButton' ;
1417
18+ import { getApiResponse } from '@/utils/shared/get-api-response' ;
19+
1520const DisplayRandomPicture = ( ) => {
1621 const [ imageUrl , setImageUrl ] = useState ( '' ) ;
17- const [ loading , setLoading ] = useState ( true ) ;
22+ const [ loading , setLoading ] = useState ( false ) ;
1823 const [ error , setError ] = useState ( '' ) ;
1924 const { fetchCount, updateClientCtx } = useClientContext ( ) ;
2025 const { setAlertBarProps, renderAlertBar } = useAlertBar ( ) ;
2126 const renderCountRef = React . useRef ( 0 ) ;
2227
2328 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+ }
2436 setLoading ( true ) ;
2537 setError ( '' ) ;
2638
2739 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' ) ;
3147 }
48+
3249 setImageUrl ( response . url ) ;
3350 updateClientCtx ( { fetchCount : fetchCount + 1 } ) ;
3451 setAlertBarProps ( {
3552 message : 'A random picture fetched successfully' ,
3653 severity : 'info' ,
3754 } ) ;
3855 } 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 ) ;
4060 setAlertBarProps ( {
41- message : 'Error fetching the image' ,
61+ message : errorMsg ,
4262 severity : 'error' ,
4363 } ) ;
64+ setLoading ( false ) ;
4465 } finally {
4566 setLoading ( false ) ;
4667 }
4768 } ;
4869
4970 useEffect ( ( ) => {
50- if ( renderCountRef . current === 0 ) {
71+ if ( renderCountRef . current === 0 && ! loading ) {
5172 fetchRandomPicture ( ) ;
5273 }
5374 renderCountRef . current += 1 ;
@@ -59,6 +80,7 @@ const DisplayRandomPicture = () => {
5980 justifyContent = 'center'
6081 alignItems = 'center'
6182 spacing = { 2 }
83+ sx = { { position : 'relative' , width : '300px' , margin : '0 auto' } }
6284 >
6385 { error && < p > { error } </ p > }
6486 { imageUrl && (
@@ -71,7 +93,7 @@ const DisplayRandomPicture = () => {
7193 ) }
7294 < div >
7395 { loading && < span > Loading...</ span > } Component Render Count:{ ' ' }
74- { renderCountRef . current }
96+ { renderCountRef . current + 1 }
7597 </ div >
7698
7799 < SubmitButton
@@ -88,9 +110,54 @@ const DisplayRandomPicture = () => {
88110 Get Another Picture
89111 </ Button >
90112 </ SubmitButton >
113+ { imageUrl && (
114+ < StyledRefreshButton onClick = { fetchRandomPicture } loading = { loading } >
115+ < Avatar sx = { { width : 24 , height : 24 } } >
116+ < Autorenew />
117+ </ Avatar >
118+ </ StyledRefreshButton >
119+ ) }
91120 { renderAlertBar ( ) }
92121 </ Stack >
93122 ) ;
94123} ;
95124
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+
96163export default DisplayRandomPicture ;
0 commit comments