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" } }