Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React cal poc kl #85

Merged
merged 32 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
87b033e
calender test
Sep 14, 2023
79ea581
current work 9/27
Sep 27, 2023
a5920d1
cal-dates-working
Oct 2, 2023
034ef56
proof of concept handoff
mmcbride2929 Mar 5, 2024
85e5590
Replace displayedEvent, setDisplayedEvent with computed value, displa…
KVinyl Mar 9, 2024
cdb6db5
Modify weekend text color to be white when selected or has event
KVinyl Mar 9, 2024
9684663
Rename custom-tile to event-tile
KVinyl Mar 9, 2024
ed8a98e
Move calendar and displayed events in flex row and modify calendar he…
KVinyl Mar 9, 2024
5ca8203
Add vertical scrollbar if multiple events in a day
KVinyl Mar 9, 2024
932d212
Fix merge conflicts
KVinyl Mar 12, 2024
344f921
Remove console.log and console.error statements
KVinyl Mar 12, 2024
5544cb7
Fix vulnerabilities without breaking changes with npm audit
KVinyl Mar 12, 2024
fa4305e
Add unique key to rendered Event array children
KVinyl Mar 12, 2024
6197477
Fix Event.propTypes shape
KVinyl Mar 12, 2024
6fd930b
Refactor getEventData by adding initialSelectedDate helper function
KVinyl Mar 12, 2024
424a2a4
Fix event prop value in Event test and update Event and Events snapshots
KVinyl Mar 14, 2024
dd2e133
Modify initialSelectedDate function from linear search to binary search
KVinyl Mar 20, 2024
b533cdb
Add hover styling to calendar active tile
KVinyl Mar 20, 2024
f5c0595
Change formattedEventDates in Calendar from array to set
KVinyl Mar 20, 2024
1a9ca18
Change active tile hover color to same color in active tile
KVinyl Mar 20, 2024
fdc7edc
Move initialSelectDate function to different useEffect hook and add e…
KVinyl Mar 20, 2024
d968ed2
Remove redundant changeDate function
KVinyl Mar 20, 2024
0462d69
Add locale and minDetail props to Calendar
KVinyl Mar 20, 2024
d51040f
Add loaded state variable
KVinyl Mar 20, 2024
c72dee0
Replace with eventDateTimeSet with eventsByDateString and modify disp…
KVinyl Mar 20, 2024
bafe9f7
Refactor and memoize tileClassName function
KVinyl Mar 20, 2024
e7b5a83
Add useEvents hook
KVinyl Mar 26, 2024
08fe94e
Add useCalendar hook
KVinyl Mar 26, 2024
3426411
Specify event-info width and overflow-wrap
KVinyl Mar 26, 2024
095c0ed
Fix events conditional rendering condition
KVinyl Mar 26, 2024
089b696
Minor code formatting
KVinyl Mar 26, 2024
dd7ab07
Add nearestFutureEventDate helper function
KVinyl Mar 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will create another backlog item to make this value configurable. It's outside the scope of this backlog item.

);

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