diff --git a/enshi/package-lock.json b/enshi/package-lock.json
index 5ae82a9..499cbe7 100644
--- a/enshi/package-lock.json
+++ b/enshi/package-lock.json
@@ -10,7 +10,9 @@
"dependencies": {
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-form": "^0.1.0",
+ "@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-navigation-menu": "^1.2.1",
+ "@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-tooltip": "^1.1.2",
"@radix-ui/themes": "^3.1.3",
"@tanstack/react-query": "^5.55.0",
@@ -1622,6 +1624,15 @@
}
}
},
+ "node_modules/@radix-ui/react-icons": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz",
+ "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
"node_modules/@radix-ui/react-id": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
@@ -2216,6 +2227,130 @@
}
}
},
+ "node_modules/@radix-ui/react-toast": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.2.tgz",
+ "integrity": "sha512-Z6pqSzmAP/bFJoqMAston4eSNa+ud44NSZTiZUmUen+IOZ5nBY8kzuU5WDBVyFXPtcW6yUalOHsxM/BP6Sv8ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.0",
+ "@radix-ui/react-collection": "1.1.0",
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.1",
+ "@radix-ui/react-portal": "1.1.2",
+ "@radix-ui/react-presence": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0",
+ "@radix-ui/react-visually-hidden": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-context": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
+ "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz",
+ "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.0",
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-escape-keydown": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-portal": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz",
+ "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-presence": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz",
+ "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-toggle": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.0.tgz",
diff --git a/enshi/package.json b/enshi/package.json
index 8f48f0b..6effe72 100644
--- a/enshi/package.json
+++ b/enshi/package.json
@@ -12,7 +12,9 @@
"dependencies": {
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-form": "^0.1.0",
+ "@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-navigation-menu": "^1.2.1",
+ "@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-tooltip": "^1.1.2",
"@radix-ui/themes": "^3.1.3",
"@tanstack/react-query": "^5.55.0",
diff --git a/enshi/src/types/UserType.ts b/enshi/src/@types/UserType.ts
similarity index 100%
rename from enshi/src/types/UserType.ts
rename to enshi/src/@types/UserType.ts
diff --git a/enshi/src/@types/index.d.ts b/enshi/src/@types/index.d.ts
new file mode 100644
index 0000000..6b484a7
--- /dev/null
+++ b/enshi/src/@types/index.d.ts
@@ -0,0 +1,11 @@
+type TToast = {
+ title: string;
+ description?: string;
+ action?: React.Component;
+};
+
+type TExistingToast = TToast & {
+ id: number;
+ resetFunc: (arg0: boolean) => void;
+ open: boolean;
+};
diff --git a/enshi/src/App.tsx b/enshi/src/App.tsx
index 7d8c2ba..b67896c 100644
--- a/enshi/src/App.tsx
+++ b/enshi/src/App.tsx
@@ -1,11 +1,12 @@
import { Theme } from "@radix-ui/themes";
import "@radix-ui/themes/styles.css";
import { QueryClientProvider } from "@tanstack/react-query";
-import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
+import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import "axios";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import queryClient from "./api/QueryClient/QueryClient";
import "./App.css";
+import ToastProvider from "./Components/ToastProvider/ToastProvider";
import { routes } from "./routes/routes";
const router = createBrowserRouter(routes);
@@ -13,11 +14,13 @@ const router = createBrowserRouter(routes);
export default function App() {
return (
-
-
- {/* */}
-
-
+
+
+
+ {/* */}
+
+
+
);
}
diff --git a/enshi/src/AtomStore/AtomStore.ts b/enshi/src/AtomStore/AtomStore.ts
index 352eca9..ed53496 100644
--- a/enshi/src/AtomStore/AtomStore.ts
+++ b/enshi/src/AtomStore/AtomStore.ts
@@ -1,4 +1,50 @@
import { atom } from "jotai";
-import { TUser } from "../types/UserType";
+import { atomWithStorage } from "jotai/utils";
+import { TUser } from "../@types/UserType";
-export const userAtom = atom()
\ No newline at end of file
+export const userAtom = atom();
+
+export const postCreationAtom = atom();
+export const postCreationTitleAtom = atom();
+
+type TPostData = {
+ title: string;
+ content: string;
+};
+
+export const storagePostAtom = atomWithStorage(
+ "draft-post",
+ { title: "", content: "" },
+ {
+ getItem: (key) => sessionStorage.getItem(key) as any,
+ setItem: (key, value) => sessionStorage.setItem(key, value as any),
+ removeItem: (key) => sessionStorage.removeItem(key),
+ },
+ { getOnInit: true }
+);
+
+export const toastAtom = atom([]);
+export const setToastAtom = atom(null, (get, set, value: TToast) => {
+ let maxToastId = Math.max(...get(toastAtom).map((toast) => toast.id));
+ maxToastId = maxToastId >= 0 ? maxToastId : 1;
+ let atomValueWithNewToast = get(toastAtom);
+ atomValueWithNewToast = [
+ ...atomValueWithNewToast,
+ {
+ id: maxToastId + 1,
+ resetFunc: (_) => {
+ let currentToasts = get(toastAtom);
+ let afterRemoval = currentToasts.filter(
+ (toast) => toast.id != maxToastId + 1
+ );
+ set(toastAtom, afterRemoval);
+ },
+ title: value.title,
+ action: value.action,
+ description: value.description,
+ open: true,
+ },
+ ];
+
+ set(toastAtom, atomValueWithNewToast);
+});
diff --git a/enshi/src/Components/Editor/Editor.tsx b/enshi/src/Components/Editor/Editor.tsx
index 9c92d3c..a758d13 100644
--- a/enshi/src/Components/Editor/Editor.tsx
+++ b/enshi/src/Components/Editor/Editor.tsx
@@ -11,13 +11,13 @@ import ReactQuill from "react-quill";
type TEditor = {
readOnly?: boolean;
defaultValue?: string | Delta;
- onChange: (d: string) => void;
+ onChange?: (d: string) => void;
onSelectionChange?: any;
};
const modules = {
toolbar: [
- [{ header: [1, 2, 3, false] }],
+ [{ header: [1, 2, 3, 4, 5, false] }],
["bold", "italic", "underline", "strike", "blockquote", "span-wrapper"],
[
{ list: "ordered" },
@@ -28,10 +28,7 @@ const modules = {
["link", "image"],
["clean"],
[{ align: [] }],
- ],
- clipboard: {
- matchVisual: true,
- },
+ ]
};
/**
@@ -52,32 +49,13 @@ const Editor = forwardRef((props: TEditor) => {
setQuill(null);
};
}, [editor.current]);
-
- useEffect(() => {
- let Inline = Quill.import('attributors/style/size');
- console.log(Inline);
-
- // //@ts-ignore
- // class BoldBlot extends Inline {}
- // //@ts-ignore
- // BoldBlot.blotName = 'bold1';
- // //@ts-ignore
- // BoldBlot.tagName = 'strong';
- // console.log(BoldBlot, true);
-
-
- Quill.register(Inline as any, true);
- return () => {
-
- }
- }, [])
const changeHandler = (val: string, _changeDelta: Delta, _source: Sources, _editor: ReactQuill.UnprivilegedEditor) => {
console.log(val);
console.log(JSON.stringify(quill?.getContents().ops, null, 2))
let fullDelta = quill?.getContents()
- props.onChange(val || "")
+ if (props.onChange) props.onChange(val || "")
setValue(fullDelta || new Delta())
}
@@ -87,8 +65,6 @@ const Editor = forwardRef((props: TEditor) => {
value={value}
ref={editor}
modules={modules}
- formats={['bold1']}
-
onChange={changeHandler}
diff --git a/enshi/src/Components/NavBar/NavBar.tsx b/enshi/src/Components/NavBar/NavBar.tsx
index 4cca591..17472c6 100644
--- a/enshi/src/Components/NavBar/NavBar.tsx
+++ b/enshi/src/Components/NavBar/NavBar.tsx
@@ -4,14 +4,12 @@ import SearchField from "./SearchField/SearchField";
export default function NavBar() {
return (
- //
-
+
+
);
-}
\ No newline at end of file
+}
diff --git a/enshi/src/Components/ToastProvider/ToastProvider.tsx b/enshi/src/Components/ToastProvider/ToastProvider.tsx
new file mode 100644
index 0000000..51ec9a7
--- /dev/null
+++ b/enshi/src/Components/ToastProvider/ToastProvider.tsx
@@ -0,0 +1,44 @@
+import { Cross1Icon } from "@radix-ui/react-icons";
+import * as Toast from "@radix-ui/react-toast";
+import { Card, Text } from "@radix-ui/themes";
+import { useAtomValue } from "jotai";
+import React from "react";
+import { toastAtom } from "../../AtomStore/AtomStore";
+
+export default function ToastProvider(props: React.PropsWithChildren) {
+ const toastsToRender = useAtomValue(toastAtom);
+
+ return (
+
+ {props.children}
+
+ {toastsToRender.map((toast) => {
+ return (
+
+
+
+
+ {toast.title}
+
+
+
+ {toast.description}
+
+
+
+
+
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/enshi/src/Pages/AuthPageWrapper/AuthPageWrapper.tsx b/enshi/src/Pages/AuthPageWrapper/AuthPageWrapper.tsx
index 9f6ced6..3418d83 100644
--- a/enshi/src/Pages/AuthPageWrapper/AuthPageWrapper.tsx
+++ b/enshi/src/Pages/AuthPageWrapper/AuthPageWrapper.tsx
@@ -10,7 +10,7 @@ export default function AuthPageWrapper(props: React.PropsWithChildren) {
const navigate = useNavigate();
if (!user) {
- navigate("/login")
+ navigate("/login");
return (
{t("errors.unauthorized")}
diff --git a/enshi/src/Pages/MainPage/MainPage.tsx b/enshi/src/Pages/MainPage/MainPage.tsx
index 06b1bf4..12bb82d 100644
--- a/enshi/src/Pages/MainPage/MainPage.tsx
+++ b/enshi/src/Pages/MainPage/MainPage.tsx
@@ -1,4 +1,4 @@
-import { Spinner } from "@radix-ui/themes";
+import { Box, Flex, Spinner } from "@radix-ui/themes";
import { useQuery } from "@tanstack/react-query";
import { useSetAtom } from "jotai";
import { Outlet } from "react-router-dom";
@@ -20,7 +20,7 @@ export default function MainPage() {
queryFn: async () => {
try {
const response = await axiosLocalhost.get("/auth/check");
-
+
setUserData({
isAdmin: response.data["is_admin"],
username: response.data["username"],
@@ -52,10 +52,14 @@ export default function MainPage() {
) : (
- <>
-
-
- >
+
+
+
+
+
+
+
+
)}
>
);
diff --git a/enshi/src/Pages/PostCreatorPage/PostCreatorPage.tsx b/enshi/src/Pages/PostCreatorPage/PostCreatorPage.tsx
index 97885fa..3e3e6dd 100644
--- a/enshi/src/Pages/PostCreatorPage/PostCreatorPage.tsx
+++ b/enshi/src/Pages/PostCreatorPage/PostCreatorPage.tsx
@@ -1,26 +1,42 @@
-import { Container } from "@radix-ui/themes";
-import { useState } from "react";
-import { useTranslation } from "react-i18next";
-import ArticleViewer from "../../Components/ArticleViewer/ArticleViewer";
+import { Box, Container, Flex } from "@radix-ui/themes";
+import { useSetAtom } from "jotai";
+import {
+ postCreationAtom,
+ postCreationTitleAtom
+} from "../../AtomStore/AtomStore";
import Editor from "../../Components/Editor/Editor";
+import SubmitPostButton from "./SubmitPostButton/SubmitPostButton";
export default function PostCreatorPage() {
- const [userInput, setUserInput] = useState("");
-
- const { t } = useTranslation();
-
+ const setTitleValue = useSetAtom(postCreationTitleAtom);
+ const setContentValue = useSetAtom(postCreationAtom);
+
return (
<>
-
-
-
-
-
+
+
+
+
+ {
+ setTitleValue(e.target.value);
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
>
);
}
diff --git a/enshi/src/Pages/PostCreatorPage/SubmitPostButton/SubmitPostButton.tsx b/enshi/src/Pages/PostCreatorPage/SubmitPostButton/SubmitPostButton.tsx
new file mode 100644
index 0000000..c57f3c6
--- /dev/null
+++ b/enshi/src/Pages/PostCreatorPage/SubmitPostButton/SubmitPostButton.tsx
@@ -0,0 +1,57 @@
+import { Button } from "@radix-ui/themes";
+import { useMutation } from "@tanstack/react-query";
+import { useAtomValue } from "jotai";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useNavigate } from "react-router-dom";
+import { axiosLocalhost } from "../../../api/axios/axios";
+import {
+ postCreationAtom,
+ postCreationTitleAtom,
+} from "../../../AtomStore/AtomStore";
+
+type TSubmitPostButton = {
+ className: string;
+};
+
+export default function SubmitPostButton(props: TSubmitPostButton) {
+ const { t } = useTranslation();
+ const contentValue = useAtomValue(postCreationAtom);
+ const titleValue = useAtomValue(postCreationTitleAtom);
+
+ const navigate = useNavigate();
+
+ const [isDisabled, setIsDisabled] = useState(false);
+
+ const postMutation = useMutation({
+ mutationFn: async () => {
+ axiosLocalhost.post("/posts", {
+ title: titleValue,
+ content: contentValue,
+ });
+ },
+ onMutate: () => {
+ setIsDisabled(true);
+ },
+ onError: () => {
+ setIsDisabled(false);
+ },
+ onSuccess: () => {
+ navigate("/");
+ },
+ });
+
+ return (
+
+ );
+}
diff --git a/enshi/src/hooks/useToast.tsx b/enshi/src/hooks/useToast.tsx
new file mode 100644
index 0000000..b4fd553
--- /dev/null
+++ b/enshi/src/hooks/useToast.tsx
@@ -0,0 +1,7 @@
+import { useSetAtom } from "jotai";
+import { setToastAtom } from "../AtomStore/AtomStore";
+
+export default function useToast() {
+ const createToast = useSetAtom(setToastAtom);
+ return createToast;
+}
diff --git a/enshi/src/main.tsx b/enshi/src/main.tsx
index 7b97595..63f11dd 100644
--- a/enshi/src/main.tsx
+++ b/enshi/src/main.tsx
@@ -4,7 +4,7 @@ import './index.css'
import './locale/i18n.ts'
import i18n from './locale/i18n.ts'
-i18n.changeLanguage("ru")
+i18n.changeLanguage(navigator.language)
createRoot(document.getElementById('root')!).render(
diff --git a/enshi/src/routes/routes.tsx b/enshi/src/routes/routes.tsx
index 4f74bfc..a6f3544 100644
--- a/enshi/src/routes/routes.tsx
+++ b/enshi/src/routes/routes.tsx
@@ -21,12 +21,13 @@ export const routes = createRoutesFromElements(
<>
} element={}>
Cringer path} />
+
Cringer path, but this a
}
- >
+ />
=0.10.0"
}
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
}
}
}
diff --git a/package.json b/package.json
index dcd08fd..5586dfe 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,7 @@
{
"dependencies": {
"@radix-ui/react-icons": "^1.3.2",
+ "@radix-ui/react-toast": "^1.2.2",
"interweave": "^13.1.0"
}
}