Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
19df7a4
Added basic embedded map in newly created components folder under Emb…
MasonMines2006 Oct 21, 2025
86fb542
Implemented basic embed map
MasonMines2006 Oct 21, 2025
d35c431
created react map
MasonMines2006 Oct 28, 2025
c7d3a4e
Created changed to
MasonMines2006 Oct 28, 2025
d11eb73
Merge branch 'main' into embedded-map/#23
MasonMines2006 Oct 28, 2025
5e1be29
Fixed Embedded Map to include the react integration library ( vis.gl/…
MasonMines2006 Oct 28, 2025
ba97c83
Merge branch 'main' into embedded-map/#23
naasanov Nov 3, 2025
df68222
Created Party Demo test page
MasonMines2006 Nov 3, 2025
119936a
Merge remote-tracking branch 'origin/main' into embedded-map/#23
MasonMines2006 Nov 3, 2025
9128996
First mockup of party list props, and page created to be displayed in…
MasonMines2006 Nov 4, 2025
49a441b
Added popup text box and color to map
MasonMines2006 Nov 4, 2025
e527ab9
Removed package and package lock in root directory, updated .env.temp…
MasonMines2006 Nov 4, 2025
e09d155
Created functioning dropdown for party list
MasonMines2006 Nov 4, 2025
adcf2e0
Modified styling to be accurate with the Lo-Fi wireframe
MasonMines2006 Nov 11, 2025
2b5c3af
Changed API keys and added basic sidebar functionality
MasonMines2006 Nov 11, 2025
b9f17ce
Working global sidebar
MasonMines2006 Nov 11, 2025
f7c714f
Created generic implementation of info chips
MasonMines2006 Nov 11, 2025
0a33dbd
Finished implementation of generic info chip details, added specific …
MasonMines2006 Nov 11, 2025
0f40423
Merge branch 'main' into mason-info-chip
MasonMines2006 Nov 11, 2025
6e95b02
Merge branch 'main' of https://github.com/cssgunc/party-registration …
naasanov Nov 14, 2025
8dfdd64
Minor changes to PartyList before merge with main
MasonMines2006 Nov 18, 2025
acdee3e
Merging with Main
MasonMines2006 Nov 18, 2025
af89dcf
fixed type erros
MasonMines2006 Nov 18, 2025
90a68ca
Resolved corrections suggested in PR review
MasonMines2006 Nov 18, 2025
a3bb5f3
Merge remote-tracking branch 'origin/mason-info-chip' into mason-info…
MasonMines2006 Nov 18, 2025
03693b0
Changes to sidebar functionality and chip information
MasonMines2006 Nov 20, 2025
6ff4e3d
Reverted to old non-shadCN sidebar implementation
MasonMines2006 Nov 20, 2025
1a490b4
Merge branch 'main' of https://github.com/cssgunc/party-registration …
naasanov Nov 20, 2025
e1319e9
chore: update package.json
naasanov Nov 20, 2025
3f6ec84
chore: reverted whitespace changes to prevent merge conflicts
naasanov Nov 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,705 changes: 3,598 additions & 107 deletions frontend/package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,26 @@
"lint": "eslint"
},
"dependencies": {
"@radix-ui/react-tabs": "^1.1.13",
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-slot": "^1.2.4",
"@tanstack/react-table": "^8.21.3",
"@vis.gl/react-google-maps": "^1.6.1",
"axios": "^1.12.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"lucide-react": "^0.544.0",
"next": "15.5.4",
"next-auth": "^4.24.13",
"react": "19.1.0",
"react-day-picker": "^9.11.1",
"react-dom": "19.1.0",
"react-hook-form": "^7.65.0",
"shadcn": "^3.5.0",
"tailwind-merge": "^3.3.1",
"zod": "^4.1.12"
},
Expand Down
131 changes: 131 additions & 0 deletions frontend/src/app/InfoChipDemo/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"use client";

import { GenericChipDetails } from "@/components/GenericChipDetails";
import { GenericInfoChip } from "@/components/GenericInfoChip";
import LocationInfoChipDetails from "@/components/LocationInfoChipDetails";
import { useSidebar } from "@/components/SidebarContext";
import StudentInfoChipDetails from "@/components/StudentInfoChipDetails";
import { Location } from "@/types/api/location";
import { Student } from "@/types/api/student";
import { Badge } from "lucide-react";
import { useState } from "react";

const Page = () => {
const { openSidebar } = useSidebar();
const defaultStudent = getTestChipData().student;
const defaultLocation = getTestChipData().location;
const [exampleData, setExampleData] = useState({ name: "Mason", age: 22 });
const [studentData, setStudentData] = useState(defaultStudent);
const [locationData, setLocationData] = useState(defaultLocation);

return (
<div className="font-sans min-h-screen flex flex-col items-center justify-center p-8 sm:p-20 max-w-2xl mx-auto">
<h1 className="text-3xl font-bold mb-4">Info Chip Demo Page</h1>
<h1 className="text-2xl font-bold mb-4">Test Sidebar</h1>
<Badge onClick={() => openSidebar(<div>Hello from Sidebar!</div>)}>
Open Sidebar
</Badge>
<h1 className="text-2xl font-bold mb-4">Example Info Chip</h1>
<GenericInfoChip
data={{ name: "Mason", age: 22 }}
renderSidebar={(data, onSave) => (
<GenericChipDetails
data={data}
onSave={(updated) => {
setExampleData(updated);
onSave(updated);
}}
renderForm={(d, setD) => (
<input
className="border p-2 w-full"
value={d.name}
onChange={(e) => setD({ ...d, name: e.target.value })}
/>
)}
/>
)}
/>
<div className="mt-4 space-y-2">
<h2 className="font-semibold text-xl">Example Info</h2>
<p>Age: {exampleData.age}</p>
<p>Name: {exampleData.name}</p>
</div>
<h1 className="text-2xl font-bold mb-4">Student Info Chip</h1>
<GenericInfoChip
data={studentData}
renderSidebar={(data, onSave) => (
<StudentInfoChipDetails
data={data}
onSave={(updated) => {
setStudentData(updated);
onSave(updated);
}}
/>
)}
/>
<div className="mt-4 space-y-2">
<h2 className="font-semibold text-xl">Student Info</h2>
<p>
Name: {studentData.firstName} {studentData.lastName}
</p>
<p>Phone: {studentData.phoneNumber}</p>
<p>Contact Pref: {studentData.contactPrefrence}</p>
</div>
<h1 className="text-2xl font-bold mb-4">Location Info Chip</h1>
<GenericInfoChip
data={locationData}
renderSidebar={(data, onSave) => (
<LocationInfoChipDetails
data={data}
onSave={(updated) => {
setLocationData(updated);
onSave(updated);
}}
/>
)}
/>
<div className="mt-4 space-y-2">
<h2 className="font-semibold text-xl">Location Info</h2>
<p>Address: {locationData.formatted_address}</p>
<p>Active Hold: {locationData.has_active_hold ? "Yes" : "No"}</p>
<p>Warnings: {locationData.warning_count}</p>
<p>Citations: {locationData.citation_count}</p>
</div>
</div>
);
};
export function getTestChipData() {
const student: Student = {
id: 1,
accountId: 101,
firstName: "Mason",
lastName: "Beast",
fullName: "Mason Beast",
contactPrefrence: "text",
registerDate: new Date("2025-01-01"),
phoneNumber: "555-1234",
};

const location: Location = {
id: 1,
warning_count: 2,
citation_count: 1,
has_active_hold: true,
hold_expiration: new Date("2025-12-31"),
google_place_id: "abcd1234",
formatted_address: "123 Main St, Chapel Hill, NC",
latitude: 35.9132,
longitude: -79.0558,
street_number: "123",
street_name: "Main St",
unit: "Apt 4",
city: "Chapel Hill",
county: "Orange",
state: "NC",
country: "USA",
zip_code: "27514",
};
return { student, location };
}

export default Page;
15 changes: 15 additions & 0 deletions frontend/src/app/PartyDemo/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";

import EmbeddedMap from "@/components/EmbeddedMap";
import PartyList from "@/components/PartyList";

const Page = () => {
return (
<div className="font-sans min-h-screen flex flex-col items-center justify-center p-8 sm:p-20 max-w-2xl mx-auto">
<PartyList />
<EmbeddedMap parties={[]} />
</div>
);
};

export default Page;
9 changes: 7 additions & 2 deletions frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { GenericSidebar } from "@/components/GenericSidebar";
import { SidebarProvider } from "@/components/SidebarContext";
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import Providers from "./providers";

const geistSans = Geist({
variable: "--font-geist-sans",
Expand All @@ -28,7 +29,11 @@ export default function RootLayout({
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<Providers>{children}</Providers>
<SidebarProvider>
{" "}
{children}
<GenericSidebar />
</SidebarProvider>
</body>
</html>
);
Expand Down
23 changes: 18 additions & 5 deletions frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@

export default function Home() {
return (
<div className="font-sans min-h-screen flex flex-col items-center justify-center p-8 sm:p-20 max-w-2xl mx-auto">
<h1 className="text-3xl font-bold mb-6 text-center">Party Registration System</h1>
<p className="mb-8 text-lg text-center">A CS+SG project for the Office of Off-Campus Student Life at UNC.</p>
<h2 className="text-xl font-semibold mb-4 text-center">About the Office of Off-Campus Student Life</h2>
<h1 className="text-3xl font-bold mb-6 text-center">
Party Registration System
</h1>
<p className="mb-8 text-lg text-center">
A CS+SG project for the Office of Off-Campus Student Life at UNC.
</p>
<h2 className="text-xl font-semibold mb-4 text-center">
About the Office of Off-Campus Student Life
</h2>
<p className="text-base text-center">
The Office of Off-Campus Student Life serves as a vital resource for students living off-campus, providing support, programs, and services to enhance the off-campus living experience. This office works to connect off-campus students with campus resources, facilitate community building, and ensure student safety and well-being in off-campus environments. Through various initiatives and programs, the office aims to bridge the gap between on-campus and off-campus student experiences, fostering a sense of belonging and engagement for all students regardless of their housing situation.
The Office of Off-Campus Student Life serves as a vital resource for
students living off-campus, providing support, programs, and services to
enhance the off-campus living experience. This office works to connect
off-campus students with campus resources, facilitate community
building, and ensure student safety and well-being in off-campus
environments. Through various initiatives and programs, the office aims
to bridge the gap between on-campus and off-campus student experiences,
fostering a sense of belonging and engagement for all students
regardless of their housing situation.
</p>
</div>
);
Expand Down
115 changes: 115 additions & 0 deletions frontend/src/components/EmbeddedMap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"use client";

import { Party } from "@/types/api/party";
import {
AdvancedMarker,
APIProvider,
InfoWindow,
Map,
MapCameraChangedEvent,
Pin,
useMap,
} from "@vis.gl/react-google-maps";
import { useCallback, useState } from "react";

interface EmbeddedMapProps {
parties: Party[];
activeParty?: Party;
}
interface PoiMarkersProps {
pois: Poi[];
activePoiKey?: string;
}
type Poi = { key: string; location: google.maps.LatLngLiteral };

const EmbeddedMap = ({ parties, activeParty }: EmbeddedMapProps) => {
const default_locations: Poi[] = [
{ key: "polkPlace", location: { lat: 35.911232, lng: -79.050331 } },
{ key: "davisLibrary", location: { lat: 35.910784, lng: -79.047729 } },
{ key: "oldWell", location: { lat: 35.911473, lng: -79.050105 } },
{ key: "kenanStadium", location: { lat: 35.906839, lng: -79.047793 } },
];
const locations =
parties && parties.length > 0
? parties.map((party) => ({
key: party.id.toString(),
location: {
lat: party.location.latitude,
lng: party.location.longitude,
},
}))
: default_locations;
const activePoiKey = activeParty ? activeParty.id.toString() : undefined;
const defaultZoom = 13;
const defaultCenter =
parties && parties.length > 0
? locations[0].location
: { lat: 35.911232, lng: -79.050331 };
const API_KEY = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY || "";
return (
<div className="w-full h-[450px] overflow-hidden rounded-2xl shadow-md">
<APIProvider
apiKey={API_KEY}
onLoad={() => console.log("Maps API has loaded.")}
>
<Map
defaultZoom={defaultZoom}
defaultCenter={defaultCenter}
mapId={process.env.NEXT_PUBLIC_GOOGLE_MAP_ID!}
onCameraChanged={(ev: MapCameraChangedEvent) =>
console.log(
"camera changed:",
ev.detail.center,
"zoom:",
ev.detail.zoom
)
}
>
<PoiMarkers pois={locations} activePoiKey={activePoiKey} />
</Map>
</APIProvider>
</div>
);
};
const PoiMarkers = ({ pois }: PoiMarkersProps) => {
const map = useMap();
const [selectedPoi, setSelectedPoi] = useState<Poi | null>(null);

const handleClick = useCallback(
(poi: Poi) => (ev: google.maps.MapMouseEvent) => {
if (!map || !ev.latLng) return;
map.panTo(ev.latLng);
setSelectedPoi(poi);
},
[map]
);

return (
<>
{pois.map((poi: Poi) => (
<AdvancedMarker
key={poi.key}
position={poi.location}
clickable={true}
onClick={handleClick(poi)}
>
<Pin
background={poi == selectedPoi ? "#4285F4" : "#EA4335"}
glyphColor={"#fff"}
borderColor={poi == selectedPoi ? "#1967D2" : "#B31412"}
/>
</AdvancedMarker>
))}
{selectedPoi && (
<InfoWindow
position={selectedPoi.location}
onCloseClick={() => setSelectedPoi(null)}
>
<div>{selectedPoi.key}</div>
</InfoWindow>
)}
</>
);
};

export default EmbeddedMap;
39 changes: 39 additions & 0 deletions frontend/src/components/GenericChipDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";

import { useSidebar } from "@/components/SidebarContext";
import { ReactNode, useState } from "react";

interface GenericChipDetailsProps<T> {
data: T;
onSave: (updatedData: T) => void;
renderForm: (value: T, setValue: (v: T) => void) => ReactNode;
}

export function GenericChipDetails<T>({
data,
onSave,
renderForm,
}: GenericChipDetailsProps<T>) {
const [localData, setLocalData] = useState<T>(data);
const { closeSidebar } = useSidebar();

const handleSave = () => {
onSave(localData);
closeSidebar();
};

return (
<div className="space-y-3">
<h2 className="text-lg font-semibold">Edit Info</h2>
{renderForm(localData, setLocalData)}
<div className="flex gap-2 mt-3">
<button
className="bg-blue-500 text-white px-3 py-1 rounded"
onClick={handleSave}
>
Save Changes
</button>
</div>
</div>
);
}
Loading