diff --git a/packages/app/src/pages/featured-event/[id].jsx b/packages/app/src/pages/featured-event/[id].jsx
new file mode 100644
index 00000000..d608db4a
--- /dev/null
+++ b/packages/app/src/pages/featured-event/[id].jsx
@@ -0,0 +1,110 @@
+import React from "react"
+import { Skeleton } from "antd"
+import ReactMarkdown from "react-markdown"
+import remarkGfm from "remark-gfm"
+
+import ProcessString from "utils/processString"
+import { Icons } from "components/Icons"
+
+import "./index.less"
+
+const LocationProcessRegexs = [
+ {
+ regex: /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi,
+ fn: (key, result) => {
+ return {result[1]}
+ }
+ }
+]
+
+export default (props) => {
+ const eventId = props.match.params["id"]
+
+ const [eventData, setEventData] = React.useState(null)
+
+ const fetchEventData = async () => {
+ const { data } = await app.api.customRequest("main", {
+ method: "GET",
+ url: `/featured_event/${eventId}`
+ }).catch((err) => {
+ console.error(err)
+ app.message.error("Failed to fetch event data")
+
+ return {
+ data: null
+ }
+ })
+
+ if (data) {
+ try {
+ data.announcement = JSON.parse(data.announcement)
+ setEventData(data)
+ } catch (error) {
+ console.error(error)
+ app.message.error("Failed to parse event data")
+ }
+ }
+ }
+
+ const renderDates = (dates) => {
+ return
+
+ {
+ dates[0]
+ }
+
+
+ to
+
+
+ {
+ dates[1]
+ }
+
+
+ }
+
+ console.log(eventData)
+
+ React.useEffect(() => {
+ fetchEventData()
+ }, [])
+
+ if (!eventData) {
+ return
+ }
+
+ return
+
+ {eventData.announcement.logoImg &&
+
+

+
+ }
+
+
{eventData.name}
+ {eventData.announcement.description}
+
+
+
+
+
+
+ {Array.isArray(eventData.dates) && renderDates(eventData.dates)}
+
+
+
+ {ProcessString(LocationProcessRegexs)(eventData.location)}
+
+
+
+
+
+
+ {eventData.description}
+
+
+
+
+
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/featured-event/index.less b/packages/app/src/pages/featured-event/index.less
new file mode 100644
index 00000000..5bbbc6ad
--- /dev/null
+++ b/packages/app/src/pages/featured-event/index.less
@@ -0,0 +1,91 @@
+.event {
+ display: flex;
+ flex-direction: column;
+
+ align-items: center;
+ justify-content: flex-end;
+
+ color: var(--text-color);
+
+ .header {
+ display: flex;
+ flex-direction: row;
+
+ width: 100%;
+
+ padding: 20px;
+
+ margin-bottom: 20px;
+
+ border-radius: 12px;
+
+ .logo {
+ height: 100%;
+
+ width: fit-content;
+
+ max-width: 200px;
+
+ img {
+ height: 100%;
+ width: 100%;
+ }
+ }
+
+ .title {
+ display: flex;
+ flex-direction: column;
+
+ margin-left: 50px;
+
+ padding: 20px 0;
+
+ font-family: "Space Grotesk", sans-serif;
+
+ h1 {
+ font-size: 2rem;
+ font-weight: 700;
+ }
+ }
+ }
+
+ .content {
+ display: grid;
+ grid-template-columns: 30% 1fr;
+ grid-template-rows: 1fr;
+ grid-column-gap: 20px;
+ grid-row-gap: 0px;
+
+ .panel {
+ display: flex;
+ flex-direction: column;
+
+ padding: 20px;
+
+ background-color: var(--background-color-accent);
+
+ border-radius: 12px;
+
+ height: fit-content;
+ }
+ }
+
+ .dates {
+ display: flex;
+ flex-direction: row;
+
+ margin-bottom: 10px;
+
+ .startsAt {
+ display: flex;
+
+ margin-right: 10px;
+ }
+
+ .endsAt {
+ display: flex;
+
+ margin-left: 10px;
+ }
+ }
+}
\ No newline at end of file