mirror of
https://github.com/docmost/docmost.git
synced 2025-11-15 06:21:15 +10:00
refactor react-query usage
This commit is contained in:
@ -5,6 +5,7 @@ module.exports = {
|
|||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended',
|
||||||
'plugin:react-hooks/recommended',
|
'plugin:react-hooks/recommended',
|
||||||
|
'plugin:@tanstack/eslint-plugin-query/recommended',
|
||||||
],
|
],
|
||||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
|
|||||||
@ -10,13 +10,14 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hocuspocus/provider": "^2.7.1",
|
"@hocuspocus/provider": "^2.7.1",
|
||||||
"@mantine/core": "^7.2.1",
|
"@mantine/core": "^7.2.2",
|
||||||
"@mantine/form": "^7.2.1",
|
"@mantine/form": "^7.2.2",
|
||||||
"@mantine/hooks": "^7.2.1",
|
"@mantine/hooks": "^7.2.2",
|
||||||
"@mantine/modals": "^7.2.1",
|
"@mantine/modals": "^7.2.2",
|
||||||
"@mantine/spotlight": "^7.2.1",
|
"@mantine/notifications": "^7.2.2",
|
||||||
|
"@mantine/spotlight": "^7.2.2",
|
||||||
"@tabler/icons-react": "^2.40.0",
|
"@tabler/icons-react": "^2.40.0",
|
||||||
"@tanstack/react-query": "^4.36.1",
|
"@tanstack/react-query": "^5.8.3",
|
||||||
"@tiptap/extension-code-block": "^2.1.12",
|
"@tiptap/extension-code-block": "^2.1.12",
|
||||||
"@tiptap/extension-collaboration": "^2.1.12",
|
"@tiptap/extension-collaboration": "^2.1.12",
|
||||||
"@tiptap/extension-collaboration-cursor": "^2.1.12",
|
"@tiptap/extension-collaboration-cursor": "^2.1.12",
|
||||||
@ -61,6 +62,7 @@
|
|||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tanstack/eslint-plugin-query": "^5.8.3",
|
||||||
"@types/js-cookie": "^3.0.4",
|
"@types/js-cookie": "^3.0.4",
|
||||||
"@types/node": "20.8.6",
|
"@types/node": "20.8.6",
|
||||||
"@types/react": "^18.2.29",
|
"@types/react": "^18.2.29",
|
||||||
@ -74,7 +76,7 @@
|
|||||||
"eslint-plugin-react-refresh": "^0.4.3",
|
"eslint-plugin-react-refresh": "^0.4.3",
|
||||||
"optics-ts": "^2.4.1",
|
"optics-ts": "^2.4.1",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
"postcss-preset-mantine": "^1.9.0",
|
"postcss-preset-mantine": "^1.11.0",
|
||||||
"postcss-simple-vars": "^7.0.1",
|
"postcss-simple-vars": "^7.0.1",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
|
|||||||
@ -1,23 +1,27 @@
|
|||||||
import { UnstyledButton, Group, Avatar, Text, rem } from '@mantine/core';
|
import { UnstyledButton, Group, Avatar, Text, rem } from '@mantine/core';
|
||||||
import { IconChevronRight } from '@tabler/icons-react';
|
import { IconChevronRight } from '@tabler/icons-react';
|
||||||
import classes from './user-button.module.css';
|
import classes from './user-button.module.css';
|
||||||
|
import { useAtom } from 'jotai/index';
|
||||||
|
import { currentUserAtom } from '@/features/user/atoms/current-user-atom';
|
||||||
|
|
||||||
export function UserButton() {
|
export function UserButton() {
|
||||||
|
const [currentUser] = useAtom(currentUserAtom);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UnstyledButton className={classes.user}>
|
<UnstyledButton className={classes.user}>
|
||||||
<Group>
|
<Group>
|
||||||
<Avatar
|
<Avatar
|
||||||
src="https://images.unsplash.com/photo-1508214751196-bcfd4ca60f91?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=255&q=80"
|
src="https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/avatars/avatar-9.png"
|
||||||
radius="xl"
|
radius="xl"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<Text size="sm" fw={500}>
|
<Text size="sm" fw={500}>
|
||||||
Harriette Spoonlicker
|
{currentUser.user.name}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text c="dimmed" size="xs">
|
<Text c="dimmed" size="xs">
|
||||||
hspoonlicker@outlook.com
|
{currentUser.user.email}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
import t, { Toaster, useToasterStore } from "react-hot-toast";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
export default function CustomToaster() {
|
|
||||||
const { toasts } = useToasterStore();
|
|
||||||
const TOAST_LIMIT = 3;
|
|
||||||
const [toastLimit, setToastLimit] = useState<number>(TOAST_LIMIT);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
toasts
|
|
||||||
.filter((tt) => tt.visible)
|
|
||||||
.filter((_, i) => i >= toastLimit)
|
|
||||||
.forEach((tt) => {
|
|
||||||
t.dismiss(tt.id);
|
|
||||||
});
|
|
||||||
}, [toastLimit, toasts]);
|
|
||||||
|
|
||||||
return <Toaster position={"top-right"}/>;
|
|
||||||
}
|
|
||||||
@ -1,11 +1,11 @@
|
|||||||
import { useState } from "react";
|
import { useState } from 'react';
|
||||||
import { login, register } from "@/features/auth/services/auth-service";
|
import { login, register } from '@/features/auth/services/auth-service';
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from 'jotai';
|
||||||
import { authTokensAtom } from "@/features/auth/atoms/auth-tokens-atom";
|
import { authTokensAtom } from '@/features/auth/atoms/auth-tokens-atom';
|
||||||
import { currentUserAtom } from "@/features/user/atoms/current-user-atom";
|
import { currentUserAtom } from '@/features/user/atoms/current-user-atom';
|
||||||
import { ILogin, IRegister } from "@/features/auth/types/auth.types";
|
import { ILogin, IRegister } from '@/features/auth/types/auth.types';
|
||||||
import toast from "react-hot-toast";
|
import { notifications } from '@mantine/notifications';
|
||||||
|
|
||||||
export default function useAuth() {
|
export default function useAuth() {
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@ -22,10 +22,13 @@ export default function useAuth() {
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setAuthToken(res.tokens);
|
setAuthToken(res.tokens);
|
||||||
|
|
||||||
navigate("/home");
|
navigate('/home');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
toast.error(err.response?.data.message)
|
notifications.show({
|
||||||
|
message: err.response?.data.message,
|
||||||
|
color: 'red',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -38,10 +41,13 @@ export default function useAuth() {
|
|||||||
|
|
||||||
setAuthToken(res.tokens);
|
setAuthToken(res.tokens);
|
||||||
|
|
||||||
navigate("/home");
|
navigate('/home');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
toast.error(err.response?.data.message)
|
notifications.show({
|
||||||
|
message: err.response?.data.message,
|
||||||
|
color: 'red',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -52,7 +58,7 @@ export default function useAuth() {
|
|||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
setAuthToken(null);
|
setAuthToken(null);
|
||||||
setCurrentUser(null);
|
setCurrentUser(null);
|
||||||
}
|
};
|
||||||
|
|
||||||
return { signIn: handleSignIn, signUp: handleSignUp, isLoading, hasTokens };
|
return { signIn: handleSignIn, signUp: handleSignUp, isLoading, hasTokens };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,5 @@
|
|||||||
import { atom } from 'jotai';
|
import { atom } from 'jotai';
|
||||||
import { atomFamily } from 'jotai/utils';
|
|
||||||
import { IComment } from '@/features/comment/types/comment.types';
|
|
||||||
|
|
||||||
export const commentsAtom = atomFamily((pageId: string) => atom<IComment[]>([]));
|
|
||||||
export const showCommentPopupAtom = atom(false);
|
export const showCommentPopupAtom = atom(false);
|
||||||
export const activeCommentIdAtom = atom<string | null>(null);
|
export const activeCommentIdAtom = atom<string | null>(null);
|
||||||
export const draftCommentIdAtom = atom<string | null>(null);
|
export const draftCommentIdAtom = atom<string | null>(null);
|
||||||
|
|
||||||
export const deleteCommentAtom = atomFamily((pageId: string) => atom(
|
|
||||||
null,
|
|
||||||
(get, set, idToDelete: string) => {
|
|
||||||
const currentPageComments = get(commentsAtom(pageId));
|
|
||||||
const updatedComments = currentPageComments.filter(comment => comment.id !== idToDelete);
|
|
||||||
set(commentsAtom(pageId), updatedComments);
|
|
||||||
}
|
|
||||||
));
|
|
||||||
|
|||||||
@ -1,34 +1,22 @@
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useAtom } from 'jotai';
|
import React from 'react';
|
||||||
import { commentsAtom } from '@/features/comment/atoms/comment-atom';
|
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
import { getPageComments } from '@/features/comment/services/comment-service';
|
|
||||||
import classes from '@/features/comment/components/comment.module.css';
|
import classes from '@/features/comment/components/comment.module.css';
|
||||||
import { Text } from '@mantine/core';
|
import { Text } from '@mantine/core';
|
||||||
import CommentList from '@/features/comment/components/comment-list';
|
import CommentList from '@/features/comment/components/comment-list';
|
||||||
|
import { useCommentsQuery } from '@/features/comment/queries/comment';
|
||||||
|
|
||||||
export default function Comments() {
|
export default function Comments() {
|
||||||
const { pageId } = useParams();
|
const { pageId } = useParams();
|
||||||
const [comments, setComments] = useAtom(commentsAtom(pageId));
|
const { data, isLoading, isError } = useCommentsQuery(pageId);
|
||||||
|
|
||||||
useEffect(() => {
|
if (isLoading) {
|
||||||
const fetchComments = async () => {
|
return <></>;
|
||||||
try {
|
|
||||||
const response = await getPageComments(pageId);
|
|
||||||
setComments(response);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch comments:', error);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
fetchComments();
|
|
||||||
}, [pageId]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.wrapper}>
|
<div className={classes.wrapper}>
|
||||||
<Text mb="md" fw={500}>Comments</Text>
|
<Text mb="md" fw={500}>Comments</Text>
|
||||||
|
{data ? <CommentList comments={data} /> : 'No comments yet'}
|
||||||
<CommentList comments={comments} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { useClickOutside } from '@mantine/hooks';
|
|||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import {
|
import {
|
||||||
activeCommentIdAtom,
|
activeCommentIdAtom,
|
||||||
commentsAtom,
|
|
||||||
draftCommentIdAtom,
|
draftCommentIdAtom,
|
||||||
showCommentPopupAtom,
|
showCommentPopupAtom,
|
||||||
} from '@/features/comment/atoms/comment-atom';
|
} from '@/features/comment/atoms/comment-atom';
|
||||||
@ -12,7 +11,7 @@ import { Editor } from '@tiptap/core';
|
|||||||
import CommentEditor from '@/features/comment/components/comment-editor';
|
import CommentEditor from '@/features/comment/components/comment-editor';
|
||||||
import CommentActions from '@/features/comment/components/comment-actions';
|
import CommentActions from '@/features/comment/components/comment-actions';
|
||||||
import { currentUserAtom } from '@/features/user/atoms/current-user-atom';
|
import { currentUserAtom } from '@/features/user/atoms/current-user-atom';
|
||||||
import useComment from '@/features/comment/hooks/use-comment';
|
import { useCreateCommentMutation } from '@/features/comment/queries/comment';
|
||||||
|
|
||||||
interface CommentDialogProps {
|
interface CommentDialogProps {
|
||||||
editor: Editor,
|
editor: Editor,
|
||||||
@ -24,12 +23,11 @@ function CommentDialog({ editor, pageId }: CommentDialogProps) {
|
|||||||
const [, setShowCommentPopup] = useAtom<boolean>(showCommentPopupAtom);
|
const [, setShowCommentPopup] = useAtom<boolean>(showCommentPopupAtom);
|
||||||
const [, setActiveCommentId] = useAtom<string | null>(activeCommentIdAtom);
|
const [, setActiveCommentId] = useAtom<string | null>(activeCommentIdAtom);
|
||||||
const [draftCommentId, setDraftCommentId] = useAtom<string | null>(draftCommentIdAtom);
|
const [draftCommentId, setDraftCommentId] = useAtom<string | null>(draftCommentIdAtom);
|
||||||
const [comments, setComments] = useAtom(commentsAtom(pageId));
|
|
||||||
const [currentUser] = useAtom(currentUserAtom);
|
const [currentUser] = useAtom(currentUserAtom);
|
||||||
const useClickOutsideRef = useClickOutside(() => {
|
const useClickOutsideRef = useClickOutside(() => {
|
||||||
handleDialogClose();
|
handleDialogClose();
|
||||||
});
|
});
|
||||||
const { createCommentMutation } = useComment();
|
const createCommentMutation = useCreateCommentMutation();
|
||||||
const { isLoading } = createCommentMutation;
|
const { isLoading } = createCommentMutation;
|
||||||
|
|
||||||
const handleDialogClose = () => {
|
const handleDialogClose = () => {
|
||||||
@ -43,6 +41,7 @@ function CommentDialog({ editor, pageId }: CommentDialogProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAddComment = async () => {
|
const handleAddComment = async () => {
|
||||||
|
try {
|
||||||
const selectedText = getSelectedText();
|
const selectedText = getSelectedText();
|
||||||
const commentData = {
|
const commentData = {
|
||||||
id: draftCommentId,
|
id: draftCommentId,
|
||||||
@ -51,9 +50,7 @@ function CommentDialog({ editor, pageId }: CommentDialogProps) {
|
|||||||
selection: selectedText,
|
selection: selectedText,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
|
||||||
const createdComment = await createCommentMutation.mutateAsync(commentData);
|
const createdComment = await createCommentMutation.mutateAsync(commentData);
|
||||||
setComments(prevComments => [...prevComments, createdComment]);
|
|
||||||
editor.chain().setComment(createdComment.id).unsetCommentDecoration().run();
|
editor.chain().setComment(createdComment.id).unsetCommentDecoration().run();
|
||||||
setActiveCommentId(createdComment.id);
|
setActiveCommentId(createdComment.id);
|
||||||
|
|
||||||
|
|||||||
@ -1,45 +1,51 @@
|
|||||||
import { Group, Avatar, Text, Box } from '@mantine/core';
|
import { Group, Avatar, Text, Box } from '@mantine/core';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import classes from './comment.module.css';
|
import classes from './comment.module.css';
|
||||||
import { useAtom, useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { deleteCommentAtom } from '@/features/comment/atoms/comment-atom';
|
|
||||||
import { timeAgo } from '@/lib/time-ago';
|
import { timeAgo } from '@/lib/time-ago';
|
||||||
import CommentEditor from '@/features/comment/components/comment-editor';
|
import CommentEditor from '@/features/comment/components/comment-editor';
|
||||||
import { editorAtom } from '@/features/editor/atoms/editorAtom';
|
import { editorAtom } from '@/features/editor/atoms/editorAtom';
|
||||||
import CommentActions from '@/features/comment/components/comment-actions';
|
import CommentActions from '@/features/comment/components/comment-actions';
|
||||||
import CommentMenu from '@/features/comment/components/comment-menu';
|
import CommentMenu from '@/features/comment/components/comment-menu';
|
||||||
import useComment from '@/features/comment/hooks/use-comment';
|
|
||||||
import ResolveComment from '@/features/comment/components/resolve-comment';
|
import ResolveComment from '@/features/comment/components/resolve-comment';
|
||||||
import { useHover } from '@mantine/hooks';
|
import { useHover } from '@mantine/hooks';
|
||||||
|
import { useDeleteCommentMutation, useUpdateCommentMutation } from '@/features/comment/queries/comment';
|
||||||
|
import { IComment } from '@/features/comment/types/comment.types';
|
||||||
|
|
||||||
function CommentListItem({ comment }) {
|
interface CommentListItemProps {
|
||||||
|
comment: IComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommentListItem({ comment }: CommentListItemProps) {
|
||||||
const { hovered, ref } = useHover();
|
const { hovered, ref } = useHover();
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const editor = useAtomValue(editorAtom);
|
const editor = useAtomValue(editorAtom);
|
||||||
const [content, setContent] = useState(comment.content);
|
const [content, setContent] = useState(comment.content);
|
||||||
const { updateCommentMutation, deleteCommentMutation } = useComment();
|
const updateCommentMutation = useUpdateCommentMutation();
|
||||||
const { isLoading } = updateCommentMutation;
|
const deleteCommentMutation = useDeleteCommentMutation(comment.pageId);
|
||||||
const [, deleteComment] = useAtom(deleteCommentAtom(comment.pageId));
|
|
||||||
|
|
||||||
async function handleUpdateComment() {
|
async function handleUpdateComment() {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
const commentToUpdate = {
|
const commentToUpdate = {
|
||||||
id: comment.id,
|
id: comment.id,
|
||||||
content: JSON.stringify(content),
|
content: JSON.stringify(content),
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
|
||||||
await updateCommentMutation.mutateAsync(commentToUpdate);
|
await updateCommentMutation.mutateAsync(commentToUpdate);
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to update comment:', error);
|
console.error('Failed to update comment:', error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDeleteComment() {
|
async function handleDeleteComment() {
|
||||||
try {
|
try {
|
||||||
await deleteCommentMutation(comment.id);
|
await deleteCommentMutation.mutateAsync(comment.id);
|
||||||
editor?.commands.unsetComment(comment.id);
|
editor?.commands.unsetComment(comment.id);
|
||||||
deleteComment(comment.id); // Todo: unify code
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to delete comment:', error);
|
console.error('Failed to delete comment:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,17 +4,16 @@ import CommentActions from '@/features/comment/components/comment-actions';
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import CommentListItem from '@/features/comment/components/comment-list-item';
|
import CommentListItem from '@/features/comment/components/comment-list-item';
|
||||||
import { IComment } from '@/features/comment/types/comment.types';
|
import { IComment } from '@/features/comment/types/comment.types';
|
||||||
import useComment from '@/features/comment/hooks/use-comment';
|
|
||||||
import { useAtom } from 'jotai';
|
|
||||||
import { commentsAtom } from '@/features/comment/atoms/comment-atom';
|
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
import { useFocusWithin } from '@mantine/hooks';
|
import { useFocusWithin } from '@mantine/hooks';
|
||||||
|
import { useCreateCommentMutation } from '@/features/comment/queries/comment';
|
||||||
|
|
||||||
function CommentList({ comments }: IComment[]) {
|
interface CommentListProps {
|
||||||
const { createCommentMutation } = useComment();
|
comments: IComment[];
|
||||||
const { isLoading } = createCommentMutation;
|
}
|
||||||
const { pageId } = useParams();
|
|
||||||
const [, setCommentsAtom] = useAtom(commentsAtom(pageId));
|
function CommentList({ comments }: CommentListProps) {
|
||||||
|
const createCommentMutation = useCreateCommentMutation();
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
const getChildComments = (parentId) => {
|
const getChildComments = (parentId) => {
|
||||||
return comments.filter(comment => comment.parentCommentId === parentId);
|
return comments.filter(comment => comment.parentCommentId === parentId);
|
||||||
@ -54,14 +53,22 @@ function CommentList({ comments }: IComment[]) {
|
|||||||
|
|
||||||
const renderComments = (comment) => {
|
const renderComments = (comment) => {
|
||||||
const handleAddReply = async (commentId, content) => {
|
const handleAddReply = async (commentId, content) => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
const commentData = {
|
const commentData = {
|
||||||
pageId: comment.pageId,
|
pageId: comment.pageId,
|
||||||
parentCommentId: comment.id,
|
parentCommentId: comment.id,
|
||||||
content: JSON.stringify(content),
|
content: JSON.stringify(content),
|
||||||
};
|
};
|
||||||
|
|
||||||
const createdComment = await createCommentMutation.mutateAsync(commentData);
|
await createCommentMutation.mutateAsync(commentData);
|
||||||
setCommentsAtom(prevComments => [...prevComments, createdComment]);
|
} catch (error) {
|
||||||
|
console.error('Failed to add reply:', error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//setCommentsAtom(prevComments => [...prevComments, createdComment]);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import { ActionIcon } from '@mantine/core';
|
import { ActionIcon } from '@mantine/core';
|
||||||
import { IconCircleCheck } from '@tabler/icons-react';
|
import { IconCircleCheck } from '@tabler/icons-react';
|
||||||
|
|
||||||
import useComment from '@/features/comment/hooks/use-comment';
|
|
||||||
import { useAtom } from 'jotai';
|
|
||||||
import { commentsAtom } from '@/features/comment/atoms/comment-atom';
|
|
||||||
import { modals } from '@mantine/modals';
|
import { modals } from '@mantine/modals';
|
||||||
|
import { useResolveCommentMutation } from '@/features/comment/queries/comment';
|
||||||
|
|
||||||
function ResolveComment({ commentId, pageId, resolvedAt }) {
|
function ResolveComment({ commentId, pageId, resolvedAt }) {
|
||||||
const [, setComments] = useAtom(commentsAtom(pageId));
|
const resolveCommentMutation = useResolveCommentMutation();
|
||||||
const { resolveCommentMutation } = useComment();
|
|
||||||
const isResolved = resolvedAt != null;
|
const isResolved = resolvedAt != null;
|
||||||
const iconColor = isResolved ? 'green' : 'gray';
|
const iconColor = isResolved ? 'green' : 'gray';
|
||||||
|
|
||||||
@ -23,17 +19,9 @@ function ResolveComment({ commentId, pageId, resolvedAt }) {
|
|||||||
|
|
||||||
const handleResolveToggle = async () => {
|
const handleResolveToggle = async () => {
|
||||||
try {
|
try {
|
||||||
const resolvedComment = await resolveCommentMutation.mutateAsync({ commentId, resolved: !isResolved });
|
await resolveCommentMutation.mutateAsync({ commentId, resolved: !isResolved });
|
||||||
//TODO: remove comment mark
|
//TODO: remove comment mark
|
||||||
// Remove comment thread from state on resolve
|
// Remove comment thread from state on resolve
|
||||||
|
|
||||||
setComments((oldComments) =>
|
|
||||||
oldComments.map((comment) =>
|
|
||||||
comment.id === commentId
|
|
||||||
? { ...comment, resolvedAt: resolvedComment.resolvedAt, resolvedById: resolvedComment.resolvedById }
|
|
||||||
: comment,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to toggle resolved state:', error);
|
console.error('Failed to toggle resolved state:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,64 +0,0 @@
|
|||||||
import { useMutation } from '@tanstack/react-query';
|
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
import {
|
|
||||||
createComment,
|
|
||||||
deleteComment,
|
|
||||||
resolveComment,
|
|
||||||
updateComment,
|
|
||||||
} from '@/features/comment/services/comment-service';
|
|
||||||
import { IComment, IResolveComment } from '@/features/comment/types/comment.types';
|
|
||||||
|
|
||||||
export default function useComment() {
|
|
||||||
|
|
||||||
const createMutation = useMutation(
|
|
||||||
(data: Partial<IComment>) => createComment(data),
|
|
||||||
{
|
|
||||||
onSuccess: (data: IComment) => {
|
|
||||||
toast.success('Comment created successfully');
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
toast.error(`Error creating comment: ${error.message}`);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateMutation = useMutation(
|
|
||||||
(data: Partial<IComment>) => updateComment(data),
|
|
||||||
{
|
|
||||||
onSuccess: (data: IComment) => {
|
|
||||||
toast.success('Comment updated successfully');
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
toast.error(`Error updating comment: ${error.message}`);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const resolveMutation = useMutation(
|
|
||||||
(data: IResolveComment) => resolveComment(data),
|
|
||||||
{
|
|
||||||
onError: (error) => {
|
|
||||||
toast.error(`Failed to perform resolve action: ${error.message}`);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const deleteMutation = useMutation(
|
|
||||||
(id: string) => deleteComment(id),
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
toast.success('Comment deleted successfully');
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
toast.error(`Error deleting comment: ${error.message}`);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
createCommentMutation: createMutation,
|
|
||||||
updateCommentMutation: updateMutation,
|
|
||||||
resolveCommentMutation: resolveMutation,
|
|
||||||
deleteCommentMutation: deleteMutation.mutateAsync,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
96
client/src/features/comment/queries/comment.ts
Normal file
96
client/src/features/comment/queries/comment.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { useMutation, useQuery, useQueryClient, UseQueryResult } from '@tanstack/react-query';
|
||||||
|
import {
|
||||||
|
createComment,
|
||||||
|
deleteComment, getPageComments,
|
||||||
|
resolveComment,
|
||||||
|
updateComment,
|
||||||
|
} from '@/features/comment/services/comment-service';
|
||||||
|
import { IComment, IResolveComment } from '@/features/comment/types/comment.types';
|
||||||
|
import { notifications } from '@mantine/notifications';
|
||||||
|
|
||||||
|
export const RQ_KEY = (pageId: string) => ['comment', pageId];
|
||||||
|
|
||||||
|
export function useCommentsQuery(pageId: string): UseQueryResult<IComment[], Error> {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: RQ_KEY(pageId),
|
||||||
|
queryFn: () => getPageComments(pageId),
|
||||||
|
enabled: !!pageId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCreateCommentMutation() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<IComment, Error, Partial<IComment>>({
|
||||||
|
mutationFn: (data) => createComment(data),
|
||||||
|
onSuccess: (data) => {
|
||||||
|
const newComment = data;
|
||||||
|
let comments = queryClient.getQueryData(RQ_KEY(data.pageId));
|
||||||
|
if (comments) {
|
||||||
|
comments = prevComments => [...prevComments, newComment];
|
||||||
|
queryClient.setQueryData(RQ_KEY(data.pageId), comments);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifications.show({ message: 'Comment created successfully' });
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
notifications.show({ message: 'Error creating comment', color: 'red' });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useUpdateCommentMutation() {
|
||||||
|
return useMutation<IComment, Error, Partial<IComment>>({
|
||||||
|
mutationFn: (data) => updateComment(data),
|
||||||
|
onSuccess: (data) => {
|
||||||
|
notifications.show({ message: 'Comment updated successfully' });
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
notifications.show({ message: 'Failed to update comment', color: 'red' });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDeleteCommentMutation(pageId?: string) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (commentId: string) => deleteComment(commentId),
|
||||||
|
onSuccess: (data, variables) => {
|
||||||
|
let comments = queryClient.getQueryData(RQ_KEY(pageId));
|
||||||
|
if (comments) {
|
||||||
|
comments = comments.filter(comment => comment.id !== variables);
|
||||||
|
queryClient.setQueryData(RQ_KEY(pageId), comments);
|
||||||
|
}
|
||||||
|
notifications.show({ message: 'Comment deleted successfully' });
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
notifications.show({ message: 'Failed to delete comment', color: 'red' });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useResolveCommentMutation() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (data: IResolveComment) => resolveComment(data),
|
||||||
|
onSuccess: (data: IComment, variables) => {
|
||||||
|
|
||||||
|
const currentComments = queryClient.getQueryData(RQ_KEY(data.pageId));
|
||||||
|
|
||||||
|
if (currentComments) {
|
||||||
|
const updatedComments = currentComments.map((comment) =>
|
||||||
|
comment.id === variables.commentId ? { ...comment, ...data } : comment,
|
||||||
|
);
|
||||||
|
queryClient.setQueryData(RQ_KEY(data.pageId), updatedComments);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifications.show({ message: 'Comment resolved successfully' });
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
notifications.show({ message: 'Failed to resolve comment', color: 'red' });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@ -14,7 +14,6 @@ import { EditorBubbleMenu } from '@/features/editor/components/bubble-menu/bubbl
|
|||||||
import { Document } from '@tiptap/extension-document';
|
import { Document } from '@tiptap/extension-document';
|
||||||
import { Text } from '@tiptap/extension-text';
|
import { Text } from '@tiptap/extension-text';
|
||||||
import { Heading } from '@tiptap/extension-heading';
|
import { Heading } from '@tiptap/extension-heading';
|
||||||
import usePage from '@/features/page/hooks/use-page';
|
|
||||||
import { useDebouncedValue } from '@mantine/hooks';
|
import { useDebouncedValue } from '@mantine/hooks';
|
||||||
import { pageAtom } from '@/features/page/atoms/page-atom';
|
import { pageAtom } from '@/features/page/atoms/page-atom';
|
||||||
import { IPage } from '@/features/page/types/page.types';
|
import { IPage } from '@/features/page/types/page.types';
|
||||||
@ -24,6 +23,7 @@ import { activeCommentIdAtom, showCommentPopupAtom } from '@/features/comment/at
|
|||||||
import CommentDialog from '@/features/comment/components/comment-dialog';
|
import CommentDialog from '@/features/comment/components/comment-dialog';
|
||||||
import { editorAtom } from '@/features/editor/atoms/editorAtom';
|
import { editorAtom } from '@/features/editor/atoms/editorAtom';
|
||||||
import { collabExtensions, mainExtensions } from '@/features/editor/extensions';
|
import { collabExtensions, mainExtensions } from '@/features/editor/extensions';
|
||||||
|
import { useUpdatePageMutation } from '@/features/page/queries/page';
|
||||||
|
|
||||||
interface EditorProps {
|
interface EditorProps {
|
||||||
pageId: string,
|
pageId: string,
|
||||||
@ -93,7 +93,7 @@ function TiptapEditor({ ydoc, provider, pageId }: TiptapEditorProps) {
|
|||||||
const [page, setPage] = useAtom(pageAtom<IPage>(pageId));
|
const [page, setPage] = useAtom(pageAtom<IPage>(pageId));
|
||||||
const [debouncedTitleState, setDebouncedTitleState] = useState('');
|
const [debouncedTitleState, setDebouncedTitleState] = useState('');
|
||||||
const [debouncedTitle] = useDebouncedValue(debouncedTitleState, 1000);
|
const [debouncedTitle] = useDebouncedValue(debouncedTitleState, 1000);
|
||||||
const { updatePageMutation } = usePage();
|
const updatePageMutation = useUpdatePageMutation();
|
||||||
const [desktopAsideOpened, setDesktopAsideOpened] = useAtom<boolean>(desktopAsideAtom);
|
const [desktopAsideOpened, setDesktopAsideOpened] = useAtom<boolean>(desktopAsideAtom);
|
||||||
const [activeCommentId, setActiveCommentId] = useAtom<string | null>(activeCommentIdAtom);
|
const [activeCommentId, setActiveCommentId] = useAtom<string | null>(activeCommentIdAtom);
|
||||||
const [showCommentPopup, setShowCommentPopup] = useAtom<boolean>(showCommentPopupAtom);
|
const [showCommentPopup, setShowCommentPopup] = useAtom<boolean>(showCommentPopupAtom);
|
||||||
@ -127,7 +127,7 @@ function TiptapEditor({ ydoc, provider, pageId }: TiptapEditorProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (debouncedTitle !== '') {
|
if (debouncedTitle !== '') {
|
||||||
updatePageMutation({ id: pageId, title: debouncedTitle });
|
updatePageMutation.mutate({ id: pageId, title: debouncedTitle });
|
||||||
}
|
}
|
||||||
}, [debouncedTitle]);
|
}, [debouncedTitle]);
|
||||||
|
|
||||||
|
|||||||
@ -3,11 +3,10 @@ import { format } from 'date-fns';
|
|||||||
import classes from './home.module.css';
|
import classes from './home.module.css';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import PageListSkeleton from '@/features/home/components/page-list-skeleton';
|
import PageListSkeleton from '@/features/home/components/page-list-skeleton';
|
||||||
import usePage from '@/features/page/hooks/use-page';
|
import { useRecentChangesQuery } from '@/features/page/queries/page';
|
||||||
|
|
||||||
function RecentChanges() {
|
function RecentChanges() {
|
||||||
const { recentPagesQuery } = usePage();
|
const { data, isLoading, isError } = useRecentChangesQuery();
|
||||||
const { data, isLoading, isError } = recentPagesQuery;
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <PageListSkeleton />;
|
return <PageListSkeleton />;
|
||||||
@ -20,9 +19,9 @@ function RecentChanges() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{data.map((page) => (
|
{data.map((page) => (
|
||||||
<>
|
<div key={page.id}>
|
||||||
<UnstyledButton component={Link} to={`/p/${page.id}`}
|
<UnstyledButton component={Link} to={`/p/${page.id}`}
|
||||||
className={classes.page} p="xs" key={page.id}>
|
className={classes.page} p="xs">
|
||||||
<Group wrap="noWrap">
|
<Group wrap="noWrap">
|
||||||
|
|
||||||
<Stack gap="xs" style={{ flex: 1 }}>
|
<Stack gap="xs" style={{ flex: 1 }}>
|
||||||
@ -37,7 +36,7 @@ function RecentChanges() {
|
|||||||
</Group>
|
</Group>
|
||||||
</UnstyledButton>
|
</UnstyledButton>
|
||||||
<Divider />
|
<Divider />
|
||||||
</>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,38 +0,0 @@
|
|||||||
import { useMutation, useQuery, UseQueryResult } from '@tanstack/react-query';
|
|
||||||
import { createPage, deletePage, getPageById, getRecentChanges, updatePage } from '@/features/page/services/page-service';
|
|
||||||
import { IPage } from '@/features/page/types/page.types';
|
|
||||||
|
|
||||||
export default function usePage(pageId?: string) {
|
|
||||||
const createMutation = useMutation(
|
|
||||||
(data: Partial<IPage>) => createPage(data),
|
|
||||||
);
|
|
||||||
|
|
||||||
const pageQueryResult: UseQueryResult<IPage, unknown> = useQuery(
|
|
||||||
['page', pageId],
|
|
||||||
() => getPageById(pageId as string),
|
|
||||||
{
|
|
||||||
enabled: !!pageId,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const recentPagesQuery: UseQueryResult<IPage[], unknown> = useQuery(
|
|
||||||
['recentChanges'],
|
|
||||||
() => getRecentChanges()
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateMutation = useMutation(
|
|
||||||
(data: Partial<IPage>) => updatePage(data),
|
|
||||||
);
|
|
||||||
|
|
||||||
const removeMutation = useMutation(
|
|
||||||
(id: string) => deletePage(id),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
pageQuery: pageQueryResult,
|
|
||||||
recentPagesQuery: recentPagesQuery,
|
|
||||||
create: createMutation.mutate,
|
|
||||||
updatePageMutation: updateMutation.mutate,
|
|
||||||
remove: removeMutation.mutate,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
45
client/src/features/page/queries/page.ts
Normal file
45
client/src/features/page/queries/page.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { useMutation, useQuery, UseQueryResult, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import {
|
||||||
|
createPage,
|
||||||
|
deletePage,
|
||||||
|
getPageById,
|
||||||
|
getRecentChanges,
|
||||||
|
updatePage,
|
||||||
|
} from '@/features/page/services/page-service';
|
||||||
|
import { IPage } from '@/features/page/types/page.types';
|
||||||
|
|
||||||
|
const RECENT_CHANGES_KEY = ['recentChanges'];
|
||||||
|
|
||||||
|
export function usePageQuery(pageId: string): UseQueryResult<IPage, Error> {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['page', pageId],
|
||||||
|
queryFn: () => getPageById(pageId),
|
||||||
|
enabled: !!pageId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRecentChangesQuery(): UseQueryResult<IPage[], Error> {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: RECENT_CHANGES_KEY,
|
||||||
|
queryFn: () => getRecentChanges(),
|
||||||
|
refetchOnMount: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCreatePageMutation() {
|
||||||
|
return useMutation<IPage, Error, Partial<IPage>>({
|
||||||
|
mutationFn: (data) => createPage(data),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useUpdatePageMutation() {
|
||||||
|
return useMutation<IPage, Error, Partial<IPage>>({
|
||||||
|
mutationFn: (data) => updatePage(data),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDeletePageMutation() {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (pageId: string) => deletePage(pageId),
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -11,13 +11,13 @@ import { treeDataAtom } from '@/features/page/tree/atoms/tree-data-atom';
|
|||||||
import { createPage, deletePage, movePage } from '@/features/page/services/page-service';
|
import { createPage, deletePage, movePage } from '@/features/page/services/page-service';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { IMovePage } from '@/features/page/types/page.types';
|
import { IMovePage } from '@/features/page/types/page.types';
|
||||||
import { useNavigate} from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { TreeNode } from '@/features/page/tree/types';
|
import { TreeNode } from '@/features/page/tree/types';
|
||||||
import usePage from '@/features/page/hooks/use-page';
|
import { useUpdatePageMutation } from '@/features/page/queries/page';
|
||||||
|
|
||||||
export function usePersistence<T>() {
|
export function usePersistence<T>() {
|
||||||
const [data, setData] = useAtom(treeDataAtom);
|
const [data, setData] = useAtom(treeDataAtom);
|
||||||
const { updatePageMutation } = usePage();
|
const updatePageMutation = useUpdatePageMutation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const tree = useMemo(() => new SimpleTree<TreeNode>(data), [data]);
|
const tree = useMemo(() => new SimpleTree<TreeNode>(data), [data]);
|
||||||
@ -34,7 +34,7 @@ export function usePersistence<T>() {
|
|||||||
const afterId = currentTreeData[newDragIndex - 1]?.id || null;
|
const afterId = currentTreeData[newDragIndex - 1]?.id || null;
|
||||||
const beforeId = !afterId && currentTreeData[newDragIndex + 1]?.id || null;
|
const beforeId = !afterId && currentTreeData[newDragIndex + 1]?.id || null;
|
||||||
|
|
||||||
const params: IMovePage= {
|
const params: IMovePage = {
|
||||||
id: args.dragIds[0],
|
id: args.dragIds[0],
|
||||||
after: afterId,
|
after: afterId,
|
||||||
before: beforeId,
|
before: beforeId,
|
||||||
@ -42,7 +42,7 @@ export function usePersistence<T>() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const payload = Object.fromEntries(
|
const payload = Object.fromEntries(
|
||||||
Object.entries(params).filter(([key, value]) => value !== null && value !== undefined)
|
Object.entries(params).filter(([key, value]) => value !== null && value !== undefined),
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -57,7 +57,7 @@ export function usePersistence<T>() {
|
|||||||
setData(tree.data);
|
setData(tree.data);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
updatePageMutation({ id, title: name });
|
updatePageMutation.mutateAsync({ id, title: name });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating page title:', error);
|
console.error('Error updating page title:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import { currentUserAtom } from '@/features/user/atoms/current-user-atom';
|
|||||||
import { updateUser } from '@/features/user/services/user-service';
|
import { updateUser } from '@/features/user/services/user-service';
|
||||||
import { IUser } from '@/features/user/types/user.types';
|
import { IUser } from '@/features/user/types/user.types';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
import { TextInput, Button } from '@mantine/core';
|
import { TextInput, Button } from '@mantine/core';
|
||||||
|
import { notifications } from '@mantine/notifications';
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z.string().min(2).max(40).nonempty('Your name cannot be blank'),
|
name: z.string().min(2).max(40).nonempty('Your name cannot be blank'),
|
||||||
@ -35,10 +35,15 @@ export default function AccountNameForm() {
|
|||||||
try {
|
try {
|
||||||
const updatedUser = await updateUser(data);
|
const updatedUser = await updateUser(data);
|
||||||
setUser(updatedUser);
|
setUser(updatedUser);
|
||||||
toast.success('Updated successfully');
|
notifications.show({
|
||||||
|
message: 'Updated successfully',
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
toast.error('Failed to update data.');
|
notifications.show({
|
||||||
|
message: 'Failed to update data',
|
||||||
|
color: 'red',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { currentUserAtom } from '@/features/user/atoms/current-user-atom';
|
import { currentUserAtom } from '@/features/user/atoms/current-user-atom';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { focusAtom } from 'jotai-optics';
|
import { focusAtom } from 'jotai-optics';
|
||||||
import { updateWorkspace } from '@/features/workspace/services/workspace-service';
|
import { updateWorkspace } from '@/features/workspace/services/workspace-service';
|
||||||
import { IWorkspace } from '@/features/workspace/types/workspace.types';
|
import { IWorkspace } from '@/features/workspace/types/workspace.types';
|
||||||
import { TextInput, Button } from '@mantine/core';
|
import { TextInput, Button } from '@mantine/core';
|
||||||
import { useForm, zodResolver } from '@mantine/form';
|
import { useForm, zodResolver } from '@mantine/form';
|
||||||
|
import { notifications } from '@mantine/notifications';
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z.string().nonempty('Workspace name cannot be blank'),
|
name: z.string().nonempty('Workspace name cannot be blank'),
|
||||||
@ -35,14 +35,15 @@ export default function WorkspaceNameForm() {
|
|||||||
try {
|
try {
|
||||||
const updatedWorkspace = await updateWorkspace(data);
|
const updatedWorkspace = await updateWorkspace(data);
|
||||||
setWorkspace(updatedWorkspace);
|
setWorkspace(updatedWorkspace);
|
||||||
toast.success('Updated successfully');
|
notifications.show({ message: 'Updated successfully' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
toast.error('Failed to update data.');
|
notifications.show({
|
||||||
|
message: 'Failed to update data',
|
||||||
|
color: 'red',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import '@mantine/core/styles.css';
|
import '@mantine/core/styles.css';
|
||||||
import '@mantine/spotlight/styles.css';
|
import '@mantine/spotlight/styles.css';
|
||||||
|
import '@mantine/notifications/styles.css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import App from './App.tsx';
|
import App from './App.tsx';
|
||||||
import { theme } from '@/theme';
|
import { theme } from '@/theme';
|
||||||
import { MantineProvider } from '@mantine/core';
|
import { MantineProvider } from '@mantine/core';
|
||||||
import { TanstackProvider } from '@/components/providers/tanstack-provider';
|
import { TanstackProvider } from '@/components/providers/tanstack-provider';
|
||||||
import CustomToaster from '@/components/ui/custom-toaster';
|
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { ModalsProvider } from '@mantine/modals';
|
import { ModalsProvider } from '@mantine/modals';
|
||||||
|
import { Notifications } from '@mantine/notifications';
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
|
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
|
||||||
|
|
||||||
@ -18,8 +18,8 @@ root.render(
|
|||||||
<MantineProvider theme={theme}>
|
<MantineProvider theme={theme}>
|
||||||
<ModalsProvider>
|
<ModalsProvider>
|
||||||
<TanstackProvider>
|
<TanstackProvider>
|
||||||
|
<Notifications />
|
||||||
<App />
|
<App />
|
||||||
<CustomToaster />
|
|
||||||
</TanstackProvider>
|
</TanstackProvider>
|
||||||
</ModalsProvider>
|
</ModalsProvider>
|
||||||
</MantineProvider>
|
</MantineProvider>
|
||||||
|
|||||||
@ -1,26 +1,26 @@
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useAtom } from 'jotai/index';
|
import { useAtom } from 'jotai';
|
||||||
import usePage from '@/features/page/hooks/use-page';
|
|
||||||
import Editor from '@/features/editor/editor';
|
import Editor from '@/features/editor/editor';
|
||||||
import { pageAtom } from '@/features/page/atoms/page-atom';
|
import { pageAtom } from '@/features/page/atoms/page-atom';
|
||||||
|
import { usePageQuery } from '@/features/page/queries/page';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const { pageId } = useParams();
|
const { pageId } = useParams();
|
||||||
const [, setPage] = useAtom(pageAtom(pageId));
|
const [, setPage] = useAtom(pageAtom(pageId));
|
||||||
const { pageQuery } = usePage(pageId);
|
const { data, isLoading, isError } = usePageQuery(pageId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pageQuery.data) {
|
if (data) {
|
||||||
setPage(pageQuery.data);
|
setPage(data);
|
||||||
}
|
}
|
||||||
}, [pageQuery.data, pageQuery.isLoading, setPage, pageId]);
|
}, [data, isLoading, setPage, pageId]);
|
||||||
|
|
||||||
if (pageQuery.isLoading) {
|
if (isLoading) {
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pageQuery.isError) {
|
if (isError) {
|
||||||
return <div>Error fetching page data.</div>;
|
return <div>Error fetching page data.</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user