diff --git a/pkgs/ui/src/app/layout.tsx b/pkgs/ui/src/app/layout.tsx
index f541db665..5bd3b55c2 100644
--- a/pkgs/ui/src/app/layout.tsx
+++ b/pkgs/ui/src/app/layout.tsx
@@ -1,24 +1,17 @@
"use client";
import { Sidebar } from "@/components/sidebar";
import { tw } from "@/utils/tailwind";
-import MenuIcon from "@mui/icons-material/Menu";
-import {
- CssBaseline,
- IconButton,
- ThemeProvider,
- useMediaQuery,
-} from "@mui/material";
+import { CssBaseline, ThemeProvider, useMediaQuery } from "@mui/material";
import { StyledEngineProvider } from "@mui/material/styles";
import axios from "axios";
import localFont from "next/font/local";
-import Image from "next/image";
import * as React from "react";
import { Toaster } from "react-hot-toast";
import "./globals.css";
import { darkTheme, lightTheme } from "./theme/themes";
-import { WithAppState } from "@/components/hooks/useAppContext";
import { ClanToolbar } from "@/components/clanToolbar";
+import { WithAppState } from "@/components/hooks/useAppContext";
const roboto = localFont({
src: [
@@ -70,29 +63,10 @@ export default function RootLayout({
!showSidebar && translate
} flex h-full w-full flex-col overflow-y-scroll transition-[margin] duration-150 ease-in-out`}
>
-
-
-
-
- setShowSidebar((c) => !c)}
- >
- {!showSidebar && }
-
-
-
-
-
-
-
-
+
{children}
diff --git a/pkgs/ui/src/app/manage/create/page.tsx b/pkgs/ui/src/app/manage/create/page.tsx
new file mode 100644
index 000000000..1737ad7ac
--- /dev/null
+++ b/pkgs/ui/src/app/manage/create/page.tsx
@@ -0,0 +1,59 @@
+"use client";
+import { createFlake } from "@/api/flake/flake";
+import { HTTPValidationError } from "@/api/model";
+import { CreateClan, CreateFormValues } from "@/components/forms/createClan";
+import { clanErrorToast } from "@/error/errorToast";
+import { AxiosError } from "axios";
+import { FormProvider, useForm } from "react-hook-form";
+
+export default function Manage() {
+ const methods = useForm
({
+ defaultValues: {
+ flakeDir: "",
+ flakeTemplateUrl: "",
+ },
+ });
+ const { handleSubmit } = methods;
+ return (
+
+
+
+ );
+}
diff --git a/pkgs/ui/src/app/join/page.tsx b/pkgs/ui/src/app/manage/join/page.tsx
similarity index 88%
rename from pkgs/ui/src/app/join/page.tsx
rename to pkgs/ui/src/app/manage/join/page.tsx
index a693cb70a..f0c76c500 100644
--- a/pkgs/ui/src/app/join/page.tsx
+++ b/pkgs/ui/src/app/manage/join/page.tsx
@@ -1,3 +1,4 @@
+"use client";
import JoinPrequel from "@/views/joinPrequel";
export default function Page() {
diff --git a/pkgs/ui/src/app/manage/page.tsx b/pkgs/ui/src/app/manage/page.tsx
new file mode 100644
index 000000000..cb74fa79f
--- /dev/null
+++ b/pkgs/ui/src/app/manage/page.tsx
@@ -0,0 +1,22 @@
+import { Button } from "@mui/material";
+import Link from "next/link";
+
+export default function Manage() {
+ return (
+
+ Select
+
+
+
+ - History
+ - Recent History
+ - Ancient History
+ - Cosmic History
+
+
+ );
+}
diff --git a/pkgs/ui/src/components/clanToolbar/index.tsx b/pkgs/ui/src/components/clanToolbar/index.tsx
index 53ea56144..b5f51c7d2 100644
--- a/pkgs/ui/src/components/clanToolbar/index.tsx
+++ b/pkgs/ui/src/components/clanToolbar/index.tsx
@@ -1,6 +1,7 @@
import { useFlakeHistoryList } from "@/api/flake/flake";
import DynamicFeedIcon from "@mui/icons-material/DynamicFeed";
-import { IconButton, LinearProgress } from "@mui/material";
+import MenuIcon from "@mui/icons-material/Menu";
+import { Button, Divider, LinearProgress } from "@mui/material";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import * as React from "react";
@@ -9,57 +10,112 @@ interface ToolbarButtonProps {
icon: React.ReactNode;
onClick: (event: React.MouseEvent) => void;
}
-function ToolbarButton(props: ToolbarButtonProps) {
+export function ToolbarButton(props: ToolbarButtonProps) {
const { icon, onClick } = props;
return (
-
- {icon}
-
+
);
}
+
+const ClanHistoryMenu = () => {
+ const { data, isLoading } = useFlakeHistoryList();
+
+ return (
+ <>
+ {isLoading ? (
+
+ ) : (
+ data?.data.map((item, index) => )
+ )}
+ {!isLoading && data?.data.length === 0 && (
+
+ )}
+ >
+ );
+};
+
type ToolbarItem = {
icon: React.ReactNode;
+ menu: React.ReactNode;
};
const toolbarItems: ToolbarItem[] = [
{
icon: ,
+ menu: ,
},
];
-export function ClanToolbar() {
- const { data, isLoading } = useFlakeHistoryList();
+
+interface ClanToolbarProps {
+ isSidebarVisible: boolean;
+ handleSidebar: React.Dispatch>;
+}
+export function ClanToolbar(props: ClanToolbarProps) {
+ const { isSidebarVisible, handleSidebar } = props;
+
const [anchorEl, setAnchorEl] = React.useState(null);
- const open = Boolean(anchorEl);
- const handleClick = (event: React.MouseEvent) => {
+ const [openIdx, setOpenIdx] = React.useState(null);
+
+ const handleClick = (
+ event: React.MouseEvent,
+ idx: number,
+ ) => {
setAnchorEl(event.currentTarget);
+ setOpenIdx(idx);
};
const handleClose = () => {
setAnchorEl(null);
+ setOpenIdx(null);
};
return (
-
- {toolbarItems.map((item, index) => (
-
- ))}
-
);
}
diff --git a/pkgs/ui/src/components/forms/createClan/index.tsx b/pkgs/ui/src/components/forms/createClan/index.tsx
new file mode 100644
index 000000000..0f542537d
--- /dev/null
+++ b/pkgs/ui/src/components/forms/createClan/index.tsx
@@ -0,0 +1,77 @@
+import CopyAllIcon from "@mui/icons-material/CopyAll";
+import SaveAltIcon from "@mui/icons-material/SaveAlt";
+import {
+ Button,
+ InputAdornment,
+ LinearProgress,
+ TextField,
+} from "@mui/material";
+import { Controller, UseFormReturn } from "react-hook-form";
+
+export type CreateFormValues = {
+ flakeTemplateUrl: string;
+ flakeDir: string;
+};
+
+interface CreateClanProps {
+ confirmAdornment?: React.ReactNode;
+ methods: UseFormReturn;
+}
+
+export const CreateClan = (props: CreateClanProps) => {
+ const { methods } = props;
+ const {
+ control,
+ formState: { isSubmitting },
+ } = methods;
+ return (
+
+ (
+
+
+
+ ),
+ }}
+ helperText={fieldState.error?.message}
+ {...field}
+ />
+ )}
+ />
+ (
+
+
+
+ ),
+ }}
+ helperText={fieldState.error?.message}
+ {...field}
+ />
+ )}
+ />
+
+ {isSubmitting && }
+
+ );
+};
diff --git a/pkgs/ui/src/components/sidebar/index.tsx b/pkgs/ui/src/components/sidebar/index.tsx
index d46f3c62d..086478c78 100644
--- a/pkgs/ui/src/components/sidebar/index.tsx
+++ b/pkgs/ui/src/components/sidebar/index.tsx
@@ -41,7 +41,7 @@ const menuEntries: MenuEntry[] = [
{
icon: ,
label: "Manage",
- to: "/join",
+ to: "/manage",
},
{
icon: ,
diff --git a/pkgs/ui/src/error/errorToast.ts b/pkgs/ui/src/error/errorToast.ts
index c6f2ad3ed..4dec64432 100644
--- a/pkgs/ui/src/error/errorToast.ts
+++ b/pkgs/ui/src/error/errorToast.ts
@@ -5,8 +5,10 @@ import { toast } from "react-hot-toast";
export function clanErrorToast(error: AxiosError) {
console.error({ error });
const detail = error.response?.data.detail?.[0]?.msg;
+ const detailAlt = error.response?.data.detail as unknown as string;
const cause = error.cause?.message;
const axiosMessage = error.message;
- const sanitizedMsg = detail || cause || axiosMessage || "Unexpected error";
+ const sanitizedMsg =
+ detail || detailAlt || cause || axiosMessage || "Unexpected error";
toast.error(sanitizedMsg);
}
diff --git a/pkgs/ui/src/views/createForm.tsx b/pkgs/ui/src/views/createForm.tsx
deleted file mode 100644
index 8c80dee73..000000000
--- a/pkgs/ui/src/views/createForm.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Input, InputAdornment, LinearProgress } from "@mui/material";
-import { Controller, useFormContext } from "react-hook-form";
-
-interface CreateFormProps {
- confirmAdornment?: React.ReactNode;
-}
-
-export const CreateForm = (props: CreateFormProps) => {
- const { confirmAdornment } = props;
- const {
- control,
- formState: { isSubmitting },
- } = useFormContext();
- return (
-
- (
- Clan
- }
- />
- )}
- />
- (
- Name
- }
- endAdornment={confirmAdornment}
- />
- )}
- />
- {isSubmitting && }
-
- );
-};
diff --git a/pkgs/ui/src/views/joinForm.tsx b/pkgs/ui/src/views/joinForm.tsx
index 83f3edef8..586c553a4 100644
--- a/pkgs/ui/src/views/joinForm.tsx
+++ b/pkgs/ui/src/views/joinForm.tsx
@@ -1,16 +1,16 @@
import { Confirm } from "@/components/join/confirm";
+import PublicIcon from "@mui/icons-material/Public";
import { Input, InputAdornment } from "@mui/material";
import { Controller, useFormContext } from "react-hook-form";
interface JoinFormProps {
- confirmAdornment?: React.ReactNode;
initialParams: {
flakeUrl: string;
flakeAttr: string;
};
}
export const JoinForm = (props: JoinFormProps) => {
- const { initialParams, confirmAdornment } = props;
+ const { initialParams } = props;
const { control, formState, reset, getValues, watch } = useFormContext();
return (
@@ -38,10 +38,11 @@ export const JoinForm = (props: JoinFormProps) => {
{...field}
required
fullWidth
- startAdornment={
- Clan
+ endAdornment={
+
+
+
}
- endAdornment={confirmAdornment}
/>
)}
/>
diff --git a/pkgs/ui/src/views/joinPrequel.tsx b/pkgs/ui/src/views/joinPrequel.tsx
index 5d5089d96..d845d5f45 100644
--- a/pkgs/ui/src/views/joinPrequel.tsx
+++ b/pkgs/ui/src/views/joinPrequel.tsx
@@ -1,29 +1,18 @@
"use client";
-import {
- IconButton,
- InputAdornment,
- MenuItem,
- Select,
- Typography,
-} from "@mui/material";
+import { Button, Typography } from "@mui/material";
import { useSearchParams } from "next/navigation";
import { Suspense, useState } from "react";
-import { createFlake } from "@/api/flake/flake";
import { VmConfig } from "@/api/model";
import { createVm } from "@/api/vm/vm";
-import { useAppState } from "@/components/hooks/useAppContext";
import { Layout } from "@/components/join/layout";
import { VmBuildLogs } from "@/components/join/vmBuildLogs";
-import { ChevronRight } from "@mui/icons-material";
import { AxiosError } from "axios";
-import { Controller, FormProvider, useForm } from "react-hook-form";
+import { FormProvider, useForm } from "react-hook-form";
import { toast } from "react-hot-toast";
-import { CreateForm } from "./createForm";
import { JoinForm } from "./joinForm";
export type FormValues = VmConfig & {
- workflow: "join" | "create";
flakeUrl: string;
dest?: string;
};
@@ -34,48 +23,21 @@ export default function JoinPrequel() {
const flakeAttr = queryParams.get("attr") || "default";
const initialParams = { flakeUrl, flakeAttr };
- const { setAppState } = useAppState();
-
const methods = useForm({
defaultValues: {
flakeUrl: "",
dest: undefined,
- workflow: "join",
cores: 4,
graphics: true,
memory_size: 2048,
},
});
- const { control, watch, handleSubmit } = methods;
+ const { handleSubmit } = methods;
const [vmUuid, setVmUuid] = useState(null);
const [showLogs, setShowLogs] = useState(false);
- const workflow = watch("workflow");
-
- const WorkflowAdornment = (
-
- (
-
- )}
- />
-
-
-
-
- );
return (
- {workflow}{" "}
-
- Clan.lol
-
+ Clan.lol
}
>
@@ -98,60 +57,32 @@ export default function JoinPrequel() {
)}