Merge pull request 'extend toolbar styling, add support for atomic menu' (#539) from hsjobeki-main into main
This commit is contained in:
@@ -1,24 +1,17 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { Sidebar } from "@/components/sidebar";
|
import { Sidebar } from "@/components/sidebar";
|
||||||
import { tw } from "@/utils/tailwind";
|
import { tw } from "@/utils/tailwind";
|
||||||
import MenuIcon from "@mui/icons-material/Menu";
|
import { CssBaseline, ThemeProvider, useMediaQuery } from "@mui/material";
|
||||||
import {
|
|
||||||
CssBaseline,
|
|
||||||
IconButton,
|
|
||||||
ThemeProvider,
|
|
||||||
useMediaQuery,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { StyledEngineProvider } from "@mui/material/styles";
|
import { StyledEngineProvider } from "@mui/material/styles";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import localFont from "next/font/local";
|
import localFont from "next/font/local";
|
||||||
import Image from "next/image";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Toaster } from "react-hot-toast";
|
import { Toaster } from "react-hot-toast";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { darkTheme, lightTheme } from "./theme/themes";
|
import { darkTheme, lightTheme } from "./theme/themes";
|
||||||
|
|
||||||
import { WithAppState } from "@/components/hooks/useAppContext";
|
|
||||||
import { ClanToolbar } from "@/components/clanToolbar";
|
import { ClanToolbar } from "@/components/clanToolbar";
|
||||||
|
import { WithAppState } from "@/components/hooks/useAppContext";
|
||||||
|
|
||||||
const roboto = localFont({
|
const roboto = localFont({
|
||||||
src: [
|
src: [
|
||||||
@@ -70,29 +63,10 @@ export default function RootLayout({
|
|||||||
!showSidebar && translate
|
!showSidebar && translate
|
||||||
} flex h-full w-full flex-col overflow-y-scroll transition-[margin] duration-150 ease-in-out`}
|
} flex h-full w-full flex-col overflow-y-scroll transition-[margin] duration-150 ease-in-out`}
|
||||||
>
|
>
|
||||||
<ClanToolbar />
|
<ClanToolbar
|
||||||
<div className="static top-0 mb-2 py-2">
|
isSidebarVisible={showSidebar}
|
||||||
<div className="grid grid-cols-3">
|
handleSidebar={setShowSidebar}
|
||||||
<div className="col-span-1">
|
|
||||||
<IconButton
|
|
||||||
hidden={showSidebar}
|
|
||||||
onClick={() => setShowSidebar((c) => !c)}
|
|
||||||
>
|
|
||||||
{!showSidebar && <MenuIcon />}
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-1 block w-full bg-fixed text-center font-semibold dark:invert lg:hidden">
|
|
||||||
<Image
|
|
||||||
src="/favicon.png"
|
|
||||||
alt="Clan Logo"
|
|
||||||
width={58}
|
|
||||||
height={58}
|
|
||||||
priority
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="px-1">
|
<div className="px-1">
|
||||||
<div className="relative flex h-full flex-1 flex-col">
|
<div className="relative flex h-full flex-1 flex-col">
|
||||||
<main>{children}</main>
|
<main>{children}</main>
|
||||||
|
|||||||
59
pkgs/ui/src/app/manage/create/page.tsx
Normal file
59
pkgs/ui/src/app/manage/create/page.tsx
Normal file
@@ -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<CreateFormValues>({
|
||||||
|
defaultValues: {
|
||||||
|
flakeDir: "",
|
||||||
|
flakeTemplateUrl: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { handleSubmit } = methods;
|
||||||
|
return (
|
||||||
|
<FormProvider {...methods}>
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit(async (values) => {
|
||||||
|
console.log({ values });
|
||||||
|
try {
|
||||||
|
await createFlake(
|
||||||
|
{
|
||||||
|
url: values.flakeTemplateUrl,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flake_dir: values.flakeDir,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
const error = e as AxiosError<HTTPValidationError>;
|
||||||
|
clanErrorToast(error);
|
||||||
|
const maybeDetail = error?.response?.data?.detail?.[0];
|
||||||
|
|
||||||
|
if (maybeDetail?.loc && maybeDetail?.msg) {
|
||||||
|
const urlError = error.response?.data.detail?.find((detail) =>
|
||||||
|
detail.loc.includes("url"),
|
||||||
|
);
|
||||||
|
urlError &&
|
||||||
|
methods.setError("flakeTemplateUrl", {
|
||||||
|
message: urlError.msg,
|
||||||
|
});
|
||||||
|
const flakeDirError = error.response?.data.detail?.find(
|
||||||
|
(detail) => detail.loc.includes("flake_dir"),
|
||||||
|
);
|
||||||
|
flakeDirError &&
|
||||||
|
methods.setError("flakeDir", {
|
||||||
|
message: flakeDirError.msg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<CreateClan methods={methods} />
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
"use client";
|
||||||
import JoinPrequel from "@/views/joinPrequel";
|
import JoinPrequel from "@/views/joinPrequel";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
22
pkgs/ui/src/app/manage/page.tsx
Normal file
22
pkgs/ui/src/app/manage/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Button } from "@mui/material";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export default function Manage() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Select
|
||||||
|
<Button>
|
||||||
|
<Link href="/manage/join">Join</Link>
|
||||||
|
</Button>
|
||||||
|
<Button>
|
||||||
|
<Link href="/manage/create">Create</Link>
|
||||||
|
</Button>
|
||||||
|
<ul>
|
||||||
|
<li>History</li>
|
||||||
|
<li>Recent History</li>
|
||||||
|
<li>Ancient History</li>
|
||||||
|
<li>Cosmic History</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useFlakeHistoryList } from "@/api/flake/flake";
|
import { useFlakeHistoryList } from "@/api/flake/flake";
|
||||||
import DynamicFeedIcon from "@mui/icons-material/DynamicFeed";
|
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 Menu from "@mui/material/Menu";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
@@ -9,57 +10,112 @@ interface ToolbarButtonProps {
|
|||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
}
|
}
|
||||||
function ToolbarButton(props: ToolbarButtonProps) {
|
export function ToolbarButton(props: ToolbarButtonProps) {
|
||||||
const { icon, onClick } = props;
|
const { icon, onClick } = props;
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<Button
|
||||||
<IconButton onClick={onClick}>{icon}</IconButton>
|
sx={{
|
||||||
</div>
|
"& .MuiButton-startIcon": {
|
||||||
|
m: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onClick={onClick}
|
||||||
|
startIcon={icon}
|
||||||
|
variant="text"
|
||||||
|
color="inherit"
|
||||||
|
// fullWidth
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ClanHistoryMenu = () => {
|
||||||
|
const { data, isLoading } = useFlakeHistoryList();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isLoading ? (
|
||||||
|
<LinearProgress />
|
||||||
|
) : (
|
||||||
|
data?.data.map((item, index) => <MenuItem key={index}>{item}</MenuItem>)
|
||||||
|
)}
|
||||||
|
{!isLoading && data?.data.length === 0 && (
|
||||||
|
<MenuItem>No Clan History</MenuItem>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
type ToolbarItem = {
|
type ToolbarItem = {
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
|
menu: React.ReactNode;
|
||||||
};
|
};
|
||||||
const toolbarItems: ToolbarItem[] = [
|
const toolbarItems: ToolbarItem[] = [
|
||||||
{
|
{
|
||||||
icon: <DynamicFeedIcon />,
|
icon: <DynamicFeedIcon />,
|
||||||
|
menu: <ClanHistoryMenu />,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
export function ClanToolbar() {
|
|
||||||
const { data, isLoading } = useFlakeHistoryList();
|
interface ClanToolbarProps {
|
||||||
|
isSidebarVisible: boolean;
|
||||||
|
handleSidebar: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
}
|
||||||
|
export function ClanToolbar(props: ClanToolbarProps) {
|
||||||
|
const { isSidebarVisible, handleSidebar } = props;
|
||||||
|
|
||||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
const open = Boolean(anchorEl);
|
const [openIdx, setOpenIdx] = React.useState<number | null>(null);
|
||||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
||||||
|
const handleClick = (
|
||||||
|
event: React.MouseEvent<HTMLButtonElement>,
|
||||||
|
idx: number,
|
||||||
|
) => {
|
||||||
setAnchorEl(event.currentTarget);
|
setAnchorEl(event.currentTarget);
|
||||||
|
setOpenIdx(idx);
|
||||||
};
|
};
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
|
setOpenIdx(null);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="grid w-full auto-cols-min grid-flow-col grid-rows-1 place-items-end justify-end gap-2">
|
<div
|
||||||
|
className="flex border-x-0 border-b
|
||||||
|
border-t-0 border-solid border-neutral-80"
|
||||||
|
>
|
||||||
|
{!isSidebarVisible && (
|
||||||
|
<ToolbarButton
|
||||||
|
icon={<MenuIcon />}
|
||||||
|
onClick={() => handleSidebar((c) => !c)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className="
|
||||||
|
grid w-full auto-cols-min grid-flow-col grid-rows-1
|
||||||
|
justify-end gap-0
|
||||||
|
"
|
||||||
|
>
|
||||||
{toolbarItems.map((item, index) => (
|
{toolbarItems.map((item, index) => (
|
||||||
<ToolbarButton key={index} icon={item.icon} onClick={handleClick} />
|
<React.Fragment key={index}>
|
||||||
))}
|
<Divider flexItem orientation="vertical" />
|
||||||
|
<ToolbarButton
|
||||||
|
icon={item.icon}
|
||||||
|
onClick={(ev) => handleClick(ev, index)}
|
||||||
|
/>
|
||||||
|
|
||||||
<Menu
|
<Menu
|
||||||
id="basic-menu"
|
id="basic-menu"
|
||||||
anchorEl={anchorEl}
|
anchorEl={anchorEl}
|
||||||
open={open}
|
open={index == openIdx}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
MenuListProps={{
|
MenuListProps={{
|
||||||
"aria-labelledby": "basic-button",
|
"aria-labelledby": "basic-button",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{item.menu}
|
||||||
<LinearProgress />
|
|
||||||
) : (
|
|
||||||
data?.data.map((item, index) => (
|
|
||||||
<MenuItem key={index}>{item}</MenuItem>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
{!isLoading && data?.data.length === 0 && (
|
|
||||||
<MenuItem>No Clan History</MenuItem>
|
|
||||||
)}
|
|
||||||
</Menu>
|
</Menu>
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
77
pkgs/ui/src/components/forms/createClan/index.tsx
Normal file
77
pkgs/ui/src/components/forms/createClan/index.tsx
Normal file
@@ -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<CreateFormValues>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CreateClan = (props: CreateClanProps) => {
|
||||||
|
const { methods } = props;
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
formState: { isSubmitting },
|
||||||
|
} = methods;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Controller
|
||||||
|
name="flakeTemplateUrl"
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState }) => (
|
||||||
|
<TextField
|
||||||
|
id="flakeTemplateUrl"
|
||||||
|
error={Boolean(fieldState.error)}
|
||||||
|
label="Clan Template"
|
||||||
|
fullWidth
|
||||||
|
variant="standard"
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<CopyAllIcon />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
helperText={fieldState.error?.message}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="flakeDir"
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState }) => (
|
||||||
|
<TextField
|
||||||
|
id="flakeDir"
|
||||||
|
error={Boolean(fieldState.error)}
|
||||||
|
label="Directory"
|
||||||
|
fullWidth
|
||||||
|
variant="standard"
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<SaveAltIcon />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
helperText={fieldState.error?.message}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button type="submit">Create</Button>
|
||||||
|
{isSubmitting && <LinearProgress />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -41,7 +41,7 @@ const menuEntries: MenuEntry[] = [
|
|||||||
{
|
{
|
||||||
icon: <WorkspacesIcon />,
|
icon: <WorkspacesIcon />,
|
||||||
label: "Manage",
|
label: "Manage",
|
||||||
to: "/join",
|
to: "/manage",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <BackupIcon />,
|
icon: <BackupIcon />,
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import { toast } from "react-hot-toast";
|
|||||||
export function clanErrorToast(error: AxiosError<HTTPValidationError>) {
|
export function clanErrorToast(error: AxiosError<HTTPValidationError>) {
|
||||||
console.error({ error });
|
console.error({ error });
|
||||||
const detail = error.response?.data.detail?.[0]?.msg;
|
const detail = error.response?.data.detail?.[0]?.msg;
|
||||||
|
const detailAlt = error.response?.data.detail as unknown as string;
|
||||||
const cause = error.cause?.message;
|
const cause = error.cause?.message;
|
||||||
const axiosMessage = error.message;
|
const axiosMessage = error.message;
|
||||||
const sanitizedMsg = detail || cause || axiosMessage || "Unexpected error";
|
const sanitizedMsg =
|
||||||
|
detail || detailAlt || cause || axiosMessage || "Unexpected error";
|
||||||
toast.error(sanitizedMsg);
|
toast.error(sanitizedMsg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (
|
|
||||||
<div>
|
|
||||||
<Controller
|
|
||||||
name="flakeUrl"
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Input
|
|
||||||
disableUnderline
|
|
||||||
placeholder="url"
|
|
||||||
color="secondary"
|
|
||||||
aria-required="true"
|
|
||||||
{...field}
|
|
||||||
required
|
|
||||||
fullWidth
|
|
||||||
startAdornment={
|
|
||||||
<InputAdornment position="start">Clan</InputAdornment>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
name="dest"
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Input
|
|
||||||
sx={{ my: 2 }}
|
|
||||||
placeholder="Location"
|
|
||||||
color="secondary"
|
|
||||||
aria-required="true"
|
|
||||||
{...field}
|
|
||||||
required
|
|
||||||
fullWidth
|
|
||||||
startAdornment={
|
|
||||||
<InputAdornment position="start">Name</InputAdornment>
|
|
||||||
}
|
|
||||||
endAdornment={confirmAdornment}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{isSubmitting && <LinearProgress />}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import { Confirm } from "@/components/join/confirm";
|
import { Confirm } from "@/components/join/confirm";
|
||||||
|
import PublicIcon from "@mui/icons-material/Public";
|
||||||
import { Input, InputAdornment } from "@mui/material";
|
import { Input, InputAdornment } from "@mui/material";
|
||||||
import { Controller, useFormContext } from "react-hook-form";
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
interface JoinFormProps {
|
interface JoinFormProps {
|
||||||
confirmAdornment?: React.ReactNode;
|
|
||||||
initialParams: {
|
initialParams: {
|
||||||
flakeUrl: string;
|
flakeUrl: string;
|
||||||
flakeAttr: string;
|
flakeAttr: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export const JoinForm = (props: JoinFormProps) => {
|
export const JoinForm = (props: JoinFormProps) => {
|
||||||
const { initialParams, confirmAdornment } = props;
|
const { initialParams } = props;
|
||||||
const { control, formState, reset, getValues, watch } = useFormContext();
|
const { control, formState, reset, getValues, watch } = useFormContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -38,10 +38,11 @@ export const JoinForm = (props: JoinFormProps) => {
|
|||||||
{...field}
|
{...field}
|
||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
startAdornment={
|
endAdornment={
|
||||||
<InputAdornment position="start">Clan</InputAdornment>
|
<InputAdornment position="end">
|
||||||
|
<PublicIcon />
|
||||||
|
</InputAdornment>
|
||||||
}
|
}
|
||||||
endAdornment={confirmAdornment}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,29 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import {
|
import { Button, Typography } from "@mui/material";
|
||||||
IconButton,
|
|
||||||
InputAdornment,
|
|
||||||
MenuItem,
|
|
||||||
Select,
|
|
||||||
Typography,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import { Suspense, useState } from "react";
|
import { Suspense, useState } from "react";
|
||||||
|
|
||||||
import { createFlake } from "@/api/flake/flake";
|
|
||||||
import { VmConfig } from "@/api/model";
|
import { VmConfig } from "@/api/model";
|
||||||
import { createVm } from "@/api/vm/vm";
|
import { createVm } from "@/api/vm/vm";
|
||||||
import { useAppState } from "@/components/hooks/useAppContext";
|
|
||||||
import { Layout } from "@/components/join/layout";
|
import { Layout } from "@/components/join/layout";
|
||||||
import { VmBuildLogs } from "@/components/join/vmBuildLogs";
|
import { VmBuildLogs } from "@/components/join/vmBuildLogs";
|
||||||
import { ChevronRight } from "@mui/icons-material";
|
|
||||||
import { AxiosError } from "axios";
|
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 { toast } from "react-hot-toast";
|
||||||
import { CreateForm } from "./createForm";
|
|
||||||
import { JoinForm } from "./joinForm";
|
import { JoinForm } from "./joinForm";
|
||||||
|
|
||||||
export type FormValues = VmConfig & {
|
export type FormValues = VmConfig & {
|
||||||
workflow: "join" | "create";
|
|
||||||
flakeUrl: string;
|
flakeUrl: string;
|
||||||
dest?: string;
|
dest?: string;
|
||||||
};
|
};
|
||||||
@@ -34,48 +23,21 @@ export default function JoinPrequel() {
|
|||||||
const flakeAttr = queryParams.get("attr") || "default";
|
const flakeAttr = queryParams.get("attr") || "default";
|
||||||
const initialParams = { flakeUrl, flakeAttr };
|
const initialParams = { flakeUrl, flakeAttr };
|
||||||
|
|
||||||
const { setAppState } = useAppState();
|
|
||||||
|
|
||||||
const methods = useForm<FormValues>({
|
const methods = useForm<FormValues>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
flakeUrl: "",
|
flakeUrl: "",
|
||||||
dest: undefined,
|
dest: undefined,
|
||||||
workflow: "join",
|
|
||||||
cores: 4,
|
cores: 4,
|
||||||
graphics: true,
|
graphics: true,
|
||||||
memory_size: 2048,
|
memory_size: 2048,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { control, watch, handleSubmit } = methods;
|
const { handleSubmit } = methods;
|
||||||
|
|
||||||
const [vmUuid, setVmUuid] = useState<string | null>(null);
|
const [vmUuid, setVmUuid] = useState<string | null>(null);
|
||||||
const [showLogs, setShowLogs] = useState<boolean>(false);
|
const [showLogs, setShowLogs] = useState<boolean>(false);
|
||||||
|
|
||||||
const workflow = watch("workflow");
|
|
||||||
|
|
||||||
const WorkflowAdornment = (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<Controller
|
|
||||||
name="workflow"
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Select
|
|
||||||
{...field}
|
|
||||||
label="workflow"
|
|
||||||
variant="standard"
|
|
||||||
disableUnderline
|
|
||||||
>
|
|
||||||
<MenuItem value={"join"}>Join</MenuItem>
|
|
||||||
<MenuItem value={"create"}>Create</MenuItem>
|
|
||||||
</Select>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<IconButton type={"submit"}>
|
|
||||||
<ChevronRight />
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<Layout
|
<Layout
|
||||||
header={
|
header={
|
||||||
@@ -84,11 +46,8 @@ export default function JoinPrequel() {
|
|||||||
className="w-full text-center"
|
className="w-full text-center"
|
||||||
sx={{ textTransform: "capitalize" }}
|
sx={{ textTransform: "capitalize" }}
|
||||||
>
|
>
|
||||||
{workflow}{" "}
|
|
||||||
<Typography variant="h4" className="font-bold" component={"span"}>
|
|
||||||
Clan.lol
|
Clan.lol
|
||||||
</Typography>
|
</Typography>
|
||||||
</Typography>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Suspense fallback="Loading">
|
<Suspense fallback="Loading">
|
||||||
@@ -98,24 +57,6 @@ export default function JoinPrequel() {
|
|||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit(async (values) => {
|
onSubmit={handleSubmit(async (values) => {
|
||||||
if (workflow === "create") {
|
|
||||||
try {
|
|
||||||
await createFlake(
|
|
||||||
{
|
|
||||||
url: values.flakeUrl,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
flake_dir: values.dest || "myclan",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
setAppState((s) => ({ ...s, isJoined: true }));
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(
|
|
||||||
`Error: ${(error as AxiosError).message || ""}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (workflow === "join") {
|
|
||||||
console.log("JOINING");
|
console.log("JOINING");
|
||||||
console.log(values);
|
console.log(values);
|
||||||
try {
|
try {
|
||||||
@@ -135,23 +76,13 @@ export default function JoinPrequel() {
|
|||||||
toast.error("Could not join");
|
toast.error("Could not join");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(
|
toast.error(`Error: ${(error as AxiosError).message || ""}`);
|
||||||
`Error: ${(error as AxiosError).message || ""}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
className="w-full max-w-2xl justify-self-center"
|
className="w-full max-w-2xl justify-self-center"
|
||||||
>
|
>
|
||||||
{workflow == "join" && (
|
<JoinForm initialParams={initialParams} />
|
||||||
<JoinForm
|
<Button type="submit">Join</Button>
|
||||||
initialParams={initialParams}
|
|
||||||
confirmAdornment={WorkflowAdornment}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{workflow == "create" && (
|
|
||||||
<CreateForm confirmAdornment={WorkflowAdornment} />
|
|
||||||
)}
|
|
||||||
</form>
|
</form>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user