Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions modules/web/src/app/login/page.tsx
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';
Expand All @@ -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();
Expand Down Expand Up @@ -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 (_) {}
};
Comment on lines +63 to +71

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The handlePaste function currently swallows errors silently with an empty catch block. 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 for navigator.clipboard.readText can be simplified using optional chaining.

  const handlePaste = async () => {
    try {
      if (navigator.clipboard?.readText) {
        const text = await navigator.clipboard.readText();
        setToken(text);
        setTokenError('');
      }
    } catch (err) {
      console.error('Failed to paste from clipboard:', err);
    }
  };


const handleRunKeink = () => {
showConfirmDialog({
title: t("login.installByKeink"),
Expand Down Expand Up @@ -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">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The edge="end" prop on an IconButton within an InputAdornment is intended for the very last element to ensure correct padding and alignment. Since there are two IconButtons here, only the second one (the visibility toggle) should have this prop. Please remove edge="end" from the paste button's IconButton for proper visual spacing.

              <IconButton aria-label="paste token" onClick={handlePaste}>

<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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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 LoginPage component:

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 TextField as onChange={handleTokenChange} and onKeyDown={handleKeyDown}.

error={!!tokenError}
helperText={tokenError}
sx={{
Expand Down