remove www directory, as it is a build artefact
replace custom made notification component with notistack
This commit is contained in:
		
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 8.7 KiB | 
| @@ -1,12 +0,0 @@ | ||||
| { | ||||
|   "name":"ESP8266 React", | ||||
|   "icons":[ | ||||
|     { | ||||
|       "src":"/app/icon.png", | ||||
|       "sizes":"48x48 72x72 96x96 128x128 256x256" | ||||
|     } | ||||
|   ], | ||||
|   "start_url":"/", | ||||
|   "display":"fullscreen", | ||||
|   "orientation":"any" | ||||
| } | ||||
| @@ -1,22 +0,0 @@ | ||||
| /* Just supporting latin due to size constrains on the esp chip */ | ||||
| @font-face { | ||||
|   font-family: 'Roboto'; | ||||
|   font-style: normal; | ||||
|   font-weight: 300; | ||||
|   src: local('Roboto Light'), local('Roboto-Light'), url(../fonts/ro-li.w2) format('woff2'); | ||||
|   unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215; | ||||
| } | ||||
| @font-face { | ||||
|   font-family: 'Roboto'; | ||||
|   font-style: normal; | ||||
|   font-weight: 400; | ||||
|   src: local('Roboto'), local('Roboto-Regular'), url(../fonts/ro-re.w2) format('woff2'); | ||||
|   unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215; | ||||
| } | ||||
| @font-face { | ||||
|   font-family: 'Roboto'; | ||||
|   font-style: normal; | ||||
|   font-weight: 500; | ||||
|   src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/ro-me.w2) format('woff2'); | ||||
|   unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215; | ||||
| } | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 1.1 KiB | 
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -1 +0,0 @@ | ||||
| <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><link rel="stylesheet" href="/css/roboto.css"><link rel="manifest" href="/app/manifest.json"><title>ESP8266 React</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script src="/js/1.b351.js"></script><script src="/js/2.182e.js"></script><script src="/js/0.9b2e.js"></script></body></html> | ||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1680
									
								
								interface/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1680
									
								
								interface/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -3,17 +3,18 @@ | ||||
|   "version": "0.1.0", | ||||
|   "private": true, | ||||
|   "dependencies": { | ||||
|     "@material-ui/core": "^4.0.2", | ||||
|     "@material-ui/icons": "^4.0.1", | ||||
|     "@material-ui/core": "^4.3.1", | ||||
|     "@material-ui/icons": "^4.2.1", | ||||
|     "compression-webpack-plugin": "^2.0.0", | ||||
|     "jwt-decode": "^2.2.0", | ||||
|     "moment": "^2.24.0", | ||||
|     "notistack": "^0.8.9", | ||||
|     "prop-types": "^15.7.2", | ||||
|     "react": "^16.8.6", | ||||
|     "react-dom": "^16.8.6", | ||||
|     "react-form-validator-core": "^0.6.3", | ||||
|     "react-jss": "^10.0.0-alpha.16", | ||||
|     "react-material-ui-form-validator": "^2.0.8", | ||||
|     "react-jss": "^10.0.0-alpha.23", | ||||
|     "react-material-ui-form-validator": "^2.0.9", | ||||
|     "react-router": "^5.0.1", | ||||
|     "react-router-dom": "^5.0.1", | ||||
|     "react-scripts": "3.0.1" | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import React, { Component } from 'react'; | ||||
| import { Redirect, Route, Switch } from 'react-router'; | ||||
|  | ||||
| import AppRouting from './AppRouting'; | ||||
| import SnackbarNotification from './components/SnackbarNotification'; | ||||
| import { SnackbarProvider } from 'notistack'; | ||||
|  | ||||
| import CssBaseline from '@material-ui/core/CssBaseline'; | ||||
| import blueGrey from '@material-ui/core/colors/blueGrey'; | ||||
| @@ -35,20 +35,20 @@ const theme = createMuiTheme({ | ||||
| const jss = create(jssPreset()); | ||||
|  | ||||
| // this redirect forces a call to authenticationContext.refresh() which invalidates the JWT if it is invalid. | ||||
| const unauthorizedRedirect = () =>  <Redirect to="/" />; | ||||
| const unauthorizedRedirect = () => <Redirect to="/" />; | ||||
|  | ||||
| class App extends Component { | ||||
|   render() { | ||||
|     return ( | ||||
|       <StylesProvider jss={jss}> | ||||
|         <MuiThemeProvider theme={theme}> | ||||
|           <SnackbarNotification> | ||||
|           <SnackbarProvider maxSnack={3}> | ||||
|             <CssBaseline /> | ||||
|             <Switch> | ||||
|               <Route exact path="/unauthorized" component={unauthorizedRedirect} /> | ||||
|               <Route component={AppRouting} /> | ||||
|             </Switch> | ||||
|           </SnackbarNotification> | ||||
|           </SnackbarProvider> | ||||
|         </MuiThemeProvider> | ||||
|       </StylesProvider> | ||||
|     ); | ||||
|   | ||||
| @@ -5,12 +5,12 @@ import { | ||||
|  | ||||
| import { withAuthenticationContext } from './Context.js'; | ||||
| import * as Authentication from './Authentication'; | ||||
| import { withNotifier } from '../components/SnackbarNotification'; | ||||
| import { withSnackbar } from 'notistack'; | ||||
|  | ||||
| export class AuthenticatedRoute extends React.Component { | ||||
|  | ||||
|   render() { | ||||
|     const { raiseNotification, authenticationContext, component: Component, ...rest } = this.props; | ||||
|     const { enqueueSnackbar, authenticationContext, component: Component, ...rest } = this.props; | ||||
|     const { location } = this.props; | ||||
|     const renderComponent = (props) => { | ||||
|       if (authenticationContext.isAuthenticated()) { | ||||
| @@ -19,7 +19,9 @@ export class AuthenticatedRoute extends React.Component { | ||||
|         ); | ||||
|       } | ||||
|       Authentication.storeLoginRedirect(location); | ||||
|       raiseNotification("Please log in to continue."); | ||||
|       enqueueSnackbar("Please log in to continue.", { | ||||
|         variant: 'info', | ||||
|       }); | ||||
|       return ( | ||||
|         <Redirect to='/' /> | ||||
|       ); | ||||
| @@ -31,4 +33,4 @@ export class AuthenticatedRoute extends React.Component { | ||||
|  | ||||
| } | ||||
|  | ||||
| export default withNotifier(withAuthenticationContext(AuthenticatedRoute)); | ||||
| export default withSnackbar(withAuthenticationContext(AuthenticatedRoute)); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import * as React from 'react'; | ||||
| import history from '../history' | ||||
| import { withNotifier } from '../components/SnackbarNotification'; | ||||
| import { withSnackbar } from 'notistack'; | ||||
| import { VERIFY_AUTHORIZATION_ENDPOINT } from '../constants/Endpoints'; | ||||
| import { ACCESS_TOKEN, authorizedFetch } from './Authentication'; | ||||
| import { AuthenticationContext } from './Context'; | ||||
| @@ -80,7 +80,9 @@ class AuthenticationWrapper extends React.Component { | ||||
|           this.setState({ initialized: true, context: { ...this.state.context, user } }); | ||||
|         }).catch(error => { | ||||
|           this.setState({ initialized: true, context: { ...this.state.context, user: undefined } }); | ||||
|           this.props.raiseNotification("Error verifying authorization: " + error.message); | ||||
|           this.props.enqueueSnackbar("Error verifying authorization: " + error.message, { | ||||
|             variant: 'error', | ||||
|           }); | ||||
|         }); | ||||
|     } else { | ||||
|       this.setState({ initialized: true, context: { ...this.state.context, user: undefined } }); | ||||
| @@ -90,7 +92,11 @@ class AuthenticationWrapper extends React.Component { | ||||
|   signIn = (accessToken) => { | ||||
|     try { | ||||
|       localStorage.setItem(ACCESS_TOKEN, accessToken); | ||||
|       this.setState({ context: { ...this.state.context, user: jwtDecode(accessToken) } }); | ||||
|       const user = jwtDecode(accessToken); | ||||
|       this.setState({ context: { ...this.state.context, user } }); | ||||
|       this.props.enqueueSnackbar(`Logged in as ${user.username}`, { | ||||
|         variant: 'success', | ||||
|       }); | ||||
|     } catch (err) { | ||||
|       this.setState({ initialized: true, context: { ...this.state.context, user: undefined } }); | ||||
|       throw new Error("Failed to parse JWT " + err.message); | ||||
| @@ -105,7 +111,7 @@ class AuthenticationWrapper extends React.Component { | ||||
|         user: undefined | ||||
|       } | ||||
|     }); | ||||
|     this.props.raiseNotification("You have signed out."); | ||||
|     this.props.enqueueSnackbar("You have signed out."); | ||||
|     history.push('/'); | ||||
|   } | ||||
|  | ||||
| @@ -120,4 +126,4 @@ class AuthenticationWrapper extends React.Component { | ||||
|  | ||||
| } | ||||
|  | ||||
| export default withStyles(styles)(withNotifier(AuthenticationWrapper)) | ||||
| export default withStyles(styles)(withSnackbar(AuthenticationWrapper)) | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import React from 'react'; | ||||
| import { withNotifier } from '../components/SnackbarNotification'; | ||||
| import { withSnackbar } from 'notistack'; | ||||
| import { redirectingAuthorizedFetch } from '../authentication/Authentication'; | ||||
| /* | ||||
| * It is unlikely this application will grow complex enough to require redux. | ||||
| @@ -10,7 +10,7 @@ import { redirectingAuthorizedFetch } from '../authentication/Authentication'; | ||||
| */ | ||||
| export const restComponent = (endpointUrl, FormComponent) => { | ||||
|  | ||||
|   return withNotifier( | ||||
|   return withSnackbar( | ||||
|     class extends React.Component { | ||||
|  | ||||
|       constructor(props) { | ||||
| @@ -51,7 +51,9 @@ export const restComponent = (endpointUrl, FormComponent) => { | ||||
|           }) | ||||
|           .then(json => { this.setState({ data: json, fetched: true }) }) | ||||
|           .catch(error => { | ||||
|             this.props.raiseNotification("Problem fetching: " + error.message); | ||||
|             this.props.enqueueSnackbar("Problem fetching: " + error.message, { | ||||
|               variant: 'error', | ||||
|             }); | ||||
|             this.setState({ data: null, fetched: true, errorMessage: error.message }); | ||||
|           }); | ||||
|       } | ||||
| @@ -72,10 +74,14 @@ export const restComponent = (endpointUrl, FormComponent) => { | ||||
|             throw Error("Invalid status code: " + response.status); | ||||
|           }) | ||||
|           .then(json => { | ||||
|             this.props.raiseNotification("Changes successfully applied."); | ||||
|             this.props.enqueueSnackbar("Changes successfully applied.", { | ||||
|               variant: 'success', | ||||
|             }); | ||||
|             this.setState({ data: json, fetched: true }); | ||||
|           }).catch(error => { | ||||
|             this.props.raiseNotification("Problem saving: " + error.message); | ||||
|             this.props.enqueueSnackbar("Problem saving: " + error.message, { | ||||
|               variant: 'error', | ||||
|             }); | ||||
|             this.setState({ data: null, fetched: true, errorMessage: error.message }); | ||||
|           }); | ||||
|       } | ||||
|   | ||||
| @@ -1,93 +0,0 @@ | ||||
| import React, {Fragment} from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { withStyles } from '@material-ui/core/styles'; | ||||
| import Snackbar from '@material-ui/core/Snackbar'; | ||||
| import IconButton from '@material-ui/core/IconButton'; | ||||
| import CloseIcon from '@material-ui/icons/Close'; | ||||
|  | ||||
| const styles = theme => ({ | ||||
|   close: { | ||||
|     padding: theme.spacing(0.5), | ||||
|   }, | ||||
| }); | ||||
|  | ||||
| class SnackbarNotification extends React.Component { | ||||
|  | ||||
|   constructor(props) { | ||||
|     super(props); | ||||
|     this.raiseNotification=this.raiseNotification.bind(this); | ||||
|   } | ||||
|  | ||||
|   static childContextTypes = { | ||||
|     raiseNotification: PropTypes.func.isRequired | ||||
|   } | ||||
|  | ||||
|   getChildContext = () => { | ||||
|     return {raiseNotification : this.raiseNotification}; | ||||
|   }; | ||||
|  | ||||
|   state = { | ||||
|     open: false, | ||||
|     message: null | ||||
|   }; | ||||
|  | ||||
|   raiseNotification = (message) => { | ||||
|     this.setState({ open: true, message:message }); | ||||
|   }; | ||||
|  | ||||
|   handleClose = (event, reason) => { | ||||
|     if (reason === 'clickaway') { | ||||
|       return; | ||||
|     } | ||||
|     this.setState({ open: false }); | ||||
|   }; | ||||
|  | ||||
|   render() { | ||||
|     const { classes } = this.props; | ||||
|     return ( | ||||
|       <Fragment> | ||||
|         <Snackbar | ||||
|           anchorOrigin={{ | ||||
|             vertical: 'bottom', | ||||
|             horizontal: 'left', | ||||
|           }} | ||||
|           open={this.state.open} | ||||
|           autoHideDuration={6000} | ||||
|           onClose={this.handleClose} | ||||
|           ContentProps={{ | ||||
|             'aria-describedby': 'message-id', | ||||
|           }} | ||||
|           message={<span id="message-id">{this.state.message}</span>} | ||||
|           action={ | ||||
|             <IconButton | ||||
|               aria-label="Close" | ||||
|               color="inherit" | ||||
|               className={classes.close} | ||||
|               onClick={this.handleClose} | ||||
|             > | ||||
|               <CloseIcon /> | ||||
|             </IconButton> | ||||
|             } | ||||
|           /> | ||||
|           {this.props.children} | ||||
|         </Fragment> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| SnackbarNotification.propTypes = { | ||||
|   classes: PropTypes.object.isRequired | ||||
| }; | ||||
|  | ||||
| export default withStyles(styles)(SnackbarNotification); | ||||
|  | ||||
| export function withNotifier(WrappedComponent) { | ||||
|   return class extends React.Component { | ||||
|     static contextTypes = { | ||||
|       raiseNotification: PropTypes.func.isRequired | ||||
|     }; | ||||
|     render() { | ||||
|       return <WrappedComponent raiseNotification={this.context.raiseNotification} {...this.props} />; | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| @@ -6,7 +6,7 @@ import Typography from '@material-ui/core/Typography'; | ||||
| import Fab from '@material-ui/core/Fab'; | ||||
| import { APP_NAME } from '../constants/App'; | ||||
| import ForwardIcon from '@material-ui/icons/Forward'; | ||||
| import { withNotifier } from '../components/SnackbarNotification'; | ||||
| import { withSnackbar } from 'notistack'; | ||||
| import { SIGN_IN_ENDPOINT } from '../constants/Endpoints'; | ||||
| import { withAuthenticationContext } from '../authentication/Context'; | ||||
| import PasswordValidator from '../components/PasswordValidator'; | ||||
| @@ -84,7 +84,9 @@ class SignInPage extends Component { | ||||
|         authenticationContext.signIn(json.access_token); | ||||
|       }) | ||||
|       .catch(error => { | ||||
|         this.props.raiseNotification(error.message); | ||||
|         this.props.enqueueSnackbar(error.message, { | ||||
|           variant: 'warning', | ||||
|         }); | ||||
|         this.setState({ processing: false }); | ||||
|       }); | ||||
|   }; | ||||
| @@ -132,5 +134,5 @@ class SignInPage extends Component { | ||||
| } | ||||
|  | ||||
| export default withAuthenticationContext( | ||||
|   withNotifier(withStyles(styles)(SignInPage)) | ||||
|   withSnackbar(withStyles(styles)(SignInPage)) | ||||
| ); | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import React, { Component } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
|  | ||||
| import { SCAN_NETWORKS_ENDPOINT, LIST_NETWORKS_ENDPOINT }  from  '../constants/Endpoints'; | ||||
| import { SCAN_NETWORKS_ENDPOINT, LIST_NETWORKS_ENDPOINT } from '../constants/Endpoints'; | ||||
| import SectionContent from '../components/SectionContent'; | ||||
| import WiFiNetworkSelector from '../forms/WiFiNetworkSelector'; | ||||
| import {withNotifier} from '../components/SnackbarNotification'; | ||||
| import { withSnackbar } from 'notistack'; | ||||
| import { redirectingAuthorizedFetch } from '../authentication/Authentication'; | ||||
|  | ||||
| const NUM_POLLS = 10 | ||||
| @@ -17,10 +17,10 @@ class WiFiNetworkScanner extends Component { | ||||
|     super(props); | ||||
|     this.pollCount = 0; | ||||
|     this.state = { | ||||
|                    scanningForNetworks: true, | ||||
|                    errorMessage:null, | ||||
|                    networkList: null | ||||
|                  }; | ||||
|       scanningForNetworks: true, | ||||
|       errorMessage: null, | ||||
|       networkList: null | ||||
|     }; | ||||
|     this.pollNetworkList = this.pollNetworkList.bind(this); | ||||
|     this.requestNetworkScan = this.requestNetworkScan.bind(this); | ||||
|   } | ||||
| @@ -38,7 +38,7 @@ class WiFiNetworkScanner extends Component { | ||||
|  | ||||
|   scanNetworks() { | ||||
|     this.pollCount = 0; | ||||
|     this.setState({scanningForNetworks:true, networkList: null, errorMessage:null}); | ||||
|     this.setState({ scanningForNetworks: true, networkList: null, errorMessage: null }); | ||||
|     redirectingAuthorizedFetch(SCAN_NETWORKS_ENDPOINT).then(response => { | ||||
|       if (response.status === 202) { | ||||
|         this.schedulePollTimeout(); | ||||
| @@ -46,8 +46,10 @@ class WiFiNetworkScanner extends Component { | ||||
|       } | ||||
|       throw Error("Scanning for networks returned unexpected response code: " + response.status); | ||||
|     }).catch(error => { | ||||
|         this.props.raiseNotification("Problem scanning: " + error.message); | ||||
|         this.setState({scanningForNetworks:false, networkList: null, errorMessage:error.message}); | ||||
|       this.props.enqueueSnackbar("Problem scanning: " + error.message, { | ||||
|         variant: 'error', | ||||
|       }); | ||||
|       this.setState({ scanningForNetworks: false, networkList: null, errorMessage: error.message }); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
| @@ -57,12 +59,12 @@ class WiFiNetworkScanner extends Component { | ||||
|  | ||||
|   retryError() { | ||||
|     return { | ||||
|       name:RETRY_EXCEPTION_TYPE, | ||||
|       message:"Network list not ready, will retry in " + POLLING_FREQUENCY + "ms." | ||||
|       name: RETRY_EXCEPTION_TYPE, | ||||
|       message: "Network list not ready, will retry in " + POLLING_FREQUENCY + "ms." | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   compareNetworks(network1,network2) { | ||||
|   compareNetworks(network1, network2) { | ||||
|     if (network1.rssi < network2.rssi) | ||||
|       return 1; | ||||
|     if (network1.rssi > network2.rssi) | ||||
| @@ -72,30 +74,32 @@ class WiFiNetworkScanner extends Component { | ||||
|  | ||||
|   pollNetworkList() { | ||||
|     redirectingAuthorizedFetch(LIST_NETWORKS_ENDPOINT) | ||||
|     .then(response => { | ||||
|       if (response.status === 200) { | ||||
|         return response.json(); | ||||
|       } | ||||
|       if (response.status === 202) { | ||||
|         if (++this.pollCount < NUM_POLLS){ | ||||
|           this.schedulePollTimeout(); | ||||
|           throw this.retryError(); | ||||
|         }else{ | ||||
|           throw Error("Device did not return network list in timely manner."); | ||||
|       .then(response => { | ||||
|         if (response.status === 200) { | ||||
|           return response.json(); | ||||
|         } | ||||
|       } | ||||
|       throw Error("Device returned unexpected response code: " + response.status); | ||||
|     }) | ||||
|     .then(json => { | ||||
|         if (response.status === 202) { | ||||
|           if (++this.pollCount < NUM_POLLS) { | ||||
|             this.schedulePollTimeout(); | ||||
|             throw this.retryError(); | ||||
|           } else { | ||||
|             throw Error("Device did not return network list in timely manner."); | ||||
|           } | ||||
|         } | ||||
|         throw Error("Device returned unexpected response code: " + response.status); | ||||
|       }) | ||||
|       .then(json => { | ||||
|         json.networks.sort(this.compareNetworks) | ||||
|         this.setState({scanningForNetworks:false, networkList: json, errorMessage:null}) | ||||
|     }) | ||||
|     .catch(error => { | ||||
|       if (error.name !== RETRY_EXCEPTION_TYPE) { | ||||
|         this.props.raiseNotification("Problem scanning: " + error.message); | ||||
|         this.setState({scanningForNetworks:false, networkList: null, errorMessage:error.message}); | ||||
|       } | ||||
|     }); | ||||
|         this.setState({ scanningForNetworks: false, networkList: json, errorMessage: null }) | ||||
|       }) | ||||
|       .catch(error => { | ||||
|         if (error.name !== RETRY_EXCEPTION_TYPE) { | ||||
|           this.props.enqueueSnackbar("Problem scanning: " + error.message, { | ||||
|             variant: 'error', | ||||
|           }); | ||||
|           this.setState({ scanningForNetworks: false, networkList: null, errorMessage: error.message }); | ||||
|         } | ||||
|       }); | ||||
|   } | ||||
|  | ||||
|   render() { | ||||
| @@ -103,11 +107,11 @@ class WiFiNetworkScanner extends Component { | ||||
|     return ( | ||||
|       <SectionContent title="Network Scanner"> | ||||
|         <WiFiNetworkSelector scanningForNetworks={scanningForNetworks} | ||||
|                              networkList={networkList} | ||||
|                              errorMessage={errorMessage} | ||||
|                              requestNetworkScan={this.requestNetworkScan} | ||||
|                              selectNetwork={this.props.selectNetwork} | ||||
|                              /> | ||||
|           networkList={networkList} | ||||
|           errorMessage={errorMessage} | ||||
|           requestNetworkScan={this.requestNetworkScan} | ||||
|           selectNetwork={this.props.selectNetwork} | ||||
|         /> | ||||
|       </SectionContent> | ||||
|     ) | ||||
|   } | ||||
| @@ -118,4 +122,4 @@ WiFiNetworkScanner.propTypes = { | ||||
|   selectNetwork: PropTypes.func.isRequired | ||||
| }; | ||||
|  | ||||
| export default withNotifier(WiFiNetworkScanner); | ||||
| export default withSnackbar(WiFiNetworkScanner); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user