Skip to content

Commit 40de508

Browse files
committed
feat: files manager
1 parent 8f69851 commit 40de508

File tree

16 files changed

+858
-105
lines changed

16 files changed

+858
-105
lines changed

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"react-ga4": "^2.1.0",
8282
"react-hook-form": "^7.54.2",
8383
"react-i18next": "^15.5.1",
84+
"react-icons": "^5.5.0",
8485
"react-markdown": "^10.1.0",
8586
"react-router-dom": "^7.6.0",
8687
"react-select": "^5.9.0",

src/autokitteh

Submodule autokitteh updated 68 files
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import React, { useEffect } from "react";
2+
3+
import { zodResolver } from "@hookform/resolvers/zod";
4+
import { useForm } from "react-hook-form";
5+
import { useTranslation } from "react-i18next";
6+
import { useParams } from "react-router-dom";
7+
import { z } from "zod";
8+
9+
import { ModalName } from "@enums/components";
10+
import { LoggerService } from "@services";
11+
import { namespaces } from "@src/constants";
12+
import { fileOperations } from "@src/factories";
13+
14+
import { useModalStore, useToastStore } from "@store";
15+
16+
import { Button, ErrorMessage, Input } from "@components/atoms";
17+
import { Modal } from "@components/molecules";
18+
19+
const hasInvalidCharacters = (name: string): boolean => {
20+
const invalidChars = /[<>:"/\\|?*]/;
21+
if (invalidChars.test(name)) return true;
22+
23+
for (let i = 0; i < name.length; i++) {
24+
const code = name.charCodeAt(i);
25+
if (code < 32) return true;
26+
}
27+
return false;
28+
};
29+
30+
const directorySchema = z.object({
31+
name: z
32+
.string()
33+
.min(1, "Directory name is required")
34+
.refine((name) => !hasInvalidCharacters(name), "Directory name contains invalid characters")
35+
.refine((name) => !name.startsWith("."), "Directory name cannot start with a dot")
36+
.refine((name) => name.trim() === name, "Directory name cannot have leading or trailing spaces"),
37+
});
38+
39+
type DirectoryFormData = z.infer<typeof directorySchema>;
40+
41+
export const AddDirectoryModal = () => {
42+
const { projectId } = useParams();
43+
const { t } = useTranslation(["modals", "buttons"]);
44+
const { closeModal } = useModalStore();
45+
const addToast = useToastStore((state) => state.addToast);
46+
const { createDirectory } = fileOperations(projectId!);
47+
48+
const {
49+
clearErrors,
50+
formState: { errors },
51+
handleSubmit,
52+
register,
53+
reset,
54+
} = useForm<DirectoryFormData>({
55+
defaultValues: {
56+
name: "",
57+
},
58+
resolver: zodResolver(directorySchema),
59+
});
60+
61+
useEffect(() => {
62+
reset({ name: "" });
63+
clearErrors();
64+
}, [reset, clearErrors]);
65+
66+
const onSubmit = async (data: DirectoryFormData) => {
67+
try {
68+
const success = await createDirectory(data.name);
69+
if (!success) {
70+
addToast({
71+
message: `Failed to create directory "${data.name}"`,
72+
type: "error",
73+
});
74+
75+
LoggerService.error(
76+
namespaces.projectUICode,
77+
`Failed to create directory "${data.name}" in project ${projectId}`
78+
);
79+
return;
80+
}
81+
82+
addToast({
83+
message: `Directory "${data.name}" created successfully`,
84+
type: "success",
85+
});
86+
} catch (error) {
87+
addToast({
88+
message: `Failed to create directory "${data.name}"`,
89+
type: "error",
90+
});
91+
92+
LoggerService.error(namespaces.projectUICode, `Failed to create directory: ${error}`);
93+
}
94+
clearErrors();
95+
closeModal(ModalName.addDirectory);
96+
reset({ name: "" });
97+
};
98+
99+
return (
100+
<Modal className="w-550" name={ModalName.addDirectory}>
101+
<div className="mx-6">
102+
<h3 className="mb-5 text-xl font-bold">{t("addDirectory.title", { ns: "modals" })}</h3>
103+
104+
<form onSubmit={handleSubmit(onSubmit)}>
105+
<div className="relative w-full">
106+
<Input
107+
{...register("name")}
108+
aria-label={t("addDirectory.ariaLabelNewDirectory", { ns: "modals" })}
109+
isError={!!errors.name}
110+
isRequired
111+
label={t("addDirectory.placeholderName", { ns: "modals" })}
112+
variant="light"
113+
/>
114+
115+
<ErrorMessage className="relative">{errors.name?.message as string}</ErrorMessage>
116+
</div>
117+
118+
<Button className="mt-3 justify-center rounded-lg py-2.5 font-bold" type="submit" variant="filled">
119+
{t("create", { ns: "buttons" })}
120+
</Button>
121+
</form>
122+
</div>
123+
</Modal>
124+
);
125+
};

src/components/organisms/files/deleteModal.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,35 @@ import { useModalStore } from "@store";
1010
import { Button, Loader } from "@components/atoms";
1111
import { Modal } from "@components/molecules";
1212

13+
interface DeleteFileModalData {
14+
name: string;
15+
isDirectory?: boolean;
16+
fileCount?: number;
17+
}
18+
1319
export const DeleteFileModal = ({ isDeleting, onDelete }: DeleteModalProps) => {
1420
const { t } = useTranslation("modals", { keyPrefix: "deleteFile" });
15-
const fileName = useModalStore((state) => state.data as string);
21+
const modalData = useModalStore((state) => state.data) as DeleteFileModalData | string;
1622
const { closeModal } = useModalStore();
1723

24+
const fileName = typeof modalData === "string" ? modalData : modalData?.name || "";
25+
const isDirectory = typeof modalData === "object" && modalData?.isDirectory;
26+
const fileCount = typeof modalData === "object" ? modalData?.fileCount : 0;
27+
28+
const typeLabel = isDirectory ? t("directory") : t("file");
29+
1830
return (
1931
<Modal hideCloseButton name={ModalName.deleteFile}>
2032
<div className="mx-6">
21-
<h3 className="mb-5 text-xl font-bold">{t("title")}</h3>
33+
<h3 className="mb-5 text-xl font-bold">{t("title", { type: typeLabel })}</h3>
2234

2335
<p>{t("content", { name: fileName })}</p>
2436

25-
<p>{t("deleteWarning")}</p>
37+
{isDirectory && fileCount ? (
38+
<p>{t("deleteDirectoryWarning", { count: fileCount })}</p>
39+
) : (
40+
<p>{t("deleteWarning")}</p>
41+
)}
2642
</div>
2743

2844
<div className="mt-8 flex w-full justify-end gap-2">

0 commit comments

Comments
 (0)