@openeventkit/event-site 2.1.17 → 2.1.18-beta.2

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.
package/.nvmrc CHANGED
@@ -1 +1 @@
1
- 18.15.0
1
+ 20.19.4
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@openeventkit/event-site",
3
3
  "description": "Event Site",
4
- "version": "2.1.17",
4
+ "version": "2.1.18-beta.2",
5
5
  "author": "Tipit LLC",
6
6
  "dependencies": {
7
7
  "@emotion/server": "^11.11.0",
@@ -9,8 +9,8 @@
9
9
  "@fortawesome/free-brands-svg-icons": "^6.5.2",
10
10
  "@fortawesome/react-fontawesome": "^0.2.2",
11
11
  "@loadable/component": "^5.16.4",
12
- "@mdx-js/react": "^3.0.1",
13
- "@mdx-js/runtime": "^1.6.22",
12
+ "@mdx-js/mdx": "^3",
13
+ "@mdx-js/react": "^3",
14
14
  "@mui/base": "^5.0.0-beta.40",
15
15
  "@mui/icons-material": "^5.15.20",
16
16
  "@mui/material": "^5.15.20",
@@ -123,9 +123,9 @@
123
123
  "redux-thunk": "^2.4.1",
124
124
  "rehype-external-links": "^3.0.0",
125
125
  "rehype-mdx-import-media": "^1.2.0",
126
- "remark-gfm": "^4.0.0",
126
+ "remark-gfm": "^4.0.1",
127
127
  "sass": "^1.49.9",
128
- "schedule-filter-widget": "3.0.3-beta.1",
128
+ "schedule-filter-widget": "3.0.4-beta.2",
129
129
  "simple-chat-widget": "^1.0.31",
130
130
  "simple-oauth2": "^4.1.0",
131
131
  "slick-carousel": "^1.8.1",
@@ -135,7 +135,7 @@
135
135
  "stream-browserify": "^3.0.0",
136
136
  "stream-chat": "^2.7.2",
137
137
  "stream-chat-react": "3.1.7",
138
- "summit-registration-lite": "6.0.3",
138
+ "summit-registration-lite": "6.0.4-beta.1",
139
139
  "superagent": "8.0.9",
140
140
  "sweetalert2": "^11.11.1",
141
141
  "upcoming-events-widget": "3.0.7",
@@ -112,3 +112,34 @@ export const getEventStreamingInfoById = (
112
112
  return (e);
113
113
  });
114
114
  };
115
+
116
+ /**
117
+ * Fetch overflow event by stream key
118
+ * @param {string} overflowStreamKey
119
+ * @returns {(function(*): Promise<*>)|*}
120
+ */
121
+ export const getOverflowEventByKey = (
122
+ overflowStreamKey
123
+ ) => async (dispatch) => {
124
+ dispatch(startLoading());
125
+
126
+ let params = {
127
+ k: overflowStreamKey
128
+ };
129
+
130
+ return getRequest(
131
+ null,
132
+ createAction(GET_EVENT_DATA),
133
+ `${window.SUMMIT_API_BASE_URL}/api/public/v1/summits/${window.SUMMIT_ID}/events/all/published/overflow`,
134
+ customErrorHandler
135
+ )(params)(dispatch).then((payload) => {
136
+ dispatch(stopLoading());
137
+ return payload.response;
138
+ }).catch(e => {
139
+ dispatch(stopLoading());
140
+ dispatch(createAction(GET_EVENT_DATA_ERROR)(e));
141
+ console.error("ERROR: ", e);
142
+ return (e);
143
+ });
144
+ };
145
+
@@ -1,49 +1,47 @@
1
1
  import React from "react";
2
2
  import PropTypes from "prop-types";
3
- import Mdx from "@mdx-js/runtime";
4
3
  import ContentPageTemplate from "../../templates/content-page/template";
5
4
  import shortcodes from "../../templates/content-page/shortcodes";
5
+ import Mdx from "../../components/Mdx";
6
6
 
7
7
  // function to transform content by replacing relative image URLs with absolute ones
8
8
  const transformContent = (mdx, getAsset) => {
9
- // regex to identify Markdown image tags ![alt](url)
10
- const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
9
+ // regex to identify Markdown image tags ![alt](url)
10
+ const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
11
11
 
12
- return mdx.replace(imageRegex, (match, alt, url) => {
13
- // check if the URL is relative (does not start with http:// or https://)
14
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
15
- const asset = getAsset(url);
16
- if (asset && asset.url) {
17
- return `![${alt}](${asset.url})`;
18
- }
19
- }
20
- return match; // return the original match if it's already an absolute URL
21
- });
12
+ return mdx.replace(imageRegex, (match, alt, url) => {
13
+ // check if the URL is relative (does not start with http:// or https://)
14
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
15
+ const asset = getAsset(url);
16
+ if (asset && asset.url) {
17
+ return `![${alt}](${asset.url})`;
18
+ }
19
+ }
20
+ return match; // return the original match if it's already an absolute URL
21
+ });
22
22
  };
23
23
 
24
24
  // function to render transformed content with Mdx
25
25
  const renderContent = (mdx, getAsset) => (
26
- <Mdx components={shortcodes}>
27
- {transformContent(mdx, getAsset)}
28
- </Mdx>
26
+ <Mdx shortcodes={shortcodes} content={transformContent(mdx, getAsset)}/>
29
27
  );
30
28
 
31
29
  const ContentPagePreview = ({ entry, getAsset }) => {
32
- const title = entry.getIn(["data", "title"]);
33
- const body = entry.getIn(["data", "body"]);
34
- return (
35
- <ContentPageTemplate
36
- title={title}
37
- content={renderContent(body, getAsset)}
38
- />
39
- );
30
+ const title = entry.getIn(["data", "title"]);
31
+ const body = entry.getIn(["data", "body"]);
32
+ return (
33
+ <ContentPageTemplate
34
+ title={title}
35
+ content={renderContent(body, getAsset)}
36
+ />
37
+ );
40
38
  };
41
39
 
42
40
  ContentPagePreview.propTypes = {
43
- entry: PropTypes.shape({
44
- getIn: PropTypes.func.isRequired
45
- }).isRequired,
46
- getAsset: PropTypes.func.isRequired
41
+ entry: PropTypes.shape({
42
+ getIn: PropTypes.func.isRequired
43
+ }).isRequired,
44
+ getAsset: PropTypes.func.isRequired
47
45
  };
48
46
 
49
- export default ContentPagePreview;
47
+ export default ContentPagePreview;
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import { Container, Box, Typography } from "@mui/material";
3
+
4
+ const ErrorMessage = ({ title, message, fullScreen = false }) => {
5
+ const containerStyles = fullScreen
6
+ ? { height: "100vh", display: "flex", justifyContent: "center", alignItems: "center" }
7
+ : {};
8
+
9
+ return (
10
+ <Container maxWidth={false} style={containerStyles}>
11
+ <Box display="flex" justifyContent="center" alignItems="center" height={fullScreen ? "auto" : "100vh"}>
12
+ <Box textAlign="center" padding={4}>
13
+ <Typography variant="h3" component="h1" gutterBottom style={{ fontWeight: "bold", color: "#333" }}>
14
+ {title}
15
+ </Typography>
16
+ <Typography variant="h4" component="p" style={{ color: "#666", lineHeight: 1.6, whiteSpace: "pre-line" }}>
17
+ {message}
18
+ </Typography>
19
+ </Box>
20
+ </Box>
21
+ </Container>
22
+ );
23
+ };
24
+
25
+ export default ErrorMessage;
@@ -0,0 +1,29 @@
1
+ import React, { useMemo } from "react";
2
+ import { evaluateSync } from "@mdx-js/mdx";
3
+ import * as jsxRuntime from "react/jsx-runtime";
4
+ import remarkGfm from "remark-gfm";
5
+ import rehypeExternalLinks from "rehype-external-links";
6
+ import { MDXProvider } from "@mdx-js/react";
7
+
8
+
9
+ const Mdx = ({ content, shortcodes }) => {
10
+ const mdxContent = useMemo(() => {
11
+ if ( !content) return null;
12
+ try {
13
+ const { default: Comp } = evaluateSync(content, {
14
+ ...jsxRuntime,
15
+ remarkPlugins: [remarkGfm],
16
+ rehypePlugins: [[rehypeExternalLinks, { target: "_blank", rel: ["nofollow","noopener","noreferrer"] }]],
17
+ useDynamicImport: false, // ensure no async imports in runtime content
18
+ });
19
+ return Comp;
20
+ } catch (err) {
21
+ console.error("MDX evaluate error:", err);
22
+ return null;
23
+ }
24
+ }, [content]);
25
+
26
+ return <MDXProvider components={shortcodes}>{mdxContent}</MDXProvider>;
27
+ }
28
+
29
+ export default Mdx;
@@ -20,7 +20,7 @@ import SynchWordsPlayer from "./SyncWordsPlayer";
20
20
  * @returns {JSX.Element}
21
21
  * @constructor
22
22
  */
23
- const VideoComponent = ({ url, title, namespace, isLive, firstHalf, autoPlay, start, tokens, onError = () => {} }) => {
23
+ const VideoComponent = ({ url, title, namespace, isLive, firstHalf, autoPlay, start, tokens, onError = () => {}, onLoaded = () => {} }) => {
24
24
 
25
25
  if (url) {
26
26
  // using mux player
@@ -46,6 +46,8 @@ const VideoComponent = ({ url, title, namespace, isLive, firstHalf, autoPlay, st
46
46
  autoplay={autoPlay}
47
47
  start={start}
48
48
  className={styles.vimeoPlayer}
49
+ onLoaded={onLoaded}
50
+ onError={onError}
49
51
  />
50
52
  );
51
53
  };
@@ -1 +1 @@
1
- {"favicon":{"asset":"icon.png"},"widgets":{"chat":{"enabled":true,"showQA":false,"showHelp":false,"defaultScope":"page"},"schedule":{"allowClick":true}},"identityProviderButtons":[{"buttonColor":"#082238","providerLabel":"Continue with FNid","providerLogo":"logo_fn.svg","providerLogoSize":27},{"buttonColor":"#0A66C2","providerLabel":"Sign in with LinkedIn","providerParam":"linkedin","providerLogo":"logo_linkedin.svg","providerLogoSize":18},{"buttonColor":"#000000","providerLabel":"Sign in with Apple","providerParam":"apple","providerLogoSize":17,"providerLogo":"logo_apple.svg"},{"buttonColor":"#1877F2","providerLabel":"Login with Facebook","providerParam":"facebook","providerLogo":"logo_facebook.svg","providerLogoSize":20}],"maintenanceMode":{"enabled":false,"title":"Site under maintenance","subtitle":"Please reload page shortly"},"staticJsonFilesBuildTime":[{"file":"src/data/summit.json","build_time":1748026605601},{"file":"src/data/events.json","build_time":1748026626928},{"file":"src/data/events.idx.json","build_time":1748026626985},{"file":"src/data/speakers.json","build_time":1748026634015},{"file":"src/data/speakers.idx.json","build_time":1748026634022},{"file":"src/content/sponsors.json","build_time":1748026637386},{"file":"src/data/voteable-presentations.json","build_time":1748026638342}],"lastBuild":1748026638342}
1
+ {"favicon":{"asset":"icon.png"},"widgets":{"chat":{"enabled":true,"showQA":false,"showHelp":false,"defaultScope":"page"},"schedule":{"allowClick":true}},"identityProviderButtons":[{"buttonColor":"#082238","providerLabel":"Continue with FNid","providerLogo":"logo_fn.svg","providerLogoSize":27},{"buttonColor":"#0A66C2","providerLabel":"Sign in with LinkedIn","providerParam":"linkedin","providerLogo":"logo_linkedin.svg","providerLogoSize":18},{"buttonColor":"#000000","providerLabel":"Sign in with Apple","providerParam":"apple","providerLogoSize":17,"providerLogo":"logo_apple.svg"},{"buttonColor":"#1877F2","providerLabel":"Login with Facebook","providerParam":"facebook","providerLogo":"logo_facebook.svg","providerLogoSize":20}],"maintenanceMode":{"enabled":false,"title":"Site under maintenance","subtitle":"Please reload page shortly"},"staticJsonFilesBuildTime":[{"file":"src/data/summit.json","build_time":1755200450530},{"file":"src/data/events.json","build_time":1755200471128},{"file":"src/data/events.idx.json","build_time":1755200471179},{"file":"src/data/speakers.json","build_time":1755200476104},{"file":"src/data/speakers.idx.json","build_time":1755200476112},{"file":"src/content/sponsors.json","build_time":1755200476553},{"file":"src/data/voteable-presentations.json","build_time":1755200476925}],"lastBuild":1755200476925}
@@ -1 +1 @@
1
- [{"id":360,"created":1741369599,"last_edited":1741369599,"order":2,"summit_id":63,"is_published":true,"side_image":null,"header_image":null,"header_image_mobile":null,"carousel_advertise_image":null,"marquee":"","intro":"","external_link":"","video_link":"","chat_link":"","featured_event_id":0,"header_image_alt_text":"","side_image_alt_text":"","header_image_mobile_alt_text":"","carousel_advertise_image_alt_text":"","show_logo_in_event_page":true,"lead_report_setting_id":null,"extra_questions":[541],"members":[96743,29308],"company":{"id":3496,"created":1654774567,"last_edited":1654774567,"name":"Tipit","url":null,"url_segment":null,"city":null,"state":null,"country":null,"description":null,"industry":null,"contributions":null,"member_level":null,"overview":null,"products":null,"commitment":null,"commitment_author":null,"logo":null,"big_logo":null,"color":"#f0f0ee","display_on_site":false,"featured":false,"contact_email":null,"admin_email":null,"sponsorships":[],"project_sponsorships":[]},"sponsorship":{"id":2065,"widget_title":"Test Widget Title","lobby_template":"big-images","expo_hall_template":"big-images","sponsor_page_template":"small-header","event_page_template":"small-images","sponsor_page_use_disqus_widget":false,"sponsor_page_use_live_event_widget":false,"sponsor_page_use_schedule_widget":false,"sponsor_page_use_banner_widget":false,"badge_image":"https://summit-api-dev-assets.nyc3.digitaloceanspaces.com/summits/63/summit_sponsorship_types/2065/shopping.png","badge_image_alt_text":"","summit_id":63,"order":2,"should_display_on_expo_hall_page":false,"should_display_on_lobby_page":false,"type":{"id":8,"created":1603223268,"last_edited":1603223268,"name":"Featured","label":"Featured","order":8,"size":"Large"}},"ads":[],"materials":[],"social_networks":[]}]
1
+ []
@@ -0,0 +1,20 @@
1
+ import i18n from "i18next";
2
+ import { initReactI18next } from "react-i18next";
3
+ import LanguageDetector from "i18next-browser-languagedetector";
4
+ import en from "./locales/en.json";
5
+
6
+ i18n
7
+ .use(LanguageDetector)
8
+ .use(initReactI18next)
9
+ .init({
10
+ fallbackLng: "en",
11
+ debug: false,
12
+ resources: {
13
+ en: { translation: en }
14
+ },
15
+ interpolation: {
16
+ escapeValue: false,
17
+ }
18
+ });
19
+
20
+ export default i18n;
@@ -0,0 +1,13 @@
1
+ {
2
+ "overflow_player": {
3
+ "no_stream_key": "No stream key provided. Please check the URL.",
4
+ "stream_unavailable": {
5
+ "title": "Stream Unavailable",
6
+ "message": "The overflow stream you're looking for is not currently available.\nPlease check with event staff or try again later."
7
+ },
8
+ "no_stream_available": {
9
+ "title": "No Stream Available",
10
+ "message": "No event data found for this overflow stream."
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,149 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { useParams } from "react-router-dom";
3
+ import { connect } from "react-redux";
4
+ import { useTranslation } from "react-i18next";
5
+ import { getOverflowEventByKey } from "../../actions/event-actions";
6
+ import { Container, Box, CircularProgress, Typography, Fade } from "@mui/material";
7
+ import VideoComponent from "../../components/VideoComponent";
8
+ import ErrorMessage from "../../components/ErrorMessage";
9
+ import "../../i18n";
10
+
11
+ const OverflowPlayerPage = ({
12
+ location,
13
+ fetchOverflowEvent,
14
+ loading,
15
+ event,
16
+ error
17
+ }) => {
18
+ const params = new URLSearchParams(location.search);
19
+ const overflowStreamKey = params.get("k");
20
+ const { t } = useTranslation();
21
+ const [showTitleFlash, setShowTitleFlash] = useState(false);
22
+ const [videoError, setVideoError] = useState(false);
23
+
24
+ useEffect(() => {
25
+ if (overflowStreamKey) {
26
+ fetchOverflowEvent(overflowStreamKey);
27
+ }
28
+ }, [overflowStreamKey, fetchOverflowEvent]);
29
+
30
+ useEffect(() => {
31
+ if (event && !loading && !error) {
32
+ setShowTitleFlash(true);
33
+ }
34
+ }, [event, loading, error]);
35
+
36
+ const handleVideoLoad = () => {
37
+ setShowTitleFlash(false);
38
+ };
39
+
40
+ const handleVideoError = (e) => {
41
+ console.error("Video error:", e);
42
+ setVideoError(true);
43
+ setShowTitleFlash(false);
44
+ };
45
+
46
+ if (!overflowStreamKey) {
47
+ return (
48
+ <ErrorMessage
49
+ title={t('overflow_player.stream_unavailable.title')}
50
+ message={t('overflow_player.no_stream_key')}
51
+ fullScreen={true}
52
+ />
53
+ );
54
+ }
55
+
56
+ return (
57
+ <>
58
+ {loading || (!event && !error && overflowStreamKey) ? (
59
+ <Container maxWidth={false}>
60
+ <Box display="flex" justifyContent="center" alignItems="center" height="100vh">
61
+ <CircularProgress sx={{ color: 'var(--color_accent)' }} />
62
+ </Box>
63
+ </Container>
64
+ ) : error ? (
65
+ <ErrorMessage
66
+ title={t('overflow_player.stream_unavailable.title')}
67
+ message={t('overflow_player.stream_unavailable.message')}
68
+ fullScreen={true}
69
+ />
70
+ ) : event ? (
71
+ videoError ? (
72
+ <ErrorMessage
73
+ title={t('overflow_player.stream_unavailable.title')}
74
+ message={t('overflow_player.stream_unavailable.message')}
75
+ fullScreen={true}
76
+ />
77
+ ) : (
78
+ <Box
79
+ width="100vw"
80
+ height="100vh"
81
+ position="relative"
82
+ sx={{
83
+ '& .video-module--vimeoPlayer--ac603': {
84
+ paddingBottom: '0 !important',
85
+ height: '100vh !important',
86
+ '& iframe': {
87
+ width: '100vw !important',
88
+ height: '100vh !important',
89
+ position: 'absolute',
90
+ top: 0,
91
+ left: 0
92
+ }
93
+ }
94
+ }}
95
+ >
96
+ <VideoComponent
97
+ url={event.overflow_streaming_url}
98
+ title={event.title}
99
+ isLive={true}
100
+ autoPlay={true}
101
+ tokens={event.overflow_tokens}
102
+ onLoaded={handleVideoLoad}
103
+ onError={handleVideoError}
104
+ />
105
+
106
+ <Fade in={showTitleFlash} timeout={500}>
107
+ <Typography
108
+ variant="h3"
109
+ component="h1"
110
+ position="absolute"
111
+ top="50%"
112
+ left="50%"
113
+ sx={{
114
+ transform: 'translate(-50%, -50%)',
115
+ color: 'white',
116
+ fontWeight: 'bold',
117
+ textShadow: '2px 2px 4px rgba(0,0,0,0.8)',
118
+ zIndex: 1000,
119
+ maxWidth: '80%',
120
+ textAlign: 'center'
121
+ }}
122
+ >
123
+ {event.title}
124
+ </Typography>
125
+ </Fade>
126
+ </Box>
127
+ )
128
+ ) : (
129
+ <ErrorMessage
130
+ title={t('overflow_player.no_stream_available.title')}
131
+ message={t('overflow_player.no_stream_available.message')}
132
+ fullScreen={true}
133
+ />
134
+ )}
135
+ </>
136
+ );
137
+ };
138
+
139
+ const mapStateToProps = ({ eventState }) => ({
140
+ loading: eventState.loading,
141
+ event: eventState.event,
142
+ error: eventState.error
143
+ });
144
+
145
+ const mapDispatchToProps = (dispatch) => ({
146
+ fetchOverflowEvent: (streamKey) => dispatch(getOverflowEventByKey(streamKey))
147
+ });
148
+
149
+ export default connect(mapStateToProps, mapDispatchToProps)(OverflowPlayerPage);
@@ -8,6 +8,7 @@ const DEFAULT_STATE = {
8
8
  event: null,
9
9
  lastUpdate: null,
10
10
  tokens:null,
11
+ error: null,
11
12
  };
12
13
 
13
14
  const eventReducer = (state = DEFAULT_STATE, action) => {
@@ -31,7 +32,7 @@ const eventReducer = (state = DEFAULT_STATE, action) => {
31
32
  const event = payload?.response ?? payload.event;
32
33
  // check if we need to update the current event or do we need to just use the new one
33
34
  const updatedEvent = event.id === state?.event?.id ? {...state, ...event} : event;
34
- return { ...state, loading: false, event: updatedEvent, tokens: null };
35
+ return { ...state, loading: false, event: updatedEvent, tokens: null, error: null };
35
36
  }
36
37
  case SYNC_DATA:{
37
38
  // update current event if we have one on data sync
@@ -46,7 +47,8 @@ const eventReducer = (state = DEFAULT_STATE, action) => {
46
47
  return state;
47
48
  }
48
49
  case GET_EVENT_DATA_ERROR: {
49
- return { ...state, loading: false, event: null, tokens: null }
50
+ const errorMessage = payload?.message || payload?.error || 'Failed to load event';
51
+ return { ...state, loading: false, event: null, tokens: null, error: errorMessage }
50
52
  }
51
53
  // reload event state
52
54
  case RELOAD_EVENT_STATE:{
@@ -1,4 +1,4 @@
1
- $color_accent: #00B189;
1
+ $color_accent: #8ac82d;
2
2
  $color_alerts: #ff0000;
3
3
  $color_background_light: #ffffff;
4
4
  $color_background_dark: #000000;
@@ -21,7 +21,7 @@ $color_input_text_color_disabled_light: #ffffff;
21
21
  $color_input_text_color_disabled_dark: #ffffff;
22
22
  $color_primary: #8DC63F;
23
23
  $color_primary_contrast: #FFFFFF;
24
- $color_secondary: #5e5f62;
24
+ $color_secondary: #26387f;
25
25
  $color_secondary_contrast: #005870;
26
26
  $color_text_light: #ffffff;
27
27
  $color_text_med: #828282;
@@ -8,6 +8,7 @@
8
8
  overflow: hidden;
9
9
  max-width: 100%;
10
10
  background: black;
11
+ height: 100vh;
11
12
 
12
13
  iframe, object, embed{
13
14
  position: absolute;
@@ -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;
@@ -12,4 +12,25 @@ export const insertSorted = (arr, elem, fnCheck) =>
12
12
  arr[i + 1] = arr[i];
13
13
  arr[i + 1] = elem;
14
14
  return i+1;
15
- }
15
+ }
16
+
17
+ export const rebuildIndex = (array, key = 'id') => {
18
+ return array.reduce((acc, item, index) => {
19
+ acc[item[key]] = index;
20
+ return acc;
21
+ }, {});
22
+ };
23
+
24
+ export const getIndexedItem = (indexMap, array, id) => {
25
+ const idx = indexMap?.[id] ?? -1;
26
+ const item = idx !== -1 ? array[idx] : null;
27
+ return item && item.id === id ? { idx, item } : null;
28
+ };
29
+
30
+ export const replaceInArray = (array, predicate, newItem) => {
31
+ const found = array.findIndex(predicate);
32
+ if (found !== -1) {
33
+ return [...array.slice(0, found), newItem, ...array.slice(found + 1)];
34
+ }
35
+ return array;
36
+ };
@@ -1,6 +1,6 @@
1
1
  import AbstractSynchStrategy from "./abstract_synch_strategy";
2
2
  import {fetchEventById, fetchStreamingInfoByEventId} from "../../actions/fetch-entities-actions";
3
- import {insertSorted, intCheck} from "../../utils/arrayUtils";
3
+ import {insertSorted, intCheck, rebuildIndex} from "../../utils/arrayUtils";
4
4
  import {
5
5
  BUCKET_EVENTS_DATA_KEY,
6
6
  BUCKET_EVENTS_IDX_DATA_KEY,
@@ -62,10 +62,8 @@ class ActivitySynchStrategy extends AbstractSynchStrategy{
62
62
  });
63
63
 
64
64
  // Rebuild the full event index to be safe
65
- this.allIDXEvents = eventsData.reduce((acc, e, i) => {
66
- acc[e.id] = i;
67
- return acc;
68
- }, {});
65
+
66
+ this.allIDXEvents = rebuildIndex(eventsData);
69
67
 
70
68
  // Update speakers
71
69
  if (entity.speakers) {
@@ -1,6 +1,7 @@
1
1
  import AbstractSynchStrategy from "./abstract_synch_strategy";
2
2
  import {fetchSpeakerById} from "../../actions/fetch-entities-actions";
3
3
  import {
4
+ BUCKET_SUMMIT_DATA_KEY,
4
5
  BUCKET_EVENTS_DATA_KEY,
5
6
  BUCKET_EVENTS_IDX_DATA_KEY,
6
7
  BUCKET_SPEAKERS_DATA_KEY,
@@ -8,120 +9,106 @@ import {
8
9
  saveFile
9
10
  } from "../../utils/dataUpdatesUtils";
10
11
 
12
+ import {rebuildIndex, getIndexedItem} from "../../utils/arrayUtils";
13
+ import moment from "moment-timezone";
14
+
11
15
  /**
12
16
  * SpeakerSynchStrategy
13
17
  */
14
- class SpeakerSynchStrategy extends AbstractSynchStrategy{
18
+ class SpeakerSynchStrategy extends AbstractSynchStrategy {
15
19
  async process(payload) {
16
20
 
17
21
  console.log(`SpeakerSynchStrategy::process`, payload);
18
22
 
19
23
  const {entity_operator, entity_id} = payload;
20
24
 
25
+ if (entity_operator !== 'UPDATE') return;
26
+
21
27
  const entity = await fetchSpeakerById(this.summit.id, entity_id, this.accessToken);
22
28
 
29
+ if (!entity) return Promise.reject('SpeakerSynchStrategy::process entity not retrieved.');
30
+
23
31
  let eventsData = [...this.allEvents];
24
32
 
25
- if (entity_operator === 'UPDATE') {
26
- if(!entity) return Promise.reject('SpeakerSynchStrategy::process entity not retrieved.');
27
- let idx = this.allIDXSpeakers.hasOwnProperty(entity_id) ? this.allIDXSpeakers[entity_id] : -1;
28
- let formerEntity = idx === -1 ? null : ( ( this.allSpeakers.length - 1 ) >= idx ? this.allSpeakers[idx] : null);
29
- if (formerEntity && formerEntity.id !== entity_id) {
30
- console.log('SpeakerSynchStrategy::process entities are not the same.'); // it's not the same
31
- formerEntity = this.allSpeakers.find((e, index) => {
32
- let res = e.id == entity.id;
33
- if(res){
34
- console.log(`SpeakerSynchStrategy::process entity id ${entity.id} found at idx ${index}`);
35
- idx = index;
36
- }
37
- return res;
38
- });
39
- if(!formerEntity){
40
- return Promise.reject(`SpeakerSynchStrategy::process entity id ${entity.id} not found`);
41
- }
42
- }
43
- const updatedSpeaker = formerEntity ? {...formerEntity, ...entity} : entity;
44
- if(idx === -1){
45
- console.log(`SpeakerSynchStrategy::process entity does not exists, inserting it at end`, updatedSpeaker);
46
- this.allSpeakers.push(updatedSpeaker);
47
- this.allIDXSpeakers[updatedSpeaker.id] = this.allSpeakers.length - 1;
48
- }
49
- else {
50
- console.log(`SpeakerSynchStrategy::process updating speaker at idx ${idx}`, updatedSpeaker);
51
- this.allSpeakers[idx] = updatedSpeaker;
33
+ // Remove speaker and re-insert
34
+ this.allSpeakers = this.allSpeakers.filter(s => s.id !== entity_id);
35
+ this.allSpeakers.push(entity);
36
+ this.allIDXSpeakers = rebuildIndex(this.allSpeakers);
37
+
38
+ // Update events where speaker is listed
39
+ for (const eventId of entity.all_presentations || []) {
40
+ const res = getIndexedItem(this.allIDXEvents, eventsData, eventId);
41
+ if (!res || !Array.isArray(res.item.speakers)) {
42
+ console.log(`SpeakerSynchStrategy::process: event ${eventId} not found or invalid`);
43
+ continue;
52
44
  }
53
45
 
54
- // check presentations
55
- if (entity && entity.hasOwnProperty('all_presentations')) {
56
- for (const publishedEventId of entity.all_presentations) {
57
- const eventIdx = this.allIDXEvents.hasOwnProperty(publishedEventId) ? this.allIDXEvents[publishedEventId] : -1;
58
- let formerEntity = eventIdx === -1 ? null : ( (eventsData.length - 1) >= eventIdx ? eventsData[eventIdx] : null);
59
- if(formerEntity === null){
60
- console.log(`SpeakerSynchStrategy::process all_presentations activity ${publishedEventId} not found on data set`);
61
- continue;
62
- }
63
- if (formerEntity && formerEntity.id !== publishedEventId) continue;
64
- // check if speakers collection
65
- let speakers = formerEntity.speakers.map( s => {
66
- if(s.id === entity_id){
67
- return updatedSpeaker;
68
- }
69
- return s;
70
- })
71
- eventsData[eventIdx] = {...formerEntity, speakers: speakers};
72
- }
73
- }
46
+ const updatedSpeakers = res.item.speakers.map(s =>
47
+ s.id === entity.id ? entity : s
48
+ );
74
49
 
75
- // check moderated presentations
76
- if(entity && entity.hasOwnProperty('all_moderated_presentations')){
77
- for (const publishedEventId of entity.all_moderated_presentations) {
78
- const eventIdx = this.allIDXEvents.hasOwnProperty(publishedEventId) ? this.allIDXEvents[publishedEventId] : -1;
79
- let formerEntity = eventIdx === -1 ? null : ( (eventsData.length - 1 ) >= eventIdx ? eventsData[eventIdx] : null);
80
- if(formerEntity === null) {
81
- console.log(`SpeakerSynchStrategy::process all_moderated_presentations activity ${publishedEventId} not found on data set`);
82
- continue;
83
- }
84
- if (formerEntity && formerEntity.id !== publishedEventId) continue; // it's not the same
85
- // check if speakers collection
86
- eventsData[eventIdx] = {...formerEntity, moderator: updatedSpeaker};
87
- }
88
- }
50
+ eventsData[res.idx] = {
51
+ ...res.item,
52
+ speakers: updatedSpeakers,
53
+ };
54
+ }
89
55
 
90
- // update files on cache
91
- console.log(`SpeakerSynchStrategy::process updating cache files`);
56
+ // Update events where speaker is moderator
57
+ for (const eventId of entity.all_moderated_presentations || []) {
58
+ const res = getIndexedItem(this.allIDXEvents, eventsData, eventId);
59
+ if (!res) {
60
+ console.log(`SpeakerSynchStrategy::process: moderator event ${eventId} not found`);
61
+ continue;
62
+ }
92
63
 
93
- try {
94
- const localNowUtc = Date.now();
64
+ eventsData[res.idx] = {
65
+ ...res.item,
66
+ moderator: entity,
67
+ };
68
+ }
95
69
 
96
- await saveFile(this.summit.id, BUCKET_EVENTS_DATA_KEY, eventsData, localNowUtc);
70
+ // Update summit timestamp
71
+ this.summit = {
72
+ ...this.summit,
73
+ timestamp: moment().unix(),
74
+ };
97
75
 
98
- await saveFile(this.summit.id, BUCKET_EVENTS_IDX_DATA_KEY, this.allIDXEvents, localNowUtc);
76
+ // update files on cache
77
+ console.log(`SpeakerSynchStrategy::process updating cache files`);
99
78
 
100
- await saveFile(this.summit.id, BUCKET_SPEAKERS_DATA_KEY, this.allSpeakers, localNowUtc);
79
+ try {
80
+ const localNowUtc = Date.now();
101
81
 
102
- await saveFile(this.summit.id, BUCKET_SPEAKERS_IDX_DATA_KEY, this.allIDXSpeakers, localNowUtc);
82
+ await saveFile(this.summit.id, BUCKET_SUMMIT_DATA_KEY, this.summit, localNowUtc);
103
83
 
104
- }
105
- catch (e){
106
- console.log(e);
107
- }
84
+ await saveFile(this.summit.id, BUCKET_EVENTS_DATA_KEY, eventsData, localNowUtc);
108
85
 
109
- let res = {
110
- payload,
111
- entity,
112
- summit : this.summit,
113
- eventsData,
114
- allIDXEvents : this.allIDXEvents,
115
- allSpeakers : this.allSpeakers,
116
- allIDXSpeakers : this.allIDXSpeakers
117
- };
86
+ await saveFile(this.summit.id, BUCKET_EVENTS_IDX_DATA_KEY, this.allIDXEvents, localNowUtc);
118
87
 
119
- console.log(`SpeakerSynchStrategy::process done`, res);
88
+ await saveFile(this.summit.id, BUCKET_SPEAKERS_DATA_KEY, this.allSpeakers, localNowUtc);
120
89
 
121
- return Promise.resolve(res);
90
+ await saveFile(this.summit.id, BUCKET_SPEAKERS_IDX_DATA_KEY, this.allIDXSpeakers, localNowUtc);
122
91
 
92
+ } catch (e) {
93
+ console.log(e);
123
94
  }
95
+
96
+ let res = {
97
+ payload,
98
+ entity,
99
+ summit: this.summit,
100
+ eventsData,
101
+ allIDXEvents: this.allIDXEvents,
102
+ allSpeakers: this.allSpeakers,
103
+ allIDXSpeakers: this.allIDXSpeakers
104
+ };
105
+
106
+ console.log(`SpeakerSynchStrategy::process done`, res);
107
+
108
+ return Promise.resolve(res);
109
+
124
110
  }
111
+
125
112
  }
126
113
 
127
114
  export default SpeakerSynchStrategy;
@@ -4,6 +4,7 @@ import {
4
4
  BUCKET_SUMMIT_DATA_KEY,
5
5
  saveFile
6
6
  } from "../../utils/dataUpdatesUtils";
7
+ import moment from "moment-timezone";
7
8
 
8
9
  /**
9
10
  * SummitSynchStrategy
@@ -16,13 +17,19 @@ class SummitSynchStrategy extends AbstractSynchStrategy {
16
17
 
17
18
  const {entity_operator} = payload;
18
19
 
19
- const entity = await fetchSummitById(this.summit.id, this.accessToken);
20
+ let entity = await fetchSummitById(this.summit.id, this.accessToken);
20
21
 
21
22
  let eventsData = [...this.allEvents];
22
23
 
23
24
  if (entity_operator === 'UPDATE') {
24
25
  if (!entity) return Promise.reject('SummitSynchStrategy::process entity not found.');
25
26
 
27
+ // Update summit timestamp
28
+ entity = {
29
+ ...entity,
30
+ timestamp: moment().unix(),
31
+ };
32
+
26
33
  // update files on cache
27
34
  console.log(`SummitSynchStrategy::process updating cache files`);
28
35
 
@@ -4,9 +4,10 @@ import {
4
4
  BUCKET_EVENTS_DATA_KEY,
5
5
  BUCKET_EVENTS_IDX_DATA_KEY,
6
6
  BUCKET_SPEAKERS_DATA_KEY,
7
- BUCKET_SPEAKERS_IDX_DATA_KEY,
7
+ BUCKET_SPEAKERS_IDX_DATA_KEY, BUCKET_SUMMIT_DATA_KEY,
8
8
  saveFile,
9
9
  } from "../../utils/dataUpdatesUtils";
10
+ import moment from "moment-timezone";
10
11
 
11
12
  /**
12
13
  * VenueRoomSynchStrategy
@@ -39,12 +40,21 @@ class VenueRoomSynchStrategy extends AbstractSynchStrategy{
39
40
  eventsData[idx] = {...formerEntity, location: entity};
40
41
  }
41
42
 
43
+ // update summit
44
+ // Update summit timestamp
45
+ this.summit = {
46
+ ...this.summit,
47
+ timestamp: moment().unix(),
48
+ };
49
+
42
50
  // update files on cache
43
51
  console.log(`VenueRoomSynchStrategy::process updating cache files`);
44
52
 
45
53
  try {
46
54
  const localNowUtc = Date.now();
47
55
 
56
+ await saveFile(this.summit.id, BUCKET_SUMMIT_DATA_KEY, this.summit, localNowUtc);
57
+
48
58
  await saveFile(this.summit.id, BUCKET_EVENTS_DATA_KEY, eventsData, localNowUtc);
49
59
 
50
60
  await saveFile(this.summit.id, BUCKET_EVENTS_IDX_DATA_KEY, this.allIDXEvents, localNowUtc);