Translate SettingsPage

This commit is contained in:
Paul Bienkowski 2022-07-24 19:06:56 +02:00
parent 76943fb1f0
commit fe7d7ce274
3 changed files with 235 additions and 120 deletions

View file

@ -1,82 +1,100 @@
import React from 'react' import React from "react";
import {connect} from 'react-redux' import { connect } from "react-redux";
import {Message, Icon, Grid, Form, Button, TextArea, Ref, Input, Header, Divider, Popup} from 'semantic-ui-react' import {
import {useForm} from 'react-hook-form' Message,
Icon,
Grid,
Form,
Button,
TextArea,
Ref,
Input,
Header,
Divider,
Popup,
} from "semantic-ui-react";
import { useForm } from "react-hook-form";
import Markdown from "react-markdown";
import { useTranslation } from "react-i18next";
import {setLogin} from 'reducers/login' import { setLogin } from "reducers/login";
import {Page, Stats} from 'components' import { Page, Stats } from "components";
import api from 'api' import api from "api";
import {findInput} from 'utils' import { findInput } from "utils";
import {useConfig} from 'config' import { useConfig } from "config";
const SettingsPage = connect((state) => ({login: state.login}), {setLogin})(function SettingsPage({login, setLogin}) { const SettingsPage = connect((state) => ({ login: state.login }), { setLogin })(
const {register, handleSubmit} = useForm() function SettingsPage({ login, setLogin }) {
const [loading, setLoading] = React.useState(false) const { t } = useTranslation();
const [errors, setErrors] = React.useState(null) const { register, handleSubmit } = useForm();
const [loading, setLoading] = React.useState(false);
const [errors, setErrors] = React.useState(null);
const onSave = React.useCallback( const onSave = React.useCallback(
async (changes) => { async (changes) => {
setLoading(true) setLoading(true);
setErrors(null) setErrors(null);
try { try {
const response = await api.put('/user', {body: changes}) const response = await api.put("/user", { body: changes });
setLogin(response) setLogin(response);
} catch (err) { } catch (err) {
setErrors(err.errors) setErrors(err.errors);
} finally { } finally {
setLoading(false) setLoading(false);
} }
}, },
[setLoading, setLogin, setErrors] [setLoading, setLogin, setErrors]
) );
const onGenerateNewKey = React.useCallback(async () => { const onGenerateNewKey = React.useCallback(async () => {
setLoading(true) setLoading(true);
setErrors(null) setErrors(null);
try { try {
const response = await api.put('/user', {body: {updateApiKey: true}}) const response = await api.put("/user", {
setLogin(response) body: { updateApiKey: true },
});
setLogin(response);
} catch (err) { } catch (err) {
setErrors(err.errors) setErrors(err.errors);
} finally { } finally {
setLoading(false) setLoading(false);
} }
}, [setLoading, setLogin, setErrors]) }, [setLoading, setLogin, setErrors]);
return ( return (
<Page title="Settings"> <Page title={t("SettingsPage.title")}>
<Grid centered relaxed divided stackable> <Grid centered relaxed divided stackable>
<Grid.Row> <Grid.Row>
<Grid.Column width={8}> <Grid.Column width={8}>
<Header as="h2">My profile</Header> <Header as="h2">{t("SettingsPage.profile.title")}</Header>
<Message info>All of this information is public.</Message> <Message info>{t("SettingsPage.profile.publicNotice")}</Message>
<Form onSubmit={handleSubmit(onSave)} loading={loading}> <Form onSubmit={handleSubmit(onSave)} loading={loading}>
<Ref innerRef={findInput(register)}> <Ref innerRef={findInput(register)}>
<Form.Input <Form.Input
error={errors?.username} error={errors?.username}
label="Username" label={t("SettingsPage.profile.username.label")}
name="username" name="username"
defaultValue={login.username} defaultValue={login.username}
disabled disabled
/> />
</Ref> </Ref>
<Form.Field error={errors?.bio}> <Form.Field error={errors?.bio}>
<label>Bio</label> <label>{t("SettingsPage.profile.bio.label")}</label>
<Ref innerRef={register}> <Ref innerRef={register}>
<TextArea name="bio" rows={4} defaultValue={login.bio} /> <TextArea name="bio" rows={4} defaultValue={login.bio} />
</Ref> </Ref>
</Form.Field> </Form.Field>
<Form.Field error={errors?.image}> <Form.Field error={errors?.image}>
<label>Avatar URL</label> <label>{t("SettingsPage.profile.avatarUrl.label")}</label>
<Ref innerRef={findInput(register)}> <Ref innerRef={findInput(register)}>
<Input name="image" defaultValue={login.image} /> <Input name="image" defaultValue={login.image} />
</Ref> </Ref>
</Form.Field> </Form.Field>
<Button type="submit" primary> <Button type="submit" primary>
Save {t("general.save")}
</Button> </Button>
</Form> </Form>
</Grid.Column> </Grid.Column>
@ -90,89 +108,101 @@ const SettingsPage = connect((state) => ({login: state.login}), {setLogin})(func
</Grid.Row> </Grid.Row>
</Grid> </Grid>
</Page> </Page>
) );
}) }
);
function CopyInput({ value, ...props }) { function CopyInput({ value, ...props }) {
const [success, setSuccess] = React.useState(null) const { t } = useTranslation();
const [success, setSuccess] = React.useState(null);
const onClick = async () => { const onClick = async () => {
try { try {
await window.navigator?.clipboard?.writeText(value) await window.navigator?.clipboard?.writeText(value);
setSuccess(true) setSuccess(true);
} catch (err) { } catch (err) {
setSuccess(false) setSuccess(false);
} finally { } finally {
setTimeout(() => { setTimeout(() => {
setSuccess(null) setSuccess(null);
}, 2000) }, 2000);
}
} }
};
return ( return (
<Popup <Popup
trigger={<Input {...props} value={value} fluid action={{icon: 'copy', onClick}} />} trigger={
<Input
{...props}
value={value}
fluid
action={{ icon: "copy", onClick }}
/>
}
position="top right" position="top right"
open={success != null} open={success != null}
content={success ? 'Copied.' : 'Failed to copy.'} content={success ? t('general.copied') : t('general.copyError')}
/> />
) );
} }
const selectField = findInput((ref) => ref?.select()) const selectField = findInput((ref) => ref?.select());
function ApiKeyDialog({ login, onGenerateNewKey }) { function ApiKeyDialog({ login, onGenerateNewKey }) {
const config = useConfig() const { t } = useTranslation();
const [show, setShow] = React.useState(false) const config = useConfig();
const [show, setShow] = React.useState(false);
const onClick = React.useCallback( const onClick = React.useCallback(
(e) => { (e) => {
e.preventDefault() e.preventDefault();
setShow(true) setShow(true);
}, },
[setShow] [setShow]
) );
const onGenerateNewKeyInner = React.useCallback( const onGenerateNewKeyInner = React.useCallback(
(e) => { (e) => {
e.preventDefault() e.preventDefault();
onGenerateNewKey() onGenerateNewKey();
}, },
[onGenerateNewKey] [onGenerateNewKey]
) );
return ( return (
<> <>
<Header as="h2">My API Key</Header> <Header as="h2">{t("SettingsPage.apiKey.title")}</Header>
<p> <Markdown>{t("SettingsPage.apiKey.description")}</Markdown>
Here you find your API Key, for use in the OpenBikeSensor. You can to copy and paste it into your sensor's
configuration interface to allow direct upload from the device.
</p>
<p>Please protect your API Key carefully as it allows full control over your account.</p>
<div style={{ minHeight: 40, marginBottom: 16 }}> <div style={{ minHeight: 40, marginBottom: 16 }}>
{show ? ( {show ? (
login.apiKey ? ( login.apiKey ? (
<Ref innerRef={selectField}> <Ref innerRef={selectField}>
<CopyInput label="Personal API Key" value={login.apiKey} /> <CopyInput
label={t("SettingsPage.apiKey.key.label")}
value={login.apiKey}
/>
</Ref> </Ref>
) : ( ) : (
<Message warning content="You have no API Key, please generate one below." /> <Message warning content={t("SettingsPage.apiKey.key.empty")} />
) )
) : ( ) : (
<Button onClick={onClick}> <Button onClick={onClick}>
<Icon name="lock" /> Show API Key <Icon name="lock" /> {t("SettingsPage.apiKey.key.show")}
</Button> </Button>
)} )}
</div> </div>
<p>The API URL should be set to:</p> <Markdown>{t("SettingsPage.apiKey.urlDescription")}</Markdown>
<div style={{ marginBottom: 16 }}> <div style={{ marginBottom: 16 }}>
<CopyInput label="API URL" value={config?.apiUrl?.replace(/\/api$/, '') ?? '...'} /> <CopyInput
label={t("SettingsPage.apiKey.url.label")}
value={config?.apiUrl?.replace(/\/api$/, "") ?? "..."}
/>
</div> </div>
<p> <Markdown>{t("SettingsPage.apiKey.generateDescription")}</Markdown>
You can generate a new API Key here, which will invalidate the old one, disconnecting all devices you used it on <p></p>
from your account. <Button onClick={onGenerateNewKeyInner}>
</p> {t("SettingsPage.apiKey.generate")}
<Button onClick={onGenerateNewKeyInner}>Generate new API key</Button> </Button>
</> </>
) );
} }
export default SettingsPage export default SettingsPage;

View file

@ -179,3 +179,45 @@ MapPage:
southWest: südwestwärts southWest: südwestwärts
west: westwärts west: westwärts
northWest: nordwestwärts northWest: nordwestwärts
SettingsPage:
title: Einstellungen
profile:
title: Mein Profil
publicNotice: All diese Informationen sind öffentlich.
username:
label: Kontoname
bio:
label: Bio
avatarUrl:
label: Avatar URL
apiKey:
title: Mein API-Schlüssel
description: |
Hier findest du deinen API-Schlüssel für die Nutzung mit dem
OpenBikeSensor. Du kannst ihn dir herauskopieren und in der Seite für die
Einstellungen deines Geräts einfügen, um dann direkt vom Gerät aus
Fahrten hochladen zu können.
Bitte schütze deinen API-Schlüssel vor ungewolltem Zugriff, denn er
erlaubt Zugang zu deinem Konto auf dieser Seite.
urlDescription: |
Die API-URL sollte wie folgt gesetzt werden:
generateDescription: |
Hier kannst du einen neuen API-Schlüssel für dein Konto erstellen. Der
alte wird damit ungültig, und alle Geräte, die damit konfiguriert wurden,
können nicht mehr auf dein Konto zugreifen.
key:
label: Persönlicher API-Schlüssel
empty: Du hast keinen API-Schlüssel, kannst dir aber unten einen erstellen.
show: API-Schlüssel zeigen
url:
label: API URL
generate: Neuen API-Schlüssel erstellen

View file

@ -11,6 +11,10 @@ general:
private: Private private: Private
show: Show show: Show
edit: Edit edit: Edit
save: Save
copied: Copied.
copyError: Failed to copy.
App: App:
footer: footer:
@ -183,3 +187,42 @@ MapPage:
southWest: south-west bound southWest: south-west bound
west: west bound west: west bound
northWest: north-west bound northWest: north-west bound
SettingsPage:
title: Settings
profile:
title: My profile
publicNotice: All of this information is public.
username:
label: Username
bio:
label: Bio
avatarUrl:
label: Avatar URL
apiKey:
title: My API Key
description: |
Here you find your API Key, for use in the OpenBikeSensor. You can to
copy and paste it into your sensor's configuration interface to allow
direct upload from the device.
Please protect your API Key carefully as it allows full control over
your account.
urlDescription: |
The API URL should be set to:
generateDescription: |
You can generate a new API Key here, which will invalidate the old one,
disconnecting all devices you used it on from your account.
key:
label: Personal API Key
empty: You have no API Key, please generate one below.
show: Show API Key
url:
label: API URL
generate: Generate new API key