Skip to content

Commit 720ff9d

Browse files
committed
feat(profile): refactor create UX with dialog-based interface
- Replace ProfileCreateForm with ProfileCreateDialog for better UX - Add Select component for dropdown functionality - Implement sensitive keys detection and masking - Enhance code editor with improved validation - Update dependencies and lock files
1 parent 46ee1df commit 720ff9d

File tree

8 files changed

+648
-366
lines changed

8 files changed

+648
-366
lines changed

ui/bun.lock

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"@radix-ui/react-label": "^2.1.8",
1313
"@radix-ui/react-popover": "^1.1.15",
1414
"@radix-ui/react-scroll-area": "^1.2.10",
15+
"@radix-ui/react-select": "^2.2.6",
1516
"@radix-ui/react-separator": "^1.1.8",
1617
"@radix-ui/react-slot": "^1.2.4",
1718
"@radix-ui/react-tabs": "^1.1.13",
@@ -245,6 +246,8 @@
245246

246247
"@radix-ui/react-scroll-area": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="],
247248

249+
"@radix-ui/react-select": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="],
250+
248251
"@radix-ui/react-separator": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="],
249252

250253
"@radix-ui/react-slot": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="],
@@ -263,6 +266,8 @@
263266

264267
"@radix-ui/react-use-layout-effect": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
265268

269+
"@radix-ui/react-use-previous": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="],
270+
266271
"@radix-ui/react-use-rect": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="],
267272

268273
"@radix-ui/react-use-size": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="],
@@ -807,6 +812,8 @@
807812

808813
"@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
809814

815+
"@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
816+
810817
"@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
811818

812819
"@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],

ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@radix-ui/react-label": "^2.1.8",
2424
"@radix-ui/react-popover": "^1.1.15",
2525
"@radix-ui/react-scroll-area": "^1.2.10",
26+
"@radix-ui/react-select": "^2.2.6",
2627
"@radix-ui/react-separator": "^1.1.8",
2728
"@radix-ui/react-slot": "^1.2.4",
2829
"@radix-ui/react-tabs": "^1.1.13",

ui/src/components/code-editor.tsx

Lines changed: 96 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
* Uses react-simple-code-editor + prism-react-renderer for minimal bundle size (~18KB)
55
*/
66

7-
import { useState, useCallback, useMemo } from 'react';
7+
import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
88
import Editor from 'react-simple-code-editor';
99
import { Highlight, themes } from 'prism-react-renderer';
1010
import { useTheme } from '@/hooks/use-theme';
1111
import { cn } from '@/lib/utils';
12-
import { AlertCircle, CheckCircle2 } from 'lucide-react';
12+
import { isSensitiveKey } from '@/lib/sensitive-keys';
13+
import { AlertCircle, CheckCircle2, Eye, EyeOff } from 'lucide-react';
14+
import { Button } from '@/components/ui/button';
1315

1416
interface CodeEditorProps {
1517
value: string;
@@ -71,6 +73,19 @@ export function CodeEditor({
7173
}: CodeEditorProps) {
7274
const { isDark } = useTheme();
7375
const [isFocused, setIsFocused] = useState(false);
76+
const [isMasked, setIsMasked] = useState(true);
77+
// Force Editor remount when theme changes (works around react-simple-code-editor caching)
78+
const [editorKey, setEditorKey] = useState(0);
79+
const isFirstRender = useRef(true);
80+
81+
useEffect(() => {
82+
// Skip first render, only trigger on theme changes
83+
if (isFirstRender.current) {
84+
isFirstRender.current = false;
85+
return;
86+
}
87+
setEditorKey((k) => k + 1);
88+
}, [isDark]);
7489

7590
// Validate on every change for JSON
7691
const validation = useMemo(() => {
@@ -81,32 +96,76 @@ export function CodeEditor({
8196
}, [value, language]);
8297

8398
// Highlight function using prism-react-renderer
99+
// Note: Line numbers removed - they break textarea/pre alignment in react-simple-code-editor
84100
const highlightCode = useCallback(
85101
(code: string) => (
86102
<Highlight theme={isDark ? themes.nightOwl : themes.github} code={code} language={language}>
87-
{({ tokens, getLineProps, getTokenProps }) => (
88-
<>
89-
{tokens.map((line, i) => (
90-
<div
91-
key={i}
92-
{...getLineProps({ line })}
93-
className={cn('table-row', validation.line === i + 1 && 'bg-destructive/20')}
94-
>
95-
<span className="table-cell pr-4 text-right text-muted-foreground select-none opacity-50 text-xs w-8">
96-
{i + 1}
97-
</span>
98-
<span className="table-cell">
99-
{line.map((token, key) => (
100-
<span key={key} {...getTokenProps({ token })} />
101-
))}
102-
</span>
103-
</div>
104-
))}
105-
</>
106-
)}
103+
{({ tokens, getLineProps, getTokenProps }) => {
104+
let nextValueIsSensitive = false;
105+
106+
return (
107+
<>
108+
{tokens.map((line, i) => (
109+
<div
110+
key={i}
111+
{...getLineProps({ line })}
112+
className={cn(validation.line === i + 1 && 'bg-destructive/20')}
113+
>
114+
{line.map((token, key) => {
115+
let isSensitive = false;
116+
117+
// Check for sensitive keys
118+
if (token.types.includes('property')) {
119+
const content = token.content.replace(/['"]/g, '');
120+
// Use shared sensitive key detection utility
121+
if (isSensitiveKey(content)) {
122+
nextValueIsSensitive = true;
123+
} else {
124+
nextValueIsSensitive = false;
125+
}
126+
}
127+
// Apply masking to values following sensitive keys
128+
else if (
129+
(token.types.includes('string') ||
130+
token.types.includes('number') ||
131+
token.types.includes('boolean')) &&
132+
nextValueIsSensitive
133+
) {
134+
isSensitive = true;
135+
// Consumes the flag for this value
136+
nextValueIsSensitive = false;
137+
}
138+
// Reset flag on commas or new keys (handled by property check),
139+
// but persist through colons and whitespace
140+
else if (token.types.includes('punctuation')) {
141+
if (
142+
token.content !== ':' &&
143+
token.content !== '[' &&
144+
token.content !== '{'
145+
) {
146+
nextValueIsSensitive = false;
147+
}
148+
}
149+
150+
const tokenProps = getTokenProps({ token });
151+
152+
if (isSensitive && isMasked) {
153+
tokenProps.className = cn(
154+
tokenProps.className,
155+
'blur-[3px] select-none opacity-70 transition-all duration-200'
156+
);
157+
}
158+
159+
return <span key={key} {...tokenProps} />;
160+
})}
161+
</div>
162+
))}
163+
</>
164+
);
165+
}}
107166
</Highlight>
108167
),
109-
[isDark, language, validation.line]
168+
[isDark, language, validation.line, isMasked]
110169
);
111170

112171
return (
@@ -126,7 +185,7 @@ export function CodeEditor({
126185
value={value}
127186
onValueChange={readonly ? () => {} : onChange}
128187
highlight={highlightCode}
129-
key={isDark ? 'dark' : 'light'}
188+
key={editorKey}
130189
padding={12}
131190
disabled={readonly}
132191
onFocus={() => setIsFocused(true)}
@@ -142,6 +201,19 @@ export function CodeEditor({
142201
minHeight,
143202
}}
144203
/>
204+
205+
{/* Secrets Toggle Overlay */}
206+
<div className="absolute top-2 right-2 z-10 opacity-50 hover:opacity-100 transition-opacity">
207+
<Button
208+
variant="ghost"
209+
size="icon"
210+
className="h-6 w-6 bg-background/50 hover:bg-background border shadow-sm rounded-full"
211+
onClick={() => setIsMasked(!isMasked)}
212+
title={isMasked ? 'Reveal sensitive values' : 'Mask sensitive values'}
213+
>
214+
{isMasked ? <Eye className="h-3 w-3" /> : <EyeOff className="h-3 w-3" />}
215+
</Button>
216+
</div>
145217
</div>
146218

147219
{/* Validation status */}

0 commit comments

Comments
 (0)