diff --git a/moon/apps/web/components/MrView/MRComment.tsx b/moon/apps/web/components/MrView/MRComment.tsx
index c8e5acdb6..6761b47c0 100644
--- a/moon/apps/web/components/MrView/MRComment.tsx
+++ b/moon/apps/web/components/MrView/MRComment.tsx
@@ -4,18 +4,24 @@ import type { MenuProps } from 'antd';
import { NotePlusIcon} from '@gitmono/ui/Icons'
import { formatDistance, fromUnixTime } from 'date-fns';
import LexicalContent from './rich-editor/LexicalContent';
+import { useDeleteMrCommentDelete } from '@/hooks/useDeleteMrCommentDelete';
+import { Conversation } from '@/pages/[org]/mr/[id]';
-const Comment = ({ conv, fetchDetail }:any) => {
+interface CommentProps {
+ conv: Conversation
+ id: string
+}
+
+const Comment = ({ conv, id }: CommentProps) => {
+
+ const { mutate: deleteComment } = useDeleteMrCommentDelete(id)
+ const handleDelete = () => {
+ deleteComment(conv.id)
+ }
- const delete_comment = async () => {
- await fetch(`/api/mr/comment/${conv.id}/delete`, {
- method: 'POST',
- });
- };
const handleMenuClick: MenuProps['onClick'] = ({ key }) => {
if (key === '3') {
- delete_comment()
- fetchDetail()
+ handleDelete()
}
};
diff --git a/moon/apps/web/components/MrView/index.tsx b/moon/apps/web/components/MrView/index.tsx
index 66e431e24..9e5b58b4f 100644
--- a/moon/apps/web/components/MrView/index.tsx
+++ b/moon/apps/web/components/MrView/index.tsx
@@ -1,12 +1,14 @@
'use client'
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { List, PaginationProps, Tag, Tabs, TabsProps } from 'antd';
import { formatDistance, fromUnixTime } from 'date-fns';
-// import { MergeOutlined, PullRequestOutlined, CloseCircleOutlined } from '@ant-design/icons';
import { ChevronSelectIcon,AlarmIcon,ClockIcon} from '@gitmono/ui/Icons'
import { Link } from '@gitmono/ui/Link'
import { Heading } from './catalyst/heading';
+import { usePostMrList } from '@/hooks/usePostMrList';
+import { apiErrorToast } from '@/utils/apiErrorToast'
+import { useScope } from '@/contexts/scope'
interface MrInfoItem {
link: string,
@@ -22,63 +24,81 @@ export default function MrView() {
const [numTotal, setNumTotal] = useState(0);
const [pageSize] = useState(10);
const [status, setStatus] = useState('open')
+ const [page, setPage] = useState(1);
+ const [isLoading, setIsLoading] = useState(false);
+ const { scope } = useScope()
+ const { mutate: fetchMrList } = usePostMrList();
- const fetchData = useCallback(async (page: number, per_page: number) => {
- try {
- const res = await fetch(`/api/mr/list`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
+ const loadMrList = useCallback(() => {
+ setIsLoading(true);
+ fetchMrList(
+ {
+ data: {
+ pagination: {
+ page,
+ per_page: pageSize
},
- body: JSON.stringify({
- pagination: {
- page: page,
- per_page: per_page
- },
- additional: {
- status: status
- }
- }),
- });
- const response = await res.json();
- const data = response.data.data;
-
- setMrList(data.items);
- setNumTotal(data.total)
- } catch (error) {
- // eslint-disable-next-line no-console
- console.error('Error fetching data:', error);
- }
- }, [status]);
+ additional: {
+ status
+ }
+ }
+ },
+ {
+ onSuccess: (response) => {
+ const data = response.data;
+
+ setMrList(
+ data?.items?.map(item => ({
+ ...item,
+ merge_timestamp: item.merge_timestamp ?? null
+ })) ?? []
+ );
+ setNumTotal(data?.total ?? 0);
+ },
+ onError: apiErrorToast,
+ onSettled: () => setIsLoading(false)
+ }
+ );
+ }, [page, pageSize, status, fetchMrList]);
useEffect(() => {
- fetchData(1, pageSize);
- }, [pageSize, status, fetchData]);
+ loadMrList();
+ }, [loadMrList]);
const getStatusTag = (status: string) => {
- switch (status) {
+ const normalizedStatus = status.toLowerCase();
+
+ switch (normalizedStatus) {
case 'open':
return open;
case 'merged':
return merged;
case 'closed':
return closed;
+ default:
+ return null;
}
};
const getStatusIcon = (status: string) => {
- switch (status) {
+ const normalizedStatus = status.toLowerCase();
+
+ switch (normalizedStatus) {
case 'open':
return ;
case 'closed':
return ;
case 'merged':
return ;
+ default:
+ return null;
}
};
const getDescription = (item: MrInfoItem) => {
- switch (item.status) {
+ const normalizedStatus = item.status.toLowerCase();
+
+ switch (normalizedStatus) {
case 'open':
return `MergeRequest opened by Admin ${formatDistance(fromUnixTime(item.open_timestamp), new Date(), { addSuffix: true })} `;
case 'merged':
@@ -88,12 +108,14 @@ export default function MrView() {
return "";
}
case 'closed':
- return (`MR ${item.link} closed by Admin ${formatDistance(fromUnixTime(item.updated_at), new Date(), { addSuffix: true })}`)
+ return (`MR ${item.link} closed by Admin ${formatDistance(fromUnixTime(item.updated_at), new Date(), { addSuffix: true })}`);
+ default:
+ return null;
}
}
- const onChange: PaginationProps['onChange'] = (current, pageSize) => {
- fetchData(current, pageSize);
+ const onChange: PaginationProps['onChange'] = (current) => {
+ setPage(current);
};
const tabsChange = (activeKey: string) => {
@@ -104,7 +126,7 @@ export default function MrView() {
}
}
- const tab_items: TabsProps['items'] = [
+ const tab_items = useMemo(() => [
{
key: '1',
label: 'Open',
@@ -113,9 +135,7 @@ export default function MrView() {
key: '2',
label: 'Closed',
}
- ];
-
-
+ ], []) as TabsProps['items'];
return (
@@ -127,13 +147,23 @@ export default function MrView() {
className="w-full mt-2"
pagination={{ align: "center", pageSize: pageSize, total: numTotal, onChange: onChange }}
dataSource={mrList}
+ loading={isLoading}
renderItem={(item) => (
{`${item.title}`} {getStatusTag(item.status)}}
+ title={
+
+ {`${item.title}`} {getStatusTag(item.status)}
+
+ }
description={getDescription(item)}
/>
diff --git a/moon/apps/web/hooks/useDeleteMrCommentDelete.ts b/moon/apps/web/hooks/useDeleteMrCommentDelete.ts
new file mode 100644
index 000000000..6642db5e4
--- /dev/null
+++ b/moon/apps/web/hooks/useDeleteMrCommentDelete.ts
@@ -0,0 +1,18 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query'
+import { legacyApiClient } from '@/utils/queryClient'
+import type { DeleteApiMrCommentDeleteData, RequestParams } from '@gitmono/types'
+
+export function useDeleteMrCommentDelete(id: string, params?: RequestParams) {
+ const queryClient = useQueryClient()
+
+ return useMutation
({
+ mutationKey: legacyApiClient.v1.deleteApiMrCommentDelete().baseKey,
+ mutationFn: (convId) =>
+ legacyApiClient.v1.deleteApiMrCommentDelete().request(convId, params),
+ onSuccess: (_data, _convId) => {
+ queryClient.invalidateQueries({
+ queryKey: legacyApiClient.v1.getApiMrDetail().requestKey(id),
+ })
+ },
+ })
+}
\ No newline at end of file
diff --git a/moon/apps/web/hooks/useGetMrDetail.ts b/moon/apps/web/hooks/useGetMrDetail.ts
new file mode 100644
index 000000000..87d438e6c
--- /dev/null
+++ b/moon/apps/web/hooks/useGetMrDetail.ts
@@ -0,0 +1,11 @@
+import { useQuery } from '@tanstack/react-query'
+import { legacyApiClient } from '@/utils/queryClient'
+import type { RequestParams } from '@gitmono/types'
+
+export function useGetMrDetail(id: string, params?: RequestParams) {
+ return useQuery({
+ queryKey: legacyApiClient.v1.getApiMrDetail().requestKey(id),
+ queryFn: () => legacyApiClient.v1.getApiMrDetail().request(id, params),
+ enabled: !!id,
+ })
+}
\ No newline at end of file
diff --git a/moon/apps/web/hooks/useGetMrFilesChanged.ts b/moon/apps/web/hooks/useGetMrFilesChanged.ts
new file mode 100644
index 000000000..d860f50f4
--- /dev/null
+++ b/moon/apps/web/hooks/useGetMrFilesChanged.ts
@@ -0,0 +1,12 @@
+import { useQuery } from '@tanstack/react-query'
+import { legacyApiClient } from '@/utils/queryClient'
+import type { RequestParams } from '@gitmono/types'
+import type { GetApiMrFilesChangedData } from '@gitmono/types/generated'
+
+export function useGetMrFilesChanged(id: string, params?: RequestParams) {
+ return useQuery({
+ queryKey: legacyApiClient.v1.getApiMrFilesChanged().requestKey(id),
+ queryFn: () => legacyApiClient.v1.getApiMrFilesChanged().request(id, params),
+ enabled: !!id,
+ })
+}
\ No newline at end of file
diff --git a/moon/apps/web/hooks/usePostMrClose.ts b/moon/apps/web/hooks/usePostMrClose.ts
new file mode 100644
index 000000000..993bb5fe6
--- /dev/null
+++ b/moon/apps/web/hooks/usePostMrClose.ts
@@ -0,0 +1,17 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query'
+import { legacyApiClient } from '@/utils/queryClient'
+import type { PostApiMrCloseData, RequestParams } from '@gitmono/types'
+
+export function usePostMrClose(link: string, params?: RequestParams) {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationKey: legacyApiClient.v1.postApiMrClose().requestKey(link),
+ mutationFn: () => legacyApiClient.v1.postApiMrClose().request(link, params),
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: legacyApiClient.v1.getApiMrDetail().requestKey(link),
+ })
+ },
+ })
+}
\ No newline at end of file
diff --git a/moon/apps/web/hooks/usePostMrComment.ts b/moon/apps/web/hooks/usePostMrComment.ts
new file mode 100644
index 000000000..55d56727c
--- /dev/null
+++ b/moon/apps/web/hooks/usePostMrComment.ts
@@ -0,0 +1,18 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query'
+import { legacyApiClient } from '@/utils/queryClient'
+import type { SaveCommentRequest, PostApiMrCommentData, RequestParams } from '@gitmono/types'
+
+export function usePostMrComment(link: string, params?: RequestParams) {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationKey: legacyApiClient.v1.postApiMrComment().requestKey(link),
+ mutationFn: (data) =>
+ legacyApiClient.v1.postApiMrComment().request(link, data, params),
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: legacyApiClient.v1.getApiMrDetail().requestKey(link),
+ })
+ },
+ })
+}
\ No newline at end of file
diff --git a/moon/apps/web/hooks/usePostMrList.ts b/moon/apps/web/hooks/usePostMrList.ts
new file mode 100644
index 000000000..2fbe1d878
--- /dev/null
+++ b/moon/apps/web/hooks/usePostMrList.ts
@@ -0,0 +1,11 @@
+// hooks/usePostApiMrList.ts
+import { useMutation } from '@tanstack/react-query'
+import { legacyApiClient } from '@/utils/queryClient'
+import { PageParamsMRStatusParams, RequestParams, PostApiMrListData } from '@gitmono/types'
+
+export function usePostMrList() {
+ return useMutation({
+ mutationFn: ({ data, params }) =>
+ legacyApiClient.v1.postApiMrList().request(data, params),
+ })
+}
\ No newline at end of file
diff --git a/moon/apps/web/hooks/usePostMrMerge.ts b/moon/apps/web/hooks/usePostMrMerge.ts
new file mode 100644
index 000000000..1aed3b2b7
--- /dev/null
+++ b/moon/apps/web/hooks/usePostMrMerge.ts
@@ -0,0 +1,19 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query'
+import { legacyApiClient } from '@/utils/queryClient'
+import type { PostApiMrMergeData, RequestParams } from '@gitmono/types'
+
+export function usePostMrMerge(link: string, params?: RequestParams) {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationKey: legacyApiClient.v1.postApiMrMerge().requestKey(link),
+ mutationFn: () =>
+ legacyApiClient.v1.postApiMrMerge().request(link, params),
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: legacyApiClient.v1.getApiMrDetail().requestKey(link),
+ })
+ },
+ })
+}
+
diff --git a/moon/apps/web/hooks/usePostMrReopen.ts b/moon/apps/web/hooks/usePostMrReopen.ts
new file mode 100644
index 000000000..a8bf1a1e8
--- /dev/null
+++ b/moon/apps/web/hooks/usePostMrReopen.ts
@@ -0,0 +1,17 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query'
+import { legacyApiClient } from '@/utils/queryClient'
+import type { PostApiMrReopenData, RequestParams } from '@gitmono/types'
+
+export function usePostMrReopen(link: string, params?: RequestParams) {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationKey: legacyApiClient.v1.postApiMrReopen().requestKey(link),
+ mutationFn: () => legacyApiClient.v1.postApiMrReopen().request(link, params),
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: legacyApiClient.v1.getApiMrDetail().requestKey(link),
+ })
+ },
+ })
+}
\ No newline at end of file
diff --git a/moon/apps/web/pages/[org]/mr/[id].tsx b/moon/apps/web/pages/[org]/mr/[id].tsx
index 751fb476a..2059aa385 100644
--- a/moon/apps/web/pages/[org]/mr/[id].tsx
+++ b/moon/apps/web/pages/[org]/mr/[id].tsx
@@ -1,13 +1,13 @@
'use client'
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useCallback, useState } from 'react';
import { Card, Tabs, TabsProps,Timeline,} from 'antd';
// import { CommentOutlined, MergeOutlined, CloseCircleOutlined, PullRequestOutlined } from '@ant-design/icons';
import { ChevronRightCircleIcon, ChevronSelectIcon,AlarmIcon,ClockIcon} from '@gitmono/ui/Icons'
import { formatDistance, fromUnixTime } from 'date-fns';
import RichEditor from '@/components/MrView/rich-editor/RichEditor';
import MRComment from '@/components/MrView/MRComment';
-import { useParams, useRouter } from 'next/navigation';
+import { useRouter } from 'next/router';
import * as Diff2Html from 'diff2html';
import 'diff2html/bundles/css/diff2html.min.css';
import FilesChanged from '@/components/MrView/files-changed';
@@ -18,13 +18,19 @@ import { cn } from '@gitmono/ui/utils';
import AuthAppProviders from '@/components/Providers/AuthAppProviders';
import { AppLayout } from '@/components/Layout/AppLayout';
import { PageWithLayout } from '@/utils/types';
+import { useGetMrDetail } from '@/hooks/useGetMrDetail'
+import { useGetMrFilesChanged } from '@/hooks/useGetMrFilesChanged';
+import { usePostMrComment } from '@/hooks/usePostMrComment'
+import { usePostMrMerge } from '@/hooks/usePostMrMerge'
+import { usePostMrReopen } from '@/hooks/usePostMrReopen';
+import { usePostMrClose } from '@/hooks/usePostMrClose';
interface MRDetail {
status: string,
conversations: Conversation[],
title: string,
}
-interface Conversation {
+export interface Conversation {
id: number,
user_id: number,
conv_type: string,
@@ -33,143 +39,71 @@ interface Conversation {
}
const MRDetailPage:PageWithLayout = () =>{
- // const { id } = React.use(params)
const router = useRouter();
- const params = useParams();
-
- const id = params?.id as string;
+ const { id : tempId, title } = router.query;
const [editorState, setEditorState] = useState("");
- const [login, setLogin] = useState(false);
- const [mrDetail, setMrDetail] = useState(
- {
- status: "",
- conversations: [],
- title: "",
- }
- );
- const [filedata, setFileData] = useState([]);
- const [loadings, setLoadings] = useState([]);
+ const [login, _setLogin] = useState(false);
const [outputHtml, setOutputHtml] = useState('');
- const checkLogin = async () => {
- const res = await fetch(`/api/auth`);
-
- setLogin(res.ok);
- };
-
- const fetchDetail = useCallback(async () => {
- const detail = await fetch(`/api/mr/${id}/detail`);
- const detail_json = await detail.json();
-
- setMrDetail(detail_json.data.data);
- }, [id]);
-
- const fetchFileList = useCallback(async () => {
- set_to_loading(2)
- try {
- const res = await fetch(`/api/mr/${id}/files`);
- const result = await res.json();
-
- setFileData(result.data.data);
- } finally {
- cancel_loading(2)
- }
- }, [id]);
-
- const get_diff_content = useCallback(async () => {
- const detail = await fetch(`/api/mr/${id}/files-changed`);
- const res = await detail.json();
- const diff = Diff2Html.html(res.data.data.content, { drawFileList: true, matching: 'lines' })
-
- setOutputHtml(diff);
- }, [id])
+ const id = typeof tempId === 'string' ? tempId : '';
+ const { data: MrDetailData } = useGetMrDetail(id)
+ const mrDetail = MrDetailData?.data as MRDetail | undefined
- useEffect(() => {
- fetchDetail()
- fetchFileList();
- checkLogin();
-
- }, [id, fetchDetail, fetchFileList]);
- useEffect(()=>{
- // eslint-disable-next-line no-console
- console.log(filedata);
- },[filedata])
-
- const set_to_loading = (index: number) => {
- setLoadings((prevLoadings) => {
- const newLoadings = [...prevLoadings];
-
- newLoadings[index] = true;
- return newLoadings;
- });
+ if (mrDetail && typeof mrDetail.status === 'string') {
+ mrDetail.status = mrDetail.status.toLowerCase();
}
- const cancel_loading = (index: number) => {
- setLoadings((prevLoadings) => {
- const newLoadings = [...prevLoadings];
+ const { data: MrFilesChangedData} = useGetMrFilesChanged(id)
+ const get_diff_content = useCallback(() => {
+ const content = MrFilesChangedData?.data?.content;
- newLoadings[index] = false;
- return newLoadings;
+ if (typeof content !== 'string') return;
+ const diff = Diff2Html.html(content, {
+ drawFileList: true,
+ matching: 'lines',
});
+
+ setOutputHtml(diff);
+ }, [MrFilesChangedData]);
+
+ const { mutate: approveMr, isPending : mrMergeIsPending } = usePostMrMerge(id)
+ const handleMrApprove = () => {
+ approveMr(undefined, {
+ onSuccess: () => {
+ router.push("/mr")
+ },
+ })
}
- async function approve_mr() {
- set_to_loading(1);
- const res = await fetch(`/api/mr/${id}/merge`, {
- method: 'POST',
- });
-
- if (res) {
- cancel_loading(1);
- router.push(
- "/mr"
- );
- }
- };
-
- async function close_mr() {
- set_to_loading(3);
- const res = await fetch(`/api/mr/${id}/close`, {
- method: 'POST',
- });
-
- if (res) {
- cancel_loading(3);
- router.push(
- "/mr"
- );
- }
- };
-
- async function reopen_mr() {
- set_to_loading(3);
- const res = await fetch(`/api/mr/${id}/reopen`, {
- method: 'POST',
- });
-
- if (res) {
- cancel_loading(3);
- router.push(
- "/mr"
- );
- }
- };
-
+ const { mutate: closeMr, isPending: mrCloseIsPending } = usePostMrClose(id)
+ const handleMrClose = () => {
+ closeMr(undefined, {
+ onSuccess: () => {
+ router.push("/mr")
+ },
+ })
+ }
- async function save_comment(comment:any) {
- set_to_loading(3);
- const res = await fetch(`/api/mr/${id}/comment`, {
- method: 'POST',
- body: comment,
- });
+ const { mutate: reopenMr, isPending: mrReopenIsPending } = usePostMrReopen(id)
+ const handleMrReopen = () => {
+ reopenMr(undefined,{
+ onSuccess: () => {
+ router.push("/mr")
+ },
+ })
+ }
- if (res) {
- setEditorState("");
- fetchDetail();
- cancel_loading(3);
- }
+ const { mutate: postMrComment, isPending : mrCommentIsPending } = usePostMrComment(id)
+ const save_comment = () => {
+ postMrComment(
+ { content: editorState },
+ {
+ onSuccess: () => {
+ setEditorState("");
+ },
+ });
}
let conv_items = mrDetail?.conversations.map(conv => {
@@ -177,7 +111,7 @@ const MRDetailPage:PageWithLayout = () =>{
let children;
switch (conv.conv_type) {
- case "Comment": icon = ; children = ; break
+ case "Comment": icon = ; children = ; break
case "Merged": icon = ; children = "Merged via the queue into main " + formatDistance(fromUnixTime(conv.created_at), new Date(), { addSuffix: true }); break;
case "Closed": icon = ; children = conv.comment; break;
case "Reopen": icon = ; children = conv.comment; break;
@@ -193,8 +127,6 @@ const MRDetailPage:PageWithLayout = () =>{
});
const onTabsChange = (key: string) => {
- // eslint-disable-next-line no-console
- console.log(key);
if (key === '2') {
get_diff_content()
}
@@ -214,33 +146,33 @@ const MRDetailPage:PageWithLayout = () =>{
{mrDetail && mrDetail.status === "open" &&
}
{mrDetail && mrDetail.status === "closed" &&
}
@@ -254,15 +186,15 @@ const MRDetailPage:PageWithLayout = () =>{
];
return (
-
+
{mrDetail && mrDetail.status === "open" &&
}