Skip to content

Commit

Permalink
Add Event Block
Browse files Browse the repository at this point in the history
  • Loading branch information
conrad-vanl committed Mar 1, 2024
1 parent 58172bb commit 980a3ca
Show file tree
Hide file tree
Showing 8 changed files with 407 additions and 0 deletions.
93 changes: 93 additions & 0 deletions packages/web-shared/components/AddToCalendar/AddToCalendar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React from 'react';
import { Menu } from '@headlessui/react';
import {
CalendarPlus,
AppleLogo,
GoogleLogo,
MicrosoftOutlookLogo,
FileArrowDown,
} from '@phosphor-icons/react';
import { ActionIcon, List, MenuLink } from './AddToCalendar.styles';
import { addSeconds, parseISO } from 'date-fns';

function convertToIcsLink({ start, duration, location, allDay, title }) {
const startDate = parseISO(start);
const endDate = addSeconds(startDate, duration);
const startDateString = startDate.toISOString().replace(/-|:|\.\d+/g, '');
const endDateString = endDate.toISOString().replace(/-|:|\.\d+/g, '');
const locationString = (location || '').replace(/<[^>]+>/g, ' ');
return `data:text/calendar;charset=utf8,BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
DTSTART:${startDateString}
DTEND:${endDateString}
SUMMARY:${title}
LOCATION:${locationString}
END:VEVENT
END:VCALENDAR`;
}

function convertToGoogleLink({ start, duration, location, allDay, title }) {
const startDate = parseISO(start);
const endDate = addSeconds(startDate, duration);
const startDateString = startDate.toISOString().replace(/-|:|\.\d+/g, '');
const endDateString = endDate.toISOString().replace(/-|:|\.\d+/g, '');
const locationString = (location || '').replace(/<[^>]+>/g, ' ');
return `https://www.google.com/calendar/render?action=TEMPLATE&text=${title}&dates=${startDateString}/${endDateString}&location=${locationString}`;
}

function convertToOutlookLink({ start, duration, location, allDay, title }) {
const startDate = parseISO(start);
const endDate = addSeconds(startDate, duration);
const startDateString = startDate.toISOString().replace(/-|:|\.\d+/g, '');
const endDateString = endDate.toISOString().replace(/-|:|\.\d+/g, '');
const locationString = (location || '').replace(/<[^>]+>/g, ' ');
return `https://outlook.live.com/calendar/action/compose&rru=addevent&startdt=${startDateString}&enddt=${endDateString}&subject=${title}&location=${locationString}`;
}

const AddToCalendar = ({ start, duration, allDay, location, title = 'Event' }) => {
return (
<Menu as="div" style={{ position: 'relative' }}>
<ActionIcon>
<CalendarPlus size={16} weight="bold" />
</ActionIcon>
<List>
<Menu.Item>
<MenuLink href={convertToIcsLink({ start, duration, allDay, location, title })}>
<AppleLogo size={14} weight="fill" />
&nbsp;Apple Calendar
</MenuLink>
</Menu.Item>
<Menu.Item>
<MenuLink
href={convertToGoogleLink({ start, duration, allDay, location, title })}
target="blank"
>
<GoogleLogo size={14} weight="fill" />
&nbsp;Google Calendar
</MenuLink>
</Menu.Item>
<Menu.Item>
<MenuLink
href={convertToOutlookLink({ start, duration, allDay, location, title })}
target="blank"
>
<MicrosoftOutlookLogo size={14} weight="fill" />
&nbsp;Microsoft Outlook
</MenuLink>
</Menu.Item>
<Menu.Item>
<MenuLink
href={convertToIcsLink({ start, duration, allDay, location, title })}
target="blank"
>
<FileArrowDown size={14} weight="fill" />
&nbsp;Download .ics
</MenuLink>
</Menu.Item>
</List>
</Menu>
);
};

export default AddToCalendar;
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react';
import { Menu } from '@headlessui/react';
import styled from 'styled-components';
import { withTheme } from 'styled-components';
import { rgba } from 'polished';

import { themeGet } from '@styled-system/theme-get';
import { system } from '../../ui-kit/_lib/system';

export const ActionIcon = withTheme(styled(Menu.Button)`
// flex items-center justify-center p-2 bg-gray-50 rounded-full
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background-color: ${themeGet('colors.fill.system4')};
transition: background-color ${themeGet('timing.xl')};
border-radius: 100%;
cursor: pointer;
border: none;
outline: none;
&:hover {
background-color: ${themeGet('colors.fill.system2')};
}
${system}
`);

export const List = withTheme(styled(Menu.Items)`
position: absolute;
right: 0;
top: 38px;
min-width: 200px;
color: ${themeGet('colors.text.primary')};
border-radius: ${themeGet('radii.xl')};
background-color: ${themeGet('colors.fill.paper')};
box-shadow: ${themeGet('shadows.medium')};
overflow: hidden;
padding: ${themeGet('space.xxs')};
${system}
`);

export const MenuContainer = withTheme(styled(Menu)`
position: relative;
${system}
`);

export const MenuLink = withTheme(styled.a`
display: flex;
align-items: center;
padding: ${themeGet('space.xxs')} ${themeGet('space.xs')};
color: ${themeGet('colors.text.primary')};
margin-bottom: ${themeGet('space.xxs')};
border-radius: ${themeGet('radii.base')};
transition: all ${themeGet('timing.l')};
text-align: left;
cursor: pointer;
&:hover {
background-color: ${(props) => rgba(themeGet('colors.base.secondary')(props), 0.15)};
color: ${themeGet('colors.base.secondary')};
}
${system}
`);
2 changes: 2 additions & 0 deletions packages/web-shared/components/AddToCalendar/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import AddToCalendar from './AddToCalendar';
export default AddToCalendar;
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ActionBarFeature,
ActionListFeature,
ChipListFeature,
EventBlockFeature,
PrayerListFeature,
ScriptureFeature,
} from './Features';
Expand All @@ -23,6 +24,7 @@ const FeatureFeedComponentMap = {
ActionBarFeature,
ActionListFeature,
ChipListFeature,
EventBlockFeature,
PrayerListFeature,
ScriptureFeature,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import React from 'react';
import { ArrowSquareOut, MapPin, CalendarPlus, Clock } from '@phosphor-icons/react';
import {
Container,
IconContainer,
LineItem,
SeparatorContainer,
Details,
ActionIcon,
} from './EventBlockFeature.styles';
import { addSeconds, isSameDay, parseISO } from 'date-fns';

import { useTheme } from 'styled-components';
import AddToCalendar from '../../AddToCalendar';
import { BodyText } from '../../../ui-kit';

function eventTimestampLines({ start, duration, allDay }) {
const startDate = parseISO(start);
const endDate = addSeconds(startDate, duration);

// native JS .toLocaleDateString() coming in clutch!

if (allDay) {
// For all-day events
if (isSameDay(startDate, endDate) || duration === 0) {
// If the event ends on the same day or has no duration
// e.g. "Wednesday June 24, 2024"
return [
startDate.toLocaleDateString(undefined, {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
}),
];
} else {
// For multi-day all-day events
// e.g. "Wednesday June 24 to", "Sunday June 28, 2024"
return [
`${startDate.toLocaleDateString(undefined, {
weekday: 'long',
month: 'long',
day: 'numeric',
})}`,
`to ${endDate.toLocaleDateString(undefined, {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
})}`,
];
}
} else {
// For events with specific times
if (isSameDay(startDate, endDate)) {
// If the event starts and ends on the same day
// e.g. "Wednesday June 24, 2024", "9:00am to 4:00pm CST"
return [
startDate.toLocaleDateString(undefined, {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
}),
`${startDate.toLocaleTimeString(undefined, {
hour: 'numeric',
minute: 'numeric',
})} to ${endDate.toLocaleTimeString(undefined, {
hour: 'numeric',
minute: 'numeric',
timeZoneName: 'short',
})}`,
];
} else {
// For events that span multiple days
// e.g. "Wednesday June 24, 2024 5:00am to Sunday June 28, 2024 7:00pm CST"
return [
`${startDate.toLocaleDateString(undefined, {
weekday: 'long',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
})}`,
`to ${endDate.toLocaleDateString(undefined, {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
timeZoneName: 'short',
})}`,
];
}
}
}

// convert address html to google map link
function convertAddressToGoogleMapLink(html) {
const address = (html || '').replace(/<[^>]+>/g, ' ');
if (address.startsWith('http')) return address; // handle links
return `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(address)}`;
}

const EventBlockFeature = ({ feature }) => {
const { allDay, start, duration, location, title } = feature;
const timelines = eventTimestampLines({ start, duration, allDay });
const theme = useTheme();

return (
<Container>
<LineItem>
<IconContainer>
<Clock size={24} fill={theme.colors.base.primary} weight="fill" />
</IconContainer>
<SeparatorContainer last={!location}>
<Details>
<BodyText>{timelines[0]}</BodyText>
{timelines[1] ? <BodyText color="text.secondary">{timelines[1]}</BodyText> : undefined}
</Details>
<AddToCalendar
allDay={allDay}
start={start}
duration={duration}
location={location}
title={title}
/>
</SeparatorContainer>
</LineItem>
{location ? (
<LineItem>
<IconContainer>
<MapPin size={24} fill={theme.colors.base.primary} weight="fill" />
</IconContainer>
<SeparatorContainer last>
<Details>
<BodyText dangerouslySetInnerHTML={{ __html: location }} />
</Details>
<ActionIcon href={convertAddressToGoogleMapLink(location)} target="blank">
<ArrowSquareOut className="h-5 w-5" weight="bold" />
</ActionIcon>
</SeparatorContainer>
</LineItem>
) : null}
</Container>
);
};

export default EventBlockFeature;
Loading

0 comments on commit 980a3ca

Please sign in to comment.