-
Notifications
You must be signed in to change notification settings - Fork 41
web: improve login form UX (paste, visibility, Enter submit) #108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,11 @@ | ||
| 'use client' | ||
|
|
||
| import React, { useEffect, useState } from 'react'; | ||
| import { Box, Typography, TextField, Button, Link } from '@mui/material'; | ||
| import { Box, Typography, TextField, Button, Link, IconButton, InputAdornment } from '@mui/material'; | ||
| import PersonIcon from '@mui/icons-material/Person'; | ||
| import VisibilityIcon from '@mui/icons-material/Visibility'; | ||
| import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; | ||
| import ContentPasteIcon from '@mui/icons-material/ContentPaste'; | ||
| import GitHubIcon from '@mui/icons-material/GitHub'; | ||
| import { getVersion } from '@/api/version'; | ||
| import { useStorage } from '@/hook/useStorage'; | ||
|
|
@@ -17,6 +20,7 @@ import { useI18n } from '@/hook/useI18n'; | |
| const LoginPage = () => { | ||
| const [token, setToken] = useState(''); | ||
| const [tokenError, setTokenError] = useState(''); | ||
| const [showToken, setShowToken] = useState(false); | ||
| const [storedToken, setStoredToken] = useStorage('token'); | ||
| const [cookie, setCookie] = useCookie('dashboard_user'); | ||
| const { error } = useAlert(); | ||
|
|
@@ -56,6 +60,16 @@ const LoginPage = () => { | |
| } | ||
| }; | ||
|
|
||
| const handlePaste = async () => { | ||
| try { | ||
| if (typeof navigator !== 'undefined' && navigator.clipboard && typeof navigator.clipboard.readText === 'function') { | ||
| const text = await navigator.clipboard.readText(); | ||
| setToken(text || ''); | ||
| setTokenError(''); | ||
| } | ||
| } catch (_) {} | ||
| }; | ||
|
|
||
| const handleRunKeink = () => { | ||
| showConfirmDialog({ | ||
| title: t("login.installByKeink"), | ||
|
|
@@ -104,13 +118,25 @@ const LoginPage = () => { | |
| <TextField | ||
| variant="outlined" | ||
| placeholder={t('messages.pleaseEnterToken')} | ||
| type={showToken ? 'text' : 'password'} | ||
| InputProps={{ | ||
| startAdornment: ( | ||
| <PersonIcon sx={{ marginRight: '8px', color: 'gray' }} /> | ||
| ), | ||
| endAdornment: ( | ||
| <InputAdornment position="end"> | ||
| <IconButton aria-label="paste token" onClick={handlePaste} edge="end"> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| <ContentPasteIcon /> | ||
| </IconButton> | ||
| <IconButton aria-label="toggle token visibility" onClick={() => setShowToken(!showToken)} edge="end"> | ||
| {showToken ? <VisibilityOffIcon /> : <VisibilityIcon />} | ||
| </IconButton> | ||
| </InputAdornment> | ||
| ), | ||
| }} | ||
| value={token} | ||
| onChange={(e) => setToken(e.target.value)} | ||
| onChange={(e) => { setToken(e.target.value); if (tokenError) setTokenError(''); }} | ||
| onKeyDown={(e) => { if (e.key === 'Enter') handleLogin(); }} | ||
|
Comment on lines
+138
to
+139
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For better readability and maintainability, it's a good practice to extract inline event handlers with logic into separate named functions. This keeps the JSX cleaner and makes the component's logic easier to understand and test. You could define these handlers within the const handleTokenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setToken(e.target.value);
if (tokenError) {
setTokenError('');
}
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
handleLogin();
}
};And then use them in the |
||
| error={!!tokenError} | ||
| helperText={tokenError} | ||
| sx={{ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
handlePastefunction currently swallows errors silently with an emptycatchblock. This can make debugging difficult if clipboard access fails for any reason (e.g., user denies permission, browser incompatibility). It's better to at least log the error to the console for debugging purposes. Additionally, the check fornavigator.clipboard.readTextcan be simplified using optional chaining.