Skip to content

Commit 67265c7

Browse files
committed
🧼 CLEANUP: some cleanup with eslint.
1 parent 824df47 commit 67265c7

20 files changed

+525
-277
lines changed

src/App.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import React from 'react';
22
import NotFound from './pages/NotFound';
33
import LoginPage from '@/pages/LoginPage';
4+
import AdminPage from './pages/AdminPage';
45
import Layout from './components/layout/Layout';
56
import ProjectsPage from './pages/ProjectsPage';
67
import { Toaster } from '@/components/ui/toaster';
78
import { AuthProvider } from './contexts/AuthContext';
9+
import AdminRoute from '@/components/auth/AdminRoute';
810
import ProjectDetailPage from './pages/ProjectDetailPage';
911
import { TooltipProvider } from '@/components/ui/tooltip';
1012
import { Toaster as Sonner } from '@/components/ui/sonner';
@@ -30,6 +32,9 @@ const App = () => {
3032
<Route element={<Layout />}>
3133
<Route path="/" element={<ProjectsPage />} />
3234
<Route path="/projects/:projectId" element={<ProjectDetailPage />} />
35+
<Route element={<AdminRoute />}>
36+
<Route path="/admin" element={<AdminPage />} />
37+
</Route>
3338
</Route>
3439
</Route>
3540
<Route path="*" element={<NotFound />} />

src/components/auth/ChangePasswordModal.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,15 @@ const ChangePasswordModal: React.FC<ChangePasswordModalProps> = ({ isOpen, onClo
8383
navigate(redirectTo, { replace: true });
8484

8585
resetForm();
86-
} catch (error: any) {
86+
}
87+
catch (error: any) {
8788
toast({
8889
title: 'Change Password Failed',
8990
description: error?.message || 'An unexpected error occurred. Please try again.',
9091
variant: 'destructive',
9192
});
92-
} finally {
93+
}
94+
finally {
9395
setIsLoading(false);
9496
}
9597
};
@@ -103,7 +105,7 @@ const ChangePasswordModal: React.FC<ChangePasswordModalProps> = ({ isOpen, onClo
103105
}
104106
}}
105107
>
106-
<DialogContent className="sm:max-w-md">
108+
<DialogContent className="sm:max-w-md max-w-4xl">
107109
<DialogHeader>
108110
<DialogTitle>Change Password</DialogTitle>
109111
<DialogDescription>

src/components/auth/LoginForm.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ const LoginForm = () => {
2828
try {
2929
await login(username, password);
3030
navigate(redirectTo, { replace: true });
31-
} catch (err) {
31+
}
32+
catch (err) {
3233
setError(err instanceof Error ? err.message : 'Login failed');
33-
} finally {
34+
}
35+
finally {
3436
setIsLoading(false);
3537
}
3638
};

src/components/endpoints/CurlConverter.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ const CurlConverter: React.FC<CurlConverterProps> = ({ projectId, openApiSpec })
4949
});
5050

5151
setCurlCommand('');
52-
} catch (error) {
52+
}
53+
catch (error) {
5354
toast({
5455
title: 'Conversion Error',
5556
description: error instanceof Error ? error.message : 'Failed to convert CURL to OpenAPI',

src/components/endpoints/DetailTabs/NotesSection.tsx

Lines changed: 101 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
1-
1+
import { Trash2 } from 'lucide-react';
22
import React, { useState } from 'react';
33
import { useToast } from '@/hooks/use-toast';
4+
import { OperationObject } from '@/types/types';
45
import { Button } from '@/components/ui/button';
5-
import { useAuth } from '@/contexts/AuthContext';
6-
import { Textarea } from '@/components/ui/textarea';
76
import { formatDate } from '@/utils/schemaUtils';
8-
import { Trash2 } from 'lucide-react';
9-
import { OperationObject } from '@/types/types';
10-
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
7+
import { Textarea } from '@/components/ui/textarea';
8+
import { useProject } from '@/hooks/api/useProject';
9+
import { usePermissions } from '@/hooks/usePermissions';
1110
import { useCreateNote, useDeleteNote } from '@/hooks/api/useNotes';
11+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
12+
13+
import {
14+
AlertDialog,
15+
AlertDialogTitle,
16+
AlertDialogAction,
17+
AlertDialogCancel,
18+
AlertDialogHeader,
19+
AlertDialogFooter,
20+
AlertDialogContent,
21+
AlertDialogTrigger,
22+
AlertDialogDescription,
23+
} from '@/components/ui/alert-dialog';
1224

1325
interface NotesSectionProps {
1426
projectId: string;
@@ -18,11 +30,13 @@ interface NotesSectionProps {
1830
}
1931

2032
const NotesSection: React.FC<NotesSectionProps> = ({ projectId, path, method, operation }) => {
21-
const { user } = useAuth();
33+
const { user, isProjectOwner } = usePermissions();
34+
const { data: project } = useProject(projectId);
2235
const { toast } = useToast();
23-
36+
2437
const [newNoteContent, setNewNoteContent] = useState('');
25-
38+
const [noteToDelete, setNoteToDelete] = useState<number | null>(null);
39+
2640
const createNoteMutation = useCreateNote();
2741
const deleteNoteMutation = useDeleteNote();
2842

@@ -52,7 +66,8 @@ const NotesSection: React.FC<NotesSectionProps> = ({ projectId, path, method, op
5266
});
5367

5468
setNewNoteContent('');
55-
} catch (error) {
69+
}
70+
catch (error) {
5671
toast({
5772
title: 'Error',
5873
description: error instanceof Error ? error.message : 'Failed to add note',
@@ -74,7 +89,8 @@ const NotesSection: React.FC<NotesSectionProps> = ({ projectId, path, method, op
7489
title: 'Note Deleted',
7590
description: 'The note has been deleted successfully',
7691
});
77-
} catch (error) {
92+
}
93+
catch (error) {
7894
toast({
7995
title: 'Error',
8096
description: error instanceof Error ? error.message : 'Failed to delete note',
@@ -83,6 +99,31 @@ const NotesSection: React.FC<NotesSectionProps> = ({ projectId, path, method, op
8399
}
84100
};
85101

102+
const confirmDeleteNote = async () => {
103+
if (noteToDelete === null) return;
104+
105+
try {
106+
await deleteNoteMutation.mutateAsync({
107+
projectId,
108+
method,
109+
path,
110+
noteIndex: noteToDelete,
111+
});
112+
113+
toast({ title: 'Note Deleted', description: 'The note has been deleted successfully.' });
114+
}
115+
catch (error) {
116+
toast({
117+
title: 'Error',
118+
description: error instanceof Error ? error.message : 'Failed to delete note',
119+
variant: 'destructive',
120+
});
121+
}
122+
finally {
123+
setNoteToDelete(null);
124+
}
125+
};
126+
86127
return (
87128
<div className="space-y-4">
88129
{notes.length === 0 ? (
@@ -96,6 +137,7 @@ const NotesSection: React.FC<NotesSectionProps> = ({ projectId, path, method, op
96137
<p className="whitespace-pre-line text-sm" style={{ unicodeBidi: 'plaintext' }}>
97138
{note.content}
98139
</p>
140+
99141
<div className="flex items-center space-x-2 mt-3 text-xs text-muted-foreground">
100142
<Avatar className="h-5 w-5">
101143
<AvatarImage
@@ -104,45 +146,66 @@ const NotesSection: React.FC<NotesSectionProps> = ({ projectId, path, method, op
104146
/>
105147
<AvatarFallback>{(note.createdBy || 'U').substring(0, 2).toUpperCase()}</AvatarFallback>
106148
</Avatar>
149+
107150
<span>
108151
{note.createdBy || 'Unknown'}{formatDate(note.createdAt) || 'Unknown date'}
109152
</span>
110153
</div>
111154
</div>
112-
{user?.accessList?.delete && (
113-
<Button
114-
variant="ghost"
115-
size="sm"
116-
onClick={() => handleDeleteNote(index)}
117-
disabled={deleteNoteMutation.isPending}
118-
className="text-red-500 hover:text-red-700 hover:bg-red-50"
119-
>
120-
<Trash2 className="h-4 w-4" />
121-
</Button>
155+
156+
{(isProjectOwner(project) || user?.username === note.createdBy) && (
157+
<AlertDialog>
158+
<AlertDialogTrigger asChild>
159+
<Button
160+
variant="ghost"
161+
size="sm"
162+
onClick={() => setNoteToDelete(index)}
163+
disabled={deleteNoteMutation.isPending}
164+
className="text-red-500 hover:text-red-700 hover:bg-red-50"
165+
>
166+
<Trash2 className="h-4 w-4" />
167+
</Button>
168+
</AlertDialogTrigger>
169+
170+
<AlertDialogContent className="max-w-3xl">
171+
<AlertDialogHeader>
172+
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
173+
174+
<AlertDialogDescription className="py-1 leading-6">
175+
This action cannot be undone. This will permanently delete this note.
176+
</AlertDialogDescription>
177+
</AlertDialogHeader>
178+
179+
<AlertDialogFooter>
180+
<AlertDialogCancel onClick={() => setNoteToDelete(null)}>Cancel</AlertDialogCancel>
181+
<AlertDialogAction
182+
onClick={confirmDeleteNote}
183+
className="bg-destructive hover:bg-destructive/90 text-white"
184+
>
185+
Delete
186+
</AlertDialogAction>
187+
</AlertDialogFooter>
188+
</AlertDialogContent>
189+
</AlertDialog>
122190
)}
123191
</div>
124192
</div>
125193
))}
126194
</div>
127195
)}
128196

129-
{user?.accessList?.write && (
130-
<div className="border-t pt-4 space-y-3">
131-
<Textarea
132-
value={newNoteContent}
133-
onChange={e => setNewNoteContent(e.target.value)}
134-
placeholder="Add a note about this endpoint..."
135-
className="min-h-[100px]"
136-
/>
137-
<Button
138-
onClick={handleAddNote}
139-
disabled={createNoteMutation.isPending || !newNoteContent.trim()}
140-
className="w-full"
141-
>
142-
{createNoteMutation.isPending ? 'Adding Note...' : 'Add Note'}
143-
</Button>
144-
</div>
145-
)}
197+
<div className="border-t pt-4 space-y-3">
198+
<Textarea
199+
value={newNoteContent}
200+
onChange={e => setNewNoteContent(e.target.value)}
201+
placeholder="Add a note about this endpoint..."
202+
className="min-h-[100px]"
203+
/>
204+
205+
<Button onClick={handleAddNote} disabled={createNoteMutation.isPending || !newNoteContent.trim()} className="w-full">
206+
{createNoteMutation.isPending ? 'Adding Note...' : 'Add Note'}
207+
</Button>
208+
</div>
146209
</div>
147210
);
148211
};

src/components/endpoints/EndpointDetail.tsx

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useToast } from '@/hooks/use-toast';
22
import React, { useState, useMemo } from 'react';
3-
import { useAuth } from '@/contexts/AuthContext';
3+
import { useProject } from '@/hooks/api/useProject';
4+
import { usePermissions } from '@/hooks/usePermissions';
45
import { useUpdateEndpoint } from '@/hooks/api/useEndpoints';
56
import { isRefObject, resolveRef, deeplyResolveReferences, formatDate } from '@/utils/schemaUtils';
67
import { OperationObject, OpenAPISpec, ParameterObject, ReferenceObject, RequestBodyObject } from '@/types/types';
@@ -33,8 +34,9 @@ const EndpointDetail: React.FC<EndpointDetailProps> = ({
3334
projectId,
3435
endpointId,
3536
}) => {
36-
const { user } = useAuth();
37+
const { isProjectOwner } = usePermissions();
3738
const { toast } = useToast();
39+
const { data: project } = useProject(projectId);
3840
const [isEditMode, setIsEditMode] = useState(false);
3941
const updateEndpointMutation = useUpdateEndpoint();
4042

@@ -46,7 +48,9 @@ const EndpointDetail: React.FC<EndpointDetailProps> = ({
4648
const op = isRefObject(initialOperationOrRef) ? resolveRef(initialOperationOrRef.$ref, openApiSpec) : initialOperationOrRef;
4749

4850
if (!op || isRefObject(op)) {
49-
return { summary: `Error: Unresolved op ${isRefObject(initialOperationOrRef) ? initialOperationOrRef.$ref : ''}` } as OperationObject;
51+
return {
52+
summary: `Error: Unresolved op ${isRefObject(initialOperationOrRef) ? initialOperationOrRef.$ref : ''}`,
53+
} as OperationObject;
5054
}
5155

5256
return deeplyResolveReferences<OperationObject>(op as OperationObject, openApiSpec);
@@ -99,7 +103,8 @@ const EndpointDetail: React.FC<EndpointDetailProps> = ({
99103

100104
toast({ title: 'Endpoint updated', description: 'The endpoint has been updated successfully' });
101105
setIsEditMode(false);
102-
} catch (error: any) {
106+
}
107+
catch (error: any) {
103108
if (error && typeof error.error === 'string' && error.status !== undefined) {
104109
if (error.status !== 409) {
105110
toast({
@@ -108,7 +113,8 @@ const EndpointDetail: React.FC<EndpointDetailProps> = ({
108113
variant: 'destructive',
109114
});
110115
}
111-
} else {
116+
}
117+
else {
112118
toast({
113119
title: 'Client Error',
114120
description: (error as Error).message || 'An unexpected client-side error occurred.',
@@ -119,7 +125,7 @@ const EndpointDetail: React.FC<EndpointDetailProps> = ({
119125
}
120126
};
121127

122-
if (isEditMode && user?.accessList?.update) {
128+
if (isEditMode && isProjectOwner(project)) {
123129
return (
124130
<Dialog
125131
open={isEditMode}
@@ -155,10 +161,7 @@ const EndpointDetail: React.FC<EndpointDetailProps> = ({
155161
<div className="flex flex-col justify-center items-start space-y-2 sm:space-y-3 flex-shrink-0">
156162
<div className="flex items-center space-x-2 text-sm text-muted-foreground">
157163
<Avatar className="h-6 w-6">
158-
<AvatarImage
159-
src={createdBy !== 'Unknown' ? `/profile-pictures/${createdBy}.png` : undefined}
160-
alt={createdBy}
161-
/>
164+
<AvatarImage src={createdBy !== 'Unknown' ? `/profile-pictures/${createdBy}.png` : undefined} alt={createdBy} />
162165
<AvatarFallback>{createdBy.substring(0, 2).toUpperCase()}</AvatarFallback>
163166
</Avatar>
164167

@@ -170,10 +173,7 @@ const EndpointDetail: React.FC<EndpointDetailProps> = ({
170173
{lastEditedBy && lastEditedAt && (
171174
<div className="flex items-center space-x-2 text-sm text-muted-foreground">
172175
<Avatar className="h-6 w-6">
173-
<AvatarImage
174-
src={lastEditedBy ? `/profile-pictures/${lastEditedBy}.png` : undefined}
175-
alt={lastEditedBy}
176-
/>
176+
<AvatarImage src={lastEditedBy ? `/profile-pictures/${lastEditedBy}.png` : undefined} alt={lastEditedBy} />
177177
<AvatarFallback>{lastEditedBy.substring(0, 2).toUpperCase()}</AvatarFallback>
178178
</Avatar>
179179

@@ -183,12 +183,12 @@ const EndpointDetail: React.FC<EndpointDetailProps> = ({
183183
</div>
184184
)}
185185

186-
{user?.accessList?.update && (
187-
<Button
188-
variant="outline"
189-
size="sm"
190-
onClick={() => setIsEditMode(true)}
191-
className="mt-2 w-full"
186+
{isProjectOwner(project) && (
187+
<Button
188+
variant="outline"
189+
size="sm"
190+
onClick={() => setIsEditMode(true)}
191+
className="mt-2 w-full"
192192
disabled={updateEndpointMutation.isPending}
193193
>
194194
Edit Endpoint
@@ -229,12 +229,7 @@ const EndpointDetail: React.FC<EndpointDetailProps> = ({
229229
</TabsContent>
230230

231231
<TabsContent value="notes">
232-
<NotesSection
233-
projectId={projectId}
234-
path={initialPath}
235-
method={initialMethod}
236-
operation={operation}
237-
/>
232+
<NotesSection projectId={projectId} path={initialPath} method={initialMethod} operation={operation} />
238233
</TabsContent>
239234
</Tabs>
240235
</div>

0 commit comments

Comments
 (0)