feat: pack creating

This commit is contained in:
Kentai Radiquum 2025-05-05 01:33:58 +05:00
parent 5be021789b
commit af7b8a6fea
Signed by: Radiquum
GPG key ID: 858E8EE696525EED
27 changed files with 677 additions and 130 deletions

221
gui/app/pack/new/page.tsx Normal file
View file

@ -0,0 +1,221 @@
"use client";
import { Card, FileInput } from "flowbite-react";
import { Label, TextInput, Select } from "flowbite-react";
import { useState } from "react";
import { HiUser, HiAnnotation } from "react-icons/hi";
import { Button } from "flowbite-react";
import { useRouter } from "next/navigation";
import mc from "../../../api/mc_version.json";
import { ENDPOINTS, PACK_IMG_ENDPOINTS } from "@/api/ENDPOINTS";
import { toast } from "react-toastify";
const mcr = mc.reverse();
export default function PackNew() {
const router = useRouter()
const [image, setImage] = useState<null | string>(null);
const [imageMime, setImageMime] = useState<null | string>(null);
const [packInfo, setPackInfo] = useState({
title: "",
author: "",
modloader: "Forge",
version: "1.21.5",
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handleImagePreview = (e: any) => {
const file = e.target.files[0];
const fileReader = new FileReader();
fileReader.onloadend = () => {
const content = fileReader.result;
setImage(content as string);
setImageMime(file.type);
e.target.value = "";
};
fileReader.readAsDataURL(file);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function handleInput(e: any) {
const regex = /[^a-zA-Zа-яА-Я0-9_.()\- \[\]]/g;
setPackInfo({
...packInfo,
[e.target.name]: e.target.value.replace(regex, ""),
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function submit(e: any) {
e.preventDefault();
async function _submit() {
const tid = toast.loading(`Creating Pack "${packInfo.title}"`)
const res = await fetch(`${ENDPOINTS.createPack}`, {
method: "POST",
body: JSON.stringify(packInfo),
headers: {
"content-type": "application/json",
accept: "application/json",
},
});
const data = await res.json();
if (data.status != "ok") {
toast.update(tid, {render: data.message, type: "error", isLoading: false})
return;
}
if (image) {
await fetch(`${PACK_IMG_ENDPOINTS("editPackImage", data.id)}`, {
method: "POST",
body: JSON.stringify({
image: image,
mimetype: imageMime
}),
headers: {
"content-type": "application/json",
accept: "application/json",
},
});
}
toast.update(tid, {render: data.message, type: "success", isLoading: false})
router.push(`/pack/${data.id}`)
}
_submit();
}
return (
<Card className="w-full">
<form
className="flex flex-col gap-4"
encType="multipart/form-data"
onSubmit={(e) => submit(e)}
>
<div className="flex w-full items-center justify-center">
<Label
htmlFor="dropzone-file"
className="flex h-64 w-full cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:hover:border-gray-500 dark:hover:bg-gray-600"
>
{image ? (
// eslint-disable-next-line @next/next/no-img-element
<img src={image} alt="preview" className="overflow-hidden" />
) : (
<div className="flex flex-col items-center justify-center pb-6 pt-5">
<svg
className="mb-4 h-8 w-8 text-gray-500 dark:text-gray-400"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 16"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"
/>
</svg>
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
<span className="font-semibold">Click to upload</span> or drag
and drop
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
PNG or JPG
</p>
</div>
)}
<FileInput
id="dropzone-file"
className="hidden"
name="image"
onChange={(e) => handleImagePreview(e)}
/>
</Label>
</div>
<div className="flex gap-4">
<div className="flex-1">
<div className="mb-2 block">
<Label htmlFor="base" className="text-lg">
Title
</Label>
</div>
<TextInput
id="base"
type="text"
sizing="md"
name="title"
onChange={(e) => handleInput(e)}
value={packInfo.title}
icon={HiAnnotation}
required
/>
</div>
<div className="flex-1">
<div className="mb-2 block">
<Label htmlFor="base" className="text-lg">
Author
</Label>
</div>
<TextInput
id="base"
type="text"
sizing="md"
name="author"
onChange={(e) => handleInput(e)}
value={packInfo.author}
icon={HiUser}
required
/>
</div>
</div>
<div className="flex gap-4">
<div className="flex-1">
<div className="mb-2 block">
<Label htmlFor="base" className="text-lg">
Mod Loader
</Label>
</div>
<Select
id="modloader"
name="modloader"
required
onChange={(e) => handleInput(e)}
>
<option value="Forge">Forge</option>
<option value="Fabric">Fabric</option>
<option value="NeoForge">NeoForge</option>
<option value="Quilt">Quilt</option>
</Select>
</div>
<div className="flex-1">
<div className="mb-2 block">
<Label htmlFor="base" className="text-lg">
Game Version
</Label>
</div>
<Select
id="version"
name="version"
required
onChange={(e) => handleInput(e)}
>
{mcr.map((version) => (
<option key={version} value={version}>
{version}
</option>
))}
</Select>
</div>
</div>
<div className="flex justify-end">
<Button type="submit">Create</Button>
</div>
</form>
</Card>
);
}