1
0
Fork 0
mirror of https://git.sr.ht/~ashkeel/strimertul synced 2024-09-18 01:50:50 +00:00

feat: add Reveal/Hide button on password fields

fixes #35
This commit is contained in:
Ash Keel 2022-12-07 10:46:48 +01:00
parent 59cfee3f28
commit 18adbc2397
No known key found for this signature in database
GPG key ID: BAD8D93E7314ED3E
8 changed files with 91 additions and 16 deletions

View file

@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added support for EventSub Websocket subscriptions on Twitch, making Twitch integration fully in-app without having to rely on third party servers. Check the "Events" tab in Twitch configuration for setting it up. The new keys for redeems are `twitch/ev/eventsub-event` and `twitch/eventsub-history`. History has been reduced to 50 to alleviate memory concerns.
- Application logs are now visible from the UI, check the little floating boxes in the top right!
- A new app icon drawn by [Sonic_chan](https://twitter.com/Sonic__Chan), say hello to Renko, strimertul's mascot!
- Hidden fields (Client secret, Kilovolt password) now have a "Reveal" toggle to show the hidden value
### Changed

View file

@ -261,7 +261,9 @@
"add": "Add",
"warning-delete": "This cannot be undone",
"create": "Create",
"submit": "Submit"
"submit": "Submit",
"password-reveal": "Reveal",
"password-hide": "Hide"
},
"debug": {
"dev-build": "Development build"

View file

@ -0,0 +1,23 @@
import React from 'react';
export interface PasswordFieldProps {
reveal: boolean;
}
function PasswordField(
props: PasswordFieldProps &
React.PropsWithChildren<
React.DetailedHTMLProps<
React.InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
>
>,
) {
return (
<input type={props.reveal ? 'text' : 'password'} {...props}>
{props.children}
</input>
);
}
export default React.memo(PasswordField);

View file

@ -0,0 +1,31 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '../../theme';
export interface RevealLinkProps {
value: boolean;
setter: (newValue: boolean) => void;
}
function RevealLink({ value, setter }: RevealLinkProps) {
const { t } = useTranslation();
const text = value
? t('form-actions.password-hide')
: t('form-actions.password-reveal');
return (
<Button
type="button"
css={{ display: 'inline-flex', marginLeft: '0.5rem' }}
onClick={() => {
if (setter) {
setter(!value);
}
}}
size={'smaller'}
>
{text}
</Button>
);
}
export default React.memo(RevealLink);

View file

@ -1,8 +1,9 @@
import React from 'react';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useModule, useStatus } from '../../lib/react-utils';
import { useAppDispatch } from '../../store';
import apiReducer, { modules } from '../../store/api/reducer';
import RevealLink from '../components/utils/RevealLink';
import SaveButton from '../components/utils/SaveButton';
import {
APPNAME,
@ -13,6 +14,7 @@ import {
PageContainer,
PageHeader,
PageTitle,
PasswordInputBox,
} from '../theme';
export default function ServerSettingsPage(): React.ReactElement {
@ -24,6 +26,7 @@ export default function ServerSettingsPage(): React.ReactElement {
const status = useStatus(loadStatus.save);
const busy =
loadStatus.load?.type !== 'success' || loadStatus.save?.type === 'pending';
const [revealKVPassword, setRevealKVPassword] = useState(false);
return (
<PageContainer>
@ -59,22 +62,23 @@ export default function ServerSettingsPage(): React.ReactElement {
<Field size="fullWidth">
<Label htmlFor="kvpassword">
{t('pages.http.kilovolt-password')}
</Label>
<InputBox
type="password"
<RevealLink value={revealKVPassword} setter={setRevealKVPassword} />
</Label>{' '}
<PasswordInputBox
reveal={revealKVPassword}
id="kvpassword"
placeholder={t('pages.http.kilovolt-placeholder')}
value={serverConfig?.kv_password ?? ''}
disabled={busy}
autoComplete="off"
onChange={(e) =>
onChange={(e) => {
dispatch(
apiReducer.actions.httpConfigChanged({
...serverConfig,
kv_password: e.target.value,
}),
)
}
);
}}
/>
<FieldNote>{t('pages.http.kilovolt-placeholder')}</FieldNote>
</Field>

View file

@ -11,6 +11,7 @@ import { RootState, useAppDispatch } from '../../store';
import apiReducer, { modules } from '../../store/api/reducer';
import BrowserLink from '../components/BrowserLink';
import DefinitionTable from '../components/DefinitionTable';
import RevealLink from '../components/utils/RevealLink';
import SaveButton from '../components/utils/SaveButton';
import {
APPNAME,
@ -26,6 +27,7 @@ import {
PageContainer,
PageHeader,
PageTitle,
PasswordInputBox,
SectionHeader,
styled,
TabButton,
@ -59,6 +61,7 @@ function TwitchBotSettings() {
const status = useStatus(loadStatus.save);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const [revealBotToken, setRevealBotToken] = useState(false);
const active = twitchConfig?.enable_bot ?? false;
return (
@ -138,9 +141,10 @@ function TwitchBotSettings() {
<Field size="fullWidth">
<Label htmlFor="bot-oauth">
{t('pages.twitch-settings.bot-oauth')}
<RevealLink value={revealBotToken} setter={setRevealBotToken} />
</Label>
<InputBox
type="password"
<PasswordInputBox
reveal={revealBotToken}
id="bot-oauth"
required={active}
disabled={!active || status?.type === 'pending'}
@ -199,6 +203,7 @@ function TwitchAPISettings() {
);
const status = useStatus(loadStatus.save);
const dispatch = useAppDispatch();
const [revealClientSecret, setRevealClientSecret] = useState(false);
return (
<form
@ -265,9 +270,13 @@ function TwitchAPISettings() {
<Field size="fullWidth">
<Label htmlFor="clientsecret">
{t('pages.twitch-settings.app-client-secret')}
<RevealLink
value={revealClientSecret}
setter={setRevealClientSecret}
/>
</Label>
<InputBox
type="password"
<PasswordInputBox
reveal={revealClientSecret}
id="clientsecret"
placeholder={t('pages.twitch-settings.app-client-secret')}
required={true}

View file

@ -2,9 +2,9 @@ import * as UnstyledLabel from '@radix-ui/react-label';
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import * as ToggleGroup from '@radix-ui/react-toggle-group';
import { CSS } from '@stitches/react';
import { styled } from './theme';
import { theme } from '.';
import { styled, theme } from './theme';
import ControlledInput from '../components/utils/ControlledInput';
import PasswordField from '../components/utils/PasswordField';
export const Field = styled('fieldset', {
all: 'unset',
@ -80,6 +80,7 @@ const inputStyles: CSS = {
export const InputBox = styled('input', inputStyles);
export const ControlledInputBox = styled(ControlledInput, inputStyles);
export const PasswordInputBox = styled(PasswordField, inputStyles);
export const Textarea = styled('textarea', {
all: 'unset',
@ -178,6 +179,11 @@ const button = {
padding: '0.3rem 0.5rem',
fontSize: '0.9rem',
},
smaller: {
padding: '5px',
paddingBottom: '3px',
fontSize: '0.8rem',
},
},
variation: {
primary: {

View file

@ -1,7 +1,6 @@
/* eslint-disable import/prefer-default-export */
import { theme } from '.';
import { styled } from './theme';
import { styled, theme } from './theme';
export const FlexRow = styled('div', {
display: 'flex',