diff --git a/packages/app/src/components/SyncRoomCard/index.jsx b/packages/app/src/components/SyncRoomCard/index.jsx
new file mode 100644
index 00000000..e6e81db0
--- /dev/null
+++ b/packages/app/src/components/SyncRoomCard/index.jsx
@@ -0,0 +1,200 @@
+import React from "react"
+import { Button, Tooltip, Badge } from "antd"
+import { Icons } from "components/Icons"
+import LiveChat from "components/LiveChat"
+import classnames from "classnames"
+
+import "./index.less"
+
+export default class SyncRoomCard extends React.Component {
+ state = {
+ roomData: {},
+ socketLatency: null,
+ owner: false,
+ chatVisible: false,
+ notReadedMessages: false,
+ }
+
+ latencyInterval = null
+
+ roomEvents = {
+ "room:joined": (data) => {
+ this.setState({
+ roomData: data,
+ })
+ },
+ "room:current-data": (data) => {
+ console.log(data)
+
+ this.setState({
+ roomData: data
+ })
+ }
+ }
+
+ chatEvents = {
+ "room:recive:message": (data) => {
+ if (!this.state.chatVisible) {
+ this.setState({
+ notReadedMessages: true
+ })
+ }
+ }
+ }
+
+ checkLatency = () => {
+ const instance = app.cores.api.instance().wsInstances.music
+
+ if (instance) {
+ this.setState({
+ socketLatency: instance.latency
+ })
+ }
+ }
+
+ componentDidMount = () => {
+ Object.keys(this.roomEvents).forEach((event) => {
+ app.cores.sync.music.eventBus.on(event, this.roomEvents[event])
+ })
+
+ // chat instance
+ const chatInstance = app.cores.api.instance().wsInstances.chat
+
+ if (chatInstance) {
+ Object.keys(this.chatEvents).forEach((event) => {
+ chatInstance.on(event, this.chatEvents[event])
+ })
+ }
+
+ this.checkLatency()
+
+ this.latencyInterval = setInterval(() => {
+ this.checkLatency()
+ }, 1000)
+ }
+
+ componentWillUnmount = () => {
+ Object.keys(this.roomEvents).forEach((event) => {
+ app.cores.sync.music.eventBus.off(event, this.roomEvents[event])
+ })
+
+ if (this.latencyInterval) {
+ clearInterval(this.latencyInterval)
+ }
+
+ // chat instance
+ const chatInstance = app.cores.api.instance().wsInstances.chat
+
+ if (chatInstance) {
+ Object.keys(this.chatEvents).forEach((event) => {
+ chatInstance.off(event, this.chatEvents[event])
+ })
+ }
+ }
+
+ leaveRoom = () => {
+ app.cores.sync.music.leaveRoom()
+ }
+
+ toogleChatVisibility = (to) => {
+ if (typeof to !== "boolean") {
+ to = !this.state.chatVisible
+ }
+
+ this.setState({
+ chatVisible: to
+ })
+
+ if (this.state.notReadedMessages && to) {
+ this.setState({
+ notReadedMessages: false
+ })
+ }
+ }
+
+ render() {
+ return <>
+
+
+
+ {this.state.roomData?.options?.title ?? "Untitled room"}
+
+
+
+
+ }
+ onClick={app.cores.sync.music.createInviteUserModal}
+ />
+
+
+ }
+ onClick={this.toogleChatVisibility}
+ />
+
+
+
+
+
+
+ {
+ Array.isArray(this.state.roomData?.connectedUsers) && this.state.roomData?.connectedUsers.map((user, index) => {
+ return
+
+ {
+ user.user_id === this.state.roomData.ownerUserId &&
+
+
+ }
+
+

+
+
+
+ })
+ }
+
+
+
+ }
+ danger
+ />
+
+
+
+
+ {
+ app.cores.api.instance().wsInstances.music.latency ?? "..."
+ }ms
+
+
+
+
+
+
+
+ >
+ }
+}
\ No newline at end of file
diff --git a/packages/app/src/components/SyncRoomCard/index.less b/packages/app/src/components/SyncRoomCard/index.less
new file mode 100644
index 00000000..22917b8a
--- /dev/null
+++ b/packages/app/src/components/SyncRoomCard/index.less
@@ -0,0 +1,191 @@
+.sync-room_card {
+ position: relative;
+
+ z-index: 300;
+
+ display: flex;
+ flex-direction: row;
+
+ align-items: center;
+
+ justify-content: space-between;
+
+ gap: 10px;
+
+ padding: 20px;
+
+ width: 100%;
+ min-width: 270px;
+
+ background-color: var(--background-color-accent);
+
+ border-radius: 8px;
+
+ box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
+
+ overflow-x: overlay;
+
+ .sync-room_actions {
+ position: sticky;
+
+ right: 0;
+ bottom: 0;
+
+ display: flex;
+ flex-direction: row;
+
+ align-items: center;
+
+ gap: 10px;
+ }
+
+ .sync-room_users {
+ display: flex;
+ flex-direction: row;
+
+ align-items: center;
+
+ gap: 6px;
+
+ // width: 100%;
+ // height: 100%;
+
+ // overflow-y: visible;
+ // overflow-x: overlay;
+
+ .sync-room_user {
+ position: relative;
+
+ display: flex;
+ flex-direction: row;
+
+ align-items: center;
+
+ gap: 5px;
+
+ animation: user-join 500ms ease-out forwards;
+
+ .sync-room_user_avatar {
+ width: 30px;
+ height: 30px;
+
+ img {
+ width: 100%;
+ height: 100%;
+
+ object-fit: cover;
+ object-position: center;
+
+ border-radius: 12px;
+
+ background-color: black;
+ }
+ }
+
+ .ownerIcon {
+ position: absolute;
+ top: 0;
+ right: 0;
+
+ transform: translate(0, -100%);
+
+ // make gold color
+ color: #FFD700;
+ }
+ }
+ }
+
+ .latency_display {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+
+ display: flex;
+ flex-direction: row;
+
+ align-items: center;
+
+ gap: 5px;
+
+ padding: 5px 10px;
+
+ border-radius: 0 8px 0 8px;
+
+ font-size: 8px;
+ font-weight: 500;
+
+ color: var(--text-color-accent);
+ }
+}
+
+.sync-room_subcard {
+ position: relative;
+ z-index: 295;
+
+ top: 0;
+ left: 0;
+
+ display: flex;
+ flex-direction: row;
+
+ align-items: center;
+ justify-content: space-between;
+
+ width: 100%;
+ max-height: 350px;
+
+ overflow-y: overlay;
+
+ padding: 10px;
+
+ background-color: var(--background-color-accent);
+
+ border-radius: 8px 8px 0 0;
+
+ box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
+
+ transition: all 250ms ease-in-out;
+
+ &.top {
+ transform: translateY(10px);
+ padding-bottom: 20px;
+
+ border-radius: 8px 8px 0 0;
+ }
+
+ &.bottom {
+ transform: translateY(-10px);
+ padding-top: 20px;
+
+ border-radius: 0 0 8px 8px;
+ }
+
+ &.hidden {
+ opacity: 0;
+ max-height: 0px;
+ }
+
+ .sync-room_share_btns {
+ display: flex;
+ flex-direction: row;
+
+ gap: 8px;
+ }
+}
+
+// animate a bounce in
+@keyframes user-join {
+ 0% {
+ transform: translateY(10px);
+ opacity: 0;
+ }
+
+ 50% {
+ transform: translateY(-10px);
+ opacity: 1;
+ }
+
+ 100% {
+ transform: translateY(0px);
+ }
+}
\ No newline at end of file