forked from CaliCastle/cali.so
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
81d4826
commit 2123d4a
Showing
11 changed files
with
391 additions
and
97 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
Oops, something went wrong.