From ee4783eada17a24130864a078df171686d8e82aa Mon Sep 17 00:00:00 2001 From: "benjamin.747" Date: Tue, 16 Jul 2024 16:44:04 +0800 Subject: [PATCH 1/2] refactor ui with template, using next.js app_router --- moon/.env.development | 0 moon/.env.production | 1 + moon/.gitignore | 1 - moon/package.json | 3 + moon/src/app/api/blob/route.ts | 16 ++ moon/src/app/api/mr/[id]/detail/route.ts | 12 + moon/src/app/api/mr/[id]/files/route.ts | 11 + moon/src/app/api/mr/[id]/merge/route.ts | 13 ++ moon/src/app/api/mr/list/route.ts | 16 ++ moon/src/app/api/tree/commit-info/route.ts | 17 ++ moon/src/app/api/tree/route.ts | 17 ++ moon/src/app/application-layout.tsx | 182 +++++++++++++++ moon/src/app/blob/[...path]/page.tsx | 34 +++ moon/src/app/layout.tsx | 33 +++ moon/src/app/mr/[id]/page.tsx | 27 +++ moon/src/app/mr/page.tsx | 25 ++ moon/src/app/page.tsx | 32 +++ moon/src/app/tree/[...path]/page.tsx | 53 +++++ moon/src/components/BreadCrumb.tsx | 12 +- moon/src/components/CodeTable.tsx | 31 ++- moon/src/components/MergeDetail.tsx | 24 +- moon/src/components/RepoTree.tsx | 7 +- moon/src/components/TopNavbar.tsx | 216 ------------------ moon/src/components/catalyst/avatar.tsx | 84 +++++++ moon/src/components/catalyst/button.tsx | 204 +++++++++++++++++ moon/src/components/catalyst/dropdown.tsx | 192 ++++++++++++++++ moon/src/components/catalyst/link.tsx | 14 ++ moon/src/components/catalyst/navbar.tsx | 96 ++++++++ .../components/catalyst/sidebar-layout.tsx | 82 +++++++ moon/src/components/catalyst/sidebar.tsx | 138 +++++++++++ moon/src/components/layout.tsx | 14 -- moon/src/pages/404.tsx | 13 -- moon/src/pages/500.tsx | 13 -- moon/src/pages/_app.tsx | 11 - moon/src/pages/api/blob.ts | 16 -- moon/src/pages/api/mr.ts | 15 -- moon/src/pages/api/mr/[id].ts | 16 -- moon/src/pages/api/mr/files.ts | 16 -- moon/src/pages/api/mr/merge.ts | 16 -- moon/src/pages/api/tree-commit.ts | 22 -- moon/src/pages/api/tree.ts | 24 -- moon/src/pages/blob/[...path].tsx | 25 -- moon/src/pages/index.tsx | 34 --- moon/src/pages/mr/[...id].tsx | 22 -- moon/src/pages/mr/index.tsx | 22 -- moon/src/pages/tree/[...path].tsx | 37 --- moon/tsconfig.json | 40 ++++ 47 files changed, 1378 insertions(+), 571 deletions(-) create mode 100644 moon/.env.development create mode 100644 moon/.env.production create mode 100644 moon/src/app/api/blob/route.ts create mode 100644 moon/src/app/api/mr/[id]/detail/route.ts create mode 100644 moon/src/app/api/mr/[id]/files/route.ts create mode 100644 moon/src/app/api/mr/[id]/merge/route.ts create mode 100644 moon/src/app/api/mr/list/route.ts create mode 100644 moon/src/app/api/tree/commit-info/route.ts create mode 100644 moon/src/app/api/tree/route.ts create mode 100644 moon/src/app/application-layout.tsx create mode 100644 moon/src/app/blob/[...path]/page.tsx create mode 100644 moon/src/app/layout.tsx create mode 100644 moon/src/app/mr/[id]/page.tsx create mode 100644 moon/src/app/mr/page.tsx create mode 100644 moon/src/app/page.tsx create mode 100644 moon/src/app/tree/[...path]/page.tsx delete mode 100644 moon/src/components/TopNavbar.tsx create mode 100644 moon/src/components/catalyst/avatar.tsx create mode 100644 moon/src/components/catalyst/button.tsx create mode 100644 moon/src/components/catalyst/dropdown.tsx create mode 100644 moon/src/components/catalyst/link.tsx create mode 100644 moon/src/components/catalyst/navbar.tsx create mode 100644 moon/src/components/catalyst/sidebar-layout.tsx create mode 100644 moon/src/components/catalyst/sidebar.tsx delete mode 100644 moon/src/components/layout.tsx delete mode 100644 moon/src/pages/404.tsx delete mode 100644 moon/src/pages/500.tsx delete mode 100644 moon/src/pages/_app.tsx delete mode 100644 moon/src/pages/api/blob.ts delete mode 100644 moon/src/pages/api/mr.ts delete mode 100644 moon/src/pages/api/mr/[id].ts delete mode 100644 moon/src/pages/api/mr/files.ts delete mode 100644 moon/src/pages/api/mr/merge.ts delete mode 100644 moon/src/pages/api/tree-commit.ts delete mode 100644 moon/src/pages/api/tree.ts delete mode 100644 moon/src/pages/blob/[...path].tsx delete mode 100644 moon/src/pages/index.tsx delete mode 100644 moon/src/pages/mr/[...id].tsx delete mode 100644 moon/src/pages/mr/index.tsx delete mode 100644 moon/src/pages/tree/[...path].tsx create mode 100644 moon/tsconfig.json diff --git a/moon/.env.development b/moon/.env.development new file mode 100644 index 000000000..e69de29bb diff --git a/moon/.env.production b/moon/.env.production new file mode 100644 index 000000000..1fee243d8 --- /dev/null +++ b/moon/.env.production @@ -0,0 +1 @@ +NEXT_MEGA_API_URL=http://mega.api.com diff --git a/moon/.gitignore b/moon/.gitignore index e02444ac3..435b013b1 100644 --- a/moon/.gitignore +++ b/moon/.gitignore @@ -35,6 +35,5 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts -tsconfig.json package-lock.json pnpm-lock.yaml \ No newline at end of file diff --git a/moon/package.json b/moon/package.json index b4993af12..6f525f599 100644 --- a/moon/package.json +++ b/moon/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@ant-design/icons": "^5.3.7", + "@ant-design/nextjs-registry": "^1.0.0", "@headlessui/react": "^2.1.0", "@headlessui/tailwindcss": "^0.2.1", "@heroicons/react": "^2.1.4", @@ -17,7 +18,9 @@ "@tailwindcss/forms": "^0.5.7", "antd": "^5.18.3", "axios": "^1.7.2", + "clsx": "^2.1.1", "date-fns": "^3.6.0", + "framer-motion": "^11.3.2", "github-markdown-css": "^5.6.1", "highlight.js": "^11.9.0", "lexical": "^0.16.0", diff --git a/moon/src/app/api/blob/route.ts b/moon/src/app/api/blob/route.ts new file mode 100644 index 000000000..e25cc0fe0 --- /dev/null +++ b/moon/src/app/api/blob/route.ts @@ -0,0 +1,16 @@ +import { type NextRequest } from 'next/server' + +export const dynamic = 'force-dynamic' // defaults to auto + +const endpoint = process.env.NEXT_MEGA_API_URL; + +export async function GET(request: NextRequest) { + const searchParams = request.nextUrl.searchParams + const path = searchParams.get('path') + + const res = await fetch(`${endpoint}/api/v1/blob?path=${path}`, { + }) + const data = await res.json() + + return Response.json({ data }) +} \ No newline at end of file diff --git a/moon/src/app/api/mr/[id]/detail/route.ts b/moon/src/app/api/mr/[id]/detail/route.ts new file mode 100644 index 000000000..c8ebdbd82 --- /dev/null +++ b/moon/src/app/api/mr/[id]/detail/route.ts @@ -0,0 +1,12 @@ +export const dynamic = 'force-dynamic' // defaults to auto +export const revalidate = 0 + +const endpoint = process.env.NEXT_MEGA_API_URL; + +export async function GET(request: Request, { params }: { params: { id: string } }) { + const res = await fetch(`${endpoint}/api/v1/mr/${params.id}/detail`, { + }) + const data = await res.json() + + return Response.json({ data }) +} \ No newline at end of file diff --git a/moon/src/app/api/mr/[id]/files/route.ts b/moon/src/app/api/mr/[id]/files/route.ts new file mode 100644 index 000000000..119e65ad2 --- /dev/null +++ b/moon/src/app/api/mr/[id]/files/route.ts @@ -0,0 +1,11 @@ +export const dynamic = 'force-dynamic' // defaults to auto + +const endpoint = process.env.NEXT_MEGA_API_URL; + +export async function GET(request: Request, { params }: { params: { id: string } }) { + const res = await fetch(`${endpoint}/api/v1/mr/${params.id}/files`, { + }) + const data = await res.json() + + return Response.json({ data }) +} \ No newline at end of file diff --git a/moon/src/app/api/mr/[id]/merge/route.ts b/moon/src/app/api/mr/[id]/merge/route.ts new file mode 100644 index 000000000..867021594 --- /dev/null +++ b/moon/src/app/api/mr/[id]/merge/route.ts @@ -0,0 +1,13 @@ +export const dynamic = 'force-dynamic' // defaults to auto + +const endpoint = process.env.NEXT_MEGA_API_URL; + + +export async function POST(request: Request, { params }: { params: { id: string } }) { + const res = await fetch(`${endpoint}/api/v1/mr/${params.id}/merge`, { + method: 'POST', + }) + const data = await res.json() + + return Response.json({ data }) +} \ No newline at end of file diff --git a/moon/src/app/api/mr/list/route.ts b/moon/src/app/api/mr/list/route.ts new file mode 100644 index 000000000..1501237d4 --- /dev/null +++ b/moon/src/app/api/mr/list/route.ts @@ -0,0 +1,16 @@ +export const dynamic = 'force-dynamic' // defaults to auto +export const revalidate = 0 + +import { type NextRequest } from 'next/server' + +const endpoint = process.env.NEXT_MEGA_API_URL; + +export async function GET(request: NextRequest) { + const searchParams = request.nextUrl.searchParams + const status = searchParams.get('status') + const res = await fetch(`${endpoint}/api/v1/mr/list?status=${status}`, { + }) + const data = await res.json() + + return Response.json({ data }) +} \ No newline at end of file diff --git a/moon/src/app/api/tree/commit-info/route.ts b/moon/src/app/api/tree/commit-info/route.ts new file mode 100644 index 000000000..1c794dfcb --- /dev/null +++ b/moon/src/app/api/tree/commit-info/route.ts @@ -0,0 +1,17 @@ +import { type NextRequest } from 'next/server' + +export const dynamic = 'force-dynamic' // defaults to auto +export const revalidate = 0 + +const endpoint = process.env.NEXT_MEGA_API_URL; + +export async function GET(request: NextRequest) { + const searchParams = request.nextUrl.searchParams + const path = searchParams.get('path') + + const res = await fetch(`${endpoint}/api/v1/tree/commit-info?path=${path}`, { + }) + const data = await res.json() + + return Response.json({ data }) +} \ No newline at end of file diff --git a/moon/src/app/api/tree/route.ts b/moon/src/app/api/tree/route.ts new file mode 100644 index 000000000..1ffb190d1 --- /dev/null +++ b/moon/src/app/api/tree/route.ts @@ -0,0 +1,17 @@ +import { type NextRequest } from 'next/server' +export const revalidate = 0 + +export const dynamic = 'force-dynamic' // defaults to auto + +const endpoint = process.env.NEXT_MEGA_API_URL; + +export async function GET(request: NextRequest) { + const searchParams = request.nextUrl.searchParams + const path = searchParams.get('path') + + const res = await fetch(`${endpoint}/api/v1/tree?path=${path}`, { + }) + const data = await res.json() + + return Response.json({ data }) +} \ No newline at end of file diff --git a/moon/src/app/application-layout.tsx b/moon/src/app/application-layout.tsx new file mode 100644 index 000000000..12458c396 --- /dev/null +++ b/moon/src/app/application-layout.tsx @@ -0,0 +1,182 @@ +'use client' + +import { Avatar } from '@/components/catalyst/avatar' +import { + Dropdown, + DropdownButton, + DropdownDivider, + DropdownItem, + DropdownLabel, + DropdownMenu, +} from '@/components/catalyst/dropdown' +import { Navbar, NavbarItem, NavbarSection, NavbarSpacer } from '@/components/catalyst/navbar' +import { + Sidebar, + SidebarBody, + SidebarFooter, + SidebarHeader, + SidebarHeading, + SidebarItem, + SidebarLabel, + SidebarSection, + SidebarSpacer, +} from '@/components/catalyst/sidebar' +import { SidebarLayout } from '@/components/catalyst/sidebar-layout' +import { + ArrowRightStartOnRectangleIcon, + ChevronDownIcon, + ChevronUpIcon, + Cog8ToothIcon, + LightBulbIcon, + PlusIcon, + ShieldCheckIcon, + UserCircleIcon, +} from '@heroicons/react/16/solid' +import { + Cog6ToothIcon, + HomeIcon, + QuestionMarkCircleIcon, + SparklesIcon, + Square2StackIcon, + TicketIcon, +} from '@heroicons/react/20/solid' +import { usePathname } from 'next/navigation' + +function AccountDropdownMenu({ anchor }: { anchor: 'top start' | 'bottom end' }) { + return ( + + + + My account + + + + + Privacy policy + + + + Share feedback + + + + + Sign out + + + ) +} + +export function ApplicationLayout({ + // events, + children, +}: { + // events: Awaited> + children: React.ReactNode +}) { + let pathname = usePathname() + + return ( + + + + + + + + + + + + } + sidebar={ + + + + + + Mega + + + + + + Settings + + + + + Admin + + + + Big Events + + + + + New team… + + + + + + + + + + Home + + + + Issue + + + + Merge Request + + + + Settings + + + + + + + + Support + + + + Changelog + + + + + + + + + + + Admin + + Admin@mega.com + + + + + + + + + + } + > + {children} + + ) +} diff --git a/moon/src/app/blob/[...path]/page.tsx b/moon/src/app/blob/[...path]/page.tsx new file mode 100644 index 000000000..52197aa22 --- /dev/null +++ b/moon/src/app/blob/[...path]/page.tsx @@ -0,0 +1,34 @@ +'use client' +import CodeContent from '@/components/CodeContent'; +import Bread from '@/components/BreadCrumb'; +import { useEffect, useState } from 'react'; + +export default function BlobPage({ params }: { params: { path: string[] } }) { + let path = '/' + params.path.join('/'); + const [readmeContent, setReadmeContent] = useState(""); + useEffect(() => { + const fetchData = async () => { + try { + let readmeContent = await getReadmeContent(path); + setReadmeContent(readmeContent); + } catch (error) { + console.error('Error fetching data:', error); + } + }; + fetchData(); + }, [path]); + + return ( +
+ + +
+ ) +} + +export async function getReadmeContent(pathname: string) { + const res = await fetch(`http://localhost:3000/api/blob/?path=/${pathname}`); + const response = await res.json(); + const directory = response.data.data; + return directory +} diff --git a/moon/src/app/layout.tsx b/moon/src/app/layout.tsx new file mode 100644 index 000000000..4f9fb41d7 --- /dev/null +++ b/moon/src/app/layout.tsx @@ -0,0 +1,33 @@ +import '@/styles/globals.css' +import type { Metadata } from 'next' +import type React from 'react' +import { ApplicationLayout } from './application-layout' +import { AntdRegistry } from '@ant-design/nextjs-registry'; + +export const metadata: Metadata = { + title: { + template: '%s - Mega', + default: 'Mega', + }, + description: '', +} + +export default async function Layout({ children }: { children: React.ReactNode }) { + + return ( + + + + + + + + {children} + + + + ) +} diff --git a/moon/src/app/mr/[id]/page.tsx b/moon/src/app/mr/[id]/page.tsx new file mode 100644 index 000000000..ab8e3add4 --- /dev/null +++ b/moon/src/app/mr/[id]/page.tsx @@ -0,0 +1,27 @@ +'use client' +import MergeDetail from "@/components/MergeDetail"; +import { useEffect, useState } from "react"; + + +export default function MRDetailPage( { params }: { params: { id: string } }) { + const [mrDetail, setMrDetail] = useState([]); + useEffect(() => { + const fetchData = async () => { + try { + const res = await fetch(`http://localhost:3000/api/mr/${params.id}/detail`); + const response = await res.json(); + const data = response.data.data; + setMrDetail(data); + } catch (error) { + console.error('Error fetching data:', error); + } + }; + fetchData(); + }, []); + + return ( +
+ +
+ ) +} diff --git a/moon/src/app/mr/page.tsx b/moon/src/app/mr/page.tsx new file mode 100644 index 000000000..1d3d87182 --- /dev/null +++ b/moon/src/app/mr/page.tsx @@ -0,0 +1,25 @@ +'use client' +import MergeList from "@/components/MergeList"; +import { useEffect, useState } from 'react'; + +export default function MergeRequestPage() { + const [mrList, setMrList] = useState([]); + useEffect(() => { + const fetchData = async () => { + try { + const res = await fetch(`http://localhost:3000/api/mr/list?status=`); + const response = await res.json(); + const mrList = response.data.data; + setMrList(mrList); + } catch (error) { + console.error('Error fetching data:', error); + } + }; + fetchData(); + }, []); + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/moon/src/app/page.tsx b/moon/src/app/page.tsx new file mode 100644 index 000000000..8ed17d338 --- /dev/null +++ b/moon/src/app/page.tsx @@ -0,0 +1,32 @@ +import CodeTable from '@/components/CodeTable' +export const revalidate = 0 + +export default async function HomePage() { + let directory = await getDirectory() + let readmeContent = await getReadmeContent(directory) + + return ( +
+ +
+ ); +} +export async function getDirectory() { + const res = await fetch(`http://localhost:3000/api/tree/commit-info?path=/`); + const response = await res.json(); + const directory = response.data.data; + return directory +} + +export async function getReadmeContent(directory) { + var readmeContent = ''; + for (const project of directory || []) { + if (project.name === 'README.md' && project.content_type === 'file') { + const res = await fetch(`http://localhost:3000/api/blob?path=/README.md`); + const response = await res.json(); + readmeContent = response.data.data; + break; + } + } + return readmeContent +} diff --git a/moon/src/app/tree/[...path]/page.tsx b/moon/src/app/tree/[...path]/page.tsx new file mode 100644 index 000000000..52cd9e854 --- /dev/null +++ b/moon/src/app/tree/[...path]/page.tsx @@ -0,0 +1,53 @@ +'use client' + +import CodeTable from '@/components/CodeTable' +import Bread from '@/components/BreadCrumb' +import RepoTree from '@/components/RepoTree' +import { useEffect, useState } from 'react' + +export default function Page({ params }: { params: { path: string[] } }) { + const [directory, setDirectory] = useState([]); + const [readmeContent, setReadmeContent] = useState(""); + let path = '/' + params.path.join('/'); + useEffect(() => { + const fetchData = async () => { + try { + let directory = await getDirectory(path); + setDirectory(directory); + let readmeContent = await getReadmeContent(path, directory); + setReadmeContent(readmeContent); + } catch (error) { + console.error('Error fetching data:', error); + } + }; + fetchData(); + }, [path]); + + return ( +
+ + + +
+ ); +} + +export async function getDirectory(pathname: string) { + const res = await fetch(`http://localhost:3000/api/tree/commit-info?path=${pathname}`); + const response = await res.json(); + const directory = response.data.data; + return directory +} + +export async function getReadmeContent(pathname, directory) { + var readmeContent = ''; + for (const project of directory || []) { + if (project.name === 'README.md' && project.content_type === 'file') { + const res = await fetch(`http://localhost:3000/api/blob?path=${pathname}/README.md`); + const response = await res.json(); + readmeContent = response.data.data; + break; + } + } + return readmeContent +} diff --git a/moon/src/components/BreadCrumb.tsx b/moon/src/components/BreadCrumb.tsx index 7ffca916d..cbf5086ec 100644 --- a/moon/src/components/BreadCrumb.tsx +++ b/moon/src/components/BreadCrumb.tsx @@ -1,19 +1,15 @@ - import 'github-markdown-css/github-markdown-light.css' -import { useRouter } from 'next/router' +import { useRouter } from 'next/navigation' import { Breadcrumb } from 'antd/lib' import styles from './BreadCrumb.module.css' -const Bread = () => { +const Bread = ({ path }) => { const router = useRouter(); - const { path } = router.query; - const safePath = Array.isArray(path) ? path : []; - const handleBreadcrumbClick = async (index) => { - router.push(`/tree/${safePath.slice(0, index + 1).join('/')}`); + router.push(`/tree/${path.slice(0, index + 1).join('/')}`); }; - const breadCrumbItems = safePath.map((path, index) => ({ + const breadCrumbItems = path.map((path, index) => ({ title: path, onClick: () => handleBreadcrumbClick(index), })); diff --git a/moon/src/components/CodeTable.tsx b/moon/src/components/CodeTable.tsx index 1916f6dab..c16a1d0bb 100644 --- a/moon/src/components/CodeTable.tsx +++ b/moon/src/components/CodeTable.tsx @@ -1,6 +1,7 @@ +'use client' import 'github-markdown-css/github-markdown-light.css' -import { useRouter } from 'next/router' +import { useRouter, usePathname } from 'next/navigation' import Markdown from 'react-markdown' import { formatDistance, fromUnixTime } from 'date-fns' import folderPic from '../../public/icons/folder.svg' @@ -18,13 +19,14 @@ export interface DataType { date: number; } -const CodeTable = ({ directory, readmeContent, showTree }) => { - +const CodeTable = ({ directory, readmeContent, treeIsShow }) => { const router = useRouter(); - const fileCodeContainerStyle = showTree ? { width: '80%', marginLeft: '17%', borderRadius: '0.5rem', marginTop: '10px' } : { width: '90%', margin: '0 auto', borderRadius: '0.5rem', marginTop: '10px' }; + const fileCodeContainerStyle = treeIsShow ? { width: '80%', marginLeft: '17%', borderRadius: '0.5rem', marginTop: '10px' } : { width: '90%', margin: '0 auto', borderRadius: '0.5rem', marginTop: '10px' }; const [open, setOpen] = useState(false); const [confirmLoading, setConfirmLoading] = useState(false); const [modalText, setModalText] = useState(''); + const pathname = usePathname(); + let real_path = pathname.replace("/tree", ""); var columns: TableProps['columns'] = [ { @@ -76,29 +78,24 @@ const CodeTable = ({ directory, readmeContent, showTree }) => { ]; const handleFileClick = (file) => { - const { path } = router.query; - const safePath = Array.isArray(path) ? path : []; - - const newPath = `/blob/${safePath.join("/")}/${file.name}`; + const newPath = `/blob/${real_path}/${file.name}`; router.push(newPath); }; const handleDirectoryClick = async (directory) => { - const { path } = router.query; var newPath = ''; - if (Array.isArray(path)) { - newPath = `/tree/${path.join("/")}/${directory.name}`; - } else { + if (real_path === '/') { newPath = `/tree/${directory.name}`; + } else { + newPath = `/tree/${real_path}/${directory.name}`; } - router.push({ - pathname: newPath, - }); + router.push( + newPath, + ); }; const handleGoBack = () => { - const { path } = router.query; - const safePath = Array.isArray(path) ? path : []; + const safePath = real_path.split('/'); if (safePath.length == 1) { router.push('/') diff --git a/moon/src/components/MergeDetail.tsx b/moon/src/components/MergeDetail.tsx index 195c620fd..03e7e8718 100644 --- a/moon/src/components/MergeDetail.tsx +++ b/moon/src/components/MergeDetail.tsx @@ -1,6 +1,7 @@ +'use client' import React, { useEffect, useState } from 'react'; import { Card, Button, List, Typography } from 'antd/lib'; -import { useRouter } from 'next/router'; +import { useRouter } from 'next/navigation'; const MRDetailPage = ({ mrDetail }) => { const router = useRouter(); @@ -12,16 +13,18 @@ const MRDetailPage = ({ mrDetail }) => { const fetchFileList = async () => { set_to_loading(2) try { - const res = await fetch(`/api/mr/files?id=${mrDetail.id}`); + const res = await fetch(`/api/mr/${mrDetail.id}/files`); const result = await res.json(); - - setFileData(result.data); + setFileData(result.data.data); } catch (err) { setError(err); } finally { cancel_loading(2) } }; + if (!mrDetail || !mrDetail.id) { + return; + } fetchFileList(); }, [mrDetail]); @@ -42,25 +45,28 @@ const MRDetailPage = ({ mrDetail }) => { }); } - const enterLoading = async (index: number, id: number) => { + const approve_mr = async (index: number, id: number) => { set_to_loading(index); - const res = await fetch(`/api/mr/merge?id=${id}`); + const res = await fetch(`/api/mr/${id}/merge`,{ + method: 'POST', + }); if (res) { cancel_loading(index); } + if (res.ok) { - router.reload(); + router.refresh(); } }; return ( - + {mrDetail.status === "open" && diff --git a/moon/src/components/RepoTree.tsx b/moon/src/components/RepoTree.tsx index 0dc89e15a..d95d5d701 100644 --- a/moon/src/components/RepoTree.tsx +++ b/moon/src/components/RepoTree.tsx @@ -1,11 +1,11 @@ import 'github-markdown-css/github-markdown-light.css' import { DownOutlined } from '@ant-design/icons/lib' import { useState, useEffect } from 'react' -import { useRouter } from 'next/router' +import { useRouter } from 'next/navigation' import { Tree } from 'antd/lib' import styles from './RepoTree.module.css' -const Bread = ({ directory }) => { +const RepoTree = ({ directory }) => { const router = useRouter(); const [treeData, setTreeData] = useState(); const [updateTree, setUpdateTree] = useState(false); @@ -26,7 +26,6 @@ const Bread = ({ directory }) => { // convert the dir to tree data const convertToTreeData = (responseData) => { - // console.log("!!!!!!!!!!!!in convert"); return sortProjectsByType(responseData).map(item => { const treeItem = { title: item.name, @@ -124,4 +123,4 @@ const Bread = ({ directory }) => { ); }; -export default Bread; +export default RepoTree; diff --git a/moon/src/components/TopNavbar.tsx b/moon/src/components/TopNavbar.tsx deleted file mode 100644 index 082a5fdce..000000000 --- a/moon/src/components/TopNavbar.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import { Disclosure, DisclosureButton, DisclosurePanel, Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react' -import { MagnifyingGlassIcon } from '@heroicons/react/20/solid' -import { Bars3Icon, BellIcon, XMarkIcon } from '@heroicons/react/24/outline' -import Link from 'next/link'; -import Image from 'next/image'; -import title from '../../public/images/megaTitle.png' -import avatar from '../../public/icons/headImage.svg' - -function classNames(...classes) { - return classes.filter(Boolean).join(' ') -} - -export default function TopNavBar() { - return ( - - {({ open }) => ( - <> -
-
-
-
- - Mega Title -
-
- {/* Current: "border-indigo-500 text-gray-900", Default: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" */} - - Code - - - Issues - - - Merge Requests - -
-
-
-
- -
-
-
- -
-
-
-
- {/* Mobile menu button */} - - - Open main menu - {open ? ( - -
-
- - - {/* Profile dropdown */} - -
- - - Open user menu - - -
- - - {({ focus }) => ( - - Your Profile - - )} - - - {({ focus }) => ( - - Settings - - )} - - - {({ focus }) => ( - - Sign out - - )} - - -
-
-
-
- - -
- {/* Current: "bg-indigo-50 border-indigo-500 text-indigo-700", Default: "border-transparent text-gray-600 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-800" */} - - Dashboard - - - Team - - - Projects - - - Calendar - -
-
-
-
- -
-
-
Tom Cook
-
tom@example.com
-
- -
-
- - Your Profile - - - Settings - - - Sign out - -
-
-
- - )} -
- ) -} diff --git a/moon/src/components/catalyst/avatar.tsx b/moon/src/components/catalyst/avatar.tsx new file mode 100644 index 000000000..ed21e313e --- /dev/null +++ b/moon/src/components/catalyst/avatar.tsx @@ -0,0 +1,84 @@ +import * as Headless from '@headlessui/react' +import clsx from 'clsx' +import React, { forwardRef } from 'react' +import { TouchTarget } from './button' +import { Link } from './link' + +type AvatarProps = { + src?: string | null + square?: boolean + initials?: string + alt?: string + className?: string +} + +export function Avatar({ + src = null, + square = false, + initials, + alt = '', + className, + ...props +}: AvatarProps & React.ComponentPropsWithoutRef<'span'>) { + return ( + + {initials && ( + + {alt && {alt}} + + {initials} + + + )} + {src && {alt}} + + ) +} + +export const AvatarButton = forwardRef(function AvatarButton( + { + src, + square = false, + initials, + alt, + className, + ...props + }: AvatarProps & + (Omit | Omit, 'className'>), + ref: React.ForwardedRef +) { + let classes = clsx( + className, + square ? 'rounded-[20%]' : 'rounded-full', + 'relative inline-grid focus:outline-none data-[focus]:outline data-[focus]:outline-2 data-[focus]:outline-offset-2 data-[focus]:outline-blue-500' + ) + + return 'href' in props ? ( + }> + + + + + ) : ( + + + + + + ) +}) diff --git a/moon/src/components/catalyst/button.tsx b/moon/src/components/catalyst/button.tsx new file mode 100644 index 000000000..215398119 --- /dev/null +++ b/moon/src/components/catalyst/button.tsx @@ -0,0 +1,204 @@ +import * as Headless from '@headlessui/react' +import clsx from 'clsx' +import React, { forwardRef } from 'react' +import { Link } from './link' + +const styles = { + base: [ + // Base + 'relative isolate inline-flex items-center justify-center gap-x-2 rounded-lg border text-base/6 font-semibold', + // Sizing + 'px-[calc(theme(spacing[3.5])-1px)] py-[calc(theme(spacing[2.5])-1px)] sm:px-[calc(theme(spacing.3)-1px)] sm:py-[calc(theme(spacing[1.5])-1px)] sm:text-sm/6', + // Focus + 'focus:outline-none data-[focus]:outline data-[focus]:outline-2 data-[focus]:outline-offset-2 data-[focus]:outline-blue-500', + // Disabled + 'data-[disabled]:opacity-50', + // Icon + '[&>[data-slot=icon]]:-mx-0.5 [&>[data-slot=icon]]:my-0.5 [&>[data-slot=icon]]:size-5 [&>[data-slot=icon]]:shrink-0 [&>[data-slot=icon]]:text-[--btn-icon] [&>[data-slot=icon]]:sm:my-1 [&>[data-slot=icon]]:sm:size-4 forced-colors:[--btn-icon:ButtonText] forced-colors:data-[hover]:[--btn-icon:ButtonText]', + ], + solid: [ + // Optical border, implemented as the button background to avoid corner artifacts + 'border-transparent bg-[--btn-border]', + // Dark mode: border is rendered on `after` so background is set to button background + 'dark:bg-[--btn-bg]', + // Button background, implemented as foreground layer to stack on top of pseudo-border layer + 'before:absolute before:inset-0 before:-z-10 before:rounded-[calc(theme(borderRadius.lg)-1px)] before:bg-[--btn-bg]', + // Drop shadow, applied to the inset `before` layer so it blends with the border + 'before:shadow', + // Background color is moved to control and shadow is removed in dark mode so hide `before` pseudo + 'dark:before:hidden', + // Dark mode: Subtle white outline is applied using a border + 'dark:border-white/5', + // Shim/overlay, inset to match button foreground and used for hover state + highlight shadow + 'after:absolute after:inset-0 after:-z-10 after:rounded-[calc(theme(borderRadius.lg)-1px)]', + // Inner highlight shadow + 'after:shadow-[shadow:inset_0_1px_theme(colors.white/15%)]', + // White overlay on hover + 'after:data-[active]:bg-[--btn-hover-overlay] after:data-[hover]:bg-[--btn-hover-overlay]', + // Dark mode: `after` layer expands to cover entire button + 'dark:after:-inset-px dark:after:rounded-lg', + // Disabled + 'before:data-[disabled]:shadow-none after:data-[disabled]:shadow-none', + ], + outline: [ + // Base + 'border-zinc-950/10 text-zinc-950 data-[active]:bg-zinc-950/[2.5%] data-[hover]:bg-zinc-950/[2.5%]', + // Dark mode + 'dark:border-white/15 dark:text-white dark:[--btn-bg:transparent] dark:data-[active]:bg-white/5 dark:data-[hover]:bg-white/5', + // Icon + '[--btn-icon:theme(colors.zinc.500)] data-[active]:[--btn-icon:theme(colors.zinc.700)] data-[hover]:[--btn-icon:theme(colors.zinc.700)] dark:data-[active]:[--btn-icon:theme(colors.zinc.400)] dark:data-[hover]:[--btn-icon:theme(colors.zinc.400)]', + ], + plain: [ + // Base + 'border-transparent text-zinc-950 data-[active]:bg-zinc-950/5 data-[hover]:bg-zinc-950/5', + // Dark mode + 'dark:text-white dark:data-[active]:bg-white/10 dark:data-[hover]:bg-white/10', + // Icon + '[--btn-icon:theme(colors.zinc.500)] data-[active]:[--btn-icon:theme(colors.zinc.700)] data-[hover]:[--btn-icon:theme(colors.zinc.700)] dark:[--btn-icon:theme(colors.zinc.500)] dark:data-[active]:[--btn-icon:theme(colors.zinc.400)] dark:data-[hover]:[--btn-icon:theme(colors.zinc.400)]', + ], + colors: { + 'dark/zinc': [ + 'text-white [--btn-bg:theme(colors.zinc.900)] [--btn-border:theme(colors.zinc.950/90%)] [--btn-hover-overlay:theme(colors.white/10%)]', + 'dark:text-white dark:[--btn-bg:theme(colors.zinc.600)] dark:[--btn-hover-overlay:theme(colors.white/5%)]', + '[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)]', + ], + light: [ + 'text-zinc-950 [--btn-bg:white] [--btn-border:theme(colors.zinc.950/10%)] [--btn-hover-overlay:theme(colors.zinc.950/2.5%)] data-[active]:[--btn-border:theme(colors.zinc.950/15%)] data-[hover]:[--btn-border:theme(colors.zinc.950/15%)]', + 'dark:text-white dark:[--btn-hover-overlay:theme(colors.white/5%)] dark:[--btn-bg:theme(colors.zinc.800)]', + '[--btn-icon:theme(colors.zinc.500)] data-[active]:[--btn-icon:theme(colors.zinc.700)] data-[hover]:[--btn-icon:theme(colors.zinc.700)] dark:[--btn-icon:theme(colors.zinc.500)] dark:data-[active]:[--btn-icon:theme(colors.zinc.400)] dark:data-[hover]:[--btn-icon:theme(colors.zinc.400)]', + ], + 'dark/white': [ + 'text-white [--btn-bg:theme(colors.zinc.900)] [--btn-border:theme(colors.zinc.950/90%)] [--btn-hover-overlay:theme(colors.white/10%)]', + 'dark:text-zinc-950 dark:[--btn-bg:white] dark:[--btn-hover-overlay:theme(colors.zinc.950/5%)]', + '[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)] dark:[--btn-icon:theme(colors.zinc.500)] dark:data-[active]:[--btn-icon:theme(colors.zinc.400)] dark:data-[hover]:[--btn-icon:theme(colors.zinc.400)]', + ], + dark: [ + 'text-white [--btn-bg:theme(colors.zinc.900)] [--btn-border:theme(colors.zinc.950/90%)] [--btn-hover-overlay:theme(colors.white/10%)]', + 'dark:[--btn-hover-overlay:theme(colors.white/5%)] dark:[--btn-bg:theme(colors.zinc.800)]', + '[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)]', + ], + white: [ + 'text-zinc-950 [--btn-bg:white] [--btn-border:theme(colors.zinc.950/10%)] [--btn-hover-overlay:theme(colors.zinc.950/2.5%)] data-[active]:[--btn-border:theme(colors.zinc.950/15%)] data-[hover]:[--btn-border:theme(colors.zinc.950/15%)]', + 'dark:[--btn-hover-overlay:theme(colors.zinc.950/5%)]', + '[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.500)] data-[hover]:[--btn-icon:theme(colors.zinc.500)]', + ], + zinc: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.zinc.600)] [--btn-border:theme(colors.zinc.700/90%)]', + 'dark:[--btn-hover-overlay:theme(colors.white/5%)]', + '[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)]', + ], + indigo: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.indigo.500)] [--btn-border:theme(colors.indigo.600/90%)]', + '[--btn-icon:theme(colors.indigo.300)] data-[active]:[--btn-icon:theme(colors.indigo.200)] data-[hover]:[--btn-icon:theme(colors.indigo.200)]', + ], + cyan: [ + 'text-cyan-950 [--btn-bg:theme(colors.cyan.300)] [--btn-border:theme(colors.cyan.400/80%)] [--btn-hover-overlay:theme(colors.white/25%)]', + '[--btn-icon:theme(colors.cyan.500)]', + ], + red: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.red.600)] [--btn-border:theme(colors.red.700/90%)]', + '[--btn-icon:theme(colors.red.300)] data-[active]:[--btn-icon:theme(colors.red.200)] data-[hover]:[--btn-icon:theme(colors.red.200)]', + ], + orange: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.orange.500)] [--btn-border:theme(colors.orange.600/90%)]', + '[--btn-icon:theme(colors.orange.300)] data-[active]:[--btn-icon:theme(colors.orange.200)] data-[hover]:[--btn-icon:theme(colors.orange.200)]', + ], + amber: [ + 'text-amber-950 [--btn-hover-overlay:theme(colors.white/25%)] [--btn-bg:theme(colors.amber.400)] [--btn-border:theme(colors.amber.500/80%)]', + '[--btn-icon:theme(colors.amber.600)]', + ], + yellow: [ + 'text-yellow-950 [--btn-hover-overlay:theme(colors.white/25%)] [--btn-bg:theme(colors.yellow.300)] [--btn-border:theme(colors.yellow.400/80%)]', + '[--btn-icon:theme(colors.yellow.600)] data-[active]:[--btn-icon:theme(colors.yellow.700)] data-[hover]:[--btn-icon:theme(colors.yellow.700)]', + ], + lime: [ + 'text-lime-950 [--btn-hover-overlay:theme(colors.white/25%)] [--btn-bg:theme(colors.lime.300)] [--btn-border:theme(colors.lime.400/80%)]', + '[--btn-icon:theme(colors.lime.600)] data-[active]:[--btn-icon:theme(colors.lime.700)] data-[hover]:[--btn-icon:theme(colors.lime.700)]', + ], + green: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.green.600)] [--btn-border:theme(colors.green.700/90%)]', + '[--btn-icon:theme(colors.white/60%)] data-[active]:[--btn-icon:theme(colors.white/80%)] data-[hover]:[--btn-icon:theme(colors.white/80%)]', + ], + emerald: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.emerald.600)] [--btn-border:theme(colors.emerald.700/90%)]', + '[--btn-icon:theme(colors.white/60%)] data-[active]:[--btn-icon:theme(colors.white/80%)] data-[hover]:[--btn-icon:theme(colors.white/80%)]', + ], + teal: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.teal.600)] [--btn-border:theme(colors.teal.700/90%)]', + '[--btn-icon:theme(colors.white/60%)] data-[active]:[--btn-icon:theme(colors.white/80%)] data-[hover]:[--btn-icon:theme(colors.white/80%)]', + ], + sky: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.sky.500)] [--btn-border:theme(colors.sky.600/80%)]', + '[--btn-icon:theme(colors.white/60%)] data-[active]:[--btn-icon:theme(colors.white/80%)] data-[hover]:[--btn-icon:theme(colors.white/80%)]', + ], + blue: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.blue.600)] [--btn-border:theme(colors.blue.700/90%)]', + '[--btn-icon:theme(colors.blue.400)] data-[active]:[--btn-icon:theme(colors.blue.300)] data-[hover]:[--btn-icon:theme(colors.blue.300)]', + ], + violet: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.violet.500)] [--btn-border:theme(colors.violet.600/90%)]', + '[--btn-icon:theme(colors.violet.300)] data-[active]:[--btn-icon:theme(colors.violet.200)] data-[hover]:[--btn-icon:theme(colors.violet.200)]', + ], + purple: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.purple.500)] [--btn-border:theme(colors.purple.600/90%)]', + '[--btn-icon:theme(colors.purple.300)] data-[active]:[--btn-icon:theme(colors.purple.200)] data-[hover]:[--btn-icon:theme(colors.purple.200)]', + ], + fuchsia: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.fuchsia.500)] [--btn-border:theme(colors.fuchsia.600/90%)]', + '[--btn-icon:theme(colors.fuchsia.300)] data-[active]:[--btn-icon:theme(colors.fuchsia.200)] data-[hover]:[--btn-icon:theme(colors.fuchsia.200)]', + ], + pink: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.pink.500)] [--btn-border:theme(colors.pink.600/90%)]', + '[--btn-icon:theme(colors.pink.300)] data-[active]:[--btn-icon:theme(colors.pink.200)] data-[hover]:[--btn-icon:theme(colors.pink.200)]', + ], + rose: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.rose.500)] [--btn-border:theme(colors.rose.600/90%)]', + '[--btn-icon:theme(colors.rose.300)] data-[active]:[--btn-icon:theme(colors.rose.200)] data-[hover]:[--btn-icon:theme(colors.rose.200)]', + ], + }, +} + +type ButtonProps = ( + | { color?: keyof typeof styles.colors; outline?: never; plain?: never } + | { color?: never; outline: true; plain?: never } + | { color?: never; outline?: never; plain: true } +) & { className?: string; children: React.ReactNode } & ( + | Omit + | Omit, 'className'> + ) + +export const Button = forwardRef(function Button( + { color, outline, plain, className, children, ...props }: ButtonProps, + ref: React.ForwardedRef +) { + let classes = clsx( + className, + styles.base, + outline ? styles.outline : plain ? styles.plain : clsx(styles.solid, styles.colors[color ?? 'dark/zinc']) + ) + + return 'href' in props ? ( + }> + {children} + + ) : ( + + {children} + + ) +}) + +/** + * Expand the hit area to at least 44×44px on touch devices + */ +export function TouchTarget({ children }: { children: React.ReactNode }) { + return ( + <> +