From a66d01628466f40d5f7775c2ed4e3a6aeaae734e Mon Sep 17 00:00:00 2001 From: Mark Gonzalez <123850833+Ohmarkg@users.noreply.github.com> Date: Tue, 25 Aug 2026 09:19:18 -0500 Subject: [PATCH 1/2] Enhance LocationPicker with autocomplete session management and debounce functionality. Refactor Google Places API integration for improved performance and reliability. --- src/components/LocationPicker.tsx | 110 ++++++++++++++++++++++++------ src/helpers/geolocationUtils.ts | 23 ++++++- 2 files changed, 110 insertions(+), 23 deletions(-) diff --git a/src/components/LocationPicker.tsx b/src/components/LocationPicker.tsx index 5a5bee23..519f0f05 100644 --- a/src/components/LocationPicker.tsx +++ b/src/components/LocationPicker.tsx @@ -1,10 +1,9 @@ -import { View, Text, Switch, useColorScheme, Platform, Pressable, FlatList, TextInput } from 'react-native'; -import React, { useContext, useEffect, useState } from 'react'; -import { GooglePlacesAutocomplete, GooglePlaceDetail } from 'react-native-google-places-autocomplete'; +import { View, Text, useColorScheme, Platform, Pressable, FlatList, TextInput } from 'react-native'; +import React, { useContext, useEffect, useRef, useState } from 'react'; import MapView, { Marker, Circle, LatLng, Region, PROVIDER_GOOGLE } from 'react-native-maps'; import * as Location from 'expo-location' -import { GooglePlacesApiKey, presetLocationList, reverseGeocode } from '../helpers/geolocationUtils'; +import { GooglePlaceDetail, GooglePlacesApiKey, reverseGeocode } from '../helpers/geolocationUtils'; import Slider from '@react-native-community/slider'; import { TouchableOpacity } from 'react-native'; import { Octicons } from '@expo/vector-icons'; @@ -13,6 +12,13 @@ import { UserContext } from '../context/UserContext'; const zacharyCoords = { latitude: 30.621160236499136, longitude: -96.3403560168198 } const initialMapDelta = { latitudeDelta: 0.0922, longitudeDelta: 0.0421 } // Size of map view +const autocompleteDebounceMs = 400; +const placeDetailsFields = 'place_id,formatted_address,geometry,name'; + +const createPlacesSessionToken = (): string => { + const randomSuffix = Math.random().toString(36).slice(2); + return `${Date.now()}-${randomSuffix}`; +}; const LocationPicker = ({ onLocationChange, initialCoordinate = zacharyCoords, initialRadius, containerClassName = "" }: { onLocationChange: (locationDetails: GooglePlaceDetail | undefined | null, radius: number | undefined) => void @@ -37,6 +43,9 @@ const LocationPicker = ({ onLocationChange, initialCoordinate = zacharyCoords, i const [geofencingEnabled, setGeofencingEnabled] = useState(initialRadius ? true : false); const [searchText, setSearchText] = useState(''); const [predictions, setPredictions] = useState([]); + const [placesSessionToken, setPlacesSessionToken] = useState(); + const debounceTimerRef = useRef | null>(null); + const latestAutocompleteRequestRef = useRef(0); useEffect(() => { @@ -58,6 +67,19 @@ const LocationPicker = ({ onLocationChange, initialCoordinate = zacharyCoords, i onLocationChange(locationDetails, radius); }, [locationDetails, radius]); + useEffect(() => { + return () => { + if (debounceTimerRef.current !== null) { + clearTimeout(debounceTimerRef.current); + } + }; + }, []); + + const resetSearchSession = () => { + setPlacesSessionToken(undefined); + latestAutocompleteRequestRef.current = 0; + }; + return ( { + onChangeText={(text) => { setSearchText(text); + if (debounceTimerRef.current !== null) { + clearTimeout(debounceTimerRef.current); + } + if (text.length < 2) { setPredictions([]); + if (text.length === 0) { + resetSearchSession(); + } return; } - try { - const response = await fetch( - `https://maps.googleapis.com/maps/api/place/autocomplete/json?input=${encodeURIComponent( - text - )}&key=${GooglePlacesApiKey}` - ); - const json = await response.json(); - console.log('[CustomSearch] Predictions:', json); - if (json.status === 'OK') { - setPredictions(json.predictions); - } else { - console.warn('[CustomSearch] Google API status:', json.status); - setPredictions([]); - } - } catch (err) { - console.error('[CustomSearch] Error fetching predictions:', err); + + if (!GooglePlacesApiKey) { + console.warn('[CustomSearch] Missing Google Places API key'); + setPredictions([]); + return; + } + + const currentSessionToken = placesSessionToken ?? createPlacesSessionToken(); + if (!placesSessionToken) { + setPlacesSessionToken(currentSessionToken); } + const requestId = latestAutocompleteRequestRef.current + 1; + latestAutocompleteRequestRef.current = requestId; + + debounceTimerRef.current = setTimeout(async () => { + const params = new URLSearchParams({ + input: text, + key: GooglePlacesApiKey, + sessiontoken: currentSessionToken, + }); + + try { + const response = await fetch( + `https://maps.googleapis.com/maps/api/place/autocomplete/json?${params.toString()}` + ); + const json = await response.json(); + if (requestId !== latestAutocompleteRequestRef.current) { + return; + } + console.log('[CustomSearch] Predictions:', json); + if (json.status === 'OK') { + setPredictions(json.predictions); + } else { + console.warn('[CustomSearch] Google API status:', json.status); + setPredictions([]); + } + } catch (err) { + console.error('[CustomSearch] Error fetching predictions:', err); + } + }, autocompleteDebounceMs); }} className={`text-lg p-2 pr-10 rounded ${darkMode ? 'text-white bg-secondary-bg-dark' : 'text-black bg-secondary-bg-light' }`} @@ -134,6 +186,7 @@ const LocationPicker = ({ onLocationChange, initialCoordinate = zacharyCoords, i onPress={() => { setSearchText(''); setPredictions([]); + resetSearchSession(); }} className="absolute right-3 top-1/3" > @@ -157,8 +210,20 @@ const LocationPicker = ({ onLocationChange, initialCoordinate = zacharyCoords, i onPress={async () => { console.log('[CustomSearch] Selected:', item); try { + if (!GooglePlacesApiKey) { + console.warn('[CustomSearch] Missing Google Places API key'); + return; + } + const params = new URLSearchParams({ + place_id: item.place_id, + key: GooglePlacesApiKey, + fields: placeDetailsFields, + }); + if (placesSessionToken) { + params.append('sessiontoken', placesSessionToken); + } const detailsResponse = await fetch( - `https://maps.googleapis.com/maps/api/place/details/json?place_id=${item.place_id}&key=${GooglePlacesApiKey}` + `https://maps.googleapis.com/maps/api/place/details/json?${params.toString()}` ); const detailsJson = await detailsResponse.json(); console.log('[CustomSearch] Details:', detailsJson); @@ -173,6 +238,7 @@ const LocationPicker = ({ onLocationChange, initialCoordinate = zacharyCoords, i setMapRegion({ ...coord, ...initialMapDelta }); setPredictions([]); setSearchText(item.description); + resetSearchSession(); } else { console.warn('[CustomSearch] Details status:', detailsJson.status); } diff --git a/src/helpers/geolocationUtils.ts b/src/helpers/geolocationUtils.ts index 85299443..b53f584d 100644 --- a/src/helpers/geolocationUtils.ts +++ b/src/helpers/geolocationUtils.ts @@ -1,9 +1,30 @@ import { LatLng } from 'react-native-maps'; -import { GooglePlaceDetail, Place } from 'react-native-google-places-autocomplete'; // If this is undefined, it will not affect functionality, but will cause unexpected behavior in location selection. export const GooglePlacesApiKey = process.env.GOOGLE_PLACES_API_KEY; +export type GooglePlaceLocation = { + lat: number; + lng: number; +}; + +/** Subset of Google Places API place details used by location pickers. */ +export type GooglePlaceDetail = { + geometry: { + location: GooglePlaceLocation; + }; + formatted_address?: string; + name?: string; + place_id?: string; +}; + +export type Place = { + description: string; + geometry: { + location: GooglePlaceLocation; + }; +}; + export type Coordinates = { /** Angle phi representing *degrees* from equator */ lat: number; From 6e8c92239f73bcf98b491dae4e627e7ac9da73c4 Mon Sep 17 00:00:00 2001 From: Mark Gonzalez Date: Tue, 25 Aug 2026 23:31:05 -0500 Subject: [PATCH 2/2] Remove unused configuration and source files from the shpe-app-web project, including ESLint, Git ignore, CORS settings, Next.js configuration, package management files, and various application components. --- shpe-app-web/.eslintrc.json | 3 - shpe-app-web/.gitignore | 36 - shpe-app-web/README.md | 36 - .../committees/components/CommitteeCard.tsx | 54 - shpe-app-web/app/(main)/committees/page.tsx | 55 - shpe-app-web/app/(main)/dashboard/page.tsx | 41 - .../app/(main)/events/components/DayModal.tsx | 54 - .../events/components/EventCalendar.tsx | 115 - .../(main)/events/components/EventModal.tsx | 478 - .../(main)/events/components/MonthView.tsx | 102 - .../(main)/events/components/PendingEvent.tsx | 45 - .../app/(main)/events/components/WeekView.tsx | 173 - .../(main)/events/components/useDayModal.tsx | 23 - .../events/components/useEventModal.tsx | 18 - shpe-app-web/app/(main)/events/page.tsx | 126 - shpe-app-web/app/(main)/layout.tsx | 17 - shpe-app-web/app/(main)/membership/page.tsx | 303 - shpe-app-web/app/(main)/points/page.tsx | 807 -- shpe-app-web/app/(main)/tools/page.tsx | 131 - .../app/(main)/tools/shirt-tracker/page.tsx | 285 - shpe-app-web/app/api/firebaseUtils.ts | 331 - shpe-app-web/app/components/CommitteeCard.tsx | 47 - shpe-app-web/app/components/MemberCard.tsx | 70 - shpe-app-web/app/components/Navbar.tsx | 78 - shpe-app-web/app/config/firebaseConfig.ts | 30 - shpe-app-web/app/favicon.ico | Bin 25931 -> 0 bytes shpe-app-web/app/globals.css | 52 - shpe-app-web/app/helpers/auth.ts | 39 - shpe-app-web/app/helpers/timeUtils.ts | 118 - shpe-app-web/app/layout.tsx | 22 - shpe-app-web/app/page.tsx | 72 - shpe-app-web/app/types/committees.ts | 116 - shpe-app-web/app/types/events.ts | 436 - shpe-app-web/app/types/membership.ts | 57 - shpe-app-web/app/types/user.ts | 157 - shpe-app-web/cors.json | 9 - shpe-app-web/next.config.js | 25 - shpe-app-web/package-lock.json | 8891 ----------------- shpe-app-web/package.json | 35 - shpe-app-web/postcss.config.js | 6 - shpe-app-web/public/Polygon1.svg | 3 - shpe-app-web/public/Vector.svg | 3 - shpe-app-web/public/alt-arrow.svg | 9 - shpe-app-web/public/arrow-solid-black.svg | 14 - shpe-app-web/public/calendar-solid-gray.svg | 10 - shpe-app-web/public/calendar-solid.svg | 10 - shpe-app-web/public/circle-check-gray.svg | 14 - shpe-app-web/public/committee_image.jpg | Bin 7910463 -> 0 bytes shpe-app-web/public/default-profile-pic.svg | 9 - shpe-app-web/public/generic_course_icon.svg | 1 - shpe-app-web/public/google-logo.svg | 9 - shpe-app-web/public/house-solid-gray.svg | 10 - shpe-app-web/public/house-solid.svg | 10 - .../internal_affairs_committee_icon.svg | 12 - .../public/jones_shpe_jr_committee.svg | 1 - shpe-app-web/public/layer-group.svg | 3 - shpe-app-web/public/logo.svg | 14 - shpe-app-web/public/logo_w.svg | 14 - .../public/mentorshpe_committee_icon.svg | 4 - shpe-app-web/public/next.svg | 1 - shpe-app-web/public/officer-picture.svg | 9 - shpe-app-web/public/plus-icon.svg | 14 - .../public/presidents_committee_icon.svg | 14 - ...rofessional_development_committee_icon.svg | 12 - .../public_relations_committee_icon.svg | 12 - shpe-app-web/public/ranking-star-solid 2.svg | 10 - .../public/scholastic_committee_icon.svg | 4 - shpe-app-web/public/screwdriver-sold.svg | 10 - .../public/secretary_committee_icon.svg | 12 - shpe-app-web/public/shpetinas_icon.svg | 13 - shpe-app-web/public/sign-out-icon.svg | 14 - shpe-app-web/public/spinner.svg | 4 - .../public/technical_affairs_icon.svg | 1 - .../public/treasurer_committee_icon.svg | 16 - shpe-app-web/public/user-solid.svg | 10 - shpe-app-web/public/vercel.svg | 1 - shpe-app-web/tailwind.config.ts | 37 - shpe-app-web/tsconfig.json | 27 - shpe-app-web/yarn.lock | 5266 ---------- 79 files changed, 19130 deletions(-) delete mode 100644 shpe-app-web/.eslintrc.json delete mode 100644 shpe-app-web/.gitignore delete mode 100644 shpe-app-web/README.md delete mode 100644 shpe-app-web/app/(main)/committees/components/CommitteeCard.tsx delete mode 100644 shpe-app-web/app/(main)/committees/page.tsx delete mode 100644 shpe-app-web/app/(main)/dashboard/page.tsx delete mode 100644 shpe-app-web/app/(main)/events/components/DayModal.tsx delete mode 100644 shpe-app-web/app/(main)/events/components/EventCalendar.tsx delete mode 100644 shpe-app-web/app/(main)/events/components/EventModal.tsx delete mode 100644 shpe-app-web/app/(main)/events/components/MonthView.tsx delete mode 100644 shpe-app-web/app/(main)/events/components/PendingEvent.tsx delete mode 100644 shpe-app-web/app/(main)/events/components/WeekView.tsx delete mode 100644 shpe-app-web/app/(main)/events/components/useDayModal.tsx delete mode 100644 shpe-app-web/app/(main)/events/components/useEventModal.tsx delete mode 100644 shpe-app-web/app/(main)/events/page.tsx delete mode 100644 shpe-app-web/app/(main)/layout.tsx delete mode 100644 shpe-app-web/app/(main)/membership/page.tsx delete mode 100644 shpe-app-web/app/(main)/points/page.tsx delete mode 100644 shpe-app-web/app/(main)/tools/page.tsx delete mode 100644 shpe-app-web/app/(main)/tools/shirt-tracker/page.tsx delete mode 100644 shpe-app-web/app/api/firebaseUtils.ts delete mode 100644 shpe-app-web/app/components/CommitteeCard.tsx delete mode 100644 shpe-app-web/app/components/MemberCard.tsx delete mode 100644 shpe-app-web/app/components/Navbar.tsx delete mode 100644 shpe-app-web/app/config/firebaseConfig.ts delete mode 100644 shpe-app-web/app/favicon.ico delete mode 100644 shpe-app-web/app/globals.css delete mode 100644 shpe-app-web/app/helpers/auth.ts delete mode 100644 shpe-app-web/app/helpers/timeUtils.ts delete mode 100644 shpe-app-web/app/layout.tsx delete mode 100644 shpe-app-web/app/page.tsx delete mode 100644 shpe-app-web/app/types/committees.ts delete mode 100644 shpe-app-web/app/types/events.ts delete mode 100644 shpe-app-web/app/types/membership.ts delete mode 100644 shpe-app-web/app/types/user.ts delete mode 100644 shpe-app-web/cors.json delete mode 100644 shpe-app-web/next.config.js delete mode 100644 shpe-app-web/package-lock.json delete mode 100644 shpe-app-web/package.json delete mode 100644 shpe-app-web/postcss.config.js delete mode 100644 shpe-app-web/public/Polygon1.svg delete mode 100644 shpe-app-web/public/Vector.svg delete mode 100644 shpe-app-web/public/alt-arrow.svg delete mode 100644 shpe-app-web/public/arrow-solid-black.svg delete mode 100644 shpe-app-web/public/calendar-solid-gray.svg delete mode 100644 shpe-app-web/public/calendar-solid.svg delete mode 100644 shpe-app-web/public/circle-check-gray.svg delete mode 100644 shpe-app-web/public/committee_image.jpg delete mode 100644 shpe-app-web/public/default-profile-pic.svg delete mode 100644 shpe-app-web/public/generic_course_icon.svg delete mode 100644 shpe-app-web/public/google-logo.svg delete mode 100644 shpe-app-web/public/house-solid-gray.svg delete mode 100644 shpe-app-web/public/house-solid.svg delete mode 100644 shpe-app-web/public/internal_affairs_committee_icon.svg delete mode 100644 shpe-app-web/public/jones_shpe_jr_committee.svg delete mode 100644 shpe-app-web/public/layer-group.svg delete mode 100644 shpe-app-web/public/logo.svg delete mode 100644 shpe-app-web/public/logo_w.svg delete mode 100644 shpe-app-web/public/mentorshpe_committee_icon.svg delete mode 100644 shpe-app-web/public/next.svg delete mode 100644 shpe-app-web/public/officer-picture.svg delete mode 100644 shpe-app-web/public/plus-icon.svg delete mode 100644 shpe-app-web/public/presidents_committee_icon.svg delete mode 100644 shpe-app-web/public/professional_development_committee_icon.svg delete mode 100644 shpe-app-web/public/public_relations_committee_icon.svg delete mode 100644 shpe-app-web/public/ranking-star-solid 2.svg delete mode 100644 shpe-app-web/public/scholastic_committee_icon.svg delete mode 100644 shpe-app-web/public/screwdriver-sold.svg delete mode 100644 shpe-app-web/public/secretary_committee_icon.svg delete mode 100644 shpe-app-web/public/shpetinas_icon.svg delete mode 100644 shpe-app-web/public/sign-out-icon.svg delete mode 100644 shpe-app-web/public/spinner.svg delete mode 100644 shpe-app-web/public/technical_affairs_icon.svg delete mode 100644 shpe-app-web/public/treasurer_committee_icon.svg delete mode 100644 shpe-app-web/public/user-solid.svg delete mode 100644 shpe-app-web/public/vercel.svg delete mode 100644 shpe-app-web/tailwind.config.ts delete mode 100644 shpe-app-web/tsconfig.json delete mode 100644 shpe-app-web/yarn.lock diff --git a/shpe-app-web/.eslintrc.json b/shpe-app-web/.eslintrc.json deleted file mode 100644 index bffb357a..00000000 --- a/shpe-app-web/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "next/core-web-vitals" -} diff --git a/shpe-app-web/.gitignore b/shpe-app-web/.gitignore deleted file mode 100644 index fd3dbb57..00000000 --- a/shpe-app-web/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js -.yarn/install-state.gz - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/shpe-app-web/README.md b/shpe-app-web/README.md deleted file mode 100644 index c4033664..00000000 --- a/shpe-app-web/README.md +++ /dev/null @@ -1,36 +0,0 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/shpe-app-web/app/(main)/committees/components/CommitteeCard.tsx b/shpe-app-web/app/(main)/committees/components/CommitteeCard.tsx deleted file mode 100644 index 37eb036d..00000000 --- a/shpe-app-web/app/(main)/committees/components/CommitteeCard.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Committee } from "../../../types/committees"; -import { getLogoComponent } from "../../../types/committees"; -import Image from 'next/image'; import { CommitteeLogosName } from "../../../types/committees"; - -const CommitteeCard: React.FC = ({ committee }) => { - const { name, color, logo, head, memberCount, description } = committee; - console.log(committee); - const { LogoComponent, height, width } = getLogoComponent(logo as CommitteeLogosName); - const truncate = (str: string, n: number) => str.length > n ? str.substring(0, n) + "..." : str; - // TODO: committee.head is now a string containing the uid of the head of the committee - // We need to fetch the user data from the uid and display the head's name and photo - // Previously, committee.head was an object containing the head's data - - return ( -
-
- -
- {/* {''} */} -
-
{memberCount} members
-
-
-
- { - // Truncate the name if it is too long , but if doesnt exist default to untitled - name ? truncate(name, 16) : 'Untittled' - }
-
- { - // Truncate the description if it is too long , but if doesnt exist default to nothing - description ? truncate(description, 140) : '' - } -
-
-
- ); -} - -interface CommitteeCardProps { - committee: Committee -} - - -export default CommitteeCard; \ No newline at end of file diff --git a/shpe-app-web/app/(main)/committees/page.tsx b/shpe-app-web/app/(main)/committees/page.tsx deleted file mode 100644 index 346cfe7e..00000000 --- a/shpe-app-web/app/(main)/committees/page.tsx +++ /dev/null @@ -1,55 +0,0 @@ -'use client' -import { useState, useEffect } from "react"; -import { useRouter } from "next/navigation"; -import { getPublicUserData, getCommittees } from "@/api/firebaseUtils"; -import { Committee } from "@/types/committees"; -import CommitteeCard from "./components/CommitteeCard"; - -const Committees = () => { - const router = useRouter(); - const [loading, setLoading] = useState(true); - const [committees, setCommittees] = useState([]); - - useEffect(() => { - const fetchCommittees = async () => { - setLoading(true); - const committees = await getCommittees(); - - const updatedCommittees = await Promise.all(committees.map(async (committee) => { - if (committee.head) { - const userData = committee.head; - return { ...committee, head: userData }; - } - return committee; - })); - - setCommittees(updatedCommittees as Committee[]); - setLoading(false); - } - - fetchCommittees(); - setLoading(false); - }, []); - - if (loading) { - return ( -
- -
- ); - } - - return ( -
- - -
- {!loading && committees.map((committees) => ( - - ))} -
- -
- ); -} -export default Committees; \ No newline at end of file diff --git a/shpe-app-web/app/(main)/dashboard/page.tsx b/shpe-app-web/app/(main)/dashboard/page.tsx deleted file mode 100644 index 0275d233..00000000 --- a/shpe-app-web/app/(main)/dashboard/page.tsx +++ /dev/null @@ -1,41 +0,0 @@ -'use client' -import { useEffect, useState } from "react"; -import { useRouter } from 'next/navigation'; -import { onAuthStateChanged } from "firebase/auth"; -import { auth } from "@/config/firebaseConfig"; - -const Dashboard = () => { - const router = useRouter(); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const unsubscribe = onAuthStateChanged(auth, (currentUser) => { - if (currentUser) { - setLoading(false); - } else { - // User is not logged in, redirect to root - router.push('/'); - } - }); - - return () => unsubscribe(); - }, [router]); - - if (loading) { - return ( -
-
- -
-
- ); - } - - return ( -
- -
- ); -}; - -export default Dashboard; \ No newline at end of file diff --git a/shpe-app-web/app/(main)/events/components/DayModal.tsx b/shpe-app-web/app/(main)/events/components/DayModal.tsx deleted file mode 100644 index f3c549f1..00000000 --- a/shpe-app-web/app/(main)/events/components/DayModal.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { SHPEEvent } from '@/types/events'; -import { format } from 'date-fns'; -import ReactDOM from 'react-dom'; - -interface DayModalProps { - day: Date; - events: SHPEEvent[]; - isShowing: boolean; - hide: () => void; - toggleEventPage: (event?: SHPEEvent) => void; -} - -export const DayModal: React.FC = ({ day, events, isShowing, hide, toggleEventPage }) => { - const modal = ( - <> - {/* Filter */} -
- - {/* Modal */} -
-
- {/* Header */} -

{format(day, 'cccc, MMMM do yyyy')}

- - {/* Event List */} -
- {events.map((event) => { - return ( -
toggleEventPage(event)} className="flex bg-red-300 cursor-pointer rounded-lg text-center font-medium p-1"> -
-

{event.name}

-

- {format(event.startTime!.toDate(), 'h:mm aaa')} - {format(event.endTime!.toDate(), 'h:mm aaa')} -

-
-
- ); - })} -
- - {/* Close Button */} - -
-
- - ); - - return isShowing ? ReactDOM.createPortal(modal, document.body) : null; -}; diff --git a/shpe-app-web/app/(main)/events/components/EventCalendar.tsx b/shpe-app-web/app/(main)/events/components/EventCalendar.tsx deleted file mode 100644 index 6b6c2488..00000000 --- a/shpe-app-web/app/(main)/events/components/EventCalendar.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { SHPEEvent } from '@/types/events'; -import { format, subMonths, addMonths, subWeeks, addWeeks, isSameMonth, startOfWeek, endOfWeek } from 'date-fns'; -import MonthView from './MonthView'; -import { useMemo, useState } from 'react'; -import WeekView from './WeekView'; - -interface EventCalendarProps { - events: SHPEEvent[]; - toggleEventPage: (event?: SHPEEvent) => void; -} - -const EventCalendar: React.FC = ({ events, toggleEventPage }) => { - const [focusDate, setFocusDate] = useState(new Date()); - const [isMonthSelected, setIsMonthSelected] = useState(true); - - // Group events by date - const eventsByDate = useMemo(() => { - return events.reduce((acc: { [key: string]: SHPEEvent[] }, event) => { - const dateKey = event.startTime?.toDate() ? format(event.startTime.toDate(), 'yyyy-MM-dd') : ''; - if (!acc[dateKey]) { - acc[dateKey] = []; - } - acc[dateKey].push(event); - return acc; - }, {}); - }, [events]); - - return ( -
- {/* Side Bar */} -
- {/* Event Filters */} -
-

Filters

-
- -

WIP

-
-
- {/* Small Calendar */} -
WIP
-
- -
- {/* Top Bar */} -
-

- {isMonthSelected - ? format(focusDate, 'MMMM yyyy') - : `${ - !isSameMonth(startOfWeek(focusDate), endOfWeek(focusDate)) - ? format(startOfWeek(focusDate), 'MMMM - ') - : '' - }${format(endOfWeek(focusDate), 'MMMM yyyy')}`} -

-
- - -
-
- - -
-
- - {/* Main Calendar */} - {isMonthSelected ? ( - - ) : ( - - )} -
-
- ); -}; - -export default EventCalendar; diff --git a/shpe-app-web/app/(main)/events/components/EventModal.tsx b/shpe-app-web/app/(main)/events/components/EventModal.tsx deleted file mode 100644 index 9fc2dacf..00000000 --- a/shpe-app-web/app/(main)/events/components/EventModal.tsx +++ /dev/null @@ -1,478 +0,0 @@ -import { SHPEEvent, EventType, SHPEEventLog } from '@/types/events'; -import { format } from 'date-fns'; -import { Timestamp } from 'firebase/firestore'; -import { useEffect, useState } from 'react'; -import { getEventLogs, getPublicUserData } from '@/api/firebaseUtils'; -import ReactDOM from 'react-dom'; - -interface EventPageProps { - event?: SHPEEvent; - isShowing: boolean; - hide: () => void; -} - -interface FormData { - [key: string]: any; -} - -export const EventModal: React.FC = ({ event, isShowing, hide }) => { - const [loading, setLoading] = useState(false); - const currentDate = new Date(); - - const [eventLogs, setEventLogs] = useState([]); - const [formData, setFormData] = useState({}); - - useEffect(() => { - console.log(event); - if (event) { - const fetchLogs = async () => { - setLoading(true); - const logs = await getEventLogs(event.id!); - setEventLogs(logs); - setLoading(false); - }; - - fetchLogs(); - - setFormData({ - name: event?.name ?? null, - description: event?.description ?? null, - locationName: event?.locationName ?? null, - startDate: event?.startTime - ? format(event?.startTime?.toDate(), 'yyyy-MM-dd') - : format(new Date(), 'yyyy-MM-dd'), - startTime: event?.startTime - ? format(event.startTime.toDate(), 'HH:mm') - : format( - new Date( - currentDate.getFullYear(), - currentDate.getMonth(), - currentDate.getDay(), - currentDate.getHours() + 1, - 0 - ), - 'HH:mm' - ), - endDate: event?.endTime ? format(event?.endTime?.toDate(), 'yyyy-MM-dd') : format(new Date(), 'yyyy-MM-dd'), - endTime: event?.endTime - ? format(event.endTime.toDate(), 'HH:mm') - : format( - new Date( - currentDate.getFullYear(), - currentDate.getMonth(), - currentDate.getDay(), - currentDate.getHours() + 2, - 0 - ), - 'HH:mm' - ), - eventType: event?.eventType ?? 'CUSTOM_EVENT', - committee: event?.committee ?? 'NONE', - signInPoints: event?.signInPoints ?? null, - signOutPoints: event?.signOutPoints, - pointsPerHour: event?.pointsPerHour ?? null, - startTimeBuffer: event?.startTimeBuffer ?? null, - endTimeBuffer: event?.endTimeBuffer ?? null, - notificationSent: event?.notificationSent ?? false, - general: event?.general ?? false, - nationalConventionEligible: event?.nationalConventionEligible ?? false, - }); - } else { - setFormData({ - name: '', - description: '', - locationName: 'ZACH', - startDate: format(currentDate, 'yyyy-MM-dd'), - startTime: format( - new Date( - currentDate.getFullYear(), - currentDate.getMonth(), - currentDate.getDay(), - currentDate.getHours() + 1, - 0 - ), - 'HH:mm' - ), - endDate: format(currentDate, 'yyyy-MM-dd'), - endTime: format( - new Date( - currentDate.getFullYear(), - currentDate.getMonth(), - currentDate.getDay(), - currentDate.getHours() + 2, - 0 - ), - 'HH:mm' - ), - eventType: 'CUSTOM_EVENT', - committee: 'NONE', - signInPoints: 0, - signOutPoints: 0, - pointsPerHour: 0, - startTimeBuffer: 1200000, - endTimeBuffer: 1200000, - notificationSent: false, - general: false, - nationalConventionEligible: false, - }); - } - console.log(formData); - }, [event]); - - const handleChange = (e: React.ChangeEvent) => { - const { name, value, type } = e.target as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement; - const checked = type === 'checkbox' ? (e.target as HTMLInputElement).checked : false; - - setFormData((prev) => ({ - ...prev, - [name]: - type === 'checkbox' - ? checked - : name === 'startTimeBuffer' || name == 'endTimeBuffer' - ? parseInt(value) * 1000 * 60 - : value, - })); - }; - - function handleSubmit(e: React.FormEvent) { - e.preventDefault(); - - const startCombined = new Date(`${formData.startDate}T${formData.startTime}`); - const endCombined = new Date(`${formData.endDate}T${formData.endTime}`); - - const startTimeFirebase = Timestamp.fromDate(startCombined); - const endTimeFirebase = Timestamp.fromDate(endCombined); - - const submissionData = { - ...formData, - startTime: startTimeFirebase, - endTime: endTimeFirebase, - }; - - delete (submissionData as { startDate?: string }).startDate; - delete (submissionData as { endDate?: string }).endDate; - - console.log(submissionData); - } - - const modal = ( -
- - - {loading ? ( -
- - - ) : ( -
-
- {/* Name */} -
- - -
-
- - {/* Event Info */} -
-
- {/* Date */} - - - {/* Time */} - - - {/* Location */} - - - {/* Type */} - - - {/* Scope */} -
-

Event Scope

-
- - - - - -
-
-
- -
- {/* Points */} -
-

Points

- - - -
- - {/* Advanced */} -
-

Advanced Options

-
-
- -

- Allow to scan QRCode {formData.startTimeBuffer ? formData.startTimeBuffer / (1000 * 60) : 0} - mins before event starts -

-
- -
- -

- Allow to scan QRCode {formData.endTimeBuffer ? formData.endTimeBuffer / (1000 * 60) : 0} mins - after event starts -

-
- - - - -
-
-
-
- -
- {/* Description */} -