Post update stuff
This commit is contained in:
parent
c8967df573
commit
1ab8022b95
@ -1,4 +1,5 @@
|
|||||||
export type TUser = {
|
export type TUser = {
|
||||||
username: string;
|
username: string;
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
|
id?: string | number;
|
||||||
}
|
}
|
||||||
@ -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 { useQuery } from "@tanstack/react-query";
|
||||||
import { Interweave } from "interweave";
|
import { Interweave } from "interweave";
|
||||||
|
import { useAtomValue } from "jotai";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { axiosLocalhost } from "../../api/axios/axios";
|
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 = {
|
type TArticleViewer = {
|
||||||
htmlToParse?: string;
|
htmlToParse?: string;
|
||||||
@ -11,6 +14,7 @@ type TArticleViewer = {
|
|||||||
|
|
||||||
export default function ArticleViewer(props: TArticleViewer) {
|
export default function ArticleViewer(props: TArticleViewer) {
|
||||||
let queryParams = useParams();
|
let queryParams = useParams();
|
||||||
|
const user = useAtomValue(userAtom)
|
||||||
|
|
||||||
const { data, isPending } = useQuery({
|
const { data, isPending } = useQuery({
|
||||||
queryKey: [`post_${queryParams["postId"]}`],
|
queryKey: [`post_${queryParams["postId"]}`],
|
||||||
@ -21,18 +25,27 @@ export default function ArticleViewer(props: TArticleViewer) {
|
|||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
if (isPending)
|
|
||||||
return (
|
if (isPending) return <SkeletonPostLoader />;
|
||||||
<SkeletonLoader />
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="ql-snow">
|
<div className="ql-snow">
|
||||||
<Container size={"2"} className="mt-4">
|
<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" />
|
<Separator size={"4"} className="mb-2" />
|
||||||
<Interweave content={data.content} />
|
<Interweave content={data.content} />
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -5,7 +5,7 @@ import {
|
|||||||
pText,
|
pText,
|
||||||
} from "../../../constants/textForSkeleton";
|
} from "../../../constants/textForSkeleton";
|
||||||
|
|
||||||
export default function SkeletonLoader() {
|
export default function SkeletonPostLoader() {
|
||||||
return (
|
return (
|
||||||
<Container size={"2"} className="mt-4">
|
<Container size={"2"} className="mt-4">
|
||||||
<Skeleton>
|
<Skeleton>
|
||||||
|
|||||||
@ -1,11 +1,6 @@
|
|||||||
import Sources from "quill";
|
import Sources from "quill";
|
||||||
import Quill, { Delta } from "quill/core";
|
import Quill, { Delta } from "quill/core";
|
||||||
import {
|
import { forwardRef, useEffect, useRef, useState } from "react";
|
||||||
forwardRef,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState
|
|
||||||
} from "react";
|
|
||||||
import ReactQuill from "react-quill";
|
import ReactQuill from "react-quill";
|
||||||
|
|
||||||
type TEditor = {
|
type TEditor = {
|
||||||
@ -28,7 +23,7 @@ const modules = {
|
|||||||
["link", "image"],
|
["link", "image"],
|
||||||
["clean"],
|
["clean"],
|
||||||
[{ align: [] }],
|
[{ align: [] }],
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,7 +32,9 @@ const modules = {
|
|||||||
const Editor = forwardRef((props: TEditor) => {
|
const Editor = forwardRef((props: TEditor) => {
|
||||||
const editor = useRef(null);
|
const editor = useRef(null);
|
||||||
const [quill, setQuill] = useState<Quill | null>(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(() => {
|
useEffect(() => {
|
||||||
if (editor.current) {
|
if (editor.current) {
|
||||||
@ -50,14 +47,33 @@ const Editor = forwardRef((props: TEditor) => {
|
|||||||
};
|
};
|
||||||
}, [editor.current]);
|
}, [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) => {
|
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(val);
|
||||||
console.log(JSON.stringify(quill?.getContents().ops, null, 2))
|
console.log(JSON.stringify(quill?.getContents().ops, null, 2));
|
||||||
let fullDelta = quill?.getContents()
|
let fullDelta = quill?.getContents();
|
||||||
if (props.onChange) props.onChange(val || "")
|
if (props.onChange) props.onChange(val || "");
|
||||||
setValue(fullDelta || new Delta())
|
if (loaded) setValue(fullDelta || new Delta());
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="text-editor">
|
<div className="text-editor">
|
||||||
@ -65,10 +81,7 @@ const Editor = forwardRef((props: TEditor) => {
|
|||||||
value={value}
|
value={value}
|
||||||
ref={editor}
|
ref={editor}
|
||||||
modules={modules}
|
modules={modules}
|
||||||
|
|
||||||
onChange={changeHandler}
|
onChange={changeHandler}
|
||||||
|
|
||||||
|
|
||||||
theme="snow"
|
theme="snow"
|
||||||
placeholder="Type your thoughts here..."
|
placeholder="Type your thoughts here..."
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -33,6 +33,7 @@ export default function LoginPage() {
|
|||||||
setUserAtom({
|
setUserAtom({
|
||||||
username: response.data.username,
|
username: response.data.username,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
|
id: response.data.id,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -48,6 +49,7 @@ export default function LoginPage() {
|
|||||||
setUserAtom({
|
setUserAtom({
|
||||||
username: userAtomValue?.username || "",
|
username: userAtomValue?.username || "",
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
|
id: userAtomValue?.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -34,7 +34,8 @@ export default function RegisterPage() {
|
|||||||
let response = await axiosLocalhost.post("/users", JSON.stringify(data));
|
let response = await axiosLocalhost.post("/users", JSON.stringify(data));
|
||||||
setUserAtom({
|
setUserAtom({
|
||||||
username: response.data.username,
|
username: response.data.username,
|
||||||
isAdmin: false
|
isAdmin: false,
|
||||||
|
id: response.data.id,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ export default function MainPage() {
|
|||||||
setUserData({
|
setUserData({
|
||||||
isAdmin: response.data["is_admin"],
|
isAdmin: response.data["is_admin"],
|
||||||
username: response.data["username"],
|
username: response.data["username"],
|
||||||
|
id: response.data["id"],
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
import ArticleViewer from "../Components/ArticleViewer/ArticleViewer";
|
import ArticleViewer from "../Components/ArticleViewer/ArticleViewer";
|
||||||
import AuthPageWrapper from "../Pages/AuthPageWrapper/AuthPageWrapper";
|
import AuthPageWrapper from "../Pages/AuthPageWrapper/AuthPageWrapper";
|
||||||
import LoginPage from "../Pages/LoginRegisterPage/LoginPage/LoginPage";
|
import LoginPage from "../Pages/LoginRegisterPage/LoginPage/LoginPage";
|
||||||
|
import PostRedactor from "../Pages/LoginRegisterPage/PostRedactor/PostRedactor";
|
||||||
import RegisterPage from "../Pages/LoginRegisterPage/RegisterPage/RegisterPage";
|
import RegisterPage from "../Pages/LoginRegisterPage/RegisterPage/RegisterPage";
|
||||||
import MainPage from "../Pages/MainPage/MainPage";
|
import MainPage from "../Pages/MainPage/MainPage";
|
||||||
import PostCreatorPage from "../Pages/PostCreatorPage/PostCreatorPage";
|
import PostCreatorPage from "../Pages/PostCreatorPage/PostCreatorPage";
|
||||||
@ -40,6 +41,7 @@ export const routes = createRoutesFromElements(
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Route path="/posts/:postId" element={<ArticleViewer />} />
|
<Route path="/posts/:postId" element={<ArticleViewer />} />
|
||||||
|
<Route path="/posts/change/:postId" element={<PostRedactor />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@ -68,6 +68,6 @@ func Login(c *gin.Context) {
|
|||||||
|
|
||||||
c.Header("Authorization", token)
|
c.Header("Authorization", token)
|
||||||
c.SetCookie(cookieName, cookieValue, maxAge, path, domain, secure, httpOnly)
|
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})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -119,5 +119,5 @@ func RegisterUser(c *gin.Context) {
|
|||||||
|
|
||||||
transaction.Commit(context.Background())
|
transaction.Commit(context.Background())
|
||||||
rest_api_stuff.SetCookie(c, cookieParams)
|
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})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,7 @@ func testAuth(c *gin.Context) {
|
|||||||
"message": "you are logged in, congrats!",
|
"message": "you are logged in, congrats!",
|
||||||
"username": userInfo.Username,
|
"username": userInfo.Username,
|
||||||
"is_admin": userInfo.IsAdmin,
|
"is_admin": userInfo.IsAdmin,
|
||||||
|
"id": userInfo.Id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user