diff --git a/public/locales/zh/dashboard.json b/public/locales/zh/dashboard.json
index d79e1536..bfa17a61 100644
--- a/public/locales/zh/dashboard.json
+++ b/public/locales/zh/dashboard.json
@@ -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": "进行中"
}
\ No newline at end of file
diff --git a/showcase.json b/showcase.json
index 3bf495d2..22e428f2 100644
--- a/showcase.json
+++ b/showcase.json
@@ -98,5 +98,7 @@
"0xcavin",
"shuxinfeng",
"tea-24",
- "revery"
+ "revery",
+ "hsinyau",
+ "rootless"
]
diff --git a/src/components/dashboard/DashboardLayout.tsx b/src/components/dashboard/DashboardLayout.tsx
index d721498c..1acfc4d4 100644
--- a/src/components/dashboard/DashboardLayout.tsx
+++ b/src/components/dashboard/DashboardLayout.tsx
@@ -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({
{isOpen && {t("Need help?")}}
@@ -225,6 +253,9 @@ export function DashboardLayout({
Please switch account or request the owner to set you as the operator.
+
+ Take me to my own dashboard
+
)
) : (
diff --git a/src/models/page.model.ts b/src/models/page.model.ts
index c4b07f83..8746ea9c 100644
--- a/src/models/page.model.ts
+++ b/src/models/page.model.ts
@@ -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 ||
diff --git a/src/pages/dashboard/[subdomain]/events.tsx b/src/pages/dashboard/[subdomain]/events.tsx
new file mode 100644
index 00000000..93cdc6c4
--- /dev/null
+++ b/src/pages/dashboard/[subdomain]/events.tsx
@@ -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 (
+
+ )
+}
+
+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()
+
+ 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 (
+
+
+
+ {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 (
+
+ {isUnread && (
+
+
+
+ )}
+
+
+
+
+ {t(status)}
+
+
+ {item.cover && (
+
+
+
+ )}
+
+ {item.title}
+
+
+
+
+ {t("Date")}:{" "}
+ {date.formatDate(
+ item.metadata?.frontMatter?.StartTime,
+ "lll",
+ isMounted ? undefined : "America/Los_Angeles",
+ )}{" "}
+ -{" "}
+ {date.formatDate(
+ item.metadata?.frontMatter?.EndTime,
+ "lll",
+ isMounted ? undefined : "America/Los_Angeles",
+ )}
+
+
+ {t("Prize")}:{" "}
+ {item.metadata?.frontMatter?.Prize}
+
+
+
{t("Winners")}:
+
+ {item.metadata?.frontMatter?.Winners.map(
+ (winner: string) => (
+
+ ),
+ )}
+
+
+
+
+ {t("Learn more")}{" "}
+
+
+
+
+ )
+ })}
+
+
+
+ )
+}
+
+EventsPage.getLayout = (page: ReactElement) => {
+ return {page}
+}