@bash-app/bash-common 30.225.0 → 30.227.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/dist/__tests__/agePolicy.test.d.ts +2 -0
  2. package/dist/__tests__/agePolicy.test.d.ts.map +1 -0
  3. package/dist/__tests__/agePolicy.test.js +203 -0
  4. package/dist/__tests__/agePolicy.test.js.map +1 -0
  5. package/dist/__tests__/getDisplayGender.test.d.ts +2 -0
  6. package/dist/__tests__/getDisplayGender.test.d.ts.map +1 -0
  7. package/dist/__tests__/getDisplayGender.test.js +34 -0
  8. package/dist/__tests__/getDisplayGender.test.js.map +1 -0
  9. package/dist/__tests__/statusEnums.test.d.ts +2 -0
  10. package/dist/__tests__/statusEnums.test.d.ts.map +1 -0
  11. package/dist/__tests__/statusEnums.test.js +24 -0
  12. package/dist/__tests__/statusEnums.test.js.map +1 -0
  13. package/dist/aiApproval.d.ts +89 -0
  14. package/dist/aiApproval.d.ts.map +1 -0
  15. package/dist/aiApproval.js +82 -0
  16. package/dist/aiApproval.js.map +1 -0
  17. package/dist/definitions.d.ts +44 -2
  18. package/dist/definitions.d.ts.map +1 -1
  19. package/dist/definitions.js +106 -2
  20. package/dist/definitions.js.map +1 -1
  21. package/dist/extendedSchemas.d.ts +280 -105
  22. package/dist/extendedSchemas.d.ts.map +1 -1
  23. package/dist/extendedSchemas.js +34 -2
  24. package/dist/extendedSchemas.js.map +1 -1
  25. package/dist/index.d.ts +5 -2
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +4 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/legalTemplates.d.ts +1 -1
  30. package/dist/legalTemplates.js +1 -1
  31. package/dist/utils/__tests__/badgeUtils.test.d.ts +2 -0
  32. package/dist/utils/__tests__/badgeUtils.test.d.ts.map +1 -0
  33. package/dist/utils/__tests__/badgeUtils.test.js +157 -0
  34. package/dist/utils/__tests__/badgeUtils.test.js.map +1 -0
  35. package/dist/utils/__tests__/bashPointsPaymentUtils.test.d.ts +2 -0
  36. package/dist/utils/__tests__/bashPointsPaymentUtils.test.d.ts.map +1 -0
  37. package/dist/utils/__tests__/bashPointsPaymentUtils.test.js +114 -0
  38. package/dist/utils/__tests__/bashPointsPaymentUtils.test.js.map +1 -0
  39. package/dist/utils/__tests__/contentFilterUtils.test.d.ts +2 -0
  40. package/dist/utils/__tests__/contentFilterUtils.test.d.ts.map +1 -0
  41. package/dist/utils/__tests__/contentFilterUtils.test.js +443 -0
  42. package/dist/utils/__tests__/contentFilterUtils.test.js.map +1 -0
  43. package/dist/utils/__tests__/displayDateUtils.test.d.ts +2 -0
  44. package/dist/utils/__tests__/displayDateUtils.test.d.ts.map +1 -0
  45. package/dist/utils/__tests__/displayDateUtils.test.js +105 -0
  46. package/dist/utils/__tests__/displayDateUtils.test.js.map +1 -0
  47. package/dist/utils/__tests__/flyerUtils.test.d.ts +2 -0
  48. package/dist/utils/__tests__/flyerUtils.test.d.ts.map +1 -0
  49. package/dist/utils/__tests__/flyerUtils.test.js +49 -0
  50. package/dist/utils/__tests__/flyerUtils.test.js.map +1 -0
  51. package/dist/utils/__tests__/getBackendHost.test.d.ts +2 -0
  52. package/dist/utils/__tests__/getBackendHost.test.d.ts.map +1 -0
  53. package/dist/utils/__tests__/getBackendHost.test.js +104 -0
  54. package/dist/utils/__tests__/getBackendHost.test.js.map +1 -0
  55. package/dist/utils/__tests__/getFrontendHost.test.d.ts +2 -0
  56. package/dist/utils/__tests__/getFrontendHost.test.d.ts.map +1 -0
  57. package/dist/utils/__tests__/getFrontendHost.test.js +207 -0
  58. package/dist/utils/__tests__/getFrontendHost.test.js.map +1 -0
  59. package/dist/utils/__tests__/promoCodesUtils.test.d.ts +2 -0
  60. package/dist/utils/__tests__/promoCodesUtils.test.d.ts.map +1 -0
  61. package/dist/utils/__tests__/promoCodesUtils.test.js +183 -0
  62. package/dist/utils/__tests__/promoCodesUtils.test.js.map +1 -0
  63. package/dist/utils/__tests__/qrCodeUtils.test.d.ts +2 -0
  64. package/dist/utils/__tests__/qrCodeUtils.test.d.ts.map +1 -0
  65. package/dist/utils/__tests__/qrCodeUtils.test.js +99 -0
  66. package/dist/utils/__tests__/qrCodeUtils.test.js.map +1 -0
  67. package/dist/utils/__tests__/stripeAccountUtils.test.d.ts +2 -0
  68. package/dist/utils/__tests__/stripeAccountUtils.test.d.ts.map +1 -0
  69. package/dist/utils/__tests__/stripeAccountUtils.test.js +75 -0
  70. package/dist/utils/__tests__/stripeAccountUtils.test.js.map +1 -0
  71. package/dist/utils/blog/__tests__/getRelatedBlogPosts.test.d.ts +2 -0
  72. package/dist/utils/blog/__tests__/getRelatedBlogPosts.test.d.ts.map +1 -0
  73. package/dist/utils/blog/__tests__/getRelatedBlogPosts.test.js +233 -0
  74. package/dist/utils/blog/__tests__/getRelatedBlogPosts.test.js.map +1 -0
  75. package/dist/utils/blog/blogCommentDbUtils.d.ts +161 -0
  76. package/dist/utils/blog/blogCommentDbUtils.d.ts.map +1 -0
  77. package/dist/utils/blog/blogCommentDbUtils.js +111 -0
  78. package/dist/utils/blog/blogCommentDbUtils.js.map +1 -0
  79. package/dist/utils/blog/blogDbUtils.d.ts +10 -0
  80. package/dist/utils/blog/blogDbUtils.d.ts.map +1 -1
  81. package/dist/utils/blog/blogDbUtils.js +71 -0
  82. package/dist/utils/blog/blogDbUtils.js.map +1 -1
  83. package/dist/utils/flyerUtils.d.ts +42 -0
  84. package/dist/utils/flyerUtils.d.ts.map +1 -0
  85. package/dist/utils/flyerUtils.js +104 -0
  86. package/dist/utils/flyerUtils.js.map +1 -0
  87. package/dist/utils/urlUtils.d.ts +10 -0
  88. package/dist/utils/urlUtils.d.ts.map +1 -1
  89. package/dist/utils/urlUtils.js +24 -5
  90. package/dist/utils/urlUtils.js.map +1 -1
  91. package/dist/utils/userUtils.d.ts +32 -0
  92. package/dist/utils/userUtils.d.ts.map +1 -1
  93. package/dist/utils/userUtils.js +27 -0
  94. package/dist/utils/userUtils.js.map +1 -1
  95. package/package.json +1 -1
  96. package/prisma/schema.prisma +1196 -845
  97. package/src/__tests__/agePolicy.test.ts +257 -0
  98. package/src/__tests__/getDisplayGender.test.ts +50 -0
  99. package/src/__tests__/statusEnums.test.ts +36 -0
  100. package/src/aiApproval.ts +118 -0
  101. package/src/definitions.ts +138 -1
  102. package/src/extendedSchemas.ts +86 -3
  103. package/src/index.ts +15 -0
  104. package/src/legalTemplates.ts +1 -1
  105. package/src/utils/__tests__/badgeUtils.test.ts +191 -0
  106. package/src/utils/__tests__/bashPointsPaymentUtils.test.ts +153 -0
  107. package/src/utils/__tests__/contentFilterUtils.test.ts +547 -0
  108. package/src/utils/__tests__/displayDateUtils.test.ts +113 -0
  109. package/src/utils/__tests__/flyerUtils.test.ts +70 -0
  110. package/src/utils/__tests__/getBackendHost.test.ts +128 -0
  111. package/src/utils/__tests__/getFrontendHost.test.ts +232 -0
  112. package/src/utils/__tests__/promoCodesUtils.test.ts +237 -0
  113. package/src/utils/__tests__/qrCodeUtils.test.ts +143 -0
  114. package/src/utils/__tests__/stripeAccountUtils.test.ts +93 -0
  115. package/src/utils/blog/__tests__/getRelatedBlogPosts.test.ts +271 -0
  116. package/src/utils/blog/blogCommentDbUtils.ts +161 -0
  117. package/src/utils/blog/blogDbUtils.ts +95 -1
  118. package/src/utils/flyerUtils.ts +137 -0
  119. package/src/utils/urlUtils.ts +27 -5
  120. package/src/utils/userUtils.ts +40 -0
  121. package/dist/utils/__tests__/cancellationPolicyRefundResolver.test.d.ts +0 -6
  122. package/dist/utils/__tests__/cancellationPolicyRefundResolver.test.d.ts.map +0 -1
  123. package/dist/utils/__tests__/cancellationPolicyRefundResolver.test.js +0 -104
  124. package/dist/utils/__tests__/cancellationPolicyRefundResolver.test.js.map +0 -1
  125. package/dist/utils/service/serviceRateDBUtils.d.ts +0 -1
  126. package/dist/utils/service/serviceRateDBUtils.d.ts.map +0 -1
  127. package/dist/utils/service/serviceRateDBUtils.js +0 -159
  128. package/dist/utils/service/serviceRateDBUtils.js.map +0 -1
  129. package/prisma/migrations/add_bash_availability.sql +0 -53
  130. package/prisma/migrations/add_bashcash_pricing_to_ticket_tier.sql +0 -15
  131. package/prisma/migrations/add_bashpoints_purchase_tracking.sql +0 -33
  132. package/prisma/migrations/add_event_group_allow_auto_join.sql +0 -17
  133. package/prisma/migrations/add_group_member_status_not_going_maybe.sql +0 -5
  134. package/prisma/migrations/add_groups_and_momentum.sql +0 -135
  135. package/prisma/migrations/add_groups_momentum_phase0_4.sql +0 -200
  136. package/prisma/migrations/add_new_feature_tables.sql +0 -140
  137. package/prisma/migrations/add_pricing_type_enum.sql +0 -88
  138. package/prisma/migrations/add_tier_privacy_and_fee_handling.sql +0 -29
  139. package/prisma/migrations/diagnostic_bashcash_columns.sql +0 -157
  140. package/prisma/migrations/fix_bashcash_referral_code_schema_mismatch.sql +0 -81
  141. package/prisma/migrations/rename_bashcash_to_bashpoints.sql +0 -183
  142. package/prisma/migrations/rename_bashcredits_to_bashpoints.sql +0 -96
@@ -0,0 +1,105 @@
1
+ /**
2
+ * displayDateUtils — UI date formatting (browser local timezone).
3
+ *
4
+ * These two helpers feed every "card date" / "long form date" string in the
5
+ * app. Any regression here ripples across feed cards, ticket emails (preview),
6
+ * blog posts, etc. We pin:
7
+ * - fallback behavior for null/undefined/empty/invalid input
8
+ * - that an ISO string is parsed without throwing
9
+ * - the exact short and long format pieces (en-US: "Mar 7, 2026" / "March 7, 2026")
10
+ *
11
+ * To make assertions deterministic regardless of where CI runs, we set
12
+ * process.env.TZ to America/New_York and use Date.UTC inputs that fall well
13
+ * inside the same NY day.
14
+ */
15
+ import { formatShortDate, formatLongDate } from "../displayDateUtils.js";
16
+ const ORIGINAL_TZ = process.env.TZ;
17
+ beforeAll(() => {
18
+ process.env.TZ = "America/New_York";
19
+ });
20
+ afterAll(() => {
21
+ process.env.TZ = ORIGINAL_TZ;
22
+ });
23
+ // Noon UTC → 7am or 8am NY time depending on DST. Either way it's the same calendar day in NY.
24
+ function utcNoon(year, month, day) {
25
+ return new Date(Date.UTC(year, month - 1, day, 12, 0, 0));
26
+ }
27
+ describe("formatShortDate", () => {
28
+ describe("fallbacks", () => {
29
+ it("returns the default fallback ('Date TBD') for null", () => {
30
+ expect(formatShortDate(null)).toBe("Date TBD");
31
+ });
32
+ it("returns the default fallback for undefined", () => {
33
+ expect(formatShortDate(undefined)).toBe("Date TBD");
34
+ });
35
+ it("returns the default fallback for empty string", () => {
36
+ expect(formatShortDate("")).toBe("Date TBD");
37
+ });
38
+ it("returns a custom fallback when provided", () => {
39
+ expect(formatShortDate(null, "TBA")).toBe("TBA");
40
+ expect(formatShortDate(undefined, "")).toBe("");
41
+ });
42
+ it("returns the fallback for an unparseable date string", () => {
43
+ expect(formatShortDate("not-a-real-date")).toBe("Date TBD");
44
+ });
45
+ it("returns the fallback for an Invalid Date object", () => {
46
+ expect(formatShortDate(new Date("garbage"))).toBe("Date TBD");
47
+ });
48
+ });
49
+ describe("formatting", () => {
50
+ it("formats a Date object as 'Mon D, YYYY' in en-US", () => {
51
+ // March 7, 2026 noon UTC → still March 7 in New York
52
+ expect(formatShortDate(utcNoon(2026, 3, 7))).toBe("Mar 7, 2026");
53
+ });
54
+ it("accepts an ISO string and produces the same short date", () => {
55
+ expect(formatShortDate("2026-03-07T12:00:00Z")).toBe("Mar 7, 2026");
56
+ });
57
+ it("uses abbreviated month names (3 chars)", () => {
58
+ expect(formatShortDate(utcNoon(2026, 9, 1))).toBe("Sep 1, 2026");
59
+ expect(formatShortDate(utcNoon(2026, 12, 25))).toBe("Dec 25, 2026");
60
+ });
61
+ it("does NOT zero-pad single-digit days", () => {
62
+ expect(formatShortDate(utcNoon(2026, 1, 5))).toBe("Jan 5, 2026");
63
+ });
64
+ });
65
+ });
66
+ describe("formatLongDate", () => {
67
+ describe("fallbacks", () => {
68
+ it("returns '' (the default fallback) for null", () => {
69
+ expect(formatLongDate(null)).toBe("");
70
+ });
71
+ it("returns '' for undefined", () => {
72
+ expect(formatLongDate(undefined)).toBe("");
73
+ });
74
+ it("returns '' for empty string", () => {
75
+ expect(formatLongDate("")).toBe("");
76
+ });
77
+ it("returns a custom fallback when provided", () => {
78
+ expect(formatLongDate(null, "Coming soon")).toBe("Coming soon");
79
+ });
80
+ it("returns the fallback for an unparseable date string", () => {
81
+ expect(formatLongDate("not a date", "n/a")).toBe("n/a");
82
+ });
83
+ it("returns the fallback for an Invalid Date object", () => {
84
+ expect(formatLongDate(new Date(NaN), "n/a")).toBe("n/a");
85
+ });
86
+ });
87
+ describe("formatting", () => {
88
+ it("formats a Date object as 'Month D, YYYY' in en-US (full month name)", () => {
89
+ expect(formatLongDate(utcNoon(2026, 3, 7))).toBe("March 7, 2026");
90
+ });
91
+ it("accepts an ISO string", () => {
92
+ expect(formatLongDate("2026-03-07T12:00:00Z")).toBe("March 7, 2026");
93
+ });
94
+ it("spells out the month even for short months", () => {
95
+ expect(formatLongDate(utcNoon(2026, 5, 1))).toBe("May 1, 2026");
96
+ expect(formatLongDate(utcNoon(2026, 6, 30))).toBe("June 30, 2026");
97
+ });
98
+ });
99
+ it("short and long disagree only on the month (sanity cross-check)", () => {
100
+ const d = utcNoon(2026, 11, 15);
101
+ expect(formatShortDate(d)).toBe("Nov 15, 2026");
102
+ expect(formatLongDate(d)).toBe("November 15, 2026");
103
+ });
104
+ });
105
+ //# sourceMappingURL=displayDateUtils.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"displayDateUtils.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/displayDateUtils.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=flyerUtils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flyerUtils.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/flyerUtils.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from "@jest/globals";
2
+ import { isEddmFlatSizeCompliant, quoteFlyerTierForHouseholds, computeHostTotalAfterSponsors, FLYER_PRICING_TIERS, USPS_EDDM_RETAIL_MIN_MAILPIECES, flyerQrScanPath, generateFlyerCampaignSlug, distanceMiles, } from "../flyerUtils.js";
3
+ describe("flyerUtils", () => {
4
+ it("isEddmFlatSizeCompliant accepts 6.25 x 11", () => {
5
+ expect(isEddmFlatSizeCompliant(6.25, 11)).toBe(true);
6
+ });
7
+ it("isEddmFlatSizeCompliant rejects too-small flats", () => {
8
+ expect(isEddmFlatSizeCompliant(4, 6)).toBe(false);
9
+ });
10
+ it("quoteFlyerTierForHouseholds returns null below minimum", () => {
11
+ expect(quoteFlyerTierForHouseholds(50)).toBeNull();
12
+ });
13
+ it("quoteFlyerTierForHouseholds returns Starter for 200 homes", () => {
14
+ const q = quoteFlyerTierForHouseholds(200);
15
+ expect(q?.tierId).toBe("starter");
16
+ expect(q?.hostPriceCents).toBe(FLYER_PRICING_TIERS[0].hostPriceCents);
17
+ });
18
+ it("computeHostTotalAfterSponsors never goes negative", () => {
19
+ expect(computeHostTotalAfterSponsors(32900, 50000)).toBe(0);
20
+ });
21
+ it("min mailpieces matches starter tier floor", () => {
22
+ expect(USPS_EDDM_RETAIL_MIN_MAILPIECES).toBe(FLYER_PRICING_TIERS[0].minHouseholds);
23
+ });
24
+ it("flyerQrScanPath encodes campaign slug for query", () => {
25
+ expect(flyerQrScanPath("abc")).toBe("/api/flyer/qr-scan?c=abc");
26
+ expect(flyerQrScanPath("a&b=c")).toBe("/api/flyer/qr-scan?c=" + encodeURIComponent("a&b=c"));
27
+ });
28
+ it("generateFlyerCampaignSlug yields fb prefix, fixed length, allowed charset", () => {
29
+ const rnd = jest.spyOn(Math, "random").mockReturnValue(0);
30
+ try {
31
+ const slug = generateFlyerCampaignSlug();
32
+ expect(slug.startsWith("fb")).toBe(true);
33
+ expect(slug).toHaveLength(14);
34
+ expect(slug.slice(2)).toMatch(/^[23456789abcdefghjkmnpqrstuvwxyz]{12}$/);
35
+ }
36
+ finally {
37
+ rnd.mockRestore();
38
+ }
39
+ });
40
+ it("distanceMiles is ~0 for identical points", () => {
41
+ expect(distanceMiles(40.7, -112, 40.7, -112)).toBe(0);
42
+ });
43
+ it("distanceMiles SLC to Provo is in plausible range (~35–50 mi)", () => {
44
+ const mi = distanceMiles(40.7608, -111.891, 40.2338, -111.6585);
45
+ expect(mi).toBeGreaterThan(30);
46
+ expect(mi).toBeLessThan(55);
47
+ });
48
+ });
49
+ //# sourceMappingURL=flyerUtils.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flyerUtils.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/flyerUtils.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=getBackendHost.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getBackendHost.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/getBackendHost.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * getBackendHost — chooses the API origin (api.bash.community vs localhost:3500
3
+ * vs the bundled API_HOST for native Capacitor builds).
4
+ *
5
+ * This file pins the contract so a future "let's tidy up env handling" change
6
+ * doesn't accidentally re-introduce the same class of bug we just fixed in
7
+ * getFrontendHost (server-only env vars leaking into the browser code path).
8
+ */
9
+ import { getBackendHost } from "../urlUtils.js";
10
+ const ORIGINAL_WINDOW = globalThis.window;
11
+ function setWindow(loc, capacitor) {
12
+ globalThis.window = {
13
+ location: loc,
14
+ ...(capacitor ? { Capacitor: capacitor } : {}),
15
+ };
16
+ }
17
+ function clearWindow() {
18
+ if (ORIGINAL_WINDOW === undefined) {
19
+ delete globalThis.window;
20
+ }
21
+ else {
22
+ globalThis.window = ORIGINAL_WINDOW;
23
+ }
24
+ }
25
+ afterEach(() => {
26
+ clearWindow();
27
+ });
28
+ describe("getBackendHost — local dev", () => {
29
+ it("returns http://localhost:3500 for localhost (does NOT reuse :3000 from the page)", () => {
30
+ setWindow({
31
+ protocol: "http:",
32
+ hostname: "localhost",
33
+ host: "localhost:3000",
34
+ port: "3000",
35
+ });
36
+ expect(getBackendHost()).toBe("http://localhost:3500");
37
+ });
38
+ it("returns http://127.0.0.1:3500 for 127.0.0.1", () => {
39
+ setWindow({
40
+ protocol: "http:",
41
+ hostname: "127.0.0.1",
42
+ host: "127.0.0.1:3000",
43
+ port: "3000",
44
+ });
45
+ expect(getBackendHost()).toBe("http://127.0.0.1:3500");
46
+ });
47
+ it("uses the LAN IP for 192.168.x.x (so a phone on the same wifi can hit the dev API)", () => {
48
+ setWindow({
49
+ protocol: "http:",
50
+ hostname: "192.168.1.42",
51
+ host: "192.168.1.42:3000",
52
+ port: "3000",
53
+ });
54
+ expect(getBackendHost()).toBe("http://192.168.1.42:3500");
55
+ });
56
+ });
57
+ describe("getBackendHost — deployed web", () => {
58
+ // For deployed web, getBackendHost falls back to API_HOST (compiled in from
59
+ // process.env.REACT_APP_API at bundle time; defaults to http://localhost:3500
60
+ // when unset, which is what jest sees here). The contract we pin is "for any
61
+ // non-local hostname, do NOT derive the API URL from window.location" — that
62
+ // would silently route prod browsers at api.bash.community… instead of the
63
+ // configured API_HOST.
64
+ it("does not derive the backend host from window.location for deployed sites", () => {
65
+ setWindow({
66
+ protocol: "https:",
67
+ hostname: "bash.community",
68
+ host: "bash.community",
69
+ port: "",
70
+ });
71
+ const result = getBackendHost();
72
+ expect(result).not.toContain("bash.community:3500");
73
+ expect(result).not.toContain("bash.community:3000");
74
+ });
75
+ it("does not derive the backend host from a QA subdomain either", () => {
76
+ setWindow({
77
+ protocol: "https:",
78
+ hostname: "qa.bash.community",
79
+ host: "qa.bash.community",
80
+ port: "",
81
+ });
82
+ const result = getBackendHost();
83
+ expect(result).not.toContain("qa.bash.community:3500");
84
+ });
85
+ });
86
+ describe("getBackendHost — Capacitor native (iOS / Android)", () => {
87
+ it("uses the bundled API_HOST when running inside a Capacitor WebView (hostname is 'localhost' but it's not really local dev)", () => {
88
+ // Capacitor loads from a localhost-like origin inside the WebView. If we
89
+ // routed to the localhost dev API based on hostname alone, the app would
90
+ // hit a port that doesn't exist on the device. The native check must win.
91
+ setWindow({
92
+ protocol: "capacitor:",
93
+ hostname: "localhost",
94
+ host: "localhost",
95
+ port: "",
96
+ }, { isNativePlatform: () => true });
97
+ const result = getBackendHost();
98
+ // We can't pin the exact value (depends on REACT_APP_API at build time),
99
+ // but we CAN pin the negative: it must not be the dev-server URL just
100
+ // because the WebView happens to say hostname=localhost.
101
+ expect(result).toBe(process.env.REACT_APP_API ?? "http://localhost:3500");
102
+ });
103
+ });
104
+ //# sourceMappingURL=getBackendHost.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getBackendHost.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/getBackendHost.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=getFrontendHost.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getFrontendHost.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/getFrontendHost.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,207 @@
1
+ /**
2
+ * getFrontendHost — the single source of truth for the URL prefix that goes
3
+ * into every shareable Bash link (SharePopup, embed snippets, LinkedIn
4
+ * Announce, Facebook event helper, QR codes…).
5
+ *
6
+ * THE BUG THIS FILE PINS
7
+ * ----------------------
8
+ * Pre-fix, getFrontendHost() called isProduction() which inspects
9
+ * process.env.ENVIRONMENT / BASH_ENV / NODE_ENV. Those env vars are server-
10
+ * only — Vite's envPlugin only inlines REACT_APP_* / VITE_* / NODE_ENV /
11
+ * PUBLIC_URL into the browser bundle. As a result, in the production browser
12
+ * `process.env.ENVIRONMENT` is undefined → isProduction() returns false →
13
+ * getFrontendHost() falls into the dev branch → appends ":3000" to whatever
14
+ * hostname the user is on. Real user-visible breakage:
15
+ *
16
+ * https://bash.community:3000/bash/cmo3...-empanada-fest-slc?ref=...
17
+ *
18
+ * Facebook / iMessage / LinkedIn cannot fetch a preview because :3000 isn't
19
+ * publicly reachable, and the link itself looks broken to recipients.
20
+ *
21
+ * THE CONTRACT WE PIN
22
+ * -------------------
23
+ * localhost / 127.0.0.1 / 192.168.x.x → preserve the dev port (3000 default)
24
+ * <anything else> → use window.location.host AS-IS
25
+ * (no port appending, no env-var dependency)
26
+ *
27
+ * Browser-hostname-driven, not env-var-driven, so the answer is correct
28
+ * regardless of how CI sets ENVIRONMENT / BASH_ENV.
29
+ */
30
+ import { getFrontendHost } from "../urlUtils.js";
31
+ const ORIGINAL_WINDOW = globalThis.window;
32
+ function setLocation(loc) {
33
+ globalThis.window = {
34
+ location: loc,
35
+ };
36
+ }
37
+ function clearLocation() {
38
+ if (ORIGINAL_WINDOW === undefined) {
39
+ delete globalThis.window;
40
+ }
41
+ else {
42
+ globalThis.window = ORIGINAL_WINDOW;
43
+ }
44
+ }
45
+ afterEach(() => {
46
+ clearLocation();
47
+ });
48
+ // ============================================================================
49
+ // PRODUCTION (the regression we're fixing)
50
+ // ============================================================================
51
+ describe("getFrontendHost — production (the share-link bug)", () => {
52
+ it("returns 'https://bash.community' for the production domain (NO :3000)", () => {
53
+ setLocation({
54
+ protocol: "https:",
55
+ hostname: "bash.community",
56
+ host: "bash.community",
57
+ port: "",
58
+ });
59
+ const host = getFrontendHost();
60
+ expect(host).toBe("https://bash.community");
61
+ expect(host).not.toMatch(/:3000/);
62
+ });
63
+ it("does NOT append :3000 to bash.community even if the bundle has no ENVIRONMENT env var", () => {
64
+ // Simulate the production browser bundle: ENVIRONMENT/BASH_ENV are NOT inlined.
65
+ const origEnv = process.env.ENVIRONMENT;
66
+ const origBashEnv = process.env.BASH_ENV;
67
+ const origNodeEnv = process.env.NODE_ENV;
68
+ try {
69
+ delete process.env.ENVIRONMENT;
70
+ delete process.env.BASH_ENV;
71
+ // Vite would replace process.env.NODE_ENV with "production" at build time.
72
+ process.env.NODE_ENV = "production";
73
+ setLocation({
74
+ protocol: "https:",
75
+ hostname: "bash.community",
76
+ host: "bash.community",
77
+ port: "",
78
+ });
79
+ expect(getFrontendHost()).toBe("https://bash.community");
80
+ }
81
+ finally {
82
+ if (origEnv === undefined) {
83
+ delete process.env.ENVIRONMENT;
84
+ }
85
+ else {
86
+ process.env.ENVIRONMENT = origEnv;
87
+ }
88
+ if (origBashEnv === undefined) {
89
+ delete process.env.BASH_ENV;
90
+ }
91
+ else {
92
+ process.env.BASH_ENV = origBashEnv;
93
+ }
94
+ if (origNodeEnv === undefined) {
95
+ delete process.env.NODE_ENV;
96
+ }
97
+ else {
98
+ process.env.NODE_ENV = origNodeEnv;
99
+ }
100
+ }
101
+ });
102
+ it("works for the QA subdomain (qa.bash.community → no :3000)", () => {
103
+ setLocation({
104
+ protocol: "https:",
105
+ hostname: "qa.bash.community",
106
+ host: "qa.bash.community",
107
+ port: "",
108
+ });
109
+ expect(getFrontendHost()).toBe("https://qa.bash.community");
110
+ });
111
+ it("works for any vanity subdomain (org.bash.community → no :3000)", () => {
112
+ setLocation({
113
+ protocol: "https:",
114
+ hostname: "myorg.bash.community",
115
+ host: "myorg.bash.community",
116
+ port: "",
117
+ });
118
+ expect(getFrontendHost()).toBe("https://myorg.bash.community");
119
+ });
120
+ it("preserves an EXPLICIT non-default port if the deployed site is running on one", () => {
121
+ // (uncommon but legal — e.g. preview deploy on :8080)
122
+ setLocation({
123
+ protocol: "https:",
124
+ hostname: "preview.bash.community",
125
+ host: "preview.bash.community:8080",
126
+ port: "8080",
127
+ });
128
+ expect(getFrontendHost()).toBe("https://preview.bash.community:8080");
129
+ });
130
+ });
131
+ // ============================================================================
132
+ // LOCAL DEV (must keep working)
133
+ // ============================================================================
134
+ describe("getFrontendHost — local development", () => {
135
+ it("returns http://localhost:3000 for hostname=localhost", () => {
136
+ setLocation({
137
+ protocol: "http:",
138
+ hostname: "localhost",
139
+ host: "localhost:3000",
140
+ port: "3000",
141
+ });
142
+ expect(getFrontendHost()).toBe("http://localhost:3000");
143
+ });
144
+ it("returns http://127.0.0.1:3000 for hostname=127.0.0.1", () => {
145
+ setLocation({
146
+ protocol: "http:",
147
+ hostname: "127.0.0.1",
148
+ host: "127.0.0.1:3000",
149
+ port: "3000",
150
+ });
151
+ expect(getFrontendHost()).toBe("http://127.0.0.1:3000");
152
+ });
153
+ it("returns the LAN-IP dev URL with :3000 for 192.168.x.x (mobile-on-same-wifi dev)", () => {
154
+ setLocation({
155
+ protocol: "http:",
156
+ hostname: "192.168.1.42",
157
+ host: "192.168.1.42:3000",
158
+ port: "3000",
159
+ });
160
+ expect(getFrontendHost()).toBe("http://192.168.1.42:3000");
161
+ });
162
+ });
163
+ // ============================================================================
164
+ // NGROK / TUNNELS (deployed-equivalent — should NOT get :3000)
165
+ // ============================================================================
166
+ describe("getFrontendHost — ngrok / public tunnels", () => {
167
+ it("does NOT append :3000 to an ngrok-free.dev tunnel hostname", () => {
168
+ // ngrok forwards port 443 publicly to the local :3000; the link Facebook
169
+ // crawls must hit the public 443, never the local 3000.
170
+ setLocation({
171
+ protocol: "https:",
172
+ hostname: "abc-123.ngrok-free.dev",
173
+ host: "abc-123.ngrok-free.dev",
174
+ port: "",
175
+ });
176
+ expect(getFrontendHost()).toBe("https://abc-123.ngrok-free.dev");
177
+ });
178
+ it("does NOT append :3000 to a .lhr.life tunnel", () => {
179
+ setLocation({
180
+ protocol: "https:",
181
+ hostname: "myapp.lhr.life",
182
+ host: "myapp.lhr.life",
183
+ port: "",
184
+ });
185
+ expect(getFrontendHost()).toBe("https://myapp.lhr.life");
186
+ });
187
+ });
188
+ // ============================================================================
189
+ // SHARE-URL ASSEMBLY (the exact concatenation SharePopup performs)
190
+ // ============================================================================
191
+ describe("getFrontendHost — composed share URL (SharePopup integration)", () => {
192
+ it("composes the canonical share URL Facebook can crawl", () => {
193
+ setLocation({
194
+ protocol: "https:",
195
+ hostname: "bash.community",
196
+ host: "bash.community",
197
+ port: "",
198
+ });
199
+ const host = getFrontendHost();
200
+ const path = "/bash/evt_123-empanada-fest-slc";
201
+ const fullShareUrl = host + path;
202
+ expect(fullShareUrl).toBe("https://bash.community/bash/evt_123-empanada-fest-slc");
203
+ // Hard guard — this is the literal regression we're preventing.
204
+ expect(fullShareUrl).not.toMatch(/bash\.community:3000/);
205
+ });
206
+ });
207
+ //# sourceMappingURL=getFrontendHost.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getFrontendHost.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/getFrontendHost.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=promoCodesUtils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promoCodesUtils.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/promoCodesUtils.test.ts"],"names":[],"mappings":""}