@openeventkit/event-site 2.1.41 → 2.1.43

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.
@@ -0,0 +1,122 @@
1
+ # Event Site - Claude Local Settings
2
+
3
+ ## Package Manager
4
+
5
+ This project uses **yarn** (not npm).
6
+
7
+ ## Node Version
8
+
9
+ This project requires Node.js 20.19.4 (see `.nvmrc`).
10
+
11
+ Before running any yarn commands, always use nvm:
12
+
13
+ ```bash
14
+ source ~/.nvm/nvm.sh && nvm use
15
+ ```
16
+
17
+ ## Build Commands
18
+
19
+ ```bash
20
+ # Install dependencies
21
+ source ~/.nvm/nvm.sh && nvm use && yarn install
22
+
23
+ # Development
24
+ source ~/.nvm/nvm.sh && nvm use && yarn develop
25
+
26
+ # Production build
27
+ source ~/.nvm/nvm.sh && nvm use && yarn build
28
+
29
+ # Clean
30
+ source ~/.nvm/nvm.sh && nvm use && yarn clean
31
+ ```
32
+
33
+ ## Release Workflow
34
+
35
+ This package is published to npm as `@openeventkit/event-site` and used as a theme by client sites (e.g., `fnevent-tier1-default-theme`, `fnevent-2025ocpemea-theme`).
36
+
37
+ ### Steps to release a new version:
38
+
39
+ 1. **Pull latest main**:
40
+ ```bash
41
+ git checkout main && git pull origin main
42
+ ```
43
+
44
+ 2. **Update dependencies** (if needed, e.g., widget updates):
45
+ ```bash
46
+ # Update package.json with new widget version
47
+ # Run yarn install to update yarn.lock
48
+ source ~/.nvm/nvm.sh && nvm use && yarn install
49
+ git add package.json yarn.lock
50
+ git commit -m "chore: update my-orders-tickets-widget to X.X.X"
51
+ ```
52
+
53
+ 3. **Bump version** in package.json:
54
+ ```bash
55
+ # Update version field in package.json (e.g., 2.1.41 → 2.1.42)
56
+ git add package.json
57
+ git commit -m "2.1.42"
58
+ ```
59
+
60
+ 4. **Tag and push**:
61
+ ```bash
62
+ git tag -a v2.1.42 -m "2.1.42"
63
+ git push origin main --tags
64
+ ```
65
+
66
+ 5. **Build to verify**, then **publish to npm**:
67
+ ```bash
68
+ # NPM token is configured in ~/.npmrc
69
+ source ~/.nvm/nvm.sh && nvm use && yarn build && npm publish
70
+ ```
71
+
72
+ 6. **Update client themes** to use the new version and deploy.
73
+
74
+ ### Notes:
75
+ - Widget updates are typically direct commits to main (not PRs)
76
+ - Version bumps are also direct commits to main
77
+ - Client themes are in `/Users/gcutrini/Development/Work/fntech/`
78
+ - Before `npm publish`, temporarily add `.claude/` to `.npmignore` to avoid publishing local settings, then rollback after publish
79
+
80
+ ## Pull Request Guidelines
81
+
82
+ - Do NOT include "Test plan" sections in PR descriptions
83
+ - Testing instructions should be added to the ClickUp task, not the PR
84
+ - Always end with `ref: <clickup_task_url>`
85
+ - When asked to "assign" a PR to someone, add them as a **reviewer** (not assignee)
86
+
87
+ ## Team Usernames
88
+
89
+ | Name | GitHub | ClickUp ID |
90
+ |------|--------|------------|
91
+ | Santiago Palenque | santipalenque | 42976547 |
92
+ | Sebastian Marcet | smarcet | 88382342 |
93
+ | Román Gutierrez | romanetar | 88434939 |
94
+ | JP Maxwell | - | 156169762 |
95
+ | Mark Leutwyler | - | 88381000 |
96
+
97
+ ## Time Tracking
98
+
99
+ - Always set time entries as **billable**
100
+ - Add a brief description in **past tense** (e.g., "Fixed PDF receipt bug" not "Fix PDF receipt bug")
101
+ - Time entries can be edited via ClickUp API if adjustment needed
102
+
103
+ ## Terminology
104
+
105
+ - "Tasks" = ClickUp tasks (not GitHub)
106
+
107
+ ## ClickUp Comment Mentions
108
+
109
+ To tag users in ClickUp comments via API, use structured JSON format (not plain `@user_id`):
110
+
111
+ ```bash
112
+ curl -X POST "https://api.clickup.com/api/v2/task/TASK_ID/comment" \
113
+ -H "Authorization: CLICKUP_API_TOKEN" \
114
+ -H "Content-Type: application/json" \
115
+ -d '{
116
+ "comment": [
117
+ {"text": "Message before "},
118
+ {"type": "tag", "user": {"id": USER_ID}},
119
+ {"text": " message after"}
120
+ ]
121
+ }'
122
+ ```
@@ -1,11 +1,20 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
+ "Bash(ls:*)",
4
5
  "Bash(source ~/.nvm/nvm.sh)",
5
- "Bash(nvm use v20.19.5)",
6
- "Bash(npm run develop:*)"
7
- ],
8
- "deny": [],
9
- "ask": []
6
+ "Bash(nvm use:*)",
7
+ "Bash(yarn gatsby clean:*)",
8
+ "Bash(time yarn build:*)",
9
+ "Bash(xargs du:*)",
10
+ "Bash(find:*)",
11
+ "Bash(du -sh:*)",
12
+ "Bash(git checkout:*)",
13
+ "Bash(yarn why:*)",
14
+ "Bash(git add:*)",
15
+ "Bash(git commit:*)",
16
+ "Bash(git cherry-pick:*)",
17
+ "mcp__clickup__clickup_find_member_by_name"
18
+ ]
10
19
  }
11
20
  }
package/README.md CHANGED
@@ -120,6 +120,14 @@ MacOS users might also encounter some errors, for more info check [node-gyp](htt
120
120
 
121
121
  This plugin uses [gatsby-plugin-purgecss](https://www.gatsbyjs.org/packages/gatsby-plugin-purgecss/) and [bulma](https://bulma.io/). The bulma builds are usually ~170K but reduced 90% by purgecss.
122
122
 
123
+ ## Dependency Notes
124
+
125
+ ### cross-fetch Resolution
126
+
127
+ The `cross-fetch` package is pinned to version 3.1.8 via `resolutions`/`overrides` in package.json to fix a "Failed to execute 'fetch' on 'Window': Illegal invocation" error that occurs with `@react-pdf/renderer` when loading custom fonts.
128
+
129
+ See: https://github.com/diegomura/react-pdf/issues/3034
130
+
123
131
  # CONTRIBUTING
124
132
 
125
133
  Contributions are always welcome, no matter how large or small. Before contributing,
package/gatsby-browser.js CHANGED
@@ -17,6 +17,9 @@ import "./src/styles/bulma.scss";
17
17
  import "./src/styles/style.scss";
18
18
  // import global fontawesome
19
19
  import "./src/utils/fontAwesome";
20
+ // PDF font registration
21
+ import { initializePdfFonts } from "./src/utils/pdfFonts";
22
+ import fontVariable from "./static/fonts/nunito-sans/NunitoSans-Variable.ttf";
20
23
 
21
24
  import colors from "data/colors.json";
22
25
  import marketingSettings from "data/marketing-settings.json";
@@ -29,6 +32,18 @@ export const onClientEntry = () => {
29
32
  // smooth scroll polyfill needed for Safari
30
33
  smoothscroll.polyfill();
31
34
 
35
+ // Initialize PDF fonts for certificate and receipt generation
36
+ // Import site settings to check for custom fonts
37
+ import("./src/content/site-settings/index.json").then((siteSettings) => {
38
+ initializePdfFonts({
39
+ defaultFontFile: fontVariable,
40
+ siteFont: siteSettings.siteFont
41
+ });
42
+ }).catch(() => {
43
+ // Fall back to default font if site settings can't be loaded
44
+ initializePdfFonts({ defaultFontFile: fontVariable });
45
+ });
46
+
32
47
  // show cookie consent only if google tag manager was deferred loaded
33
48
  // see gatsby-google-tag-manager-plugin in gatsby-config
34
49
  if (getEnvVariable(GOOGLE_TAGMANAGER_ID)) {
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.41",
4
+ "version": "2.1.43",
5
5
  "author": "Tipit LLC",
6
6
  "dependencies": {
7
7
  "@emotion/server": "^11.11.0",
@@ -87,7 +87,7 @@
87
87
  "markdown-it": "^12.0.0",
88
88
  "moment": "^2.27.0",
89
89
  "moment-timezone": "^0.5.31",
90
- "my-orders-tickets-widget": "1.0.6",
90
+ "my-orders-tickets-widget": "1.0.9",
91
91
  "object.assign": "^4.1.5",
92
92
  "openstack-uicore-foundation": "4.2.16",
93
93
  "path-browserify": "^1.0.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.5",
138
+ "summit-registration-lite": "6.0.6",
139
139
  "superagent": "8.0.9",
140
140
  "sweetalert2": "^11.11.1",
141
141
  "upcoming-events-widget": "3.0.7",
@@ -161,7 +161,7 @@
161
161
  "gatsby-clean": "gatsby clean",
162
162
  "start": "npm run develop",
163
163
  "build": "NODE_ENV=production NODE_OPTIONS=--max-old-space-size=10240 cross-env node --trace-warnings node_modules/.bin/gatsby build --log-pages",
164
- "develop": "NODE_OPTIONS=--max-old-space-size=8192 npm run gatsby-clean && node --trace-warnings node_modules/.bin/gatsby develop --S -H 0.0.0.0",
164
+ "develop": "NODE_OPTIONS=--max-old-space-size=8192 npm run gatsby-clean && NODE_OPTIONS=--max-old-space-size=8192 node --trace-warnings node_modules/.bin/gatsby develop --S -H 0.0.0.0",
165
165
  "format": "prettier --trailing-comma es5 --no-semi --single-quote --write \"{gatsby-*.js,src/**/*.js}\"",
166
166
  "test": "jest",
167
167
  "update-libs": "yarn upgrade live-event-widget openstack-uicore-foundation schedule-lite simple-chat-widget speakers-widget attendee-to-attendee-widget"
@@ -225,5 +225,11 @@
225
225
  "setupFilesAfterEnv": [
226
226
  "<rootDir>/jest.setup.js"
227
227
  ]
228
+ },
229
+ "resolutions": {
230
+ "cross-fetch": "3.1.8"
231
+ },
232
+ "overrides": {
233
+ "cross-fetch": "3.1.8"
228
234
  }
229
235
  }
@@ -103,14 +103,7 @@ export const getEventStreamingInfoById = (
103
103
  customErrorHandler,
104
104
  {},
105
105
  true)
106
- (params)(dispatch).then((payload) => {
107
- return payload
108
- }).catch(e => {
109
- dispatch(createAction(GET_EVENT_DATA_ERROR)(e));
110
- console.log('ERROR: ', e);
111
- clearAccessToken();
112
- return (e);
113
- });
106
+ (params)(dispatch);
114
107
  };
115
108
 
116
109
  /**
@@ -1,94 +1,12 @@
1
1
  import React from "react";
2
- import { Document, Page, Text, View, Image, StyleSheet, Font, pdf } from "@react-pdf/renderer";
2
+ import { Document, Page, Text, View, Image, StyleSheet, pdf } from "@react-pdf/renderer";
3
3
 
4
- import fontVariable from "../../../static/fonts/nunito-sans/NunitoSans-Variable.ttf";
4
+ import {
5
+ getRegisteredFontFamily,
6
+ registerCustomFont
7
+ } from "../../utils/pdfFonts";
5
8
  import { USER_ROLES } from './constants';
6
9
 
7
- const registerDefaultFont = () => {
8
- try {
9
- Font.register({
10
- family: "Nunito Sans",
11
- fonts: [
12
- {
13
- src: fontVariable,
14
- fontWeight: "normal"
15
- },
16
- {
17
- src: fontVariable,
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
10
 
93
11
  const calculateOptimalFontSize = (text, maxWidth = 650, initialFontSize = 48, minFontSize = 24) => {
94
12
  // estimate average character width based on font size
@@ -119,26 +37,6 @@ const calculateOptimalFontSize = (text, maxWidth = 650, initialFontSize = 48, mi
119
37
  return finalSize;
120
38
  };
121
39
 
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
-
142
40
  const CertificatePDF = ({
143
41
  attendee,
144
42
  summit,
@@ -156,17 +54,15 @@ const CertificatePDF = ({
156
54
  const nameFontSize = calculateOptimalFontSize(fullName);
157
55
 
158
56
 
159
- let fontFamily = "Nunito Sans";
57
+ // Use fonts registered at app init, or try custom font from site settings
58
+ let fontFamily = getRegisteredFontFamily();
160
59
 
60
+ // If site settings has a custom font, try to register it (may already be registered at init)
161
61
  if (settings?.siteFont && settings.siteFont.fontFamily) {
162
62
  const customFontRegistered = registerCustomFont(settings.siteFont);
163
63
  if (customFontRegistered) {
164
64
  fontFamily = settings.siteFont.fontFamily;
165
- } else {
166
- registerDefaultFont();
167
65
  }
168
- } else {
169
- registerDefaultFont();
170
66
  }
171
67
 
172
68
  const styles = StyleSheet.create({
@@ -336,15 +232,13 @@ const CertificatePDF = ({
336
232
  // helper function to generate and download the certificate
337
233
  export const generatePDF = async (attendee, summit, settings) => {
338
234
  try {
339
- // Validate logo URL before generating PDF
340
- const logoUrlToValidate = settings.logo || summit.logo;
341
- const validatedLogoUrl = await validateImageUrl(logoUrlToValidate);
342
-
343
- const doc = <CertificatePDF
344
- attendee={attendee}
345
- summit={summit}
235
+ const logoUrl = settings.logo || summit.logo || null;
236
+
237
+ const doc = <CertificatePDF
238
+ attendee={attendee}
239
+ summit={summit}
346
240
  settings={settings}
347
- logoUrl={validatedLogoUrl}
241
+ logoUrl={logoUrl}
348
242
  />;
349
243
  const blob = await pdf(doc).toBlob();
350
244
 
@@ -8,6 +8,9 @@ import loadable from "@loadable/component";
8
8
  import 'my-orders-tickets-widget/dist/index.css';
9
9
  import 'my-orders-tickets-widget/dist/i18n';
10
10
  import { SentryFallbackFunction } from "./SentryErrorComponent";
11
+ import useMarketingSettings, { MARKETING_SETTINGS_KEYS } from '../utils/useMarketingSettings';
12
+ import useSiteSettings from '../utils/useSiteSettings';
13
+ import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_PATH, getFontUrl } from '../utils/pdfFonts';
11
14
  const MyOrdersMyTicketsWidget = loadable(() => import("my-orders-tickets-widget/dist/index"), {
12
15
  ssr: false,
13
16
  fallback: null,
@@ -17,9 +20,34 @@ export const MyOrdersTicketsComponent = () => {
17
20
  const dispatch = useDispatch();
18
21
  const user = useSelector(state => state.userState);
19
22
  const summit = useSelector(state => state.summitState.summit);
23
+ const { getSettingByKey } = useMarketingSettings();
24
+ const siteSettings = useSiteSettings();
20
25
 
21
26
  if (!summit) return null;
22
27
 
28
+ // Get font info from site settings, defaulting to Nunito Sans
29
+ const siteFont = siteSettings?.siteFont;
30
+ const fontFamily = siteFont?.fontFamily || DEFAULT_FONT_FAMILY;
31
+ const fontUrl = getFontUrl(siteFont?.regularFont?.fontFile || DEFAULT_FONT_PATH, window.location.origin);
32
+
33
+ // Build receipt settings from marketing settings
34
+ const receiptSettings = {
35
+ primaryColor: getSettingByKey(MARKETING_SETTINGS_KEYS.colorPrimary),
36
+ fontFamily,
37
+ fontUrl,
38
+ printLogo: getSettingByKey(MARKETING_SETTINGS_KEYS.printLogo),
39
+ organizerLegalName: getSettingByKey(MARKETING_SETTINGS_KEYS.receiptOrganizerLegalName),
40
+ organizerAddressLine1: getSettingByKey(MARKETING_SETTINGS_KEYS.receiptOrganizerAddressLine1),
41
+ organizerAddressLine2: getSettingByKey(MARKETING_SETTINGS_KEYS.receiptOrganizerAddressLine2),
42
+ organizerCity: getSettingByKey(MARKETING_SETTINGS_KEYS.receiptOrganizerCity),
43
+ organizerState: getSettingByKey(MARKETING_SETTINGS_KEYS.receiptOrganizerState),
44
+ organizerPostalCode: getSettingByKey(MARKETING_SETTINGS_KEYS.receiptOrganizerPostalCode),
45
+ organizerCountry: getSettingByKey(MARKETING_SETTINGS_KEYS.receiptOrganizerCountry),
46
+ organizerTaxId: getSettingByKey(MARKETING_SETTINGS_KEYS.receiptOrganizerTaxId),
47
+ organizerTaxIdLabel: getSettingByKey(MARKETING_SETTINGS_KEYS.receiptOrganizerTaxIdLabel),
48
+ supportEmail: summit.support_email || getEnvVariable(SUPPORT_EMAIL),
49
+ };
50
+
23
51
  const widgetProps = {
24
52
  apiBaseUrl: getEnvVariable(SUMMIT_API_BASE_URL),
25
53
  clientId: getEnvVariable(OAUTH2_CLIENT_ID),
@@ -30,6 +58,7 @@ export const MyOrdersTicketsComponent = () => {
30
58
  updateProfile: (profile) => dispatch(updateProfile(profile)),
31
59
  summit,
32
60
  user,
61
+ receiptSettings,
33
62
  onTicketAssigned: (ticket) => dispatch(ticketOwnerChange(ticket))
34
63
  };
35
64
 
@@ -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":1757078904637},{"file":"src/data/events.json","build_time":1757078906503},{"file":"src/data/events.idx.json","build_time":1757078906506},{"file":"src/data/speakers.json","build_time":1757078907177},{"file":"src/data/speakers.idx.json","build_time":1757078907177},{"file":"src/content/sponsors.json","build_time":1757078912121},{"file":"src/data/voteable-presentations.json","build_time":1757078912473}],"lastBuild":1757078912473}
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":1769788624706},{"file":"src/data/events.json","build_time":1769788626401},{"file":"src/data/events.idx.json","build_time":1769788626403},{"file":"src/data/speakers.json","build_time":1769788626986},{"file":"src/data/speakers.idx.json","build_time":1769788626987},{"file":"src/content/sponsors.json","build_time":1769788629079},{"file":"src/data/voteable-presentations.json","build_time":1769788629429}],"lastBuild":1769788629430}