Skip to content

Commit

Permalink
experiment with react-intl
Browse files Browse the repository at this point in the history
  • Loading branch information
rjwats committed Sep 9, 2020
1 parent 2ed5d26 commit 162e03c
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 26 deletions.
4 changes: 2 additions & 2 deletions interface/.env.development
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Change the IP address to that of your ESP device to enable local development of the UI.
# Remember to also enable CORS in platformio.ini before uploading the code to the device.
REACT_APP_HTTP_ROOT=http://192.168.0.88
REACT_APP_WEB_SOCKET_ROOT=ws://192.168.0.88
REACT_APP_HTTP_ROOT=http://192.168.0.7
REACT_APP_WEB_SOCKET_ROOT=ws://192.168.0.7
97 changes: 97 additions & 0 deletions interface/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions interface/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"react-dom": "^16.13.1",
"react-dropzone": "^11.0.1",
"react-form-validator-core": "^0.6.4",
"react-intl": "^5.8.1",
"react-material-ui-form-validator": "^2.0.10",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
Expand Down
15 changes: 9 additions & 6 deletions interface/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import AppRouting from './AppRouting';
import CustomMuiTheme from './CustomMuiTheme';
import { PROJECT_NAME } from './api';
import FeaturesWrapper from './features/FeaturesWrapper';
import I18n from './i18n/I18n';

// this redirect forces a call to authenticationContext.refresh() which invalidates the JWT if it is invalid.
const unauthorizedRedirect = () => <Redirect to="/" />;
Expand All @@ -35,12 +36,14 @@ class App extends Component {
<CloseIcon />
</IconButton>
)}>
<FeaturesWrapper>
<Switch>
<Route exact path="/unauthorized" component={unauthorizedRedirect} />
<Route component={AppRouting} />
</Switch>
</FeaturesWrapper>
<I18n>
<FeaturesWrapper>
<Switch>
<Route exact path="/unauthorized" component={unauthorizedRedirect} />
<Route component={AppRouting} />
</Switch>
</FeaturesWrapper>
</I18n>
</SnackbarProvider>
</CustomMuiTheme>
);
Expand Down
30 changes: 18 additions & 12 deletions interface/src/SignIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { Paper, Typography, Fab } from '@material-ui/core';
import ForwardIcon from '@material-ui/icons/Forward';

import { withAuthenticationContext, AuthenticationContextProps } from './authentication/AuthenticationContext';
import {PasswordValidator} from './components';
import { PasswordValidator } from './components';
import { PROJECT_NAME, SIGN_IN_ENDPOINT } from './api';
import { FormattedMessage, injectIntl, WrappedComponentProps } from 'react-intl';

const styles = (theme: Theme) => createStyles({
signInPage: {
Expand Down Expand Up @@ -39,7 +40,7 @@ const styles = (theme: Theme) => createStyles({
}
});

type SignInProps = WithSnackbarProps & WithStyles<typeof styles> & AuthenticationContextProps;
type SignInProps = WrappedComponentProps & WithSnackbarProps & WithStyles<typeof styles> & AuthenticationContextProps;

interface SignInState {
username: string,
Expand Down Expand Up @@ -68,7 +69,7 @@ class SignIn extends Component<SignInProps, SignInState> {

onSubmit = () => {
const { username, password } = this.state;
const { authenticationContext } = this.props;
const { authenticationContext, intl } = this.props;
this.setState({ processing: true });
fetch(SIGN_IN_ENDPOINT, {
method: 'POST',
Expand All @@ -81,9 +82,14 @@ class SignIn extends Component<SignInProps, SignInState> {
if (response.status === 200) {
return response.json();
} else if (response.status === 401) {
throw Error("Invalid credentials.");
throw Error(intl.formatMessage(
{ id: 'signIn.invalidCredentials', defaultMessage: 'Invalid credentials' })
);
} else {
throw Error("Invalid status code: " + response.status);
throw Error(intl.formatMessage(
{ id: 'signIn.invalidStatusCode', defaultMessage: 'Invalid status code: {code}' },
{ code: response.status })
);
}
}).then(json => {
authenticationContext.signIn(json.access_token);
Expand All @@ -98,7 +104,7 @@ class SignIn extends Component<SignInProps, SignInState> {

render() {
const { username, password, processing } = this.state;
const { classes } = this.props;
const { classes, intl } = this.props;
return (
<div className={classes.signInPage}>
<Paper className={classes.signInPanel}>
Expand All @@ -107,9 +113,9 @@ class SignIn extends Component<SignInProps, SignInState> {
<TextValidator
disabled={processing}
validators={['required']}
errorMessages={['Username is required']}
errorMessages={[intl.formatMessage({ id: 'signIn.usernameRequired', defaultMessage: 'Username is required' })]}
name="username"
label="Username"
label={[intl.formatMessage({ id: 'signIn.username', defaultMessage: 'Username' })]}
fullWidth
variant="outlined"
value={username}
Expand All @@ -123,9 +129,9 @@ class SignIn extends Component<SignInProps, SignInState> {
<PasswordValidator
disabled={processing}
validators={['required']}
errorMessages={['Password is required']}
errorMessages={[intl.formatMessage({ id: 'signIn.passwordRequired', defaultMessage: 'Password is required' })]}
name="password"
label="Password"
label={[intl.formatMessage({ id: 'signIn.password', defaultMessage: 'Password' })]}
fullWidth
variant="outlined"
value={password}
Expand All @@ -134,7 +140,7 @@ class SignIn extends Component<SignInProps, SignInState> {
/>
<Fab variant="extended" color="primary" className={classes.button} type="submit" disabled={processing}>
<ForwardIcon className={classes.extendedIcon} />
Sign In
<FormattedMessage id="signIn.signIn" defaultMessage="Sign In" />
</Fab>
</ValidatorForm>
</Paper>
Expand All @@ -144,4 +150,4 @@ class SignIn extends Component<SignInProps, SignInState> {

}

export default withAuthenticationContext(withSnackbar(withStyles(styles)(SignIn)));
export default injectIntl(withAuthenticationContext(withSnackbar(withStyles(styles)(SignIn))));
45 changes: 45 additions & 0 deletions interface/src/i18n/I18n.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { Component } from 'react';
import { createIntl, createIntlCache, RawIntlProvider } from 'react-intl';
import { messages } from './messages';

const defaultLocale = "es";

const cache = createIntlCache()

export const intl = createIntl({
locale: defaultLocale,
defaultLocale: defaultLocale,
messages: messages[defaultLocale]
}, cache)

interface LanguageWrapperState {
locale: string;
};

class I18n extends Component<{}, LanguageWrapperState> {

state: LanguageWrapperState = { locale: defaultLocale };

// load locale from local storage here
componentDidMount = () => {

}

selectLanguage = (locale: string) => {
intl.locale = locale;
intl.messages = messages[locale];
this.setState({ locale })
}

render() {
const { locale } = this.state;
return (
<RawIntlProvider key={locale} value={intl}>
{this.props.children}
</RawIntlProvider>
);
}

}

export default I18n;
8 changes: 8 additions & 0 deletions interface/src/i18n/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { merge } from 'lodash';
import { signInMessages } from '../messages';
import { projectMessages } from '../project/messages';

export const messages: Record<string, Record<string, string>> = merge(
signInMessages,
projectMessages
);
11 changes: 11 additions & 0 deletions interface/src/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const signInMessages: Record<string, Record<string, string>> = {
es: {
"signIn.invalidCredentials": "Credenciales no válidas",
"signIn.invalidStatusCode": "Codigo invalido: {code}",
"signIn.password": "Contraseña",
"signIn.passwordRequired": "Se requiere contraseña",
"signIn.signIn": "Registrarse",
"signIn.username": "Nombre de usuario",
"signIn.usernameRequired": "Se requiere nombre de usuario"
}
};
28 changes: 22 additions & 6 deletions interface/src/project/DemoProject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,37 @@ import DemoInformation from './DemoInformation';
import LightStateRestController from './LightStateRestController';
import LightStateWebSocketController from './LightStateWebSocketController';
import LightMqttSettingsController from './LightMqttSettingsController';
import { WrappedComponentProps, injectIntl } from 'react-intl';

class DemoProject extends Component<RouteComponentProps> {
type DemoProjectProps = RouteComponentProps & WrappedComponentProps;

class DemoProject extends Component<DemoProjectProps> {

handleTabChange = (event: React.ChangeEvent<{}>, path: string) => {
this.props.history.push(path);
};

render() {
const { intl } = this.props;
return (
<MenuAppBar sectionTitle="Demo Project">
<Tabs value={this.props.match.url} onChange={this.handleTabChange} variant="fullWidth">
<Tab value={`/${PROJECT_PATH}/demo/information`} label="Information" />
<Tab value={`/${PROJECT_PATH}/demo/rest`} label="REST Controller" />
<Tab value={`/${PROJECT_PATH}/demo/socket`} label="WebSocket Controller" />
<Tab value={`/${PROJECT_PATH}/demo/mqtt`} label="MQTT Controller" />
<Tab value={`/${PROJECT_PATH}/demo/information`} label={intl.formatMessage({
id: 'project.information',
defaultMessage: 'Information'
})} />
<Tab value={`/${PROJECT_PATH}/demo/rest`} label={intl.formatMessage({
id: 'project.restController',
defaultMessage: 'REST Controller'
})} />
<Tab value={`/${PROJECT_PATH}/demo/socket`} label={intl.formatMessage({
id: 'project.webSocketController',
defaultMessage: 'WebSocket Controller'
})} />
<Tab value={`/${PROJECT_PATH}/demo/mqtt`} label={intl.formatMessage({
id: 'project.mqttController',
defaultMessage: 'MQTT Controller'
})} />
</Tabs>
<Switch>
<AuthenticatedRoute exact path={`/${PROJECT_PATH}/demo/information`} component={DemoInformation} />
Expand All @@ -40,4 +56,4 @@ class DemoProject extends Component<RouteComponentProps> {

}

export default DemoProject;
export default injectIntl(DemoProject);
Loading

0 comments on commit 162e03c

Please sign in to comment.