@openeventkit/event-site 2.1.20 → 2.1.22
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/package.json +3 -2
- package/src/cms/config/collections/configurationsCollection/siteSettings/index.js +2 -0
- package/src/cms/config/collections/configurationsCollection/siteSettings/typeDefs.js +10 -0
- package/src/components/CertificatePDF.js +313 -0
- package/src/components/CertificateSection.js +139 -0
- package/src/templates/full-profile-page.js +61 -2
- package/src/utils/certificateSettings.js +45 -0
- package/src/utils/schedule.js +8 -4
- package/src/utils/useMarketingSettings.js +48 -1
- package/src/utils/useSiteSettings.js +11 -0
- package/src/workers/feeds.worker.js +85 -90
- package/static/fonts/fonts.css +120 -20
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-200.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-200italic.ttf +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-200italic.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-300.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-300italic.ttf +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-300italic.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-400.ttf +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-400.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-400italic.ttf +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-400italic.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-500.ttf +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-500.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-500italic.ttf +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-500italic.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-600.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-600italic.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-700.ttf +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-700.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-700italic.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-800.ttf +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-800.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-800italic.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-900.ttf +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-900.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v18-latin-900italic.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v12-latin-300.woff +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v12-latin-300.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v12-latin-300italic.woff +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v12-latin-300italic.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v12-latin-600.woff +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v12-latin-600.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v12-latin-600italic.woff +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v12-latin-600italic.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v12-latin-700.woff +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v12-latin-700.woff2 +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v12-latin-700italic.woff +0 -0
- package/static/fonts/nunito-sans/nunito-sans-v12-latin-700italic.woff2 +0 -0
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.
|
|
4
|
+
"version": "2.1.22",
|
|
5
5
|
"author": "Tipit LLC",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@emotion/server": "^11.11.0",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"font-awesome": "^4.7.0",
|
|
56
56
|
"formik": "^2.4.6",
|
|
57
57
|
"fs-extra": "^11.3.0",
|
|
58
|
-
"full-schedule-widget": "3.1.
|
|
58
|
+
"full-schedule-widget": "3.1.1-beta.2",
|
|
59
59
|
"gatsby": "^5.13.5",
|
|
60
60
|
"gatsby-alias-imports": "^1.0.6",
|
|
61
61
|
"gatsby-plugin-decap-cms": "^4.0.4",
|
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
"immutability-helper": "2.9.1",
|
|
79
79
|
"immutable": "^5.0.0-beta.5",
|
|
80
80
|
"js-cookie": "^3.0.5",
|
|
81
|
+
"jsdom": "^24",
|
|
81
82
|
"klaro": "^0.7.21",
|
|
82
83
|
"lite-schedule-widget": "3.0.3",
|
|
83
84
|
"live-event-widget": "4.0.4",
|
|
@@ -148,6 +148,7 @@ const siteSettings = {
|
|
|
148
148
|
selectField({
|
|
149
149
|
label: "Font Format",
|
|
150
150
|
name: "fontFormat",
|
|
151
|
+
hint: "TTF format is required for certificate PDF generation",
|
|
151
152
|
multiple: false,
|
|
152
153
|
required: false,
|
|
153
154
|
options: mapObjectToSelectOptions(FONT_FORMATS)
|
|
@@ -167,6 +168,7 @@ const siteSettings = {
|
|
|
167
168
|
selectField({
|
|
168
169
|
label: "Font Format",
|
|
169
170
|
name: "fontFormat",
|
|
171
|
+
hint: "TTF format is required for certificate PDF generation",
|
|
170
172
|
multiple: false,
|
|
171
173
|
required: false,
|
|
172
174
|
options: mapObjectToSelectOptions(FONT_FORMATS)
|
|
@@ -8,6 +8,15 @@ module.exports = `
|
|
|
8
8
|
type Favicon {
|
|
9
9
|
asset: File @fileByRelativePath
|
|
10
10
|
}
|
|
11
|
+
type FontFile {
|
|
12
|
+
fontFile: String
|
|
13
|
+
fontFormat: String
|
|
14
|
+
}
|
|
15
|
+
type SiteFont {
|
|
16
|
+
fontFamily: String
|
|
17
|
+
regularFont: FontFile
|
|
18
|
+
boldFont: FontFile
|
|
19
|
+
}
|
|
11
20
|
type Schedule {
|
|
12
21
|
allowClick: Boolean
|
|
13
22
|
}
|
|
@@ -42,6 +51,7 @@ module.exports = `
|
|
|
42
51
|
type SiteSettingsJson implements Node {
|
|
43
52
|
siteMetadata: SiteMetadata
|
|
44
53
|
favicon: Favicon
|
|
54
|
+
siteFont: SiteFont
|
|
45
55
|
widgets: Widgets
|
|
46
56
|
idpLogo: IdpLogo
|
|
47
57
|
identityProviderButtons: [IdentityProviderButton]
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Document, Page, Text, View, Image, StyleSheet, Font, pdf } from "@react-pdf/renderer";
|
|
3
|
+
|
|
4
|
+
import fontRegular from "../../static/fonts/nunito-sans/nunito-sans-v18-latin-400.ttf";
|
|
5
|
+
import fontBold from "../../static/fonts/nunito-sans/nunito-sans-v18-latin-700.ttf";
|
|
6
|
+
|
|
7
|
+
const registerDefaultFont = () => {
|
|
8
|
+
try {
|
|
9
|
+
Font.register({
|
|
10
|
+
family: "Nunito Sans",
|
|
11
|
+
fonts: [
|
|
12
|
+
{
|
|
13
|
+
src: fontRegular,
|
|
14
|
+
fontWeight: "normal"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
src: fontBold,
|
|
18
|
+
fontWeight: "bold"
|
|
19
|
+
},
|
|
20
|
+
]
|
|
21
|
+
});
|
|
22
|
+
return true;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error("Failed to register default font:", error);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// helper to convert relative font paths to absolute URLs
|
|
30
|
+
const getFontUrl = (fontPath) => {
|
|
31
|
+
if (!fontPath) return null;
|
|
32
|
+
|
|
33
|
+
if (fontPath.startsWith("http://") || fontPath.startsWith("https://")) {
|
|
34
|
+
return fontPath;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// For relative paths, prepend the origin
|
|
38
|
+
// Site settings fonts are served from /fonts/ (not /static/fonts/)
|
|
39
|
+
if (typeof window !== "undefined") {
|
|
40
|
+
// Remove /static prefix if present since fonts are served at root /fonts/
|
|
41
|
+
const cleanPath = fontPath.replace("/static/fonts/", "/fonts/");
|
|
42
|
+
return `${window.location.origin}${cleanPath}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return fontPath;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// register custom font from site settings if available
|
|
49
|
+
const registerCustomFont = (siteFont) => {
|
|
50
|
+
if (!siteFont || !siteFont.fontFamily || !siteFont.regularFont || !siteFont.boldFont) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const fonts = [];
|
|
55
|
+
|
|
56
|
+
if (siteFont.regularFont.fontFile && siteFont.regularFont.fontFormat === "ttf") {
|
|
57
|
+
const fontUrl = getFontUrl(siteFont.regularFont.fontFile);
|
|
58
|
+
if (fontUrl) {
|
|
59
|
+
fonts.push({
|
|
60
|
+
src: fontUrl,
|
|
61
|
+
fontWeight: "normal"
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (siteFont.boldFont.fontFile && siteFont.boldFont.fontFormat === "ttf") {
|
|
67
|
+
const fontUrl = getFontUrl(siteFont.boldFont.fontFile);
|
|
68
|
+
if (fontUrl) {
|
|
69
|
+
fonts.push({
|
|
70
|
+
src: fontUrl,
|
|
71
|
+
fontWeight: "bold"
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (fonts.length > 0) {
|
|
77
|
+
try {
|
|
78
|
+
Font.register({
|
|
79
|
+
family: siteFont.fontFamily,
|
|
80
|
+
fonts: fonts
|
|
81
|
+
});
|
|
82
|
+
return true;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.warn("Failed to register custom font:", error);
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return false;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const calculateOptimalFontSize = (text, maxWidth = 650, initialFontSize = 48, minFontSize = 24) => {
|
|
93
|
+
// estimate average character width based on font size
|
|
94
|
+
// for most fonts, character width is roughly 0.5-0.6 times the font size
|
|
95
|
+
const avgCharWidthRatio = 0.55;
|
|
96
|
+
|
|
97
|
+
// start with initial font size
|
|
98
|
+
let fontSize = initialFontSize;
|
|
99
|
+
|
|
100
|
+
// calculate the approximate text width
|
|
101
|
+
const estimatedWidth = text.length * fontSize * avgCharWidthRatio;
|
|
102
|
+
|
|
103
|
+
// if text fits within maxWidth, use initial font size
|
|
104
|
+
if (estimatedWidth <= maxWidth) {
|
|
105
|
+
return fontSize;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// calculate optimal font size to fit within maxWidth
|
|
109
|
+
fontSize = Math.floor(maxWidth / (text.length * avgCharWidthRatio));
|
|
110
|
+
|
|
111
|
+
// ensure font size is within bounds
|
|
112
|
+
fontSize = Math.max(minFontSize, Math.min(initialFontSize, fontSize));
|
|
113
|
+
|
|
114
|
+
// round to common font sizes for better appearance
|
|
115
|
+
const commonSizes = [48, 44, 40, 36, 32, 28, 24];
|
|
116
|
+
const finalSize = commonSizes.find(size => size <= fontSize) || minFontSize;
|
|
117
|
+
|
|
118
|
+
return finalSize;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const CertificatePDF = ({
|
|
122
|
+
attendee,
|
|
123
|
+
summit,
|
|
124
|
+
settings,
|
|
125
|
+
isCheckedIn = true
|
|
126
|
+
}) => {
|
|
127
|
+
|
|
128
|
+
const role = attendee.role || "Attendee";
|
|
129
|
+
const position = attendee.jobTitle || "";
|
|
130
|
+
const company = attendee.company || "";
|
|
131
|
+
|
|
132
|
+
const fullName = `${attendee.firstName} ${attendee.lastName}`;
|
|
133
|
+
|
|
134
|
+
const nameFontSize = calculateOptimalFontSize(fullName);
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
let fontFamily = "Nunito Sans";
|
|
138
|
+
|
|
139
|
+
if (settings?.siteFont && settings.siteFont.fontFamily) {
|
|
140
|
+
const customFontRegistered = registerCustomFont(settings.siteFont);
|
|
141
|
+
if (customFontRegistered) {
|
|
142
|
+
fontFamily = settings.siteFont.fontFamily;
|
|
143
|
+
} else {
|
|
144
|
+
registerDefaultFont();
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
registerDefaultFont();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const styles = StyleSheet.create({
|
|
151
|
+
page: {
|
|
152
|
+
width: settings.width || "11in",
|
|
153
|
+
height: settings.height || "8.5in",
|
|
154
|
+
backgroundColor: settings.mainColor || settings.colorAccent || "#ff5e32",
|
|
155
|
+
fontFamily: fontFamily,
|
|
156
|
+
display: "flex",
|
|
157
|
+
alignItems: "center",
|
|
158
|
+
justifyContent: "center",
|
|
159
|
+
padding: 40,
|
|
160
|
+
},
|
|
161
|
+
whiteCard: {
|
|
162
|
+
width: "100%",
|
|
163
|
+
maxWidth: 680,
|
|
164
|
+
height: "100%",
|
|
165
|
+
maxHeight: 502,
|
|
166
|
+
backgroundColor: "#ffffff",
|
|
167
|
+
borderRadius: 4,
|
|
168
|
+
display: "flex",
|
|
169
|
+
alignItems: "center",
|
|
170
|
+
justifyContent: "center",
|
|
171
|
+
},
|
|
172
|
+
content: {
|
|
173
|
+
position: "relative",
|
|
174
|
+
width: "100%",
|
|
175
|
+
height: "100%",
|
|
176
|
+
padding: 60,
|
|
177
|
+
display: "flex",
|
|
178
|
+
flexDirection: "column",
|
|
179
|
+
alignItems: "center",
|
|
180
|
+
justifyContent: "center",
|
|
181
|
+
},
|
|
182
|
+
logo: {
|
|
183
|
+
maxWidth: settings.logoWidth || 250,
|
|
184
|
+
...(settings.logoHeight && { maxHeight: settings.logoHeight }),
|
|
185
|
+
marginBottom: 25,
|
|
186
|
+
objectFit: "contain",
|
|
187
|
+
},
|
|
188
|
+
title: {
|
|
189
|
+
fontSize: 20,
|
|
190
|
+
fontWeight: "normal",
|
|
191
|
+
color: "#000000",
|
|
192
|
+
textAlign: "center",
|
|
193
|
+
letterSpacing: 0.15,
|
|
194
|
+
lineHeight: "160%",
|
|
195
|
+
},
|
|
196
|
+
summitName: {
|
|
197
|
+
fontSize: 24,
|
|
198
|
+
fontWeight: "bold",
|
|
199
|
+
color: settings.mainColor || settings.colorAccent || "#ff5e32",
|
|
200
|
+
textAlign: "center",
|
|
201
|
+
textTransform: "uppercase",
|
|
202
|
+
letterSpacing: 0,
|
|
203
|
+
lineHeight: "133%",
|
|
204
|
+
},
|
|
205
|
+
name: {
|
|
206
|
+
fontSize: nameFontSize,
|
|
207
|
+
fontWeight: "normal",
|
|
208
|
+
color: "#000000",
|
|
209
|
+
textAlign: "center",
|
|
210
|
+
letterSpacing: 0,
|
|
211
|
+
lineHeight: "117%",
|
|
212
|
+
marginTop: 36,
|
|
213
|
+
},
|
|
214
|
+
nameUnderline: {
|
|
215
|
+
width: 500,
|
|
216
|
+
height: 1.2,
|
|
217
|
+
backgroundColor: "#000000",
|
|
218
|
+
marginTop: 8,
|
|
219
|
+
marginBottom: 20,
|
|
220
|
+
},
|
|
221
|
+
details: {
|
|
222
|
+
fontSize: 14,
|
|
223
|
+
fontWeight: "normal",
|
|
224
|
+
color: "#000000",
|
|
225
|
+
textAlign: "center",
|
|
226
|
+
letterSpacing: 0.4,
|
|
227
|
+
lineHeight: "166%",
|
|
228
|
+
},
|
|
229
|
+
role: {
|
|
230
|
+
fontSize: 14,
|
|
231
|
+
fontWeight: "normal",
|
|
232
|
+
color: "#000000",
|
|
233
|
+
textAlign: "center",
|
|
234
|
+
letterSpacing: 0.4,
|
|
235
|
+
lineHeight: "166%",
|
|
236
|
+
marginTop: 6,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<Document>
|
|
242
|
+
<Page size="LETTER" orientation="landscape" style={styles.page}>
|
|
243
|
+
{/* White Card Container */}
|
|
244
|
+
<View style={styles.whiteCard}>
|
|
245
|
+
<View style={styles.content}>
|
|
246
|
+
{/* Logo */}
|
|
247
|
+
{(settings.logo || summit.logo) && (
|
|
248
|
+
<Image
|
|
249
|
+
src={settings.logo || summit.logo}
|
|
250
|
+
style={styles.logo}
|
|
251
|
+
/>
|
|
252
|
+
)}
|
|
253
|
+
|
|
254
|
+
{/* Title */}
|
|
255
|
+
<Text style={styles.title}>
|
|
256
|
+
{settings.titleText || "CERTIFICATE OF ATTENDANCE"}
|
|
257
|
+
</Text>
|
|
258
|
+
|
|
259
|
+
{/* Event Name */}
|
|
260
|
+
<Text style={styles.summitName}>
|
|
261
|
+
{settings.summitName || summit.name || "EVENT NAME"}
|
|
262
|
+
</Text>
|
|
263
|
+
|
|
264
|
+
{/* Attendee Name */}
|
|
265
|
+
<Text style={styles.name}>
|
|
266
|
+
{fullName}
|
|
267
|
+
</Text>
|
|
268
|
+
|
|
269
|
+
{/* Underline */}
|
|
270
|
+
<View style={styles.nameUnderline} />
|
|
271
|
+
|
|
272
|
+
{/* Position and Company */}
|
|
273
|
+
{(position || company) && (
|
|
274
|
+
<Text style={styles.details}>
|
|
275
|
+
{position}{position && company ? ", " : ""}{company}
|
|
276
|
+
</Text>
|
|
277
|
+
)}
|
|
278
|
+
|
|
279
|
+
{/* Role */}
|
|
280
|
+
{settings.showRole && (
|
|
281
|
+
<Text style={styles.role}>
|
|
282
|
+
{role}
|
|
283
|
+
</Text>
|
|
284
|
+
)}
|
|
285
|
+
</View>
|
|
286
|
+
</View>
|
|
287
|
+
</Page>
|
|
288
|
+
</Document>
|
|
289
|
+
);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// helper function to generate and download the certificate
|
|
293
|
+
export const generateCertificatePDF = async (attendee, summit, settings) => {
|
|
294
|
+
try {
|
|
295
|
+
const doc = <CertificatePDF attendee={attendee} summit={summit} settings={settings} />;
|
|
296
|
+
const blob = await pdf(doc).toBlob();
|
|
297
|
+
|
|
298
|
+
// create download link
|
|
299
|
+
const url = URL.createObjectURL(blob);
|
|
300
|
+
const link = document.createElement("a");
|
|
301
|
+
link.href = url;
|
|
302
|
+
link.download = `certificate-${attendee.firstName}-${attendee.lastName}.pdf`.toLowerCase().replace(/\s+/g, "-");
|
|
303
|
+
document.body.appendChild(link);
|
|
304
|
+
link.click();
|
|
305
|
+
document.body.removeChild(link);
|
|
306
|
+
URL.revokeObjectURL(url);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
console.error("Error generating certificate PDF:", error);
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
export default CertificatePDF;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { connect } from 'react-redux';
|
|
3
|
+
import { generateCertificatePDF } from './CertificatePDF';
|
|
4
|
+
import { useCertificateSettings } from '../utils/certificateSettings';
|
|
5
|
+
import useMarketingSettings from '../utils/useMarketingSettings';
|
|
6
|
+
import { MARKETING_SETTINGS_KEYS, DISPLAY_OPTIONS } from '../utils/useMarketingSettings';
|
|
7
|
+
import useSiteSettings from '../utils/useSiteSettings';
|
|
8
|
+
import styles from '../styles/full-profile.module.scss';
|
|
9
|
+
|
|
10
|
+
const USER_ROLES = {
|
|
11
|
+
SPEAKER: 'Speaker',
|
|
12
|
+
ATTENDEE: 'Attendee'
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const CertificateSection = ({
|
|
16
|
+
user,
|
|
17
|
+
summit,
|
|
18
|
+
freshTickets = []
|
|
19
|
+
}) => {
|
|
20
|
+
const [error, setError] = useState(null);
|
|
21
|
+
const { getSettingByKey } = useMarketingSettings();
|
|
22
|
+
const siteSettings = useSiteSettings();
|
|
23
|
+
|
|
24
|
+
// Get certificate settings
|
|
25
|
+
const certificateSettings = useCertificateSettings(summit, siteSettings?.siteFont);
|
|
26
|
+
|
|
27
|
+
// Check if certificates are enabled
|
|
28
|
+
const certificatesEnabled = getSettingByKey(MARKETING_SETTINGS_KEYS.certificateEnabled) !== DISPLAY_OPTIONS.hide;
|
|
29
|
+
|
|
30
|
+
if (!certificatesEnabled) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Filter tickets that are checked in
|
|
35
|
+
const checkedInTickets = freshTickets.filter(ticket => {
|
|
36
|
+
const isCheckedIn = ticket.owner?.summit_hall_checked_in === true;
|
|
37
|
+
const isValidTicket =
|
|
38
|
+
ticket.status !== 'Cancelled' &&
|
|
39
|
+
ticket.status !== 'RefundRequested' &&
|
|
40
|
+
ticket.owner !== null;
|
|
41
|
+
|
|
42
|
+
return isCheckedIn && isValidTicket;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (checkedInTickets.length === 0) {
|
|
46
|
+
return (
|
|
47
|
+
<div>
|
|
48
|
+
<h3 className={styles.header}>Certificate of Attendance</h3>
|
|
49
|
+
<div style={{ padding: '15px 0', color: '#666' }}>
|
|
50
|
+
Check-in required to download certificate.
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Get user role from tickets/badges - prioritize Speaker role across all tickets
|
|
57
|
+
const getUserRole = (allTickets) => {
|
|
58
|
+
// Check if any ticket has speaker role
|
|
59
|
+
const hasSpeakerTicket = allTickets.some(ticket => {
|
|
60
|
+
// Check badge type name for speaker
|
|
61
|
+
const badgeType = summit?.badge_types?.find(bt => bt.id === ticket.badge?.type_id);
|
|
62
|
+
if (badgeType?.name?.toLowerCase().includes('speaker')) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check badge features for speaker
|
|
67
|
+
const featureIds = ticket.badge?.features || [];
|
|
68
|
+
const badgeFeaturesTypes = summit?.badge_features_types || [];
|
|
69
|
+
|
|
70
|
+
return featureIds.some(featureId => {
|
|
71
|
+
const feature = badgeFeaturesTypes.find(f => f.id === featureId);
|
|
72
|
+
return feature?.name?.toLowerCase().includes('speaker');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return hasSpeakerTicket ? USER_ROLES.SPEAKER : USER_ROLES.ATTENDEE;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Determine the user's role across all tickets (prioritize Speaker)
|
|
80
|
+
const userRole = getUserRole(checkedInTickets);
|
|
81
|
+
|
|
82
|
+
const handleDownloadCertificate = async (ticket) => {
|
|
83
|
+
setError(null);
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const attendeeData = {
|
|
87
|
+
firstName: user.first_name || user.given_name || ticket.owner?.first_name,
|
|
88
|
+
lastName: user.last_name || user.family_name || ticket.owner?.last_name,
|
|
89
|
+
company: user.company || ticket.owner?.company || '',
|
|
90
|
+
jobTitle: user.job_title || user.jobTitle || '',
|
|
91
|
+
email: user.email || ticket.owner?.email,
|
|
92
|
+
role: userRole // Use the prioritized role across all tickets
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const summitData = {
|
|
96
|
+
name: summit.name,
|
|
97
|
+
logo: summit.logo,
|
|
98
|
+
start_date: summit.start_date,
|
|
99
|
+
end_date: summit.end_date
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
await generateCertificatePDF(attendeeData, summitData, certificateSettings);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.error('Error downloading certificate:', err);
|
|
105
|
+
setError('Failed to download certificate. Please try again.');
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<div style={{ marginTop: '20px' }}>
|
|
111
|
+
<button
|
|
112
|
+
className="button is-large"
|
|
113
|
+
onClick={() => handleDownloadCertificate(checkedInTickets[0])}
|
|
114
|
+
style={{
|
|
115
|
+
width: '100%',
|
|
116
|
+
height: '5.5rem',
|
|
117
|
+
color: 'var(--color_input_text_color)',
|
|
118
|
+
backgroundColor: 'var(--color_input_background_color)',
|
|
119
|
+
borderColor: 'var(--color_input_border_color)'
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
Download Certificate
|
|
123
|
+
</button>
|
|
124
|
+
|
|
125
|
+
{error && (
|
|
126
|
+
<div style={{ color: '#d32f2f', fontSize: '14px', marginTop: '10px' }}>
|
|
127
|
+
{error}
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const mapStateToProps = ({ summitState, userState }) => ({
|
|
135
|
+
summit: summitState.summit,
|
|
136
|
+
user: userState.userProfile || userState.idpProfile
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
export default connect(mapStateToProps)(CertificateSection);
|
|
@@ -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,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import useMarketingSettings, { MARKETING_SETTINGS_KEYS, DISPLAY_OPTIONS } from './useMarketingSettings';
|
|
2
|
+
|
|
3
|
+
export const useCertificateSettings = (siteFont = null) => {
|
|
4
|
+
const { getSettingByKey } = useMarketingSettings();
|
|
5
|
+
|
|
6
|
+
const certificateKeys = {
|
|
7
|
+
// general summit keys
|
|
8
|
+
colorAccent: MARKETING_SETTINGS_KEYS.colorAccent,
|
|
9
|
+
colorPrimary: MARKETING_SETTINGS_KEYS.colorPrimary,
|
|
10
|
+
colorPrimaryContrast: MARKETING_SETTINGS_KEYS.colorPrimaryContrast,
|
|
11
|
+
colorSecondary: MARKETING_SETTINGS_KEYS.colorSecondary,
|
|
12
|
+
colorTextDark: MARKETING_SETTINGS_KEYS.colorTextDark,
|
|
13
|
+
colorTextLight: MARKETING_SETTINGS_KEYS.colorTextLight,
|
|
14
|
+
// certificate specific
|
|
15
|
+
enabled: MARKETING_SETTINGS_KEYS.certificateEnabled,
|
|
16
|
+
height: MARKETING_SETTINGS_KEYS.certificateHeight,
|
|
17
|
+
width: MARKETING_SETTINGS_KEYS.certificateWidth,
|
|
18
|
+
mainColor: MARKETING_SETTINGS_KEYS.certificateMainColor,
|
|
19
|
+
logo: MARKETING_SETTINGS_KEYS.certificateLogo,
|
|
20
|
+
logoWidth: MARKETING_SETTINGS_KEYS.certificateLogoWidth,
|
|
21
|
+
logoHeight: MARKETING_SETTINGS_KEYS.certificateLogoHeight,
|
|
22
|
+
titleText: MARKETING_SETTINGS_KEYS.certificateTitleText,
|
|
23
|
+
summitName: MARKETING_SETTINGS_KEYS.certificateSummitName,
|
|
24
|
+
showRole: MARKETING_SETTINGS_KEYS.certificateShowRole,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const certificateSettings = {};
|
|
28
|
+
|
|
29
|
+
Object.entries(certificateKeys).forEach(([propName, key]) => {
|
|
30
|
+
const value = getSettingByKey(key);
|
|
31
|
+
if (value !== undefined) {
|
|
32
|
+
certificateSettings[propName] = value;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
certificateSettings.enabled = certificateSettings.enabled !== DISPLAY_OPTIONS.hide;
|
|
37
|
+
certificateSettings.showRole = certificateSettings.showRole !== DISPLAY_OPTIONS.hide;
|
|
38
|
+
|
|
39
|
+
// Pass through the site font information if available
|
|
40
|
+
if (siteFont) {
|
|
41
|
+
certificateSettings.siteFont = siteFont;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return certificateSettings;
|
|
45
|
+
};
|
package/src/utils/schedule.js
CHANGED
|
@@ -5,6 +5,8 @@ import { isString } from "lodash";
|
|
|
5
5
|
import { getEnvVariable, SCHEDULE_EXCLUDING_TAGS } from "./envVariables";
|
|
6
6
|
import {getUserAccessLevelIds, isAuthorizedUser} from './authorizedGroups';
|
|
7
7
|
import {uniq} from "lodash";
|
|
8
|
+
import * as Sentry from "@sentry/react";
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
const groupByDay = (events) => {
|
|
10
12
|
let groupedEvents = [];
|
|
@@ -61,13 +63,15 @@ export const filterEventsByTags = (events) => {
|
|
|
61
63
|
|
|
62
64
|
export const filterEventsByTicket = (events, user) => {
|
|
63
65
|
const assignedTickets = user?.summit_tickets || [];
|
|
64
|
-
const ticketTypeIds = uniq(assignedTickets
|
|
66
|
+
const ticketTypeIds = uniq(assignedTickets?.map(t => t?.ticket_type?.id));
|
|
65
67
|
|
|
66
68
|
return events.filter(ev => {
|
|
67
|
-
const hasEventRestriction = ev
|
|
68
|
-
|
|
69
|
+
const hasEventRestriction = ev?.allowed_ticket_types?.length > 0;
|
|
70
|
+
if(!ev?.allowed_ticket_types){
|
|
71
|
+
Sentry?.captureMessage(`event ${ev.id} has not set allowed_ticket_types collection`);
|
|
72
|
+
}
|
|
73
|
+
const typeAllowed = ev?.type?.allowed_ticket_types.length === 0 || ev?.type?.allowed_ticket_types.some(att => ticketTypeIds.includes(att));
|
|
69
74
|
const eventAllowed = !hasEventRestriction || ev.allowed_ticket_types.some(att => ticketTypeIds.includes(att));
|
|
70
|
-
|
|
71
75
|
return hasEventRestriction ? eventAllowed : typeAllowed;
|
|
72
76
|
});
|
|
73
77
|
};
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { graphql, useStaticQuery } from "gatsby";
|
|
3
3
|
|
|
4
|
+
export const DISPLAY_OPTIONS = {
|
|
5
|
+
show: "SHOW",
|
|
6
|
+
hide: "HIDE"
|
|
7
|
+
};
|
|
8
|
+
|
|
4
9
|
export const MARKETING_SETTINGS_KEYS = {
|
|
5
10
|
disqusThreadsBy: "disqus_threads_by",
|
|
6
11
|
disqusExcludeEvents: "disqus_exclude_events",
|
|
@@ -22,9 +27,51 @@ export const MARKETING_SETTINGS_KEYS = {
|
|
|
22
27
|
regLiteOrderComplete2ndParagraph: "REG_LITE_ORDER_COMPLETE_STEP_2ND_PARAGRAPH",
|
|
23
28
|
regLiteOrderCompleteButton: "REG_LITE_ORDER_COMPLETE_BTN_LABEL",
|
|
24
29
|
regLiteNoAllowedTicketsMessage: "REG_LITE_NO_ALLOWED_TICKETS_MESSAGE",
|
|
30
|
+
// Color settings (from scssUtils/defaults)
|
|
31
|
+
colorAccent: "color_accent",
|
|
32
|
+
colorAlerts: "color_alerts",
|
|
33
|
+
colorBackgroundLight: "color_background_light",
|
|
34
|
+
colorBackgroundDark: "color_background_dark",
|
|
35
|
+
colorButtonBackgroundColor: "color_button_background_color",
|
|
36
|
+
colorButtonColor: "color_button_color",
|
|
37
|
+
colorGrayLighter: "color_gray_lighter",
|
|
38
|
+
colorGrayLight: "color_gray_light",
|
|
39
|
+
colorGrayDark: "color_gray_dark",
|
|
40
|
+
colorGrayDarker: "color_gray_darker",
|
|
41
|
+
colorHorizontalRuleLight: "color_horizontal_rule_light",
|
|
42
|
+
colorHorizontalRuleDark: "color_horizontal_rule_dark",
|
|
43
|
+
colorIconLight: "color_icon_light",
|
|
44
|
+
colorInputBackgroundColorLight: "color_input_background_color_light",
|
|
45
|
+
colorInputBackgroundColorDark: "color_input_background_color_dark",
|
|
46
|
+
colorInputBorderColorLight: "color_input_border_color_light",
|
|
47
|
+
colorInputBorderColorDark: "color_input_border_color_dark",
|
|
48
|
+
colorInputTextColorLight: "color_input_text_color_light",
|
|
49
|
+
colorInputTextColorDark: "color_input_text_color_dark",
|
|
50
|
+
colorInputTextColorDisabledLight: "color_input_text_color_disabled_light",
|
|
51
|
+
colorInputTextColorDisabledDark: "color_input_text_color_disabled_dark",
|
|
52
|
+
colorPrimary: "color_primary",
|
|
53
|
+
colorPrimaryContrast: "color_primary_contrast",
|
|
54
|
+
colorSecondary: "color_secondary",
|
|
55
|
+
colorSecondaryContrast: "color_secondary_contrast",
|
|
56
|
+
colorTextLight: "color_text_light",
|
|
57
|
+
colorTextMed: "color_text_med",
|
|
58
|
+
colorTextDark: "color_text_dark",
|
|
59
|
+
colorTextInputHintsLight: "color_text_input_hints_light",
|
|
60
|
+
colorTextInputHintsDark: "color_text_input_hints_dark",
|
|
61
|
+
colorTextInputHints: "color_text_input_hints",
|
|
62
|
+
// Certificate of Attendance settings
|
|
63
|
+
certificateEnabled: "CERTIFICATE_ENABLED",
|
|
64
|
+
certificateHeight: "CERTIFICATE_HEIGHT",
|
|
65
|
+
certificateWidth: "CERTIFICATE_WIDTH",
|
|
66
|
+
certificateMainColor: "CERTIFICATE_MAIN_COLOR",
|
|
67
|
+
certificateLogo: "CERTIFICATE_LOGO",
|
|
68
|
+
certificateLogoWidth: "CERTIFICATE_LOGO_WIDTH",
|
|
69
|
+
certificateLogoHeight: "CERTIFICATE_LOGO_HEIGHT",
|
|
70
|
+
certificateTitleText: "CERTIFICATE_TITLE_TEXT",
|
|
71
|
+
certificateSummitName: "CERTIFICATE_SUMMIT_NAME",
|
|
72
|
+
certificateShowRole: "CERTIFICATE_SHOW_ROLE",
|
|
25
73
|
}
|
|
26
74
|
|
|
27
|
-
|
|
28
75
|
const marketingSettingsQuery = graphql`
|
|
29
76
|
query {
|
|
30
77
|
allMarketingSettingsJson {
|
|
@@ -6,101 +6,96 @@ import speakersBuildJson from "data/speakers.json";
|
|
|
6
6
|
import speakersIDXBuildJson from "data/speakers.idx.json";
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
bucket_getSummit,
|
|
10
|
+
bucket_getEvents,
|
|
11
|
+
bucket_getEventsIDX,
|
|
12
|
+
bucket_getSpeakers,
|
|
13
|
+
bucket_getSpeakersIDX
|
|
14
14
|
} from "../actions/update-data-actions";
|
|
15
15
|
|
|
16
16
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
SUMMIT_FILE_PATH,
|
|
18
|
+
EVENTS_FILE_PATH,
|
|
19
|
+
EVENTS_IDX_FILE_PATH,
|
|
20
|
+
SPEAKERS_FILE_PATH,
|
|
21
|
+
SPEAKERS_IDX_FILE_PATH
|
|
22
22
|
} from "../utils/filePath";
|
|
23
23
|
|
|
24
|
+
const isNonEmptyObject = (v) =>
|
|
25
|
+
v && typeof v === "object" && !Array.isArray(v) && Object.keys(v).length > 0;
|
|
26
|
+
|
|
27
|
+
const isNonEmptyArray = (v) => Array.isArray(v) && v.length > 0;
|
|
28
|
+
|
|
29
|
+
const pick = (result, expect) => {
|
|
30
|
+
if (!result || typeof result !== "object") {
|
|
31
|
+
return {accepted: false, data: null, lastModified: 0};
|
|
32
|
+
}
|
|
33
|
+
const {file, lastModified} = result;
|
|
34
|
+
const ok =
|
|
35
|
+
expect === "object" ? isNonEmptyObject(file) :
|
|
36
|
+
expect === "array" ? isNonEmptyArray(file) : false;
|
|
37
|
+
|
|
38
|
+
return ok
|
|
39
|
+
? {accepted: true, data: file, lastModified: lastModified}
|
|
40
|
+
: {accepted: false, data: null, lastModified: 0};
|
|
41
|
+
};
|
|
42
|
+
|
|
24
43
|
/* eslint-disable-next-line no-restricted-globals */
|
|
25
44
|
self.onmessage = async ({data: {summitId, staticJsonFilesBuildTime}}) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
else
|
|
83
|
-
eventsIDXData = eventsIDXBuildJson;
|
|
84
|
-
// speakers
|
|
85
|
-
if (speakersData && speakersData?.file){
|
|
86
|
-
if(speakersData.lastModified > lastModified)
|
|
87
|
-
lastModified = speakersData.lastModified;
|
|
88
|
-
speakersData = speakersData.file;
|
|
89
|
-
}
|
|
90
|
-
else
|
|
91
|
-
speakersData = speakersBuildJson;
|
|
92
|
-
// speakers idx
|
|
93
|
-
if (speakersIXData && speakersIXData?.file){
|
|
94
|
-
if(speakersIXData.lastModified > lastModified)
|
|
95
|
-
lastModified = speakersIXData.lastModified;
|
|
96
|
-
speakersIXData = speakersIXData.file;
|
|
97
|
-
}
|
|
98
|
-
else
|
|
99
|
-
speakersIXData = speakersIDXBuildJson;
|
|
100
|
-
|
|
101
|
-
/* eslint-disable-next-line no-restricted-globals */
|
|
102
|
-
self.postMessage({
|
|
103
|
-
eventsData, summitData, speakersData, eventsIDXData, speakersIXData, lastModified
|
|
104
|
-
});
|
|
105
|
-
});
|
|
45
|
+
staticJsonFilesBuildTime = JSON.parse(staticJsonFilesBuildTime);
|
|
46
|
+
|
|
47
|
+
console.log(`feeds worker running for ${summitId} ....`)
|
|
48
|
+
const calls = [];
|
|
49
|
+
|
|
50
|
+
// events
|
|
51
|
+
let buildTime = staticJsonFilesBuildTime.find(e => e.file === EVENTS_FILE_PATH).build_time;
|
|
52
|
+
|
|
53
|
+
calls.push(bucket_getEvents(summitId, buildTime));
|
|
54
|
+
|
|
55
|
+
buildTime = staticJsonFilesBuildTime.find(e => e.file === EVENTS_IDX_FILE_PATH).build_time;
|
|
56
|
+
calls.push(bucket_getEventsIDX(summitId, buildTime));
|
|
57
|
+
|
|
58
|
+
// summit
|
|
59
|
+
buildTime = staticJsonFilesBuildTime.find(e => e.file === SUMMIT_FILE_PATH).build_time;
|
|
60
|
+
calls.push(bucket_getSummit(summitId, buildTime));
|
|
61
|
+
|
|
62
|
+
//speakers
|
|
63
|
+
buildTime = staticJsonFilesBuildTime.find(e => e.file === SPEAKERS_FILE_PATH).build_time;
|
|
64
|
+
calls.push(bucket_getSpeakers(summitId, buildTime));
|
|
65
|
+
|
|
66
|
+
buildTime = staticJsonFilesBuildTime.find(e => e.file === SPEAKERS_IDX_FILE_PATH).build_time;
|
|
67
|
+
calls.push(bucket_getSpeakersIDX(summitId, buildTime));
|
|
68
|
+
|
|
69
|
+
Promise.all(calls)
|
|
70
|
+
.then((values) => {
|
|
71
|
+
let lastModified = settings.lastBuild;
|
|
72
|
+
let eventsData = values[0];
|
|
73
|
+
let eventsIDXData = values[1];
|
|
74
|
+
let summitData = values[2];
|
|
75
|
+
let speakersData = values[3];
|
|
76
|
+
let speakersIXData = values[4];
|
|
77
|
+
|
|
78
|
+
// if null , then set the SSR content
|
|
79
|
+
// summit
|
|
80
|
+
const summitDataPicked = pick(summitData, "object");
|
|
81
|
+
summitData = summitDataPicked.accepted && summitDataPicked.lastModified > lastModified ? summitDataPicked.data : summitBuildJson;
|
|
82
|
+
// events
|
|
83
|
+
const eventsDataPicked = pick(eventsData, "array");
|
|
84
|
+
eventsData = eventsDataPicked.accepted && eventsDataPicked.lastModified > lastModified ? eventsDataPicked.data : eventsBuildJson;
|
|
85
|
+
// events idx
|
|
86
|
+
const eventsIDXDataPicked = pick(eventsIDXData, "object");
|
|
87
|
+
eventsIDXData = eventsIDXDataPicked.accepted && eventsIDXDataPicked.lastModified > lastModified ? eventsIDXDataPicked.data : eventsIDXBuildJson;
|
|
88
|
+
// speakers
|
|
89
|
+
const speakersDataPicked = pick(speakersData, "array");
|
|
90
|
+
speakersData = speakersDataPicked.accepted && speakersDataPicked.lastModified > lastModified ? speakersDataPicked.data : speakersBuildJson;
|
|
91
|
+
// speakers idx
|
|
92
|
+
const speakersIXDataPicked = pick(speakersIXData, "object");
|
|
93
|
+
speakersIXData = speakersIXDataPicked.accepted && speakersIXDataPicked.lastModified > lastModified ? speakersIXDataPicked.data : speakersIDXBuildJson;
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
/* eslint-disable-next-line no-restricted-globals */
|
|
97
|
+
self.postMessage({
|
|
98
|
+
eventsData, summitData, speakersData, eventsIDXData, speakersIXData, lastModified
|
|
99
|
+
});
|
|
100
|
+
});
|
|
106
101
|
};
|
package/static/fonts/fonts.css
CHANGED
|
@@ -1,65 +1,165 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Nunito Sans Font Family - Version 18
|
|
3
|
+
* Weights: 200, 300, 400, 500, 600, 700, 800, 900
|
|
4
|
+
* Styles: normal, italic
|
|
4
5
|
*/
|
|
5
6
|
|
|
7
|
+
/* nunito-sans-200 - latin */
|
|
8
|
+
@font-face {
|
|
9
|
+
font-family: "Nunito Sans";
|
|
10
|
+
font-style: normal;
|
|
11
|
+
font-weight: 200;
|
|
12
|
+
font-display: swap;
|
|
13
|
+
src: local(""),
|
|
14
|
+
url("nunito-sans/nunito-sans-v18-latin-200.woff2") format("woff2");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* nunito-sans-200italic - latin */
|
|
18
|
+
@font-face {
|
|
19
|
+
font-family: "Nunito Sans";
|
|
20
|
+
font-style: italic;
|
|
21
|
+
font-weight: 200;
|
|
22
|
+
font-display: swap;
|
|
23
|
+
src: local(""),
|
|
24
|
+
url("nunito-sans/nunito-sans-v18-latin-200italic.woff2") format("woff2");
|
|
25
|
+
}
|
|
26
|
+
|
|
6
27
|
/* nunito-sans-300 - latin */
|
|
7
28
|
@font-face {
|
|
8
29
|
font-family: "Nunito Sans";
|
|
9
30
|
font-style: normal;
|
|
10
31
|
font-weight: 300;
|
|
32
|
+
font-display: swap;
|
|
11
33
|
src: local(""),
|
|
12
|
-
url("nunito-sans/nunito-sans-
|
|
13
|
-
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
|
14
|
-
url("nunito-sans/nunito-sans-v12-latin-300.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
|
34
|
+
url("nunito-sans/nunito-sans-v18-latin-300.woff2") format("woff2");
|
|
15
35
|
}
|
|
36
|
+
|
|
16
37
|
/* nunito-sans-300italic - latin */
|
|
17
38
|
@font-face {
|
|
18
39
|
font-family: "Nunito Sans";
|
|
19
40
|
font-style: italic;
|
|
20
41
|
font-weight: 300;
|
|
42
|
+
font-display: swap;
|
|
43
|
+
src: local(""),
|
|
44
|
+
url("nunito-sans/nunito-sans-v18-latin-300italic.woff2") format("woff2");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* nunito-sans-400 - latin */
|
|
48
|
+
@font-face {
|
|
49
|
+
font-family: "Nunito Sans";
|
|
50
|
+
font-style: normal;
|
|
51
|
+
font-weight: 400;
|
|
52
|
+
font-display: swap;
|
|
53
|
+
src: local(""),
|
|
54
|
+
url("nunito-sans/nunito-sans-v18-latin-400.woff2") format("woff2");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* nunito-sans-400italic - latin */
|
|
58
|
+
@font-face {
|
|
59
|
+
font-family: "Nunito Sans";
|
|
60
|
+
font-style: italic;
|
|
61
|
+
font-weight: 400;
|
|
62
|
+
font-display: swap;
|
|
63
|
+
src: local(""),
|
|
64
|
+
url("nunito-sans/nunito-sans-v18-latin-400italic.woff2") format("woff2");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* nunito-sans-500 - latin */
|
|
68
|
+
@font-face {
|
|
69
|
+
font-family: "Nunito Sans";
|
|
70
|
+
font-style: normal;
|
|
71
|
+
font-weight: 500;
|
|
72
|
+
font-display: swap;
|
|
73
|
+
src: local(""),
|
|
74
|
+
url("nunito-sans/nunito-sans-v18-latin-500.woff2") format("woff2");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* nunito-sans-500italic - latin */
|
|
78
|
+
@font-face {
|
|
79
|
+
font-family: "Nunito Sans";
|
|
80
|
+
font-style: italic;
|
|
81
|
+
font-weight: 500;
|
|
82
|
+
font-display: swap;
|
|
21
83
|
src: local(""),
|
|
22
|
-
url("nunito-sans/nunito-sans-
|
|
23
|
-
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
|
24
|
-
url("nunito-sans/nunito-sans-v12-latin-300italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
|
84
|
+
url("nunito-sans/nunito-sans-v18-latin-500italic.woff2") format("woff2");
|
|
25
85
|
}
|
|
86
|
+
|
|
26
87
|
/* nunito-sans-600 - latin */
|
|
27
88
|
@font-face {
|
|
28
89
|
font-family: "Nunito Sans";
|
|
29
90
|
font-style: normal;
|
|
30
91
|
font-weight: 600;
|
|
92
|
+
font-display: swap;
|
|
31
93
|
src: local(""),
|
|
32
|
-
url("nunito-sans/nunito-sans-
|
|
33
|
-
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
|
34
|
-
url("nunito-sans/nunito-sans-v12-latin-600.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
|
94
|
+
url("nunito-sans/nunito-sans-v18-latin-600.woff2") format("woff2");
|
|
35
95
|
}
|
|
96
|
+
|
|
36
97
|
/* nunito-sans-600italic - latin */
|
|
37
98
|
@font-face {
|
|
38
99
|
font-family: "Nunito Sans";
|
|
39
100
|
font-style: italic;
|
|
40
101
|
font-weight: 600;
|
|
102
|
+
font-display: swap;
|
|
41
103
|
src: local(""),
|
|
42
|
-
url("nunito-sans/nunito-sans-
|
|
43
|
-
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
|
44
|
-
url("nunito-sans/nunito-sans-v12-latin-600italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
|
104
|
+
url("nunito-sans/nunito-sans-v18-latin-600italic.woff2") format("woff2");
|
|
45
105
|
}
|
|
106
|
+
|
|
46
107
|
/* nunito-sans-700 - latin */
|
|
47
108
|
@font-face {
|
|
48
109
|
font-family: "Nunito Sans";
|
|
49
110
|
font-style: normal;
|
|
50
111
|
font-weight: 700;
|
|
112
|
+
font-display: swap;
|
|
51
113
|
src: local(""),
|
|
52
|
-
url("nunito-sans/nunito-sans-
|
|
53
|
-
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
|
54
|
-
url("nunito-sans/nunito-sans-v12-latin-700.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
|
114
|
+
url("nunito-sans/nunito-sans-v18-latin-700.woff2") format("woff2");
|
|
55
115
|
}
|
|
116
|
+
|
|
56
117
|
/* nunito-sans-700italic - latin */
|
|
57
118
|
@font-face {
|
|
58
119
|
font-family: "Nunito Sans";
|
|
59
120
|
font-style: italic;
|
|
60
121
|
font-weight: 700;
|
|
122
|
+
font-display: swap;
|
|
123
|
+
src: local(""),
|
|
124
|
+
url("nunito-sans/nunito-sans-v18-latin-700italic.woff2") format("woff2");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* nunito-sans-800 - latin */
|
|
128
|
+
@font-face {
|
|
129
|
+
font-family: "Nunito Sans";
|
|
130
|
+
font-style: normal;
|
|
131
|
+
font-weight: 800;
|
|
132
|
+
font-display: swap;
|
|
133
|
+
src: local(""),
|
|
134
|
+
url("nunito-sans/nunito-sans-v18-latin-800.woff2") format("woff2");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* nunito-sans-800italic - latin */
|
|
138
|
+
@font-face {
|
|
139
|
+
font-family: "Nunito Sans";
|
|
140
|
+
font-style: italic;
|
|
141
|
+
font-weight: 800;
|
|
142
|
+
font-display: swap;
|
|
143
|
+
src: local(""),
|
|
144
|
+
url("nunito-sans/nunito-sans-v18-latin-800italic.woff2") format("woff2");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* nunito-sans-900 - latin */
|
|
148
|
+
@font-face {
|
|
149
|
+
font-family: "Nunito Sans";
|
|
150
|
+
font-style: normal;
|
|
151
|
+
font-weight: 900;
|
|
152
|
+
font-display: swap;
|
|
61
153
|
src: local(""),
|
|
62
|
-
url("nunito-sans/nunito-sans-
|
|
63
|
-
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
|
64
|
-
url("nunito-sans/nunito-sans-v12-latin-700italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
|
154
|
+
url("nunito-sans/nunito-sans-v18-latin-900.woff2") format("woff2");
|
|
65
155
|
}
|
|
156
|
+
|
|
157
|
+
/* nunito-sans-900italic - latin */
|
|
158
|
+
@font-face {
|
|
159
|
+
font-family: "Nunito Sans";
|
|
160
|
+
font-style: italic;
|
|
161
|
+
font-weight: 900;
|
|
162
|
+
font-display: swap;
|
|
163
|
+
src: local(""),
|
|
164
|
+
url("nunito-sans/nunito-sans-v18-latin-900italic.woff2") format("woff2");
|
|
165
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|