diff --git a/packages/app/src/pages/music/[type].jsx b/packages/app/src/pages/music/[type].jsx
new file mode 100644
index 00000000..85c7e516
--- /dev/null
+++ b/packages/app/src/pages/music/[type].jsx
@@ -0,0 +1,87 @@
+import React from "react"
+import * as antd from "antd"
+import classnames from "classnames"
+
+import { Icons, createIconRender } from "components/Icons"
+
+import Tabs from "./tabs"
+
+import "./index.less"
+
+export default class MusicDashboard extends React.Component {
+ state = {
+ activeTab: this.props.match.params.type ?? "feed"
+ }
+
+ primaryPanelRef = React.createRef()
+
+ componentDidMount() {
+ app.eventBus.emit("style.compactMode", false)
+ }
+
+ renderActiveTab() {
+ const tab = Tabs[this.state.activeTab]
+
+ if (!tab) {
+ return
+ }
+
+ return React.createElement(tab.component)
+ }
+
+ handleTabChange = (key) => {
+ if (this.state.activeTab === key) return
+
+ // set to primary panel fade-opacity-leave class
+ this.primaryPanelRef.current.classList.add("fade-opacity-leave")
+
+ setTimeout(() => {
+ this.setState({ activeTab: key })
+ // update location
+ app.history.replace(key)
+ }, 200)
+
+ // remove fade-opacity-leave class after animation
+ setTimeout(() => {
+ this.primaryPanelRef.current.classList.remove("fade-opacity-leave")
+ }, 300)
+ }
+
+ render() {
+ return
+
+ {this.renderActiveTab()}
+
+
+
+
+
Music
+
this.handleTabChange(key)}
+ >
+ {Object.keys(Tabs).map((key) => {
+ const tab = Tabs[key]
+
+ return
+ {tab.title}
+
+ })}
+
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/music/components/playlists/index.jsx b/packages/app/src/pages/music/components/playlists/index.jsx
new file mode 100644
index 00000000..6e870e1d
--- /dev/null
+++ b/packages/app/src/pages/music/components/playlists/index.jsx
@@ -0,0 +1,7 @@
+import React from "react"
+
+export default () => {
+ return
+
+
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/music/components/spaces/index.jsx b/packages/app/src/pages/music/components/spaces/index.jsx
new file mode 100644
index 00000000..8bdb865a
--- /dev/null
+++ b/packages/app/src/pages/music/components/spaces/index.jsx
@@ -0,0 +1,7 @@
+import React from "react"
+
+export default () => {
+ return
+
+
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/music/index.jsx b/packages/app/src/pages/music/index.jsx
index b7f42752..6dcd7dc6 100644
--- a/packages/app/src/pages/music/index.jsx
+++ b/packages/app/src/pages/music/index.jsx
@@ -1,7 +1,7 @@
import React from "react"
-export default (props) => {
- return
+export default () => {
+ app.setLocation("/music/feed")
-
+ return <>>
}
\ No newline at end of file
diff --git a/packages/app/src/pages/music/index.less b/packages/app/src/pages/music/index.less
new file mode 100644
index 00000000..95102cf6
--- /dev/null
+++ b/packages/app/src/pages/music/index.less
@@ -0,0 +1,47 @@
+.musicDashboard {
+ display: grid;
+
+ grid-template-columns: 3fr 1fr;
+ grid-template-rows: 1fr;
+ grid-column-gap: 10px;
+ grid-row-gap: 0px;
+
+ width: 100%;
+
+ padding-left: 30px;
+
+ .panel {
+ position: sticky;
+ top: 0;
+
+ height: fit-content;
+
+ display: flex;
+ flex-direction: column;
+
+ align-items: center;
+
+ >div {
+ margin-bottom: 15px;
+ }
+
+ .card {
+ background-color: var(--background-color-accent);
+ border-radius: 12px;
+ padding: 20px;
+
+ min-width: 20vw;
+
+ h1,
+ h2 {
+ font-family: "Space Grotesk", sans-serif;
+ }
+ }
+ }
+
+ .ant-menu {
+ svg {
+ margin-right: 0 !important;
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/music/tabs.jsx b/packages/app/src/pages/music/tabs.jsx
new file mode 100644
index 00000000..3e5ce2ff
--- /dev/null
+++ b/packages/app/src/pages/music/tabs.jsx
@@ -0,0 +1,15 @@
+import PlaylistsTabs from "./components/playlists"
+import SpacesTabs from "./components/spaces"
+
+export default {
+ "playlists": {
+ title: "Playlists",
+ icon: "MdLibraryMusic",
+ component: PlaylistsTabs
+ },
+ "spaces": {
+ title: "Spaces",
+ icon: "MdDeck",
+ component: SpacesTabs
+ },
+}
\ No newline at end of file