Skip to content

Commit

Permalink
Merge pull request #85 from FCCColumbus/react-cal-poc-kl
Browse files Browse the repository at this point in the history
React cal poc kl
  • Loading branch information
readysetagile authored Apr 1, 2024
2 parents 431046b + dd7ab07 commit ac855e3
Show file tree
Hide file tree
Showing 9 changed files with 4,131 additions and 6,380 deletions.
10,157 changes: 3,843 additions & 6,314 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0",
"cal-parser": "^1.0.2",
"husky": "^8.0.1",
"lint-staged": "^13.0.3",
"nth-check": "^2.1.1",
"prettier": "^2.7.1",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-calendar": "^4.6.0",
"react-dom": "^18.2.0",
"web-vitals": "^2.1.4"
},
Expand Down
50 changes: 32 additions & 18 deletions src/components/Event.jsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import PropTypes from "prop-types";
import PropTypes from 'prop-types';

function Event({ event }) {
// add id to event
// const href = `/coding-${id + 1}.jpg`
const formattedDateTime = new Intl.DateTimeFormat('en-US', {
dateStyle: 'medium',
timeStyle: 'short',
}).format(event.dtstart.value);

function Event({ event, id }) {
const href = `/coding-${id + 1}.jpg`
const formattedDateTime = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'short' }).format(event.time);
/*
We replace the "narrow non-breaking space" that appears before the AM/PM in some environments
with a regular space to help prevent snapshot test failures.
fwiw, it appears that this is a somewhat recent change in Node: https://www.reddit.com/r/webdev/comments/1054ve6/psa_node_1813_changed_datetime_formatting/
- [email protected], 2023 May 22
*/
const replacedNonBreakingSpace = formattedDateTime.replace(/\u202f/g, ' ');

return (
<div className="event">
<img src={href} alt='people coding' className="event-img" />
<div className="info">
<h5>{event.name}</h5>
{/* <img src={href} alt='people coding' className="event-img" /> */}
<div className="event-info">
<h5>{event.summary.value}</h5>
<table>
<tbody>
<tr>
Expand All @@ -24,25 +29,34 @@ function Event({ event, id }) {
</tr>
<tr>
<th>location:</th>
<td>{event.location}</td>
{event.location && <td>{event.location.value}</td>}
</tr>
</tbody>
</table>
<p>{event.description}</p>
<p>{event.description.value}</p>
<div className="meet-up-register">
<a
target="_blank"
rel="noreferrer"
href="https://www.meetup.com/register/"
>
Join Meetup.com
</a>
</div>
</div>
</div>
)
);
}

Event.propTypes = {
event: PropTypes.shape({
name: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
time: PropTypes.objectOf(Date),
location : PropTypes.string.isRequired,
id : PropTypes.string.isRequired,
summary: PropTypes.objectOf(PropTypes.string.isRequired),
description: PropTypes.objectOf(PropTypes.string.isRequired),
dtstart: PropTypes.objectOf(Date),
location: PropTypes.objectOf(PropTypes.string.isRequired),
uid: PropTypes.objectOf(PropTypes.string.isRequired),
url: PropTypes.objectOf(PropTypes.string.isRequired),
}).isRequired,
id: PropTypes.number.isRequired,
};

export default Event
export default Event;
62 changes: 41 additions & 21 deletions src/components/Events.jsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,57 @@
// import Event from './Event'
import Calendar from 'react-calendar';
import Event from './Event';

function Events() {
// The arrays below are retained for future reference,
// pending implementation of some API call to retrieve events from Google Calendar or Meetup.com, for example.
import useCalendar from '../hooks/useCalendar';
import useEvents from '../hooks/useEvents';

import 'react-calendar/dist/Calendar.css';

// const events = [{
// name: "regular meetup",
// description: "have pizza and talk about code",
// time: new Date('2023-04-17T18:30:00'),
// location: 'Co-Hatch Upper Arlington',
// id: 'event-01'
// }, {
// name: "What are web components?",
// description: "John will give a lecture about what web components are and why we should care. Don't worry, there'll be pizza!",
// time: new Date('2023-05-17T18:30:00'),
// location: 'Co-Hatch Upper Arlington',
// id: 'event-02'
// }]
function Events() {
const { events } = useEvents();
const { selectedDate, setSelectedDate, displayedEvents, tileClassName } =
useCalendar(events);

return (
<div id="events" className="events">
<h3>Upcoming Events</h3>
<div id="events-placeholder-text">
The &quot;Events&quot; section is under development.
<br /><br />
<br />
<br />
In the meantime, please visit
<br />
<a href="https://www.meetup.com/techlifecolumbus/events/">https://www.meetup.com/techlifecolumbus/events/</a>
<a href="https://www.meetup.com/techlifecolumbus/events/">
https://www.meetup.com/techlifecolumbus/events/
</a>
<br />
and look for FreeCodeCamp Columbus events there!
</div>
{/* The array mapping below is retained for future reference. */}
{/* {events.map((event, i) => <Event event={event} key={event.id} id={i} />)} */}
{events.length > 0 && (
<div className="calendar-events-container">
<Calendar
className="calendar"
onChange={setSelectedDate}
value={selectedDate}
locale="en-US"
minDetail="year"
aria-label="Event Calendar"
nextAriaLabel="Next"
prevAriaLabel="Previous"
next2AriaLabel="Jump forwards"
prev2AriaLabel="Jump backwards"
tileClassName={tileClassName}
/>
<div
className={`event-container ${
displayedEvents.length > 1 ? 'multiple-events' : ''
}`}
>
{displayedEvents.map((event) => (
<Event key={event.uid.value} event={event} />
))}
</div>
</div>
)}
</div>
);
}
Expand Down
86 changes: 86 additions & 0 deletions src/hooks/useCalendar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useState, useEffect, useMemo, useCallback } from 'react';

const useCalendar = (events) => {
const [selectedDate, setSelectedDate] = useState(new Date());

const dateOnly = (date) =>
new Date(date.getFullYear(), date.getMonth(), date.getDate());

const eventsByDateString = useMemo(() => {
// Key: date string of date object
// Value: Array of event objects
const eventsDict = {};
events.forEach((event) => {
const dateString = event.dtstart.value.toDateString();
if (!eventsDict[dateString]) {
eventsDict[dateString] = [];
}
eventsDict[dateString].push(event);
});
return eventsDict;
}, [events]);

const displayedEvents = useMemo(
() => eventsByDateString[selectedDate.toDateString()] ?? [],
[eventsByDateString, selectedDate]
);

useEffect(() => {
const nearestFutureEventDate = (currentDate) => {
// Binary search for nearest future date with an event
// when there's no event today.
// Assumes events is sorted chronologically.

let left = 0;
let right = events.length - 1;
while (left < right) {
const mid = Math.floor((left + right) / 2);
const midDate = dateOnly(events[mid].dtstart.value);

if (midDate.getTime() > currentDate.getTime()) {
right = mid;
} else {
left = mid + 1;
}
}
return dateOnly(events[left].dtstart.value);
};

const initialSelectedDate = () => {
// Returns today's date if there is an event.
// If not, returns the nearest future date with an event.
// If no future events, returns the last date with an event.

const currentDate = dateOnly(new Date());

if (events.length === 0) {
return currentDate;
}
// Check if today's date has an event
if (eventsByDateString[currentDate.toDateString()]) {
return currentDate;
}

// Check if today's date is after the last event date
const lastEventDate = dateOnly(events[events.length - 1].dtstart.value);
if (lastEventDate.getTime() < currentDate.getTime()) {
return lastEventDate;
}

return nearestFutureEventDate(currentDate);
};
setSelectedDate(initialSelectedDate());
}, [events, eventsByDateString]);

const tileClassName = useCallback(
({ date, view }) =>
view === 'month' && eventsByDateString[date.toDateString()]
? 'event-tile'
: '',
[eventsByDateString]
);

return { selectedDate, setSelectedDate, displayedEvents, tileClassName };
};

export default useCalendar;
38 changes: 38 additions & 0 deletions src/hooks/useEvents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useState, useEffect } from 'react';
import ical from 'cal-parser';

const useEvents = () => {
const [events, setEvents] = useState([]);

const fetchData = async () => {
try {
const response = await fetch(
'https://docs.google.com/document/d/1OcnWWt1qHaJWE-8v_FPtNO8DKviRQ7DMaqylupuvPXE/export?format=txt'
);

const content = await response.text();
return content;
} catch (error) {
return null;
}
};

useEffect(() => {
const eventsData = async () => {
try {
const fetchedData = await fetchData();
const parsedData = ical.parseString(fetchedData);
return parsedData.events;
} catch (error) {
return [];
}
};
eventsData().then((data) => {
setEvents(data);
});
}, []);

return { events };
};

export default useEvents;
Loading

0 comments on commit ac855e3

Please sign in to comment.