Skip to content

Commit 68c212f

Browse files
authored
feat: add eye toggle for Encrypted Secrets/UUID (#135)
* feat: add eye toggle for Encrypted Secrets/UUID * fix toggle effect * Fix tests * revert Task.tsx * fix linter issues
1 parent ded6bc7 commit 68c212f

6 files changed

Lines changed: 110 additions & 10 deletions

File tree

frontend/src/components/HomeComponents/Hero/Hero.tsx

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import { Props } from '../../utils/types';
22
import { CopyButton } from './CopyButton';
33
import { ToastNotification } from './ToastNotification';
4+
import { useState } from 'react';
5+
import { Eye, EyeOff } from 'lucide-react';
46

57
export const Hero = (props: Props) => {
8+
const [showUuid, setShowUuid] = useState(true);
9+
const [showSecret, setShowSecret] = useState(true);
10+
11+
const createMask = (text: string) => '•'.repeat(text.length);
12+
613
return (
714
<section id="#" className="container py-20 md:py-32">
815
<div className="text-center lg:text-start space-y-6">
@@ -38,8 +45,21 @@ export const Hero = (props: Props) => {
3845
<h3 className="text-xl text-foreground font-semibold">UUID</h3>
3946
<div className="mt-4 flex items-center">
4047
<div className="bg-gray-900 text-white p-4 rounded-lg relative flex-grow-1 overflow-x-auto">
41-
<code className="whitespace-nowrap">{props.uuid}</code>
48+
<code className="whitespace-nowrap">
49+
{showUuid ? props.uuid : createMask(props.uuid)}
50+
</code>
4251
</div>
52+
<button
53+
onClick={() => setShowUuid(!showUuid)}
54+
className="text-white font-bold p-2 rounded-full m-2"
55+
aria-label={showUuid ? 'Hide UUID' : 'Show UUID'}
56+
>
57+
{showUuid ? (
58+
<EyeOff className="size-5" />
59+
) : (
60+
<Eye className="size-5" />
61+
)}
62+
</button>
4363
<CopyButton text={props.uuid} label="UUID" />
4464
</div>
4565
<br></br>
@@ -50,9 +70,22 @@ export const Hero = (props: Props) => {
5070
<div className="mt-4 flex items-center">
5171
<div className="bg-gray-900 text-white p-4 rounded-lg relative flex-grow-1 overflow-x-auto">
5272
<code className="whitespace-nowrap">
53-
{props.encryption_secret}
73+
{showSecret
74+
? props.encryption_secret
75+
: createMask(props.encryption_secret)}
5476
</code>
5577
</div>
78+
<button
79+
onClick={() => setShowSecret(!showSecret)}
80+
className="text-white font-bold p-2 rounded-full m-2"
81+
aria-label={showSecret ? 'Hide secret' : 'Show secret'}
82+
>
83+
{showSecret ? (
84+
<EyeOff className="size-5" />
85+
) : (
86+
<Eye className="size-5" />
87+
)}
88+
</button>
5689
<CopyButton
5790
text={props.encryption_secret}
5891
label="Encryption Secret"

frontend/src/components/HomeComponents/Hero/__tests__/Hero.test.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import { Props } from '../../../utils/types';
55

66
jest.mock('../CopyButton', () => ({
77
CopyButton: ({ text, label }: { text: string; label: string }) => (
8-
<button data-testid={`copy-button-${text.replace(/\s+/g, '-')}`}>
9-
{label}
8+
<button
9+
data-testid={`copy-button-${label.toLowerCase().replace(/\s+/g, '-')}`}
10+
>
11+
{text}
1012
</button>
1113
),
1214
}));
@@ -48,4 +50,15 @@ describe('Hero component', () => {
4850
const toastNotification = screen.getByTestId('toast-notification');
4951
expect(toastNotification).toBeInTheDocument();
5052
});
53+
54+
test('renders UUID and encryption secret with toggle buttons', () => {
55+
render(<Hero {...mockProps} />);
56+
57+
const uuidCopyButton = screen.getByTestId('copy-button-uuid');
58+
const secretCopyButton = screen.getByTestId(
59+
'copy-button-encryption-secret'
60+
);
61+
expect(uuidCopyButton).toBeInTheDocument();
62+
expect(secretCopyButton).toBeInTheDocument();
63+
});
5164
});

frontend/src/components/HomeComponents/SetupGuide/CopyableCode.tsx

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1-
import { CopyIcon } from 'lucide-react';
1+
import { CopyIcon, Eye, EyeOff } from 'lucide-react';
22
import CopyToClipboard from 'react-copy-to-clipboard';
33
import { toast } from 'react-toastify';
4+
import { useState } from 'react';
45

56
interface CopyableCodeProps {
67
text: string;
78
copyText: string;
9+
isSensitive?: boolean;
810
}
911

10-
export const CopyableCode = ({ text, copyText }: CopyableCodeProps) => {
12+
export const CopyableCode = ({
13+
text,
14+
copyText,
15+
isSensitive = false,
16+
}: CopyableCodeProps) => {
17+
const [showSensitive, setShowSensitive] = useState(true);
18+
1119
const handleCopy = (text: string) => {
1220
toast.success(`${text} copied to clipboard!`, {
1321
position: 'bottom-left',
@@ -20,13 +28,42 @@ export const CopyableCode = ({ text, copyText }: CopyableCodeProps) => {
2028
});
2129
};
2230

31+
const maskSensitiveValue = (fullText: string) => {
32+
const parts = fullText.split(' ');
33+
if (parts.length > 0) {
34+
const lastPart = parts[parts.length - 1];
35+
const maskedValue = '•'.repeat(lastPart.length);
36+
parts[parts.length - 1] = maskedValue;
37+
return parts.join(' ');
38+
}
39+
return fullText;
40+
};
41+
42+
const displayText =
43+
isSensitive && !showSensitive ? maskSensitiveValue(text) : text;
44+
2345
return (
2446
<div className="mt-4 flex items-center gap-2 w-full">
2547
<div className="bg-gray-900 text-white p-3 sm:p-4 rounded-lg flex-grow overflow-x-auto">
2648
<code className="text-sm sm:text-base whitespace-nowrap break-all">
27-
{text}
49+
{displayText}
2850
</code>
2951
</div>
52+
{isSensitive && (
53+
<button
54+
onClick={() => setShowSensitive(!showSensitive)}
55+
className="bg-gray-700 hover:bg-gray-600 text-white font-bold p-3 sm:p-4 rounded flex-shrink-0"
56+
aria-label={
57+
showSensitive ? 'Hide sensitive value' : 'Show sensitive value'
58+
}
59+
>
60+
{showSensitive ? (
61+
<EyeOff className="size-5" />
62+
) : (
63+
<Eye className="size-5" />
64+
)}
65+
</button>
66+
)}
3067
<CopyToClipboard text={copyText} onCopy={() => handleCopy(copyText)}>
3168
<button className="bg-blue-500 hover:bg-gray-900 text-white font-bold p-3 sm:p-4 rounded flex-shrink-0">
3269
<CopyIcon className="size-5" />

frontend/src/components/HomeComponents/SetupGuide/SetupGuide.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export const SetupGuide = (props: Props) => {
6767
<CopyableCode
6868
text={`task config sync.encryption_secret ${props.encryption_secret}`}
6969
copyText={`task config sync.encryption_secret ${props.encryption_secret}`}
70+
isSensitive={true}
7071
/>
7172
<div className="my-4">
7273
Configure Taskwarrior with these commands, run these
@@ -81,6 +82,7 @@ export const SetupGuide = (props: Props) => {
8182
<CopyableCode
8283
text={`task config sync.server.client_id ${props.uuid}`}
8384
copyText={`task config sync.server.client_id ${props.uuid}`}
85+
isSensitive={true}
8486
/>
8587
<div className="mt-4">
8688
For more information about how this works, refer to the{' '}

frontend/src/components/HomeComponents/SetupGuide/__tests__/CopyableCode.test.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ jest.mock('react-toastify', () => ({
99
},
1010
}));
1111

12-
// Mock CopyIcon
12+
// Mock icons
1313
jest.mock('lucide-react', () => ({
1414
CopyIcon: () => <svg data-testid="copy-icon"></svg>,
15+
Eye: () => <svg data-testid="eye-icon"></svg>,
16+
EyeOff: () => <svg data-testid="eye-off-icon"></svg>,
1517
}));
1618

1719
describe('CopyableCode', () => {

frontend/src/components/HomeComponents/SetupGuide/__tests__/SetupGuide.test.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,21 @@ import { url } from '@/components/utils/URLs';
55

66
// Mocking the CopyableCode component
77
jest.mock('../CopyableCode', () => ({
8-
CopyableCode: ({ text, copyText }: { text: string; copyText: string }) => (
9-
<div data-testid="copyable-code" data-text={text} data-copytext={copyText}>
8+
CopyableCode: ({
9+
text,
10+
copyText,
11+
isSensitive,
12+
}: {
13+
text: string;
14+
copyText: string;
15+
isSensitive?: boolean;
16+
}) => (
17+
<div
18+
data-testid="copyable-code"
19+
data-text={text}
20+
data-copytext={copyText}
21+
data-issensitive={isSensitive}
22+
>
1023
{text}
1124
</div>
1225
),

0 commit comments

Comments
 (0)