Skip to content

Commit

Permalink
feat: optimize admin dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
CaliCastle committed Jun 13, 2023
1 parent 81d4826 commit 2123d4a
Show file tree
Hide file tree
Showing 11 changed files with 391 additions and 97 deletions.
2 changes: 1 addition & 1 deletion app/(main)/blog/BlogPosts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export async function BlogPosts({ limit = 5 }) {
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw"
/>
</div>
<span className="relative z-10 flex w-full flex-col gap-2 p-4 after:pointer-events-none after:absolute after:inset-0 after:select-none after:rounded-b-3xl after:bg-[--post-image-bg] after:from-transparent after:to-[--post-image-bg] after:backdrop-blur-lg after:transition-opacity md:p-5">
<span className="relative z-10 flex w-full flex-col gap-0.5 p-4 after:pointer-events-none after:absolute after:inset-0 after:select-none after:rounded-b-3xl after:bg-[--post-image-bg] after:from-transparent after:to-[--post-image-bg] after:backdrop-blur-lg after:transition-opacity md:p-5">
<h2 className="z-20 text-base font-bold tracking-tight text-[--post-image-fg] md:text-xl">
{title}
</h2>
Expand Down
22 changes: 14 additions & 8 deletions app/(main)/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,20 @@ export default async function BlogPage({

let reactions: number[] = []
try {
const res = await fetch(url(`/api/reactions?id=${post._id}`), {
next: {
tags: [`reactions:${post._id}`],
},
})
const data = await res.json()
if (Array.isArray(data)) {
reactions = data
if (env.VERCEL_ENV === 'production') {
const res = await fetch(url(`/api/reactions?id=${post._id}`), {
next: {
tags: [`reactions:${post._id}`],
},
})
const data = await res.json()
if (Array.isArray(data)) {
reactions = data
}
} else {
reactions = Array.from({ length: 4 }, () =>
Math.floor(Math.random() * 50000)
)
}
} catch (error) {
console.error(error)
Expand Down
81 changes: 81 additions & 0 deletions app/admin/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
'use client'

import { clsxm } from '@zolplay/utils'
import Image from 'next/image'
import Link from 'next/link'
import { usePathname } from 'next/navigation'

import {
DashboardIcon,
HomeIcon,
NewCommentIcon,
SubscriberIcon,
} from '~/assets'

import logo from './../apple-icon.png'

const navigation = [
{ name: '仪表盘', href: '', icon: DashboardIcon },
{ name: '订阅', href: '/subscribers', icon: SubscriberIcon },
{ name: '评论', href: '/comments', icon: NewCommentIcon },
]

export function Sidebar() {
const pathname = usePathname()
function isActive(href: string) {
return pathname === `/admin${href}`
}

return (
<div className="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-72 lg:flex-col">
{/* Sidebar component, swap this element with another sidebar if you like */}
<div className="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-6 dark:border-slate-800 dark:bg-slate-900">
<div className="flex h-16 shrink-0 items-center">
<Image className="h-8 w-auto" src={logo} alt="" />
</div>
<nav className="flex flex-1 flex-col">
<ul role="list" className="flex flex-1 flex-col gap-y-7">
<li>
<ul role="list" className="-mx-2 space-y-1">
{navigation.map((item) => (
<li key={item.name}>
<Link
href={`/admin${item.href}`}
className={clsxm(
isActive(item.href)
? 'bg-gray-50 text-indigo-600 dark:bg-slate-800 dark:text-white'
: 'text-gray-700 hover:bg-gray-50 hover:text-indigo-600 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-white',
'group flex items-center gap-x-3 rounded-md p-2 text-sm font-semibold leading-6'
)}
>
<item.icon
className={clsxm(
isActive(item.href)
? 'text-indigo-600 dark:text-slate-300'
: 'text-gray-400 group-hover:text-indigo-600 dark:text-slate-600 dark:group-hover:text-slate-300',
'h-5 w-5 shrink-0'
)}
aria-hidden="true"
/>
{item.name}
</Link>
</li>
))}
</ul>
</li>

<li className="-mx-6 mt-auto">
<Link
href="/"
className="flex items-center gap-x-4 px-6 py-3 text-sm font-semibold leading-6 text-gray-900 hover:bg-gray-50 dark:text-slate-50 dark:hover:bg-slate-800"
>
<HomeIcon className="h-5 w-5" aria-hidden="true" />
返回前台
</Link>
</li>
</ul>
</nav>
</div>
</div>
)
}
113 changes: 113 additions & 0 deletions app/admin/comments/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import {
Card,
Grid,
Metric,
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
Text,
Title,
} from '@tremor/react'
import { desc, sql } from 'drizzle-orm'
import Link from 'next/link'
import React from 'react'

import { Container } from '~/components/ui/Container'
import { db } from '~/db'
import { comments } from '~/db/schema'
import { url } from '~/lib'
import { truncate } from '~/lib/string'
import { clientFetch } from '~/sanity/lib/client'

export default async function AdminCommentsPage() {
const {
rows: [commentsCount],
} = await db.execute<{ today_count: number }>(
sql`SELECT
(SELECT COUNT(*) FROM comments WHERE DATE(created_at) = CURDATE()) as today_count,
(SELECT COUNT(*) FROM comments WHERE YEARWEEK(created_at, 1) = YEARWEEK(CURDATE(), 1)) as this_week_count,
(SELECT COUNT(*) FROM comments WHERE YEAR(created_at) = YEAR(CURDATE()) AND MONTH(created_at) = MONTH(CURDATE())) as this_month_count`
)

const latestComments = await db
.select()
.from(comments)
.orderBy(desc(comments.createdAt))
.limit(15)
// get unique post IDs from comments
const postIds = [...new Set(latestComments.map((comment) => comment.postId))]
const posts = await clientFetch<
{ _id: string; title: string; slug: string }[]
>(
`*[_type == "post" && (_id in [${postIds
.map((v) => `"${v}"`)
.join(',')}])]{ _id, title, "slug":slug.current }`
)
// define a map with key of post IDs to posts
const postMap = new Map(posts.map((post) => [post._id, post]))

return (
<Container className="py-12">
<Title>评论</Title>

<Grid numItemsMd={2} numItemsLg={3} className="mt-6 gap-6">
<Card>
<Text>今日评论数</Text>

{commentsCount && 'today_count' in commentsCount && (
<Metric>{commentsCount.today_count}</Metric>
)}
</Card>
<Card>
<Text>本周评论数</Text>
{commentsCount && 'this_week_count' in commentsCount && (
<Metric>{commentsCount.this_week_count}</Metric>
)}
</Card>

<Card>
<Text>本月评论数</Text>
{commentsCount && 'this_month_count' in commentsCount && (
<Metric>{commentsCount.this_month_count}</Metric>
)}
</Card>
</Grid>

<Card className="mt-6">
<Table className="mt-5">
<TableHead>
<TableRow>
<TableHeaderCell>文章</TableHeaderCell>
<TableHeaderCell>评论内容</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{latestComments.map((comment) => (
<TableRow key={comment.id}>
<TableCell>
<Link
href={
url(`/blog/${postMap.get(comment.postId)?.slug ?? ''}`)
.href
}
>
{postMap.get(comment.postId)?.title}
</Link>
</TableCell>
<TableCell>
<Text>
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
{truncate((comment.body as any).text as string)}
</Text>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</Container>
)
}
12 changes: 11 additions & 1 deletion app/admin/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { currentUser } from '@clerk/nextjs'
import { redirect } from 'next/navigation'

import { Sidebar } from './Sidebar'

export default async function AdminLayout({
children,
}: {
Expand All @@ -11,5 +13,13 @@ export default async function AdminLayout({
redirect('/')
}

return <div>{children}</div>
return (
<div>
<Sidebar />

<main className="py-10 lg:pl-72">
<div className="px-4 sm:px-6 lg:px-8">{children}</div>
</main>
</div>
)
}
102 changes: 15 additions & 87 deletions app/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,113 +1,41 @@
import {
Card,
Grid,
Metric,
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
Text,
Title,
} from '@tremor/react'
import { desc, sql } from 'drizzle-orm'
import Link from 'next/link'
import { Card, Grid, Metric, Text, Title } from '@tremor/react'
import { sql } from 'drizzle-orm'
import React from 'react'

import { Container } from '~/components/ui/Container'
import { db } from '~/db'
import { comments } from '~/db/schema'
import { url } from '~/lib'
import { truncate } from '~/lib/string'
import { clientFetch } from '~/sanity/lib/client'

export default async function AdminPage() {
const {
rows: [commentsCount],
rows: [count],
} = await db.execute<{ today_count: number }>(
sql`SELECT
(SELECT COUNT(*) FROM comments WHERE DATE(created_at) = CURDATE()) as today_count,
(SELECT COUNT(*) FROM comments WHERE YEARWEEK(created_at, 1) = YEARWEEK(CURDATE(), 1)) as this_week_count,
(SELECT COUNT(*) FROM comments WHERE YEAR(created_at) = YEAR(CURDATE()) AND MONTH(created_at) = MONTH(CURDATE())) as this_month_count`
(SELECT COUNT(*) FROM comments) as comments,
(SELECT COUNT(*) FROM subscribers WHERE subscribed_at IS NOT NULL) as subscribers,
(SELECT COUNT(*) FROM guestbook) as guestbook`
)

const latestComments = await db
.select()
.from(comments)
.orderBy(desc(comments.createdAt))
.limit(15)
// get unique post IDs from comments
const postIds = [...new Set(latestComments.map((comment) => comment.postId))]
const posts = await clientFetch<
{ _id: string; title: string; slug: string }[]
>(
`*[_type == "post" && (_id in [${postIds
.map((v) => `"${v}"`)
.join(',')}])]{ _id, title, "slug":slug.current }`
)
// define a map with key of post IDs to posts
const postMap = new Map(posts.map((post) => [post._id, post]))

return (
<Container className="mt-12">
<Title>后台数据中心</Title>
<Container className="mt-12 pb-12">
<Title>后台仪表盘</Title>

<Grid numItemsMd={2} numItemsLg={3} className="mt-6 gap-6">
<Card>
<Text>今日评论数</Text>
<Text>总评论</Text>

{commentsCount && 'today_count' in commentsCount && (
<Metric>{commentsCount.today_count}</Metric>
)}
{count && 'comments' in count && <Metric>{count.comments}</Metric>}
</Card>
<Card>
<Text>本周评论数</Text>
{commentsCount && 'this_week_count' in commentsCount && (
<Metric>{commentsCount.this_week_count}</Metric>
<Text>总订阅</Text>
{count && 'subscribers' in count && (
<Metric>{count.subscribers}</Metric>
)}
</Card>

<Card>
<Text>本月评论数</Text>
{commentsCount && 'this_month_count' in commentsCount && (
<Metric>{commentsCount.this_month_count}</Metric>
)}
<Text>总留言</Text>
{count && 'guestbook' in count && <Metric>{count.guestbook}</Metric>}
</Card>
</Grid>
<div className="mt-6">
<Card>
<Table className="mt-5">
<TableHead>
<TableRow>
<TableHeaderCell>文章</TableHeaderCell>
<TableHeaderCell>评论内容</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{latestComments.map((comment) => (
<TableRow key={comment.id}>
<TableCell>
<Link
href={url(
`/blog/${postMap.get(comment.postId)?.slug ?? ''}`
)}
>
{postMap.get(comment.postId)?.title}
</Link>
</TableCell>
<TableCell>
<Text>
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
{truncate((comment.body as any).text as string)}
</Text>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</div>
</Container>
)
}
Loading

0 comments on commit 2123d4a

Please sign in to comment.