feat: events page in dashboard
This commit is contained in:
parent
8324beaa09
commit
20ae6a2091
|
|
@ -6,6 +6,7 @@
|
|||
"Unread notifications": "未读通知",
|
||||
"Settings": "设置",
|
||||
"Events": "活动",
|
||||
"New Events": "新活动",
|
||||
"Site Stats": "站点统计",
|
||||
"Total Posts": "文章总数",
|
||||
"Total Comments": "评论总数",
|
||||
|
|
@ -131,5 +132,12 @@
|
|||
"Published!" : "已发布!",
|
||||
"Title goes here...": "标题在这里...",
|
||||
"Start writing...": "开始写作...",
|
||||
"Slug can only contain letters, numbers, hyphens, and underscores." : "短链接只能包含字母、数字、连字符和下划线。"
|
||||
"Slug can only contain letters, numbers, hyphens, and underscores." : "短链接只能包含字母、数字、连字符和下划线。",
|
||||
"Date": "日期",
|
||||
"Prize": "奖金",
|
||||
"Winners": "获奖者",
|
||||
"Learn more": "了解更多",
|
||||
"Ended": "已结束",
|
||||
"Upcoming": "即将开始",
|
||||
"Ongoing": "进行中"
|
||||
}
|
||||
|
|
@ -98,5 +98,7 @@
|
|||
"0xcavin",
|
||||
"shuxinfeng",
|
||||
"tea-24",
|
||||
"revery"
|
||||
"revery",
|
||||
"hsinyau",
|
||||
"rootless"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import { useAccountState, useConnectModal } from "@crossbell/connect-kit"
|
|||
import { useTranslation } from "react-i18next"
|
||||
import { Logo } from "~/components/common/Logo"
|
||||
import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline"
|
||||
import { getStorage } from "~/lib/storage"
|
||||
import { useGetPagesBySite } from "~/queries/page"
|
||||
|
||||
export function DashboardLayout({
|
||||
children,
|
||||
|
|
@ -64,6 +66,26 @@ export function DashboardLayout({
|
|||
const showNotificationModal = useShowNotificationModal()
|
||||
const { isAllRead } = useNotifications()
|
||||
|
||||
const pages = useGetPagesBySite({
|
||||
type: "post",
|
||||
site: "xlog-events",
|
||||
take: 1,
|
||||
})
|
||||
const [isEventsAllRead, setIsEventsAllRead] = React.useState(true)
|
||||
const latestEventRead = getStorage("latestEventRead")?.value || 0
|
||||
useEffect(() => {
|
||||
if (pages.isSuccess) {
|
||||
if (
|
||||
new Date(pages.data.pages[0].list?.[0].date_created) >
|
||||
new Date(latestEventRead)
|
||||
) {
|
||||
setIsEventsAllRead(false)
|
||||
} else {
|
||||
setIsEventsAllRead(true)
|
||||
}
|
||||
}
|
||||
}, [pages.isSuccess, pages.data?.pages, latestEventRead])
|
||||
|
||||
const links: {
|
||||
href?: string
|
||||
onClick?: () => void
|
||||
|
|
@ -95,6 +117,12 @@ export function DashboardLayout({
|
|||
icon: isAllRead ? "i-bi:bell" : "i-bi:bell-fill",
|
||||
text: isAllRead ? "Notifications" : "Unread notifications",
|
||||
},
|
||||
{
|
||||
href: `/dashboard/${subdomain}/events`,
|
||||
isActive: ({ href, pathname }) => href === pathname,
|
||||
icon: isEventsAllRead ? "i-bi-gift" : "i-bi-gift-fill",
|
||||
text: isEventsAllRead ? "Events" : "New Events",
|
||||
},
|
||||
{
|
||||
href: `/dashboard/${subdomain}/settings/general`,
|
||||
isActive: ({ pathname }) =>
|
||||
|
|
@ -195,7 +223,7 @@ export function DashboardLayout({
|
|||
<div className="absolute bottom-5 left-0 right-0 flex items-center px-4 flex-col">
|
||||
<UniLink
|
||||
href={DISCORD_LINK}
|
||||
className="space-x-2 text-zinc-500 hover:text-zinc-800 flex w-full h-12 items-center justify-center transition-colors mb-2"
|
||||
className="space-x-1 text-zinc-500 hover:text-zinc-800 flex w-full h-12 items-center justify-center transition-colors mb-2"
|
||||
>
|
||||
<QuestionMarkCircleIcon className="w-5 h-5" />
|
||||
{isOpen && <span>{t("Need help?")}</span>}
|
||||
|
|
@ -225,6 +253,9 @@ export function DashboardLayout({
|
|||
<div className="mt-4">
|
||||
Please switch account or request the owner to set you as the operator.
|
||||
</div>
|
||||
<UniLink href="/dashboard" className="mt-4 text-accent">
|
||||
Take me to my own dashboard
|
||||
</UniLink>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ const expandPage = async (page: Note, useStat?: boolean) => {
|
|||
}
|
||||
}
|
||||
page.cover = rendered.cover
|
||||
page.metadata!.frontMatter = rendered.frontMatter
|
||||
}
|
||||
page.slug = encodeURIComponent(
|
||||
page.attributes?.find((a) => a.trait_type === "xlog_slug")?.value ||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,194 @@
|
|||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { DashboardMain } from "~/components/dashboard/DashboardMain"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { Image } from "~/components/ui/Image"
|
||||
import type { ReactElement } from "react"
|
||||
import { useGetSite } from "~/queries/site"
|
||||
import { useDate } from "~/hooks/useDate"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { GetServerSideProps } from "next"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { useGetPagesBySite } from "~/queries/page"
|
||||
import { CharacterFloatCard } from "~/components/common/CharacterFloatCard"
|
||||
import { useEffect, useState } from "react"
|
||||
import { Avatar } from "~/components/ui/Avatar"
|
||||
import { cn } from "~/lib/utils"
|
||||
import { ChevronRightIcon } from "@heroicons/react/20/solid"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { getStorage, setStorage } from "~/lib/storage"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const { props: layoutProps } = await getLayoutServerSideProps(ctx)
|
||||
|
||||
return {
|
||||
props: {
|
||||
...layoutProps,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
function SiteAvatar({ siteId }: { siteId: string }) {
|
||||
const site = useGetSite(siteId)
|
||||
return (
|
||||
<div className="inline-block w-10 h-10 relative">
|
||||
<CharacterFloatCard siteId={siteId}>
|
||||
<UniLink
|
||||
href={getSiteLink({
|
||||
subdomain: siteId,
|
||||
})}
|
||||
>
|
||||
<Avatar
|
||||
images={site.data?.avatars || []}
|
||||
name={site.data?.name || ""}
|
||||
size={40}
|
||||
/>
|
||||
</UniLink>
|
||||
</CharacterFloatCard>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function EventsPage() {
|
||||
const date = useDate()
|
||||
const { t } = useTranslation("dashboard")
|
||||
const pages = useGetPagesBySite({
|
||||
type: "post",
|
||||
site: "xlog-events",
|
||||
take: 100,
|
||||
})
|
||||
|
||||
const [latestEventRead, setLatestEventRead] = useState<Date>()
|
||||
|
||||
const [isMounted, setIsMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setIsMounted(true)
|
||||
setLatestEventRead(new Date(getStorage("latestEventRead")?.value || 0))
|
||||
setStorage("latestEventRead", {
|
||||
value: new Date().toISOString(),
|
||||
})
|
||||
}, [])
|
||||
|
||||
pages.data?.pages[0]?.list.forEach((item) => {
|
||||
item.metadata?.frontMatter?.Winners
|
||||
})
|
||||
|
||||
return (
|
||||
<DashboardMain title="Events">
|
||||
<div className="min-w-[270px] flex flex-col xl:flex-row space-y-8 xl:space-y-0">
|
||||
<div className="grid gap-4 grid-cols-2 sm:grid-cols-4">
|
||||
{pages.data?.pages[0]?.list.map((item) => {
|
||||
let status
|
||||
if (item.metadata?.frontMatter?.EndTime < new Date()) {
|
||||
status = "Ended"
|
||||
} else if (item.metadata?.frontMatter?.StartTime > new Date()) {
|
||||
status = "Upcoming"
|
||||
} else {
|
||||
status = "Ongoing"
|
||||
}
|
||||
|
||||
let isUnread = false
|
||||
if (
|
||||
latestEventRead &&
|
||||
new Date(item.date_created) > latestEventRead
|
||||
) {
|
||||
isUnread = true
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={cn("bg-slate-100 rounded-lg relative", {
|
||||
"opacity-70": status === "Ended",
|
||||
})}
|
||||
key={item.id}
|
||||
>
|
||||
{isUnread && (
|
||||
<div>
|
||||
<span className="absolute -left-2 -top-2 bg-red-500 rounded-full w-4 h-4"></span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col p-7 justify-between h-full">
|
||||
<div>
|
||||
<div className="font-bold mb-4">
|
||||
<span
|
||||
className={cn(
|
||||
"border py-1 px-4 rounded-full text-white",
|
||||
{
|
||||
"bg-gray-500": status === "Ended",
|
||||
"bg-green-500": status === "Upcoming",
|
||||
"bg-yellow-500": status === "Ongoing",
|
||||
},
|
||||
)}
|
||||
>
|
||||
{t(status)}
|
||||
</span>
|
||||
</div>
|
||||
{item.cover && (
|
||||
<div className="w-full h-24 mb-4">
|
||||
<Image
|
||||
className="object-cover rounded"
|
||||
alt="cover"
|
||||
fill={true}
|
||||
src={item.cover}
|
||||
></Image>
|
||||
</div>
|
||||
)}
|
||||
<div className="font-bold text-xl text-zinc-800 leading-tight">
|
||||
{item.title}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 space-y-4 text-sm">
|
||||
<div>
|
||||
<span className="font-bold">{t("Date")}:</span>{" "}
|
||||
{date.formatDate(
|
||||
item.metadata?.frontMatter?.StartTime,
|
||||
"lll",
|
||||
isMounted ? undefined : "America/Los_Angeles",
|
||||
)}{" "}
|
||||
-{" "}
|
||||
{date.formatDate(
|
||||
item.metadata?.frontMatter?.EndTime,
|
||||
"lll",
|
||||
isMounted ? undefined : "America/Los_Angeles",
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-bold">{t("Prize")}:</span>{" "}
|
||||
{item.metadata?.frontMatter?.Prize}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-bold">{t("Winners")}:</span>
|
||||
<div className="flex items-center space-x-2 mt-2">
|
||||
{item.metadata?.frontMatter?.Winners.map(
|
||||
(winner: string) => (
|
||||
<SiteAvatar key={winner} siteId={winner} />
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<UniLink
|
||||
className="mt-6 font-bold flex items-center leading-none"
|
||||
href={
|
||||
item.metadata?.frontMatter?.ExtraLink ||
|
||||
item.related_urls?.[0]
|
||||
}
|
||||
>
|
||||
{t("Learn more")}{" "}
|
||||
<ChevronRightIcon className="w-5 h-5 inline ml-1" />
|
||||
</UniLink>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</DashboardMain>
|
||||
)
|
||||
}
|
||||
|
||||
EventsPage.getLayout = (page: ReactElement) => {
|
||||
return <DashboardLayout title="Dashboard">{page}</DashboardLayout>
|
||||
}
|
||||
Loading…
Reference in New Issue