From 46521022045af348076f582d2670c329f031a406 Mon Sep 17 00:00:00 2001 From: Karen Gao Date: Wed, 8 Apr 2026 20:42:02 -0400 Subject: [PATCH 1/6] create saved events page --- frontend/app/testSavedEvents/page.tsx | 5 + .../components/savedEvents/SavedEvents.tsx | 564 ++++++++++++++++++ 2 files changed, 569 insertions(+) create mode 100644 frontend/app/testSavedEvents/page.tsx create mode 100644 frontend/components/savedEvents/SavedEvents.tsx diff --git a/frontend/app/testSavedEvents/page.tsx b/frontend/app/testSavedEvents/page.tsx new file mode 100644 index 0000000..b57d84e --- /dev/null +++ b/frontend/app/testSavedEvents/page.tsx @@ -0,0 +1,5 @@ +import SavedEvents from '@/components/savedEvents/SavedEvents'; + +export default function TestSavedPage() { + return ; +} \ No newline at end of file diff --git a/frontend/components/savedEvents/SavedEvents.tsx b/frontend/components/savedEvents/SavedEvents.tsx new file mode 100644 index 0000000..1c1f968 --- /dev/null +++ b/frontend/components/savedEvents/SavedEvents.tsx @@ -0,0 +1,564 @@ +'use client'; + +import { useState } from 'react'; + +import { useTheme } from 'evergreen-ui'; + +// ── Types ──────────────────────────────────────────────────────────────────── + +type Tab = 'All' | 'Saved' | 'Going' | 'Created'; +type SortOption = 'Date (soonest)' | 'Recently added' | 'Category'; +type Category = 'SPORTS' | 'ACADEMIC' | 'SOCIAL' | 'FOOD' | 'ARTS' | 'CAREER' | 'HOUSING' | 'OTHER'; + +interface SavedEvent { + id: string; + title: string; + date: string; // e.g. "WED, APR 1" + timeRange: string; // e.g. "4:30 PM – 5:30 PM" + location: string; + description: string; + interested: number; + category: Category; + tab: Tab[]; // which tabs this event appears under + isStarred?: boolean; + isOwned?: boolean; // show Edit button + accentColor: string; +} + +// ── Mock data ──────────────────────────────────────────────────────────────── + +const MOCK_EVENTS: SavedEvent[] = [ + { + id: '1', + title: 'Campus YMCA Yoga', + date: 'WED, APR 1', + timeRange: '4:30 PM – 5:30 PM', + location: 'Dillon Gymnasium Dance Studio', + description: + 'All-levels yoga class open to all Princeton students, staff, and faculty. Mats provided. Wear comfortable clothes.', + interested: 45, + category: 'SPORTS', + tab: ['All', 'Saved'], + isStarred: true, + accentColor: '#22c55e', + }, + { + id: '2', + title: 'Debate Club Practice', + date: 'WED, APR 1', + timeRange: '4:30 PM – 6 PM', + location: '1879 Hall', + description: + 'Princeton Debate Panel weekly practice session. Open to all levels — beginners welcome. Practice British Parliamentary...', + interested: 18, + category: 'ACADEMIC', + tab: ['All', 'Saved'], + isStarred: true, + accentColor: '#3b82f6', + }, + { + id: '3', + title: 'Midnight Coding Marathon', + date: 'THU, APR 2', + timeRange: '11 PM – 2 AM', + location: 'CS Building, Room 104', + description: + 'All-night coding session. Snacks provided! Build something cool from 11 PM to 2 AM.', + interested: 42, + category: 'ACADEMIC', + tab: ['All', 'Going'], + isStarred: true, + accentColor: '#3b82f6', + }, + { + id: '4', + title: 'test', + date: 'THU, APR 2', + timeRange: '12 PM – 1 PM', + location: '', + description: '', + interested: 0, + category: 'SOCIAL', + tab: ['All', 'Created'], + isOwned: true, + accentColor: '#ef4444', + }, + { + id: '5', + title: 'Volleyball Championship', + date: 'SAT, APR 4', + timeRange: '2 PM – 5 PM', + location: 'Dillon Gymnasium', + description: 'Intramural volleyball championship finals. Come cheer on your fellow Tigers!', + interested: 38, + category: 'SPORTS', + tab: ['All', 'Going'], + isStarred: true, + accentColor: '#22c55e', + }, +]; + +const CATEGORY_COLORS: Record = { + SPORTS: '#22c55e', + ACADEMIC: '#3b82f6', + SOCIAL: '#ef4444', + FOOD: '#f97316', + ARTS: '#a855f7', + CAREER: '#6b7280', + HOUSING: '#f59e0b', + OTHER: '#14b8a6', +}; + +const SORT_OPTIONS: SortOption[] = ['Date (soonest)', 'Recently added', 'Category']; + +// ── Component ──────────────────────────────────────────────────────────────── + +export default function SavedEvents() { + const { colors } = useTheme(); + const themeColors = colors as unknown as Record; + + const [activeTab, setActiveTab] = useState('All'); + const [search, setSearch] = useState(''); + const [sort, setSort] = useState('Date (soonest)'); + const [sortOpen, setSortOpen] = useState(false); + + const tabCounts: Record = { + All: MOCK_EVENTS.length, + Saved: MOCK_EVENTS.filter((e) => e.tab.includes('Saved')).length, + Going: MOCK_EVENTS.filter((e) => e.tab.includes('Going')).length, + Created: MOCK_EVENTS.filter((e) => e.tab.includes('Created')).length, + }; + + const filtered = MOCK_EVENTS.filter((e) => { + const matchesTab = e.tab.includes(activeTab); + const matchesSearch = + search === '' || + e.title.toLowerCase().includes(search.toLowerCase()) || + e.location.toLowerCase().includes(search.toLowerCase()); + return matchesTab && matchesSearch; + }); + + return ( +
+ {/* ── Header ── */} +
+

+ Saved Events +

+ + {/* Count badge */} +
+ {tabCounts[activeTab]} +
+
+ + {/* ── Tabs ── */} +
+ {(['All', 'Saved', 'Going', 'Created'] as Tab[]).map((tab) => ( + + ))} +
+ + {/* ── Search + Sort ── */} +
+ {/* Search */} +
+ 🔍 + setSearch(e.target.value)} + style={{ + border: 'none', + outline: 'none', + flex: 1, + fontSize: 14, + color: themeColors.gray800, + background: 'transparent', + }} + /> +
+ + {/* Sort dropdown */} +
+ + + {sortOpen && ( +
+ {SORT_OPTIONS.map((option) => ( + + ))} +
+ )} +
+
+ + {/* ── Event cards grid ── */} +
+ {filtered.length === 0 ? ( +
+ No events found. +
+ ) : ( +
+ {filtered.map((event) => ( + + ))} +
+ )} +
+
+ ); +} + +// ── EventCard ──────────────────────────────────────────────────────────────── + +function EventCard({ + event, + themeColors, + colors, +}: { + event: SavedEvent; + themeColors: Record; + colors: Record; +}) { + const categoryColor = CATEGORY_COLORS[event.category]; + + return ( +
+ {/* Colored top accent bar */} +
+ +
+ {/* Date + category + star row */} +
+
+ + {event.date} + + + {event.category} + +
+ + {event.isOwned ? ( + + ) : event.isStarred ? ( + + ) : null} +
+ + {/* Title */} +
+ {event.title} +
+ + {/* Time + location */} + {(event.timeRange || event.location) && ( +
+ {event.timeRange && ( +
+ 🕐 + {event.timeRange} + {event.location && ( + <> + · + 📍 + {event.location} + + )} +
+ )} +
+ )} + + {/* Description */} + {event.description && ( +
+ {event.description} +
+ )} + + {/* Interested count */} + {event.interested > 0 && ( +
+ {event.interested} interested +
+ )} +
+
+ ); +} From edfcc5295cc999e8704458a3e0cc10d40f5cca12 Mon Sep 17 00:00:00 2001 From: Karen Gao Date: Wed, 8 Apr 2026 20:44:54 -0400 Subject: [PATCH 2/6] removed test page --- frontend/app/testSavedEvents/page.tsx | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 frontend/app/testSavedEvents/page.tsx diff --git a/frontend/app/testSavedEvents/page.tsx b/frontend/app/testSavedEvents/page.tsx deleted file mode 100644 index b57d84e..0000000 --- a/frontend/app/testSavedEvents/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import SavedEvents from '@/components/savedEvents/SavedEvents'; - -export default function TestSavedPage() { - return ; -} \ No newline at end of file From 33d7719a2481c2c6b0d147f975327b3ea2c1a741 Mon Sep 17 00:00:00 2001 From: Karen Gao Date: Wed, 22 Apr 2026 20:12:15 -0400 Subject: [PATCH 3/6] myEvents2 including search and filter by category feature --- frontend/app/my-events/page.tsx | 5 + frontend/components/myEvents/MyEvents.tsx | 352 ++++++++++ frontend/components/myEvents/myEvents2.tsx | 723 +++++++++++++++++++++ 3 files changed, 1080 insertions(+) create mode 100644 frontend/app/my-events/page.tsx create mode 100644 frontend/components/myEvents/MyEvents.tsx create mode 100644 frontend/components/myEvents/myEvents2.tsx diff --git a/frontend/app/my-events/page.tsx b/frontend/app/my-events/page.tsx new file mode 100644 index 0000000..4edbe53 --- /dev/null +++ b/frontend/app/my-events/page.tsx @@ -0,0 +1,5 @@ +import MyEvents from '@/components/myEvents/myEvents2'; + +export default function Page() { + return ; +} \ No newline at end of file diff --git a/frontend/components/myEvents/MyEvents.tsx b/frontend/components/myEvents/MyEvents.tsx new file mode 100644 index 0000000..bd36ec7 --- /dev/null +++ b/frontend/components/myEvents/MyEvents.tsx @@ -0,0 +1,352 @@ +'use client'; + +import { useState } from 'react'; + +import { useTheme } from 'evergreen-ui'; + +// ── Types ───────────────────────────────────────────────────── + +type Tab = 'All' | 'Saved' | 'Created'; +type SortOption = 'Date (soonest)' | 'Date (latest)' | 'Recently added'; + +type Category = + | 'SPORTS' + | 'ACADEMIC' + | 'SOCIAL' + | 'FOOD' + | 'ARTS' + | 'CAREER' + | 'HOUSING' + | 'OTHER'; + +interface Event { + id: string; + title: string; + dateISO: string; + displayDate: string; + timeRange: string; + location: string; + description: string; + interested: number; + category: Category; + tab: Tab[]; + isStarred?: boolean; + isOwned?: boolean; +} + +// ── Mock Data ───────────────────────────────────────────────── + +const EVENTS: Event[] = [ + { + id: '1', + title: 'Campus YMCA Yoga', + dateISO: '2026-04-01', + displayDate: 'WED, APR 1', + timeRange: '4:30 PM – 5:30 PM', + location: 'Dillon Gymnasium', + description: 'All-level yoga class.', + interested: 45, + category: 'SPORTS', + tab: ['All', 'Saved'], + isStarred: true, + }, + { + id: '2', + title: 'Debate Club Practice', + dateISO: '2026-04-01', + displayDate: 'WED, APR 1', + timeRange: '4:30 PM – 6 PM', + location: '1879 Hall', + description: 'Weekly debate practice.', + interested: 18, + category: 'ACADEMIC', + tab: ['All', 'Saved'], + isStarred: true, + }, + { + id: '3', + title: 'Midnight Coding Marathon', + dateISO: '2026-04-02', + displayDate: 'THU, APR 2', + timeRange: '11 PM – 2 AM', + location: 'CS Building', + description: 'Build something cool!', + interested: 42, + category: 'ACADEMIC', + tab: ['All'], + }, + { + id: '4', + title: 'My Private Event', + dateISO: '2026-04-03', + displayDate: 'FRI, APR 3', + timeRange: '12 PM – 1 PM', + location: '', + description: '', + interested: 0, + category: 'SOCIAL', + tab: ['All', 'Created'], + isOwned: true, + }, +]; + +// ── Sort Options ────────────────────────────────────────────── + +const SORT_OPTIONS: SortOption[] = [ + 'Date (soonest)', + 'Date (latest)', + 'Recently added', +]; + +// ── Component ───────────────────────────────────────────────── + +export default function MyEvents() { + const { colors } = useTheme(); + const themeColors = colors as any; + + const [activeTab, setActiveTab] = useState('All'); + const [search, setSearch] = useState(''); + const [sort, setSort] = useState('Date (soonest)'); + const [sortOpen, setSortOpen] = useState(false); + + const tabCounts = { + All: EVENTS.length, + Saved: EVENTS.filter((e) => e.tab.includes('Saved')).length, + Created: EVENTS.filter((e) => e.tab.includes('Created')).length, + }; + + const query = search.trim().toLowerCase(); + + const filtered = EVENTS + .filter((e) => { + const matchesTab = e.tab.includes(activeTab); + const matchesSearch = + query === '' || e.title.toLowerCase().includes(query); + + return matchesTab && matchesSearch; + }) + .sort((a, b) => { + if (sort === 'Date (soonest)') { + return new Date(a.dateISO).getTime() - new Date(b.dateISO).getTime(); + } + if (sort === 'Date (latest)') { + return new Date(b.dateISO).getTime() - new Date(a.dateISO).getTime(); + } + if (sort === 'Recently added') { + return Number(b.id) - Number(a.id); + } + return 0; + }); + + return ( +
+

+ My Events +

+ + {/* Tabs */} +
+ {(['All', 'Saved', 'Created'] as Tab[]).map((tab) => ( + + ))} +
+ + {/* Search + Sort */} +
+ setSearch(e.target.value)} + style={{ + flex: 1, + padding: 8, + border: `1px solid ${themeColors.gray300}`, + borderRadius: 6, + background: colors.white, + color: themeColors.gray800, + }} + /> + +
+ + + {sortOpen && ( +
+ {SORT_OPTIONS.map((opt) => ( +
{ + setSort(opt); + setSortOpen(false); + }} + style={{ + padding: 10, + cursor: 'pointer', + color: themeColors.gray800, + }} + > + {opt} +
+ ))} +
+ )} +
+
+ + {/* Grid */} +
+ {filtered.map((event) => ( + + ))} +
+
+ ); +} + +// ── Event Card ──────────────────────────────────────────────── + +function EventCard({ + event, + themeColors, + colors, +}: { + event: Event; + themeColors: any; + colors: any; +}) { + return ( +
+
+
+ {event.displayDate} +
+ +
+ {event.title} +
+ + {(event.timeRange || event.location) && ( +
+ {event.timeRange} + {event.location && ` • ${event.location}`} +
+ )} + + {event.description && ( +
+ {event.description} +
+ )} + + {event.isOwned && ( + + )} +
+
+ ); +} \ No newline at end of file diff --git a/frontend/components/myEvents/myEvents2.tsx b/frontend/components/myEvents/myEvents2.tsx new file mode 100644 index 0000000..cdf8375 --- /dev/null +++ b/frontend/components/myEvents/myEvents2.tsx @@ -0,0 +1,723 @@ +'use client'; + +import React, { useState, useRef, useEffect } from 'react'; + +import { useTheme } from 'evergreen-ui'; + +// ── Types ───────────────────────────────────────────────────── + +type Tab = 'All' | 'Saved' | 'Created'; +type SortOption = 'Date (soonest)' | 'Date (latest)' | 'Recently added'; + +type Category = + | 'SPORTS' + | 'ACADEMIC' + | 'SOCIAL' + | 'FOOD' + | 'ARTS' + | 'CAREER' + | 'HOUSING' + | 'OTHER'; + +const ALL_CATEGORIES: Category[] = [ + 'SPORTS', + 'ACADEMIC', + 'SOCIAL', + 'FOOD', + 'ARTS', + 'CAREER', + 'HOUSING', + 'OTHER', +]; + +const CATEGORY_COLORS: Record = { + SPORTS: { bg: '#E8F5E9', text: '#2E7D32' }, + ACADEMIC: { bg: '#E3F2FD', text: '#1565C0' }, + SOCIAL: { bg: '#F3E5F5', text: '#6A1B9A' }, + FOOD: { bg: '#FFF3E0', text: '#E65100' }, + ARTS: { bg: '#FCE4EC', text: '#AD1457' }, + CAREER: { bg: '#E8EAF6', text: '#283593' }, + HOUSING: { bg: '#E0F2F1', text: '#00695C' }, + OTHER: { bg: '#F5F5F5', text: '#424242' }, +}; + +interface Event { + id: string; + title: string; + dateISO: string; + displayDate: string; + timeRange: string; + location: string; + description: string; + interested: number; + category: Category; + tab: Tab[]; + isStarred?: boolean; + isOwned?: boolean; +} + +// ── Mock Data ───────────────────────────────────────────────── + +const EVENTS: Event[] = [ + { + id: '1', + title: 'Mock Interview Night', + dateISO: '2026-04-06', + displayDate: 'MON, APR 6', + timeRange: '6 PM – 8 PM', + location: 'Robertson Hall', + description: + 'Practice behavioral and technical interviews with alumni volunteers from top tech and finance firms. Sign up for ...', + interested: 29, + category: 'CAREER', + tab: ['All', 'Saved'], + isStarred: true, + }, + { + id: '2', + title: 'AI at Princeton: LLM Fine-Tuning Workshop', + dateISO: '2026-04-07', + displayDate: 'TUE, APR 7', + timeRange: '6 PM – 8 PM', + location: 'COS Building Room 302', + description: + 'Hands-on workshop on fine-tuning large language models using LoRA and QLoRA. Bring your laptop with ...', + interested: 118, + category: 'ACADEMIC', + tab: ['All', 'Saved'], + isStarred: true, + }, + { + id: '3', + title: 'Preview Day', + dateISO: '2026-04-08', + displayDate: 'WED, APR 8', + timeRange: '', + location: 'University-wide', + description: + 'Princeton Preview Day for admitted students. Campus tours, info sessions, and class visits across all...', + interested: 0, + category: 'SOCIAL', + tab: ['All', 'Saved'], + isStarred: true, + }, + { + id: '4', + title: 'Campus YMCA Yoga', + dateISO: '2026-04-10', + displayDate: 'FRI, APR 10', + timeRange: '4:30 PM – 5:30 PM', + location: 'Dillon Gymnasium', + description: 'All-level yoga class. No experience needed — just bring a mat!', + interested: 45, + category: 'SPORTS', + tab: ['All', 'Created'], + isOwned: true, + }, + { + id: '5', + title: 'Spring Fling Food Festival', + dateISO: '2026-04-12', + displayDate: 'SUN, APR 12', + timeRange: '12 PM – 4 PM', + location: 'Prospect Garden', + description: + 'Student-run food fair with dishes from 20+ cuisines. Live music and lawn games included.', + interested: 203, + category: 'FOOD', + tab: ['All', 'Created'], + isOwned: true, + }, + { + id: '6', + title: 'A Cappella Showcase', + dateISO: '2026-04-15', + displayDate: 'WED, APR 15', + timeRange: '7 PM – 9 PM', + location: 'Richardson Auditorium', + description: 'Six Princeton a cappella groups perform in this semester-end showcase.', + interested: 87, + category: 'ARTS', + tab: ['All', 'Created'], + isOwned: true, + }, +]; + +// ── Sort Options ────────────────────────────────────────────── + +const SORT_OPTIONS: SortOption[] = [ + 'Date (soonest)', + 'Date (latest)', + 'Recently added', +]; + +// ── Dropdown Hook ───────────────────────────────────────────── + +function useClickOutside(ref: React.RefObject, cb: () => void) { + useEffect(() => { + function handler(e: MouseEvent) { + if (ref.current && !ref.current.contains(e.target as Node)) cb(); + } + document.addEventListener('mousedown', handler); + return () => document.removeEventListener('mousedown', handler); + }, [ref, cb]); +} + +// ── Component ───────────────────────────────────────────────── + +export default function MyEvents() { + const { colors } = useTheme(); + const themeColors = colors as any; + + const TEAL = themeColors['hoagie-teal'] ?? '#00897B'; + + const [activeTab, setActiveTab] = useState('All'); + const [search, setSearch] = useState(''); + const [sort, setSort] = useState('Date (soonest)'); + const [sortOpen, setSortOpen] = useState(false); + const [categoryOpen, setCategoryOpen] = useState(false); + const [selectedCategories, setSelectedCategories] = useState([]); + + const sortRef = useRef(null!); + const categoryRef = useRef(null!); + + useClickOutside(sortRef, () => setSortOpen(false)); + useClickOutside(categoryRef, () => setCategoryOpen(false)); + + const tabCounts = { + All: EVENTS.length, + Saved: EVENTS.filter((e) => e.tab.includes('Saved')).length, + Created: EVENTS.filter((e) => e.tab.includes('Created')).length, + }; + + const query = search.trim().toLowerCase(); + + const filtered = EVENTS + .filter((e) => { + if (!e.tab.includes(activeTab)) return false; + if (query && !e.title.toLowerCase().includes(query)) return false; + if (selectedCategories.length > 0 && !selectedCategories.includes(e.category)) return false; + return true; + }) + .sort((a, b) => { + if (sort === 'Date (soonest)') return new Date(a.dateISO).getTime() - new Date(b.dateISO).getTime(); + if (sort === 'Date (latest)') return new Date(b.dateISO).getTime() - new Date(a.dateISO).getTime(); + if (sort === 'Recently added') return Number(b.id) - Number(a.id); + return 0; + }); + + function toggleCategory(cat: Category) { + setSelectedCategories((prev) => + prev.includes(cat) ? prev.filter((c) => c !== cat) : [...prev, cat] + ); + } + + const categoryLabel = + selectedCategories.length === 0 + ? 'Category' + : selectedCategories.length === 1 + ? selectedCategories[0] + : `${selectedCategories.length} categories`; + + return ( +
+ {/* Header */} +
+

+ My Events +

+
+ + {/* Tabs */} +
+ {(['All', 'Saved', 'Created'] as Tab[]).map((tab) => ( + + ))} +
+ + {/* Search + Filters */} +
+ {/* Search */} +
+ + + + + setSearch(e.target.value)} + style={{ + width: '100%', + padding: '8px 10px 8px 32px', + border: `1px solid ${themeColors.gray300}`, + borderRadius: 6, + fontSize: 13, + background: colors.white, + color: themeColors.gray800, + outline: 'none', + boxSizing: 'border-box', + }} + /> +
+ + {/* Sort Dropdown */} +
+ + {sortOpen && ( + + {SORT_OPTIONS.map((opt) => ( + { setSort(opt); setSortOpen(false); }} + /> + ))} + + )} +
+ + {/* Category Dropdown */} +
+ + {categoryOpen && ( + + {selectedCategories.length > 0 && ( +
setSelectedCategories([])} + style={{ + padding: '8px 12px', + fontSize: 12, + cursor: 'pointer', + color: TEAL, + fontWeight: 600, + borderBottom: `1px solid #f0f0f0`, + }} + > + Clear all +
+ )} + {ALL_CATEGORIES.map((cat) => { + const isSelected = selectedCategories.includes(cat); + const style = CATEGORY_COLORS[cat]; + return ( +
toggleCategory(cat)} + style={{ + padding: '8px 12px', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + gap: 8, + background: isSelected ? '#f9f9f9' : 'transparent', + }} + > + + + {cat} + + {isSelected && ( + + + + )} +
+ ); + })} +
+ )} +
+
+ + {/* Active category chips */} + {selectedCategories.length > 0 && ( +
+ {selectedCategories.map((cat) => { + const s = CATEGORY_COLORS[cat]; + return ( + toggleCategory(cat)} + style={{ + display: 'inline-flex', + alignItems: 'center', + gap: 4, + fontSize: 11, + fontWeight: 600, + background: s.bg, + color: s.text, + padding: '3px 8px', + borderRadius: 20, + cursor: 'pointer', + letterSpacing: '0.03em', + }} + > + {cat} + + + + + ); + })} +
+ )} + + {/* Grid */} + {filtered.length === 0 ? ( +
+ No events match your filters. +
+ ) : ( +
+ {filtered.map((event) => ( + + ))} +
+ )} +
+ ); +} + +// ── Dropdown helpers ────────────────────────────────────────── + +function dropdownButtonStyle( + themeColors: any, + colors: any, + isOpen: boolean, + teal: string, + hasActive = false +) { + return { + padding: '7px 12px', + border: `1px solid ${hasActive || isOpen ? teal : themeColors.gray300}`, + borderRadius: 6, + background: hasActive ? `${teal}10` : colors.white, + cursor: 'pointer', + color: hasActive ? teal : themeColors.gray700, + fontSize: 13, + fontWeight: hasActive ? 600 : 400, + display: 'flex', + alignItems: 'center', + whiteSpace: 'nowrap' as const, + transition: 'border-color 0.15s, background 0.15s', + }; +} + +function DropdownMenu({ children, minWidth = 160 }: { children: React.ReactNode; minWidth?: number }) { + return ( +
+ {children} +
+ ); +} + +function DropdownItem({ + label, + selected, + themeColors, + teal, + onClick, +}: { + label: string; + selected: boolean; + themeColors: any; + teal: string; + onClick: () => void; +}) { + return ( +
+ {label} + {selected && ( + + + + )} +
+ ); +} + +// ── Event Card ──────────────────────────────────────────────── + +function EventCard({ + event, + themeColors, + colors, +}: { + event: Event; + themeColors: any; + colors: any; + teal: string; +}) { + const catStyle = CATEGORY_COLORS[event.category]; + + return ( +
{ + (e.currentTarget as HTMLDivElement).style.boxShadow = '0 4px 16px rgba(0,0,0,0.09)'; + (e.currentTarget as HTMLDivElement).style.borderColor = themeColors.gray300; + }} + onMouseLeave={(e) => { + (e.currentTarget as HTMLDivElement).style.boxShadow = 'none'; + (e.currentTarget as HTMLDivElement).style.borderColor = themeColors.gray200; + }} + > +
+ {/* Date + Category + Star */} +
+
+ + {event.displayDate} + + + {event.category} + +
+ {event.isStarred && ( + + + + )} +
+ + {/* Title */} +
+ {event.title} +
+ + {/* Time + Location */} + {(event.timeRange || event.location) && ( +
+ {event.timeRange && ( + <> + + + + {event.timeRange} + + )} + {event.location && ( + <> + + + + + {event.location} + + )} +
+ )} + + {/* Description */} + {event.description && ( +
+ {event.description} +
+ )} +
+ + {/* Footer */} +
+ {event.interested > 0 ? ( + + {event.interested} interested + + ) : ( + + )} + {event.isOwned && ( + + )} +
+
+ ); +} \ No newline at end of file From e860e0c15cb13855f3d86158a1d637c652d0eb72 Mon Sep 17 00:00:00 2001 From: Karen Gao Date: Wed, 22 Apr 2026 20:21:49 -0400 Subject: [PATCH 4/6] add button under profile dropdown for checking events --- frontend/lib/hoagie-ui/ProfileCard/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/lib/hoagie-ui/ProfileCard/index.tsx b/frontend/lib/hoagie-ui/ProfileCard/index.tsx index e2db00b..39f5627 100644 --- a/frontend/lib/hoagie-ui/ProfileCard/index.tsx +++ b/frontend/lib/hoagie-ui/ProfileCard/index.tsx @@ -40,8 +40,11 @@ export function ProfileCard({ user }: { user: User }) { {email} + + + - + ); From a83a85e6723856593575c27ce1343819514e8b8b Mon Sep 17 00:00:00 2001 From: Karen Gao Date: Wed, 22 Apr 2026 20:23:55 -0400 Subject: [PATCH 5/6] delete savedEvents directory --- frontend/app/my-events/page.tsx | 2 +- frontend/components/myEvents/MyEvents.tsx | 701 +++++++++++++---- frontend/components/myEvents/myEvents2.tsx | 723 ------------------ .../components/savedEvents/SavedEvents.tsx | 564 -------------- 4 files changed, 537 insertions(+), 1453 deletions(-) delete mode 100644 frontend/components/myEvents/myEvents2.tsx delete mode 100644 frontend/components/savedEvents/SavedEvents.tsx diff --git a/frontend/app/my-events/page.tsx b/frontend/app/my-events/page.tsx index 4edbe53..dca8548 100644 --- a/frontend/app/my-events/page.tsx +++ b/frontend/app/my-events/page.tsx @@ -1,4 +1,4 @@ -import MyEvents from '@/components/myEvents/myEvents2'; +import MyEvents from '@/components/myEvents/MyEvents'; export default function Page() { return ; diff --git a/frontend/components/myEvents/MyEvents.tsx b/frontend/components/myEvents/MyEvents.tsx index bd36ec7..cdf8375 100644 --- a/frontend/components/myEvents/MyEvents.tsx +++ b/frontend/components/myEvents/MyEvents.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { useTheme } from 'evergreen-ui'; @@ -19,6 +19,28 @@ type Category = | 'HOUSING' | 'OTHER'; +const ALL_CATEGORIES: Category[] = [ + 'SPORTS', + 'ACADEMIC', + 'SOCIAL', + 'FOOD', + 'ARTS', + 'CAREER', + 'HOUSING', + 'OTHER', +]; + +const CATEGORY_COLORS: Record = { + SPORTS: { bg: '#E8F5E9', text: '#2E7D32' }, + ACADEMIC: { bg: '#E3F2FD', text: '#1565C0' }, + SOCIAL: { bg: '#F3E5F5', text: '#6A1B9A' }, + FOOD: { bg: '#FFF3E0', text: '#E65100' }, + ARTS: { bg: '#FCE4EC', text: '#AD1457' }, + CAREER: { bg: '#E8EAF6', text: '#283593' }, + HOUSING: { bg: '#E0F2F1', text: '#00695C' }, + OTHER: { bg: '#F5F5F5', text: '#424242' }, +}; + interface Event { id: string; title: string; @@ -39,52 +61,83 @@ interface Event { const EVENTS: Event[] = [ { id: '1', - title: 'Campus YMCA Yoga', - dateISO: '2026-04-01', - displayDate: 'WED, APR 1', - timeRange: '4:30 PM – 5:30 PM', - location: 'Dillon Gymnasium', - description: 'All-level yoga class.', - interested: 45, - category: 'SPORTS', + title: 'Mock Interview Night', + dateISO: '2026-04-06', + displayDate: 'MON, APR 6', + timeRange: '6 PM – 8 PM', + location: 'Robertson Hall', + description: + 'Practice behavioral and technical interviews with alumni volunteers from top tech and finance firms. Sign up for ...', + interested: 29, + category: 'CAREER', tab: ['All', 'Saved'], isStarred: true, }, { id: '2', - title: 'Debate Club Practice', - dateISO: '2026-04-01', - displayDate: 'WED, APR 1', - timeRange: '4:30 PM – 6 PM', - location: '1879 Hall', - description: 'Weekly debate practice.', - interested: 18, + title: 'AI at Princeton: LLM Fine-Tuning Workshop', + dateISO: '2026-04-07', + displayDate: 'TUE, APR 7', + timeRange: '6 PM – 8 PM', + location: 'COS Building Room 302', + description: + 'Hands-on workshop on fine-tuning large language models using LoRA and QLoRA. Bring your laptop with ...', + interested: 118, category: 'ACADEMIC', tab: ['All', 'Saved'], isStarred: true, }, { id: '3', - title: 'Midnight Coding Marathon', - dateISO: '2026-04-02', - displayDate: 'THU, APR 2', - timeRange: '11 PM – 2 AM', - location: 'CS Building', - description: 'Build something cool!', - interested: 42, - category: 'ACADEMIC', - tab: ['All'], + title: 'Preview Day', + dateISO: '2026-04-08', + displayDate: 'WED, APR 8', + timeRange: '', + location: 'University-wide', + description: + 'Princeton Preview Day for admitted students. Campus tours, info sessions, and class visits across all...', + interested: 0, + category: 'SOCIAL', + tab: ['All', 'Saved'], + isStarred: true, }, { id: '4', - title: 'My Private Event', - dateISO: '2026-04-03', - displayDate: 'FRI, APR 3', - timeRange: '12 PM – 1 PM', - location: '', - description: '', - interested: 0, - category: 'SOCIAL', + title: 'Campus YMCA Yoga', + dateISO: '2026-04-10', + displayDate: 'FRI, APR 10', + timeRange: '4:30 PM – 5:30 PM', + location: 'Dillon Gymnasium', + description: 'All-level yoga class. No experience needed — just bring a mat!', + interested: 45, + category: 'SPORTS', + tab: ['All', 'Created'], + isOwned: true, + }, + { + id: '5', + title: 'Spring Fling Food Festival', + dateISO: '2026-04-12', + displayDate: 'SUN, APR 12', + timeRange: '12 PM – 4 PM', + location: 'Prospect Garden', + description: + 'Student-run food fair with dishes from 20+ cuisines. Live music and lawn games included.', + interested: 203, + category: 'FOOD', + tab: ['All', 'Created'], + isOwned: true, + }, + { + id: '6', + title: 'A Cappella Showcase', + dateISO: '2026-04-15', + displayDate: 'WED, APR 15', + timeRange: '7 PM – 9 PM', + location: 'Richardson Auditorium', + description: 'Six Princeton a cappella groups perform in this semester-end showcase.', + interested: 87, + category: 'ARTS', tab: ['All', 'Created'], isOwned: true, }, @@ -98,20 +151,42 @@ const SORT_OPTIONS: SortOption[] = [ 'Recently added', ]; +// ── Dropdown Hook ───────────────────────────────────────────── + +function useClickOutside(ref: React.RefObject, cb: () => void) { + useEffect(() => { + function handler(e: MouseEvent) { + if (ref.current && !ref.current.contains(e.target as Node)) cb(); + } + document.addEventListener('mousedown', handler); + return () => document.removeEventListener('mousedown', handler); + }, [ref, cb]); +} + // ── Component ───────────────────────────────────────────────── export default function MyEvents() { const { colors } = useTheme(); const themeColors = colors as any; - const [activeTab, setActiveTab] = useState('All'); - const [search, setSearch] = useState(''); - const [sort, setSort] = useState('Date (soonest)'); - const [sortOpen, setSortOpen] = useState(false); + const TEAL = themeColors['hoagie-teal'] ?? '#00897B'; + + const [activeTab, setActiveTab] = useState('All'); + const [search, setSearch] = useState(''); + const [sort, setSort] = useState('Date (soonest)'); + const [sortOpen, setSortOpen] = useState(false); + const [categoryOpen, setCategoryOpen] = useState(false); + const [selectedCategories, setSelectedCategories] = useState([]); + + const sortRef = useRef(null!); + const categoryRef = useRef(null!); + + useClickOutside(sortRef, () => setSortOpen(false)); + useClickOutside(categoryRef, () => setCategoryOpen(false)); const tabCounts = { - All: EVENTS.length, - Saved: EVENTS.filter((e) => e.tab.includes('Saved')).length, + All: EVENTS.length, + Saved: EVENTS.filter((e) => e.tab.includes('Saved')).length, Created: EVENTS.filter((e) => e.tab.includes('Created')).length, }; @@ -119,45 +194,42 @@ export default function MyEvents() { const filtered = EVENTS .filter((e) => { - const matchesTab = e.tab.includes(activeTab); - const matchesSearch = - query === '' || e.title.toLowerCase().includes(query); - - return matchesTab && matchesSearch; + if (!e.tab.includes(activeTab)) return false; + if (query && !e.title.toLowerCase().includes(query)) return false; + if (selectedCategories.length > 0 && !selectedCategories.includes(e.category)) return false; + return true; }) .sort((a, b) => { - if (sort === 'Date (soonest)') { - return new Date(a.dateISO).getTime() - new Date(b.dateISO).getTime(); - } - if (sort === 'Date (latest)') { - return new Date(b.dateISO).getTime() - new Date(a.dateISO).getTime(); - } - if (sort === 'Recently added') { - return Number(b.id) - Number(a.id); - } + if (sort === 'Date (soonest)') return new Date(a.dateISO).getTime() - new Date(b.dateISO).getTime(); + if (sort === 'Date (latest)') return new Date(b.dateISO).getTime() - new Date(a.dateISO).getTime(); + if (sort === 'Recently added') return Number(b.id) - Number(a.id); return 0; }); + function toggleCategory(cat: Category) { + setSelectedCategories((prev) => + prev.includes(cat) ? prev.filter((c) => c !== cat) : [...prev, cat] + ); + } + + const categoryLabel = + selectedCategories.length === 0 + ? 'Category' + : selectedCategories.length === 1 + ? selectedCategories[0] + : `${selectedCategories.length} categories`; + return ( -
-

- My Events -

+
+ {/* Header */} +
+

+ My Events +

+
{/* Tabs */} -
+
{(['All', 'Saved', 'Created'] as Tab[]).map((tab) => (
- {/* Search + Sort */} -
- setSearch(e.target.value)} - style={{ - flex: 1, - padding: 8, - border: `1px solid ${themeColors.gray300}`, - borderRadius: 6, - background: colors.white, - color: themeColors.gray800, - }} - /> - -
- - {sortOpen && ( -
+ {SORT_OPTIONS.map((opt) => ( -
{ - setSort(opt); - setSortOpen(false); - }} + label={opt} + selected={sort === opt} + themeColors={themeColors} + teal={TEAL} + onClick={() => { setSort(opt); setSortOpen(false); }} + /> + ))} + + )} +
+ + {/* Category Dropdown */} +
+ + {categoryOpen && ( + + {selectedCategories.length > 0 && ( +
setSelectedCategories([])} style={{ - padding: 10, + padding: '8px 12px', + fontSize: 12, cursor: 'pointer', - color: themeColors.gray800, + color: TEAL, + fontWeight: 600, + borderBottom: `1px solid #f0f0f0`, }} > - {opt} + Clear all
- ))} -
+ )} + {ALL_CATEGORIES.map((cat) => { + const isSelected = selectedCategories.includes(cat); + const style = CATEGORY_COLORS[cat]; + return ( +
toggleCategory(cat)} + style={{ + padding: '8px 12px', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + gap: 8, + background: isSelected ? '#f9f9f9' : 'transparent', + }} + > + + + {cat} + + {isSelected && ( + + + + )} +
+ ); + })} +
)}
+ {/* Active category chips */} + {selectedCategories.length > 0 && ( +
+ {selectedCategories.map((cat) => { + const s = CATEGORY_COLORS[cat]; + return ( + toggleCategory(cat)} + style={{ + display: 'inline-flex', + alignItems: 'center', + gap: 4, + fontSize: 11, + fontWeight: 600, + background: s.bg, + color: s.text, + padding: '3px 8px', + borderRadius: 20, + cursor: 'pointer', + letterSpacing: '0.03em', + }} + > + {cat} + + + + + ); + })} +
+ )} + {/* Grid */} -
- {filtered.map((event) => ( - - ))} -
+ {filtered.length === 0 ? ( +
+ No events match your filters. +
+ ) : ( +
+ {filtered.map((event) => ( + + ))} +
+ )} +
+ ); +} + +// ── Dropdown helpers ────────────────────────────────────────── + +function dropdownButtonStyle( + themeColors: any, + colors: any, + isOpen: boolean, + teal: string, + hasActive = false +) { + return { + padding: '7px 12px', + border: `1px solid ${hasActive || isOpen ? teal : themeColors.gray300}`, + borderRadius: 6, + background: hasActive ? `${teal}10` : colors.white, + cursor: 'pointer', + color: hasActive ? teal : themeColors.gray700, + fontSize: 13, + fontWeight: hasActive ? 600 : 400, + display: 'flex', + alignItems: 'center', + whiteSpace: 'nowrap' as const, + transition: 'border-color 0.15s, background 0.15s', + }; +} + +function DropdownMenu({ children, minWidth = 160 }: { children: React.ReactNode; minWidth?: number }) { + return ( +
+ {children} +
+ ); +} + +function DropdownItem({ + label, + selected, + themeColors, + teal, + onClick, +}: { + label: string; + selected: boolean; + themeColors: any; + teal: string; + onClick: () => void; +}) { + return ( +
+ {label} + {selected && ( + + + + )}
); } @@ -280,7 +580,10 @@ function EventCard({ event: Event; themeColors: any; colors: any; + teal: string; }) { + const catStyle = CATEGORY_COLORS[event.category]; + return (
{ + (e.currentTarget as HTMLDivElement).style.boxShadow = '0 4px 16px rgba(0,0,0,0.09)'; + (e.currentTarget as HTMLDivElement).style.borderColor = themeColors.gray300; + }} + onMouseLeave={(e) => { + (e.currentTarget as HTMLDivElement).style.boxShadow = 'none'; + (e.currentTarget as HTMLDivElement).style.borderColor = themeColors.gray200; }} > -
-
- {event.displayDate} +
+ {/* Date + Category + Star */} +
+
+ + {event.displayDate} + + + {event.category} + +
+ {event.isStarred && ( + + + + )}
-
+ {/* Title */} +
{event.title}
+ {/* Time + Location */} {(event.timeRange || event.location) && (
- {event.timeRange} - {event.location && ` • ${event.location}`} + {event.timeRange && ( + <> + + + + {event.timeRange} + + )} + {event.location && ( + <> + + + + + {event.location} + + )}
)} + {/* Description */} {event.description && (
{event.description}
)} +
+ {/* Footer */} +
+ {event.interested > 0 ? ( + + {event.interested} interested + + ) : ( + + )} {event.isOwned && ( - ))} -
- - {/* Search + Filters */} -
- {/* Search */} -
- - - - - setSearch(e.target.value)} - style={{ - width: '100%', - padding: '8px 10px 8px 32px', - border: `1px solid ${themeColors.gray300}`, - borderRadius: 6, - fontSize: 13, - background: colors.white, - color: themeColors.gray800, - outline: 'none', - boxSizing: 'border-box', - }} - /> -
- - {/* Sort Dropdown */} -
- - {sortOpen && ( - - {SORT_OPTIONS.map((opt) => ( - { setSort(opt); setSortOpen(false); }} - /> - ))} - - )} -
- - {/* Category Dropdown */} -
- - {categoryOpen && ( - - {selectedCategories.length > 0 && ( -
setSelectedCategories([])} - style={{ - padding: '8px 12px', - fontSize: 12, - cursor: 'pointer', - color: TEAL, - fontWeight: 600, - borderBottom: `1px solid #f0f0f0`, - }} - > - Clear all -
- )} - {ALL_CATEGORIES.map((cat) => { - const isSelected = selectedCategories.includes(cat); - const style = CATEGORY_COLORS[cat]; - return ( -
toggleCategory(cat)} - style={{ - padding: '8px 12px', - cursor: 'pointer', - display: 'flex', - alignItems: 'center', - gap: 8, - background: isSelected ? '#f9f9f9' : 'transparent', - }} - > - - - {cat} - - {isSelected && ( - - - - )} -
- ); - })} -
- )} -
-
- - {/* Active category chips */} - {selectedCategories.length > 0 && ( -
- {selectedCategories.map((cat) => { - const s = CATEGORY_COLORS[cat]; - return ( - toggleCategory(cat)} - style={{ - display: 'inline-flex', - alignItems: 'center', - gap: 4, - fontSize: 11, - fontWeight: 600, - background: s.bg, - color: s.text, - padding: '3px 8px', - borderRadius: 20, - cursor: 'pointer', - letterSpacing: '0.03em', - }} - > - {cat} - - - - - ); - })} -
- )} - - {/* Grid */} - {filtered.length === 0 ? ( -
- No events match your filters. -
- ) : ( -
- {filtered.map((event) => ( - - ))} -
- )} -
- ); -} - -// ── Dropdown helpers ────────────────────────────────────────── - -function dropdownButtonStyle( - themeColors: any, - colors: any, - isOpen: boolean, - teal: string, - hasActive = false -) { - return { - padding: '7px 12px', - border: `1px solid ${hasActive || isOpen ? teal : themeColors.gray300}`, - borderRadius: 6, - background: hasActive ? `${teal}10` : colors.white, - cursor: 'pointer', - color: hasActive ? teal : themeColors.gray700, - fontSize: 13, - fontWeight: hasActive ? 600 : 400, - display: 'flex', - alignItems: 'center', - whiteSpace: 'nowrap' as const, - transition: 'border-color 0.15s, background 0.15s', - }; -} - -function DropdownMenu({ children, minWidth = 160 }: { children: React.ReactNode; minWidth?: number }) { - return ( -
- {children} -
- ); -} - -function DropdownItem({ - label, - selected, - themeColors, - teal, - onClick, -}: { - label: string; - selected: boolean; - themeColors: any; - teal: string; - onClick: () => void; -}) { - return ( -
- {label} - {selected && ( - - - - )} -
- ); -} - -// ── Event Card ──────────────────────────────────────────────── - -function EventCard({ - event, - themeColors, - colors, -}: { - event: Event; - themeColors: any; - colors: any; - teal: string; -}) { - const catStyle = CATEGORY_COLORS[event.category]; - - return ( -
{ - (e.currentTarget as HTMLDivElement).style.boxShadow = '0 4px 16px rgba(0,0,0,0.09)'; - (e.currentTarget as HTMLDivElement).style.borderColor = themeColors.gray300; - }} - onMouseLeave={(e) => { - (e.currentTarget as HTMLDivElement).style.boxShadow = 'none'; - (e.currentTarget as HTMLDivElement).style.borderColor = themeColors.gray200; - }} - > -
- {/* Date + Category + Star */} -
-
- - {event.displayDate} - - - {event.category} - -
- {event.isStarred && ( - - - - )} -
- - {/* Title */} -
- {event.title} -
- - {/* Time + Location */} - {(event.timeRange || event.location) && ( -
- {event.timeRange && ( - <> - - - - {event.timeRange} - - )} - {event.location && ( - <> - - - - - {event.location} - - )} -
- )} - - {/* Description */} - {event.description && ( -
- {event.description} -
- )} -
- - {/* Footer */} -
- {event.interested > 0 ? ( - - {event.interested} interested - - ) : ( - - )} - {event.isOwned && ( - - )} -
-
- ); -} \ No newline at end of file diff --git a/frontend/components/savedEvents/SavedEvents.tsx b/frontend/components/savedEvents/SavedEvents.tsx deleted file mode 100644 index 1c1f968..0000000 --- a/frontend/components/savedEvents/SavedEvents.tsx +++ /dev/null @@ -1,564 +0,0 @@ -'use client'; - -import { useState } from 'react'; - -import { useTheme } from 'evergreen-ui'; - -// ── Types ──────────────────────────────────────────────────────────────────── - -type Tab = 'All' | 'Saved' | 'Going' | 'Created'; -type SortOption = 'Date (soonest)' | 'Recently added' | 'Category'; -type Category = 'SPORTS' | 'ACADEMIC' | 'SOCIAL' | 'FOOD' | 'ARTS' | 'CAREER' | 'HOUSING' | 'OTHER'; - -interface SavedEvent { - id: string; - title: string; - date: string; // e.g. "WED, APR 1" - timeRange: string; // e.g. "4:30 PM – 5:30 PM" - location: string; - description: string; - interested: number; - category: Category; - tab: Tab[]; // which tabs this event appears under - isStarred?: boolean; - isOwned?: boolean; // show Edit button - accentColor: string; -} - -// ── Mock data ──────────────────────────────────────────────────────────────── - -const MOCK_EVENTS: SavedEvent[] = [ - { - id: '1', - title: 'Campus YMCA Yoga', - date: 'WED, APR 1', - timeRange: '4:30 PM – 5:30 PM', - location: 'Dillon Gymnasium Dance Studio', - description: - 'All-levels yoga class open to all Princeton students, staff, and faculty. Mats provided. Wear comfortable clothes.', - interested: 45, - category: 'SPORTS', - tab: ['All', 'Saved'], - isStarred: true, - accentColor: '#22c55e', - }, - { - id: '2', - title: 'Debate Club Practice', - date: 'WED, APR 1', - timeRange: '4:30 PM – 6 PM', - location: '1879 Hall', - description: - 'Princeton Debate Panel weekly practice session. Open to all levels — beginners welcome. Practice British Parliamentary...', - interested: 18, - category: 'ACADEMIC', - tab: ['All', 'Saved'], - isStarred: true, - accentColor: '#3b82f6', - }, - { - id: '3', - title: 'Midnight Coding Marathon', - date: 'THU, APR 2', - timeRange: '11 PM – 2 AM', - location: 'CS Building, Room 104', - description: - 'All-night coding session. Snacks provided! Build something cool from 11 PM to 2 AM.', - interested: 42, - category: 'ACADEMIC', - tab: ['All', 'Going'], - isStarred: true, - accentColor: '#3b82f6', - }, - { - id: '4', - title: 'test', - date: 'THU, APR 2', - timeRange: '12 PM – 1 PM', - location: '', - description: '', - interested: 0, - category: 'SOCIAL', - tab: ['All', 'Created'], - isOwned: true, - accentColor: '#ef4444', - }, - { - id: '5', - title: 'Volleyball Championship', - date: 'SAT, APR 4', - timeRange: '2 PM – 5 PM', - location: 'Dillon Gymnasium', - description: 'Intramural volleyball championship finals. Come cheer on your fellow Tigers!', - interested: 38, - category: 'SPORTS', - tab: ['All', 'Going'], - isStarred: true, - accentColor: '#22c55e', - }, -]; - -const CATEGORY_COLORS: Record = { - SPORTS: '#22c55e', - ACADEMIC: '#3b82f6', - SOCIAL: '#ef4444', - FOOD: '#f97316', - ARTS: '#a855f7', - CAREER: '#6b7280', - HOUSING: '#f59e0b', - OTHER: '#14b8a6', -}; - -const SORT_OPTIONS: SortOption[] = ['Date (soonest)', 'Recently added', 'Category']; - -// ── Component ──────────────────────────────────────────────────────────────── - -export default function SavedEvents() { - const { colors } = useTheme(); - const themeColors = colors as unknown as Record; - - const [activeTab, setActiveTab] = useState('All'); - const [search, setSearch] = useState(''); - const [sort, setSort] = useState('Date (soonest)'); - const [sortOpen, setSortOpen] = useState(false); - - const tabCounts: Record = { - All: MOCK_EVENTS.length, - Saved: MOCK_EVENTS.filter((e) => e.tab.includes('Saved')).length, - Going: MOCK_EVENTS.filter((e) => e.tab.includes('Going')).length, - Created: MOCK_EVENTS.filter((e) => e.tab.includes('Created')).length, - }; - - const filtered = MOCK_EVENTS.filter((e) => { - const matchesTab = e.tab.includes(activeTab); - const matchesSearch = - search === '' || - e.title.toLowerCase().includes(search.toLowerCase()) || - e.location.toLowerCase().includes(search.toLowerCase()); - return matchesTab && matchesSearch; - }); - - return ( -
- {/* ── Header ── */} -
-

- Saved Events -

- - {/* Count badge */} -
- {tabCounts[activeTab]} -
-
- - {/* ── Tabs ── */} -
- {(['All', 'Saved', 'Going', 'Created'] as Tab[]).map((tab) => ( - - ))} -
- - {/* ── Search + Sort ── */} -
- {/* Search */} -
- 🔍 - setSearch(e.target.value)} - style={{ - border: 'none', - outline: 'none', - flex: 1, - fontSize: 14, - color: themeColors.gray800, - background: 'transparent', - }} - /> -
- - {/* Sort dropdown */} -
- - - {sortOpen && ( -
- {SORT_OPTIONS.map((option) => ( - - ))} -
- )} -
-
- - {/* ── Event cards grid ── */} -
- {filtered.length === 0 ? ( -
- No events found. -
- ) : ( -
- {filtered.map((event) => ( - - ))} -
- )} -
-
- ); -} - -// ── EventCard ──────────────────────────────────────────────────────────────── - -function EventCard({ - event, - themeColors, - colors, -}: { - event: SavedEvent; - themeColors: Record; - colors: Record; -}) { - const categoryColor = CATEGORY_COLORS[event.category]; - - return ( -
- {/* Colored top accent bar */} -
- -
- {/* Date + category + star row */} -
-
- - {event.date} - - - {event.category} - -
- - {event.isOwned ? ( - - ) : event.isStarred ? ( - - ) : null} -
- - {/* Title */} -
- {event.title} -
- - {/* Time + location */} - {(event.timeRange || event.location) && ( -
- {event.timeRange && ( -
- 🕐 - {event.timeRange} - {event.location && ( - <> - · - 📍 - {event.location} - - )} -
- )} -
- )} - - {/* Description */} - {event.description && ( -
- {event.description} -
- )} - - {/* Interested count */} - {event.interested > 0 && ( -
- {event.interested} interested -
- )} -
-
- ); -} From c26d3955d0e7d326ccdc2c48548e76b37d096970 Mon Sep 17 00:00:00 2001 From: Karen Gao Date: Wed, 22 Apr 2026 20:30:48 -0400 Subject: [PATCH 6/6] used Prettier to fix formatting --- frontend/app/my-events/page.tsx | 4 +- frontend/components/myEvents/MyEvents.tsx | 1449 ++++++++++-------- frontend/lib/hoagie-ui/ProfileCard/index.tsx | 8 +- 3 files changed, 791 insertions(+), 670 deletions(-) diff --git a/frontend/app/my-events/page.tsx b/frontend/app/my-events/page.tsx index dca8548..4f5ed07 100644 --- a/frontend/app/my-events/page.tsx +++ b/frontend/app/my-events/page.tsx @@ -1,5 +1,5 @@ import MyEvents from '@/components/myEvents/MyEvents'; export default function Page() { - return ; -} \ No newline at end of file + return ; +} diff --git a/frontend/components/myEvents/MyEvents.tsx b/frontend/components/myEvents/MyEvents.tsx index cdf8375..2046027 100644 --- a/frontend/components/myEvents/MyEvents.tsx +++ b/frontend/components/myEvents/MyEvents.tsx @@ -9,715 +9,832 @@ import { useTheme } from 'evergreen-ui'; type Tab = 'All' | 'Saved' | 'Created'; type SortOption = 'Date (soonest)' | 'Date (latest)' | 'Recently added'; -type Category = - | 'SPORTS' - | 'ACADEMIC' - | 'SOCIAL' - | 'FOOD' - | 'ARTS' - | 'CAREER' - | 'HOUSING' - | 'OTHER'; +type Category = 'SPORTS' | 'ACADEMIC' | 'SOCIAL' | 'FOOD' | 'ARTS' | 'CAREER' | 'HOUSING' | 'OTHER'; const ALL_CATEGORIES: Category[] = [ - 'SPORTS', - 'ACADEMIC', - 'SOCIAL', - 'FOOD', - 'ARTS', - 'CAREER', - 'HOUSING', - 'OTHER', + 'SPORTS', + 'ACADEMIC', + 'SOCIAL', + 'FOOD', + 'ARTS', + 'CAREER', + 'HOUSING', + 'OTHER', ]; const CATEGORY_COLORS: Record = { - SPORTS: { bg: '#E8F5E9', text: '#2E7D32' }, - ACADEMIC: { bg: '#E3F2FD', text: '#1565C0' }, - SOCIAL: { bg: '#F3E5F5', text: '#6A1B9A' }, - FOOD: { bg: '#FFF3E0', text: '#E65100' }, - ARTS: { bg: '#FCE4EC', text: '#AD1457' }, - CAREER: { bg: '#E8EAF6', text: '#283593' }, - HOUSING: { bg: '#E0F2F1', text: '#00695C' }, - OTHER: { bg: '#F5F5F5', text: '#424242' }, + SPORTS: { bg: '#E8F5E9', text: '#2E7D32' }, + ACADEMIC: { bg: '#E3F2FD', text: '#1565C0' }, + SOCIAL: { bg: '#F3E5F5', text: '#6A1B9A' }, + FOOD: { bg: '#FFF3E0', text: '#E65100' }, + ARTS: { bg: '#FCE4EC', text: '#AD1457' }, + CAREER: { bg: '#E8EAF6', text: '#283593' }, + HOUSING: { bg: '#E0F2F1', text: '#00695C' }, + OTHER: { bg: '#F5F5F5', text: '#424242' }, }; interface Event { - id: string; - title: string; - dateISO: string; - displayDate: string; - timeRange: string; - location: string; - description: string; - interested: number; - category: Category; - tab: Tab[]; - isStarred?: boolean; - isOwned?: boolean; + id: string; + title: string; + dateISO: string; + displayDate: string; + timeRange: string; + location: string; + description: string; + interested: number; + category: Category; + tab: Tab[]; + isStarred?: boolean; + isOwned?: boolean; } // ── Mock Data ───────────────────────────────────────────────── const EVENTS: Event[] = [ - { - id: '1', - title: 'Mock Interview Night', - dateISO: '2026-04-06', - displayDate: 'MON, APR 6', - timeRange: '6 PM – 8 PM', - location: 'Robertson Hall', - description: - 'Practice behavioral and technical interviews with alumni volunteers from top tech and finance firms. Sign up for ...', - interested: 29, - category: 'CAREER', - tab: ['All', 'Saved'], - isStarred: true, - }, - { - id: '2', - title: 'AI at Princeton: LLM Fine-Tuning Workshop', - dateISO: '2026-04-07', - displayDate: 'TUE, APR 7', - timeRange: '6 PM – 8 PM', - location: 'COS Building Room 302', - description: - 'Hands-on workshop on fine-tuning large language models using LoRA and QLoRA. Bring your laptop with ...', - interested: 118, - category: 'ACADEMIC', - tab: ['All', 'Saved'], - isStarred: true, - }, - { - id: '3', - title: 'Preview Day', - dateISO: '2026-04-08', - displayDate: 'WED, APR 8', - timeRange: '', - location: 'University-wide', - description: - 'Princeton Preview Day for admitted students. Campus tours, info sessions, and class visits across all...', - interested: 0, - category: 'SOCIAL', - tab: ['All', 'Saved'], - isStarred: true, - }, - { - id: '4', - title: 'Campus YMCA Yoga', - dateISO: '2026-04-10', - displayDate: 'FRI, APR 10', - timeRange: '4:30 PM – 5:30 PM', - location: 'Dillon Gymnasium', - description: 'All-level yoga class. No experience needed — just bring a mat!', - interested: 45, - category: 'SPORTS', - tab: ['All', 'Created'], - isOwned: true, - }, - { - id: '5', - title: 'Spring Fling Food Festival', - dateISO: '2026-04-12', - displayDate: 'SUN, APR 12', - timeRange: '12 PM – 4 PM', - location: 'Prospect Garden', - description: - 'Student-run food fair with dishes from 20+ cuisines. Live music and lawn games included.', - interested: 203, - category: 'FOOD', - tab: ['All', 'Created'], - isOwned: true, - }, - { - id: '6', - title: 'A Cappella Showcase', - dateISO: '2026-04-15', - displayDate: 'WED, APR 15', - timeRange: '7 PM – 9 PM', - location: 'Richardson Auditorium', - description: 'Six Princeton a cappella groups perform in this semester-end showcase.', - interested: 87, - category: 'ARTS', - tab: ['All', 'Created'], - isOwned: true, - }, + { + id: '1', + title: 'Mock Interview Night', + dateISO: '2026-04-06', + displayDate: 'MON, APR 6', + timeRange: '6 PM – 8 PM', + location: 'Robertson Hall', + description: + 'Practice behavioral and technical interviews with alumni volunteers from top tech and finance firms. Sign up for ...', + interested: 29, + category: 'CAREER', + tab: ['All', 'Saved'], + isStarred: true, + }, + { + id: '2', + title: 'AI at Princeton: LLM Fine-Tuning Workshop', + dateISO: '2026-04-07', + displayDate: 'TUE, APR 7', + timeRange: '6 PM – 8 PM', + location: 'COS Building Room 302', + description: + 'Hands-on workshop on fine-tuning large language models using LoRA and QLoRA. Bring your laptop with ...', + interested: 118, + category: 'ACADEMIC', + tab: ['All', 'Saved'], + isStarred: true, + }, + { + id: '3', + title: 'Preview Day', + dateISO: '2026-04-08', + displayDate: 'WED, APR 8', + timeRange: '', + location: 'University-wide', + description: + 'Princeton Preview Day for admitted students. Campus tours, info sessions, and class visits across all...', + interested: 0, + category: 'SOCIAL', + tab: ['All', 'Saved'], + isStarred: true, + }, + { + id: '4', + title: 'Campus YMCA Yoga', + dateISO: '2026-04-10', + displayDate: 'FRI, APR 10', + timeRange: '4:30 PM – 5:30 PM', + location: 'Dillon Gymnasium', + description: 'All-level yoga class. No experience needed — just bring a mat!', + interested: 45, + category: 'SPORTS', + tab: ['All', 'Created'], + isOwned: true, + }, + { + id: '5', + title: 'Spring Fling Food Festival', + dateISO: '2026-04-12', + displayDate: 'SUN, APR 12', + timeRange: '12 PM – 4 PM', + location: 'Prospect Garden', + description: + 'Student-run food fair with dishes from 20+ cuisines. Live music and lawn games included.', + interested: 203, + category: 'FOOD', + tab: ['All', 'Created'], + isOwned: true, + }, + { + id: '6', + title: 'A Cappella Showcase', + dateISO: '2026-04-15', + displayDate: 'WED, APR 15', + timeRange: '7 PM – 9 PM', + location: 'Richardson Auditorium', + description: 'Six Princeton a cappella groups perform in this semester-end showcase.', + interested: 87, + category: 'ARTS', + tab: ['All', 'Created'], + isOwned: true, + }, ]; // ── Sort Options ────────────────────────────────────────────── -const SORT_OPTIONS: SortOption[] = [ - 'Date (soonest)', - 'Date (latest)', - 'Recently added', -]; +const SORT_OPTIONS: SortOption[] = ['Date (soonest)', 'Date (latest)', 'Recently added']; // ── Dropdown Hook ───────────────────────────────────────────── function useClickOutside(ref: React.RefObject, cb: () => void) { - useEffect(() => { - function handler(e: MouseEvent) { - if (ref.current && !ref.current.contains(e.target as Node)) cb(); - } - document.addEventListener('mousedown', handler); - return () => document.removeEventListener('mousedown', handler); - }, [ref, cb]); + useEffect(() => { + function handler(e: MouseEvent) { + if (ref.current && !ref.current.contains(e.target as Node)) cb(); + } + document.addEventListener('mousedown', handler); + return () => document.removeEventListener('mousedown', handler); + }, [ref, cb]); } // ── Component ───────────────────────────────────────────────── export default function MyEvents() { - const { colors } = useTheme(); - const themeColors = colors as any; - - const TEAL = themeColors['hoagie-teal'] ?? '#00897B'; - - const [activeTab, setActiveTab] = useState('All'); - const [search, setSearch] = useState(''); - const [sort, setSort] = useState('Date (soonest)'); - const [sortOpen, setSortOpen] = useState(false); - const [categoryOpen, setCategoryOpen] = useState(false); - const [selectedCategories, setSelectedCategories] = useState([]); - - const sortRef = useRef(null!); - const categoryRef = useRef(null!); - - useClickOutside(sortRef, () => setSortOpen(false)); - useClickOutside(categoryRef, () => setCategoryOpen(false)); - - const tabCounts = { - All: EVENTS.length, - Saved: EVENTS.filter((e) => e.tab.includes('Saved')).length, - Created: EVENTS.filter((e) => e.tab.includes('Created')).length, - }; - - const query = search.trim().toLowerCase(); - - const filtered = EVENTS - .filter((e) => { - if (!e.tab.includes(activeTab)) return false; - if (query && !e.title.toLowerCase().includes(query)) return false; - if (selectedCategories.length > 0 && !selectedCategories.includes(e.category)) return false; - return true; - }) - .sort((a, b) => { - if (sort === 'Date (soonest)') return new Date(a.dateISO).getTime() - new Date(b.dateISO).getTime(); - if (sort === 'Date (latest)') return new Date(b.dateISO).getTime() - new Date(a.dateISO).getTime(); - if (sort === 'Recently added') return Number(b.id) - Number(a.id); - return 0; - }); - - function toggleCategory(cat: Category) { - setSelectedCategories((prev) => - prev.includes(cat) ? prev.filter((c) => c !== cat) : [...prev, cat] - ); - } - - const categoryLabel = - selectedCategories.length === 0 - ? 'Category' - : selectedCategories.length === 1 - ? selectedCategories[0] - : `${selectedCategories.length} categories`; - - return ( -
- {/* Header */} -
-

- My Events -

-
- - {/* Tabs */} -
- {(['All', 'Saved', 'Created'] as Tab[]).map((tab) => ( - - ))} -
- - {/* Search + Filters */} -
- {/* Search */} -
- - - - - setSearch(e.target.value)} - style={{ - width: '100%', - padding: '8px 10px 8px 32px', - border: `1px solid ${themeColors.gray300}`, - borderRadius: 6, - fontSize: 13, - background: colors.white, - color: themeColors.gray800, - outline: 'none', - boxSizing: 'border-box', - }} - /> -
- - {/* Sort Dropdown */} -
- - {sortOpen && ( - - {SORT_OPTIONS.map((opt) => ( - { setSort(opt); setSortOpen(false); }} - /> - ))} - - )} -
- - {/* Category Dropdown */} -
- - {categoryOpen && ( - - {selectedCategories.length > 0 && ( -
setSelectedCategories([])} - style={{ - padding: '8px 12px', - fontSize: 12, - cursor: 'pointer', - color: TEAL, - fontWeight: 600, - borderBottom: `1px solid #f0f0f0`, - }} - > - Clear all -
- )} - {ALL_CATEGORIES.map((cat) => { - const isSelected = selectedCategories.includes(cat); - const style = CATEGORY_COLORS[cat]; - return ( -
toggleCategory(cat)} - style={{ - padding: '8px 12px', - cursor: 'pointer', - display: 'flex', - alignItems: 'center', - gap: 8, - background: isSelected ? '#f9f9f9' : 'transparent', - }} - > - - - {cat} - - {isSelected && ( - - - - )} -
- ); - })} -
- )} -
-
- - {/* Active category chips */} - {selectedCategories.length > 0 && ( -
- {selectedCategories.map((cat) => { - const s = CATEGORY_COLORS[cat]; - return ( - toggleCategory(cat)} - style={{ - display: 'inline-flex', - alignItems: 'center', - gap: 4, - fontSize: 11, - fontWeight: 600, - background: s.bg, - color: s.text, - padding: '3px 8px', - borderRadius: 20, - cursor: 'pointer', - letterSpacing: '0.03em', - }} - > - {cat} - - - - - ); - })} -
- )} - - {/* Grid */} - {filtered.length === 0 ? ( -
- No events match your filters. -
- ) : ( -
- {filtered.map((event) => ( - - ))} -
- )} -
- ); + const { colors } = useTheme(); + const themeColors = colors as any; + + const TEAL = themeColors['hoagie-teal'] ?? '#00897B'; + + const [activeTab, setActiveTab] = useState('All'); + const [search, setSearch] = useState(''); + const [sort, setSort] = useState('Date (soonest)'); + const [sortOpen, setSortOpen] = useState(false); + const [categoryOpen, setCategoryOpen] = useState(false); + const [selectedCategories, setSelectedCategories] = useState([]); + + const sortRef = useRef(null!); + const categoryRef = useRef(null!); + + useClickOutside(sortRef, () => setSortOpen(false)); + useClickOutside(categoryRef, () => setCategoryOpen(false)); + + const tabCounts = { + All: EVENTS.length, + Saved: EVENTS.filter((e) => e.tab.includes('Saved')).length, + Created: EVENTS.filter((e) => e.tab.includes('Created')).length, + }; + + const query = search.trim().toLowerCase(); + + const filtered = EVENTS.filter((e) => { + if (!e.tab.includes(activeTab)) return false; + if (query && !e.title.toLowerCase().includes(query)) return false; + if (selectedCategories.length > 0 && !selectedCategories.includes(e.category)) return false; + return true; + }).sort((a, b) => { + if (sort === 'Date (soonest)') + return new Date(a.dateISO).getTime() - new Date(b.dateISO).getTime(); + if (sort === 'Date (latest)') + return new Date(b.dateISO).getTime() - new Date(a.dateISO).getTime(); + if (sort === 'Recently added') return Number(b.id) - Number(a.id); + return 0; + }); + + function toggleCategory(cat: Category) { + setSelectedCategories((prev) => + prev.includes(cat) ? prev.filter((c) => c !== cat) : [...prev, cat] + ); + } + + const categoryLabel = + selectedCategories.length === 0 + ? 'Category' + : selectedCategories.length === 1 + ? selectedCategories[0] + : `${selectedCategories.length} categories`; + + return ( +
+ {/* Header */} +
+

+ My Events +

+
+ + {/* Tabs */} +
+ {(['All', 'Saved', 'Created'] as Tab[]).map((tab) => ( + + ))} +
+ + {/* Search + Filters */} +
+ {/* Search */} +
+ + + + + setSearch(e.target.value)} + style={{ + width: '100%', + padding: '8px 10px 8px 32px', + border: `1px solid ${themeColors.gray300}`, + borderRadius: 6, + fontSize: 13, + background: colors.white, + color: themeColors.gray800, + outline: 'none', + boxSizing: 'border-box', + }} + /> +
+ + {/* Sort Dropdown */} +
+ + {sortOpen && ( + + {SORT_OPTIONS.map((opt) => ( + { + setSort(opt); + setSortOpen(false); + }} + /> + ))} + + )} +
+ + {/* Category Dropdown */} +
+ + {categoryOpen && ( + + {selectedCategories.length > 0 && ( +
setSelectedCategories([])} + style={{ + padding: '8px 12px', + fontSize: 12, + cursor: 'pointer', + color: TEAL, + fontWeight: 600, + borderBottom: `1px solid #f0f0f0`, + }} + > + Clear all +
+ )} + {ALL_CATEGORIES.map((cat) => { + const isSelected = selectedCategories.includes(cat); + const style = CATEGORY_COLORS[cat]; + return ( +
toggleCategory(cat)} + style={{ + padding: '8px 12px', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + gap: 8, + background: isSelected ? '#f9f9f9' : 'transparent', + }} + > + + + {cat} + + {isSelected && ( + + + + )} +
+ ); + })} +
+ )} +
+
+ + {/* Active category chips */} + {selectedCategories.length > 0 && ( +
+ {selectedCategories.map((cat) => { + const s = CATEGORY_COLORS[cat]; + return ( + toggleCategory(cat)} + style={{ + display: 'inline-flex', + alignItems: 'center', + gap: 4, + fontSize: 11, + fontWeight: 600, + background: s.bg, + color: s.text, + padding: '3px 8px', + borderRadius: 20, + cursor: 'pointer', + letterSpacing: '0.03em', + }} + > + {cat} + + + + + + ); + })} +
+ )} + + {/* Grid */} + {filtered.length === 0 ? ( +
+ No events match your filters. +
+ ) : ( +
+ {filtered.map((event) => ( + + ))} +
+ )} +
+ ); } // ── Dropdown helpers ────────────────────────────────────────── function dropdownButtonStyle( - themeColors: any, - colors: any, - isOpen: boolean, - teal: string, - hasActive = false + themeColors: any, + colors: any, + isOpen: boolean, + teal: string, + hasActive = false ) { - return { - padding: '7px 12px', - border: `1px solid ${hasActive || isOpen ? teal : themeColors.gray300}`, - borderRadius: 6, - background: hasActive ? `${teal}10` : colors.white, - cursor: 'pointer', - color: hasActive ? teal : themeColors.gray700, - fontSize: 13, - fontWeight: hasActive ? 600 : 400, - display: 'flex', - alignItems: 'center', - whiteSpace: 'nowrap' as const, - transition: 'border-color 0.15s, background 0.15s', - }; + return { + padding: '7px 12px', + border: `1px solid ${hasActive || isOpen ? teal : themeColors.gray300}`, + borderRadius: 6, + background: hasActive ? `${teal}10` : colors.white, + cursor: 'pointer', + color: hasActive ? teal : themeColors.gray700, + fontSize: 13, + fontWeight: hasActive ? 600 : 400, + display: 'flex', + alignItems: 'center', + whiteSpace: 'nowrap' as const, + transition: 'border-color 0.15s, background 0.15s', + }; } -function DropdownMenu({ children, minWidth = 160 }: { children: React.ReactNode; minWidth?: number }) { - return ( -
- {children} -
- ); +function DropdownMenu({ + children, + minWidth = 160, +}: { + children: React.ReactNode; + minWidth?: number; +}) { + return ( +
+ {children} +
+ ); } function DropdownItem({ - label, - selected, - themeColors, - teal, - onClick, + label, + selected, + themeColors, + teal, + onClick, }: { - label: string; - selected: boolean; - themeColors: any; - teal: string; - onClick: () => void; + label: string; + selected: boolean; + themeColors: any; + teal: string; + onClick: () => void; }) { - return ( -
- {label} - {selected && ( - - - - )} -
- ); + return ( +
+ {label} + {selected && ( + + + + )} +
+ ); } // ── Event Card ──────────────────────────────────────────────── function EventCard({ - event, - themeColors, - colors, + event, + themeColors, + colors, }: { - event: Event; - themeColors: any; - colors: any; - teal: string; + event: Event; + themeColors: any; + colors: any; + teal: string; }) { - const catStyle = CATEGORY_COLORS[event.category]; - - return ( -
{ - (e.currentTarget as HTMLDivElement).style.boxShadow = '0 4px 16px rgba(0,0,0,0.09)'; - (e.currentTarget as HTMLDivElement).style.borderColor = themeColors.gray300; - }} - onMouseLeave={(e) => { - (e.currentTarget as HTMLDivElement).style.boxShadow = 'none'; - (e.currentTarget as HTMLDivElement).style.borderColor = themeColors.gray200; - }} - > -
- {/* Date + Category + Star */} -
-
- - {event.displayDate} - - - {event.category} - -
- {event.isStarred && ( - - - - )} -
- - {/* Title */} -
- {event.title} -
- - {/* Time + Location */} - {(event.timeRange || event.location) && ( -
- {event.timeRange && ( - <> - - - - {event.timeRange} - - )} - {event.location && ( - <> - - - - - {event.location} - - )} -
- )} - - {/* Description */} - {event.description && ( -
- {event.description} -
- )} -
- - {/* Footer */} -
- {event.interested > 0 ? ( - - {event.interested} interested - - ) : ( - - )} - {event.isOwned && ( - - )} -
-
- ); -} \ No newline at end of file + const catStyle = CATEGORY_COLORS[event.category]; + + return ( +
{ + (e.currentTarget as HTMLDivElement).style.boxShadow = '0 4px 16px rgba(0,0,0,0.09)'; + (e.currentTarget as HTMLDivElement).style.borderColor = themeColors.gray300; + }} + onMouseLeave={(e) => { + (e.currentTarget as HTMLDivElement).style.boxShadow = 'none'; + (e.currentTarget as HTMLDivElement).style.borderColor = themeColors.gray200; + }} + > +
+ {/* Date + Category + Star */} +
+
+ + {event.displayDate} + + + {event.category} + +
+ {event.isStarred && ( + + + + )} +
+ + {/* Title */} +
+ {event.title} +
+ + {/* Time + Location */} + {(event.timeRange || event.location) && ( +
+ {event.timeRange && ( + <> + + + + + {event.timeRange} + + )} + {event.location && ( + <> + + + + + + {event.location} + + )} +
+ )} + + {/* Description */} + {event.description && ( +
+ {event.description} +
+ )} +
+ + {/* Footer */} +
+ {event.interested > 0 ? ( + + {event.interested} interested + + ) : ( + + )} + {event.isOwned && ( + + )} +
+
+ ); +} diff --git a/frontend/lib/hoagie-ui/ProfileCard/index.tsx b/frontend/lib/hoagie-ui/ProfileCard/index.tsx index 39f5627..28087cf 100644 --- a/frontend/lib/hoagie-ui/ProfileCard/index.tsx +++ b/frontend/lib/hoagie-ui/ProfileCard/index.tsx @@ -41,10 +41,14 @@ export function ProfileCard({ user }: { user: User }) { {email} - + - + );