@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
@@ -345,4 +345,98 @@ export async function getRecentBlogPosts(
345
345
  });
346
346
 
347
347
  return posts as BlogPostExt[];
348
- }
348
+ }
349
+
350
+ export interface RelatedBlogPost {
351
+ id: string;
352
+ slug: string | null;
353
+ title: string;
354
+ excerpt: string | null;
355
+ coverImage: string | null;
356
+ readTime: number | null;
357
+ publishedAt: Date | null;
358
+ }
359
+
360
+ // Score candidate posts by (1) tag overlap with the source post, (2) shared
361
+ // category, then (3) recency. Drives the "Related posts" module that internal-
362
+ // links blog detail pages -- a major SEO lever per the backlink/SEO plan.
363
+ // Falls back to recent same-category posts when the source has no tags.
364
+ export async function getRelatedBlogPosts(
365
+ prisma: PrismaClient,
366
+ postId: string,
367
+ limit: number = 4
368
+ ): Promise<RelatedBlogPost[]> {
369
+ const source = await prisma.blogPost.findUnique({
370
+ where: { id: postId },
371
+ select: {
372
+ id: true,
373
+ categoryId: true,
374
+ tags: { select: { id: true } },
375
+ },
376
+ });
377
+ if (!source) {
378
+ return [];
379
+ }
380
+ const tagIds = source.tags.map((t) => t.id);
381
+
382
+ const orClauses: Prisma.BlogPostWhereInput[] = [];
383
+ if (tagIds.length > 0) {
384
+ orClauses.push({ tags: { some: { id: { in: tagIds } } } });
385
+ }
386
+ if (source.categoryId) {
387
+ orClauses.push({ categoryId: source.categoryId });
388
+ }
389
+
390
+ const where: Prisma.BlogPostWhereInput = {
391
+ id: { not: postId },
392
+ status: BlogStatus.Published,
393
+ publishedAt: { not: null },
394
+ ...(orClauses.length > 0 ? { OR: orClauses } : {}),
395
+ };
396
+
397
+ const candidates = await prisma.blogPost.findMany({
398
+ where,
399
+ select: {
400
+ id: true,
401
+ slug: true,
402
+ title: true,
403
+ excerpt: true,
404
+ coverImage: true,
405
+ readTime: true,
406
+ publishedAt: true,
407
+ categoryId: true,
408
+ tags: { select: { id: true } },
409
+ },
410
+ orderBy: { publishedAt: 'desc' },
411
+ take: limit * 4,
412
+ });
413
+
414
+ const scored = candidates.map((c) => {
415
+ const sharedTagCount = c.tags.filter((t) => tagIds.includes(t.id)).length;
416
+ const sharedCategory =
417
+ source.categoryId && c.categoryId === source.categoryId ? 1 : 0;
418
+ const recency = c.publishedAt ? c.publishedAt.getTime() : 0;
419
+ return { c, sharedTagCount, sharedCategory, recency };
420
+ });
421
+
422
+ scored.sort((a, b) => {
423
+ if (b.sharedTagCount !== a.sharedTagCount) {
424
+ return b.sharedTagCount - a.sharedTagCount;
425
+ }
426
+ if (b.sharedCategory !== a.sharedCategory) {
427
+ return b.sharedCategory - a.sharedCategory;
428
+ }
429
+ return b.recency - a.recency;
430
+ });
431
+
432
+ return scored.slice(0, limit).map(({ c }) => ({
433
+ id: c.id,
434
+ slug: c.slug,
435
+ title: c.title,
436
+ excerpt: c.excerpt,
437
+ coverImage: c.coverImage,
438
+ readTime: c.readTime,
439
+ publishedAt: c.publishedAt,
440
+ }));
441
+ }
442
+
@@ -0,0 +1,137 @@
1
+ /**
2
+ * USPS EDDM flat minimum: 6.125" × 11" (one dimension must be at least 11").
3
+ * Product standard used by Flyer Blast: 6.25" × 11" portrait.
4
+ */
5
+ export const FLYER_EDDM_WIDTH_IN = 6.25;
6
+ export const FLYER_EDDM_HEIGHT_IN = 11;
7
+ export const FLYER_EDDM_MIN_WIDTH_IN = 6.125;
8
+ export const FLYER_EDDM_MIN_HEIGHT_IN = 11;
9
+
10
+ /** USPS EDDM Retail per-piece postage (2026 — verify periodically). */
11
+ export const USPS_EDDM_RETAIL_PER_PIECE_CENTS = 24.7;
12
+
13
+ /**
14
+ * USPS EDDM Retail minimum mailpieces per mailing (saturation).
15
+ * Aligns with starter tier lower bound in `FLYER_PRICING_TIERS`.
16
+ */
17
+ export const USPS_EDDM_RETAIL_MIN_MAILPIECES = 200;
18
+
19
+ export type FlyerPricingTierId = "starter" | "neighborhood" | "district" | "metro";
20
+
21
+ export interface FlyerTierQuote {
22
+ tierId: FlyerPricingTierId;
23
+ label: string;
24
+ minHouseholds: number;
25
+ maxHouseholds: number;
26
+ hostPriceCents: number;
27
+ }
28
+
29
+ export const FLYER_PRICING_TIERS: FlyerTierQuote[] = [
30
+ {
31
+ tierId: "starter",
32
+ label: "Starter",
33
+ minHouseholds: 200,
34
+ maxHouseholds: 499,
35
+ hostPriceCents: 14900,
36
+ },
37
+ {
38
+ tierId: "neighborhood",
39
+ label: "Neighborhood",
40
+ minHouseholds: 500,
41
+ maxHouseholds: 1499,
42
+ hostPriceCents: 32900,
43
+ },
44
+ {
45
+ tierId: "district",
46
+ label: "District",
47
+ minHouseholds: 1500,
48
+ maxHouseholds: 3999,
49
+ hostPriceCents: 69900,
50
+ },
51
+ {
52
+ tierId: "metro",
53
+ label: "Metro",
54
+ minHouseholds: 4000,
55
+ maxHouseholds: 9999,
56
+ hostPriceCents: 139900,
57
+ },
58
+ ];
59
+
60
+ /** Default sponsor slot list prices (Utah / intermountain — cents). */
61
+ export const FLYER_SPONSOR_DEFAULT_FEATURED_CENTS = 10000;
62
+ export const FLYER_SPONSOR_DEFAULT_SUPPORTING_CENTS = 5000;
63
+
64
+ export function isEddmFlatSizeCompliant(widthIn: number, heightIn: number): boolean {
65
+ const long = Math.max(widthIn, heightIn);
66
+ const short = Math.min(widthIn, heightIn);
67
+ return long >= FLYER_EDDM_MIN_HEIGHT_IN && short >= FLYER_EDDM_MIN_WIDTH_IN;
68
+ }
69
+
70
+ /**
71
+ * Map total selected households to a pricing tier. Returns null if below minimum (200 for EDDM Retail).
72
+ */
73
+ export function quoteFlyerTierForHouseholds(
74
+ totalHouseholds: number
75
+ ): FlyerTierQuote | null {
76
+ if (totalHouseholds < FLYER_PRICING_TIERS[0].minHouseholds) {
77
+ return null;
78
+ }
79
+ for (const tier of FLYER_PRICING_TIERS) {
80
+ if (
81
+ totalHouseholds >= tier.minHouseholds &&
82
+ totalHouseholds <= tier.maxHouseholds
83
+ ) {
84
+ return tier;
85
+ }
86
+ }
87
+ // Above metro max — use metro as ceiling for MVP
88
+ const last = FLYER_PRICING_TIERS[FLYER_PRICING_TIERS.length - 1];
89
+ if (totalHouseholds > last.maxHouseholds) {
90
+ return last;
91
+ }
92
+ return null;
93
+ }
94
+
95
+ export function computeHostTotalAfterSponsors(
96
+ printPostageCostCents: number,
97
+ sponsorRevenueCents: number
98
+ ): number {
99
+ return Math.max(0, printPostageCostCents - sponsorRevenueCents);
100
+ }
101
+
102
+ const EARTH_RADIUS_MI = 3958.8;
103
+
104
+ /** Haversine distance in miles between two WGS84 points. */
105
+ export function distanceMiles(
106
+ lat1: number,
107
+ lng1: number,
108
+ lat2: number,
109
+ lng2: number
110
+ ): number {
111
+ const rlat1 = (lat1 * Math.PI) / 180;
112
+ const rlat2 = (lat2 * Math.PI) / 180;
113
+ const dLat = rlat2 - rlat1;
114
+ const dLng = ((lng2 - lng1) * Math.PI) / 180;
115
+ const a =
116
+ Math.sin(dLat / 2) * Math.sin(dLat / 2) +
117
+ Math.cos(rlat1) * Math.cos(rlat2) * Math.sin(dLng / 2) * Math.sin(dLng / 2);
118
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
119
+ return EARTH_RADIUS_MI * c;
120
+ }
121
+
122
+ /** URL path for the public QR scan endpoint (prepend API origin in clients). */
123
+ export function flyerQrScanPath(campaignQrSlug: string): string {
124
+ return `/api/flyer/qr-scan?c=${encodeURIComponent(campaignQrSlug)}`;
125
+ }
126
+
127
+ /**
128
+ * Short slug for campaign QR (avoid ambiguous chars); unique enforced in DB.
129
+ */
130
+ export function generateFlyerCampaignSlug(): string {
131
+ const alphabet = "23456789abcdefghjkmnpqrstuvwxyz";
132
+ let out = "fb";
133
+ for (let i = 0; i < 12; i++) {
134
+ out += alphabet[Math.floor(Math.random() * alphabet.length)];
135
+ }
136
+ return out;
137
+ }
@@ -1,4 +1,3 @@
1
- import { isProduction } from "./apiUtils.js";
2
1
  import { BASH_DETAIL_URL, STRIPE_CHECKOUT_TIMEOUT_MS, STRIPE_REDIRECT_TIMEOUT_MS } from "../definitions.js";
3
2
  import { generateBashDetailUrl } from "./slugUtils.js";
4
3
 
@@ -17,11 +16,34 @@ function isCapacitorNativePlatform(): boolean {
17
16
  return cap?.isNativePlatform?.() === true;
18
17
  }
19
18
 
19
+ /** True when the current page is being served from a developer's local machine (not ngrok, not deployed). */
20
+ function isLocalDevHostname(hostname: string): boolean {
21
+ return (
22
+ hostname === "localhost" ||
23
+ hostname === "127.0.0.1" ||
24
+ hostname.startsWith("192.168.")
25
+ );
26
+ }
27
+
28
+ /**
29
+ * Canonical frontend origin for share links, embeds, QR codes, etc.
30
+ *
31
+ * Browser-hostname-driven (NOT env-var driven) so the answer is correct in any
32
+ * deployed environment — including ones where `process.env.ENVIRONMENT` /
33
+ * `BASH_ENV` aren't inlined into the bundle (Vite only inlines REACT_APP_* /
34
+ * VITE_* / NODE_ENV / PUBLIC_URL). Previously this depended on `isProduction()`
35
+ * and produced URLs like `https://bash.community:3000/bash/...` in production,
36
+ * which broke Facebook/iMessage previews because `:3000` isn't publicly reachable.
37
+ */
20
38
  export function getFrontendHost(): string {
21
- const host = isProduction()
22
- ? `${window.location.protocol}//${window.location.host}`
23
- : `${window.location.protocol}//${window.location.hostname}:3000`;
24
- return host;
39
+ if (typeof window === "undefined") {
40
+ return "";
41
+ }
42
+ const { protocol, hostname, host, port } = window.location;
43
+ if (isLocalDevHostname(hostname)) {
44
+ return `${protocol}//${hostname}:${port || "3000"}`;
45
+ }
46
+ return `${protocol}//${host}`;
25
47
  }
26
48
 
27
49
  export function getBackendHost(): string {
@@ -1 +1,41 @@
1
+ import { Gender } from "@prisma/client";
2
+
1
3
  export const MAX_USER_NAME_CHANGES = 3 as const;
4
+
5
+ /**
6
+ * Minimum shape needed by `getDisplayGender` — accepts any object that has the
7
+ * two relevant columns (e.g. `User`, `UserExt`, `PublicUser`, or a partial
8
+ * select). The free-text `genderIdentity` is the source of truth for *display*
9
+ * because it is user-self-described and may not map to a `Gender` enum value
10
+ * (e.g. "Non-binary", "Genderfluid").
11
+ */
12
+ export interface GenderDisplayUser {
13
+ gender?: Gender | null;
14
+ genderIdentity?: string | null;
15
+ }
16
+
17
+ /**
18
+ * Resolve the gender label that should be **displayed in the UI** (profile
19
+ * header, badges, organizer cards, etc.) for a given user.
20
+ *
21
+ * Precedence:
22
+ * 1. `genderIdentity` (free-text self-description) when present and
23
+ * non-blank — wins over the enum so the user sees their own words.
24
+ * 2. `gender` (enum) when present.
25
+ * 3. `null` when neither is set.
26
+ *
27
+ * IMPORTANT: This helper is for **display only**. Analytics, demographics
28
+ * cohorts, target-audience matching, and anything that needs a stable
29
+ * categorical value should keep reading `user.gender` directly. Adding a
30
+ * comment at those sites (e.g. "intentional: analytics uses raw enum") makes
31
+ * the divergence explicit.
32
+ *
33
+ * @returns the user-facing string (free-text or enum value), or `null` if no
34
+ * gender info has been provided.
35
+ */
36
+ export function getDisplayGender(user: GenderDisplayUser): string | null {
37
+ const trimmedIdentity = user.genderIdentity?.trim();
38
+ if (trimmedIdentity) return trimmedIdentity;
39
+ if (user.gender) return user.gender;
40
+ return null;
41
+ }
@@ -1,6 +0,0 @@
1
- /**
2
- * Tests for cancellation refund resolution (single source of truth:
3
- * SERVICE_CANCELLATION_POLICY_DATA).
4
- */
5
- export {};
6
- //# sourceMappingURL=cancellationPolicyRefundResolver.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cancellationPolicyRefundResolver.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/cancellationPolicyRefundResolver.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -1,104 +0,0 @@
1
- /**
2
- * Tests for cancellation refund resolution (single source of truth:
3
- * SERVICE_CANCELLATION_POLICY_DATA).
4
- */
5
- import { ServiceCancellationPolicy } from "@prisma/client";
6
- import { cancellationRuleThresholdHours, getMatchingCancellationRefundRule, hoursUntilEventStart, resolveCancellationRefundFraction, } from "../service/cancellationPolicyRefundResolver.js";
7
- describe("cancellationPolicyRefundResolver", () => {
8
- const fixedNow = new Date("2026-01-01T12:00:00.000Z");
9
- describe("hoursUntilEventStart", () => {
10
- test("returns 0 when event is in the past", () => {
11
- expect(hoursUntilEventStart(new Date("2025-12-01T12:00:00.000Z"), fixedNow)).toBe(0);
12
- });
13
- test("returns positive hours until start", () => {
14
- const start = new Date("2026-01-03T12:00:00.000Z");
15
- expect(hoursUntilEventStart(start, fixedNow)).toBe(48);
16
- });
17
- });
18
- describe("cancellationRuleThresholdHours", () => {
19
- test("converts days to hours", () => {
20
- expect(cancellationRuleThresholdHours({ days: 7, refundPercentage: 100 })).toBe(168);
21
- });
22
- test("uses hours when set", () => {
23
- expect(cancellationRuleThresholdHours({ hours: 24, refundPercentage: 50 })).toBe(24);
24
- });
25
- });
26
- describe("VendorStandard (14d full, 3d partial)", () => {
27
- test("20 days before: 100%", () => {
28
- const eventStart = new Date("2026-01-21T12:00:00.000Z");
29
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VendorStandard, {
30
- eventStart,
31
- now: fixedNow,
32
- })).toBe(1);
33
- });
34
- test("10 days before: 50%", () => {
35
- const eventStart = new Date("2026-01-11T12:00:00.000Z");
36
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VendorStandard, {
37
- eventStart,
38
- now: fixedNow,
39
- })).toBe(0.5);
40
- });
41
- test("2 days before: 0%", () => {
42
- const eventStart = new Date("2026-01-03T12:00:00.000Z");
43
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VendorStandard, {
44
- eventStart,
45
- now: fixedNow,
46
- })).toBe(0);
47
- });
48
- });
49
- describe("VendorFlexible (7d full, 24h partial)", () => {
50
- test("8 days before: 100%", () => {
51
- const eventStart = new Date("2026-01-09T12:00:00.000Z");
52
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VendorFlexible, {
53
- eventStart,
54
- now: fixedNow,
55
- })).toBe(1);
56
- });
57
- test("5 days before: 50%", () => {
58
- const eventStart = new Date("2026-01-06T12:00:00.000Z");
59
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VendorFlexible, {
60
- eventStart,
61
- now: fixedNow,
62
- })).toBe(0.5);
63
- });
64
- test("12 hours before: 0%", () => {
65
- const eventStart = new Date("2026-01-02T00:00:00.000Z");
66
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VendorFlexible, {
67
- eventStart,
68
- now: fixedNow,
69
- })).toBe(0);
70
- });
71
- test("6d 23h before (~167h): still 50% tier, not full 7d", () => {
72
- const eventStart = new Date("2026-01-08T11:00:00.000Z");
73
- expect(hoursUntilEventStart(eventStart, fixedNow)).toBeCloseTo(167, 0);
74
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VendorFlexible, {
75
- eventStart,
76
- now: fixedNow,
77
- })).toBe(0.5);
78
- });
79
- });
80
- describe("VeryFlexible (24h full only)", () => {
81
- test("2 days before: 100%", () => {
82
- const eventStart = new Date("2026-01-03T12:00:00.000Z");
83
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VeryFlexible, {
84
- eventStart,
85
- now: fixedNow,
86
- })).toBe(1);
87
- });
88
- test("12 hours before: 0%", () => {
89
- const eventStart = new Date("2026-01-02T00:00:00.000Z");
90
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VeryFlexible, {
91
- eventStart,
92
- now: fixedNow,
93
- })).toBe(0);
94
- });
95
- });
96
- describe("getMatchingCancellationRefundRule", () => {
97
- test("returns the winning tier rule object", () => {
98
- const eventStart = new Date("2026-01-11T12:00:00.000Z");
99
- const rule = getMatchingCancellationRefundRule(ServiceCancellationPolicy.VendorStandard, { eventStart, now: fixedNow });
100
- expect(rule).toEqual({ days: 3, refundPercentage: 50 });
101
- });
102
- });
103
- });
104
- //# sourceMappingURL=cancellationPolicyRefundResolver.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cancellationPolicyRefundResolver.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/cancellationPolicyRefundResolver.test.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- //# sourceMappingURL=serviceRateDBUtils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"serviceRateDBUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/service/serviceRateDBUtils.ts"],"names":[],"mappings":""}
@@ -1,159 +0,0 @@
1
- "use strict";
2
- // import { ServiceRate } from "@prisma/client";
3
- // import { ApiResult } from "../../definitions";
4
- // import { ServiceSpecialRatesExt } from "../../extendedSchemas";
5
- // import { convertDollarsToCents, convertCentsToDollars } from "../paymentUtils";
6
- // export function specialRateConvertToDb(
7
- // specialRate: Partial<ServiceSpecialRatesExt>
8
- // ): Partial<ServiceSpecialRatesExt> {
9
- // return specialRateConvertRatesToDB(
10
- // specialRateConvertDateStringsToDb(structuredClone(specialRate))
11
- // );
12
- // }
13
- // export function specialRateConvertFromDb(
14
- // specialRate: ServiceSpecialRatesExt
15
- // ): ServiceSpecialRatesExt {
16
- // // console.log(`specialRateConvertFromDb`);
17
- // return specialRateConvertRatesFromDB(
18
- // specialRateConvertDateStringsFromDb(specialRate)
19
- // );
20
- // }
21
- // function specialRateConvertDateStringsToDb(
22
- // specialRate: Partial<ServiceSpecialRatesExt>
23
- // ): Partial<ServiceSpecialRatesExt> {
24
- // // const oldStart = new Date(specialRate.startDate!);
25
- // // const oldEnd = new Date(specialRate.endDate!);
26
- // // specialRate.startDate = dateTimeLocalToUTCDate(specialRate.startDate!);
27
- // // specialRate.endDate = dateTimeLocalToUTCDate(specialRate.endDate!);
28
- // // specialRate.startDate = normalizeDate(specialRate.startDate!);
29
- // // specialRate.endDate = normalizeDate(specialRate.endDate!);
30
- // // const localStart = dateTimeLocalFromUTCDate(new Date(specialRate.startDate!));
31
- // // const localEnd = dateTimeLocalFromUTCDate(new Date(specialRate.endDate!));
32
- // // console.log(
33
- // // `specialRateConvertDateStringsToDb oldStart: ${oldStart?.toString()}`
34
- // // );
35
- // // console.log(
36
- // // `specialRateConvertDateStringsToDb oldEnd: ${oldEnd?.toString()}`
37
- // // );
38
- // // console.log(
39
- // // `specialRateConvertDateStringsToDb specialRate.startDate: ${specialRate.startDate?.toString()}`
40
- // // );
41
- // // console.log(
42
- // // `specialRateConvertDateStringsToDb specialRate.endDate: ${specialRate.endDate?.toString()}`
43
- // // );
44
- // // console.log(
45
- // // `specialRateConvertDateStringsToDb localStart: ${localStart?.toString()}`
46
- // // );
47
- // // console.log(
48
- // // `specialRateConvertDateStringsToDb localEnd: ${localEnd?.toString()}`
49
- // // );
50
- // // const a = 3 + 4;
51
- // // specialRate.startDate = specialRate.startDate.toISOString();
52
- // // specialRate.endDate = specialRate.endDate.toISOString();
53
- // // return specialRate;
54
- // return specialRate;
55
- // }
56
- // function specialRateConvertDateStringsFromDb(
57
- // specialRate: ServiceSpecialRatesExt
58
- // ): ServiceSpecialRatesExt {
59
- // // specialRate.startDate = localDateToUTC(new Date(specialRate.startDate));
60
- // // specialRate.endDate = localDateToUTC(new Date(specialRate.endDate));
61
- // //this shouldnt even be necessary as javascript dates are always stored in UTC timezone (they lack timezone information)
62
- // // specialRate.startDate = ensureDateTimeIsLocalDateTime(
63
- // // new Date(specialRate.startDate)
64
- // // ).toDate();
65
- // // specialRate.endDate = ensureDateTimeIsLocalDateTime(
66
- // // new Date(specialRate.endDate)
67
- // // ).toDate();
68
- // specialRate.startDate = new Date(specialRate.startDate);
69
- // specialRate.endDate = new Date(specialRate.endDate);
70
- // // const oldStart = new Date(specialRate.startDate!);
71
- // // const oldEnd = new Date(specialRate.endDate!);
72
- // // specialRate.startDate = dateTimeLocalFromUTCDate(
73
- // // new Date(specialRate.startDate)
74
- // // );
75
- // // specialRate.endDate = dateTimeLocalFromUTCDate(new Date(specialRate.endDate));
76
- // // // specialRate.startDate = ensureDateTimeIsLocalDateTime(
77
- // // // new Date(specialRate.startDate)
78
- // // // ).toDate();
79
- // // // specialRate.endDate = ensureDateTimeIsLocalDateTime(
80
- // // // new Date(specialRate.endDate)
81
- // // // ).toDate();
82
- // // // specialRate.startDate = localDateToUTC(new Date(specialRate.startDate));
83
- // // // specialRate.endDate = localDateToUTC(new Date(specialRate.endDate));
84
- // // const date2 = normalizeDate(specialRate.startDate)!;
85
- // // const date3 = normalizeDate(specialRate.endDate)!;
86
- // // const a = 3 + 4;
87
- // return specialRate;
88
- // }
89
- // function specialRateConvertRatesToDB(
90
- // specialRate: Partial<ServiceSpecialRatesExt>
91
- // ): Partial<ServiceSpecialRatesExt> {
92
- // if (specialRate.serviceRate) {
93
- // specialRate.serviceRate = generalRatesConvertRatesToDB(
94
- // specialRate.serviceRate
95
- // ) as ServiceRate;
96
- // }
97
- // return specialRate;
98
- // }
99
- // function specialRateConvertRatesFromDB(
100
- // specialRate: ServiceSpecialRatesExt
101
- // ): ServiceSpecialRatesExt {
102
- // if (specialRate.serviceRate) {
103
- // specialRate.serviceRate = generalRatesConvertRatesFromDB(
104
- // specialRate.serviceRate
105
- // );
106
- // }
107
- // return specialRate;
108
- // }
109
- // export function generalRatesConvertRatesToDB(
110
- // generalRate: ServiceRate
111
- // ): ServiceRate {
112
- // return generalRatesConvertRatesToDBHelper(structuredClone(generalRate));
113
- // }
114
- // export function generalRatesConvertRatesToDBHelper(
115
- // generalRate: ServiceRate
116
- // ): ServiceRate {
117
- // // console.log(
118
- // // `generalRatesConvertRatesToDBHelper A: ${JSON.stringify(generalRate)}`
119
- // // );
120
- // if (generalRate?.dailyRate) {
121
- // generalRate.dailyRate = convertDollarsToCents(generalRate.dailyRate);
122
- // }
123
- // if (generalRate?.hourlyRate) {
124
- // generalRate.hourlyRate = convertDollarsToCents(generalRate.hourlyRate);
125
- // }
126
- // if (generalRate?.cleaningFeePerBooking) {
127
- // generalRate.cleaningFeePerBooking = convertDollarsToCents(
128
- // generalRate.cleaningFeePerBooking
129
- // );
130
- // }
131
- // // console.log(
132
- // // `generalRatesConvertRatesToDBHelper B: ${JSON.stringify(generalRate)}`
133
- // // );
134
- // return generalRate;
135
- // }
136
- // export function generalRatesConvertRatesFromDB(
137
- // generalRate: ServiceRate
138
- // ): ServiceRate {
139
- // // console.log(
140
- // // `generalRatesConvertRatesFromDB A: ${JSON.stringify(generalRate)}`
141
- // // );
142
- // // console.log(`specialRateConvertRatesFromDB`);
143
- // if (generalRate?.dailyRate) {
144
- // generalRate.dailyRate = convertCentsToDollars(generalRate.dailyRate);
145
- // }
146
- // if (generalRate?.hourlyRate) {
147
- // generalRate.hourlyRate = convertCentsToDollars(generalRate.hourlyRate);
148
- // }
149
- // if (generalRate?.cleaningFeePerBooking) {
150
- // generalRate.cleaningFeePerBooking = convertCentsToDollars(
151
- // generalRate.cleaningFeePerBooking
152
- // );
153
- // }
154
- // // console.log(
155
- // // `generalRatesConvertRatesFromDB B: ${JSON.stringify(generalRate)}`
156
- // // );
157
- // return generalRate;
158
- // }
159
- //# sourceMappingURL=serviceRateDBUtils.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"serviceRateDBUtils.js","sourceRoot":"","sources":["../../../src/utils/service/serviceRateDBUtils.ts"],"names":[],"mappings":";AAAA,gDAAgD;AAChD,iDAAiD;AACjD,kEAAkE;AAClE,kFAAkF;AAElF,0CAA0C;AAC1C,iDAAiD;AACjD,uCAAuC;AACvC,wCAAwC;AACxC,sEAAsE;AACtE,OAAO;AACP,IAAI;AAEJ,4CAA4C;AAC5C,wCAAwC;AACxC,8BAA8B;AAC9B,gDAAgD;AAChD,0CAA0C;AAC1C,uDAAuD;AACvD,OAAO;AACP,IAAI;AAEJ,8CAA8C;AAC9C,iDAAiD;AACjD,uCAAuC;AACvC,0DAA0D;AAC1D,sDAAsD;AAEtD,+EAA+E;AAC/E,2EAA2E;AAC3E,sEAAsE;AACtE,kEAAkE;AAElE,sFAAsF;AACtF,kFAAkF;AAElF,oBAAoB;AACpB,+EAA+E;AAC/E,UAAU;AACV,oBAAoB;AACpB,2EAA2E;AAC3E,UAAU;AACV,oBAAoB;AACpB,yGAAyG;AACzG,UAAU;AACV,oBAAoB;AACpB,qGAAqG;AACrG,UAAU;AACV,oBAAoB;AACpB,mFAAmF;AACnF,UAAU;AACV,oBAAoB;AACpB,+EAA+E;AAC/E,UAAU;AAEV,wBAAwB;AAExB,oEAAoE;AACpE,gEAAgE;AAChE,2BAA2B;AAC3B,wBAAwB;AACxB,IAAI;AAEJ,gDAAgD;AAChD,wCAAwC;AACxC,8BAA8B;AAC9B,gFAAgF;AAChF,4EAA4E;AAE5E,6HAA6H;AAC7H,8DAA8D;AAC9D,yCAAyC;AACzC,mBAAmB;AACnB,4DAA4D;AAC5D,uCAAuC;AACvC,mBAAmB;AAEnB,6DAA6D;AAC7D,yDAAyD;AAEzD,0DAA0D;AAC1D,sDAAsD;AAEtD,yDAAyD;AACzD,yCAAyC;AACzC,UAAU;AACV,sFAAsF;AAEtF,iEAAiE;AACjE,4CAA4C;AAC5C,sBAAsB;AACtB,+DAA+D;AAC/D,0CAA0C;AAC1C,sBAAsB;AAEtB,mFAAmF;AACnF,+EAA+E;AAE/E,4DAA4D;AAC5D,0DAA0D;AAE1D,wBAAwB;AACxB,wBAAwB;AACxB,IAAI;AAEJ,wCAAwC;AACxC,iDAAiD;AACjD,uCAAuC;AACvC,mCAAmC;AACnC,8DAA8D;AAC9D,gCAAgC;AAChC,wBAAwB;AACxB,MAAM;AACN,wBAAwB;AACxB,IAAI;AAEJ,0CAA0C;AAC1C,wCAAwC;AACxC,8BAA8B;AAC9B,mCAAmC;AACnC,gEAAgE;AAChE,gCAAgC;AAChC,SAAS;AACT,MAAM;AACN,wBAAwB;AACxB,IAAI;AAEJ,gDAAgD;AAChD,6BAA6B;AAC7B,mBAAmB;AACnB,6EAA6E;AAC7E,IAAI;AAEJ,sDAAsD;AACtD,6BAA6B;AAC7B,mBAAmB;AACnB,sBAAsB;AACtB,kFAAkF;AAClF,YAAY;AACZ,kCAAkC;AAClC,4EAA4E;AAC5E,MAAM;AACN,mCAAmC;AACnC,8EAA8E;AAC9E,MAAM;AACN,8CAA8C;AAC9C,iEAAiE;AACjE,0CAA0C;AAC1C,SAAS;AACT,MAAM;AACN,sBAAsB;AACtB,kFAAkF;AAClF,YAAY;AACZ,wBAAwB;AACxB,IAAI;AAEJ,kDAAkD;AAClD,6BAA6B;AAC7B,mBAAmB;AACnB,sBAAsB;AACtB,8EAA8E;AAC9E,YAAY;AACZ,qDAAqD;AACrD,kCAAkC;AAClC,4EAA4E;AAC5E,MAAM;AACN,mCAAmC;AACnC,8EAA8E;AAC9E,MAAM;AACN,8CAA8C;AAC9C,iEAAiE;AACjE,0CAA0C;AAC1C,SAAS;AACT,MAAM;AACN,sBAAsB;AACtB,8EAA8E;AAC9E,YAAY;AACZ,wBAAwB;AACxB,IAAI"}
@@ -1,53 +0,0 @@
1
- -- Migration: Create BashAvailability table and BashAvailabilityWindow enum
2
- -- Required for the "Who's going out?" / LookingForSomethingSection feature.
3
- -- Run this against QA and prod before (or alongside) deploying the API code.
4
-
5
- DO $$ BEGIN
6
-
7
- -- 1. Create the enum type if it doesn't exist
8
- IF NOT EXISTS (
9
- SELECT 1 FROM pg_type WHERE typname = 'BashAvailabilityWindow'
10
- ) THEN
11
- CREATE TYPE "BashAvailabilityWindow" AS ENUM (
12
- 'Now',
13
- 'Tonight',
14
- 'ThisWeekend',
15
- 'Custom'
16
- );
17
- RAISE NOTICE 'Created enum BashAvailabilityWindow';
18
- ELSE
19
- RAISE NOTICE 'Enum BashAvailabilityWindow already exists — skipping';
20
- END IF;
21
-
22
- -- 2. Create the BashAvailability table if it doesn't exist
23
- IF NOT EXISTS (
24
- SELECT 1 FROM information_schema.tables
25
- WHERE table_name = 'BashAvailability'
26
- ) THEN
27
- CREATE TABLE "BashAvailability" (
28
- "id" TEXT NOT NULL,
29
- "userId" TEXT NOT NULL,
30
- "window" "BashAvailabilityWindow" NOT NULL,
31
- "city" TEXT NOT NULL,
32
- "state" TEXT,
33
- "customAt" TIMESTAMP(3),
34
- "expiresAt" TIMESTAMP(3) NOT NULL,
35
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT NOW(),
36
- "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT NOW(),
37
-
38
- CONSTRAINT "BashAvailability_pkey" PRIMARY KEY ("id"),
39
- CONSTRAINT "BashAvailability_userId_key" UNIQUE ("userId"),
40
- CONSTRAINT "BashAvailability_userId_fkey"
41
- FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE
42
- );
43
-
44
- -- Composite index used by GET /availability?city=...&window=...
45
- CREATE INDEX "BashAvailability_city_window_expiresAt_idx"
46
- ON "BashAvailability" ("city", "window", "expiresAt");
47
-
48
- RAISE NOTICE 'Created table BashAvailability';
49
- ELSE
50
- RAISE NOTICE 'Table BashAvailability already exists — skipping';
51
- END IF;
52
-
53
- END $$;