@@ -11,15 +11,15 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@
11
11
import { Textarea } from "@/components/ui/textarea" ;
12
12
import { EnvVar } from "@/types/userTypes" ;
13
13
import { motion } from 'framer-motion' ;
14
- import { Copy , Edit , Plus , Save , Trash } from 'lucide-react' ;
14
+ import { Copy , Edit , LockKeyhole , Plus , Save , Trash , Unlock } from 'lucide-react' ;
15
15
import moment from 'moment' ;
16
16
import { useRouter } from 'next/navigation' ;
17
17
import { useState } from 'react' ;
18
18
import { toast } from 'sonner' ;
19
19
20
20
type TFVarTableProps = {
21
21
envVars : EnvVar [ ] ;
22
- onUpdate : ( name : string , value : string , isSecret : boolean ) => Promise < EnvVar [ ] > ;
22
+ onUpdate : ( oldName : string , newName : string , value : string , isSecret : boolean ) => Promise < EnvVar [ ] > ;
23
23
onDelete : ( name : string ) => Promise < EnvVar [ ] > ;
24
24
onBulkUpdate : ( vars : EnvVar [ ] ) => Promise < EnvVar [ ] > ;
25
25
} ;
@@ -31,7 +31,7 @@ const EmptyState: React.FC<{ onAddVariable: () => void }> = ({ onAddVariable })
31
31
animate = { { opacity : 1 , y : 0 } }
32
32
transition = { { duration : 0.5 } }
33
33
>
34
- < Card className = "mt-6 border-none bg-transparent shadow-none" >
34
+ < Card className = " border-none bg-transparent shadow-none" >
35
35
< CardContent className = "flex flex-col items-center justify-center py-12" >
36
36
< h3 className = "text-2xl font-semibold mb-2" > No Environment Variables Yet</ h3 >
37
37
< p className = "text-muted-foreground mb-6 text-center max-w-md" >
@@ -52,7 +52,7 @@ const EmptyState: React.FC<{ onAddVariable: () => void }> = ({ onAddVariable })
52
52
} ;
53
53
54
54
export default function TFVarTable ( { envVars, onUpdate, onDelete, onBulkUpdate } : TFVarTableProps ) {
55
- const [ editingVar , setEditingVar ] = useState < EnvVar | null > ( null ) ;
55
+ const [ editingVar , setEditingVar ] = useState < { originalName : string , currentVar : EnvVar } | null > ( null ) ;
56
56
const [ newVar , setNewVar ] = useState < Omit < EnvVar , 'updated_at' > > ( { name : '' , value : '' , is_secret : false } ) ;
57
57
const [ bulkEditMode , setBulkEditMode ] = useState ( false ) ;
58
58
const [ bulkEditValue , setBulkEditValue ] = useState < string > ( '' ) ;
@@ -61,14 +61,31 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
61
61
const router = useRouter ( ) ;
62
62
63
63
const handleEdit = ( envVar : EnvVar ) => {
64
- setEditingVar ( { ...envVar , value : envVar . is_secret ? '' : envVar . value } ) ;
64
+ setEditingVar ( {
65
+ originalName : envVar . name ,
66
+ currentVar : {
67
+ ...envVar ,
68
+ value : envVar . is_secret ? '' : envVar . value
69
+ }
70
+ } ) ;
65
71
} ;
66
72
73
+
67
74
const handleSave = async ( ) => {
68
75
if ( editingVar ) {
76
+ if ( editingVar . currentVar . name . toLowerCase ( ) !== editingVar . originalName . toLowerCase ( ) &&
77
+ envVars . some ( v => v . name . toLowerCase ( ) === editingVar . currentVar . name . toLowerCase ( ) ) ) {
78
+ toast . error ( 'A variable with this name already exists' ) ;
79
+ return ;
80
+ }
69
81
setIsLoading ( true ) ;
70
82
try {
71
- await onUpdate ( editingVar . name , editingVar . value , editingVar . is_secret ) ;
83
+ await onUpdate (
84
+ editingVar . originalName ,
85
+ editingVar . currentVar . name ,
86
+ editingVar . currentVar . value ,
87
+ editingVar . currentVar . is_secret
88
+ ) ;
72
89
toast . success ( 'Variable updated successfully' ) ;
73
90
setEditingVar ( null ) ;
74
91
router . refresh ( ) ;
@@ -80,15 +97,16 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
80
97
}
81
98
} ;
82
99
100
+
83
101
const handleAddNew = async ( ) => {
84
102
if ( newVar . name && newVar . value ) {
85
- if ( envVars . some ( v => v . name === newVar . name ) ) {
103
+ if ( envVars . some ( v => v . name . toLowerCase ( ) === newVar . name . toLowerCase ( ) ) ) {
86
104
toast . error ( 'A variable with this name already exists' ) ;
87
105
return ;
88
106
}
89
107
setIsLoading ( true ) ;
90
108
try {
91
- await onUpdate ( newVar . name , newVar . value , newVar . is_secret ) ;
109
+ await onUpdate ( newVar . name , newVar . name , newVar . value , newVar . is_secret ) ;
92
110
toast . success ( 'New variable added successfully' ) ;
93
111
setNewVar ( { name : '' , value : '' , is_secret : false } ) ;
94
112
setShowAddForm ( false ) ;
@@ -118,6 +136,13 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
118
136
try {
119
137
const parsedVars = JSON . parse ( bulkEditValue ) ;
120
138
if ( Array . isArray ( parsedVars ) ) {
139
+ // Check for duplicate names in the parsed vars
140
+ const names = parsedVars . map ( v => v . name . toLowerCase ( ) ) ;
141
+ if ( new Set ( names ) . size !== names . length ) {
142
+ toast . error ( 'Duplicate variable names are not allowed' ) ;
143
+ return ;
144
+ }
145
+
121
146
setIsLoading ( true ) ;
122
147
await onBulkUpdate ( parsedVars ) ;
123
148
toast . success ( 'Bulk update successful' ) ;
@@ -131,6 +156,7 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
131
156
}
132
157
} ;
133
158
159
+
134
160
const toggleBulkEdit = ( ) => {
135
161
if ( ! bulkEditMode ) {
136
162
const nonSecretVars = envVars . filter ( v => ! v . is_secret ) . map ( ( { name, value } ) => ( { name, value } ) ) ;
@@ -167,16 +193,15 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
167
193
rows = { 24 }
168
194
className = "font-mono"
169
195
/>
170
- < div className = "space-x-2" >
196
+ < div className = "flex gap-2 w-full justify-end" >
197
+ < Button variant = "outline" onClick = { toggleBulkEdit } > Cancel</ Button >
171
198
< Button onClick = { handleBulkEdit } disabled = { isLoading } >
172
199
{ isLoading ? 'Applying...' : 'Apply Bulk Edit' }
173
200
</ Button >
174
- < Button variant = "outline" onClick = { toggleBulkEdit } > Cancel</ Button >
175
201
</ div >
176
202
</ div >
177
203
) ;
178
204
}
179
-
180
205
if ( envVars . length === 0 && ! showAddForm ) {
181
206
return < EmptyState onAddVariable = { ( ) => setShowAddForm ( true ) } /> ;
182
207
}
@@ -198,30 +223,46 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
198
223
</ TableHeader >
199
224
) }
200
225
< TableBody >
201
- { envVars . map ( ( envVar ) => (
226
+ { envVars . map ( ( envVar , index ) => (
202
227
< TableRow key = { envVar . name } >
203
- < TableCell > { envVar . name } </ TableCell >
204
228
< TableCell >
205
- { editingVar && editingVar . name === envVar . name ? (
229
+ { editingVar && editingVar . originalName === envVar . name ? (
206
230
< Input
207
- type = { envVar . is_secret ? "password" : "text" }
208
- value = { editingVar . value }
209
- onChange = { ( e ) => setEditingVar ( { ...editingVar , value : e . target . value } ) }
210
- placeholder = { envVar . is_secret ? "Enter new secret value" : "" }
231
+ value = { editingVar . currentVar . name }
232
+ onChange = { ( e ) => setEditingVar ( {
233
+ ...editingVar ,
234
+ currentVar : { ...editingVar . currentVar , name : e . target . value . toUpperCase ( ) }
235
+ } ) }
236
+ />
237
+ ) : (
238
+ envVar . name
239
+ ) } </ TableCell >
240
+ < TableCell >
241
+ { editingVar && editingVar . originalName === envVar . name ? (
242
+ < Input
243
+ type = { editingVar . currentVar . is_secret ? "password" : "text" }
244
+ value = { editingVar . currentVar . value }
245
+ onChange = { ( e ) => setEditingVar ( {
246
+ ...editingVar ,
247
+ currentVar : { ...editingVar . currentVar , value : e . target . value }
248
+ } ) }
249
+ placeholder = { editingVar . currentVar . is_secret ? "Enter new secret value" : "" }
211
250
/>
212
251
) : (
213
252
< span > { envVar . is_secret ? '********' : envVar . value } </ span >
214
253
) }
215
254
</ TableCell >
216
- < TableCell > { envVar . is_secret ? 'Yes' : 'No' } </ TableCell >
255
+ < TableCell >
256
+ { envVar . is_secret ? < LockKeyhole className = "h-4 w-4" /> : < Unlock className = "h-4 w-4" /> }
257
+ </ TableCell >
217
258
< TableCell > { moment ( envVar . updated_at ) . fromNow ( ) } </ TableCell >
218
259
< TableCell >
219
260
< Button variant = "ghost" size = "icon" onClick = { ( ) => handleCopy ( envVar ) } >
220
261
< Copy className = "h-4 w-4" />
221
262
</ Button >
222
263
</ TableCell >
223
264
< TableCell >
224
- { editingVar && editingVar . name === envVar . name ? (
265
+ { editingVar && editingVar . originalName === envVar . name ? (
225
266
< Button variant = "ghost" size = "icon" onClick = { handleSave } disabled = { isLoading } >
226
267
< Save className = "h-4 w-4" />
227
268
</ Button >
@@ -294,7 +335,6 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
294
335
< >
295
336
< div className = "mt-4 flex justify-end" >
296
337
< Button variant = "outline" onClick = { ( ) => setShowAddForm ( ! showAddForm ) } >
297
- < Plus className = "h-4 w-4 mr-2" />
298
338
{ showAddForm ? 'Cancel' : 'Add New Variable' }
299
339
</ Button >
300
340
</ div >
0 commit comments