Skip to content

Commit

Permalink
Merge branch 'main' into events_alerts
Browse files Browse the repository at this point in the history
  • Loading branch information
ZL-Asica authored Dec 6, 2024
2 parents 010cbf3 + eda1489 commit 60d824f
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 65 deletions.
65 changes: 46 additions & 19 deletions src/components/Home/DonorDashboard/OrganizationCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Box,
Card,
CardContent,
CardActions,
Expand All @@ -7,6 +8,7 @@ import {
Collapse,
Typography,
Divider,
Link,
} from '@mui/material';
import { ExpandMore, ExpandLess } from '@mui/icons-material';
import { lighten, useTheme } from '@mui/material/styles';
Expand Down Expand Up @@ -82,16 +84,30 @@ const OrganizationCard = ({ organization }: OrganizationCardProps) => {
[theme]
);

// Extracted component for header actions
const HeaderActions = () =>
user && (
<Button
color={isSaved ? 'secondary' : 'primary'}
onClick={() => handleAction('save')}
>
{isSaved ? 'Unsave' : 'Save'}
</Button>
);
const HeaderActions = () => (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-end',
mt: 1,
}}
>
{user && (
<Button
size='small'
sx={{
width: 'fit-content',
backgroundColor: 'primary.light',
}}
color={isSaved ? 'secondary' : 'primary'}
onClick={() => handleAction('save')}
>
{isSaved ? 'Unsaved' : 'Save'}
</Button>
)}
</Box>
);

// Extracted component for needs section
const NeedsSection = () =>
Expand Down Expand Up @@ -127,7 +143,26 @@ const OrganizationCard = ({ organization }: OrganizationCardProps) => {
<Card sx={cardStyles}>
<CardHeader
title={organization.name}
subheader={organization.location}
subheader={
<>
{organization.location}
<br />
<Link
href={organization.website}
target='_blank'
rel='noopener noreferrer'
variant='button'
sx={{
mt: -1,
textDecoration: 'none',
width: 'fit-content',
textTransform: 'none',
}}
>
Visit Website
</Link>
</>
}
action={<HeaderActions />}
/>
<CardContent>
Expand All @@ -137,14 +172,6 @@ const OrganizationCard = ({ organization }: OrganizationCardProps) => {
>
{organization.description}
</Typography>
<Button
size='small'
href={organization.website}
target='_blank'
sx={{ mt: 1 }}
>
Visit Website
</Button>
</CardContent>
<CardActions>
{hasNeeds && (
Expand Down
39 changes: 26 additions & 13 deletions src/components/Saved/SavedCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Box,
Card,
CardContent,
CardHeader,
Expand All @@ -25,13 +26,32 @@ const SavedOrganizationCard: React.FC<{
title={organization.name}
subheader={organization.location}
action={
<Button
variant='text'
color='error'
onClick={onRemove}
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-end',
gap: '0.25rem',
mt: 1,
}}
>
Remove
</Button>
<Button
size='small'
variant='text'
color='error'
onClick={onRemove}
>
Unsaved
</Button>
<Button
size='small'
sx={{ width: 'fit-content' }}
href={organization.website}
target='_blank'
>
Visit Website
</Button>
</Box>
}
/>
<CardContent>
Expand All @@ -41,13 +61,6 @@ const SavedOrganizationCard: React.FC<{
>
{organization.description}
</Typography>
<Button
size='small'
href={organization.website}
target='_blank'
>
Visit Website
</Button>
</CardContent>
</Card>
);
Expand Down
50 changes: 39 additions & 11 deletions src/components/Schedule/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import {
Button,
IconButton,
} from '@mui/material';
import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
import { FileDownload, CalendarMonth } from '@mui/icons-material';
import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs';

import EventsCalendar from './EventsCalendar';

import generateICSFile from '@/utils/generateICSFile';
import {
generateICSFile,
generateCombinedICSFile,
} from '@/utils/generateICSFile';

interface ScheduleBaseProps {
events: DonationEvent[];
Expand Down Expand Up @@ -49,6 +52,7 @@ const ScheduleBase = ({ events, title, description }: ScheduleBaseProps) => {
<Typography
variant='h4'
mb={2}
mt={4}
textAlign='center'
>
{title}
Expand All @@ -67,13 +71,37 @@ const ScheduleBase = ({ events, title, description }: ScheduleBaseProps) => {
setSelectedDate={setSelectedDate}
/>

<Box mt={2}>
<Typography
variant='h6'
mb={1}
<>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '1rem',
flexDirection: 'column',
gap: '0.5rem',
}}
>
Events for {selectedDate?.format('MMMM D, YYYY')}:
</Typography>
<Button
onClick={() => generateCombinedICSFile(events)}
variant='contained'
sx={{
p: '0.5rem 1.5rem',
display: 'flex',
alignItems: 'center',
}}
>
<FileDownload sx={{ marginRight: '0.5rem' }} />
Export All to Calendar
</Button>
<Typography
variant='h6'
textAlign='center'
>
Events for {selectedDate?.format('MMMM D, YYYY')}:
</Typography>
</Box>

{eventsForSelectedDate.length > 0 ? (
eventsForSelectedDate.map((event_) => (
<Box
Expand Down Expand Up @@ -123,8 +151,8 @@ const ScheduleBase = ({ events, title, description }: ScheduleBaseProps) => {
onClick={() => generateICSFile(event_)}
sx={{ fontSize: 12, display: 'flex', flexDirection: 'column' }}
>
<CalendarMonthIcon fontSize='large' />
Add to Calendar
<CalendarMonth fontSize='large' />
Export to Calendar
</IconButton>
</Box>
))
Expand All @@ -136,7 +164,7 @@ const ScheduleBase = ({ events, title, description }: ScheduleBaseProps) => {
No events for this date.
</Typography>
)}
</Box>
</>

{/* Event Details Modal */}
<Dialog
Expand Down
65 changes: 43 additions & 22 deletions src/utils/generateICSFile.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
const formatDate = (dateString: string) => {
const formatDate = (dateString: string): string => {
const date = new Date(dateString);
// Replace colons, dashes, and milliseconds
return date
.toISOString()
.replace(/[:-]/g, '') // Remove colons and dashes
.replace(/\.\d{3}/, ''); // Remove milliseconds
.replace(/[:-]/g, '')
.replace(/\.\d{3}/, '');
};

const createICSContent = (event: DonationEvent) => {
const downloadICSFile = (fileName: string, content: string): void => {
const blob = new Blob([content], { type: 'text/calendar;charset=utf-8' });
const link = document.createElement('a');
link.href = globalThis.URL.createObjectURL(blob);
link.setAttribute('download', fileName);
document.body.append(link);
link.click();
link.remove();
globalThis.URL.revokeObjectURL(link.href); // Clean up
};

const createICSContent = (event: DonationEvent): string => {
const endTime = new Date(new Date(event.date).getTime() + 60 * 60 * 1000);
return [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//OpenHands//Donation Event//EN',
'BEGIN:VEVENT',
`DTSTART:${formatDate(event.date)}`,
`DTEND:${formatDate(endTime.toISOString())}`,
Expand All @@ -22,22 +29,36 @@ const createICSContent = (event: DonationEvent) => {
.join(', ')}`,
`UID:${event.eventId}-${formatDate(event.date)}`,
'END:VEVENT',
].join('\r\n');
};

const createCombinedICSContent = (events: DonationEvent[]): string => {
const eventContents = events
.map((event) => createICSContent(event))
.join('\r\n');
return [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//OpenHands//Combined Donation Events//EN',
eventContents,
'END:VCALENDAR',
].join('\r\n'); // use CRLF for better compatibility
].join('\r\n');
};

const generateICSFile = (event: DonationEvent) => {
const icsContent = createICSContent(event);
const blob = new Blob([icsContent], {
type: 'text/calendar;charset=utf-8',
});
const link = document.createElement('a');
link.href = globalThis.URL.createObjectURL(blob);
link.setAttribute('download', `donation-event-${event.eventId}.ics`);
document.body.append(link);
link.click();
link.remove();
globalThis.URL.revokeObjectURL(link.href); // Clean up
const generateICSFile = (event: DonationEvent): void => {
const icsContent = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//OpenHands//Donation Event//EN',
createICSContent(event),
'END:VCALENDAR',
].join('\r\n');
downloadICSFile(`openhands-donation-event-${event.eventId}.ics`, icsContent);
};

const generateCombinedICSFile = (events: DonationEvent[]): void => {
const icsContent = createCombinedICSContent(events);
downloadICSFile('openhands-donation-events.ics', icsContent);
};

export default generateICSFile;
export { generateICSFile, generateCombinedICSFile };

0 comments on commit 60d824f

Please sign in to comment.