Skip to content

Commit

Permalink
v2 test
Browse files Browse the repository at this point in the history
  • Loading branch information
dkat0 committed Feb 1, 2025
1 parent d69b73b commit 4ee23cb
Show file tree
Hide file tree
Showing 78 changed files with 7,246 additions and 0 deletions.
27 changes: 27 additions & 0 deletions frontend/v2-test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules

# next.js
/.next/
/out/

# production
/build

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
46 changes: 46 additions & 0 deletions frontend/v2-test/app/api/events/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { NextResponse } from 'next/server'
import { EventType } from '@/types/events'

export async function GET() {
// This is a mock implementation. In a real-world scenario, you would fetch this data from a database or external API.
const events: EventType[] = [
{
id: '1',
title: '15-151 Recitation',
date: '2024-10-22',
startTime: '08:00',
endTime: '08:50',
type: 'recitation',
course: '15-151'
},
{
id: '2',
title: '15-122 Office Hours',
date: '2024-10-22',
startTime: '13:30',
endTime: '15:30',
type: 'office-hours',
course: '15-122'
},
{
id: '3',
title: '15-151 Lecture',
date: '2024-10-22',
startTime: '13:00',
endTime: '14:20',
type: 'lecture',
course: '15-151'
},
{
id: '4',
title: 'ScottyLabs Meeting',
date: '2024-10-25',
startTime: '16:00',
endTime: '18:00',
type: 'meeting'
}
]

return NextResponse.json(events)
}

299 changes: 299 additions & 0 deletions frontend/v2-test/app/events/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
'use client'

import { useState, useEffect } from 'react'
import { Search, Calendar, Briefcase, GraduationCap } from 'lucide-react'
import { Button } from '@/components/ui/button'
import CalendarGrid from '@/components/calendar-grid'
import { useRouter } from 'next/navigation'
import { Checkbox } from '@/components/ui/checkbox'
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog'
import { toast } from '@/components/ui/use-toast'

interface Category {
id: string
name: string
icon: React.ComponentType
subcategories: Subcategory[]
}

interface Subcategory {
id: string
name: string
checked: boolean
}

interface PublicEvent {
id: string
title: string
description: string
date: Date
startDate: string
endDate: string
startTime: string
endTime: string
location: string
categories: string[]
subcategories: string[]
organizer: string
color?: string
}

const categories: Category[] = [
{
id: 'recreational',
name: 'Recreational',
icon: Calendar,
subcategories: [
{ id: 'music', name: 'Music', checked: true },
{ id: 'food', name: 'Food', checked: true },
{ id: 'culture', name: 'Culture', checked: true },
{ id: 'sports', name: 'Sports', checked: true },
{ id: 'social', name: 'Social', checked: true }
]
},
{
id: 'professional',
name: 'Professional',
icon: Briefcase,
subcategories: [
{ id: 'software', name: 'Software Engineering', checked: true },
{ id: 'business', name: 'Business', checked: true },
{ id: 'design', name: 'Design', checked: true },
{ id: 'internships', name: 'Internships', checked: true },
{ id: 'coops', name: 'Co-ops', checked: true }
]
},
{
id: 'academic',
name: 'Academic',
icon: GraduationCap,
subcategories: [
{ id: 'lectures', name: 'Guest Lectures', checked: true },
{ id: 'workshops', name: 'Workshops', checked: true },
{ id: 'research', name: 'Research', checked: true },
{ id: 'studyabroad', name: 'Study Abroad', checked: true },
{ id: 'academic-events', name: 'Academic Events', checked: true }
]
}
]

// Mock public events - in real app, fetch from API
const publicEvents: PublicEvent[] = [
{
id: '1',
title: 'Study Abroad Coffee Hour',
description: 'Come join CMU Study Abroad for coffee! Do you have questions about study abroad programs? Want to learn more about the opportunities available? Come and drop in to chat with our team over coffee!',
date: new Date('2024-01-10'),
startDate: '2024-01-10',
endDate: '2024-01-10',
startTime: '11:00',
endTime: '13:00',
location: 'Wean Hall - La Prima',
categories: ['academic'],
subcategories: ['studyabroad'],
organizer: 'CMU Study Abroad Office'
}
]

export default function EventsExplorePage() {
const [selectedCategory, setSelectedCategory] = useState('recreational')
const [categoriesState, setCategoriesState] = useState(categories)
const [searchQuery, setSearchQuery] = useState('')
const [selectedEvent, setSelectedEvent] = useState<PublicEvent | null>(null)
const [isEventDialogOpen, setIsEventDialogOpen] = useState(false)
const [addedEvents, setAddedEvents] = useState<{[key: string]: boolean}>({})
const router = useRouter()

useEffect(() => {
const savedEvents = localStorage.getItem('selectedEvents')
if (savedEvents) {
setAddedEvents(JSON.parse(savedEvents))
}
}, [])

const handleSubcategoryToggle = (categoryId: string, subcategoryId: string) => {
setCategoriesState(prev => prev.map(category =>
category.id === categoryId
? {
...category,
subcategories: category.subcategories.map(sub =>
sub.id === subcategoryId
? { ...sub, checked: !sub.checked }
: sub
)
}
: category
))
}

const filteredEvents = publicEvents.filter(event => {
// Filter by category
if (!event.categories.includes(selectedCategory)) return false

// Filter by subcategories
const category = categoriesState.find(c => c.id === selectedCategory)
if (!category) return false

const activeSubcategories = category.subcategories
.filter(sub => sub.checked)
.map(sub => sub.id)

if (!event.subcategories.some(sub => activeSubcategories.includes(sub))) return false

// Filter by search query
if (searchQuery) {
const searchLower = searchQuery.toLowerCase()
return (
event.title.toLowerCase().includes(searchLower) ||
event.description.toLowerCase().includes(searchLower) ||
event.location.toLowerCase().includes(searchLower) ||
event.organizer.toLowerCase().includes(searchLower)
)
}

return true
}).map(event => ({
...event,
color: addedEvents[event.id] ? '#50a3f6' : undefined
}))

const handleEventClick = (event: PublicEvent) => {
setSelectedEvent(event)
setIsEventDialogOpen(true)
}

const handleAddToCMUCal = () => {
if (selectedEvent) {
// Get existing events from localStorage
const existingEvents = JSON.parse(localStorage.getItem('selectedEvents') || '{}')

// Add the new event with all its details
existingEvents[selectedEvent.id] = {
...selectedEvent,
added: true
}

// Update localStorage
localStorage.setItem('selectedEvents', JSON.stringify(existingEvents))

// Update local state
setAddedEvents(existingEvents)

// Close the dialog
setIsEventDialogOpen(false)

// Show a success message
toast({
title: "Event Added",
description: `${selectedEvent.title} has been added to your CMUCal.`,
})
}
}

return (
<div className="min-h-screen flex flex-col">
<header className="border-b p-4">
<div className="flex items-center gap-4 max-w-7xl mx-auto">
<Button
variant="ghost"
size="icon"
onClick={() => router.push('/')}
>
<Calendar className="h-4 w-4" />
</Button>
<div className="flex-1 relative">
<input
type="search"
placeholder="Search events..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full px-4 py-2 rounded-md border bg-background"
/>
</div>
</div>
</header>

<div className="flex-1 flex">
{/* Sidebar */}
<div className="w-80 border-r p-4">
<div className="space-y-6">
<Accordion type="single" collapsible defaultValue="recreational">
{categoriesState.map(category => (
<AccordionItem key={category.id} value={category.id}>
<AccordionTrigger
onClick={() => setSelectedCategory(category.id)}
className={`${
selectedCategory === category.id ? 'text-primary' : ''
}`}
>
<div className="flex items-center gap-2">
<category.icon className="h-4 w-4" />
<span>{category.name}</span>
</div>
</AccordionTrigger>
<AccordionContent>
<div className="space-y-2 pl-6">
{category.subcategories.map(subcategory => (
<label
key={subcategory.id}
className="flex items-center gap-2"
>
<Checkbox
checked={subcategory.checked}
onCheckedChange={() =>
handleSubcategoryToggle(category.id, subcategory.id)
}
/>
<span className="text-sm">{subcategory.name}</span>
</label>
))}
</div>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
</div>

{/* Calendar View */}
<div className="flex-1">
<CalendarGrid
publicEvents={filteredEvents}
onEventClick={handleEventClick}
isExploreView={true}
/>
</div>
</div>

{/* Event Details Dialog */}
<Dialog open={isEventDialogOpen} onOpenChange={setIsEventDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>{selectedEvent?.title}</DialogTitle>
</DialogHeader>
<div className="space-y-2">
<DialogDescription>
{selectedEvent?.description}
</DialogDescription>
<div>
<strong>When:</strong> {selectedEvent?.startDate} {selectedEvent?.startTime} - {selectedEvent?.endTime}
</div>
<div>
<strong>Where:</strong> {selectedEvent?.location}
</div>
<div>
<strong>Organizer:</strong> {selectedEvent?.organizer}
</div>
</div>
<DialogFooter>
<Button onClick={handleAddToCMUCal} disabled={selectedEvent ? addedEvents[selectedEvent.id] : false}>
{selectedEvent && addedEvents[selectedEvent.id] ? 'Added to CMUCal' : 'Add to CMUCal'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
)
}

Loading

0 comments on commit 4ee23cb

Please sign in to comment.