@bash-app/bash-common 30.403.0 → 30.408.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.
@@ -15,6 +15,16 @@ import {
15
15
  COMPLEMENTARY_SERVICE_TYPE,
16
16
  complementaryServiceTypeFor,
17
17
  ServiceTypes,
18
+ isIntroIntentId,
19
+ INTRO_INTENT_OPTIONS,
20
+ isLocusOfControlId,
21
+ LOCUS_OF_CONTROL_OPTIONS,
22
+ hasSensingIntuitionOverlap,
23
+ personalityOverlapHint,
24
+ normalizePersonalityType,
25
+ isInterestExpressionMode,
26
+ sharedIntroIntentHint,
27
+ locusOfControlShortLabel,
18
28
  } from "../definitions.js";
19
29
 
20
30
  describe("definitions helpers", () => {
@@ -183,4 +193,44 @@ describe("definitions helpers", () => {
183
193
  ).toBeUndefined();
184
194
  });
185
195
  });
196
+
197
+ describe("Intros intents", () => {
198
+ test("accepts the three platonic/professional intent ids", () => {
199
+ expect(INTRO_INTENT_OPTIONS.map((option) => option.id)).toEqual([
200
+ "friends",
201
+ "professional",
202
+ "mentorship",
203
+ ]);
204
+ expect(isIntroIntentId("friends")).toBe(true);
205
+ expect(isIntroIntentId("dating")).toBe(false);
206
+ });
207
+
208
+ test("normalizes personality type and detects S/N overlap", () => {
209
+ expect(normalizePersonalityType("enfp!")).toBe("ENFP");
210
+ expect(normalizePersonalityType(" ")).toBeNull();
211
+ expect(hasSensingIntuitionOverlap("ENFP", "INTJ")).toBe(true);
212
+ expect(hasSensingIntuitionOverlap("ESFP", "INTJ")).toBe(false);
213
+ expect(personalityOverlapHint("ENFP", "INTJ")).toBe(
214
+ "You both picked N — good icebreaker."
215
+ );
216
+ expect(isLocusOfControlId("internal")).toBe(true);
217
+ expect(isLocusOfControlId("random")).toBe(false);
218
+ expect(LOCUS_OF_CONTROL_OPTIONS.map((option) => option.id)).toEqual([
219
+ "internal",
220
+ "external",
221
+ "mixed",
222
+ ]);
223
+ expect(isInterestExpressionMode("named")).toBe(true);
224
+ expect(isInterestExpressionMode("crush")).toBe(false);
225
+ });
226
+
227
+ test("shared intro intent and locus labels stay conversational", () => {
228
+ expect(
229
+ sharedIntroIntentHint(["friends", "mentorship"], ["professional", "friends"])
230
+ ).toBe("You're both here for making friends.");
231
+ expect(sharedIntroIntentHint(["friends"], ["professional"])).toBeNull();
232
+ expect(locusOfControlShortLabel("internal")).toBe("Internal");
233
+ expect(locusOfControlShortLabel("nope")).toBeNull();
234
+ });
235
+ });
186
236
  });
@@ -4,7 +4,6 @@ import {
4
4
  Contact,
5
5
  DietaryOption,
6
6
  EntertainmentServiceType,
7
- IncomeRange,
8
7
  JudgingType,
9
8
  MembershipTier,
10
9
  MusicGenreType,
@@ -17,6 +16,7 @@ import {
17
16
  } from "@prisma/client";
18
17
  import type {
19
18
  AgePolicy as AgePolicyEnum,
19
+ IncomeRange,
20
20
  BashPassTier,
21
21
  BashEventDressTags,
22
22
  BashEventVibeTags,
@@ -134,6 +134,109 @@ export const AgePolicy = {
134
134
 
135
135
  export type AgePolicy = AgePolicyEnum;
136
136
 
137
+ export const INTRO_INTENT_OPTIONS = [
138
+ { id: "friends", label: "Making friends" },
139
+ { id: "professional", label: "Professional networking" },
140
+ { id: "mentorship", label: "Finding a mentor or mentee" },
141
+ ] as const;
142
+
143
+ export type IntroIntentId = (typeof INTRO_INTENT_OPTIONS)[number]["id"];
144
+
145
+ export const INTRO_INTENT_IDS: readonly IntroIntentId[] =
146
+ INTRO_INTENT_OPTIONS.map((option) => option.id);
147
+
148
+ export function isIntroIntentId(value: string): value is IntroIntentId {
149
+ return (INTRO_INTENT_IDS as readonly string[]).includes(value);
150
+ }
151
+
152
+ export const LOCUS_OF_CONTROL_OPTIONS = [
153
+ { id: "internal", label: "Internal — I shape my own outcomes" },
154
+ { id: "external", label: "External — circumstances shape outcomes" },
155
+ { id: "mixed", label: "Mixed — bit of both" },
156
+ ] as const;
157
+
158
+ export type LocusOfControlId = (typeof LOCUS_OF_CONTROL_OPTIONS)[number]["id"];
159
+
160
+ export const LOCUS_OF_CONTROL_IDS: readonly LocusOfControlId[] =
161
+ LOCUS_OF_CONTROL_OPTIONS.map((option) => option.id);
162
+
163
+ export function isLocusOfControlId(value: string): value is LocusOfControlId {
164
+ return (LOCUS_OF_CONTROL_IDS as readonly string[]).includes(value);
165
+ }
166
+
167
+ export const PERSONALITY_TYPE_MAX_LENGTH = 4;
168
+
169
+ export function normalizePersonalityType(
170
+ value: string | null | undefined
171
+ ): string | null {
172
+ if (!value) return null;
173
+ const normalized = value
174
+ .toUpperCase()
175
+ .replace(/[^A-Z]/g, "")
176
+ .slice(0, PERSONALITY_TYPE_MAX_LENGTH);
177
+ return normalized.length > 0 ? normalized : null;
178
+ }
179
+
180
+ /** True if two MBTI codes share the same second letter (S/N axis). */
181
+ export function hasSensingIntuitionOverlap(a: string, b: string): boolean {
182
+ return (
183
+ a.length >= 2 &&
184
+ b.length >= 2 &&
185
+ a[1].toUpperCase() === b[1].toUpperCase()
186
+ );
187
+ }
188
+
189
+ export function personalityOverlapHint(
190
+ a: string | null | undefined,
191
+ b: string | null | undefined
192
+ ): string | null {
193
+ if (!a || !b || !hasSensingIntuitionOverlap(a, b)) return null;
194
+ return `You both picked ${a[1].toUpperCase()} — good icebreaker.`;
195
+ }
196
+
197
+ export function sharedIntroIntentHint(
198
+ viewerIntents: readonly string[] | null | undefined,
199
+ guestIntents: readonly string[] | null | undefined
200
+ ): string | null {
201
+ if (!viewerIntents?.length || !guestIntents?.length) return null;
202
+ const guest = new Set(guestIntents);
203
+ const match = viewerIntents.find((id) => guest.has(id));
204
+ if (!match || !isIntroIntentId(match)) return null;
205
+ const label = INTRO_INTENT_OPTIONS.find((option) => option.id === match)?.label;
206
+ return label ? `You're both here for ${label.toLowerCase()}.` : null;
207
+ }
208
+
209
+ export function locusOfControlShortLabel(
210
+ id: string | null | undefined
211
+ ): string | null {
212
+ if (!id || !isLocusOfControlId(id)) return null;
213
+ if (id === "internal") return "Internal";
214
+ if (id === "external") return "External";
215
+ return "Mixed";
216
+ }
217
+
218
+ export const INTEREST_EXPRESSION_MODES = ["named", "anonymous"] as const;
219
+
220
+ export type InterestExpressionMode = (typeof INTEREST_EXPRESSION_MODES)[number];
221
+
222
+ export function isInterestExpressionMode(
223
+ value: string
224
+ ): value is InterestExpressionMode {
225
+ return (INTEREST_EXPRESSION_MODES as readonly string[]).includes(value);
226
+ }
227
+
228
+ export const INTROS_DISCLAIMER =
229
+ "Intros connects you only with people who have also explicitly opted in. Bash does not conduct background checks, verify identities beyond basic account registration, or screen users for any purpose. This feature supports platonic and professional connections only — romantic or sexual contact is a violation of our Community Guidelines. Always meet at the event, in public. Use the Report button if anyone makes you uncomfortable. By turning on Intros, you agree that Bash is not responsible for interactions between users.";
230
+
231
+ export const INTROS_DISCLAIMER_SHORT =
232
+ "Bash does not background-check users. Meet at the event, in public. Romantic or sexual contact is prohibited. Bash is not responsible for interactions between users.";
233
+
234
+ /** @deprecated Use INTROS_DISCLAIMER */
235
+ export const SCENE_INTROS_DISCLAIMER = INTROS_DISCLAIMER;
236
+
237
+ /** @deprecated Use INTROS_DISCLAIMER_SHORT */
238
+ export const SCENE_INTROS_DISCLAIMER_SHORT = INTROS_DISCLAIMER_SHORT;
239
+
137
240
  export const PASSWORD_MIN_LENGTH = 8 as const;
138
241
  export const PASSWORD_REQUIREMENTS_REGEX = new RegExp(
139
242
  String.raw`^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&^#])[A-Za-z\d@$!%*?&^#]{${PASSWORD_MIN_LENGTH},}$`
@@ -602,8 +705,8 @@ export const BASH_EVENT_WIZARD_WHAT_SUBSTEP = {
602
705
  } as const;
603
706
 
604
707
  export const BASH_EVENT_WIZARD_WHERE_SUBSTEP = {
605
- LOCATION: 0,
606
- UPLOAD_MEDIA: 1,
708
+ UPLOAD_MEDIA: 0,
709
+ LOCATION: 1,
607
710
  } as const;
608
711
 
609
712
  export const BASH_EVENT_WIZARD_WHEN_SUBSTEP = {
@@ -1366,6 +1469,8 @@ export interface StripeConfirmPaymentResult {
1366
1469
  ticketsSubtotalCents?: number;
1367
1470
  /** Actual amount charged to the guest (cents), including pass-through fees when applicable */
1368
1471
  totalPaidCents?: number;
1472
+ /** True when this is the buyer's first ticket purchase on Bash ever. */
1473
+ isFirstTicketPurchase?: boolean;
1369
1474
  }
1370
1475
 
1371
1476
  export interface IMediaUploadArgs {
@@ -1649,96 +1754,101 @@ export type BashEventTypeToStringType = {
1649
1754
  [key in BashEventType]: string;
1650
1755
  };
1651
1756
  export { BashEventType };
1757
+ /**
1758
+ * Keys are string literals (not `BashEventType.*`) so this module can load
1759
+ * when Jest's `@prisma/client` cache is briefly incomplete after resetModules
1760
+ * (see api/jest.setup.afterEnv.cjs). Same pattern as SERVICE_TYPE_LABELS.
1761
+ */
1652
1762
  export const BashEventTypeToString: BashEventTypeToStringType = {
1653
- [BashEventType.AfterParty]: "After-Party",
1654
- [BashEventType.AnniversaryCelebration]: "Anniversary Celebration",
1655
- [BashEventType.ArtAndCraftNight]: "Art and Craft Night",
1656
- [BashEventType.ArtExhibitOpening]: "Art Exhibit Opening",
1657
- [BashEventType.BBQCookout]: "BBQ Cookout",
1658
- [BashEventType.BabyShower]: "Baby Shower",
1659
- [BashEventType.BachelorParty]: "Bachelor Party",
1660
- [BashEventType.BacheloretteParty]: "Bachelorette Party",
1661
- [BashEventType.BeachParty]: "Beach Party",
1662
- [BashEventType.Birthday]: "Birthday",
1663
- [BashEventType.BoatParty]: "Boat Party",
1664
- [BashEventType.Bonfire]: "Bonfire",
1665
- [BashEventType.BridalShower]: "Bridal Shower",
1666
- [BashEventType.BrunchGathering]: "Brunch Gathering",
1667
- [BashEventType.CarShow]: "Car Show",
1668
- [BashEventType.Carnival]: "Carnival",
1669
- [BashEventType.CasinoNight]: "Casino Night",
1670
- [BashEventType.CasualMixer]: "Casual Mixer",
1671
- [BashEventType.CharityGala]: "Charity Gala",
1672
- [BashEventType.ChristmasParty]: "Christmas Party",
1673
- [BashEventType.ChurchEvent]: "Church Event",
1674
- [BashEventType.CitySponsored]: "City-Sponsored Event",
1675
- [BashEventType.CocktailParty]: "Cocktail Party",
1676
- [BashEventType.CollegeParty]: "College Party",
1677
- [BashEventType.ComedyShow]: "Comedy Show",
1678
- [BashEventType.Concert]: "Concert",
1679
- [BashEventType.Conference]: "Conference",
1680
- [BashEventType.Convention]: "Convention",
1681
- [BashEventType.CorporateEvent]: "Corporate Event",
1682
- [BashEventType.Cruise]: "Cruise",
1683
- [BashEventType.DanceParty]: "Dance Party",
1684
- [BashEventType.DiscoNight]: "Disco Night",
1685
- [BashEventType.EasterGathering]: "Easter Gathering",
1686
- [BashEventType.EngagementParty]: "Engagement Party",
1687
- [BashEventType.Fair]: "Fair",
1688
- [BashEventType.FarewellParty]: "Farewell Party",
1689
- [BashEventType.FashionShow]: "Fashion Show",
1690
- [BashEventType.Festival]: "Festival",
1691
- [BashEventType.FestivalFilm]: "Film Festival",
1692
- [BashEventType.FestivalFood]: "Food Festival",
1693
- [BashEventType.Fundraiser]: "Fundraiser",
1694
- [BashEventType.GameNight]: "Game Night",
1695
- [BashEventType.GamingEvent]: "Gaming Event",
1696
- [BashEventType.GardenParty]: "Garden Party",
1697
- [BashEventType.GraduationParty]: "Graduation Party",
1698
- [BashEventType.HalloweenParty]: "Halloween Party",
1699
- [BashEventType.HanukkahParty]: "Hanukkah Party",
1700
- [BashEventType.HolidayParty]: "Holiday Party",
1701
- [BashEventType.HouseParty]: "House Party",
1702
- [BashEventType.HousewarmingParty]: "Housewarming Party",
1703
- [BashEventType.KaraokeNight]: "Karaoke Night",
1704
- [BashEventType.LaunchParty]: "Launch Party",
1705
- [BashEventType.LuxuryRetreat]: "Luxury Retreat",
1706
- [BashEventType.MansionParty]: "Mansion Party",
1707
- [BashEventType.MardiGras]: "Mardi Gras",
1708
- [BashEventType.Memorial]: "Memorial",
1709
- [BashEventType.MovieNight]: "Movie Night",
1710
- [BashEventType.MoviePremiere]: "Movie Premiere",
1711
- [BashEventType.MusicFestival]: "Music Festival",
1712
- [BashEventType.NewYearsEveCelebration]: "New Year's Eve Celebration",
1713
- [BashEventType.OfficeParty]: "Office Party",
1714
- [BashEventType.OpenMicNight]: "Open Mic Night",
1715
- [BashEventType.OutdoorActivity]: "Outdoor Activity",
1716
- [BashEventType.Parade]: "Parade",
1717
- [BashEventType.Party]: "Party",
1718
- [BashEventType.PoolParty]: "Pool Party",
1719
- [BashEventType.Potluck]: "Potluck",
1720
- [BashEventType.PreParty]: "Pre-Party",
1721
- [BashEventType.ProfessionalNetworkingEvent]: "Professional Networking Event",
1722
- [BashEventType.PubCrawl]: "Pub Crawl",
1723
- [BashEventType.Rave]: "Rave",
1724
- [BashEventType.RetirementCelebration]: "Retirement Celebration",
1725
- [BashEventType.Reunion]: "Reunion",
1726
- [BashEventType.SchoolEvent]: "School Event",
1727
- [BashEventType.SocialClubEvent]: "Social Club Event",
1728
- [BashEventType.SportsWatchParty]: "Sports Watch Party",
1729
- [BashEventType.ThanksgivingDinner]: "Thanksgiving Dinner",
1730
- [BashEventType.ThemedDinnerParty]: "Themed Dinner Party",
1731
- [BashEventType.ThemedParty]: "Themed Party",
1732
- [BashEventType.ThemedPubCrawl]: "Themed Pub Crawl",
1733
- [BashEventType.Tournament]: "Tournament",
1734
- [BashEventType.TradeShow]: "Trade Show",
1735
- [BashEventType.TriviaNight]: "Trivia Night",
1736
- [BashEventType.ValentinesDayParty]: "Valentine's Day Party",
1737
- [BashEventType.WeddingReception]: "Wedding Reception",
1738
- [BashEventType.WelcomeHomeParty]: "Welcome Home Party",
1739
- [BashEventType.WineTastingEvent]: "Wine Tasting Event",
1740
- [BashEventType.Workshop]: "Workshop",
1741
- [BashEventType.Other]: "Other",
1763
+ AfterParty: "After-Party",
1764
+ AnniversaryCelebration: "Anniversary Celebration",
1765
+ ArtAndCraftNight: "Art and Craft Night",
1766
+ ArtExhibitOpening: "Art Exhibit Opening",
1767
+ BBQCookout: "BBQ Cookout",
1768
+ BabyShower: "Baby Shower",
1769
+ BachelorParty: "Bachelor Party",
1770
+ BacheloretteParty: "Bachelorette Party",
1771
+ BeachParty: "Beach Party",
1772
+ Birthday: "Birthday",
1773
+ BoatParty: "Boat Party",
1774
+ Bonfire: "Bonfire",
1775
+ BridalShower: "Bridal Shower",
1776
+ BrunchGathering: "Brunch Gathering",
1777
+ CarShow: "Car Show",
1778
+ Carnival: "Carnival",
1779
+ CasinoNight: "Casino Night",
1780
+ CasualMixer: "Casual Mixer",
1781
+ CharityGala: "Charity Gala",
1782
+ ChristmasParty: "Christmas Party",
1783
+ ChurchEvent: "Church Event",
1784
+ CitySponsored: "City-Sponsored Event",
1785
+ CocktailParty: "Cocktail Party",
1786
+ CollegeParty: "College Party",
1787
+ ComedyShow: "Comedy Show",
1788
+ Concert: "Concert",
1789
+ Conference: "Conference",
1790
+ Convention: "Convention",
1791
+ CorporateEvent: "Corporate Event",
1792
+ Cruise: "Cruise",
1793
+ DanceParty: "Dance Party",
1794
+ DiscoNight: "Disco Night",
1795
+ EasterGathering: "Easter Gathering",
1796
+ EngagementParty: "Engagement Party",
1797
+ Fair: "Fair",
1798
+ FarewellParty: "Farewell Party",
1799
+ FashionShow: "Fashion Show",
1800
+ Festival: "Festival",
1801
+ FestivalFilm: "Film Festival",
1802
+ FestivalFood: "Food Festival",
1803
+ Fundraiser: "Fundraiser",
1804
+ GameNight: "Game Night",
1805
+ GamingEvent: "Gaming Event",
1806
+ GardenParty: "Garden Party",
1807
+ GraduationParty: "Graduation Party",
1808
+ HalloweenParty: "Halloween Party",
1809
+ HanukkahParty: "Hanukkah Party",
1810
+ HolidayParty: "Holiday Party",
1811
+ HouseParty: "House Party",
1812
+ HousewarmingParty: "Housewarming Party",
1813
+ KaraokeNight: "Karaoke Night",
1814
+ LaunchParty: "Launch Party",
1815
+ LuxuryRetreat: "Luxury Retreat",
1816
+ MansionParty: "Mansion Party",
1817
+ MardiGras: "Mardi Gras",
1818
+ Memorial: "Memorial",
1819
+ MovieNight: "Movie Night",
1820
+ MoviePremiere: "Movie Premiere",
1821
+ MusicFestival: "Music Festival",
1822
+ NewYearsEveCelebration: "New Year's Eve Celebration",
1823
+ OfficeParty: "Office Party",
1824
+ OpenMicNight: "Open Mic Night",
1825
+ OutdoorActivity: "Outdoor Activity",
1826
+ Parade: "Parade",
1827
+ Party: "Party",
1828
+ PoolParty: "Pool Party",
1829
+ Potluck: "Potluck",
1830
+ PreParty: "Pre-Party",
1831
+ ProfessionalNetworkingEvent: "Professional Networking Event",
1832
+ PubCrawl: "Pub Crawl",
1833
+ Rave: "Rave",
1834
+ RetirementCelebration: "Retirement Celebration",
1835
+ Reunion: "Reunion",
1836
+ SchoolEvent: "School Event",
1837
+ SocialClubEvent: "Social Club Event",
1838
+ SportsWatchParty: "Sports Watch Party",
1839
+ ThanksgivingDinner: "Thanksgiving Dinner",
1840
+ ThemedDinnerParty: "Themed Dinner Party",
1841
+ ThemedParty: "Themed Party",
1842
+ ThemedPubCrawl: "Themed Pub Crawl",
1843
+ Tournament: "Tournament",
1844
+ TradeShow: "Trade Show",
1845
+ TriviaNight: "Trivia Night",
1846
+ ValentinesDayParty: "Valentine's Day Party",
1847
+ WeddingReception: "Wedding Reception",
1848
+ WelcomeHomeParty: "Welcome Home Party",
1849
+ WineTastingEvent: "Wine Tasting Event",
1850
+ Workshop: "Workshop",
1851
+ Other: "Other",
1742
1852
  };
1743
1853
 
1744
1854
  /**
@@ -1751,47 +1861,61 @@ export type DietaryOptionToStringType = {
1751
1861
  [key in DietaryOption]: string;
1752
1862
  };
1753
1863
  export const DietaryOptionToString: DietaryOptionToStringType = {
1754
- [DietaryOption.GlutenFreeOptions]: "Gluten-Free Options Available",
1755
- [DietaryOption.VeganOptions]: "Vegan Options Available",
1756
- [DietaryOption.VegetarianOptions]: "Vegetarian Options Available",
1757
- [DietaryOption.DairyFreeOptions]: "Dairy-Free Options Available",
1758
- [DietaryOption.NutFreeOptions]: "Nut-Free Options Available",
1759
- [DietaryOption.HalalOptions]: "Halal Options Available",
1760
- [DietaryOption.KosherOptions]: "Kosher Options Available",
1761
- [DietaryOption.SugarFreeOptions]: "Low-Sugar / Sugar-Free Options Available",
1762
- [DietaryOption.KetoOptions]: "Keto-Friendly Options Available",
1763
- [DietaryOption.PaleoOptions]: "Paleo-Friendly Options Available",
1764
- [DietaryOption.AlcoholFreeOptions]: "Non-Alcoholic Options Available",
1864
+ GlutenFreeOptions: "Gluten-Free Options Available",
1865
+ VeganOptions: "Vegan Options Available",
1866
+ VegetarianOptions: "Vegetarian Options Available",
1867
+ DairyFreeOptions: "Dairy-Free Options Available",
1868
+ NutFreeOptions: "Nut-Free Options Available",
1869
+ HalalOptions: "Halal Options Available",
1870
+ KosherOptions: "Kosher Options Available",
1871
+ SugarFreeOptions: "Low-Sugar / Sugar-Free Options Available",
1872
+ KetoOptions: "Keto-Friendly Options Available",
1873
+ PaleoOptions: "Paleo-Friendly Options Available",
1874
+ AlcoholFreeOptions: "Non-Alcoholic Options Available",
1765
1875
  };
1766
1876
 
1767
1877
  /** Short chip labels used in tight UI spots (filter modal, card pills). */
1768
1878
  export const DietaryOptionShortLabel: DietaryOptionToStringType = {
1769
- [DietaryOption.GlutenFreeOptions]: "Gluten-Free",
1770
- [DietaryOption.VeganOptions]: "Vegan",
1771
- [DietaryOption.VegetarianOptions]: "Vegetarian",
1772
- [DietaryOption.DairyFreeOptions]: "Dairy-Free",
1773
- [DietaryOption.NutFreeOptions]: "Nut-Free",
1774
- [DietaryOption.HalalOptions]: "Halal",
1775
- [DietaryOption.KosherOptions]: "Kosher",
1776
- [DietaryOption.SugarFreeOptions]: "Sugar-Free",
1777
- [DietaryOption.KetoOptions]: "Keto",
1778
- [DietaryOption.PaleoOptions]: "Paleo",
1779
- [DietaryOption.AlcoholFreeOptions]: "Non-Alcoholic",
1879
+ GlutenFreeOptions: "Gluten-Free",
1880
+ VeganOptions: "Vegan",
1881
+ VegetarianOptions: "Vegetarian",
1882
+ DairyFreeOptions: "Dairy-Free",
1883
+ NutFreeOptions: "Nut-Free",
1884
+ HalalOptions: "Halal",
1885
+ KosherOptions: "Kosher",
1886
+ SugarFreeOptions: "Sugar-Free",
1887
+ KetoOptions: "Keto",
1888
+ PaleoOptions: "Paleo",
1889
+ AlcoholFreeOptions: "Non-Alcoholic",
1780
1890
  };
1781
1891
 
1782
1892
  /** All `DietaryOption` enum values in display order (for rendering checkboxes/tags). */
1783
1893
  export const DIETARY_OPTION_VALUES: ReadonlyArray<DietaryOption> = [
1784
- DietaryOption.GlutenFreeOptions,
1785
- DietaryOption.VeganOptions,
1786
- DietaryOption.VegetarianOptions,
1787
- DietaryOption.DairyFreeOptions,
1788
- DietaryOption.NutFreeOptions,
1789
- DietaryOption.HalalOptions,
1790
- DietaryOption.KosherOptions,
1791
- DietaryOption.SugarFreeOptions,
1792
- DietaryOption.KetoOptions,
1793
- DietaryOption.PaleoOptions,
1794
- DietaryOption.AlcoholFreeOptions,
1894
+ "GlutenFreeOptions",
1895
+ "VeganOptions",
1896
+ "VegetarianOptions",
1897
+ "DairyFreeOptions",
1898
+ "NutFreeOptions",
1899
+ "HalalOptions",
1900
+ "KosherOptions",
1901
+ "SugarFreeOptions",
1902
+ "KetoOptions",
1903
+ "PaleoOptions",
1904
+ "AlcoholFreeOptions",
1905
+ ];
1906
+
1907
+ /**
1908
+ * Guest-facing filter chips — everyday accommodations only.
1909
+ * Religious, drink, and diet-trend tags stay on the host form / enum
1910
+ * so existing events keep their data, but they do not crowd the filter menu.
1911
+ */
1912
+ export const DIETARY_OPTION_FILTER_VALUES: ReadonlyArray<DietaryOption> = [
1913
+ "VegetarianOptions",
1914
+ "VeganOptions",
1915
+ "GlutenFreeOptions",
1916
+ "DairyFreeOptions",
1917
+ "NutFreeOptions",
1918
+ "SugarFreeOptions",
1795
1919
  ];
1796
1920
 
1797
1921
  /** Display labels for `IncomeRange` (wizard primary/secondary chips). */
@@ -1799,29 +1923,28 @@ export type IncomeRangeToStringType = {
1799
1923
  [key in IncomeRange]: string;
1800
1924
  };
1801
1925
  export const IncomeRangeLabel: IncomeRangeToStringType = {
1802
- [IncomeRange.NoPreference]: "Anyone",
1803
- [IncomeRange.Under40k]: "Under $40k",
1804
- [IncomeRange.From40to80k]: "$40k – $80k",
1805
- [IncomeRange.From80to150k]: "$80k – $150k",
1806
- [IncomeRange.From150to200k]: "$150k – $200k",
1807
- [IncomeRange.From200to500k]:
1808
- "$200k – $500k (common accredited-income band)",
1809
- [IncomeRange.From500kTo1M]: "$500k – $1M",
1810
- [IncomeRange.Over1M]: "$1M+",
1811
- [IncomeRange.PreferNotToSay]: "Prefer not to say",
1926
+ NoPreference: "Anyone",
1927
+ Under40k: "Under $40k",
1928
+ From40to80k: "$40k – $80k",
1929
+ From80to150k: "$80k – $150k",
1930
+ From150to200k: "$150k – $200k",
1931
+ From200to500k: "$200k – $500k (common accredited-income band)",
1932
+ From500kTo1M: "$500k – $1M",
1933
+ Over1M: "$1M+",
1934
+ PreferNotToSay: "Prefer not to say",
1812
1935
  };
1813
1936
 
1814
1937
  /** Short labels for filter modal / tight chips. */
1815
1938
  export const IncomeRangeShortLabel: IncomeRangeToStringType = {
1816
- [IncomeRange.NoPreference]: "Any income",
1817
- [IncomeRange.Under40k]: "<$40k",
1818
- [IncomeRange.From40to80k]: "$40–80k",
1819
- [IncomeRange.From80to150k]: "$80–150k",
1820
- [IncomeRange.From150to200k]: "$150–200k",
1821
- [IncomeRange.From200to500k]: "$200–500k",
1822
- [IncomeRange.From500kTo1M]: "$500k–$1M",
1823
- [IncomeRange.Over1M]: "$1M+",
1824
- [IncomeRange.PreferNotToSay]: "No answer",
1939
+ NoPreference: "Any income",
1940
+ Under40k: "<$40k",
1941
+ From40to80k: "$40–80k",
1942
+ From80to150k: "$80–150k",
1943
+ From150to200k: "$150–200k",
1944
+ From200to500k: "$200–500k",
1945
+ From500kTo1M: "$500k–$1M",
1946
+ Over1M: "$1M+",
1947
+ PreferNotToSay: "No answer",
1825
1948
  };
1826
1949
 
1827
1950
  /**
@@ -1829,15 +1952,15 @@ export const IncomeRangeShortLabel: IncomeRangeToStringType = {
1829
1952
  * Audience-targeting only — never use to gate ticket purchase or entry.
1830
1953
  */
1831
1954
  export const INCOME_RANGE_VALUES: ReadonlyArray<IncomeRange> = [
1832
- IncomeRange.Under40k,
1833
- IncomeRange.From40to80k,
1834
- IncomeRange.From80to150k,
1835
- IncomeRange.From150to200k,
1836
- IncomeRange.From200to500k,
1837
- IncomeRange.From500kTo1M,
1838
- IncomeRange.Over1M,
1839
- IncomeRange.PreferNotToSay,
1840
- IncomeRange.NoPreference,
1955
+ "Under40k",
1956
+ "From40to80k",
1957
+ "From80to150k",
1958
+ "From150to200k",
1959
+ "From200to500k",
1960
+ "From500kTo1M",
1961
+ "Over1M",
1962
+ "PreferNotToSay",
1963
+ "NoPreference",
1841
1964
  ];
1842
1965
 
1843
1966
  /** Homepage / discovery section labels for synthetic rows (not `BashEventType` enum values). */
@@ -257,7 +257,22 @@ export const PUBLIC_TEASER_USER_DATA_TO_SELECT = {
257
257
 
258
258
  export type PublicTeaserUser = Prisma.UserGetPayload<{
259
259
  select: typeof PUBLIC_TEASER_USER_DATA_TO_SELECT;
260
- }>;
260
+ }> & {
261
+ /** Only populated by Intros endpoints for eligible viewers. */
262
+ openToIntros?: boolean;
263
+ /** Only populated by Intros endpoints for eligible viewers. */
264
+ personalityType?: string | null;
265
+ /** Only populated by Intros endpoints for eligible viewers. */
266
+ locusOfControl?: string | null;
267
+ /** True when the viewer already sent an interest expression to this person. */
268
+ alreadyExpressedInterest?: boolean;
269
+ /** True when this person already sent an interest expression to the viewer. */
270
+ interestFromThem?: boolean;
271
+ /** True when both people have sent a signal. */
272
+ mutualInterest?: boolean;
273
+ /** Only populated by Intros endpoints for eligible viewers. */
274
+ introIntents?: string[];
275
+ };
261
276
 
262
277
  export const PRIVATE_USER_ACCOUNT_TO_SELECT = {
263
278
  ...FRONT_END_USER_DATA_TO_SELECT,
@@ -652,6 +667,9 @@ type BashEventExtBase = Override<
652
667
  dateOptions?: IdeaDateOption[];
653
668
  /** Public aggregate when poll enabled (no PII) */
654
669
  datePollPublicSummary?: IdeaDatePollPublicSummary;
670
+ hideCancellationPolicy?: boolean;
671
+ hideGroupUp?: boolean;
672
+ hideServicesOpenTo?: boolean;
655
673
  /** GET /event/:id — tickets sold (non-cancelled), for social proof */
656
674
  goingCount?: number;
657
675
  /** GET /event/:id — true when at least one Budget line has `showOnBashDetailPage` set. Drives the "View budget" reveal on the public bash page; never carries dollar amounts. */
@@ -729,6 +747,10 @@ type BashEventExtBase = Override<
729
747
  * When true (default), signed-in visitors can message the host from the bash detail page.
730
748
  */
731
749
  allowViewerContact: boolean;
750
+ /**
751
+ * Host opt-in: guests must have a profile photo to RSVP or buy tickets.
752
+ */
753
+ requireProfilePhoto: boolean;
732
754
  };
733
755
 
734
756
  export type BashEventExt = Omit<
@@ -1310,6 +1332,10 @@ export const SERVICE_BOOKING_PUBLIC_DATA_TO_INCLUDE = {
1310
1332
  title: true,
1311
1333
  slug: true,
1312
1334
  status: true,
1335
+ coverPhoto: true,
1336
+ startDateTime: true,
1337
+ endDateTime: true,
1338
+ timezone: true,
1313
1339
  },
1314
1340
  },
1315
1341
  representedByOrganization: {
@@ -1344,7 +1370,14 @@ export const SERVICE_BOOKING_PRIVATE_DATA_TO_INCLUDE = {
1344
1370
  // so call sites can cast prisma.upsert/findUnique results without needing `as unknown`.
1345
1371
  export type ServiceBookingBashEventRef = Pick<
1346
1372
  BashEvent,
1347
- "id" | "title" | "slug" | "status"
1373
+ | "id"
1374
+ | "title"
1375
+ | "slug"
1376
+ | "status"
1377
+ | "coverPhoto"
1378
+ | "startDateTime"
1379
+ | "endDateTime"
1380
+ | "timezone"
1348
1381
  >;
1349
1382
 
1350
1383
  export type ServiceBookingCheckoutRef = Pick<
@@ -1981,10 +2014,10 @@ export interface UserExt extends User {
1981
2014
  socialEventCadenceDisplay?: string;
1982
2015
 
1983
2016
  /**
1984
- * Computed trust badges. Server gates the entire object on
1985
- * `showVerifiedBadgesOnProfile` and strips the underlying raw fields
2017
+ * Computed trust badges. Server strips the underlying raw fields
1986
2018
  * (`paymentVerified`, `stripeAccountId`, `studentIdImageS3Key`, `schoolName`)
1987
- * from the public response.
2019
+ * from the public response. `paymentVerified` stays public once true;
2020
+ * ID / host / student badges still require `showVerifiedBadgesOnProfile`.
1988
2021
  */
1989
2022
  badges?: {
1990
2023
  idVerified: boolean;
@@ -42,7 +42,7 @@ export interface CircuitVendorOptionExt {
42
42
  status: string;
43
43
  contactEmail: string | null;
44
44
  contactName: string | null;
45
- kindLabel: "Vendor" | "Exhibitor";
45
+ kindLabel: "Vendor" | "Exhibitor" | "Hire";
46
46
  }
47
47
 
48
48
  export interface SideQuestExt {