diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d6fb49dfd97..7cd3367b729 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,10 +25,8 @@ jobs: with: node-version: "${{ matrix.node-version }}" - - name: Build - run: | - npm install - npm run build + - name: Install + run: npm ci - name: Setup environment run: gpg --quiet --batch --yes --decrypt --passphrase="$PASSPHRASE" --output calendar-service-account.json calendar-service-account.json.gpg @@ -38,9 +36,12 @@ jobs: - name: Get events from Google API run: npm run getEvents + - name: Build + run: npm run build + - name: Add CNAME in dist folder run: cp CNAME dist - + - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 with: diff --git a/README.md b/README.md index ff669061285..80a6056c28c 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,6 @@ Create a new directory named `dist` in the root directory and run : npm run getEvents ``` -Copy `events.json` from `dist` to root directory. - ### Run development server ```bash diff --git a/package-lock.json b/package-lock.json index 3e3a70731f9..753770cbee3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@mdi/react": "^1.6.1", "googleapis": "^133.0.0", "html-react-parser": "^5.0.7", + "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/package.json b/package.json index ba22724886e..b4aec5e606c 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@mdi/react": "^1.6.1", "googleapis": "^133.0.0", "html-react-parser": "^5.0.7", + "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/src/App.jsx b/src/App.jsx index 5ca40a21225..bdfd6821790 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -9,7 +9,9 @@ import { mdiCalendarRange, mdiClose, mdiMapMarkerOutline } from '@mdi/js'; import Icon from '@mdi/react'; import parse from 'html-react-parser'; import { createRef, useCallback, useEffect, useMemo, useState } from 'react'; +import SearchHeader from './components/SearchHeader'; import useEscKey from './hooks/useEscKey'; +import eventData from '../dist/events.json'; import './App.css'; const htmlRegex = /<\/*html-blob>/; @@ -18,6 +20,9 @@ function App() { const calendarRef = createRef(); const eventDetailRef = createRef(); + const eventsArray = Array.from(eventData); + const [events, setEvents] = useState(eventsArray); + const [loading, setLoading] = useState(true); const [clickedEvent, setClickedEvent] = useState([]); const [showEventDetails, setShowEventDetails] = useState(false); @@ -65,6 +70,16 @@ function App() { setPopupPosition({ left: position.left + 'px', top: position.top + 'px' }); }; + const filterEvents = (searchTerm)=>{ + if(!searchTerm) return setEvents(eventsArray); //handles searchbox clear + let matchingEvents = eventsArray.filter((event) => { + const titleIncludes = event.title?.toLowerCase().includes(searchTerm.toLowerCase()); + const descriptionIncludes = event.description?.toLowerCase().includes(searchTerm.toLowerCase()); + return titleIncludes || descriptionIncludes; + }); + setEvents(matchingEvents); + }; + const handleEventClick = useCallback((clickInfo) => { window.outerWidth > 600 && createPopupPosition(clickInfo.jsEvent); setEventDetails(clickInfo.event); @@ -234,7 +249,7 @@ function App() { aspectRatio={aspectRatio} handleWindowResize={true} windowResize={windowResize} - events="events.json" + events={events} headerToolbar={{ left: 'prev,next today', center: 'title', @@ -249,11 +264,12 @@ function App() { loading={(isLoading) => setLoading(isLoading)} /> ), - [aspectRatio, initialView, calendarRef, handleEventClick] + [aspectRatio, initialView, calendarRef, handleEventClick, events] ); return (
+
{renderFullCalendar}
{showEventDetails && renderEventDetails()} {loading &&
} diff --git a/src/components/SearchHeader.css b/src/components/SearchHeader.css new file mode 100644 index 00000000000..19c1f222f2e --- /dev/null +++ b/src/components/SearchHeader.css @@ -0,0 +1,54 @@ +.search-header{ + display:flex; + justify-content: center; + padding: 20px 0; + gap:10px; +} + +.search-header input{ + max-width: 70%; + width: 250px; + padding: 8px 10px; + font-size: 16px; + border-radius: 4px; + border: 1px solid black; +} + +.search-header input:focus, .search-header input:active{ + border-color: #3788d8; + outline: none; +} + +.search-header button{ + font-size: 16px; + background-color: #3788d8; + color: white; + border: 0; + border-radius: 4px; + padding: 0 20px; + cursor: pointer +} + +.search-header button:hover{ + transition: 300ms ease; + background-color: #2a6097; +} + +@media only screen and (prefers-color-scheme : dark){ + .search-header input{ + border-color: transparent; + background-color: #2c3e50; + color: #eee; + } + .search-header button{ + background-color: #2c3e50; + color: #eee; + } + ::-ms-input-placeholder{ + color: #d0d0d0; + opacity: 1; + } + ::placeholder{ + color: #d0d0d0; + } +} \ No newline at end of file diff --git a/src/components/SearchHeader.jsx b/src/components/SearchHeader.jsx new file mode 100644 index 00000000000..fd6eab2637d --- /dev/null +++ b/src/components/SearchHeader.jsx @@ -0,0 +1,34 @@ +import propTypes from 'prop-types'; +import { useState } from 'react'; +import './SearchHeader.css'; + +const SearchHeader = ({filterEvents})=>{ + const [ searchTerm, setSearchTerm ] = useState(''); + + const handleInputTextChange = (e)=>{ + setSearchTerm(e.target.value); + if(!e.target.value.trim()) filterEvents(false) ;// handles searchbox clear + }; + + const handleSearch = ()=>{ + if(!searchTerm.trim()) return; + filterEvents(searchTerm); + }; + + const handleEnterPress = (e)=>{ + if(e.key === 'Enter') handleSearch(); + }; + + return( +
+ + +
+ ); +}; + +SearchHeader.propTypes = { + filterEvents : propTypes.oneOfType([propTypes.func]) +}; + +export default SearchHeader; \ No newline at end of file