Skip to content

Commit d97a828

Browse files
Save events to the database; refactor the code
1 parent 020e7e5 commit d97a828

File tree

10 files changed

+228
-126
lines changed

10 files changed

+228
-126
lines changed

src/api/profile.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,39 @@ export const makeUpdateUserGridLocation = ({
4848
firestore.doc(doc).set(newData);
4949
});
5050
};
51+
52+
export interface SaveEventToProfileProps {
53+
venueId: string;
54+
userId: string;
55+
eventId: string;
56+
removeMode?: boolean;
57+
}
58+
59+
export const saveEventToProfile = async ({
60+
venueId,
61+
userId,
62+
eventId,
63+
removeMode = false,
64+
}: SaveEventToProfileProps): Promise<void> => {
65+
const userProfileRef = firebase.firestore().collection("users").doc(userId);
66+
67+
const modify = removeMode
68+
? firebase.firestore.FieldValue.arrayRemove
69+
: firebase.firestore.FieldValue.arrayUnion;
70+
71+
const newSavedEvents = {
72+
[`myPersonalizedSchedule.${venueId}`]: modify(eventId),
73+
};
74+
75+
return userProfileRef.update(newSavedEvents).catch((err) => {
76+
Bugsnag.notify(err, (event) => {
77+
event.addMetadata("context", {
78+
location: "api/profile::saveEventToProfile",
79+
venueId,
80+
userId,
81+
eventId,
82+
removeMode,
83+
});
84+
});
85+
});
86+
};

src/components/molecules/Schedule/Schedule.tsx

Lines changed: 48 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,83 @@
1-
import React, { useState } from "react";
1+
import React, { useCallback, useMemo } from "react";
22
import classNames from "classnames";
33
import { format } from "date-fns";
44
import { range } from "lodash";
55
import { useCss } from "react-use";
66

7-
import { Room } from "types/rooms";
8-
import { VenueEvent } from "types/venues";
7+
import { ONE_SECOND_IN_MILLISECONDS } from "utils/time";
98

10-
import {
11-
ONE_SECOND_IN_MILLISECONDS,
12-
ONE_HOUR_IN_SECONDS,
13-
getSecondsFromStartOfDay,
14-
} from "utils/time";
9+
import { RoomWithEvents, ScheduleProps } from "./Schedule.types";
10+
11+
import { calcStartPosition } from "./Schedule.utils";
1512

1613
import { ScheduleRoomEvents } from "../ScheduleRoomEvents";
1714

1815
import "./Schedule.scss";
1916

20-
export type RoomWithEvents = Room & { events: VenueEvent[] };
21-
22-
export interface ScheduleDay {
23-
isToday: boolean;
24-
weekday: string;
25-
dayStartUtcSeconds: number;
26-
rooms: RoomWithEvents[];
27-
}
28-
29-
export interface ScheduleProps {
30-
scheduleDay: ScheduleDay;
31-
}
32-
3317
const MAX_SCHEDULE_START_HOUR = 16;
34-
const HOUR_WIDTH = 200; // px
18+
const MAX_HOUR = 24;
19+
const MIDDAY_HOUR = 12;
3520

3621
export const Schedule: React.FC<ScheduleProps> = ({ scheduleDay }) => {
37-
const getStartHour = (utcSeconds: number) => {
38-
return utcSeconds >= scheduleDay.dayStartUtcSeconds
39-
? Number(format(utcSeconds * ONE_SECOND_IN_MILLISECONDS, "H"))
40-
: 0;
41-
};
42-
43-
const roomStartHour = (room: RoomWithEvents) =>
44-
room.events?.reduce(
45-
(acc, event) => Math.min(acc, getStartHour(event.start_utc_seconds)),
46-
MAX_SCHEDULE_START_HOUR
47-
) ?? MAX_SCHEDULE_START_HOUR;
48-
49-
const scheduleStartHour = Math.min(
50-
...scheduleDay.rooms.map((room) => roomStartHour(room)),
51-
MAX_SCHEDULE_START_HOUR
22+
const getStartHour = useCallback(
23+
(utcSeconds: number) => {
24+
return utcSeconds >= scheduleDay.dayStartUtcSeconds
25+
? Number(format(utcSeconds * ONE_SECOND_IN_MILLISECONDS, "H"))
26+
: 0;
27+
},
28+
[scheduleDay]
5229
);
5330

54-
const hours = range(scheduleStartHour, 24).map(
55-
(hour: number) => `${hour % 12 || 12} ${hour >= 12 ? "PM" : "AM"}`
31+
const roomStartHour = useCallback(
32+
(room: RoomWithEvents) =>
33+
room.events?.reduce(
34+
(acc, event) => Math.min(acc, getStartHour(event.start_utc_seconds)),
35+
MAX_SCHEDULE_START_HOUR
36+
) ?? MAX_SCHEDULE_START_HOUR,
37+
[getStartHour]
5638
);
5739

58-
const calcStartPosition = (startTimeUtcSeconds: number) => {
59-
const startTimeTodaySeconds = getSecondsFromStartOfDay(startTimeUtcSeconds);
40+
const scheduleStartHour = useMemo(
41+
() =>
42+
Math.min(
43+
...scheduleDay.rooms.map((room) => roomStartHour(room)),
44+
MAX_SCHEDULE_START_HOUR
45+
),
46+
[scheduleDay, roomStartHour]
47+
);
6048

61-
return Math.floor(
62-
HOUR_WIDTH / 2 +
63-
(startTimeTodaySeconds / ONE_HOUR_IN_SECONDS - scheduleStartHour) *
64-
HOUR_WIDTH
65-
);
66-
};
49+
const hours = useMemo(
50+
() =>
51+
range(scheduleStartHour, MAX_HOUR).map(
52+
(hour: number) =>
53+
`${hour % MIDDAY_HOUR || MIDDAY_HOUR} ${
54+
hour >= MIDDAY_HOUR ? "PM" : "AM"
55+
}`
56+
),
57+
[scheduleStartHour]
58+
);
6759

6860
const containerVars = useCss({
6961
"--room-count": scheduleDay.rooms.length + 1,
7062
"--current-time--position": calcStartPosition(
71-
Math.floor(Date.now() / ONE_SECOND_IN_MILLISECONDS)
63+
Math.floor(Date.now() / ONE_SECOND_IN_MILLISECONDS),
64+
scheduleStartHour
7265
),
7366
"--hours-count": hours.length,
7467
});
7568

76-
const containerClasses = classNames("ScheduledEvents", containerVars);
77-
78-
const [usersEvents, setUsersEvents] = useState<VenueEvent[]>([]);
79-
80-
const onEventBookmarked = (isBookmarked: boolean, event: VenueEvent) => {
81-
console.log("is bookmarked", isBookmarked);
82-
if (isBookmarked) {
83-
setUsersEvents([...usersEvents, event]);
84-
} else {
85-
setUsersEvents(usersEvents.filter((e) => e.name !== event.name));
86-
}
87-
};
69+
const containerClasses = useMemo(
70+
() => classNames("ScheduledEvents", containerVars),
71+
[containerVars]
72+
);
8873

8974
return (
9075
<div className={containerClasses}>
9176
<div className="ScheduledEvents__rooms">
9277
<div className="ScheduledEvents__room">
9378
<p className="ScheduledEvents__room-title">My Daily Schedule</p>
9479
<span className="ScheduledEvents__events-count">
95-
{usersEvents.length} events
80+
{scheduleDay.personalEvents.length} events
9681
</span>
9782
</div>
9883
{scheduleDay.rooms.map((room) => (
@@ -120,18 +105,16 @@ export const Schedule: React.FC<ScheduleProps> = ({ scheduleDay }) => {
120105

121106
<div className="ScheduledEvents__user-schedule">
122107
<ScheduleRoomEvents
123-
isUsers={true}
124-
events={usersEvents}
108+
isUsers
109+
events={scheduleDay.personalEvents}
125110
scheduleStartHour={scheduleStartHour}
126-
onEventBookmarked={onEventBookmarked}
127111
/>
128112
</div>
129113
{scheduleDay.rooms.map((room) => (
130114
<ScheduleRoomEvents
131115
key={room.title}
132116
events={room.events}
133117
scheduleStartHour={scheduleStartHour}
134-
onEventBookmarked={onEventBookmarked}
135118
/>
136119
))}
137120
</div>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Room } from "types/rooms";
2+
import { PersonalizedVenueEvent } from "types/venues";
3+
import { WithVenueId } from "utils/id";
4+
5+
export type RoomWithEvents = Room & {
6+
events: WithVenueId<PersonalizedVenueEvent>[];
7+
};
8+
9+
export interface ScheduleDay {
10+
isToday: boolean;
11+
weekday: string;
12+
dayStartUtcSeconds: number;
13+
rooms: RoomWithEvents[];
14+
personalEvents: WithVenueId<PersonalizedVenueEvent>[];
15+
}
16+
17+
export interface ScheduleProps {
18+
scheduleDay: ScheduleDay;
19+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { getSecondsFromStartOfDay, ONE_HOUR_IN_SECONDS } from "utils/time";
2+
3+
export const HOUR_WIDTH = 200; // px
4+
5+
export const calcStartPosition = (
6+
startTimeUtcSeconds: number,
7+
scheduleStartHour: number
8+
) => {
9+
const startTimeTodaySeconds = getSecondsFromStartOfDay(startTimeUtcSeconds);
10+
11+
return Math.floor(
12+
HOUR_WIDTH / 2 +
13+
(startTimeTodaySeconds / ONE_HOUR_IN_SECONDS - scheduleStartHour) *
14+
HOUR_WIDTH
15+
);
16+
};

src/components/molecules/ScheduleEvent/ScheduleEvent.tsx

Lines changed: 64 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,97 @@
1-
import React, { useState } from "react";
1+
import React, {
2+
MouseEventHandler,
3+
useCallback,
4+
useEffect,
5+
useMemo,
6+
useState,
7+
} from "react";
28
import classNames from "classnames";
39

410
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
511
import { faBookmark as solidBookmark } from "@fortawesome/free-solid-svg-icons";
612
import { faBookmark as regularBookmark } from "@fortawesome/free-regular-svg-icons";
713

8-
import { VenueEvent } from "types/venues";
14+
import { PersonalizedVenueEvent } from "types/venues";
15+
16+
import { WithVenueId } from "utils/id";
917
import { isTruthy } from "utils/types";
1018

11-
import {
12-
getSecondsFromStartOfDay,
13-
ONE_HOUR_IN_SECONDS,
14-
isLiveEvent,
15-
} from "utils/time";
19+
import { isLiveEvent, ONE_HOUR_IN_MINUTES } from "utils/time";
20+
21+
import { saveEventToProfile } from "api/profile";
22+
import { useUser } from "hooks/useUser";
23+
24+
import { calcStartPosition, HOUR_WIDTH } from "../Schedule/Schedule.utils";
1625

1726
import "./ScheduleEvent.scss";
1827

1928
export interface ScheduleEventProps {
20-
event: VenueEvent;
29+
event: WithVenueId<PersonalizedVenueEvent>;
2130
scheduleStartHour: number;
22-
onEventBookmarked: Function;
2331
isUsers?: boolean;
2432
}
2533

26-
const HOUR_WIDTH = 200; // px
27-
2834
export const ScheduleEvent: React.FC<ScheduleEventProps> = ({
2935
event,
3036
scheduleStartHour,
31-
onEventBookmarked,
3237
isUsers,
3338
}) => {
34-
const [isBookmarked, setBookmark] = useState(isTruthy(isUsers));
35-
36-
const containerClasses = classNames(
37-
"ScheduleEvent",
38-
{
39-
"ScheduleEvent--live": isLiveEvent(
40-
event.start_utc_seconds,
41-
event.duration_minutes
39+
const [isBookmarked, setBookmark] = useState(event.isSaved);
40+
41+
useEffect(() => {
42+
setBookmark(event.isSaved);
43+
}, [event.isSaved]);
44+
45+
const { userId } = useUser();
46+
47+
const containerClasses = useMemo(
48+
() =>
49+
classNames(
50+
"ScheduleEvent",
51+
{
52+
"ScheduleEvent--live": isLiveEvent(
53+
event.start_utc_seconds,
54+
event.duration_minutes
55+
),
56+
},
57+
{ "ScheduleEvent--users": isTruthy(isUsers) }
4258
),
43-
},
44-
{ "ScheduleEvent--users": isTruthy(isUsers) }
59+
[event, isUsers]
4560
);
4661

47-
const calcStartPosition = (startTimeUtcSeconds: number) => {
48-
const startTimeTodaySeconds = getSecondsFromStartOfDay(startTimeUtcSeconds);
49-
50-
return (
51-
HOUR_WIDTH / 2 +
52-
(startTimeTodaySeconds / ONE_HOUR_IN_SECONDS - scheduleStartHour) *
53-
HOUR_WIDTH
54-
);
55-
};
62+
const eventWidth = useMemo(
63+
() => (event.duration_minutes * HOUR_WIDTH) / ONE_HOUR_IN_MINUTES,
64+
[event]
65+
);
66+
const bookmarkEvent = useCallback(() => {
67+
event.isSaved = !event.isSaved;
5668

57-
const eventWidth = (event.duration_minutes * 200) / 60;
69+
if (userId && event.id) {
70+
saveEventToProfile({
71+
venueId: event.venueId,
72+
userId: userId,
73+
removeMode: !event.isSaved,
74+
eventId: event.id,
75+
});
76+
}
77+
}, [userId, event]);
5878

59-
const bookmarkEvent = () => {
60-
setBookmark(!isBookmarked);
61-
onEventBookmarked(!isBookmarked, event);
62-
};
79+
const onBookmark: MouseEventHandler<HTMLDivElement> = useCallback(
80+
(e) => {
81+
e.stopPropagation();
82+
bookmarkEvent();
83+
},
84+
[bookmarkEvent]
85+
);
6386

6487
return (
6588
<div
6689
className={containerClasses}
6790
style={{
68-
marginLeft: `${calcStartPosition(event.start_utc_seconds)}px`,
91+
marginLeft: `${calcStartPosition(
92+
event.start_utc_seconds,
93+
scheduleStartHour
94+
)}px`,
6995
width: `${eventWidth}px`,
7096
}}
7197
>
@@ -74,16 +100,9 @@ export const ScheduleEvent: React.FC<ScheduleEventProps> = ({
74100
<div className="ScheduleEvent__description">by {event.host}</div>
75101
</div>
76102

77-
<div
78-
className="ScheduleEvent__bookmark"
79-
onClick={(e) => {
80-
e.stopPropagation();
81-
bookmarkEvent();
82-
}}
83-
>
103+
<div className="ScheduleEvent__bookmark" onClick={onBookmark}>
84104
<FontAwesomeIcon
85105
icon={isBookmarked ? solidBookmark : regularBookmark}
86-
style={{ cursor: "pointer", width: "15px", height: "20px" }}
87106
/>
88107
</div>
89108
</div>

0 commit comments

Comments
 (0)