decompose join/create clan, move to manage page
This commit is contained in:
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";
|
||||
|
||||
export default function Page() {
|
||||
11
pkgs/ui/src/app/manage/page.tsx
Normal file
11
pkgs/ui/src/app/manage/page.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Manage() {
|
||||
return (
|
||||
<div>
|
||||
Select
|
||||
<Link href="/manage/join">Join</Link>
|
||||
<Link href="/manage/create">Create</Link>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
@@ -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 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={
|
||||
<InputAdornment position="start">Clan</InputAdornment>
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<PublicIcon />
|
||||
</InputAdornment>
|
||||
}
|
||||
endAdornment={confirmAdornment}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -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<FormValues>({
|
||||
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<string | null>(null);
|
||||
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 (
|
||||
<Layout
|
||||
header={
|
||||
@@ -84,11 +46,8 @@ export default function JoinPrequel() {
|
||||
className="w-full text-center"
|
||||
sx={{ textTransform: "capitalize" }}
|
||||
>
|
||||
{workflow}{" "}
|
||||
<Typography variant="h4" className="font-bold" component={"span"}>
|
||||
Clan.lol
|
||||
</Typography>
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Suspense fallback="Loading">
|
||||
@@ -98,24 +57,6 @@ export default function JoinPrequel() {
|
||||
<FormProvider {...methods}>
|
||||
<form
|
||||
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(values);
|
||||
try {
|
||||
@@ -135,23 +76,13 @@ export default function JoinPrequel() {
|
||||
toast.error("Could not join");
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
`Error: ${(error as AxiosError).message || ""}`,
|
||||
);
|
||||
}
|
||||
toast.error(`Error: ${(error as AxiosError).message || ""}`);
|
||||
}
|
||||
})}
|
||||
className="w-full max-w-2xl justify-self-center"
|
||||
>
|
||||
{workflow == "join" && (
|
||||
<JoinForm
|
||||
initialParams={initialParams}
|
||||
confirmAdornment={WorkflowAdornment}
|
||||
/>
|
||||
)}
|
||||
{workflow == "create" && (
|
||||
<CreateForm confirmAdornment={WorkflowAdornment} />
|
||||
)}
|
||||
<JoinForm initialParams={initialParams} />
|
||||
<Button type="submit">Join</Button>
|
||||
</form>
|
||||
</FormProvider>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user