@openeventkit/event-site 2.1.22 → 2.1.23
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
CHANGED
|
@@ -89,6 +89,7 @@ const registerCustomFont = (siteFont) => {
|
|
|
89
89
|
return false;
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
+
|
|
92
93
|
const calculateOptimalFontSize = (text, maxWidth = 650, initialFontSize = 48, minFontSize = 24) => {
|
|
93
94
|
// estimate average character width based on font size
|
|
94
95
|
// for most fonts, character width is roughly 0.5-0.6 times the font size
|
|
@@ -118,13 +119,33 @@ const calculateOptimalFontSize = (text, maxWidth = 650, initialFontSize = 48, mi
|
|
|
118
119
|
return finalSize;
|
|
119
120
|
};
|
|
120
121
|
|
|
122
|
+
// Validate image URL before PDF generation
|
|
123
|
+
const validateImageUrl = async (url) => {
|
|
124
|
+
if (!url) return null;
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const response = await fetch(url, {
|
|
128
|
+
method: 'GET',
|
|
129
|
+
mode: 'cors'
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (response.ok) {
|
|
133
|
+
return url;
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.warn("Image validation failed:", error);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
121
142
|
const CertificatePDF = ({
|
|
122
143
|
attendee,
|
|
123
144
|
summit,
|
|
124
145
|
settings,
|
|
125
|
-
isCheckedIn = true
|
|
146
|
+
isCheckedIn = true,
|
|
147
|
+
logoUrl = null
|
|
126
148
|
}) => {
|
|
127
|
-
|
|
128
149
|
const role = attendee.role || "Attendee";
|
|
129
150
|
const position = attendee.jobTitle || "";
|
|
130
151
|
const company = attendee.company || "";
|
|
@@ -181,7 +202,7 @@ const CertificatePDF = ({
|
|
|
181
202
|
},
|
|
182
203
|
logo: {
|
|
183
204
|
maxWidth: settings.logoWidth || 250,
|
|
184
|
-
|
|
205
|
+
maxHeight: settings.logoHeight || 150,
|
|
185
206
|
marginBottom: 25,
|
|
186
207
|
objectFit: "contain",
|
|
187
208
|
},
|
|
@@ -244,10 +265,11 @@ const CertificatePDF = ({
|
|
|
244
265
|
<View style={styles.whiteCard}>
|
|
245
266
|
<View style={styles.content}>
|
|
246
267
|
{/* Logo */}
|
|
247
|
-
{
|
|
268
|
+
{logoUrl && (
|
|
248
269
|
<Image
|
|
249
|
-
src={
|
|
250
|
-
style={styles.logo}
|
|
270
|
+
src={logoUrl}
|
|
271
|
+
style={styles.logo}
|
|
272
|
+
debug={false} // When true, shows a visible error placeholder if image fails to load
|
|
251
273
|
/>
|
|
252
274
|
)}
|
|
253
275
|
|
|
@@ -292,7 +314,16 @@ const CertificatePDF = ({
|
|
|
292
314
|
// helper function to generate and download the certificate
|
|
293
315
|
export const generateCertificatePDF = async (attendee, summit, settings) => {
|
|
294
316
|
try {
|
|
295
|
-
|
|
317
|
+
// Validate logo URL before generating PDF
|
|
318
|
+
const logoUrlToValidate = settings.logo || summit.logo;
|
|
319
|
+
const validatedLogoUrl = await validateImageUrl(logoUrlToValidate);
|
|
320
|
+
|
|
321
|
+
const doc = <CertificatePDF
|
|
322
|
+
attendee={attendee}
|
|
323
|
+
summit={summit}
|
|
324
|
+
settings={settings}
|
|
325
|
+
logoUrl={validatedLogoUrl}
|
|
326
|
+
/>;
|
|
296
327
|
const blob = await pdf(doc).toBlob();
|
|
297
328
|
|
|
298
329
|
// create download link
|
|
@@ -22,7 +22,7 @@ const CertificateSection = ({
|
|
|
22
22
|
const siteSettings = useSiteSettings();
|
|
23
23
|
|
|
24
24
|
// Get certificate settings
|
|
25
|
-
const certificateSettings = useCertificateSettings(
|
|
25
|
+
const certificateSettings = useCertificateSettings(siteSettings?.siteFont);
|
|
26
26
|
|
|
27
27
|
// Check if certificates are enabled
|
|
28
28
|
const certificatesEnabled = getSettingByKey(MARKETING_SETTINGS_KEYS.certificateEnabled) !== DISPLAY_OPTIONS.hide;
|
|
@@ -107,27 +107,29 @@ const CertificateSection = ({
|
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
return (
|
|
110
|
-
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
{
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
110
|
+
<>
|
|
111
|
+
<div style={{ marginTop: '20px' }}>
|
|
112
|
+
<button
|
|
113
|
+
className={`button is-large ${styles.certificateButton}`}
|
|
114
|
+
onClick={() => handleDownloadCertificate(checkedInTickets[0])}
|
|
115
|
+
style={{
|
|
116
|
+
width: '100%',
|
|
117
|
+
height: '5.5rem',
|
|
118
|
+
color: 'var(--color_input_text_color)',
|
|
119
|
+
backgroundColor: 'var(--color_input_background_color)',
|
|
120
|
+
borderColor: 'var(--color_input_border_color)'
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
Download Certificate of Attendance
|
|
124
|
+
</button>
|
|
125
|
+
|
|
126
|
+
{error && (
|
|
127
|
+
<div style={{ color: '#d32f2f', fontSize: '14px', marginTop: '10px' }}>
|
|
128
|
+
{error}
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
</>
|
|
131
133
|
);
|
|
132
134
|
};
|
|
133
135
|
|
|
@@ -15,6 +15,10 @@ class AblyRealTimeStrategy extends AbstractRealTimeStrategy {
|
|
|
15
15
|
console.log('AblyRealTimeStrategy::constructor');
|
|
16
16
|
this._client = null;
|
|
17
17
|
this._wsError = false;
|
|
18
|
+
this._closing = false;
|
|
19
|
+
this._channel = null;
|
|
20
|
+
this._onConn = null;
|
|
21
|
+
this._onMessage = null;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
/**
|
|
@@ -29,12 +33,12 @@ class AblyRealTimeStrategy extends AbstractRealTimeStrategy {
|
|
|
29
33
|
const key = getEnvVariable(ABLY_API_KEY);
|
|
30
34
|
|
|
31
35
|
if(this._wsError) {
|
|
32
|
-
console.
|
|
36
|
+
console.warn('AblyRealTimeStrategy::create error state');
|
|
33
37
|
return;
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
if(!key){
|
|
37
|
-
console.
|
|
41
|
+
console.warn('AblyRealTimeStrategy::create ABLY_KEY is not set');
|
|
38
42
|
this._wsError = true;
|
|
39
43
|
return;
|
|
40
44
|
}
|
|
@@ -50,22 +54,17 @@ class AblyRealTimeStrategy extends AbstractRealTimeStrategy {
|
|
|
50
54
|
this._client.close();
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
this._client = new Ably.Realtime({
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const channel = this._client.channels.get(`${summitId}:*:*`);
|
|
58
|
-
|
|
59
|
-
channel.subscribe((message) => {
|
|
60
|
-
const { data : payload } = message;
|
|
61
|
-
console.log('AblyRealTimeStrategy::create Change received', payload)
|
|
62
|
-
this._callback(payload);
|
|
57
|
+
this._client = new Ably.Realtime({
|
|
58
|
+
key,
|
|
59
|
+
// see https://faqs.ably.com/ably-js-page-unload-behaviour
|
|
60
|
+
closeOnUnload: false,
|
|
63
61
|
});
|
|
64
62
|
|
|
65
63
|
// connect handler
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
|
|
65
|
+
this._onConn = (stateChange) => {
|
|
66
|
+
const { current: state, reason } = stateChange;
|
|
67
|
+
console.log(`AblyRealTimeStrategy::connection WS ${state}`, reason || '');
|
|
69
68
|
if(state === 'connected') {
|
|
70
69
|
this._wsError = false;
|
|
71
70
|
// RELOAD
|
|
@@ -76,29 +75,80 @@ class AblyRealTimeStrategy extends AbstractRealTimeStrategy {
|
|
|
76
75
|
this.stopUsingFallback();
|
|
77
76
|
return;
|
|
78
77
|
}
|
|
79
|
-
|
|
78
|
+
|
|
79
|
+
if ((state === 'suspended' || state === 'failed') && !this._closing) {
|
|
80
80
|
if(!this._wsError) {
|
|
81
81
|
this._wsError = true;
|
|
82
82
|
this.startUsingFallback(summitId);
|
|
83
83
|
}
|
|
84
84
|
return;
|
|
85
85
|
}
|
|
86
|
-
if(state
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this.startUsingFallback(summitId);
|
|
90
|
-
}
|
|
91
|
-
return;
|
|
86
|
+
if (state === 'closed') {
|
|
87
|
+
// Expected on unmount, don’t start fallback, don’t log as error
|
|
88
|
+
this._wsError = false;
|
|
92
89
|
}
|
|
93
|
-
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
this._client.connection.on(this._onConn);
|
|
93
|
+
|
|
94
|
+
// start listening for event
|
|
95
|
+
|
|
96
|
+
this._channel = this._client.channels.get(`${summitId}:*:*`);
|
|
97
|
+
|
|
98
|
+
this._onMessage = (message) => {
|
|
99
|
+
try {
|
|
100
|
+
const {data: payload} = message;
|
|
101
|
+
console.log('AblyRealTimeStrategy::create Change received', payload)
|
|
102
|
+
this._callback(payload);
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
console.error('AblyRealTimeStrategy::message handler failed', e);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
this._channel.subscribe(this._onMessage);
|
|
110
|
+
|
|
94
111
|
}
|
|
95
112
|
|
|
96
113
|
close() {
|
|
114
|
+
console.log("AblyRealTimeStrategy::close");
|
|
97
115
|
super.close();
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
116
|
+
this._closing = true;
|
|
117
|
+
try { this.stopUsingFallback(); } catch {}
|
|
118
|
+
|
|
119
|
+
try { this._onMessage && this._channel?.unsubscribe(this._onMessage); }
|
|
120
|
+
catch (e){ console.warn('AblyRealTimeStrategy::close channel.unsubscribe',e); }
|
|
121
|
+
try { this._channel?.off(); }
|
|
122
|
+
catch(e) { console.warn('AblyRealTimeStrategy::close channel.off', e);}
|
|
123
|
+
try { this._onConn && this._client?.connection.off(this._onConn); }
|
|
124
|
+
catch(e) { console.warn('AblyRealTimeStrategy::close AblyRealTimeStrategy::close.off', e); }
|
|
125
|
+
this._onMessage = null;
|
|
126
|
+
this._onConn = null;
|
|
127
|
+
|
|
128
|
+
const client = this._client;
|
|
129
|
+
const channel = this._channel;
|
|
130
|
+
|
|
131
|
+
const tryRelease = () => {
|
|
132
|
+
try {
|
|
133
|
+
const releasable = ['initialized','detached','failed'].includes(channel?.state);
|
|
134
|
+
if (releasable) client?.channels.release(channel.name);
|
|
135
|
+
} catch {}
|
|
136
|
+
try { client?.close(); } catch(e) {
|
|
137
|
+
console.warn('AblyRealTimeStrategy::close client.close', e);
|
|
138
|
+
}
|
|
101
139
|
this._client = null;
|
|
140
|
+
this._channel = null;
|
|
141
|
+
this._closing = false;
|
|
142
|
+
this._wsError = false;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
if (client && channel && client.connection.state !== 'closed' &&
|
|
146
|
+
['attached','attaching','detaching'].includes(channel.state)) {
|
|
147
|
+
// Detach asynchronously, then release if allowed
|
|
148
|
+
channel.detach(() => tryRelease());
|
|
149
|
+
} else {
|
|
150
|
+
// Already detached/failed/closed (or no channel) → just release if possible and close
|
|
151
|
+
tryRelease();
|
|
102
152
|
}
|
|
103
153
|
}
|
|
104
154
|
}
|