Skip to content

Commit 074306c

Browse files
bugs fixed
1 parent db32801 commit 074306c

File tree

3 files changed

+85
-27
lines changed

3 files changed

+85
-27
lines changed

src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/TFVarTable.tsx

+61-21
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@
1111
import { Textarea } from "@/components/ui/textarea";
1212
import { EnvVar } from "@/types/userTypes";
1313
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';
1515
import moment from 'moment';
1616
import { useRouter } from 'next/navigation';
1717
import { useState } from 'react';
1818
import { toast } from 'sonner';
1919

2020
type TFVarTableProps = {
2121
envVars: EnvVar[];
22-
onUpdate: (name: string, value: string, isSecret: boolean) => Promise<EnvVar[]>;
22+
onUpdate: (oldName: string, newName: string, value: string, isSecret: boolean) => Promise<EnvVar[]>;
2323
onDelete: (name: string) => Promise<EnvVar[]>;
2424
onBulkUpdate: (vars: EnvVar[]) => Promise<EnvVar[]>;
2525
};
@@ -31,7 +31,7 @@ const EmptyState: React.FC<{ onAddVariable: () => void }> = ({ onAddVariable })
3131
animate={{ opacity: 1, y: 0 }}
3232
transition={{ duration: 0.5 }}
3333
>
34-
<Card className="mt-6 border-none bg-transparent shadow-none">
34+
<Card className=" border-none bg-transparent shadow-none">
3535
<CardContent className="flex flex-col items-center justify-center py-12">
3636
<h3 className="text-2xl font-semibold mb-2">No Environment Variables Yet</h3>
3737
<p className="text-muted-foreground mb-6 text-center max-w-md">
@@ -52,7 +52,7 @@ const EmptyState: React.FC<{ onAddVariable: () => void }> = ({ onAddVariable })
5252
};
5353

5454
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);
5656
const [newVar, setNewVar] = useState<Omit<EnvVar, 'updated_at'>>({ name: '', value: '', is_secret: false });
5757
const [bulkEditMode, setBulkEditMode] = useState(false);
5858
const [bulkEditValue, setBulkEditValue] = useState<string>('');
@@ -61,14 +61,31 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
6161
const router = useRouter();
6262

6363
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+
});
6571
};
6672

73+
6774
const handleSave = async () => {
6875
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+
}
6981
setIsLoading(true);
7082
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+
);
7289
toast.success('Variable updated successfully');
7390
setEditingVar(null);
7491
router.refresh();
@@ -80,15 +97,16 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
8097
}
8198
};
8299

100+
83101
const handleAddNew = async () => {
84102
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())) {
86104
toast.error('A variable with this name already exists');
87105
return;
88106
}
89107
setIsLoading(true);
90108
try {
91-
await onUpdate(newVar.name, newVar.value, newVar.is_secret);
109+
await onUpdate(newVar.name, newVar.name, newVar.value, newVar.is_secret);
92110
toast.success('New variable added successfully');
93111
setNewVar({ name: '', value: '', is_secret: false });
94112
setShowAddForm(false);
@@ -118,6 +136,13 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
118136
try {
119137
const parsedVars = JSON.parse(bulkEditValue);
120138
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+
121146
setIsLoading(true);
122147
await onBulkUpdate(parsedVars);
123148
toast.success('Bulk update successful');
@@ -131,6 +156,7 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
131156
}
132157
};
133158

159+
134160
const toggleBulkEdit = () => {
135161
if (!bulkEditMode) {
136162
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 }
167193
rows={24}
168194
className="font-mono"
169195
/>
170-
<div className="space-x-2">
196+
<div className="flex gap-2 w-full justify-end">
197+
<Button variant="outline" onClick={toggleBulkEdit}>Cancel</Button>
171198
<Button onClick={handleBulkEdit} disabled={isLoading}>
172199
{isLoading ? 'Applying...' : 'Apply Bulk Edit'}
173200
</Button>
174-
<Button variant="outline" onClick={toggleBulkEdit}>Cancel</Button>
175201
</div>
176202
</div>
177203
);
178204
}
179-
180205
if (envVars.length === 0 && !showAddForm) {
181206
return <EmptyState onAddVariable={() => setShowAddForm(true)} />;
182207
}
@@ -198,30 +223,46 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
198223
</TableHeader>
199224
)}
200225
<TableBody>
201-
{envVars.map((envVar) => (
226+
{envVars.map((envVar, index) => (
202227
<TableRow key={envVar.name}>
203-
<TableCell>{envVar.name}</TableCell>
204228
<TableCell>
205-
{editingVar && editingVar.name === envVar.name ? (
229+
{editingVar && editingVar.originalName === envVar.name ? (
206230
<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" : ""}
211250
/>
212251
) : (
213252
<span>{envVar.is_secret ? '********' : envVar.value}</span>
214253
)}
215254
</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>
217258
<TableCell>{moment(envVar.updated_at).fromNow()}</TableCell>
218259
<TableCell>
219260
<Button variant="ghost" size="icon" onClick={() => handleCopy(envVar)}>
220261
<Copy className="h-4 w-4" />
221262
</Button>
222263
</TableCell>
223264
<TableCell>
224-
{editingVar && editingVar.name === envVar.name ? (
265+
{editingVar && editingVar.originalName === envVar.name ? (
225266
<Button variant="ghost" size="icon" onClick={handleSave} disabled={isLoading}>
226267
<Save className="h-4 w-4" />
227268
</Button>
@@ -294,7 +335,6 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
294335
<>
295336
<div className="mt-4 flex justify-end">
296337
<Button variant="outline" onClick={() => setShowAddForm(!showAddForm)}>
297-
<Plus className="h-4 w-4 mr-2" />
298338
{showAddForm ? 'Cancel' : 'Add New Variable'}
299339
</Button>
300340
</div>

src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/TFVarsDetails.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ type TFVarsDetailsProps = {
1212
tfvars: EnvVar[];
1313
updated_at: string;
1414
};
15-
onUpdate: (name: string, value: string, isSecret: boolean) => Promise<EnvVar[]>;
15+
onUpdate: (oldName: string, newName: string, value: string, isSecret: boolean) => Promise<EnvVar[]>;
1616
onDelete: (name: string) => Promise<EnvVar[]>;
1717
onBulkUpdate: (vars: EnvVar[]) => Promise<EnvVar[]>;
1818
}

src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/tfvars/page.tsx

+23-5
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,12 @@ export default async function TFVarsPage({ params }: { params: unknown }) {
3030

3131
const envVars = await getAllEnvVars(project.id, MASTER_PASSWORD, ENCRYPTION_SALT);
3232

33-
async function handleUpdate(name: string, value: string, isSecret: boolean) {
33+
async function handleUpdate(oldName: string, newName: string, value: string, isSecret: boolean) {
3434
'use server'
35-
await storeEncryptedEnvVar(project.id, name, value, isSecret, MASTER_PASSWORD, ENCRYPTION_SALT);
35+
if (oldName !== newName) {
36+
await deleteEnvVar(project.id, oldName);
37+
}
38+
await storeEncryptedEnvVar(project.id, newName, value, isSecret, MASTER_PASSWORD, ENCRYPTION_SALT);
3639
return getAllEnvVars(project.id, MASTER_PASSWORD, ENCRYPTION_SALT);
3740
}
3841

@@ -44,11 +47,26 @@ export default async function TFVarsPage({ params }: { params: unknown }) {
4447

4548
async function handleBulkUpdate(vars: EnvVar[]) {
4649
'use server'
47-
for (const envVar of vars) {
48-
if (!envVar.is_secret) {
49-
await storeEncryptedEnvVar(project.id, envVar.name, envVar.value, envVar.is_secret, MASTER_PASSWORD, ENCRYPTION_SALT);
50+
const currentVars = await getAllEnvVars(project.id, MASTER_PASSWORD, ENCRYPTION_SALT);
51+
const currentVarsMap = Object.fromEntries(currentVars.map(v => [v.name, v]));
52+
53+
for (const newVar of vars) {
54+
const currentVar = currentVarsMap[newVar.name];
55+
if (currentVar) {
56+
if (!currentVar.is_secret && (currentVar.value !== newVar.value || currentVar.name !== newVar.name)) {
57+
await handleUpdate(currentVar.name, newVar.name, newVar.value, currentVar.is_secret);
58+
}
59+
} else {
60+
await handleUpdate(newVar.name, newVar.name, newVar.value, false);
61+
}
62+
}
63+
64+
for (const currentVar of currentVars) {
65+
if (!vars.some(v => v.name === currentVar.name) && !currentVar.is_secret) {
66+
await deleteEnvVar(project.id, currentVar.name);
5067
}
5168
}
69+
5270
return getAllEnvVars(project.id, MASTER_PASSWORD, ENCRYPTION_SALT);
5371
}
5472

0 commit comments

Comments
 (0)