diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 400218f..eae39d5 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -3,27 +3,39 @@ @tailwind utilities; :root { - --bg-base: #f0f2f5; - --bg-panel: rgba(255, 255, 255, 0.9); + --bg-base: #f0f2f5; + --bg-panel: rgba(255, 255, 255, 0.9); --bg-panel-hover: rgba(0, 0, 0, 0.05); - + --text-primary: #121212; --text-secondary: #555555; - + --border-color: rgba(0, 0, 0, 0.15); --shadow-glow: rgba(0, 136, 255, 0.2); + + --pin-academic: #D91A4D; + --pin-food: #00995C; + --pin-social: #8E00E6; + --pin-transit: #D98200; + --pin-utility: #0088CC; } .dark { - --bg-base: #0f1115; - --bg-panel: rgba(10, 10, 12, 0.85); + --bg-base: #0f1115; + --bg-panel: rgba(10, 10, 12, 0.85); --bg-panel-hover: rgba(255, 255, 255, 0.1); - + --text-primary: #ffffff; --text-secondary: #8899A6; - + --border-color: rgba(255, 255, 255, 0.15); --shadow-glow: rgba(0, 229, 255, 0.2); + + --pin-academic: #FF3366; + --pin-food: #00FF99; + --pin-social: #B026FF; + --pin-transit: #FFD700; + --pin-utility: #00E5FF; } body { @@ -36,7 +48,9 @@ body { font-family: var(--font-chakra), sans-serif; } -p, .font-desc, .details-card p { +p, +.font-desc, +.details-card p { font-family: var(--font-nunito), sans-serif; } @@ -111,18 +125,28 @@ p, .font-desc, .details-card p { } /* --- ZONES --- */ -.zone-left, .zone-right { - pointer-events: auto; +.zone-left, +.zone-right { + pointer-events: auto; } .zone-left { - position: absolute; top: 8px; left: 8px; z-index: 20; + position: absolute; + top: 8px; + left: 8px; + z-index: 20; } .zone-right { - position: absolute; top: 8px; right: 8px; bottom: 24px; - display: flex; flex-direction: column; justify-content: space-between; - width: 44px; z-index: 20; + position: absolute; + top: 8px; + right: 8px; + bottom: 24px; + display: flex; + flex-direction: column; + justify-content: space-between; + width: 44px; + z-index: 20; } .zone-center { @@ -130,31 +154,33 @@ p, .font-desc, .details-card p { top: 8px; left: 50%; transform: translateX(-50%); - width: 100%; - max-width: 600px; - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; + width: max-content; + max-width: calc(100vw - 120px); + display: flex; + flex-direction: column; + align-items: stretch; + gap: 8px; pointer-events: none; z-index: 10; } /* --- CENTER CONTENTS --- */ .search-block { - position: relative; - width: 100%; - max-width: 350px; + position: relative; + width: 100%; margin: 0 auto; pointer-events: auto; transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); display: flex; align-items: center; } -.search-block:focus-within { transform: scale(1.05); } + +.search-block:focus-within { + transform: scale(1.05); +} .search-input { - width: 100%; + width: 100%; height: 44px; background: var(--bg-panel); backdrop-filter: blur(12px); @@ -162,129 +188,168 @@ p, .font-desc, .details-card p { border-radius: 14px; box-sizing: border-box; padding: 0 40px 0 14px; - color: var(--text-primary); font-size: 13px; font-weight: 700; + color: var(--text-primary); + font-size: 13px; + font-weight: 700; font-family: var(--font-chakra); } -.search-input:focus { outline: none; border-color: rgba(255, 255, 255, 0.3); } + +.search-input:focus { + outline: none; + border-color: rgba(255, 255, 255, 0.3); +} .search-icon-right { - position: absolute; - right: 12px; + position: absolute; + right: 12px; top: 50%; - transform: translateY(-50%); + transform: translateY(-50%); color: #aaa; pointer-events: none; z-index: 5; } .filter-row { - display: flex; + display: flex; justify-content: center; - gap: 8px; - width: 100%; + gap: 8px; + width: 100%; overflow-x: auto; - padding-top: 4px; + padding-top: 4px; padding-bottom: 4px; pointer-events: auto; } -.no-scrollbar::-webkit-scrollbar { display: none; } + +.no-scrollbar::-webkit-scrollbar { + display: none; +} .filter-chip { - padding: 6px 14px; border: 1px solid; border-radius: 18px; - font-size: 10px; font-weight: 900; white-space: nowrap; cursor: pointer; + padding: 6px 14px; + border: 1px solid; + border-radius: 18px; + font-size: 10px; + font-weight: 900; + white-space: nowrap; + cursor: pointer; font-family: var(--font-chakra); transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275), background 0.2s; } /* --- SIDE CONTROLS --- */ -.icon-button, .control-button { - width: 44px; height: 44px; +.icon-button, +.control-button { + width: 44px; + height: 44px; background: var(--bg-panel); backdrop-filter: blur(12px); border: 1px solid var(--border-color); border-radius: 12px; color: var(--text-primary); - display: flex; align-items: center; justify-content: center; + display: flex; + align-items: center; + justify-content: center; cursor: pointer; transition: transform 0.1s; font-family: var(--font-chakra); } -.icon-button:active, .control-button:active { +.icon-button:active, +.control-button:active { transform: scale(0.92); } -.tool-group { display: flex; flex-direction: column; gap: 8px; } -.bottom-align { margin-top: auto; } +.tool-group { + display: flex; + flex-direction: column; + gap: 8px; +} + +.bottom-align { + margin-top: auto; +} .zoom-stack { - display: flex; flex-direction: column; + display: flex; + flex-direction: column; background: var(--bg-panel); backdrop-filter: blur(12px); border: 1px solid var(--border-color); - border-radius: 12px; overflow: hidden; + border-radius: 12px; + overflow: hidden; +} + +.control-button { + border: none; + border-radius: 0; +} + +.divider { + height: 1px; + background: var(--border-color); + width: 80%; + margin: 0 auto; } -.control-button { border: none; border-radius: 0; } -.divider { height: 1px; background: var(--border-color); width: 80%; margin: 0 auto; } .transit-system-container { - position: relative; - margin-top: 12px; - display: flex; - align-items: center; + position: relative; + margin-top: 12px; + display: flex; + align-items: center; } .transit-btn.active { - background: rgba(0, 229, 255, 0.15); - color: var(--neon-blue, #00E5FF); - border-color: var(--neon-blue, #00E5FF); + background: rgba(0, 229, 255, 0.15); + color: var(--neon-blue, #00E5FF); + border-color: var(--neon-blue, #00E5FF); } .extruded-menu { - position: absolute; - left: 100%; - margin-left: 12px; - display: flex; - gap: 8px; - background: var(--bg-panel); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - border: 1px solid var(--border-color); - border-radius: 20px; - padding: 6px; - animation: extrudeRight 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; + position: absolute; + left: 100%; + margin-left: 12px; + display: flex; + gap: 8px; + background: var(--bg-panel); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid var(--border-color); + border-radius: 20px; + padding: 6px; + animation: extrudeRight 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; } .route-node { - width: 32px; - height: 32px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-family: var(--font-chakra), sans-serif; - font-weight: 700; - font-size: 14px; - cursor: pointer; - border: 1px solid transparent; - transition: all 0.2s ease; + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-family: var(--font-chakra), sans-serif; + font-weight: 700; + font-size: 14px; + cursor: pointer; + border: 1px solid transparent; + transition: all 0.2s ease; } .route-node:hover { - transform: scale(1.1); - background: var(--bg-panel-hover) !important; - color: var(--text-primary) !important; + transform: scale(1.1); + background: var(--bg-panel-hover) !important; + color: var(--text-primary) !important; } @keyframes extrudeRight { - from { - opacity: 0; - transform: translateX(-15px) scale(0.9); - } - to { - opacity: 1; - transform: translateX(0) scale(1); - } + from { + opacity: 0; + transform: translateX(-15px) scale(0.9); + } + + to { + opacity: 1; + transform: translateX(0) scale(1); + } } /* --- HUD & ANIMATIONS --- */ @@ -294,7 +359,8 @@ p, .font-desc, .details-card p { border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 20px; padding: 24px; - width: 100%; max-width: 400px; + width: 100%; + max-width: 400px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5); animation: slideUp 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); } @@ -306,10 +372,65 @@ p, .font-desc, .details-card p { cursor: pointer; transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); } -.lock-button:active { transform: scale(0.95); } -@media (max-width: 768px) { .zone-left, .profile-btn, .theme-toggle { display: none; } } +.lock-button:active { + transform: scale(0.95); +} + +@media (max-width: 768px) { + + .profile-btn, + .theme-toggle { + display: none; + } + + .zone-left { + top: auto; + bottom: 24px; + display: flex; + flex-direction: column-reverse; + gap: 12px; + } + + .zone-left .transit-system-container { + margin-top: 0; + } +} + +@keyframes slideUp { + from { + transform: translateY(100px); + opacity: 0; + } + + to { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: scale(0.95); + } + + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes pulse { + 0% { + opacity: 1; + } + + 50% { + opacity: 0.4; + } -@keyframes slideUp { from { transform: translateY(100px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } -@keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } } -@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.4; } 100% { opacity: 1; } } \ No newline at end of file + 100% { + opacity: 1; + } +} \ No newline at end of file diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index f2b34a0..4491f46 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -16,6 +16,7 @@ import { MapCursor } from "@/components/MapCursor"; import { TargetLine } from "@/components/TargetLine"; import { Polyline } from "@/components/Polyline"; import { Polygon } from "@/components/Polygon"; +import { Sidebar } from "@/components/Sidebar"; import { JEEPNEY_ROUTES, CAMPUS_ZONES, ZONE_CATEGORIES } from "@/data/map-layers"; import { useState, useEffect, useRef, useMemo, useCallback } from "react"; import { useTheme } from "@/lib/ThemeContext"; @@ -69,6 +70,9 @@ export default function Home() { }); }, []); + const mockUserLocation = { lat: 14.6549, lng: 121.0645 }; + const mockHeading = 45; + const { theme } = useTheme(); const [pendingPinCoords, setPendingPinCoords] = useState<{ @@ -102,9 +106,6 @@ export default function Home() { return () => window.removeEventListener("mousemove", handleMouseMove); }, [isAddingPin]); - const mockUserLocation = { lat: 14.6549, lng: 121.0645 }; - const mockHeading = 45; - return (
{/* TARGETING CROSSHAIR (Only visible when armed) */} @@ -290,6 +293,11 @@ export default function Home() { }} /> + + {pendingPinCoords && ( ; @@ -129,29 +130,40 @@ export function AddPinModal({ coords, onSave, onCancel }: AddPinModalProps) {
- PIN TYPES (select all that apply) -
- {tagsData?.map((t) => ( - - ))} -
-
+ PIN TYPES (select all that apply) +
+ {tagsData?.map((t) => { + const isActive = tags.includes(t.id); + const tagColor = getPinColor(t.title); + + return ( + + ); + })} +
+ void; + pinId: string; + onClose: () => void; } type Comment = { - id: string; - createdAt: string; - updatedAt: string; - ownerId: string; - pinId: string; - message: string; - parentId: string | null; - deletedAt: string | null; - replies: Comment[]; - authorName: string; + id: string; + createdAt: string; + updatedAt: string; + ownerId: string; + pinId: string; + message: string; + parentId: string | null; + deletedAt: string | null; + replies: Comment[]; + authorName: string; }; const commentSchema = z.object({ - message: z.string(), + message: z.string(), }); type commentSchemaType = z.infer; const CommentNode = ({ - comment, - depth = 0, + comment, + depth = 0, }: { - comment: Comment; - depth: number; + comment: Comment; + depth: number; }) => { - const { data: sessionData } = useSession(); - const utils = trpc.useUtils(); - const createComment = trpc.comment.create.useMutation({ - onSuccess(output) { - utils.pin.getById.invalidate(); - setIsReplying(false); - }, - }); - const formMethods = useForm({ resolver: zodResolver(commentSchema) }); - const [isReplying, setIsReplying] = useState(false); - if (depth > 3) return null; - - function onSubmit(data: commentSchemaType) { - createComment.mutate({ - message: data.message, - pinId: comment.pinId, - parentId: comment.id, - }); - } - - return ( -
0 ? "is-reply" : ""}`}> -
- {comment.authorName} - - {new Date(comment.createdAt).toLocaleString("default")} - -
- -

{comment.message}

- -
- {!isReplying ? ( - sessionData && - depth < 3 && ( - - ) - ) : ( -
- 3) return null; + + function onSubmit(data: commentSchemaType) { + createComment.mutate({ + message: data.message, + pinId: comment.pinId, + parentId: comment.id, + }); + } + + return ( +
0 ? "is-reply" : ""}`}> +
+ {comment.authorName} + + {new Date(comment.createdAt).toLocaleString("default")} + +
+ +

{comment.message}

+ +
+ {!isReplying ? ( + sessionData && + depth < 3 && ( + + ) + ) : ( + + - - - - )} -
- - {comment.replies && comment.replies.length > 0 && ( -
- {comment.replies.map((reply) => ( - - ))} -
- )} - - -
- ); +
+ ); }; export function ExpandedPinView({ pinId, onClose }: ExpandedPinViewProps) { - const utils = trpc.useUtils(); - const { data: sessionData } = useSession(); - const { data: pin } = trpc.pin.getById.useQuery( - { id: pinId }, - { refetchOnWindowFocus: false }, - ); - - const createComment = trpc.comment.create.useMutation({ - onSuccess(output) { - utils.pin.getById.invalidate(); - setIsReplying(false); - }, - }); - const deletePin = trpc.pin.userDelete.useMutation({ - onSuccess(output) { - utils.pin.getAll.invalidate(); - onClose(); - }, - }); - - const formMethods = useForm({ resolver: zodResolver(commentSchema) }); - const [isReplying, setIsReplying] = useState(false); - - function onSubmit(data: commentSchemaType) { - createComment.mutate({ - message: data.message, - pinId: pinId, - }); - } - - const [isDeleting, setIsDeleting] = useState(false); - - function onDelete() { - deletePin.mutate({ id: pinId }); - } - - const color = getFilterColor( - pin?.pinTags ? pin.pinTags[0]?.tag.title || "" : "", - ); - - return ( -
-
e.stopPropagation()}> -
- {/* HEADER */} -
-
- - {pin?.pinTags?.map((pt) => pt.tag.title).join(", ")} - -

{pin?.title}

-
- -
- - {/* HORIZONTAL BENTO GALLERY */} -
- {pin?.images?.map((img) => ( -
- -
- ))} -
- - {/* BODY: INTEL DASHBOARD */} -
-

{pin?.description}

- -
- {/* PIN ID */} -
- PIN ID - - {pin?.id?.padStart(7, "0")} - -
- - {/* OWNER */} -
- OWNED BY - {pin?.ownerId} -
- - {/* COORDINATES */} -
- COORDINATES (LAT, LNG) - - {pin?.latitude?.toFixed(6)}, {pin?.longitude?.toFixed(6)} - -
- - {/* STATUS */} -
- STATUS - - {pin?.status} - -
- - {/* TIMESTAMPS */} -
- CREATED AT - - {new Date(pin?.createdAt || "").toLocaleString("default", { - month: "long", - year: "numeric", - day: "2-digit", - })} - -
-
- LAST UPDATED - - {new Date(pin?.updatedAt || "").toLocaleString("default", { - month: "long", - year: "numeric", - day: "2-digit", - })} - -
-
- - {/* OWNER ACTIONS */} - {!isDeleting && sessionData?.user.id === pin?.ownerId && ( - - )} - - {isDeleting && ( -
-

Are you sure you want to permanently delete this pin?

-
- - -
-
- )} - -
-

FORUM

- {!isReplying ? ( - sessionData && ( - - ) - ) : ( -
- { + switch (status) { + case "VERIFIED": + return { + text: "VERIFIED", + // color: "var(--neon-green, #00FF99)", + icon: ( + + + + + ) + }; + case "PENDING_VERIFICATION": + return { + text: "PENDING", + // color: "var(--neon-yellow, #FFD700)", + icon: ( + + + + + ) + }; + case "REJECTED": + return { + text: "REJECTED", + // color: "#ff4d4d", + icon: ( + + + + + + ) + }; + default: + return { + text: status || "UNKNOWN", + color: "var(--text-secondary)", + icon: ( + + + + + + ) + }; + } + }; + + const statusData = getStatusDisplay(pin?.status); + + return ( +
+
e.stopPropagation()}> +
+ {/* HEADER */} +
+
+ + {pin?.pinTags?.map((pt) => pt.tag.title).join(", ")} + +

{pin?.title}

+
+ +
+ {sessionData?.user?.id === pin?.ownerId && ( + + )} + + +
+
+ + {/* HORIZONTAL BENTO GALLERY OR FALLBACK */} + {pin?.images && pin.images.length > 0 ? ( +
+ {pin.images.map((img) => ( +
+ +
+ ))} +
+ ) : ( +
+ + + + + + + NO IMAGES AVAILABLE +
+ )} + + {/* BODY: INTEL DASHBOARD */} +
+

{pin?.description}

+ +
+ {/* PIN ID */} +
+ PIN ID + + {pin?.id?.padStart(7, "0")} + +
+ + {/* OWNER */} +
+ OWNED BY + {pin?.ownerId} +
+ + {/* COORDINATES */} +
+ COORDINATES (LAT, LNG) + + {pin?.latitude?.toFixed(6)}, {pin?.longitude?.toFixed(6)} + +
+ + {/* STATUS */} +
+ STATUS + + {statusData.icon} + {statusData.text} + +
+ + {/* TIMESTAMPS */} +
+ CREATED AT + + {new Date(pin?.createdAt || "").toLocaleString("default", { + month: "long", + year: "numeric", + day: "2-digit", + })} + +
+
+ LAST UPDATED + + {new Date(pin?.updatedAt || "").toLocaleString("default", { + month: "long", + year: "numeric", + day: "2-digit", + })} + +
+
+ + {/* OWNER ACTIONS */} + {!isDeleting && sessionData?.user.id === pin?.ownerId && ( + + )} + + {isDeleting && ( +
+

Are you sure you want to permanently delete this pin?

+
+ + +
+
+ )} + +
+

FORUM

+ {!isReplying ? ( + sessionData && ( + + ) + ) : ( + + - - - - )} -
- {pin?.comments?.map((thread) => ( - - ))} -
-
-
-
- -
-
- - -
- ); +
+ ); } \ No newline at end of file diff --git a/apps/web/components/NeonPin.tsx b/apps/web/components/NeonPin.tsx index d5a9d7f..d38c3dd 100644 --- a/apps/web/components/NeonPin.tsx +++ b/apps/web/components/NeonPin.tsx @@ -1,5 +1,5 @@ import type { PinRouterOutputs } from "@repo/api"; -import { getFilterColor } from "./TopBar"; +import { getPinColor } from "@/data/pin-categories"; type Pin = PinRouterOutputs["getAll"][number]; interface NeonPinProps { @@ -17,7 +17,7 @@ export function NeonPin({ isVisible, onClick, }: NeonPinProps) { - const color = getFilterColor(pin?.pinTags[0]?.tag.title || ""); + const color = getPinColor(pin?.pinTags[0]?.tag.title || ""); const baseScale = isVisible ? (isSelected ? 1.2 : 1) : 0; const opacity = isVisible ? 1 : 0; diff --git a/apps/web/components/PinDetailsCard.tsx b/apps/web/components/PinDetailsCard.tsx index 494fc4f..de6e01e 100644 --- a/apps/web/components/PinDetailsCard.tsx +++ b/apps/web/components/PinDetailsCard.tsx @@ -1,6 +1,6 @@ "use client"; -import { getFilterColor } from "@/components/TopBar"; +import { getPinColor } from "@/data/pin-categories"; import { trpc } from "@/lib/trpc"; interface PinDetailsCardProps { @@ -22,9 +22,9 @@ export function PinDetailsCard({ { id: pinId }, { refetchOnWindowFocus: false }, ); - const color = getFilterColor( - pin?.pinTags ? pin?.pinTags[0]?.tag.title || "" : "", - ); + const color = getPinColor( + pin?.pinTags ? pin?.pinTags[0]?.tag.title || "" : "", + ); return (
diff --git a/apps/web/components/Sidebar.tsx b/apps/web/components/Sidebar.tsx new file mode 100644 index 0000000..77c63f8 --- /dev/null +++ b/apps/web/components/Sidebar.tsx @@ -0,0 +1,202 @@ +"use client"; + +import { useTheme } from "@/lib/ThemeContext"; +import { useSession } from "@/lib/auth-client"; +import { useRouter } from "next/navigation"; + +interface SidebarProps { + isOpen: boolean; + onClose: () => void; +} + +export function Sidebar({ isOpen, onClose }: SidebarProps) { + const { theme, toggleTheme } = useTheme(); + const { data: sessionData } = useSession(); + const router = useRouter(); + + if (!isOpen) return null; + + const handleNavigation = (path: string) => { + onClose(); + router.push(path); + }; + + return ( + <> +
+ +
+
+

UP WAYPOINT

+ +
+ +
+ {/* PRIMARY ACTIONS */} +
+ SYSTEM ACCESS + +
+ + {/* SETTINGS */} +
+ DISPLAY SETTINGS + +
+ + {/* PLACEHOLDERS */} +
+ DATABASE + + +
+
+ +
+ UP WAYPOINT v1.3.0 + SYSTEM ONLINE +
+
+ + + + ); +} \ No newline at end of file diff --git a/apps/web/components/TopBar.tsx b/apps/web/components/TopBar.tsx index 29116ea..e697a04 100644 --- a/apps/web/components/TopBar.tsx +++ b/apps/web/components/TopBar.tsx @@ -5,9 +5,9 @@ import { useRouter } from "next/navigation"; import { trpc } from "@/lib/trpc"; import { useTheme } from "@/lib/ThemeContext"; import { useSession } from "@/lib/auth-client"; +import { useMap } from "@vis.gl/react-google-maps"; import { JEEPNEY_ROUTES, ZONE_CATEGORIES } from "@/data/map-layers"; - -export type FilterType = "all" | "academic" | "food" | "social" | "utility"; +import { PIN_CATEGORIES, type FilterType, getPinColor } from "@/data/pin-categories"; interface TopBarProps { onMenuClick: () => void; @@ -19,8 +19,12 @@ interface TopBarProps { onToggleRoute?: (routeId: string) => void; activeZoneCategories?: string[]; onToggleZoneCategory?: (categoryId: string) => void; + userLocation?: { lat: number; lng: number }; + hideControls?: boolean; } +export type { FilterType }; + export function TopBar({ onMenuClick, activeFilter, @@ -31,6 +35,8 @@ export function TopBar({ onToggleRoute = () => {}, activeZoneCategories = [], onToggleZoneCategory = () => {}, + userLocation = { lat: 14.6549, lng: 121.0645 }, + hideControls = false, }: TopBarProps) { const router = useRouter(); const { data: sessionData } = useSession(); @@ -38,6 +44,29 @@ export function TopBar({ const [isZoneMenuOpen, setIsZoneMenuOpen] = useState(false); const { theme, toggleTheme } = useTheme(); + const map = useMap(); + + const handleCenterMap = () => { + if (map && userLocation) { + map.panTo(userLocation); + map.setZoom(19); + } + }; + + const handleZoomIn = () => { + if (map) { + const currentZoom = map.getZoom() || 19; + map.setZoom(currentZoom + 1); + } + }; + + const handleZoomOut = () => { + if (map) { + const currentZoom = map.getZoom() || 19; + map.setZoom(currentZoom - 1); + } + }; + const handleProfileClick = () => { if (sessionData?.user) { router.push("/dashboard"); @@ -46,18 +75,19 @@ export function TopBar({ } }; - const filters: FilterType[] = [ - "all", - "academic", - "food", - "social", - "utility", - ]; + const filters: FilterType[] = ["all", ...PIN_CATEGORIES.map(c => c.id as FilterType)]; return (
{/* === LEFT ZONE === */} -
+
- ); - })} -
+ {filters.map((filter) => { + const color = getPinColor(filter); + const isActive = activeFilter === filter; + return ( + + ); + })} +
{/* === RIGHT ZONE (Full Height Tool Stack) === */} @@ -262,67 +292,51 @@ export function TopBar({
{/* Bottom Group */} -
- +
+ -
- -
- -
-
+
+ +
+ +
+
); -} - -export const getFilterColor = (type: string) => { - switch (type) { - case "academic": - return "#ff4d4d"; - case "food": - return "#00ffa3"; - case "social": - return "#ff007a"; - case "transit": - return "#f4ff4d"; - case "utility": - return "#00d1ff"; - default: - return "var(--text-primary)"; - } -}; +} \ No newline at end of file diff --git a/apps/web/data/pin-categories.ts b/apps/web/data/pin-categories.ts new file mode 100644 index 0000000..90ba903 --- /dev/null +++ b/apps/web/data/pin-categories.ts @@ -0,0 +1,16 @@ +export const PIN_CATEGORIES = [ + { id: "academic", label: "ACADEMIC", color: "var(--pin-academic)" }, + { id: "food", label: "FOOD", color: "var(--pin-food)" }, + { id: "social", label: "SOCIAL", color: "var(--pin-social)" }, + { id: "transit", label: "TRANSIT", color: "var(--pin-transit)" }, + { id: "utility", label: "UTILITY", color: "var(--pin-utility)" }, +] as const; + +export type PinCategory = typeof PIN_CATEGORIES[number]["id"]; +export type FilterType = "all" | PinCategory; + +export const getPinColor = (type: string) => { + const normalizedType = type.toLowerCase(); + const category = PIN_CATEGORIES.find((c) => c.id === normalizedType); + return category ? category.color : "var(--text-primary)"; +}; \ No newline at end of file