Post update stuff

This commit is contained in:
Max 2024-12-02 22:06:55 +03:00
parent c8967df573
commit 1ab8022b95
14 changed files with 217 additions and 30 deletions

View File

@ -1,4 +1,5 @@
export type TUser = {
username: string;
isAdmin: boolean;
id?: string | number;
}

View File

@ -1,9 +1,12 @@
import { Container, Separator, Text } from "@radix-ui/themes";
import { Container, Flex, Separator, Text } from "@radix-ui/themes";
import { useQuery } from "@tanstack/react-query";
import { Interweave } from "interweave";
import { useAtomValue } from "jotai";
import { useParams } from "react-router-dom";
import { axiosLocalhost } from "../../api/axios/axios";
import SkeletonLoader from "./SkeletonLoader/SkeletonLoader";
import { userAtom } from "../../AtomStore/AtomStore";
import ChangePostButton from "./ChangePostButton/ChangePostButton";
import SkeletonPostLoader from "./SkeletonLoader/SkeletonLoader";
type TArticleViewer = {
htmlToParse?: string;
@ -11,6 +14,7 @@ type TArticleViewer = {
export default function ArticleViewer(props: TArticleViewer) {
let queryParams = useParams();
const user = useAtomValue(userAtom)
const { data, isPending } = useQuery({
queryKey: [`post_${queryParams["postId"]}`],
@ -21,18 +25,27 @@ export default function ArticleViewer(props: TArticleViewer) {
return response.data;
},
});
})
if (isPending)
return (
<SkeletonLoader />
);
if (isPending) return <SkeletonPostLoader />;
return (
<>
<div className="ql-snow">
<Container size={"2"} className="mt-4">
<Text className="mb-2" as="div" size={"9"}>{data.title}</Text>
<Flex direction={"column"}>
<Text className="mb-2" as="div" size={"9"}>
{data.title}
</Text>
<Flex className="mt-4 mb-2">
<div hidden={data.user_id != user?.id}>
<ChangePostButton
postId={queryParams["postId"] || ""}
/>
</div>
</Flex>
</Flex>
<Separator size={"4"} className="mb-2" />
<Interweave content={data.content} />
</Container>

View File

@ -0,0 +1,21 @@
import { Button } from "@radix-ui/themes";
import { useNavigate } from "react-router-dom";
type TChangePostButton = {
postId: number | string;
};
export default function ChangePostButton(props: TChangePostButton) {
const navigate = useNavigate();
return (
<Button
size={"1"}
className="h-5"
variant="surface"
onClick={() => navigate("/posts/change/" + props.postId)}
>
{"Change article"}
</Button>
);
}

View File

@ -5,7 +5,7 @@ import {
pText,
} from "../../../constants/textForSkeleton";
export default function SkeletonLoader() {
export default function SkeletonPostLoader() {
return (
<Container size={"2"} className="mt-4">
<Skeleton>

View File

@ -1,11 +1,6 @@
import Sources from "quill";
import Quill, { Delta } from "quill/core";
import {
forwardRef,
useEffect,
useRef,
useState
} from "react";
import { forwardRef, useEffect, useRef, useState } from "react";
import ReactQuill from "react-quill";
type TEditor = {
@ -28,7 +23,7 @@ const modules = {
["link", "image"],
["clean"],
[{ align: [] }],
]
],
};
/**
@ -37,7 +32,9 @@ const modules = {
const Editor = forwardRef((props: TEditor) => {
const editor = useRef(null);
const [quill, setQuill] = useState<Quill | null>(null);
const [value, setValue] = useState(new Delta())
const [value, setValue] = useState(new Delta());
const [loaded, setLoaded] = useState(false);
useEffect(() => {
if (editor.current) {
@ -50,25 +47,41 @@ const Editor = forwardRef((props: TEditor) => {
};
}, [editor.current]);
useEffect(() => {
const quill = new Quill(document.createElement("div"));
const t = quill.clipboard.convert({
html: props.defaultValue as string,
}) as Delta;
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()
if (props.onChange) props.onChange(val || "")
setValue(fullDelta || new Delta())
if (!loaded) {
setValue(t);
console.log(t);
}
setLoaded(true);
}, [props.defaultValue]);
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();
if (props.onChange) props.onChange(val || "");
if (loaded) setValue(fullDelta || new Delta());
};
return (
<div className="text-editor">
<ReactQuill
value={value}
ref={editor}
modules={modules}
onChange={changeHandler}
theme="snow"
placeholder="Type your thoughts here..."
/>

View File

@ -33,6 +33,7 @@ export default function LoginPage() {
setUserAtom({
username: response.data.username,
isAdmin: false,
id: response.data.id,
});
},
@ -48,6 +49,7 @@ export default function LoginPage() {
setUserAtom({
username: userAtomValue?.username || "",
isAdmin: true,
id: userAtomValue?.id,
});
}
};

View File

@ -0,0 +1,75 @@
import { Box, Container, Flex, Spinner } from "@radix-ui/themes";
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import { useParams } from "react-router-dom";
import { axiosLocalhost } from "../../../api/axios/axios";
import Editor from "../../../Components/Editor/Editor";
import SubmitChangesButton from "./SubmitChangesButton/SubmitChangesButton";
export default function PostRedactor() {
const [contentValue, setContentValue] = useState("");
const [titleValue, setTitleValue] = useState("");
const queryParams = useParams();
const { isPending } = useQuery({
queryKey: ["changePostKey", queryParams.postId],
queryFn: async () => {
try {
const response = await axiosLocalhost.get(
`/posts/${queryParams.postId}`
);
setTitleValue(response.data["title"]);
setContentValue(response.data["content"]);
return response.data;
} catch (error) {
console.log(error);
return error;
}
},
gcTime: Infinity,
});
return (
<>
<Box className="flex flex-col flex-1">
<Flex gap={"4"} direction={"column"} className="flex-[1]">
<Container className="flex-[1]">
<input
disabled={isPending}
placeholder={"Post title"}
className="mb-2 border-0 border-b-[1px]
outline-none w-full border-b-gray-400
text-[60px] pl-4 pr-4 font-times"
onChange={(e) => {
setTitleValue(e.target.value);
}}
value={titleValue}
/>
</Container>
<Container className="overflow-y-auto flex-grow-[100]">
{isPending ? (
<Spinner />
) : (
<Editor
defaultValue={contentValue}
onChange={setContentValue}
/>
)}
</Container>
<Box className="flex justify-center flex-[1] mb-4">
<SubmitChangesButton
contentValue={contentValue}
titleValue={titleValue}
className="text-2xl rounded-full w-52" />
</Box>
</Flex>
</Box>
</>
);
}

View File

@ -0,0 +1,57 @@
import { Button } from "@radix-ui/themes";
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
import { axiosLocalhost } from "../../../../api/axios/axios";
type TSubmitChangesButton = {
className: string;
titleValue: string;
contentValue: string;
};
export default function SubmitChangesButton(props: TSubmitChangesButton) {
const { t } = useTranslation();
const [isDisabled, setIsDisabled] = useState(false);
const navigate = useNavigate();
const queryParams = useParams();
const postMutation = useMutation({
mutationFn: async () => {
if (!props.titleValue) throw new Error("no title provided");
if (!props.contentValue || props.contentValue === "<p><br></p>")
throw new Error("no content provided");
axiosLocalhost.put(`/posts/${queryParams["postId"]}`, {
title: props.titleValue,
content: props.contentValue,
});
},
onMutate: () => {
setIsDisabled(true);
},
onError: () => {
setIsDisabled(false);
},
onSuccess: () => {
navigate("/");
},
});
return (
<Button
onClick={() => {
postMutation.mutate();
}}
className={props.className}
variant="soft"
size={"4"}
disabled={isDisabled}
>
{t("updatePost")}
</Button>
);
}

View File

@ -34,7 +34,8 @@ export default function RegisterPage() {
let response = await axiosLocalhost.post("/users", JSON.stringify(data));
setUserAtom({
username: response.data.username,
isAdmin: false
isAdmin: false,
id: response.data.id,
})
},

View File

@ -24,6 +24,7 @@ export default function MainPage() {
setUserData({
isAdmin: response.data["is_admin"],
username: response.data["username"],
id: response.data["id"],
});
return true;
} catch (error) {

View File

@ -7,6 +7,7 @@ import {
import ArticleViewer from "../Components/ArticleViewer/ArticleViewer";
import AuthPageWrapper from "../Pages/AuthPageWrapper/AuthPageWrapper";
import LoginPage from "../Pages/LoginRegisterPage/LoginPage/LoginPage";
import PostRedactor from "../Pages/LoginRegisterPage/PostRedactor/PostRedactor";
import RegisterPage from "../Pages/LoginRegisterPage/RegisterPage/RegisterPage";
import MainPage from "../Pages/MainPage/MainPage";
import PostCreatorPage from "../Pages/PostCreatorPage/PostCreatorPage";
@ -40,6 +41,7 @@ export const routes = createRoutesFromElements(
/>
<Route path="/posts/:postId" element={<ArticleViewer />} />
<Route path="/posts/change/:postId" element={<PostRedactor />} />
</Route>
<Route

View File

@ -68,6 +68,6 @@ func Login(c *gin.Context) {
c.Header("Authorization", token)
c.SetCookie(cookieName, cookieValue, maxAge, path, domain, secure, httpOnly)
c.IndentedJSON(http.StatusOK, gin.H{"token": token, "username": user.Username})
c.IndentedJSON(http.StatusOK, gin.H{"token": token, "username": user.Username, "id": user.UserID})
}

View File

@ -119,5 +119,5 @@ func RegisterUser(c *gin.Context) {
transaction.Commit(context.Background())
rest_api_stuff.SetCookie(c, cookieParams)
c.IndentedJSON(http.StatusOK, gin.H{"status": "All good", "username": userParams.Username})
c.IndentedJSON(http.StatusOK, gin.H{"status": "All good", "username": userParams.Username, "id": userParams.UserID})
}

View File

@ -36,6 +36,7 @@ func testAuth(c *gin.Context) {
"message": "you are logged in, congrats!",
"username": userInfo.Username,
"is_admin": userInfo.IsAdmin,
"id": userInfo.Id,
},
)
}