diff --git a/frontend/src/components/molecules/Sidebar.tsx b/frontend/src/components/molecules/Sidebar.tsx
index 87cc405..25d1239 100644
--- a/frontend/src/components/molecules/Sidebar.tsx
+++ b/frontend/src/components/molecules/Sidebar.tsx
@@ -7,19 +7,25 @@ import {
} from "@heroicons/react/outline";
import { FC, ReactNode } from "react";
import { useNavigate } from "react-router-dom";
-import useServices from "../../hooks/useServices";
+import { useServicesQuery } from "../../generated/graphql";
import Badge from "../atoms/Badge";
import InputLabel from "../atoms/InputLabel";
+import clsx from "clsx";
+
+const styleClasses = {
+ base: "cursor-pointer inline-flex items-center justify-between w-full px-4 py-2 text-base text-black transition duration-500 ease-in-out transform rounded-lg focus:shadow-outline hover:bg-gray-100",
+};
const SidebarTab: FC<{
onClick?: any;
children?: ReactNode;
badge?: ReactNode;
+ className?: string;
}> = (props) => {
return (
;
onBlur?: React.FocusEventHandler;
+ value?: string;
}
const TextField: FC = (props) => {
@@ -33,6 +34,7 @@ const TextField: FC = (props) => {
size={props.size}
onChange={props.onChange}
onBlur={props.onBlur}
+ value={props.value}
/>
diff --git a/frontend/src/components/pages/login/index.tsx b/frontend/src/components/pages/login/index.tsx
index e7208b6..730fbd8 100644
--- a/frontend/src/components/pages/login/index.tsx
+++ b/frontend/src/components/pages/login/index.tsx
@@ -1,3 +1,4 @@
+import { useFormik } from "formik";
import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
@@ -7,18 +8,19 @@ import Button from "../../atoms/Button";
import TextField from "../../molecules/TextField";
const LoginPage = () => {
- const {
- handleSubmit,
- control,
- formState: { errors },
- } = useForm();
const navigator = useNavigate();
const loginMutation = useLoginMutation();
const [submitError, setSubmitError] = useState("");
- const onSubmit = ({ name, password }: any) => {
- loginMutation.mutate({ credentials: { name, password } });
- };
+ const loginForm = useFormik({
+ initialValues: {
+ name: "",
+ password: "",
+ },
+ onSubmit: ({ name, password }) => {
+ loginMutation.mutate({ credentials: { name, password } });
+ },
+ });
useEffect(() => {
if (!loginMutation.data) return;
@@ -48,35 +50,31 @@ const LoginPage = () => {
diff --git a/frontend/src/components/pages/middleware/index.tsx b/frontend/src/components/pages/middleware/index.tsx
deleted file mode 100644
index 135326e..0000000
--- a/frontend/src/components/pages/middleware/index.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import DashboardLayout from "../../layouts/Dashboard";
-
-const MiddlewarePage = () => {
- return (
-
- Middleware
-
- );
-};
-
-export default MiddlewarePage;
diff --git a/frontend/src/components/pages/services/index.tsx b/frontend/src/components/pages/services/index.tsx
index 18d886f..9284c33 100644
--- a/frontend/src/components/pages/services/index.tsx
+++ b/frontend/src/components/pages/services/index.tsx
@@ -1,39 +1,109 @@
import { PlusIcon } from "@heroicons/react/outline";
import clsx from "clsx";
-import { useState } from "react";
-import useServices from "../../../hooks/useServices";
+import { useEffect, useMemo, useState } from "react";
import Button from "../../atoms/Button";
import InlineLink from "../../atoms/InlineLink";
import Container from "../../layouts/Container";
import DashboardLayout from "../../layouts/Dashboard";
import TextField from "../../molecules/TextField";
+import {
+ useServicesQuery,
+ useRemoveServiceMutation,
+ useAddServiceMutation,
+ Service,
+} from "../../../generated/graphql";
+import { queryClient } from "../../../main";
+import { useFormik } from "formik";
const ServicesPage = () => {
const [toggleCreate, setToggleCreate] = useState(false);
- const services = useServices();
+ const [searchQuery, setSearchQuery] = useState("");
+ const services = useServicesQuery();
- const handleToggleCreate = () => {
- setToggleCreate(!toggleCreate);
+ const serviceForm = useFormik({
+ initialValues: {
+ name: "",
+ destination: "",
+ },
+ onSubmit: ({ name, destination }, helpers) => {
+ addService.mutate({ name, loadbalancer: { servers: [destination] } });
+ helpers.resetForm();
+ },
+ });
+
+ // TODO: Figure out what I am doing wrong with optimistic updating (removeService and addService)
+ const removeService = useRemoveServiceMutation({
+ onMutate: async ({ id }) => {
+ const queryKey = ["Services"];
+ await queryClient.cancelQueries(queryKey);
+ const prevServices = queryClient.getQueryData(queryKey);
+ queryClient.setQueryData(queryKey, (old) =>
+ old.services.filter((s: Service) => s.id !== id)
+ );
+ return { prevServices };
+ },
+ onError: (err, newTodo, ctx) => {
+ const queryKey = ["Services"];
+ queryClient.setQueryData(queryKey, ctx.prevServices);
+ },
+ onSettled: () => {
+ const queryKey = ["Services"];
+ queryClient.invalidateQueries(queryKey);
+ },
+ });
+
+ const addService = useAddServiceMutation({
+ onMutate: async (newService) => {
+ const queryKey = ["Services"];
+ await queryClient.cancelQueries(queryKey);
+ const prevServices = queryClient.getQueryData(queryKey);
+ queryClient.setQueryData(queryKey, (old) => [
+ ...old.services,
+ newService,
+ ]);
+ return { prevServices };
+ },
+ onError: (err, newTodo, ctx) => {
+ const queryKey = ["Services"];
+ queryClient.setQueryData(queryKey, ctx.prevServices);
+ },
+ onSettled: () => {
+ const queryKey = ["Services"];
+ queryClient.invalidateQueries(queryKey);
+ },
+ });
+
+ const deleteService = (serviceId: number) => {
+ return (ev: React.MouseEvent
) => {
+ ev.preventDefault();
+ removeService.mutate({ id: serviceId });
+ };
};
+ const filteredServices = useMemo(() => {
+ if (!services.data?.services) return [];
+ return services.data.services.filter((service) => {
+ return service.name.toLowerCase().includes(searchQuery.toLowerCase());
+ });
+ }, [services, searchQuery]);
+
return (
-
+
Services
setSearchQuery(ev.target.value)}
/>