Skip to content

Commit 1df335b

Browse files
authored
feat(activities): add to calendar in upcoming events
Signed-off-by: Max Makaluk <70341920+FussuChalice@users.noreply.github.com>
1 parent e09f397 commit 1df335b

File tree

6 files changed

+234
-4
lines changed

6 files changed

+234
-4
lines changed

package-lock.json

Lines changed: 78 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"file-select-dialog": "^1.5.4",
4848
"firebase": "^12.8.0",
4949
"i18next": "^25.8.0",
50+
"ics": "^3.8.1",
5051
"interweave": "^13.1.1",
5152
"jotai": "^2.17.0",
5253
"jszip": "^3.10.1",
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Button from '@components/button';
2+
import { AddToCalendarProps } from './index.types';
3+
import { IconAddMonth } from '@components/icons';
4+
import { useAppTranslation } from '@hooks/index';
5+
import { Box } from '@mui/material';
6+
import useAddToCalendar from './useAddToCalendar';
7+
import IconLoading from '@components/icon_loading';
8+
9+
const AddToCalendar = (props: AddToCalendarProps) => {
10+
const { t } = useAppTranslation();
11+
const { isProcessing, handleAddToCalendar } = useAddToCalendar(props);
12+
13+
return (
14+
<Box className="upc-add-to-calendar-btn">
15+
<Button
16+
variant="small"
17+
startIcon={isProcessing ? <IconLoading /> : <IconAddMonth />}
18+
sx={{ width: '100%' }}
19+
onClick={handleAddToCalendar}
20+
>
21+
{t('tr_addToCalendar')}
22+
</Button>
23+
</Box>
24+
);
25+
};
26+
27+
export default AddToCalendar;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { UpcomingEventType } from '@definition/upcoming_events';
2+
3+
export type AddToCalendarProps = {
4+
event: UpcomingEventType;
5+
};
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { useState } from 'react';
2+
import { createEvent, EventAttributes } from 'ics';
3+
import { AddToCalendarProps } from './index.types';
4+
import {
5+
UpcomingEventCategory,
6+
UpcomingEventDuration,
7+
} from '@definition/upcoming_events';
8+
import { useAppTranslation } from '@hooks/index';
9+
import { decorationsForEvent } from '../decorations_for_event';
10+
import { displaySnackNotification } from '@services/states/app';
11+
import { getMessageByCode } from '@services/i18n/translation';
12+
import { saveAs } from 'file-saver';
13+
14+
const useAddToCalendar = ({ event }: AddToCalendarProps) => {
15+
const { t } = useAppTranslation();
16+
const [isProcessing, setIsProcessing] = useState(false);
17+
18+
const eventDecoration =
19+
event.event_data.category !== undefined &&
20+
event.event_data.category < decorationsForEvent.length
21+
? decorationsForEvent[event.event_data.category]
22+
: decorationsForEvent[decorationsForEvent.length - 1];
23+
24+
const handleAddToCalendar = () => {
25+
if (isProcessing) return;
26+
27+
setIsProcessing(true);
28+
29+
const startDate = new Date(event.event_data.start);
30+
const endDate = new Date(event.event_data.end);
31+
32+
const eventTitle =
33+
event.event_data.category !== UpcomingEventCategory.Custom
34+
? t(eventDecoration.translationKey)
35+
: event.event_data.custom;
36+
37+
const eventDetails: EventAttributes = {
38+
title: eventTitle,
39+
description: event.event_data.description,
40+
start:
41+
event.event_data.duration === UpcomingEventDuration.SingleDay
42+
? [
43+
startDate.getFullYear(),
44+
startDate.getMonth() + 1,
45+
startDate.getDate(),
46+
startDate.getHours(),
47+
startDate.getMinutes(),
48+
]
49+
: [
50+
startDate.getFullYear(),
51+
startDate.getMonth() + 1,
52+
startDate.getDate(),
53+
],
54+
end:
55+
event.event_data.duration === UpcomingEventDuration.SingleDay
56+
? [
57+
endDate.getFullYear(),
58+
endDate.getMonth() + 1,
59+
endDate.getDate(),
60+
endDate.getHours(),
61+
endDate.getMinutes(),
62+
]
63+
: [
64+
endDate.getFullYear(),
65+
endDate.getMonth() + 1,
66+
endDate.getDate() + 1,
67+
],
68+
};
69+
70+
createEvent(eventDetails, (error, value) => {
71+
if (error) {
72+
console.error(error);
73+
74+
setIsProcessing(false);
75+
76+
displaySnackNotification({
77+
header: getMessageByCode('error_app_generic-title'),
78+
message: getMessageByCode(error.message),
79+
severity: 'error',
80+
});
81+
} else {
82+
const blob = new Blob([value], { type: 'text/calendar' });
83+
84+
const filename = `${event.event_uid}.ics`;
85+
86+
saveAs(blob, filename);
87+
}
88+
setIsProcessing(false);
89+
});
90+
};
91+
92+
return {
93+
isProcessing,
94+
handleAddToCalendar,
95+
};
96+
};
97+
98+
export default useAddToCalendar;

src/features/activities/upcoming_events/upcoming_event/index.tsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ import { UpcomingEventProps } from './index.types';
1414
import useUpcomingEvent from './useUpcomingEvent';
1515
import Divider from '@components/divider';
1616
import EditUpcomingEvent from '../edit_upcoming_event';
17+
import AddToCalendar from '../add_to_calendar';
1718
import Typography from '@components/typography';
1819
import UpcomingEventDate from '../upcoming_event_date';
1920

2021
const UpcomingEvent = (props: UpcomingEventProps) => {
2122
const { t } = useAppTranslation();
2223

2324
const { isAdmin } = useCurrentUser();
24-
25-
const { desktopUp, tabletUp } = useBreakpoints();
25+
const { desktopUp, tabletUp, tablet600Up } = useBreakpoints();
2626

2727
const {
2828
eventDecoration,
@@ -59,23 +59,42 @@ const UpcomingEvent = (props: UpcomingEventProps) => {
5959
display: 'flex',
6060
flexDirection: 'column',
6161
gap: '16px',
62+
63+
...(desktopUp && {
64+
'.upc-edit-btn': {
65+
visibility: 'hidden',
66+
},
67+
'.upc-add-to-calendar-btn': {
68+
visibility: 'hidden',
69+
},
70+
'&:hover': {
71+
'.upc-edit-btn': {
72+
visibility: 'visible',
73+
},
74+
'.upc-add-to-calendar-btn': {
75+
visibility: 'visible',
76+
},
77+
},
78+
}),
6279
}}
6380
onMouseEnter={handleMouseEnter}
6481
onMouseLeave={handleMouseLeave}
6582
>
6683
<Box
6784
sx={{
6885
display: 'flex',
69-
flexDirection: 'row',
86+
flexDirection: tablet600Up ? 'row' : 'column',
7087
gap: '16px',
88+
justifyContent: 'space-between',
7189
}}
7290
>
7391
<Box
7492
sx={{
7593
display: 'flex',
7694
gap: '4px',
7795
flexDirection: 'column',
78-
width: desktopUp ? 'auto' : '100%',
96+
justifyContent: 'center',
97+
width: tablet600Up ? 'auto' : '100%',
7998
}}
8099
>
81100
<Box
@@ -114,6 +133,7 @@ const UpcomingEvent = (props: UpcomingEventProps) => {
114133
{props.data.event_data.description}
115134
</Typography>
116135
</Box>
136+
<AddToCalendar event={props.data} />
117137
</Box>
118138

119139
<Divider color="var(--accent-200)" />
@@ -164,3 +184,4 @@ const UpcomingEvent = (props: UpcomingEventProps) => {
164184
};
165185

166186
export default UpcomingEvent;
187+

0 commit comments

Comments
 (0)