Translate SettingsPage
This commit is contained in:
parent
76943fb1f0
commit
fe7d7ce274
|
@ -1,178 +1,208 @@
|
|||
import React from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {Message, Icon, Grid, Form, Button, TextArea, Ref, Input, Header, Divider, Popup} from 'semantic-ui-react'
|
||||
import {useForm} from 'react-hook-form'
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
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 {Page, Stats} from 'components'
|
||||
import api from 'api'
|
||||
import {findInput} from 'utils'
|
||||
import {useConfig} from 'config'
|
||||
import { setLogin } from "reducers/login";
|
||||
import { Page, Stats } from "components";
|
||||
import api from "api";
|
||||
import { findInput } from "utils";
|
||||
import { useConfig } from "config";
|
||||
|
||||
const SettingsPage = connect((state) => ({login: state.login}), {setLogin})(function SettingsPage({login, setLogin}) {
|
||||
const {register, handleSubmit} = useForm()
|
||||
const [loading, setLoading] = React.useState(false)
|
||||
const [errors, setErrors] = React.useState(null)
|
||||
const SettingsPage = connect((state) => ({ login: state.login }), { setLogin })(
|
||||
function SettingsPage({ login, setLogin }) {
|
||||
const { t } = useTranslation();
|
||||
const { register, handleSubmit } = useForm();
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [errors, setErrors] = React.useState(null);
|
||||
|
||||
const onSave = React.useCallback(
|
||||
async (changes) => {
|
||||
setLoading(true)
|
||||
setErrors(null)
|
||||
const onSave = React.useCallback(
|
||||
async (changes) => {
|
||||
setLoading(true);
|
||||
setErrors(null);
|
||||
try {
|
||||
const response = await api.put("/user", { body: changes });
|
||||
setLogin(response);
|
||||
} catch (err) {
|
||||
setErrors(err.errors);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[setLoading, setLogin, setErrors]
|
||||
);
|
||||
|
||||
const onGenerateNewKey = React.useCallback(async () => {
|
||||
setLoading(true);
|
||||
setErrors(null);
|
||||
try {
|
||||
const response = await api.put('/user', {body: changes})
|
||||
setLogin(response)
|
||||
const response = await api.put("/user", {
|
||||
body: { updateApiKey: true },
|
||||
});
|
||||
setLogin(response);
|
||||
} catch (err) {
|
||||
setErrors(err.errors)
|
||||
setErrors(err.errors);
|
||||
} finally {
|
||||
setLoading(false)
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[setLoading, setLogin, setErrors]
|
||||
)
|
||||
}, [setLoading, setLogin, setErrors]);
|
||||
|
||||
const onGenerateNewKey = React.useCallback(async () => {
|
||||
setLoading(true)
|
||||
setErrors(null)
|
||||
try {
|
||||
const response = await api.put('/user', {body: {updateApiKey: true}})
|
||||
setLogin(response)
|
||||
} catch (err) {
|
||||
setErrors(err.errors)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [setLoading, setLogin, setErrors])
|
||||
return (
|
||||
<Page title={t("SettingsPage.title")}>
|
||||
<Grid centered relaxed divided stackable>
|
||||
<Grid.Row>
|
||||
<Grid.Column width={8}>
|
||||
<Header as="h2">{t("SettingsPage.profile.title")}</Header>
|
||||
|
||||
return (
|
||||
<Page title="Settings">
|
||||
<Grid centered relaxed divided stackable>
|
||||
<Grid.Row>
|
||||
<Grid.Column width={8}>
|
||||
<Header as="h2">My profile</Header>
|
||||
<Message info>{t("SettingsPage.profile.publicNotice")}</Message>
|
||||
|
||||
<Message info>All of this information is public.</Message>
|
||||
|
||||
<Form onSubmit={handleSubmit(onSave)} loading={loading}>
|
||||
<Ref innerRef={findInput(register)}>
|
||||
<Form.Input
|
||||
error={errors?.username}
|
||||
label="Username"
|
||||
name="username"
|
||||
defaultValue={login.username}
|
||||
disabled
|
||||
/>
|
||||
</Ref>
|
||||
<Form.Field error={errors?.bio}>
|
||||
<label>Bio</label>
|
||||
<Ref innerRef={register}>
|
||||
<TextArea name="bio" rows={4} defaultValue={login.bio} />
|
||||
</Ref>
|
||||
</Form.Field>
|
||||
<Form.Field error={errors?.image}>
|
||||
<label>Avatar URL</label>
|
||||
<Form onSubmit={handleSubmit(onSave)} loading={loading}>
|
||||
<Ref innerRef={findInput(register)}>
|
||||
<Input name="image" defaultValue={login.image} />
|
||||
<Form.Input
|
||||
error={errors?.username}
|
||||
label={t("SettingsPage.profile.username.label")}
|
||||
name="username"
|
||||
defaultValue={login.username}
|
||||
disabled
|
||||
/>
|
||||
</Ref>
|
||||
</Form.Field>
|
||||
<Form.Field error={errors?.bio}>
|
||||
<label>{t("SettingsPage.profile.bio.label")}</label>
|
||||
<Ref innerRef={register}>
|
||||
<TextArea name="bio" rows={4} defaultValue={login.bio} />
|
||||
</Ref>
|
||||
</Form.Field>
|
||||
<Form.Field error={errors?.image}>
|
||||
<label>{t("SettingsPage.profile.avatarUrl.label")}</label>
|
||||
<Ref innerRef={findInput(register)}>
|
||||
<Input name="image" defaultValue={login.image} />
|
||||
</Ref>
|
||||
</Form.Field>
|
||||
|
||||
<Button type="submit" primary>
|
||||
Save
|
||||
</Button>
|
||||
</Form>
|
||||
</Grid.Column>
|
||||
<Grid.Column width={6}>
|
||||
<ApiKeyDialog {...{login, onGenerateNewKey}} />
|
||||
<Button type="submit" primary>
|
||||
{t("general.save")}
|
||||
</Button>
|
||||
</Form>
|
||||
</Grid.Column>
|
||||
<Grid.Column width={6}>
|
||||
<ApiKeyDialog {...{ login, onGenerateNewKey }} />
|
||||
|
||||
<Divider />
|
||||
<Divider />
|
||||
|
||||
<Stats user={login.username} />
|
||||
</Grid.Column>
|
||||
</Grid.Row>
|
||||
</Grid>
|
||||
</Page>
|
||||
)
|
||||
})
|
||||
<Stats user={login.username} />
|
||||
</Grid.Column>
|
||||
</Grid.Row>
|
||||
</Grid>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
function CopyInput({value, ...props}) {
|
||||
const [success, setSuccess] = React.useState(null)
|
||||
function CopyInput({ value, ...props }) {
|
||||
const { t } = useTranslation();
|
||||
const [success, setSuccess] = React.useState(null);
|
||||
const onClick = async () => {
|
||||
try {
|
||||
await window.navigator?.clipboard?.writeText(value)
|
||||
setSuccess(true)
|
||||
await window.navigator?.clipboard?.writeText(value);
|
||||
setSuccess(true);
|
||||
} catch (err) {
|
||||
setSuccess(false)
|
||||
setSuccess(false);
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
setSuccess(null)
|
||||
}, 2000)
|
||||
setSuccess(null);
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Popup
|
||||
trigger={<Input {...props} value={value} fluid action={{icon: 'copy', onClick}} />}
|
||||
trigger={
|
||||
<Input
|
||||
{...props}
|
||||
value={value}
|
||||
fluid
|
||||
action={{ icon: "copy", onClick }}
|
||||
/>
|
||||
}
|
||||
position="top right"
|
||||
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}) {
|
||||
const config = useConfig()
|
||||
const [show, setShow] = React.useState(false)
|
||||
function ApiKeyDialog({ login, onGenerateNewKey }) {
|
||||
const { t } = useTranslation();
|
||||
const config = useConfig();
|
||||
const [show, setShow] = React.useState(false);
|
||||
const onClick = React.useCallback(
|
||||
(e) => {
|
||||
e.preventDefault()
|
||||
setShow(true)
|
||||
e.preventDefault();
|
||||
setShow(true);
|
||||
},
|
||||
[setShow]
|
||||
)
|
||||
);
|
||||
|
||||
const onGenerateNewKeyInner = React.useCallback(
|
||||
(e) => {
|
||||
e.preventDefault()
|
||||
onGenerateNewKey()
|
||||
e.preventDefault();
|
||||
onGenerateNewKey();
|
||||
},
|
||||
[onGenerateNewKey]
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header as="h2">My API Key</Header>
|
||||
<p>
|
||||
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}}>
|
||||
<Header as="h2">{t("SettingsPage.apiKey.title")}</Header>
|
||||
<Markdown>{t("SettingsPage.apiKey.description")}</Markdown>
|
||||
<div style={{ minHeight: 40, marginBottom: 16 }}>
|
||||
{show ? (
|
||||
login.apiKey ? (
|
||||
<Ref innerRef={selectField}>
|
||||
<CopyInput label="Personal API Key" value={login.apiKey} />
|
||||
<CopyInput
|
||||
label={t("SettingsPage.apiKey.key.label")}
|
||||
value={login.apiKey}
|
||||
/>
|
||||
</Ref>
|
||||
) : (
|
||||
<Message warning content="You have no API Key, please generate one below." />
|
||||
<Message warning content={t("SettingsPage.apiKey.key.empty")} />
|
||||
)
|
||||
) : (
|
||||
<Button onClick={onClick}>
|
||||
<Icon name="lock" /> Show API Key
|
||||
<Icon name="lock" /> {t("SettingsPage.apiKey.key.show")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p>The API URL should be set to:</p>
|
||||
<div style={{marginBottom: 16}}>
|
||||
<CopyInput label="API URL" value={config?.apiUrl?.replace(/\/api$/, '') ?? '...'} />
|
||||
<Markdown>{t("SettingsPage.apiKey.urlDescription")}</Markdown>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<CopyInput
|
||||
label={t("SettingsPage.apiKey.url.label")}
|
||||
value={config?.apiUrl?.replace(/\/api$/, "") ?? "..."}
|
||||
/>
|
||||
</div>
|
||||
<p>
|
||||
You can generate a new API Key here, which will invalidate the old one, disconnecting all devices you used it on
|
||||
from your account.
|
||||
</p>
|
||||
<Button onClick={onGenerateNewKeyInner}>Generate new API key</Button>
|
||||
<Markdown>{t("SettingsPage.apiKey.generateDescription")}</Markdown>
|
||||
<p></p>
|
||||
<Button onClick={onGenerateNewKeyInner}>
|
||||
{t("SettingsPage.apiKey.generate")}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default SettingsPage
|
||||
export default SettingsPage;
|
||||
|
|
|
@ -179,3 +179,45 @@ MapPage:
|
|||
southWest: südwestwärts
|
||||
west: westwä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
|
||||
|
||||
|
|
|
@ -11,6 +11,10 @@ general:
|
|||
private: Private
|
||||
show: Show
|
||||
edit: Edit
|
||||
save: Save
|
||||
|
||||
copied: Copied.
|
||||
copyError: Failed to copy.
|
||||
|
||||
App:
|
||||
footer:
|
||||
|
@ -183,3 +187,42 @@ MapPage:
|
|||
southWest: south-west bound
|
||||
west: 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
|
||||
|
||||
|
|
Loading…
Reference in a new issue