@openeventkit/event-site 2.1.18 → 2.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/.github/workflows/jest.yml +1 -1
  2. package/babel.config.json +9 -9
  3. package/gatsby-node.js +67 -125
  4. package/jest.setup.js +2 -0
  5. package/netlify.toml +1 -1
  6. package/package.json +25 -16
  7. package/src/__mocks__/@mdx-js/mdx.js +32 -0
  8. package/src/__mocks__/@mdx-js/react.js +15 -0
  9. package/src/__mocks__/rehype-external-links.js +3 -0
  10. package/src/__mocks__/remark-gfm.js +3 -0
  11. package/src/actions/fetch-entities-actions.js +45 -87
  12. package/src/actions/update-data-actions.js +2 -2
  13. package/src/actions/user-actions.js +578 -430
  14. package/src/cms/config/collections/configurationsCollection/siteSettings/index.js +2 -0
  15. package/src/cms/config/collections/configurationsCollection/siteSettings/typeDefs.js +10 -0
  16. package/src/cms/preview-templates/ContentPagePreview.js +27 -29
  17. package/src/components/AvatarEditorModal/index.js +10 -0
  18. package/src/components/CertificatePDF.js +313 -0
  19. package/src/components/CertificateSection.js +139 -0
  20. package/src/components/FullSchedule.js +83 -66
  21. package/src/components/Mdx.js +39 -0
  22. package/src/components/__tests__/Mdx.test.jsx +70 -0
  23. package/src/content/site-settings/index.json +1 -1
  24. package/src/content/sponsors.json +1 -1
  25. package/src/i18n/locales/en.json +9 -1
  26. package/src/pages/a/[...].js +3 -0
  27. package/src/reducers/user-reducer.js +89 -27
  28. package/src/routes/authorization-callback-route.js +20 -2
  29. package/src/styles/rsvp-page.module.scss +63 -0
  30. package/src/templates/full-profile-page.js +61 -2
  31. package/src/templates/marketing-page-template/MainColumn.js +40 -42
  32. package/src/templates/rsvp-page.js +144 -0
  33. package/src/utils/alerts.js +1 -1
  34. package/src/utils/build-json/BaseAPIRequest.js +25 -0
  35. package/src/utils/build-json/EventsAPIRequest.js +171 -0
  36. package/src/utils/build-json/SpeakersAPIRequest.js +62 -0
  37. package/src/utils/build-json/SummitAPIRequest.js +115 -0
  38. package/src/utils/build-json/constants.js +5 -0
  39. package/src/utils/certificateSettings.js +45 -0
  40. package/src/utils/customErrorHandler.js +40 -1
  41. package/src/utils/rsvpConstants.js +7 -0
  42. package/src/utils/useMarketingSettings.js +48 -1
  43. package/src/utils/useSiteSettings.js +11 -0
  44. package/src/workers/feeds.worker.js +85 -90
  45. package/src/workers/sync_strategies/activity_synch_strategy.js +147 -102
  46. package/src/workers/sync_strategies/speaker_synch_strategy.js +3 -3
  47. package/src/workers/sync_strategies/track_synch_strategy.js +149 -48
  48. package/src/workers/synch.worker.js +123 -88
  49. package/static/fonts/fonts.css +120 -20
  50. package/static/fonts/nunito-sans/nunito-sans-v18-latin-200.woff2 +0 -0
  51. package/static/fonts/nunito-sans/nunito-sans-v18-latin-200italic.ttf +0 -0
  52. package/static/fonts/nunito-sans/nunito-sans-v18-latin-200italic.woff2 +0 -0
  53. package/static/fonts/nunito-sans/nunito-sans-v18-latin-300.woff2 +0 -0
  54. package/static/fonts/nunito-sans/nunito-sans-v18-latin-300italic.ttf +0 -0
  55. package/static/fonts/nunito-sans/nunito-sans-v18-latin-300italic.woff2 +0 -0
  56. package/static/fonts/nunito-sans/nunito-sans-v18-latin-400.ttf +0 -0
  57. package/static/fonts/nunito-sans/nunito-sans-v18-latin-400.woff2 +0 -0
  58. package/static/fonts/nunito-sans/nunito-sans-v18-latin-400italic.ttf +0 -0
  59. package/static/fonts/nunito-sans/nunito-sans-v18-latin-400italic.woff2 +0 -0
  60. package/static/fonts/nunito-sans/nunito-sans-v18-latin-500.ttf +0 -0
  61. package/static/fonts/nunito-sans/nunito-sans-v18-latin-500.woff2 +0 -0
  62. package/static/fonts/nunito-sans/nunito-sans-v18-latin-500italic.ttf +0 -0
  63. package/static/fonts/nunito-sans/nunito-sans-v18-latin-500italic.woff2 +0 -0
  64. package/static/fonts/nunito-sans/nunito-sans-v18-latin-600.woff2 +0 -0
  65. package/static/fonts/nunito-sans/nunito-sans-v18-latin-600italic.woff2 +0 -0
  66. package/static/fonts/nunito-sans/nunito-sans-v18-latin-700.ttf +0 -0
  67. package/static/fonts/nunito-sans/nunito-sans-v18-latin-700.woff2 +0 -0
  68. package/static/fonts/nunito-sans/nunito-sans-v18-latin-700italic.woff2 +0 -0
  69. package/static/fonts/nunito-sans/nunito-sans-v18-latin-800.ttf +0 -0
  70. package/static/fonts/nunito-sans/nunito-sans-v18-latin-800.woff2 +0 -0
  71. package/static/fonts/nunito-sans/nunito-sans-v18-latin-800italic.woff2 +0 -0
  72. package/static/fonts/nunito-sans/nunito-sans-v18-latin-900.ttf +0 -0
  73. package/static/fonts/nunito-sans/nunito-sans-v18-latin-900.woff2 +0 -0
  74. package/static/fonts/nunito-sans/nunito-sans-v18-latin-900italic.woff2 +0 -0
  75. package/static/fonts/nunito-sans/nunito-sans-v12-latin-300.woff +0 -0
  76. package/static/fonts/nunito-sans/nunito-sans-v12-latin-300.woff2 +0 -0
  77. package/static/fonts/nunito-sans/nunito-sans-v12-latin-300italic.woff +0 -0
  78. package/static/fonts/nunito-sans/nunito-sans-v12-latin-300italic.woff2 +0 -0
  79. package/static/fonts/nunito-sans/nunito-sans-v12-latin-600.woff +0 -0
  80. package/static/fonts/nunito-sans/nunito-sans-v12-latin-600.woff2 +0 -0
  81. package/static/fonts/nunito-sans/nunito-sans-v12-latin-600italic.woff +0 -0
  82. package/static/fonts/nunito-sans/nunito-sans-v12-latin-600italic.woff2 +0 -0
  83. package/static/fonts/nunito-sans/nunito-sans-v12-latin-700.woff +0 -0
  84. package/static/fonts/nunito-sans/nunito-sans-v12-latin-700.woff2 +0 -0
  85. package/static/fonts/nunito-sans/nunito-sans-v12-latin-700italic.woff +0 -0
  86. package/static/fonts/nunito-sans/nunito-sans-v12-latin-700italic.woff2 +0 -0
@@ -0,0 +1,63 @@
1
+ .speakerWrapper {
2
+ color: var(--color_text_dark);
3
+ display: flex;
4
+ margin-top: 15px;
5
+
6
+ .name {
7
+ width: max-content;
8
+ }
9
+
10
+ .moderator {
11
+ font-size: 12px;
12
+ margin-top: -2px;
13
+ }
14
+
15
+ .job {
16
+ font-size: 10px;
17
+
18
+ .company {
19
+ overflow: hidden;
20
+ text-overflow: ellipsis;
21
+ white-space: nowrap;
22
+ font-size: inherit;
23
+ }
24
+ }
25
+
26
+ .speaker {
27
+ height: 55px;
28
+ margin: 0 15px 15px 0;
29
+ border-radius: 4px;
30
+ display: flex;
31
+ flex: 1 0 auto;
32
+
33
+ .picWrapper {
34
+ height: 100%;
35
+ width: 55px;
36
+
37
+ .pic {
38
+ height: 100%;
39
+ width: 100%;
40
+ background-size: cover;
41
+ border-radius: 50%;
42
+ }
43
+ }
44
+
45
+ .nameWrapper {
46
+ background-color: var(--color_background_light);
47
+ height: 100%;
48
+ padding: 5px;
49
+ border-radius: 0 4px 4px 0;
50
+ overflow: hidden;
51
+ flex: 1 0;
52
+ min-width: 150px;
53
+ max-width: 300px;
54
+ margin-left: 5px;
55
+ }
56
+ }
57
+ }
58
+
59
+ .buttonWrapper {
60
+ margin-top: 15px;
61
+ display: flex;
62
+ gap: 15px;
63
+ }
@@ -17,6 +17,11 @@ import LiteScheduleComponent from '../components/LiteScheduleComponent'
17
17
  import AvatarEditorModal from '../components/AvatarEditorModal'
18
18
  import ChangePasswordComponent from '../components/ChangePasswordComponent';
19
19
  import AccessTracker from "../components/AttendeeToAttendeeWidgetComponent";
20
+ import CertificateSection from '../components/CertificateSection';
21
+ import useMarketingSettings from '../utils/useMarketingSettings';
22
+ import { MARKETING_SETTINGS_KEYS, DISPLAY_OPTIONS } from '../utils/useMarketingSettings';
23
+ import { getAccessTokenSafely } from '../utils/loginUtils';
24
+ import { getEnvVariable, SUMMIT_API_BASE_URL } from '../utils/envVariables';
20
25
 
21
26
  import { updateProfilePicture, updateProfile, getIDPProfile, updatePassword } from '../actions/user-actions'
22
27
 
@@ -24,9 +29,12 @@ import styles from '../styles/full-profile.module.scss'
24
29
 
25
30
  import "openstack-uicore-foundation/lib/css/components/inputs/datetimepicker.css";
26
31
 
27
- export const FullProfilePageTemplate = ({ user, getIDPProfile, updateProfile, updateProfilePicture, updatePassword }) => {
32
+ export const FullProfilePageTemplate = ({ user, getIDPProfile, updateProfile, updateProfilePicture, updatePassword, summit }) => {
28
33
 
29
34
  const [showProfile, setShowProfile] = useState(false);
35
+ const [freshTickets, setFreshTickets] = useState([]);
36
+ const [ticketsFetched, setTicketsFetched] = useState(false);
37
+ const { getSettingByKey } = useMarketingSettings();
30
38
  const [personalProfile, setPersonalProfile] = useState({
31
39
  firstName: '',
32
40
  lastName: '',
@@ -193,6 +201,51 @@ export const FullProfilePageTemplate = ({ user, getIDPProfile, updateProfile, up
193
201
  navigate('/a/my-schedule')
194
202
  };
195
203
 
204
+ // Fetch fresh tickets for certificate validation
205
+ const fetchFreshTickets = async () => {
206
+ try {
207
+ const accessToken = await getAccessTokenSafely();
208
+ if (!accessToken || !summit) return;
209
+
210
+ const params = new URLSearchParams({
211
+ access_token: accessToken,
212
+ fields: 'id,status',
213
+ expand: 'owner'
214
+ });
215
+
216
+ const apiBaseUrl = getEnvVariable(SUMMIT_API_BASE_URL);
217
+ const url = `${apiBaseUrl}/api/v1/summits/${summit.id}/orders/all/tickets/me?${params}`;
218
+
219
+ const response = await fetch(url);
220
+
221
+ if (response.ok) {
222
+ const data = await response.json();
223
+ setFreshTickets(data.data || []);
224
+ }
225
+ } catch (err) {
226
+ console.error('Profile page - Error fetching tickets:', err);
227
+ } finally {
228
+ setTicketsFetched(true);
229
+ }
230
+ };
231
+
232
+ useEffect(() => {
233
+ if (summit && user.idpProfile && !ticketsFetched) {
234
+ fetchFreshTickets();
235
+ }
236
+ }, [summit, user.idpProfile, ticketsFetched]);
237
+
238
+ // Check if certificates are enabled and user has checked-in tickets
239
+ const certificatesEnabled = getSettingByKey(MARKETING_SETTINGS_KEYS.certificateEnabled) !== DISPLAY_OPTIONS.hide;
240
+ const checkedInTickets = freshTickets.filter(ticket => {
241
+ const isCheckedIn = ticket.owner?.summit_hall_checked_in === true;
242
+ console.log(ticket)
243
+ const isValidTicket = ticket.status === 'Paid';
244
+ return isCheckedIn && isValidTicket;
245
+ });
246
+
247
+ const showCertificate = certificatesEnabled && checkedInTickets.length > 0;
248
+
196
249
  const discardChanges = (state) => {
197
250
  switch (state) {
198
251
  case 'profile':
@@ -264,6 +317,9 @@ export const FullProfilePageTemplate = ({ user, getIDPProfile, updateProfile, up
264
317
  <h4>
265
318
  @{user.idpProfile?.nickname}
266
319
  </h4>
320
+ {showCertificate && (
321
+ <CertificateSection freshTickets={freshTickets} />
322
+ )}
267
323
  <ChangePasswordComponent updatePassword={handlePasswordUpdate} />
268
324
  </div>
269
325
  <div className="column">
@@ -661,6 +717,7 @@ const FullProfilePage = (
661
717
  {
662
718
  location,
663
719
  user,
720
+ summit,
664
721
  getIDPProfile,
665
722
  updateProfile,
666
723
  updateProfilePicture,
@@ -671,6 +728,7 @@ const FullProfilePage = (
671
728
  <Layout location={location}>
672
729
  <OrchestedTemplate
673
730
  user={user}
731
+ summit={summit}
674
732
  getIDPProfile={getIDPProfile}
675
733
  updateProfile={updateProfile}
676
734
  updateProfilePicture={updateProfilePicture}
@@ -695,8 +753,9 @@ FullProfilePageTemplate.propTypes = {
695
753
  updatePassword: PropTypes.func
696
754
  };
697
755
 
698
- const mapStateToProps = ({ userState }) => ({
756
+ const mapStateToProps = ({ userState, summitState }) => ({
699
757
  user: userState,
758
+ summit: summitState.summit,
700
759
  });
701
760
 
702
761
  export default connect(mapStateToProps,
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import { navigate } from "gatsby";
3
3
  import PropTypes from "prop-types";
4
4
  import { GatsbyImage, getImage } from "gatsby-plugin-image";
5
- import Mdx from "@mdx-js/runtime";
5
+ import Mdx from "../../components/Mdx";
6
6
  import Container from "./Container";
7
7
  import LiteScheduleComponent from "../../components/LiteScheduleComponent";
8
8
  import DisqusComponent from "../../components/DisqusComponent";
@@ -22,49 +22,47 @@ const onEventClick = (ev) => navigate(`/a/event/${ev.id}`);
22
22
 
23
23
  const MainColumn = ({ widgets, summitPhase, isLoggedUser, onEventClick, lastDataSync, fullWidth, maxHeight }) => {
24
24
  const { content, schedule, disqus, image } = widgets || {};
25
-
25
+
26
26
  const scheduleProps = schedule && isLoggedUser && summitPhase !== PHASES.BEFORE ? { onEventClick } : {};
27
27
 
28
28
  return (
29
- <div
30
- className={`column pt-6 pb-5 px-6 ${!fullWidth ? "is-half" : ""} ${styles.mainColumn || ""}`}
31
- style={{ maxHeight: !fullWidth && maxHeight ? maxHeight : "none", overflowY: "auto" }}
32
- >
33
- <Container>
34
- {content?.display && content?.body && (
35
- <Mdx components={shortcodes}>
36
- {content.body}
37
- </Mdx>
38
- )}
39
- {schedule?.display && (
40
- <>
41
- <h2><b>{schedule.title}</b></h2>
42
- <LiteScheduleComponent
43
- {...scheduleProps}
44
- lastDataSync={lastDataSync}
45
- id={`marketing_lite_schedule_${lastDataSync}`}
46
- page="marketing-site"
47
- showAllEvents={true}
48
- showSearch={false}
49
- showNav={true}
50
- />
51
- </>
52
- )}
53
- {disqus?.display && (
54
- <>
55
- <h2><b>{disqus.title}</b></h2>
56
- <DisqusComponent page="marketing-site" />
57
- </>
58
- )}
59
- {image?.display && image?.image?.src && (
60
- <>
61
- <h2><b>{image.title}</b></h2>
62
- <br/>
63
- <GatsbyImage image={getImage(image.image.src)} alt={image.image.alt ?? ""} />
64
- </>
65
- )}
66
- </Container>
67
- </div>
29
+ <div
30
+ className={`column pt-6 pb-5 px-6 ${!fullWidth ? "is-half" : ""} ${styles.mainColumn || ""}`}
31
+ style={{ maxHeight: !fullWidth && maxHeight ? maxHeight : "none", overflowY: "auto" }}
32
+ >
33
+ <Container>
34
+ {content?.display && content?.body && (
35
+ <Mdx shortcodes={shortcodes} content={content.body}/>
36
+ )}
37
+ {schedule?.display && (
38
+ <>
39
+ <h2><b>{schedule.title}</b></h2>
40
+ <LiteScheduleComponent
41
+ {...scheduleProps}
42
+ lastDataSync={lastDataSync}
43
+ id={`marketing_lite_schedule_${lastDataSync}`}
44
+ page="marketing-site"
45
+ showAllEvents={true}
46
+ showSearch={false}
47
+ showNav={true}
48
+ />
49
+ </>
50
+ )}
51
+ {disqus?.display && (
52
+ <>
53
+ <h2><b>{disqus.title}</b></h2>
54
+ <DisqusComponent page="marketing-site" />
55
+ </>
56
+ )}
57
+ {image?.display && image?.image?.src && (
58
+ <>
59
+ <h2><b>{image.title}</b></h2>
60
+ <br/>
61
+ <GatsbyImage image={getImage(image.image.src)} alt={image.image.alt ?? ""} />
62
+ </>
63
+ )}
64
+ </Container>
65
+ </div>
68
66
  );
69
67
  };
70
68
 
@@ -77,4 +75,4 @@ MainColumn.propTypes = {
77
75
  maxHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
78
76
  };
79
77
 
80
- export default MainColumn;
78
+ export default MainColumn;
@@ -0,0 +1,144 @@
1
+ import React, { useEffect, useMemo, useState } from "react";
2
+ import { connect } from "react-redux";
3
+ import { Redirect } from "@gatsbyjs/reach-router";
4
+ import { useTranslation } from "react-i18next";
5
+ import AjaxLoader from 'openstack-uicore-foundation/lib/components/ajaxloader'
6
+ import Layout from "../components/Layout";
7
+ import { acceptRSVPInvitation, declineRSVPInvitation, getRSVPInvitation } from "../actions/user-actions";
8
+ import { getEventById } from "../actions/event-actions";
9
+ import styles from "../styles/rsvp-page.module.scss"
10
+ import { RSVP_STATUS } from "@utils/rsvpConstants";
11
+ import { Badge } from "react-bootstrap";
12
+ import "../i18n";
13
+
14
+ const RSVPPage = ({ location, rsvpInvitation, event, getRSVPInvitation, acceptRSVPInvitation, declineRSVPInvitation, getEventById }) => {
15
+
16
+ const { t } = useTranslation();
17
+ const [isLoading, setIsLoading] = useState(true);
18
+
19
+ const queryParams = useMemo(() => {
20
+ const search = location?.search || "";
21
+ const searchParams = new URLSearchParams(search);
22
+ const k = searchParams.get("k");
23
+ const eventId = searchParams.get("event_id");
24
+ return {
25
+ invitationToken: k ? k : null,
26
+ eventId: eventId ? Number(eventId) : null,
27
+ };
28
+ }, [location?.search]);
29
+
30
+ const { invitationToken, eventId } = queryParams;
31
+
32
+ useEffect(() => {
33
+ if (!invitationToken) return;
34
+ getRSVPInvitation(invitationToken, eventId)
35
+ .then(() => getEventById(eventId))
36
+ .finally(() => setIsLoading(false));
37
+ }, [invitationToken, eventId]);
38
+
39
+ const moderatorId = event?.moderator_speaker_id;
40
+ const errorMessage = rsvpInvitation?.errorMessage;
41
+
42
+ const handleConfirmRSVP = (isAccepted) => {
43
+ setIsLoading(true);
44
+ const action = isAccepted ? acceptRSVPInvitation : declineRSVPInvitation;
45
+ return action(invitationToken, eventId).finally(() => setIsLoading(false));
46
+ }
47
+
48
+ if (!invitationToken || !eventId) {
49
+ return <Redirect to="/" noThrow />;
50
+ }
51
+
52
+ return (
53
+ <Layout location={location}>
54
+ <AjaxLoader show={isLoading} size={120} />
55
+ {!isLoading && (
56
+ <div className={`container`}>
57
+ {event ?
58
+ (<>
59
+ <h2>{t("rsvp_page.invite_message", { event: event?.title })} </h2>
60
+
61
+ <br />
62
+
63
+ <div dangerouslySetInnerHTML={{ __html: event?.description || "" }} />
64
+
65
+ <br />
66
+
67
+ {event?.speakers?.length > 0 &&
68
+ <div>
69
+ <b>{t("rsvp_page.speakers")}</b>
70
+ <div className={styles.speakerWrapper}>
71
+ {event.speakers.map((speaker) => {
72
+ const isModerator = speaker.id === moderatorId;
73
+ return (
74
+ <div className={styles.speaker}>
75
+ {speaker.pic && (
76
+ <div className={styles.picWrapper}>
77
+ <div className={styles.pic} style={{ backgroundImage: `url(${speaker.pic})` }} />
78
+ </div>
79
+ )}
80
+ <div className={styles.nameWrapper}>
81
+ <div className={styles.name}>
82
+ {speaker.first_name} {speaker.last_name} {isModerator && <Badge className={styles.moderator} pill>Moderator</Badge>}
83
+ </div>
84
+ {speaker.title &&
85
+ <div className={styles.job}>
86
+ <span>{speaker.title}</span>
87
+ {speaker.company && <span className={styles.company}> - {speaker.company}</span>}
88
+ </div>
89
+ }
90
+ </div>
91
+ </div>
92
+ )
93
+ })}
94
+ </div>
95
+ </div>
96
+ }
97
+ <div className={styles.buttonWrapper}>
98
+ {errorMessage && (
99
+ <h3 dangerouslySetInnerHTML={{ __html: errorMessage }} />
100
+ )}
101
+ {rsvpInvitation?.status === RSVP_STATUS.rejected && (
102
+ <h4>{t("rsvp_page.decline_message")} </h4>
103
+ )}
104
+ {rsvpInvitation?.status === RSVP_STATUS.accepted && (
105
+ <h4>{t("rsvp_page.confirm_message")} </h4>
106
+ )}
107
+ {rsvpInvitation?.status === RSVP_STATUS.pending && (
108
+ <>
109
+ <button className="button is-large" onClick={() => handleConfirmRSVP(true)}>
110
+ {t("rsvp_page.accept_button")}
111
+ </button>
112
+ <button className="button is-large" onClick={() => handleConfirmRSVP(false)}>
113
+ {t("rsvp_page.decline_button")}
114
+ </button>
115
+ </>
116
+ )}
117
+ </div>
118
+ </>)
119
+ :
120
+ (
121
+ <>
122
+ <h3>Activity not found.</h3>
123
+ </>
124
+ )
125
+ }
126
+ </div>
127
+ )}
128
+ </Layout>
129
+ );
130
+ };
131
+
132
+ const mapStateToProps = ({ userState, eventState }) => ({
133
+ rsvpInvitation: userState.rsvpInvitation,
134
+ event: eventState.event,
135
+ });
136
+
137
+ export default connect(mapStateToProps,
138
+ {
139
+ getRSVPInvitation,
140
+ acceptRSVPInvitation,
141
+ declineRSVPInvitation,
142
+ getEventById
143
+ }
144
+ )(RSVPPage);
@@ -30,8 +30,8 @@ export const alertPopup = (title, html, confirmLabel, confirmAction, cancelLabel
30
30
  })
31
31
  };
32
32
 
33
-
34
33
  export const needsLogin = (action, msg = null) => {
34
+
35
35
  const defaultMessage = "Please log in to add sessions to My Schedule.";
36
36
 
37
37
  const login = () => {
@@ -0,0 +1,25 @@
1
+ class BaseAPIRequest {
2
+ constructor(fields = [], relations = [], expands = []) {
3
+ this.fields = fields;
4
+ this.relations = relations;
5
+ this.expands = expands;
6
+ }
7
+
8
+ getFields() {
9
+ return this.fields.join(",");
10
+ }
11
+
12
+ getExpands() {
13
+ return this.expands.join(",");
14
+ }
15
+
16
+ getRelations() {
17
+ return this.relations.join(",");
18
+ }
19
+
20
+ static build(apiUrl) {
21
+ throw new Error("This method should not be used from BaseAPIRequest class.");
22
+ }
23
+ }
24
+
25
+ module.exports = BaseAPIRequest;
@@ -0,0 +1,171 @@
1
+ const BaseAPIRequest = require("./BaseAPIRequest");
2
+
3
+ const SPEAKER_MODERATOR_FIELDS = ['id', 'first_name', 'last_name', 'title', 'bio', 'member_id', 'pic', 'big_pic', 'company', 'featured'];
4
+ const DOCUMENTS_FIELDS = ['display_on_site', 'name', 'order', 'class_name', 'type', 'public_url', 'link'];
5
+ const CURRENT_ATTENDANCE_FIELDS = ['member_first_name', 'member_last_name', 'member_pic'];
6
+
7
+ class EventAPIRequest extends BaseAPIRequest {
8
+ static instance;
9
+
10
+ constructor() {
11
+ const primary_fields = [
12
+ "id",
13
+ "created",
14
+ "start_date",
15
+ "end_date",
16
+ "title",
17
+ "abstract",
18
+ "description",
19
+ "level",
20
+ "image",
21
+ "stream_thumbnail",
22
+ "location_id",
23
+ "class_name",
24
+ "allow_feedback",
25
+ "head_count",
26
+ "attendance_count",
27
+ "current_attendance_count",
28
+ "to_record",
29
+ "etherpad_link",
30
+ "streaming_url",
31
+ "streaming_type",
32
+ "meeting_url",
33
+ "current_attendance",
34
+ "attendees_expected_learnt",
35
+ "show_sponsors",
36
+ "duration",
37
+ // RSVP fields
38
+ 'rsvp_type',
39
+ 'rsvp_capacity',
40
+ ];
41
+
42
+ const type_fields = ["type.id", "type.name", "type.allows_publishing_dates", "type.color"];
43
+ const tags_fields = ["tags.id", "tags.tag"];
44
+ const location_fields = ["location.id", "location.class_name", "location.name", "location.venue.name", "location.floor.name"];
45
+ const track_fields = ["track.id", "track.name", "track.icon_url", "track.color", "track.text_color", "track.parent_id"];
46
+ const track_groups_fields = ["track_groups.id", "track_groups.name", "track_groups.parent_id", "track_groups.color", "track_groups.order"];
47
+ const speakers_badge_feature_fields = ["speakers.badge_features.id", "speakers.badge_features.name", "speakers.badge_features.image"];
48
+ const sponsors_fields = ["sponsors.id", "sponsors.name", "sponsors.logo"];
49
+
50
+ const speakers_fields = SPEAKER_MODERATOR_FIELDS.map((e) => `speakers.${e}`);
51
+ const moderator_fields = SPEAKER_MODERATOR_FIELDS.map((e) => `moderator.${e}`);
52
+ const media_upload_fields = DOCUMENTS_FIELDS.map((e) => `media_uploads.${e}`)
53
+ const slides_fields = DOCUMENTS_FIELDS.map((e) => `slides.${e}`)
54
+ const links_fields = DOCUMENTS_FIELDS.map((e) => `links.${e}`)
55
+ const video_fields = DOCUMENTS_FIELDS.map((e) => `videos.${e}`)
56
+ const current_attendance_fields = CURRENT_ATTENDANCE_FIELDS.map((e) => `current_attendance.${e}`)
57
+
58
+ const primary_relations = [
59
+ "slides",
60
+ "links",
61
+ "videos",
62
+ "media_uploads",
63
+ "type",
64
+ "track",
65
+ "location",
66
+ "speakers",
67
+ "moderator",
68
+ "sponsors",
69
+ "tags",
70
+ "current_attendance",
71
+ "allowed_ticket_types"
72
+ ];
73
+
74
+ const speakers_relations = ["speakers.badge_features", "speakers.presentations", "speakers.moderated_presentations"];
75
+
76
+ const track_relations = ["track.track_groups"];
77
+
78
+ const type_relations = ["type.allowed_ticket_types"];
79
+
80
+ const locations_relations = ["location.venue", "location.floor", "location.venue.none", "location.floor.none"];
81
+
82
+ // TODO: review relations for "current_attendance"
83
+
84
+ const relations = [
85
+ ...track_relations,
86
+ ...type_relations,
87
+ ...speakers_relations,
88
+ ...locations_relations,
89
+ ...primary_relations,
90
+ ];
91
+
92
+ const speakers_expand = ["speakers.badge_features"];
93
+
94
+ const location_expand = ["location.venue", "location.floor"];
95
+
96
+ const track_expand = [
97
+ "track.subtracks",
98
+ "track.allowed_access_levels",
99
+ ]
100
+
101
+ const expands = [
102
+ "slides",
103
+ "links",
104
+ "videos",
105
+ "media_uploads",
106
+ "type",
107
+ "track",
108
+ "location",
109
+ "speakers",
110
+ "moderator",
111
+ "sponsors",
112
+ "tags",
113
+ "current_attendance",
114
+ ...track_expand,
115
+ ...speakers_expand,
116
+ ...location_expand
117
+ ];
118
+
119
+ super(
120
+ [
121
+ ...primary_fields,
122
+ ...type_fields,
123
+ ...tags_fields,
124
+ ...location_fields,
125
+ ...track_fields,
126
+ ...track_groups_fields,
127
+ ...sponsors_fields,
128
+ ...speakers_fields,
129
+ ...moderator_fields,
130
+ ...media_upload_fields,
131
+ ...slides_fields,
132
+ ...links_fields,
133
+ ...video_fields,
134
+ ...current_attendance_fields,
135
+ ...speakers_badge_feature_fields
136
+ ],
137
+ relations,
138
+ expands
139
+ );
140
+
141
+ if (!EventAPIRequest.instance) {
142
+ EventAPIRequest.instance = this;
143
+ }
144
+ }
145
+
146
+ static getInstance() {
147
+ if (!EventAPIRequest.instance) {
148
+ new EventAPIRequest();
149
+ }
150
+ return EventAPIRequest.instance;
151
+ }
152
+
153
+ static getParams(apiUrl) {
154
+ const instance = EventAPIRequest.getInstance();
155
+ apiUrl.addQuery("fields", instance.getFields());
156
+ apiUrl.addQuery("expand", instance.getExpands());
157
+ apiUrl.addQuery("relations", instance.getRelations());
158
+ return apiUrl.query(true);
159
+ }
160
+
161
+ static build(apiUrl) {
162
+ const instance = EventAPIRequest.getInstance();
163
+ apiUrl.addQuery("fields", instance.getFields());
164
+ apiUrl.addQuery("expand", instance.getExpands());
165
+ apiUrl.addQuery("relations", instance.getRelations());
166
+
167
+ return apiUrl.toString();
168
+ }
169
+ }
170
+
171
+ module.exports = EventAPIRequest;