Skip to content

Commit dc142b3

Browse files
authored
Merge pull request #4 from elixir-cloud-aai/pages
feat: added service updation page
2 parents a5e3858 + 86d032e commit dc142b3

25 files changed

Lines changed: 952 additions & 9 deletions

bun.lock

Lines changed: 17 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
},
1313
"dependencies": {
1414
"@elixir-cloud/cloud-registry": "^2.0.0-alpha.48",
15+
"@tanstack/react-form": "^1.29.1",
1516
"axios": "^1.15.2",
1617
"class-variance-authority": "^0.7.1",
1718
"clsx": "^2.1.1",
@@ -25,7 +26,8 @@
2526
"shadcn": "^4.4.0",
2627
"sonner": "^2.0.7",
2728
"tailwind-merge": "^3.5.0",
28-
"tw-animate-css": "^1.4.0"
29+
"tw-animate-css": "^1.4.0",
30+
"zod": "^4.4.3"
2931
},
3032
"devDependencies": {
3133
"@tailwindcss/postcss": "^4",

src/app/auth/error/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ async function Page({ searchParams }: Props) {
1111
if (type === "params") message = "Invalid request parameters found.";
1212
else if (type === "state") message = "Invalid state found. State does not match.";
1313
else if (type === "logout-err")
14-
message = "An internal server error occurred while logging you out. Please try logging out again.";
14+
message =
15+
"An internal server error occurred while logging you out. Please try logging out again.";
1516

1617
return (
1718
<div className="flex w-full h-screen justify-center items-center">

src/app/dashboard/services/_components/table-body.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ type Props = {
1212
function ServicesTableBody({ services, fetchServices }: Props) {
1313
const { cloudRegistryProvider } = useCloudRegistry();
1414
const [selectedService, setSelectedService] = useState<ExternalService | null>(null);
15-
15+
1616
if (services.length === 0)
1717
return (
1818
<TableBody>

src/app/dashboard/services/_components/view-service-dailog.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "@/components/ui/dialog";
1111
import { AuthenticatedCloudRegistryProvider } from "@/lib/authenticated-cloud-registry";
1212
import { ExternalService } from "@elixir-cloud/cloud-registry/providers";
13+
import Link from "next/link";
1314
import { useState } from "react";
1415
import { toast } from "sonner";
1516
import ViewServiceDialogBody from "./view-service-dailog-body";
@@ -67,8 +68,8 @@ function ServiceDetailsDialog({
6768
<ViewServiceDialogBody service={service} />
6869

6970
<DialogFooter className="px-4 py-4 border-t">
70-
<Button size="sm" onClick={() => onOpenChange(false)}>
71-
Close
71+
<Button size="sm" asChild>
72+
<Link href={`/dashboard/update-service/${service.id}`}>Edit</Link>
7273
</Button>
7374
<Button
7475
size="sm"
Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,48 @@
1+
"use client";
2+
import { useCloudRegistry } from "@/hooks";
3+
import { ExternalService } from "@elixir-cloud/cloud-registry/providers";
4+
import { useParams } from "next/navigation";
5+
import { useEffect, useState } from "react";
6+
import { toast } from "sonner";
7+
import { ServiceUpdationForm } from "../_components";
8+
19
function Page() {
2-
return <div></div>;
10+
const { id } = useParams();
11+
const [loading, setLoading] = useState(true);
12+
const { cloudRegistryProvider } = useCloudRegistry();
13+
const [service, setService] = useState<ExternalService>();
14+
15+
useEffect(() => {
16+
if (!id) return;
17+
cloudRegistryProvider
18+
?.getServiceById(id as string)
19+
.then((_val) => {
20+
setService(_val);
21+
console.log(_val);
22+
})
23+
.catch(() => toast.error("Cannot fetch the service details. Please try again later."))
24+
.finally(() => setLoading(false));
25+
}, [cloudRegistryProvider, id]);
26+
27+
if (loading)
28+
return (
29+
<div className="w-full flex justify-center items-center h-full">
30+
<p>Loading...</p>
31+
</div>
32+
);
33+
34+
if (loading === false && !service)
35+
return (
36+
<div className="w-full flex justify-center items-center h-full">
37+
<p>No service data found. Please try again later.</p>
38+
</div>
39+
);
40+
41+
return (
42+
<div className="max-w-5xl mx-auto mt-8">
43+
{service && <ServiceUpdationForm service={service} />}{" "}
44+
</div>
45+
);
346
}
447

548
export default Page;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
2+
import {
3+
Select,
4+
SelectContent,
5+
SelectItem,
6+
SelectTrigger,
7+
SelectValue,
8+
} from "@/components/ui/select";
9+
10+
type Option = {
11+
label: string;
12+
value: string;
13+
};
14+
15+
type SelectFieldProps = {
16+
form: any;
17+
name: string;
18+
label: string;
19+
placeholder?: string;
20+
options: Option[];
21+
};
22+
23+
export function SelectField({ form, name, label, placeholder, options }: SelectFieldProps) {
24+
return (
25+
<form.Field name={name}>
26+
{(field: any) => {
27+
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid;
28+
29+
return (
30+
<Field data-invalid={isInvalid}>
31+
<FieldLabel htmlFor={field.name}>{label}</FieldLabel>
32+
33+
<Select
34+
value={field.state.value}
35+
onValueChange={(value) => field.handleChange(value)}
36+
>
37+
<SelectTrigger aria-invalid={isInvalid} id={field.name}>
38+
<SelectValue placeholder={placeholder} />
39+
</SelectTrigger>
40+
41+
<SelectContent>
42+
{options.map((option) => (
43+
<SelectItem key={option.value} value={option.value}>
44+
{option.label}
45+
</SelectItem>
46+
))}
47+
</SelectContent>
48+
</Select>
49+
50+
{isInvalid && <FieldError errors={field.state.meta.errors} />}
51+
</Field>
52+
);
53+
}}
54+
</form.Field>
55+
);
56+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { SelectField } from "./dropdown-field";
2+
import { TextField } from "./text-field";
3+
4+
export { SelectField, TextField };
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
2+
import { Input } from "@/components/ui/input";
3+
4+
type TextFieldProps = {
5+
form: any;
6+
name: string;
7+
label: string;
8+
placeholder?: string;
9+
};
10+
11+
export function TextField({ form, name, label, placeholder }: TextFieldProps) {
12+
return (
13+
<form.Field name={name}>
14+
{(field: any) => {
15+
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid;
16+
17+
return (
18+
<Field data-invalid={isInvalid}>
19+
<FieldLabel htmlFor={field.name}>{label}</FieldLabel>
20+
21+
<Input
22+
id={field.name}
23+
name={field.name}
24+
value={field.state.value}
25+
onBlur={field.handleBlur}
26+
onChange={(e) => field.handleChange(e.target.value)}
27+
placeholder={placeholder}
28+
aria-invalid={isInvalid}
29+
/>
30+
31+
{isInvalid && <FieldError errors={field.state.meta.errors} />}
32+
</Field>
33+
);
34+
}}
35+
</form.Field>
36+
);
37+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
2+
import { Textarea } from "@/components/ui/textarea";
3+
4+
type TextareaFieldProps = {
5+
form: any;
6+
name: string;
7+
label: string;
8+
placeholder?: string;
9+
rows?: number;
10+
className?: string;
11+
};
12+
13+
export function TextareaField({
14+
form,
15+
name,
16+
label,
17+
placeholder,
18+
rows = 5,
19+
className,
20+
}: TextareaFieldProps) {
21+
return (
22+
<div className={className}>
23+
<form.Field name={name}>
24+
{(field: any) => {
25+
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid;
26+
27+
return (
28+
<Field data-invalid={isInvalid}>
29+
<FieldLabel htmlFor={field.name}>{label}</FieldLabel>
30+
31+
<Textarea
32+
id={field.name}
33+
name={field.name}
34+
value={field.state.value ?? ""}
35+
onBlur={field.handleBlur}
36+
onChange={(e) => field.handleChange(e.target.value)}
37+
placeholder={placeholder}
38+
rows={rows}
39+
aria-invalid={isInvalid}
40+
/>
41+
42+
{isInvalid && <FieldError errors={field.state.meta.errors} />}
43+
</Field>
44+
);
45+
}}
46+
</form.Field>
47+
</div>
48+
);
49+
}

0 commit comments

Comments
 (0)