diff --git a/.github/workflows/web-deploy.yml b/.github/workflows/web-deploy.yml index 3315eec49..b236d2afe 100644 --- a/.github/workflows/web-deploy.yml +++ b/.github/workflows/web-deploy.yml @@ -59,10 +59,6 @@ jobs: working-directory: ./moon run: pnpm run lint - - name: Build Next.js application - working-directory: ./moon - run: pnpm run build - - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: diff --git a/moon/Dockerfile b/moon/Dockerfile index 9aff212dd..57df3cbaf 100644 --- a/moon/Dockerfile +++ b/moon/Dockerfile @@ -1,16 +1,31 @@ - FROM node:20-alpine AS runner + # 构建阶段 + FROM node:20-alpine AS builder WORKDIR /app - ENV PORT=3000 - # 安装 pnpm(保持一致版本) RUN corepack enable && corepack prepare pnpm@9.7.1 --activate - # 拷贝构建产物和依赖 + # 安装依赖 COPY . . + RUN pnpm install --frozen-lockfile + + # 构建:递归构建nextjs中的所有依赖 + RUN pnpm run build + + # 运行阶段 + FROM node:20-alpine AS runner + WORKDIR /app + ENV PORT=3000 + + # 启用 pnpm + RUN corepack enable && corepack prepare pnpm@9.7.1 --activate + + COPY --from=builder /app/apps/web/.next/standalone ./ + COPY --from=builder /app/apps/web/.next/static ./.next/static + COPY --from=builder /app/apps/web/public ./public # 启动 SSR 服务 WORKDIR /app/apps/web - CMD ["pnpm", "start"] + CMD ["node", "server.js"] \ No newline at end of file diff --git a/moon/apps/web/components/Catalyst/Heading.tsx b/moon/apps/web/components/Catalyst/Heading.tsx new file mode 100644 index 000000000..5ccc9d134 --- /dev/null +++ b/moon/apps/web/components/Catalyst/Heading.tsx @@ -0,0 +1,27 @@ +import clsx from 'clsx' + +type HeadingProps = { level?: 1 | 2 | 3 | 4 | 5 | 6 } & React.ComponentPropsWithoutRef< + 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' +> + +export function Heading({ className, level = 1, ...props }: HeadingProps) { + let Element: `h${typeof level}` = `h${level}` + + return ( + + ) +} + +export function Subheading({ className, level = 2, ...props }: HeadingProps) { + let Element: `h${typeof level}` = `h${level}` + + return ( + + ) +} diff --git a/moon/apps/web/components/Issues/IssueDetailPage.tsx b/moon/apps/web/components/Issues/IssueDetailPage.tsx new file mode 100644 index 000000000..f53ecb316 --- /dev/null +++ b/moon/apps/web/components/Issues/IssueDetailPage.tsx @@ -0,0 +1,230 @@ +'use client' + +import React, { useCallback, useEffect, useState } from 'react' +// import { CloseCircleOutlined, CommentOutlined } from '@ant-design/icons' +import { Button, Card, Flex, Space, Tabs, TabsProps, Timeline } from 'antd' +import { useRouter } from 'next/router' +import toast from 'react-hot-toast' + +import Comment from '@/components/MrView/MRComment' +import RichEditor from '@/components/MrView/rich-editor/RichEditor' +import { useGetIssueDetail } from '@/hooks/issues/useGetIssueDetail' +import { usePostIssueClose } from '@/hooks/issues/usePostIssueClose' +import { usePostIssueComment } from '@/hooks/issues/usePostIssueComment' +import { usePostIssueReopen } from '@/hooks/issues/usePostIssueReopen' +import { apiErrorToast } from '@/utils/apiErrorToast' + +interface IssueDetail { + status: string + conversations: Conversation[] + title: string +} +interface Conversation { + id: number + user_id: number + conv_type: string + comment: string + created_at: number +} + +interface detailRes { + err_message: string + data: IssueDetail + req_result: boolean +} + +export default function IssueDetailPage({ id }: { id: string }) { + const [editorState, setEditorState] = useState('') + const [login, setLogin] = useState(false) + const [info, setInfo] = useState({ + status: '', + conversations: [], + title: '' + }) + + const [buttonLoading, setButtonLoading] = useState<{ [key: string]: boolean }>({ + comment: false, + close: false, + reopen: false + }) + + const setLoading = (key: string, value: boolean) => { + setButtonLoading((prev) => ({ ...prev, [key]: value })) + } + + const { mutate: closeIssue } = usePostIssueClose() + + const { mutate: reopenIssue } = usePostIssueReopen() + + const { mutate: saveComment } = usePostIssueComment() + + const { data: issueDetailObj, error, isError, refetch } = useGetIssueDetail(id) + + const applyDetailData = (detail: detailRes | undefined) => { + if (!detail || !detail.req_result) return + setInfo({ + title: detail.data.title, + status: detail.data.status, + conversations: detail.data.conversations + }) + } + + const fetchDetail = useCallback(() => { + if (error || isError) return + applyDetailData(issueDetailObj) + setLogin(true) + }, [issueDetailObj, error, isError]) + + useEffect(() => { + fetchDetail() + }, [fetchDetail]) + + const [_loadings, setLoadings] = useState([]) + const router = useRouter() + + const set_to_loading = (index: number) => { + setLoadings((prevLoadings) => { + const newLoadings = [...prevLoadings] + + newLoadings[index] = true + return newLoadings + }) + } + + const cancel_loading = (index: number) => { + setLoadings((prevLoadings) => { + const newLoadings = [...prevLoadings] + + newLoadings[index] = false + return newLoadings + }) + } + + const close_issue = useCallback(() => { + setLoading('close', true) + set_to_loading(3) + closeIssue( + { link: id }, + { + onSuccess: () => { + router.push(`/${router.query.org}/issue`) + cancel_loading(3) + }, + onError: apiErrorToast, + onSettled: () => setLoading('close', false) + } + ) + }, [id, router, closeIssue]) + + const reopen_issue = useCallback(() => { + setLoading('reopen', true) + set_to_loading(3) + reopenIssue( + { link: id }, + { + onSuccess: () => { + router.push(`/${router.query.org}/issue`) + }, + onError: apiErrorToast, + onSettled: () => setLoading('reopen', false) + } + ) + }, [id, router, reopenIssue]) + + const save_comment = useCallback( + (comment: string) => { + if (JSON.parse(comment).root.children[0].children.length === 0) { + toast.error('comment can not be empty!') + return + } + setLoading('comment', true) + set_to_loading(3) + saveComment( + { link: id, data: { content: comment } }, + { + onSuccess: async () => { + setEditorState('') + const { data: issueDetailObj } = await refetch({ throwOnError: true }) + + applyDetailData(issueDetailObj) + cancel_loading(3) + toast.success('comment successfully!') + }, + onError: apiErrorToast, + onSettled: () => setLoading('comment', false) + } + ) + }, + [id, refetch, saveComment] + ) + + const conv_items = info?.conversations.map((conv) => { + let icon + let children + + switch (conv.conv_type) { + case 'Comment': + // icon = + children = + break + case 'Closed': + // icon = + children = conv.comment + } + + const element = { + dot: icon, + children: children + } + + return element + }) + + const tab_items: TabsProps['items'] = [ + { + key: '1', + label: 'Conversation', + children: ( + + + {info && info.status === 'open' && ( + <> +

Add a comment

+ + + + + + + )} + {info && info.status === 'closed' && ( + + + + )} +
+ ) + } + ] + + return ( + + + + ) +} diff --git a/moon/apps/web/components/Issues/IssueNewPage.tsx b/moon/apps/web/components/Issues/IssueNewPage.tsx new file mode 100644 index 000000000..e2687b57f --- /dev/null +++ b/moon/apps/web/components/Issues/IssueNewPage.tsx @@ -0,0 +1,87 @@ +'use client' + +import { useCallback, useState } from 'react' +import { Button, Flex, Input, Space } from 'antd/lib' +import { useRouter } from 'next/router' +import toast from 'react-hot-toast' + +import RichEditor from '@/components/MrView/rich-editor/RichEditor' +import { usePostIssueSubmit } from '@/hooks/issues/usePostIssueSubmit' +import { apiErrorToast } from '@/utils/apiErrorToast' + +export default function IssueNewPage() { + const [editorState, setEditorState] = useState('') + const [title, setTitle] = useState('') + const [loadings, setLoadings] = useState([]) + const router = useRouter() + const { mutate: submitNewIssue } = usePostIssueSubmit() + + const set_to_loading = (index: number) => { + setLoadings((prevLoadings) => { + const newLoadings = [...prevLoadings] + + newLoadings[index] = true + return newLoadings + }) + } + + const cancel_loading = (index: number) => { + setLoadings((prevLoadings) => { + const newLoadings = [...prevLoadings] + + newLoadings[index] = false + return newLoadings + }) + } + + const submit = useCallback( + (description: string) => { + if (JSON.parse(description).root.children[0].children.length === 0 || !title) { + toast.error('please fill the issue list first!') + return + } + set_to_loading(3) + submitNewIssue( + { data: { title, description } }, + { + onSuccess: (_response) => { + setEditorState('') + cancel_loading(3) + toast.success('success') + router.push(`/${router.query.org}/issue`) + }, + onError: () => apiErrorToast + } + ) + }, + [router, title, submitNewIssue] + ) + + return ( + <> +
+ +

+ Add a title + setTitle(e.target.value)} + > +

+
+ +

Add a description

+ + + + +
+
+ + ) +} diff --git a/moon/apps/web/components/Issues/IssuePage.tsx b/moon/apps/web/components/Issues/IssuePage.tsx new file mode 100644 index 000000000..eb96871d4 --- /dev/null +++ b/moon/apps/web/components/Issues/IssuePage.tsx @@ -0,0 +1,151 @@ +'use client' + +import React, { useCallback, useEffect, useState } from 'react' +// import { CheckCircleOutlined, ExclamationCircleOutlined } from '@ant-design/icons' +import { Link } from '@gitmono/ui' +import { Button, Flex, List, PaginationProps, Tabs, TabsProps, Tag } from 'antd' +import { formatDistance, fromUnixTime } from 'date-fns' +import { useRouter } from 'next/router' + +import { Heading } from '@/components/Catalyst/Heading' +import { useGetIssueLists } from '@/hooks/issues/useGetIssueLists' +import { apiErrorToast } from '@/utils/apiErrorToast' + +interface Item { + closed_at?: number | null + link: string + owner: number + title: string + status: string + open_timestamp: number + updated_at: number +} + +export default function IssuePage() { + const router = useRouter() + const [itemList, setItemList] = useState([]) + const [numTotal, setNumTotal] = useState(0) + const [pageSize, _setPageSize] = useState(10) + const [loading, setLoading] = useState(false) + const [status, setStatus] = useState('open') + const { mutate: issueLists } = useGetIssueLists() + + const fetchData = useCallback( + (page: number, per_page: number) => { + setLoading(true) + + issueLists( + { + data: { pagination: { page, per_page }, additional: { status } } + }, + { + onSuccess: (response) => { + const data = response.data + + setItemList(data?.items ?? []) + setNumTotal(data?.total ?? 0) + }, + onError: apiErrorToast, + onSettled: () => setLoading(false) + } + ) + }, + + [status, issueLists] + ) + + useEffect(() => { + fetchData(1, pageSize) + }, [pageSize, fetchData]) + + const getStatusTag = (status: string) => { + switch (status) { + case 'open': + return open + case 'closed': + return closed + } + } + + // const getStatusIcon = (status: string) => { + // switch (status) { + // case 'open': + // // return + // case 'closed': + // // return + // } + // } + + const getDescription = (item: Item) => { + switch (item.status) { + case 'open': + return `Issue opened by Admin ${formatDistance(fromUnixTime(item.open_timestamp), new Date(), { addSuffix: true })} ` + case 'closed': + return `Issue ${item.link} closed by Admin ${formatDistance(fromUnixTime(item.updated_at), new Date(), { addSuffix: true })}` + } + } + + const onChange: PaginationProps['onChange'] = (current, pageSize) => { + fetchData(current, pageSize) + } + + const tabsChange = (activeKey: string) => { + if (activeKey === '1') { + setStatus('open') + } else { + setStatus('closed') + } + } + + const tab_items: TabsProps['items'] = [ + { + key: '1', + label: 'Open' + }, + { + key: '2', + label: 'Closed' + } + ] + + return ( + <> +
+ Issues + + + + + + + ( + + + // getStatusIcon(item.status) + // } + title={ + + {item.title} {getStatusTag(item.status)} + + } + description={getDescription(item)} + /> + + )} + /> +
+ + ) +} diff --git a/moon/apps/web/components/MrView/MRComment.tsx b/moon/apps/web/components/MrView/MRComment.tsx index 6761b47c0..9aece062b 100644 --- a/moon/apps/web/components/MrView/MRComment.tsx +++ b/moon/apps/web/components/MrView/MRComment.tsx @@ -1,67 +1,84 @@ -import { Card, Dropdown } from 'antd/lib'; -import type { MenuProps } from 'antd'; // import { MoreOutlined } from '@ant-design/icons'; -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]'; +import { NotePlusIcon } from '@gitmono/ui/Icons' +import type { MenuProps } from 'antd' +import { Card, Dropdown } from 'antd/lib' +import { formatDistance, fromUnixTime } from 'date-fns' + +import { useDeleteIssueComment } from '@/hooks/issues/useDeleteIssueComment' +import { useDeleteMrCommentDelete } from '@/hooks/useDeleteMrCommentDelete' +import { Conversation } from '@/pages/[org]/mr/[id]' + +import LexicalContent from './rich-editor/LexicalContent' interface CommentProps { - conv: Conversation + conv: Conversation id: string + whoamI: string } -const Comment = ({ conv, id }: CommentProps) => { - +const Comment = ({ conv, id, whoamI }: CommentProps) => { const { mutate: deleteComment } = useDeleteMrCommentDelete(id) + const { mutate: deleteIssueComment } = useDeleteIssueComment(id) const handleDelete = () => { - deleteComment(conv.id) + switch (whoamI) { + case 'issue': + deleteIssueComment(conv.id) + break + case 'mr': + deleteComment(conv.id) + break + default: + return + } } - const handleMenuClick: MenuProps['onClick'] = ({ key }) => { - if (key === '3') { - handleDelete() - } - }; - - const items: MenuProps['items'] = [ - { - label: 'Edit', - key: '1', - disabled: true - }, - { - label: 'Hide', - key: '2', - disabled: true - }, - { - type: 'divider', - }, - { - label: 'Delete', - key: '3', - danger: true, - } - ]; - const menuProps = { - items, - onClick: handleMenuClick, - }; + const handleMenuClick: MenuProps['onClick'] = ({ key }) => { + if (key === '3') { + handleDelete() + } + } - const time = formatDistance(fromUnixTime(conv.created_at), new Date(), { addSuffix: true }); + const items: MenuProps['items'] = [ + { + label: 'Edit', + key: '1', + disabled: true + }, + { + label: 'Hide', + key: '2', + disabled: true + }, + { + type: 'divider' + }, + { + label: 'Delete', + key: '3', + danger: true + } + ] + const menuProps = { + items, + onClick: handleMenuClick + } - return ( - - - - }> - - - ) + const time = formatDistance(fromUnixTime(conv.created_at), new Date(), { addSuffix: true }) + return ( + + + + } + > + + + ) } -export default Comment; \ No newline at end of file +export default Comment diff --git a/moon/apps/web/components/Sidebar/SidebarIssue.tsx b/moon/apps/web/components/Sidebar/SidebarIssue.tsx new file mode 100644 index 000000000..a7db85a04 --- /dev/null +++ b/moon/apps/web/components/Sidebar/SidebarIssue.tsx @@ -0,0 +1,22 @@ +import { ChatBubblePlusIcon } from '@gitmono/ui/Icons' +import router from 'next/router' + +import { useScope } from '@/contexts/scope' + +import { SidebarLink } from './SidebarLink' + +export function SidebarIssue() { + const { scope } = useScope() + + return ( + <> + } + /> + + ) +} diff --git a/moon/apps/web/components/Sidebar/SidebarOrgSwitcher.tsx b/moon/apps/web/components/Sidebar/SidebarOrgSwitcher.tsx index 9cec56853..0afcfddd1 100644 --- a/moon/apps/web/components/Sidebar/SidebarOrgSwitcher.tsx +++ b/moon/apps/web/components/Sidebar/SidebarOrgSwitcher.tsx @@ -1,13 +1,12 @@ import { useRef, useState } from 'react' -import { Reorder } from 'framer-motion' -import { useAtomValue } from 'jotai' -import { useRouter } from 'next/router' -import { isMacOs } from 'react-device-detect' - import { PublicOrganization } from '@gitmono/types' import { Avatar, LayeredHotkeys, Link, PlusIcon, Tooltip } from '@gitmono/ui' import { useIsDesktopApp } from '@gitmono/ui/src/hooks' import { cn } from '@gitmono/ui/src/utils' +import { Reorder } from 'framer-motion' +import { useAtomValue } from 'jotai' +import { useRouter } from 'next/router' +import { isMacOs } from 'react-device-detect' import { OrganizationSwitchHoverCard } from '@/components/InboxItems/OrganizationSwitchHoverCard' import { sidebarCollapsedAtom } from '@/components/Layout/AppLayout' @@ -48,28 +47,30 @@ export function SidebarOrgSwitcher() { onReorder={onReorder} className='flex flex-col gap-3' > - {memberships?.map(({ id, organization }, index) => ( - setDraggingId(id)} - onDragEnd={() => { - setDraggingId(undefined) - reorder.mutate(organizationMembershipIds) - }} - className={cn('group/reorder-item relative', { - 'opacity-60': draggingId === id, - 'pointer-events-none': !!draggingId - })} - > - - - ))} + {memberships + ?.filter((m) => m.organization !== null) + ?.map(({ id, organization }, index) => ( + setDraggingId(id)} + onDragEnd={() => { + setDraggingId(undefined) + reorder.mutate(organizationMembershipIds) + }} + className={cn('group/reorder-item relative', { + 'opacity-60': draggingId === id, + 'pointer-events-none': !!draggingId + })} + > + + + ))} + diff --git a/moon/apps/web/hooks/issues/useDeleteIssueComment.ts b/moon/apps/web/hooks/issues/useDeleteIssueComment.ts new file mode 100644 index 000000000..f328ce383 --- /dev/null +++ b/moon/apps/web/hooks/issues/useDeleteIssueComment.ts @@ -0,0 +1,18 @@ +import { DeleteApiIssueCommentDeleteData, RequestParams } from '@gitmono/types/generated' +import { useMutation, useQueryClient } from '@tanstack/react-query' + +import { legacyApiClient } from '@/utils/queryClient' + +export function useDeleteIssueComment(id: string, params?: RequestParams) { + const queryClient = useQueryClient() + + return useMutation({ + mutationKey: legacyApiClient.v1.deleteApiIssueCommentDelete().baseKey, + mutationFn: (convId) => legacyApiClient.v1.deleteApiIssueCommentDelete().request(convId, params), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: legacyApiClient.v1.getApiIssueDetail().requestKey(id) + }) + } + }) +} diff --git a/moon/apps/web/hooks/issues/useGetIssueDetail.ts b/moon/apps/web/hooks/issues/useGetIssueDetail.ts new file mode 100644 index 000000000..4b06108e0 --- /dev/null +++ b/moon/apps/web/hooks/issues/useGetIssueDetail.ts @@ -0,0 +1,59 @@ +import { useQuery } from '@tanstack/react-query' + +import { legacyApiClient } from '@/utils/queryClient' + +interface conversations { + id: number + user_id: number + conv_type: string + comment: string + created_at: number + updated_at: number +} + +interface raw { + id: number + link: string + title: string + status: string + open_timestamp: number + conversations: conversations[] +} + +interface issueDetail { + status: string + conversations: { id: number; user_id: number; conv_type: string; comment: string; created_at: number }[] + title: string +} + +interface detailRes { + err_message: string + data: issueDetail + req_result: boolean +} +const getApiIssueDetail = legacyApiClient.v1.getApiIssueDetail() + +export function useGetIssueDetail(id: string) { + return useQuery({ + queryKey: ['issueDetail', id], + queryFn: async () => { + const { err_message, data, req_result } = await getApiIssueDetail.request(id) + + if (!req_result) throw new Error(err_message || 'fetching failed') + const rawData = data as unknown as raw + const converted: issueDetail = { + title: rawData.title, + conversations: rawData.conversations, + status: rawData.status + } + + return { + err_message, + data: converted, + req_result + } + }, + + enabled: !!id + }) +} diff --git a/moon/apps/web/hooks/issues/useGetIssueLists.ts b/moon/apps/web/hooks/issues/useGetIssueLists.ts new file mode 100644 index 000000000..dfe3e2f9b --- /dev/null +++ b/moon/apps/web/hooks/issues/useGetIssueLists.ts @@ -0,0 +1,10 @@ +import { PageParamsStatusParams, PostApiIssueListData, RequestParams } from '@gitmono/types/generated' +import { useMutation } from '@tanstack/react-query' + +import { legacyApiClient } from '@/utils/queryClient' + +export function useGetIssueLists() { + return useMutation({ + mutationFn: ({ data, params }) => legacyApiClient.v1.postApiIssueList().request(data, params) + }) +} diff --git a/moon/apps/web/hooks/issues/usePostIssueClose.ts b/moon/apps/web/hooks/issues/usePostIssueClose.ts new file mode 100644 index 000000000..4f57d83a6 --- /dev/null +++ b/moon/apps/web/hooks/issues/usePostIssueClose.ts @@ -0,0 +1,10 @@ +import { PostApiIssueCloseData, RequestParams } from '@gitmono/types/generated' +import { useMutation } from '@tanstack/react-query' + +import { legacyApiClient } from '@/utils/queryClient' + +export function usePostIssueClose() { + return useMutation({ + mutationFn: ({ link, params }) => legacyApiClient.v1.postApiIssueClose().request(link, params) + }) +} diff --git a/moon/apps/web/hooks/issues/usePostIssueComment.ts b/moon/apps/web/hooks/issues/usePostIssueComment.ts new file mode 100644 index 000000000..eb5d43090 --- /dev/null +++ b/moon/apps/web/hooks/issues/usePostIssueComment.ts @@ -0,0 +1,14 @@ +import { PostApiIssueCommentData, RequestParams, SaveCommentRequest } from '@gitmono/types/generated' +import { useMutation } from '@tanstack/react-query' + +import { legacyApiClient } from '@/utils/queryClient' + +export function usePostIssueComment() { + return useMutation< + PostApiIssueCommentData, + Error, + { link: string; data: SaveCommentRequest; params?: RequestParams } + >({ + mutationFn: ({ link, data, params }) => legacyApiClient.v1.postApiIssueComment().request(link, data, params) + }) +} diff --git a/moon/apps/web/hooks/issues/usePostIssueReopen.ts b/moon/apps/web/hooks/issues/usePostIssueReopen.ts new file mode 100644 index 000000000..0cbc9f95a --- /dev/null +++ b/moon/apps/web/hooks/issues/usePostIssueReopen.ts @@ -0,0 +1,10 @@ +import { PostApiIssueReopenData, RequestParams } from '@gitmono/types/generated' +import { useMutation } from '@tanstack/react-query' + +import { legacyApiClient } from '@/utils/queryClient' + +export function usePostIssueReopen() { + return useMutation({ + mutationFn: ({ link, params }) => legacyApiClient.v1.postApiIssueReopen().request(link, params) + }) +} diff --git a/moon/apps/web/hooks/issues/usePostIssueSubmit.ts b/moon/apps/web/hooks/issues/usePostIssueSubmit.ts new file mode 100644 index 000000000..2f0af6c02 --- /dev/null +++ b/moon/apps/web/hooks/issues/usePostIssueSubmit.ts @@ -0,0 +1,10 @@ +import { NewIssue, PostApiIssueNewData } from '@gitmono/types/generated' +import { useMutation } from '@tanstack/react-query' + +import { legacyApiClient } from '@/utils/queryClient' + +export function usePostIssueSubmit() { + return useMutation({ + mutationFn: ({ data }) => legacyApiClient.v1.postApiIssueNew().request(data) + }) +} diff --git a/moon/apps/web/next.config.js b/moon/apps/web/next.config.js index 11395c52f..9be480de0 100644 --- a/moon/apps/web/next.config.js +++ b/moon/apps/web/next.config.js @@ -101,6 +101,7 @@ const ContentSecurityPolicy = Object.keys(cspResourcesByDirective).reduce((prevP /** @type {import('next').NextConfig} */ const moduleExports = { + output: 'standalone', experimental: { // https://nextjs.org/docs/messages/import-esm-externals esmExternals: 'loose', diff --git a/moon/apps/web/pages/[org]/issue/[id]/index.tsx b/moon/apps/web/pages/[org]/issue/[id]/index.tsx new file mode 100644 index 000000000..50152a831 --- /dev/null +++ b/moon/apps/web/pages/[org]/issue/[id]/index.tsx @@ -0,0 +1,40 @@ +import { GetServerSideProps } from 'next' + +import IssueDetailPage from '@/components/Issues/IssueDetailPage' +import { AppLayout } from '@/components/Layout/AppLayout' +import { AuthAppProviders } from '@/components/Providers/AuthAppProviders' +import { PageWithLayout } from '@/utils/types' + +export const getServerSideProps: GetServerSideProps = async ({ query }) => { + if (!query.id) { + return { + redirect: { + destination: `/${query.org}/issue`, + permanent: false + } + } + } + return { + props: { + id: query.id + } + } +} + +const OrganizationIssueDetailPage: PageWithLayout = ({ id }) => { + return ( + <> + + + ) +} + +OrganizationIssueDetailPage.getProviders = (page, pageProps) => { + return ( + + {page} + + ) +} + +export default OrganizationIssueDetailPage diff --git a/moon/apps/web/pages/[org]/issue/index.tsx b/moon/apps/web/pages/[org]/issue/index.tsx new file mode 100644 index 000000000..5b93ac2e1 --- /dev/null +++ b/moon/apps/web/pages/[org]/issue/index.tsx @@ -0,0 +1,22 @@ +import IssuePage from '@/components/Issues/IssuePage' +import { AppLayout } from '@/components/Layout/AppLayout' +import { AuthAppProviders } from '@/components/Providers/AuthAppProviders' +import { PageWithLayout } from '@/utils/types' + +const OrganizationIssuePage: PageWithLayout = () => { + return ( + <> + + + ) +} + +OrganizationIssuePage.getProviders = (page, pageProps) => { + return ( + + {page} + + ) +} + +export default OrganizationIssuePage diff --git a/moon/apps/web/pages/[org]/issue/new/index.tsx b/moon/apps/web/pages/[org]/issue/new/index.tsx new file mode 100644 index 000000000..6b4c53305 --- /dev/null +++ b/moon/apps/web/pages/[org]/issue/new/index.tsx @@ -0,0 +1,22 @@ +import IssueNewPage from '@/components/Issues/IssueNewPage' +import { AppLayout } from '@/components/Layout/AppLayout' +import { AuthAppProviders } from '@/components/Providers/AuthAppProviders' +import { PageWithLayout } from '@/utils/types' + +const OrganizationIssueNewPage: PageWithLayout = () => { + return ( + <> + + + ) +} + +OrganizationIssueNewPage.getProviders = (page, pageProps) => { + return ( + + {page} + + ) +} + +export default OrganizationIssueNewPage diff --git a/moon/apps/web/pages/[org]/mr/[id].tsx b/moon/apps/web/pages/[org]/mr/[id].tsx index 2059aa385..5bdfbf703 100644 --- a/moon/apps/web/pages/[org]/mr/[id].tsx +++ b/moon/apps/web/pages/[org]/mr/[id].tsx @@ -111,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;