diff --git a/devcon-app/precache.js b/devcon-app/precache.js
index 08f768dfc..a95ed65c6 100644
--- a/devcon-app/precache.js
+++ b/devcon-app/precache.js
@@ -16,6 +16,11 @@ const pages = [
precacheHtml: true,
// precacheJson: true,
},
+ {
+ route: '/event',
+ precacheHtml: true,
+ // precacheJson: true,
+ },
// {
// route: '/side-events',
// precacheHtml: true,
diff --git a/devcon-app/src/assets/icons/dc-7/event-fill.svg b/devcon-app/src/assets/icons/dc-7/event-fill.svg
new file mode 100644
index 000000000..cc8a995d3
--- /dev/null
+++ b/devcon-app/src/assets/icons/dc-7/event-fill.svg
@@ -0,0 +1,3 @@
+
diff --git a/devcon-app/src/assets/icons/dc-7/event.svg b/devcon-app/src/assets/icons/dc-7/event.svg
new file mode 100644
index 000000000..8a9edcf8a
--- /dev/null
+++ b/devcon-app/src/assets/icons/dc-7/event.svg
@@ -0,0 +1,3 @@
+
diff --git a/devcon-app/src/assets/images/dc-7/venue/qsncc.png b/devcon-app/src/assets/images/dc-7/venue/qsncc.png
new file mode 100644
index 000000000..605a258ec
Binary files /dev/null and b/devcon-app/src/assets/images/dc-7/venue/qsncc.png differ
diff --git a/devcon-app/src/assets/images/dc-7/venue/venue-map.png b/devcon-app/src/assets/images/dc-7/venue/venue-map.png
new file mode 100644
index 000000000..e202f2c04
Binary files /dev/null and b/devcon-app/src/assets/images/dc-7/venue/venue-map.png differ
diff --git a/devcon-app/src/components/domain/app/Layout.tsx b/devcon-app/src/components/domain/app/Layout.tsx
index 2eeb0b426..f87995208 100644
--- a/devcon-app/src/components/domain/app/Layout.tsx
+++ b/devcon-app/src/components/domain/app/Layout.tsx
@@ -1,6 +1,5 @@
import React, { PropsWithChildren, useState, useEffect, useRef, RefObject, ReactNode } from 'react'
import css from './app.module.scss'
-import useGetElementHeight from 'hooks/useGetElementHeight'
import AppIcon from 'assets/icons/app-icons-1.svg'
import TilesIcon from 'assets/icons/dc-7/tiles.svg'
import TilesFillIcon from 'assets/icons/dc-7/tiles-fill.svg'
@@ -23,6 +22,8 @@ import { useRecoilState } from 'recoil'
import ChevronRightIcon from 'assets/icons/chevron_right.svg'
import { devaBotVisibleAtom, selectedSessionAtom, sessionIdAtom, useSeenNotifications } from 'pages/_app'
import { useAccountContext } from 'context/account-context'
+import IconVenue from 'assets/icons/dc-7/event.svg'
+import IconFilledVenue from 'assets/icons/dc-7/event-fill.svg'
import ArrowBackIcon from 'assets/icons/arrow_left.svg'
import { selectedSpeakerAtom } from 'pages/_app'
@@ -326,6 +327,13 @@ const navItems = (isLoggedIn: boolean, pathname: string) => [
href: '/speakers',
size: 18,
},
+ {
+ label: 'Event',
+ icon: pathname === '/event' ? IconFilledVenue : IconVenue,
+ href: '/event',
+ isActive: pathname.startsWith('/event'),
+ size: 18,
+ },
{
icon: pathname.startsWith('/account') ? UserFillIcon : UserIcon,
label: isLoggedIn ? 'Account' : 'Log In',
@@ -333,12 +341,6 @@ const navItems = (isLoggedIn: boolean, pathname: string) => [
isActive: pathname.startsWith('/account'),
size: 22,
},
- // {
- // icon: TicketIcon,
- // label: 'Venue',
- // href: '/venue',
- // size: 18,
- // },
]
export const useWindowWidth = () => {
diff --git a/devcon-app/src/components/domain/app/dc7/event/event.module.scss b/devcon-app/src/components/domain/app/dc7/event/event.module.scss
new file mode 100644
index 000000000..6da5cd23c
--- /dev/null
+++ b/devcon-app/src/components/domain/app/dc7/event/event.module.scss
@@ -0,0 +1,35 @@
+.panzoom {
+ overflow: hidden;
+ position: relative;
+ height: min(60vh, 600px);
+ width: 100%;
+ cursor: grab;
+
+ .image {
+ user-select: none;
+ width: 100%;
+ height: 100%;
+ max-height: 100%;
+ max-width: 100%;
+ position: relative;
+ overflow: hidden;
+
+
+ img {
+ object-fit: contain;
+ width: 100%;
+ max-height: 100%;
+ }
+ }
+ }
+
+ .panzoom-cover {
+ @extend .panzoom;
+ cursor: auto;
+
+ .image {
+ img {
+ object-fit: cover !important;
+ }
+ }
+ }
\ No newline at end of file
diff --git a/devcon-app/src/components/domain/app/dc7/event/index.tsx b/devcon-app/src/components/domain/app/dc7/event/index.tsx
new file mode 100644
index 000000000..0248743e4
--- /dev/null
+++ b/devcon-app/src/components/domain/app/dc7/event/index.tsx
@@ -0,0 +1,232 @@
+import React from 'react'
+import cn from 'classnames'
+import ListIcon from 'assets/icons/list.svg'
+import TimelineIcon from 'assets/icons/timeline.svg'
+import VenueMap from 'assets/images/dc-7/venue/venue-map.png'
+import VenueLogo from 'assets/images/dc-7/venue/qsncc.png'
+import Image from 'next/image'
+import css from './event.module.scss'
+import { usePanzoom, PanzoomControls } from './panzoom'
+import { StandalonePrompt } from 'lib/components/ai/standalone-prompt'
+import { useRecoilState } from 'recoil'
+import { devaBotVisibleAtom } from 'pages/_app'
+import { Link } from 'components/common/link'
+
+// import Panzoom, { PanZoom } from 'panzoom'
+
+export const cardClass =
+ 'flex flex-col lg:border lg:border-solid lg:border-[#E4E6EB] rounded-3xl relative lg:bg-[#fbfbfb]'
+
+// const Switch = () => {
+// return (
+//
+//
setTimelineView(false)}
+// >
+//
+// Floor Map
+//
+//
{
+// setTimelineView(true)
+
+// // if (Object.keys(sessionFilter.day).length === 0) {
+// // setSessionFilter({
+// // ...sessionFilter,
+// // day: { 'Nov 12': true },
+// // })
+// // }
+// }}
+// >
+//
+// Timeline View
+//
+//
+// )
+// }
+
+const List = (props: any) => {
+ console.log(props)
+
+ return (
+
+
Floors & Rooms
+
+
+ {/*
STAGES
*/}
+ {props.floors.map((floor: any) => {
+ let floorName = floor
+
+ if (floor === 'G') floorName = 'G — Ground Floor'
+ if (floor === '1') floorName = 'L1 — First Floor'
+ if (floor === '2') floorName = 'L2 — Second Floor'
+
+ const rooms = props.rooms
+ .filter((room: any) => room.info === floor)
+ .sort((a: any, b: any) => {
+ if (a.name === 'Main Stage') return -1
+ if (b.name === 'Main Stage') return 1
+
+ if (a.name.toLowerCase().startsWith('stage')) {
+ if (b.name.toLowerCase().startsWith('stage')) {
+ return a.name.localeCompare(b.name)
+ }
+ return -1
+ }
+
+ if (b.name.toLowerCase().startsWith('stage')) return 1
+
+ if (a.name.toLowerCase().startsWith('breakout')) {
+ if (b.name.toLowerCase().startsWith('breakout')) {
+ return a.name.localeCompare(b.name)
+ }
+ return 1
+ }
+
+ if (b.name.toLowerCase().startsWith('breakout')) return -1
+
+ return a.name.localeCompare(b.name)
+ })
+
+ return (
+
+
+ {/*
*/}
+
{floorName}
+
+
+ {rooms.map((room: any) => {
+ const getColor = (roomName: string) => {
+ const name = roomName.toLowerCase()
+ if (name.startsWith('classroom')) return '#14B8A6' // teal
+ if (name.includes('decompression') || name.includes('music stage')) return '#22C55E' // green
+ if (name.startsWith('stage') || name === 'main stage') return '#7D52F4' // purple
+ if (name.startsWith('breakout')) return '#EF4444' // red
+ return '#7D52F4' // default purple
+ }
+
+ console.log(room.name)
+
+ let roomName = room.name
+
+ if (roomName === 'Main Stage') {
+ roomName += ' — Masks'
+ }
+
+ if (roomName === 'Stage 1') {
+ roomName += ' — Fans'
+ }
+
+ if (roomName === 'Stage 2') {
+ roomName += ' — Lanterns'
+ }
+
+ if (roomName === 'Stage 3') {
+ roomName += ' — Fabrics'
+ }
+
+ if (roomName === 'Stage 4') {
+ roomName += ' — Leaf'
+ }
+
+ if (roomName === 'Stage 5') {
+ roomName += ' — Hats'
+ }
+
+ if (roomName === 'Stage 6') {
+ roomName += ' — Kites'
+ }
+
+ if (roomName === 'Keynote') return null
+
+ return (
+
+
+
+ Click to view sessions in this room
+
+
+ )
+ })}
+
+ )
+ })}
+
+
+ )
+}
+
+export const Venue = (props: any) => {
+ const pz = usePanzoom()
+ const [_, setDevaBotVisible] = useRecoilState(devaBotVisibleAtom)
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
Zoom/drag for a closer view
+
+
+
+
+
+
+
Ask Devai
+
+
setDevaBotVisible('Tell me where I can go to take a nap')}>
+ Tell me where I can go to take a nap
+
+
setDevaBotVisible('What can I do at the music stage?')}>
+ What can I do at the music stage?
+
+
setDevaBotVisible('What is the decompression room?')}>
+ What is the decompression room?
+
+
setDevaBotVisible('What are breakout rooms?')}>
+ What are breakout rooms?
+
+
+ >
+ )
+}
diff --git a/devcon-app/src/components/domain/app/dc7/event/panzoom.tsx b/devcon-app/src/components/domain/app/dc7/event/panzoom.tsx
new file mode 100644
index 000000000..8a19629b7
--- /dev/null
+++ b/devcon-app/src/components/domain/app/dc7/event/panzoom.tsx
@@ -0,0 +1,287 @@
+import React from 'react'
+// import { Link } from 'components/common/link'
+import css from './event.module.scss'
+import { Room } from 'types/Room'
+// import { AppNav } from 'components/domain/app/navigation'
+// import { Search } from 'components/common/filter/Filter'
+// import filterCss from 'components/domain/app/app-filter.module.scss'
+// import Image from 'next/image'
+// import VenueMap from 'assets/images/venue-map/Venue.png'
+import Panzoom, { PanZoom } from 'panzoom'
+import IconPlus from 'assets/icons/plus.svg'
+import IconMinus from 'assets/icons/minus.svg'
+import { CircleIcon } from 'lib/components/circle-icon'
+import { motion } from 'framer-motion'
+
+// import ListIcon from 'assets/icons/list.svg'
+// import TileIcon from 'assets/icons/layers.svg'
+// import { getFloorImage } from './Floor'
+// import { defaultSlugify } from 'utils/formatting'
+// import { RoomList } from './roomlist'
+// import { CollapsedSection, CollapsedSectionContent, CollapsedSectionHeader } from 'components/common/collapsed-section'
+// import { useIsStandalone } from 'utils/pwa-link'
+// import imageAgora from './agora.png'
+// import { Button } from 'components/common/button'
+// import { useRouter } from 'next/router'
+// import IconInformation from 'assets/icons/info.svg'
+// import IconDirections from 'assets/icons/directions.svg'
+
+interface Props {
+ rooms: Array
+ floors: Array
+}
+
+export const PanzoomControls = (props: { pz: PanZoom | null }) => {
+ const onClick = (e: any) => {
+ e.nativeEvent.preventDefault()
+
+ const scene = document.getElementById('image-container')
+
+ if (!scene || !props.pz) return
+
+ const rect = scene.getBoundingClientRect()
+ // const cx = rect.x + rect.width / 2
+ // const cy = rect.y + rect.height / 2
+ const cx = scene.offsetLeft + rect.width / 2
+ const cy = scene.offsetTop + rect.height / 2
+ const isZoomIn = e.currentTarget.id === 'zoomIn'
+ const zoomBy = isZoomIn ? 2 : 0.5
+ props.pz.smoothZoom(cx, cy, zoomBy)
+ }
+
+ return (
+
+ {/*
+
+
+
+
+ */}
+
+
+
+
+
+
+
+ )
+
+ return (
+
+ )
+}
+
+export const usePanzoom = () => {
+ const [panzoomInstance, setPanzoomInstance] = React.useState(null)
+
+ React.useEffect(() => {
+ const scene = document.getElementById('venue-image')
+
+ if (scene) {
+ const panzoomInstance = Panzoom(scene, {
+ bounds: true,
+ boundsPadding: 0.1,
+ // maxZoom: 1,
+ minZoom: 0.5,
+ beforeWheel: function (e) {
+ // allow wheel-zoom only if altKey is down. Otherwise - ignore
+ var shouldIgnore = !e.ctrlKey
+ return shouldIgnore
+ },
+ })
+
+ setPanzoomInstance(panzoomInstance)
+
+ return () => {
+ setPanzoomInstance(null)
+ panzoomInstance.dispose()
+ }
+ }
+ }, [])
+
+ return panzoomInstance
+}
+
+// export const Venue = (props: Props) => {
+// const router = useRouter()
+// const isStandalone = useIsStandalone()
+// const [openFloors, setOpenFloors] = React.useState({} as { [key: string]: boolean })
+// const [listView, setListView] = React.useState(false)
+// const [search, setSearch] = React.useState('')
+
+// const filteredFloors = (
+// search
+// ? props.floors.filter(floor => {
+// if (floor.toLowerCase().includes(search.toLowerCase())) return true
+
+// const roomsByFloor = props.rooms.filter(i => i.info === floor)
+
+// return roomsByFloor.some(
+// room => room.name.toLowerCase().includes(search) || room.description.toLowerCase().includes(search)
+// )
+// })
+// : props.floors
+// ).sort((a, b) => b.localeCompare(a))
+// const basement = filteredFloors.shift()
+// if (basement) filteredFloors.push(basement)
+
+// function onSearch(nextVal: any) {
+// setSearch(nextVal)
+
+// if (!nextVal) {
+// setOpenFloors({})
+// } else {
+// filteredFloors.forEach(floor =>
+// setOpenFloors(openFloors => {
+// return {
+// ...openFloors,
+// [floor]: true,
+// }
+// })
+// )
+// }
+// }
+
+// return (
+// <>
+// {
+// return (
+// <>
+//
+//
+//
+
+//
+//
+//
+// >
+// )
+// }}
+// />
+
+// {/* */}
+
+//
+//
+//
+//
+
+//
+//
+//
+//
+//
+//
+//
+
+//
+// {/*
Floors
*/}
+
+//
+//
+//
Agora Bogotá Convention Center
+//
+//
+//
+//
+//
+//
+
+// {listView &&
+// filteredFloors.map(floor => {
+// const roomsByFloor = props.rooms.filter(i => i.info === floor)
+
+// return (
+//
{
+// const isOpen = openFloors[floor]
+// const nextOpenState = {
+// ...openFloors,
+// [floor]: true,
+// }
+
+// if (isOpen) {
+// delete nextOpenState[floor]
+// }
+
+// setOpenFloors(nextOpenState)
+// }}
+// >
+//
+// {floor}
+//
+//
+//
+//
+//
+//
+//
+// )
+// })}
+
+// {!listView &&
+// filteredFloors.map(floor => {
+// return (
+//
+//
{floor}
+//
{getFloorImage(floor, 'fill')}
+//
+// )
+// })}
+//
+// >
+// )
+// }
diff --git a/devcon-app/src/components/domain/app/dc7/sessions/index.tsx b/devcon-app/src/components/domain/app/dc7/sessions/index.tsx
index c7e959ef1..85671dca5 100644
--- a/devcon-app/src/components/domain/app/dc7/sessions/index.tsx
+++ b/devcon-app/src/components/domain/app/dc7/sessions/index.tsx
@@ -38,6 +38,7 @@ import CollapsedIcon from 'assets/icons/collapsed.svg'
import ExpandedIcon from 'assets/icons/expanded.svg'
import {
devaBotVisibleAtom,
+ initialFilterState,
selectedSessionAtom,
selectedSessionSelector,
sessionFilterAtom,
@@ -108,7 +109,7 @@ const useSessionFilter = (sessions: SessionType[], event: any) => {
if (typeof window === 'undefined') return
const searchParams = new URLSearchParams(window.location.search)
- const newFilter = { ...sessionFilter }
+ const newFilter = { ...initialFilterState } as any //...sessionFilter }
searchParams.forEach((value, key) => {
if (key in newFilter) {
diff --git a/devcon-app/src/pages/_app.tsx b/devcon-app/src/pages/_app.tsx
index 5464e21da..12927994f 100644
--- a/devcon-app/src/pages/_app.tsx
+++ b/devcon-app/src/pages/_app.tsx
@@ -21,6 +21,11 @@ import { Toaster } from 'lib/components/ui/toaster'
import { usePathname } from 'next/navigation'
import { DataProvider } from 'context/data'
+export const selectedEventTabAtom = atom<'venue' | 'information' | 'contact' | 'directions'>({
+ key: 'selectedEventTab',
+ default: 'venue',
+})
+
// This selector is used to get the full speaker object from the selectedSpeakerAtom - useful for /speakers pages where the full object is needed - this can be impartial if the speaker was linked from a session (where the speakers don't recursively have the session objects)
export const selectedSpeakerSelector = selector({
key: 'selectedSpeakerSelector',
diff --git a/devcon-app/src/pages/event/index.tsx b/devcon-app/src/pages/event/index.tsx
new file mode 100644
index 000000000..65a7a9e8b
--- /dev/null
+++ b/devcon-app/src/pages/event/index.tsx
@@ -0,0 +1,96 @@
+import { AppLayout } from 'components/domain/app/Layout'
+import React from 'react'
+import { fetchEvent, fetchRooms, fetchSessionsByRoom } from 'services/event-data'
+import { SEO } from 'components/domain/seo'
+import cn from 'classnames'
+import { useRecoilValue, useRecoilState } from 'recoil'
+import { selectedEventTabAtom } from 'pages/_app'
+import { Venue } from 'components/domain/app/dc7/event'
+
+const activeClass = '!border-[#7D52F4] !text-[#7D52F4] '
+const tabClass =
+ 'cursor-pointer pb-2 px-0.5 border-b-2 border-solid border-transparent transition-all duration-300 select-none'
+
+const Tabs = () => {
+ const [selectedEventTab, setSelectedEventTab] = useRecoilState(selectedEventTabAtom)
+
+ // return null
+
+ return (
+
+
setSelectedEventTab('venue')}
+ className={cn(tabClass, selectedEventTab === 'venue' && activeClass)}
+ >
+ Venue Map
+
+ {/*
setSelectedEventTab('information')}
+ className={cn(tabClass, selectedEventTab === 'information' && activeClass)}
+ >
+ Information
+
*/}
+ {/*
setSelectedEventTab('contact')}
+ className={cn(tabClass, selectedEventTab === 'contact' && activeClass)}
+ >
+ Contact
+
+
setSelectedEventTab('directions')}
+ className={cn(tabClass, selectedEventTab === 'directions' && activeClass)}
+ >
+ Directions
+
*/}
+
+ )
+}
+
+const VenuePage = (props: any) => {
+ const floorOrder: any = { G: 0, '1': 1, '2': 2 }
+ const uniqueFloors = [...new Set(props.rooms.map((room: any) => room.info))].sort(
+ (a: any, b: any) => floorOrder[a] - floorOrder[b]
+ )
+
+ return (
+
+
+
+
+
+ )
+}
+
+export default VenuePage
+
+// export async function getStaticPaths() {
+// const rooms = await fetchRooms()
+// const paths = rooms.map(i => {
+// return { params: { id: i.id } }
+// })
+
+// return {
+// paths,
+// fallback: false,
+// }
+// }
+
+export async function getStaticProps(context: any) {
+ // const id = context.params.id
+ // const room = (await fetchRooms()).find(i => i.id === id)
+
+ // if (!room) {
+ // return {
+ // props: null,
+ // notFound: true,
+ // }
+ // }
+
+ return {
+ props: {
+ event: await fetchEvent(),
+ rooms: await fetchRooms(),
+ // sessions: await fetchSessionsByRoom(id),
+ },
+ }
+}
diff --git a/devcon-app/src/pages/venue/room b/devcon-app/src/pages/venue/room
deleted file mode 100644
index 033431bbe..000000000
--- a/devcon-app/src/pages/venue/room
+++ /dev/null
@@ -1,50 +0,0 @@
-import { AppLayout } from 'components/domain/app/Layout'
-import { Room } from 'components/domain/app/venue'
-import React from 'react'
-import { fetchEvent, fetchRooms, fetchSessionsByRoom } from 'services/event-data'
-import { DEFAULT_APP_PAGE } from 'utils/constants'
-import { SEO } from 'components/domain/seo'
-
-const VenuePage = (props: any) => {
- return (
-
-
-
-
- )
-}
-
-export default VenuePage
-
-export async function getStaticPaths() {
- const rooms = await fetchRooms()
- const paths = rooms.map(i => {
- return { params: { id: i.id } }
- })
-
- return {
- paths,
- fallback: false,
- }
-}
-
-export async function getStaticProps(context: any) {
- const id = context.params.id
- const room = (await fetchRooms()).find(i => i.id === id)
-
- if (!room) {
- return {
- props: null,
- notFound: true,
- }
- }
-
- return {
- props: {
- page: DEFAULT_APP_PAGE,
- event: await fetchEvent(),
- room,
- sessions: await fetchSessionsByRoom(id),
- },
- }
-}
\ No newline at end of file
diff --git a/lib/components/circle-icon/index.tsx b/lib/components/circle-icon/index.tsx
index ec5a345e0..98c84fef7 100644
--- a/lib/components/circle-icon/index.tsx
+++ b/lib/components/circle-icon/index.tsx
@@ -20,6 +20,7 @@ export const CircleIcon = (props: any) => {
const body = (