Skip to content

Commit 7d7d514

Browse files
committed
refactor: improve layout and loading state handling in Metrics and Connection components
1 parent 5b8d526 commit 7d7d514

File tree

7 files changed

+419
-367
lines changed

7 files changed

+419
-367
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
// ConnectionManager.tsx
2+
import React from "react";
3+
import {
4+
Dialog,
5+
DialogContent,
6+
DialogDescription,
7+
DialogHeader,
8+
DialogTitle,
9+
DialogFooter,
10+
} from "@/components/ui/dialog";
11+
import { zodResolver } from "@hookform/resolvers/zod";
12+
import { useForm } from "react-hook-form";
13+
import * as z from "zod";
14+
import {
15+
Form,
16+
FormControl,
17+
FormField,
18+
FormItem,
19+
FormLabel,
20+
FormMessage,
21+
} from "@/components/ui/form";
22+
import { Input } from "@/components/ui/input";
23+
import {
24+
Select,
25+
SelectContent,
26+
SelectItem,
27+
SelectTrigger,
28+
SelectValue,
29+
} from "@/components/ui/select";
30+
import { Button } from "@/components/ui/button";
31+
import { ScrollArea } from "@/components/ui/scroll-area";
32+
import { useDuckStore } from "@/store";
33+
34+
const connectionSchema = z.object({
35+
name: z
36+
.string()
37+
.min(2, {
38+
message: "Connection name must be at least 2 characters.",
39+
})
40+
.max(30, {
41+
message: "Connection name must not exceed 30 characters.",
42+
}),
43+
scope: z.enum(["External"]),
44+
host: z.string().url({
45+
message: "Host must be a valid URL.",
46+
}),
47+
port: z
48+
.string()
49+
.refine((val) => !isNaN(parseInt(val, 10)) || val === "", {
50+
//Allow empty string
51+
message: "Port must be a number.",
52+
})
53+
.optional(),
54+
database: z.string().optional(),
55+
user: z.string().optional(),
56+
password: z.string().optional(),
57+
authMode: z.enum(["none", "password", "api_key"]).optional(),
58+
apiKey: z.string().optional(),
59+
});
60+
61+
type ConnectionFormValues = z.infer<typeof connectionSchema>;
62+
63+
interface ConnectionManagerProps {
64+
open: boolean;
65+
onOpenChange: (open: boolean) => void;
66+
onSubmit: (values: ConnectionFormValues) => Promise<void>; // Change to Promise<void>
67+
initialValues?: ConnectionFormValues;
68+
isEditMode?: boolean; // Add isEditMode prop
69+
}
70+
71+
const ConnectionManager: React.FC<ConnectionManagerProps> = ({
72+
open,
73+
onOpenChange,
74+
onSubmit,
75+
initialValues,
76+
isEditMode = false, // Default value for isEditMode
77+
}) => {
78+
const form = useForm<ConnectionFormValues>({
79+
resolver: zodResolver(connectionSchema),
80+
defaultValues: initialValues || {
81+
name: "",
82+
scope: "External",
83+
host: "",
84+
port: "",
85+
database: "",
86+
user: "",
87+
password: "",
88+
authMode: "none",
89+
apiKey: "",
90+
},
91+
mode: "onChange",
92+
});
93+
94+
const { isLoadingExternalConnection } = useDuckStore();
95+
96+
const handleSubmit = async (values: ConnectionFormValues) => {
97+
await onSubmit(values);
98+
form.reset();
99+
onOpenChange(false);
100+
};
101+
102+
return (
103+
<Dialog open={open} onOpenChange={onOpenChange}>
104+
<DialogContent className="sm:max-w-[500px]">
105+
<DialogHeader>
106+
<DialogTitle>
107+
{isEditMode ? "Edit Connection" : "Add New Connection"}
108+
</DialogTitle>
109+
<DialogDescription>
110+
{isEditMode
111+
? "Modify existing connection details."
112+
: "Create a new database connection."}
113+
</DialogDescription>
114+
</DialogHeader>
115+
<ScrollArea className="h-[500px]">
116+
<Form {...form}>
117+
<form
118+
onSubmit={form.handleSubmit(handleSubmit)}
119+
className="space-y-4 p-2"
120+
>
121+
<FormField
122+
control={form.control}
123+
name="name"
124+
render={({ field }) => (
125+
<FormItem>
126+
<FormLabel>Connection Name</FormLabel>
127+
<FormControl>
128+
<Input placeholder="My Database Connection" {...field} />
129+
</FormControl>
130+
<FormMessage />
131+
</FormItem>
132+
)}
133+
/>
134+
<FormField
135+
control={form.control}
136+
name="scope"
137+
render={({ field }) => (
138+
<FormItem>
139+
<FormLabel>Connection Scope</FormLabel>
140+
<Select
141+
onValueChange={field.onChange}
142+
defaultValue={field.value}
143+
>
144+
<FormControl>
145+
<SelectTrigger>
146+
<SelectValue placeholder="Select Scope" />
147+
</SelectTrigger>
148+
</FormControl>
149+
<SelectContent>
150+
<SelectItem value="External">External</SelectItem>
151+
</SelectContent>
152+
</Select>
153+
<FormMessage />
154+
</FormItem>
155+
)}
156+
/>
157+
158+
{form.watch("scope") === "External" && (
159+
<>
160+
<FormField
161+
control={form.control}
162+
name="host"
163+
render={({ field }) => (
164+
<FormItem>
165+
<FormLabel>Host</FormLabel>
166+
<FormControl>
167+
<Input placeholder="localhost" {...field} />
168+
</FormControl>
169+
<FormMessage />
170+
</FormItem>
171+
)}
172+
/>
173+
<FormField
174+
control={form.control}
175+
name="port"
176+
render={({ field }) => (
177+
<FormItem>
178+
<FormLabel>Port</FormLabel>
179+
<FormControl>
180+
<Input placeholder="8123" {...field} />
181+
</FormControl>
182+
<FormMessage />
183+
</FormItem>
184+
)}
185+
/>
186+
<FormField
187+
control={form.control}
188+
name="database"
189+
render={({ field }) => (
190+
<FormItem>
191+
<FormLabel>Database</FormLabel>
192+
<FormControl>
193+
<Input placeholder="my_database" {...field} />
194+
</FormControl>
195+
<FormMessage />
196+
</FormItem>
197+
)}
198+
/>
199+
<FormField
200+
control={form.control}
201+
name="authMode"
202+
render={({ field }) => (
203+
<FormItem>
204+
<FormLabel>Authentication Mode</FormLabel>
205+
<Select
206+
onValueChange={field.onChange}
207+
defaultValue={field.value}
208+
>
209+
<FormControl>
210+
<SelectTrigger>
211+
<SelectValue placeholder="Select auth mode" />
212+
</SelectTrigger>
213+
</FormControl>
214+
<SelectContent>
215+
<SelectItem value="none">None</SelectItem>
216+
<SelectItem value="password">Password</SelectItem>
217+
<SelectItem value="api_key">API Key</SelectItem>
218+
</SelectContent>
219+
</Select>
220+
<FormMessage />
221+
</FormItem>
222+
)}
223+
/>
224+
225+
{form.watch("authMode") === "password" && (
226+
<>
227+
<FormField
228+
control={form.control}
229+
name="user"
230+
render={({ field }) => (
231+
<FormItem>
232+
<FormLabel>Username</FormLabel>
233+
<FormControl>
234+
<Input placeholder="database_user" {...field} />
235+
</FormControl>
236+
<FormMessage />
237+
</FormItem>
238+
)}
239+
/>
240+
<FormField
241+
control={form.control}
242+
name="password"
243+
render={({ field }) => (
244+
<FormItem>
245+
<FormLabel>Password</FormLabel>
246+
<FormControl>
247+
<Input
248+
type="password"
249+
placeholder="••••••••"
250+
{...field}
251+
/>
252+
</FormControl>
253+
<FormMessage />
254+
</FormItem>
255+
)}
256+
/>
257+
</>
258+
)}
259+
260+
{form.watch("authMode") === "api_key" && (
261+
<FormField
262+
control={form.control}
263+
name="apiKey"
264+
render={({ field }) => (
265+
<FormItem>
266+
<FormLabel>API Key</FormLabel>
267+
<FormControl>
268+
<Input
269+
type="password"
270+
placeholder="Enter your API key"
271+
{...field}
272+
/>
273+
</FormControl>
274+
<FormMessage />
275+
</FormItem>
276+
)}
277+
/>
278+
)}
279+
</>
280+
)}
281+
<DialogFooter>
282+
<Button
283+
type="button"
284+
variant="secondary"
285+
onClick={() => onOpenChange(false)}
286+
disabled={isLoadingExternalConnection}
287+
>
288+
Cancel
289+
</Button>
290+
<Button type="submit" disabled={isLoadingExternalConnection}>
291+
{isEditMode ? "Update Connection" : "Create Connection"}
292+
</Button>
293+
</DialogFooter>
294+
</form>
295+
</Form>
296+
</ScrollArea>
297+
</DialogContent>
298+
</Dialog>
299+
);
300+
};
301+
302+
export default ConnectionManager;

src/components/connection/Disclaimer.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export const ConnectionDisclaimer = () => {
6161
You can use the following connection string to connect to a
6262
server (Quackpy) that has the HTTP Server Extension enabled:
6363
<pre className="text-sm bg-secondary rounded-md p-2 mt-2">
64-
<code>Host: https://quackpy.fly.dev</code>
64+
<code>Host: https://quackpy.fly.dev</code>
6565
<br />
6666
<code>Port: 443</code>
6767
<br />
@@ -186,8 +186,7 @@ export const ConnectionDisclaimer = () => {
186186
</div>
187187
</ScrollArea>
188188

189-
<DialogFooter className="mt-4">
190-
<Button onClick={() => setOpen(false)}>I Understand</Button>
189+
<DialogFooter className="gap-2">
191190
<Button
192191
onClick={() => {
193192
// open issue link
@@ -200,6 +199,7 @@ export const ConnectionDisclaimer = () => {
200199
<GithubIcon className="h-4 w-4" />
201200
Report Issue
202201
</Button>
202+
<Button onClick={() => setOpen(false)}>I Understand</Button>
203203
</DialogFooter>
204204
</DialogContent>
205205
</Dialog>

0 commit comments

Comments
 (0)