Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion public/app.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/components/Map.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default function Map({
filteredSlugs.forEach(slug => {
const meeting = state.meetings[slug];

if (meeting.latitude && meeting.longitude && meeting.isInPerson) {
if (meeting?.latitude && meeting?.longitude && meeting?.isInPerson) {
const coords = meeting.latitude + ',' + meeting.longitude;

//create a new pin
Expand Down
118 changes: 59 additions & 59 deletions src/components/Meeting.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import Icon from './Icon';
import Link from './Link';
import Map from './Map';

export default function Meeting({ state, setState, mapbox }) {
export default function Meeting({
state,
setState,
mapbox,
feedback_emails = [],
}) {
//open types
const [define, setDefine] = useState(null);

Expand All @@ -36,15 +41,15 @@ export default function Meeting({ state, setState, mapbox }) {

//format time string (duration? or appointment?)
const timeString = meeting.start
? strings[settings.weekdays[meeting.start.format('d')]].concat(
' ',
meeting.start.format('h:mm a'),
meeting.end ? ` – ${meeting.end.format('h:mm a')}` : ''
)
? `${
strings[settings.weekdays[meeting.start.format('d')]]
} ${meeting.start.format('h:mm a')}${
meeting.end && ` – ${meeting.end.format('h:mm a')}`
}`
: strings.appointment;

//feedback URL link
if (!meeting.feedback_url && settings.feedback_emails.length) {
if (!meeting.feedback_url && feedback_emails.length) {
meeting.feedback_url = formatFeedbackEmail(
settings.feedback_emails,
meeting
Expand Down Expand Up @@ -104,7 +109,6 @@ export default function Meeting({ state, setState, mapbox }) {
.filter(
m =>
meeting.isInPerson &&
!meeting.approximate &&
m.isInPerson &&
m.formatted_address === meeting.formatted_address
)
Expand All @@ -128,7 +132,7 @@ export default function Meeting({ state, setState, mapbox }) {
<div
className={cx('d-flex flex-column flex-grow-1 meeting', {
'in-person': meeting.isInPerson,
'inactive': !meeting.isInPerson && !meeting.isOnline,
'inactive': !meeting.isActive,
'online': meeting.isOnline,
})}
>
Expand Down Expand Up @@ -177,42 +181,40 @@ export default function Meeting({ state, setState, mapbox }) {
.sort((a, b) =>
strings.types[a].localeCompare(strings.types[b])
)
.map((type, index) =>
strings.type_descriptions[type] ? (
<li
className="cursor-pointer m-0"
key={index}
onClick={() =>
setDefine(define === type ? null : type)
}
>
<div className="d-flex align-items-center gap-2">
<span>{strings.types[type]}</span>
<Icon
icon="info"
size={13}
className={
define === type ? 'text-muted' : undefined
}
/>
</div>
{define === type && (
<small className="d-block mt-1 mb-2">
{strings.type_descriptions[type]}
</small>
)}
</li>
) : (
<li className="m-0" key={index}>
{strings.types[type]}
</li>
)
)}
.map((type, index) => (
<li className="m-0" key={index}>
{strings.type_descriptions[type] ? (
<button
className="d-block bg-transparent border-0 p-0 text-start"
onClick={() =>
setDefine(define === type ? null : type)
}
>
<div className="d-flex align-items-center gap-2">
<span>{strings.types[type]}</span>
<Icon
icon="info"
size={13}
className={
define === type ? 'text-muted' : undefined
}
/>
</div>
{define === type && (
<small className="d-block mt-1 mb-2">
{strings.type_descriptions[type]}
</small>
)}
</button>
) : (
strings.types[type]
)}
</li>
))}
</ul>
)}
{meeting.notes && <Paragraphs text={meeting.notes} />}
{(meeting.isInPerson ||
meeting.isOnline ||
{(meeting.isActive ||
(!meeting.group && !!contactButtons.length)) && (
<div className="d-grid gap-3 mt-2">
{meeting.conference_provider && (
Expand Down Expand Up @@ -247,15 +249,13 @@ export default function Meeting({ state, setState, mapbox }) {
)}
</div>
)}
{meeting.start &&
meeting.timezone &&
(meeting.isInPerson || meeting.isOnline) && (
<Button
onClick={() => formatIcs(meeting)}
icon="calendar"
text={strings.add_to_calendar}
/>
)}
{meeting.start && meeting.isActive && (
<Button
onClick={() => formatIcs(meeting)}
icon="calendar"
text={strings.add_to_calendar}
/>
)}
{!meeting.group &&
contactButtons.map((button, index) => (
<Button {...button} key={index} />
Expand Down Expand Up @@ -290,8 +290,7 @@ export default function Meeting({ state, setState, mapbox }) {
</div>
)}
{meeting.group &&
(meeting.approximate ||
meeting.district ||
(meeting.district ||
meeting.group_notes ||
!!groupWeekdays.length ||
!!contactButtons.length) && (
Expand All @@ -301,7 +300,7 @@ export default function Meeting({ state, setState, mapbox }) {
{meeting.group_notes && (
<Paragraphs text={meeting.group_notes} />
)}
{meeting.group && !!contactButtons.length && (
{!!contactButtons.length && (
<div className="d-grid gap-3 mt-2">
{contactButtons.map((button, index) => (
<Button {...button} key={index} />
Expand Down Expand Up @@ -364,11 +363,11 @@ function Paragraphs({ text, className }) {
function formatWeekdays(weekday, slug, state, setState) {
return weekday
.filter(e => e.meetings.length)
.map((weekday, index) => (
.map(({ meetings, name }, index) => (
<div key={index}>
<h3 className="h6 mb-1 mt-2">{weekday.name}</h3>
<h3 className="h6 mb-1 mt-2">{name}</h3>
<ol className="list-unstyled">
{weekday.meetings.map((m, index) => (
{meetings.map((m, index) => (
<li
className="d-flex flex-row gap-2 justify-content-between m-0"
key={index}
Expand All @@ -377,8 +376,9 @@ function formatWeekdays(weekday, slug, state, setState) {
{m.start.format('h:mm a')}
</div>
<div className="flex-grow-1">
{m.slug === slug && <Link meeting={m} />}
{m.slug !== slug && (
{m.slug === slug ? (
<Link meeting={m} />
) : (
<Link meeting={m} setState={setState} state={state} />
)}
</div>
Expand Down
154 changes: 154 additions & 0 deletions src/components/Meeting.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { render, screen, fireEvent } from '@testing-library/react';
import moment from 'moment-timezone';

import { strings } from '../helpers';
import Meeting from './Meeting';

describe('<Meeting />', () => {
const mockMeeting = {
isInPerson: true,
isOnline: true,
isActive: true,
latitude: 40.712776,
longitude: -74.005974,
name: 'First Meeting',
start: moment(),
end: moment(),
types: ['O', 'M', 'X'],
timezone: 'America/New_York',
approximate: false,
formatted_address: '123 Main St, New York, NY 12345, USA',
email: 'test@test.com',
venmo: '@test',
square: '$test',
paypal: 'https://paypal.me/test',
location: 'Empire State Building',
notes: 'Testing meeting notes\n\nTesting new line',
location_notes: 'Testing meeting notes\n\nTesting new line',
group_notes: 'Testing meeting notes\n\nTesting new line',
website: 'https://test.com',
phone: '+18005551212',
regions: ['Manhattan', 'Midtown'],
district: 'District 13',
conference_url: 'https://zoom.us/d/123456789',
conference_url_notes: 'Test',
conference_phone: '+1234567890',
conference_phone_notes: 'Test',
conference_provider: 'Zoom',
updated: '2/17/22',
};

const mockState = {
capabilities: {
type: true,
},
input: {
meeting: 'foo',
},
meetings: {
foo: mockMeeting,
bar: mockMeeting,
},
};

it('renders with clickable buttons', () => {
const { container } = render(
<Meeting state={mockState} setState={jest.fn()} mapbox="pk.123456" />
);
expect(container).toBeTruthy();

//click type definition
const type_definition = screen.getByText(strings.types.O);
expect(type_definition).toBeTruthy();
fireEvent.click(type_definition);
fireEvent.click(type_definition);

//click formatIcs
const calendar_link = screen.getByText(strings.add_to_calendar);
expect(calendar_link).toBeTruthy();
fireEvent.click(calendar_link);

//click back
const back_link = screen.getByText(strings.back_to_meetings);
expect(back_link).toBeTruthy();
fireEvent.click(back_link);
});

it('renders with group info', () => {
const { container } = render(
<Meeting
state={{
...mockState,
meetings: {
foo: {
...mockMeeting,
group: 'Test',
},
bar: {
...mockMeeting,
group: 'Test',
},
},
}}
setState={jest.fn()}
mapbox="pk.123456"
feedback_emails={['test@test.com']}
/>
);
expect(container).toBeTruthy();
});

it('renders when inactive', () => {
const { container } = render(
<Meeting
state={{
...mockState,
meetings: {
foo: {
...mockMeeting,
isActive: false,
isInPerson: false,
},
bar: {
...mockMeeting,
start: moment().add(1, 'day'),
},
},
}}
setState={jest.fn()}
mapbox="pk.123456"
/>
);
expect(container).toBeTruthy();
});

it('renders with group but no contact', () => {
const { container } = render(
<Meeting
state={{
...mockState,
meetings: {
foo: {
...mockMeeting,
group: 'Test',
start: undefined,
email: undefined,
website: undefined,
phone: undefined,
venmo: undefined,
square: undefined,
paypal: undefined,
},
bar: {
...mockMeeting,
isOnline: false,
},
},
}}
setState={jest.fn()}
mapbox="pk.123456"
/>
);
expect(container).toBeTruthy();
});
});
7 changes: 6 additions & 1 deletion src/components/TsmlUI.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,12 @@ export default function TsmlUI({ json, mapbox, timezone }) {
<div className="tsml-ui">
<div className="container-fluid d-flex flex-column py-3">
{state.input.meeting && state.input.meeting in state.meetings ? (
<Meeting state={state} setState={setState} mapbox={mapbox} />
<Meeting
state={state}
setState={setState}
mapbox={mapbox}
feedback_emails={settings.feedback_emails}
/>
) : (
<>
{settings.show.title && <Title state={state} />}
Expand Down