Skip to content

Commit a3b1427

Browse files
authored
Merge branch 'main' into erik/add-footer
2 parents 494ef0f + 06d657d commit a3b1427

35 files changed

+4661
-216
lines changed

public/map_icons/disability.png

1.38 KB
Loading

public/map_icons/door.png

1.16 KB
Loading

public/map_icons/exit.png

1.17 KB
Loading

public/map_icons/stair.png

1019 Bytes
Loading

public/map_icons/wc.png

1.46 KB
Loading

src/app/student/_components/MultiSelect.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import { ScrollArea } from "@/components/ui/scroll-area"
1414
import { ChevronDown, X } from "lucide-react"
1515
import { useRef, useState } from "react"
1616

17+
// TODO:
18+
// - keyboard navigation?
19+
1720
export default function MultiSelect({
1821
filter,
1922
onChange
@@ -108,7 +111,7 @@ export default function MultiSelect({
108111
role="option"
109112
aria-selected={isSelected(item)}
110113
key={item.id}
111-
className="flex min-w-32 cursor-default items-center gap-2 p-2 pl-3 hover:bg-emerald-950 hover:text-melon-700"
114+
className="flex min-w-32 cursor-default items-center gap-2 p-2 pl-3 text-left hover:bg-emerald-950 hover:text-melon-700"
112115
onClick={() => onSelectionChange(item)}>
113116
<Checkbox checked={isSelected(item)} tabIndex={-1} />
114117
<span>{item.name}</span>

src/app/student/events/_components/EventDetails.tsx

+13-9
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export default function EventDetails({
3737
event: Event
3838
className?: string
3939
}) {
40+
const today = Date.now() / 1000
4041
return (
4142
<div className={cn("mx-auto max-w-[600px] lg:max-w-[1000px]", className)}>
4243
<Page.Header>{event.name}</Page.Header>
@@ -68,12 +69,10 @@ export default function EventDetails({
6869
label="Time"
6970
value={`${formatTimestampAsTime(event.event_start)} - ${formatTimestampAsTime(event.event_end)}`}
7071
icon={<Clock size={16} />}></InfoBoxItem>
71-
7272
{/* Separator */}
7373
{(event.food || event.fee) && (
7474
<div className="h-[1px] w-full bg-stone-400"></div>
7575
)}
76-
7776
{/* Bottom row */}
7877
<InfoBoxItem
7978
label="Food"
@@ -83,25 +82,30 @@ export default function EventDetails({
8382
label="Fee"
8483
value={`${event.fee} kr`}
8584
icon={<Coins size={16} />}></InfoBoxItem>
86-
8785
{event.open_for_signup_student && event.registration_end && (
8886
<p className="-mb-1 mt-3 text-xs text-stone-400">
8987
Registration closes{" "}
9088
{formatTimestampAsDate(event.registration_end)}
9189
</p>
9290
)}
93-
9491
{/* Signup */}
95-
{event.open_for_signup_student ? (
92+
{event.open_for_signup_student &&
93+
today < (event.registration_end ?? event.event_start) ? (
9694
<Link href={event.signup_link ?? ""}>
9795
<Button className="w-full">Sign Up</Button>
9896
</Link>
9997
) : (
10098
<Button disabled>
101-
Registration closed{" "}
102-
{event.registration_end
103-
? formatTimestampAsDate(event.registration_end)
104-
: ""}
99+
{today < (event.registration_end ?? event.event_start) ? (
100+
<> Signup opening soon ! </>
101+
) : (
102+
<>
103+
Registration closed
104+
{event.registration_end
105+
? formatTimestampAsDate(event.registration_end)
106+
: ""}
107+
</>
108+
)}
105109
</Button>
106110
)}
107111
</div>

src/app/student/events/_components/EventItem.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export function EventItem({ event }: { event: Event }) {
4343
<Image
4444
width={200}
4545
height={200}
46-
className="h-full max-h-48 w-full rounded-t-lg object-cover sm:h-48 sm:w-48 sm:rounded-l-lg sm:rounded-tr-none "
46+
className="h-full max-h-48 w-full rounded-t-lg object-contain sm:h-48 sm:w-48 sm:rounded-l-lg sm:rounded-tr-none "
4747
src={image_url}
4848
alt=""
4949
/>

src/app/student/map/_components/LocationSelect.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default function SelectLocation({
1717
setActiveBoothId: (id: number | null) => void
1818
}) {
1919
return (
20-
<div className="absolute top-2 justify-self-center rounded-full sm:right-2">
20+
<div className="absolute right-2 top-2 justify-self-center rounded-full">
2121
<Select
2222
value={locationId}
2323
onValueChange={(id: LocationId) => {

src/app/student/map/_components/MainView.tsx

+47-6
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22

33
import LocationSelect from "@/app/student/map/_components/LocationSelect"
44
import { MapComponent } from "@/app/student/map/_components/MapComponent"
5+
import { QuestionnaireForm } from "@/app/student/map/_components/QuestionnaireForm"
56
import Sidebar from "@/app/student/map/_components/Sidebar"
67
import EditorMapComponent from "@/app/student/map/editor/EditorMapComponent"
78
import { Exhibitor } from "@/components/shared/hooks/api/useExhibitors"
9+
import { useScreenSize } from "@/components/shared/hooks/useScreenSize"
10+
import { useSurveyData } from "@/components/shared/hooks/useSurveyData"
11+
import Modal from "@/components/shared/Modal"
812
import { Button } from "@/components/ui/button"
9-
import { useSearchParams } from "next/navigation"
10-
import { useState } from "react"
13+
import { Filter } from "lucide-react"
14+
import { useRouter, useSearchParams } from "next/navigation"
15+
import { useEffect, useState } from "react"
1116
import { Booth, BoothID, BoothMap } from "../lib/booths"
1217
import {
1318
defaultLocation,
@@ -25,12 +30,16 @@ export default function MainView({
2530
boothsById: BoothMap
2631
exhibitorsById: Map<number, Exhibitor>
2732
}) {
28-
// url: /student/map?floor=[nymble/1|nymble/2|nymble/3|library]&lat=[number]&lng=[number]&zoom=[number]
29-
// if floor is not provided or is invalid, default to nymble/1
33+
// url: /student/map?floor=[nymble/2|nymble/3|library]&lat=[number]&lng=[number]&zoom=[number]
34+
// if floor is not provided or is invalid, default to nymble/2
3035
// if lat, lng or zoom is not provided, default to location center
36+
3137
const searchParams = useSearchParams()
38+
const { width } = useScreenSize()
39+
const router = useRouter()
40+
const { surveyData, isSurveyDataLoaded } = useSurveyData()
3241

33-
const floorUrlString = searchParams.get("floor") ?? "nymble/1"
42+
const floorUrlString = searchParams.get("floor") ?? "nymble/2"
3443
const [locationId, setLocationId] = useState<LocationId>(
3544
validLocationId(floorUrlString) ? floorUrlString : defaultLocation.id
3645
)
@@ -51,9 +60,27 @@ export default function MainView({
5160
const [filteredBooths, setFilteredBooths] = useState<Booth[]>(
5261
Array.from(boothsById.values())
5362
)
54-
63+
const [openSurvey, setOpenSurvey] = useState(false)
5564
const [editorMode, setEditorMode] = useState(false)
5665

66+
useEffect(() => {
67+
if (isSurveyDataLoaded) setOpenSurvey(!surveyData)
68+
}, [surveyData, isSurveyDataLoaded])
69+
70+
useEffect(() => {
71+
// A new survey page for filter when using mobile
72+
if (openSurvey && width && width < 768) {
73+
router.push("/student/map/survey")
74+
}
75+
}, [width])
76+
77+
const handleClickFilter = () => {
78+
setOpenSurvey(prev => !prev)
79+
if (width && width < 768) {
80+
router.push("/student/map/survey")
81+
}
82+
}
83+
5784
return (
5885
<div className="relative flex h-full w-full">
5986
{!editorMode ? (
@@ -96,6 +123,20 @@ export default function MainView({
96123
</Button>
97124
)}
98125

126+
{/* Questions Modal for filter when using PC*/}
127+
<Modal
128+
open={openSurvey}
129+
setOpen={setOpenSurvey}
130+
className="max-w-[780px] bg-gradient-to-br from-emerald-950 via-stone-900 to-stone-900 p-0">
131+
<QuestionnaireForm onClose={() => setOpenSurvey(false)} />
132+
</Modal>
133+
134+
<Button
135+
className="absolute top-2 ml-2 justify-self-center rounded-full sm:right-2 sm:top-20"
136+
onClick={handleClickFilter}>
137+
<Filter />
138+
</Button>
139+
99140
<LocationSelect
100141
locationId={locationId}
101142
setLocationId={setLocationId}

src/app/student/map/_components/MapComponent.tsx

+70-46
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { BoothPopup } from "@/app/student/map/_components/BoothPopup"
22
import {
33
BoothID,
4-
geoJsonBoothDataByLocation,
5-
geoJsonBuildingData
4+
geoJsonBoothDataByLocation
65
} from "@/app/student/map/lib/booths"
76
import { Location } from "@/app/student/map/lib/locations"
7+
import { useFeatureState } from "@/components/shared/hooks/useFeatureState"
8+
import { useGeoJsonPlanData } from "@/components/shared/hooks/useGeoJsonPlanData"
89
import "maplibre-gl/dist/maplibre-gl.css"
9-
import { MutableRefObject, useEffect, useMemo, useRef, useState } from "react"
10+
import { useEffect, useMemo, useRef, useState } from "react"
1011
import {
1112
Layer,
1213
MapLayerMouseEvent,
@@ -16,41 +17,17 @@ import {
1617
} from "react-map-gl/maplibre"
1718
import { BoothMap, GeoJsonBooth } from "../lib/booths"
1819
import {
20+
addMapIconAssets,
1921
backgroundLayerStyle,
2022
boothLayerStyle,
21-
buildingLayerStyle
23+
buildingLayerStyle,
24+
lineLayerStyle,
25+
roomLayerStyle,
26+
routeLayerStyle,
27+
symbolLayerStyle
2228
} from "../lib/config"
2329
import { BoothMarker } from "./BoothMarker"
2430

25-
// Keep mapbox feature state in sync with component state
26-
// to allow for styling of the features
27-
function useFeatureState(
28-
mapRef: MutableRefObject<MapRef | null>,
29-
boothIds: BoothID[],
30-
stateKey: "active" | "hover" | "filtered"
31-
) {
32-
useEffect(() => {
33-
const map = mapRef.current
34-
if (map == null || boothIds.length === 0) return
35-
36-
for (const boothId of boothIds) {
37-
map.setFeatureState(
38-
{ source: "booths", id: boothId },
39-
{ [stateKey]: true }
40-
)
41-
}
42-
43-
return () => {
44-
for (const boothId of boothIds) {
45-
map.setFeatureState(
46-
{ source: "booths", id: boothId },
47-
{ [stateKey]: false }
48-
)
49-
}
50-
}
51-
}, [boothIds, stateKey])
52-
}
53-
5431
export function MapComponent({
5532
boothsById,
5633
location,
@@ -81,17 +58,28 @@ export function MapComponent({
8158
center: [longitude, latitude],
8259
zoom: zoom
8360
})
84-
})
61+
setMarkerScale(0.6)
62+
}, [location])
63+
64+
useEffect(() => {
65+
// Load icon assets for points location
66+
if (mapRef && !mapRef.current?.hasImage("exit-icon")) {
67+
addMapIconAssets(mapRef)
68+
}
69+
}, [mapRef.current])
70+
71+
//Change layer style data source based on selected location
72+
const [geoJsonPlanData, geoJsonPlanRoutesData, geoJsonPlanRoomsData] =
73+
useGeoJsonPlanData(location)
8574

8675
// Fly to selected booth on change
8776
useEffect(() => {
8877
if (activeBoothId == null) return
8978
const booth = boothsById.get(activeBoothId)
9079
if (!booth) return
91-
9280
mapRef.current?.flyTo({
9381
center: booth.center as [number, number],
94-
zoom: 18.5,
82+
zoom: 21,
9583
speed: 0.8
9684
})
9785
}, [activeBoothId, boothsById])
@@ -123,10 +111,14 @@ export function MapComponent({
123111
}
124112
}
125113

126-
function onBoothMouseEnter(e: MapLayerMouseEvent) {
114+
// Avoid delays in booth switching
115+
function onBoothMouseMove(e: MapLayerMouseEvent) {
127116
const feature = e.features?.[0] as GeoJsonBooth | undefined
128117
if (feature) {
129-
setHoveredBoothId(feature.properties.id)
118+
const boothId = feature.properties.id
119+
if (boothId !== hoveredBoothId) {
120+
setHoveredBoothId(boothId)
121+
}
130122
}
131123
}
132124

@@ -140,7 +132,7 @@ export function MapComponent({
140132
function onZoomChange() {
141133
const zoom = mapRef.current?.getZoom()
142134
if (zoom === undefined) return
143-
const scale = Math.max(0.3, Math.min(2, 1 + (zoom - 18) * 0.3))
135+
const scale = Math.max(0.3, Math.min(2, 1 + (zoom - 20) * 0.5))
144136
setMarkerScale(scale)
145137
}
146138

@@ -149,21 +141,38 @@ export function MapComponent({
149141
<MapboxMap
150142
ref={mapRef}
151143
onClick={onMapClick}
152-
onMouseEnter={onBoothMouseEnter}
144+
onMouseMove={onBoothMouseMove}
153145
onMouseLeave={onBoothMouseLeave}
154146
onZoom={onZoomChange}
155147
interactiveLayerIds={["booths"]}
156148
initialViewState={initialView}
157149
cursor={"auto"}
158-
minZoom={16}
159-
maxZoom={20}
150+
minZoom={17}
151+
maxZoom={22}
160152
maxBounds={[
161153
[18.063, 59.345],
162154
[18.079, 59.35]
163155
]}
164156
mapStyle="https://api.maptiler.com/maps/977e9770-60b4-4b8a-94e9-a9fa8db4c68d/style.json?key=57xj41WPFBbOEWiVSSwL">
165157
<Layer {...backgroundLayerStyle}></Layer>
166158

159+
{/** Order sensitive! */}
160+
<Source
161+
id="buildings"
162+
type="geojson"
163+
promoteId={"id"}
164+
data={geoJsonPlanData}>
165+
<Layer {...buildingLayerStyle}></Layer>
166+
</Source>
167+
168+
<Source
169+
id="rooms"
170+
type="geojson"
171+
promoteId={"id"}
172+
data={geoJsonPlanRoomsData}>
173+
<Layer {...roomLayerStyle}></Layer>
174+
</Source>
175+
167176
<Source
168177
id="booths"
169178
type="geojson"
@@ -173,15 +182,30 @@ export function MapComponent({
173182
</Source>
174183

175184
<Source
176-
id="buildings"
185+
id="nymble-plan-style"
177186
type="geojson"
178187
promoteId={"id"}
179-
data={geoJsonBuildingData}>
180-
<Layer {...buildingLayerStyle}></Layer>
188+
data={geoJsonPlanData}>
189+
<Layer {...lineLayerStyle}></Layer>
181190
</Source>
182191

183-
{markers}
192+
<Source
193+
id="nymble-plan-routes"
194+
type="geojson"
195+
promoteId={"id"}
196+
data={geoJsonPlanRoutesData}>
197+
<Layer {...routeLayerStyle}></Layer>
198+
</Source>
184199

200+
<Source
201+
id="nymble-plan-points"
202+
type="geojson"
203+
promoteId={"id"}
204+
data={geoJsonPlanData}>
205+
<Layer {...symbolLayerStyle}></Layer>
206+
</Source>
207+
208+
{markers}
185209
{activeBooth && <BoothPopup key={activeBooth.id} booth={activeBooth} />}
186210
</MapboxMap>
187211
</div>

0 commit comments

Comments
 (0)