{"version":3,"file":"index-Dc5z7Wgn.js","sources":["../../../../types/index.ts","../../../../core/auth.tsx","../../../../core/config.ts","../../../../core/errors.ts","../../../../core/api/base.ts","../../../../core/api/flag.ts","../../../../core/api/appConfiguration.ts","../../../../core/apm.ts","../../../../ui/Logo/index.tsx","../../../../ui/Page/footer.tsx","../../../../ui/Page/index.tsx","../../../../ui/ErrorBoundary/index.tsx","../../pages/intro.tsx","../../../../ui/Form/text.tsx","../../../../ui/CoverageItem/index.tsx","../../../../ui/Spinner/index.tsx","../../../../ui/Form/select.tsx","../../../../ui/Form/checkbox.tsx","../../../../ui/Form/radio.tsx","../../../../ui/Form/option.tsx","../../../../ui/Button/index.tsx","../../../../core/statics.ts","../../../../core/policy.tsx","../../../../core/analytics/googletags.ts","../../../../core/analytics/analytics.ts","../../../../ui/common.tsx","../../../../ui/Form/builder.tsx","../../../../ui/Form/dob.tsx","../../pages/coverage.tsx","../../pages/bike/quickquote.tsx","../../pages/motor/quickquote.tsx","../../pages/jewelry/quickquote.tsx","../../pages/electronics/quickquote.tsx","../../../../core/url.tsx","../../../../core/api/merchant.ts","../../../../core/analytics/attribution.ts","../../../../ui/Text/skeleton.tsx","../../../../ui/Color/types.tsx","../../../../core/api/user.ts","../../../../core/api/sdk.ts","../../../../ui/Banner/index.tsx","../../../../core/debounce.ts","../../../../core/hubspot.tsx","../../../../ui/Loadable/index.tsx","../../../../ui/Form/switch.tsx","../../../partners/pages/personalization/forms.tsx","../../pages/rental/index.tsx","../../pages/landing.tsx","../../pages/complete.tsx","../../../../ui/Form/progress.tsx","../../pages/page.tsx","../../../../core/stripe.tsx","../../../../ui/Table/index.tsx","../../../../core/payment.tsx","../../pages/payment.tsx","../../../../core/mapbox.tsx","../../../../ui/Form/address.tsx","../../pages/motor/collect.tsx","../../pages/product.tsx","../../pages/motor/index.tsx","../../pages/bike/collect.tsx","../../pages/bike/index.tsx","../../../../ui/Tooltip/index.tsx","../../../../ui/Attachment/lozenge.tsx","../../../../ui/Attachment/index.tsx","../../pages/jewelry/collect.tsx","../../pages/jewelry/chubb/disclosures/disclosures.tsx","../../pages/jewelry/chubb/index.tsx","../../pages/jewelry/minico/index.tsx","../../pages/jewelry/index.tsx","../../pages/electronics/collect.tsx","../../pages/electronics/index.tsx","../../../../core/ga.ts","../../../oysterjs/components/forms/styles.tsx","../../../partners/pages/rental/components.tsx","../../../../ui/Badge/index.tsx","../../../partners/pages/rental/RentalBookingDetailsPage.tsx","../../../partners/pages/rental/RentalCustomerForm.tsx","../../../partners/pages/rental/RentalCompletionPage.tsx","../../../partners/pages/rental/RentalAgreement.tsx","../../index.tsx"],"sourcesContent":["///////////////////////////////////////////////////////\n// SDK-specific types //\n///////////////////////////////////////////////////////\n\nimport { CSSProperties } from 'react';\n\nexport type EcommercePlatform = 'shopify' | 'magento' | 'custom';\n\nexport type DeepPartial = T extends object\n ? {\n [P in keyof T]?: DeepPartial;\n }\n : T;\n\nexport interface InitOptions {\n apiKey: string;\n integrationID?: string;\n platform?: EcommercePlatform;\n apiBaseUrl?: string;\n metricsBaseUrl?: string;\n staticsBaseUrl?: string;\n environment?: string;\n}\n\nexport interface OysterWidgetState {\n // map of policy ID to policy for each policy added to cart\n Policies?: Record;\n}\n\nexport enum WidgetRenderStyle {\n default = '',\n switch = 'switch',\n single_line = 'single_line',\n cart_widget = 'cart_widget',\n registration_widget = 'registration_widget',\n card_selection_widget = 'card_selection_widget'\n}\n\nexport interface WidgetStyle {\n renderStyle?: WidgetRenderStyle;\n center?: boolean;\n title?: CSSProperties;\n addCoverageButton?: CSSProperties;\n fonts?: FontProperties[];\n}\n\nexport interface FontProperties {\n family: string;\n src: string;\n stretch?: string;\n style?: string;\n weight?: string;\n display?: string;\n unicodeRange?: string;\n}\n\nexport enum WidgetFlowStyle {\n product = 'product',\n hybrid = 'hybrid',\n full = 'full',\n single_click = 'single_click',\n registration = 'registration'\n}\n\nexport interface WidgetFlow {\n style: WidgetFlowStyle;\n enableDefaultInsurance?: boolean;\n}\n\nexport interface InjectOptions {\n products: Product[];\n selectedPolicyId?: string;\n hasInsuranceProductInCart?: boolean;\n insured?: Insured;\n style?: WidgetStyle;\n flow?: WidgetFlow;\n subChannel?: ReferralSubChannel;\n}\n\nexport interface InjectBadgesOptions {\n test?: string;\n}\n\nexport interface InjectSubtotalBadgeOptions {\n products?: Product[];\n addBadgeToItems?: boolean;\n invertBadgeColor?: boolean;\n}\n\nexport interface ShopifyCheckout {\n id: number;\n attributes: Record;\n email: string;\n\n // eslint-disable-next-line\n billing_address: any;\n\n // eslint-disable-next-line\n line_items: any[];\n\n metafields: Record>;\n productType?: ProductType;\n}\n\nexport interface ConfirmPurchaseOptions {\n policyIds: string[];\n productSkus: string[];\n insured?: Insured;\n shopifyCheckout?: ShopifyCheckout;\n injectPostPurchase?: boolean;\n}\n\nexport type InjectCallback = (selectedPolicy?: Policy) => void;\nexport type ConfirmPurchaseCallback = (policy: Policy | null, err?: Error) => void;\n\n///////////////////////////////////////////////////////\n// Types corresponding to product models //\n///////////////////////////////////////////////////////\n\nexport enum ProductType {\n bike = 'bike',\n offroad = 'offroad_vehicle',\n motorcycle = 'motorcycle',\n jewelry = 'jewelry',\n phone = 'phone',\n electronics = 'electronics'\n}\n\nexport enum AdditionalMerchantProductType {\n collectibles = 'collectibles',\n furniture = 'furniture',\n fashion = 'fashion',\n food_and_beverage = 'food_and_beverage',\n beauty_and_supplements = 'beauty_and_supplements',\n outdoor_sports = 'outdoor_sports',\n other = 'other'\n}\n\nexport type MerchantProductType = ProductType | AdditionalMerchantProductType;\n\nexport interface Product {\n ID?: string;\n Type: ProductType;\n SKU: string;\n Name: string;\n Description: string;\n ImageURL: string;\n Quantity: number;\n Price: Price;\n Pending?: boolean;\n HasNotReceived?: boolean;\n Details: T;\n}\n\nexport enum BikeType {\n roadTrack = 'road_track',\n mountain = 'mountain',\n hybridCruiser = 'hybrid_cruiser',\n recumbent = 'recumbent',\n tandem = 'tandem',\n bmx = 'bmx',\n tricycle = 'tricycle',\n electric = 'electric'\n}\n\nexport enum BikeFrameType {\n none = '',\n aluminum = 'aluminum',\n carbonFiber = 'carbon_fiber',\n steel = 'steel',\n titatium = 'titanium',\n wood = 'wood',\n other = 'other'\n}\n\nexport enum BikePowerSource {\n none = '',\n throttleAssist20MPH = 'throttle_assist_20_mph',\n pedalAssist20MPH = 'pedal_assist_20_mph',\n pedalAssist28MPH = 'pedal_assist_28_mph',\n other = 'other'\n}\n\nexport enum MotorProductType {\n // Motorcycles\n preferred_tour = 'preferred_tour',\n tour = 'tour',\n sport_tour = 'sport_tour',\n non_touring_bmw = 'non_touring_bmw',\n big_twin = 'big_twin',\n low_surcharge = 'low_surcharge',\n high_surcharge = 'high_surcharge',\n professional_racing_surcharge = 'professional_racing_surcharge',\n naked_sport = 'naked_sport',\n sportster = 'sportster',\n cruiser = 'cruiser',\n high_performance_cruiser = 'high_performance_cruiser',\n moped = 'moped',\n scooter = 'scooter',\n street_bike = 'street_bike',\n off_road_trail_bike = 'off_road_trail_bike',\n tour_trike = 'tour_trike',\n\n // Offroad vehicles\n sport_performance_atv = 'sport_performance_atv',\n recreation_utility_atv = 'recreation_utility_atv',\n super_utility_atv = 'super_utility_atv',\n golf_cart = 'golf_cart'\n}\n\nexport enum ActivationSource {\n TrackingNumber = 'TrackingNumber',\n SetStartDate = 'SetStartDate',\n ManuallyActivate = 'ManuallyActivate'\n}\n\nexport interface ActivationTrigger {\n Source: ActivationSource;\n TrackingNumber?: string;\n StartDate?: string;\n}\n\nexport interface MotorProduct {\n ModelYear: string;\n PurchaseYear: string;\n\n Type: MotorProductType;\n Make: string;\n Model: string;\n VIN: string;\n CCSize: string;\n\n EstimatedAnnualMileage: string;\n AccessoryValue?: Price;\n\n HasElectronicAlarm?: boolean;\n HasTrackingSystem?: boolean;\n HasLienHolder?: boolean;\n IsPerformanceEnhanced?: boolean;\n IsStructurallyModified?: boolean;\n}\n\nexport const MotorProductValidationKeys: (keyof MotorProduct)[] = [\n 'ModelYear',\n 'PurchaseYear',\n\n 'Type',\n 'Make',\n 'Model',\n 'VIN',\n 'CCSize',\n\n // 'EstimatedAnnualMileage', - this one is checked on a later page\n 'AccessoryValue'\n];\n\nexport interface BikeProduct {\n Type: BikeType;\n FrameType: BikeFrameType;\n PowerSource?: BikePowerSource;\n CanUnlock?: boolean;\n IsPendingSerialNumber?: boolean;\n StartLater?: boolean;\n\n ModelYear: string;\n PurchaseYear: string;\n Make: string;\n Model: string;\n FrameSerialNumber: string;\n IsSecondHandPurchase: boolean;\n\n TotalInsuredValue?: Price;\n}\n\nexport const BikeProductValidationKeys: (keyof BikeProduct)[] = [\n 'Type',\n 'FrameType',\n 'PowerSource',\n 'CanUnlock',\n 'ModelYear',\n 'PurchaseYear',\n 'Make',\n 'Model',\n 'FrameSerialNumber',\n 'TotalInsuredValue'\n];\n\nexport enum ElectronicsType {\n laptop = 'laptop',\n iPhone = 'iphone',\n smartPhone = 'smartphone',\n iPad = 'ipad',\n smallElectronics = 'small_electronics',\n gamingSystem = 'gaming_system',\n eReaderKindle = 'ereader_kindle',\n tablet = 'tablet'\n}\n\nexport interface ElectronicsProduct {\n Type: ElectronicsType;\n Manufacturer: string;\n Model: string;\n SerialNumber: string;\n PurchaseReceiptFileIDs?: string[];\n\n _PurchaseReceiptFiles?: File[];\n}\n\nexport const ElectronicsProductValidationKeys: (keyof ElectronicsProduct)[] = [\n 'Type',\n 'Manufacturer',\n 'Model',\n 'SerialNumber'\n];\n\nexport interface JewelryProduct {\n Type: string;\n Metal: string;\n Brand: string;\n StoneType: string;\n StoneWeight: string;\n StoneCarat: string;\n StoneColor: string;\n StoneClarity: string;\n InSafe: boolean;\n IsNewPurchase: boolean;\n AppraisalFileIDs?: string[];\n\n _AppraisalFiles?: File[];\n}\n\nexport const JewelryProductValidationKeys: (keyof JewelryProduct)[] = [\n 'Type',\n 'Metal',\n 'Brand',\n 'StoneType',\n 'StoneWeight',\n 'StoneCarat',\n 'StoneColor',\n 'StoneClarity'\n];\n\n///////////////////////////////////////////////////////\n// Types corresponding to policy models //\n///////////////////////////////////////////////////////\n\nexport enum Cadence {\n year = 'year',\n month = 'month'\n}\n\nexport enum PolicyState {\n unknown = '',\n offerSubmitted = 'offer_submitted',\n applicationSubmitted = 'application_submitted',\n quoteActive = 'quote_active',\n quoteSigned = 'quote_signed',\n policyBinding = 'policy_binding',\n policyInforce = 'policy_inforce',\n policyCanceled = 'policy_canceled',\n policyExpired = 'policy_expired'\n}\n\nexport enum PolicyType {\n bike = 'bike',\n markelBike = 'markel_bike',\n markelOffroad = 'markel_offroad',\n markelMotorcycle = 'markel_motorcycle',\n minicoJewelry = 'minico_jewelry',\n chubbJewelry = 'chubb_jewelry',\n worthAveElectronics = 'worth_ave_electronics'\n}\n\nexport interface PolicyTableValues {\n CreatedAt: Date;\n InsuredItems: Product[];\n}\n\nexport interface Policy {\n ID: string;\n Name: string;\n UserID: string;\n MerchantID: string;\n\n Type: PolicyType;\n State: PolicyState;\n\n InsuredItems: Product[];\n Underwriting: Underwriting;\n Coverage: Coverage;\n ReferralChannelInfo?: PolicyReferralChannelInfo;\n\n Price?: Price;\n PaymentCadence: Cadence;\n DisableAutoRenew: boolean;\n\n CreatedAt: Date;\n StartedAt: Date;\n ExpiresAt: Date;\n}\n\nexport interface Price {\n Amount: number;\n Currency: string;\n}\n\nexport interface Coverage {\n Deductible: DiscreteOption;\n Options: Option[];\n ActivationTrigger?: ActivationTrigger;\n}\n\nexport interface PolicyReferralChannelInfo {\n ReferralChannel: ReferralChannelType;\n ReferralSubChannel: ReferralSubChannel;\n Settings: MerchantReferralSettings | MarketingCampaignSettings | DirectReferralSettings;\n}\n\nexport enum ReferralSubChannel {\n product = 'ProductPage',\n addToCartButton = 'AddToCartButton',\n cart = 'CartPage',\n confirm = 'OrderConfirmationPage',\n defaultInsurance = 'DefaultInsurance',\n unknown = ''\n}\n\nexport interface MerchantReferralSettings {\n MerchantID: string;\n IntegrationID: string;\n}\n\nexport interface MarketingCampaignSettings {\n Source: string;\n Campaign: string;\n Term: string;\n Medium: string;\n PageHistory: string[];\n}\n\nexport interface DirectReferralSettings {\n Parameters: string;\n PageHistory: string[];\n}\n\nexport enum ReferralChannelType {\n ReferralChannelMerchant = 'Merchant',\n ReferralChannelMarketingCampaign = 'Marketing Campaign',\n ReferralChannelDirect = 'Direct'\n}\n\nexport enum BikeUsageType {\n casual = 'casual',\n fitness = 'fitness',\n commute = 'commute',\n competition = 'competition'\n}\n\nexport enum MarkelPersonalCharacteristicFlag {\n homeowner = 'homeowner',\n motorcycle_endorsed = 'motorcycle_endorsed',\n taken_safety_course = 'taken_safety_course',\n taken_mature_driver_course = 'taken_mature_driver_course'\n}\n\nexport interface MarkelMotorCriteria {\n ZipCode: string;\n\n YearsOfExperience: string;\n MinorViolations: string;\n MajorViolations: string;\n AtFaultAccidents: string;\n\n DriversLicenseNumber: string;\n DriversLicenseState: string;\n\n PersonalCharacteristicFlags: MarkelPersonalCharacteristicFlag[];\n}\n\nexport interface BikeCriteria {\n ZipCode: string;\n UsageTypes: BikeUsageType[];\n\n StorageAddressLine1: string;\n StorageAddressLine2: string;\n StorageCity: string;\n StorageState: string;\n StorageZipCode: string;\n ResidentialIsStorage: boolean;\n}\n\nexport interface MarkelBikeCriteria {\n ZipCode: string;\n UsageTypes: BikeUsageType[];\n\n StorageAddressLine1: string;\n StorageAddressLine2: string;\n StorageCity: string;\n StorageState: string;\n StorageZipCode: string;\n ResidentialIsStorage: boolean;\n YearsOfExperience: string;\n}\n\nexport enum JewelryUnderwritingFlag {\n default = 'default',\n exhibition = 'exhibition',\n bankruptcy = 'bankruptcy',\n judgment = 'judgment',\n conviction = 'conviction'\n}\n\nexport enum JewelryStorageLocationFlag {\n central_station_fire = 'central_station_fire',\n burglar_alarm = 'burglar_alarm',\n central_station_burglar_alarm = 'central_station_burglar_alarm',\n deadbolts = 'deadbolts',\n below_ground = 'below_ground',\n off_the_ground = 'off_the_ground',\n near_fire_hydrant = 'near_fire_hydrant',\n flood_zone = 'flood_zone',\n has_doorman = 'has_doorman',\n storage_climate_controlled = 'storage_climate_controlled'\n}\n\nexport interface JewelryPriorLoss {\n lossIdentifier: string;\n dateOfLoss: Date;\n lossAmount: number;\n description: string;\n}\n\nexport interface JewelryCriteria {\n ZipCode: string;\n PriorLosses: JewelryPriorLoss[];\n UnderwritingFlags: JewelryUnderwritingFlag[];\n UnderwritingJustification: string;\n\n StorageStreetAddress: string;\n StorageAptFloor: string;\n StorageAptUnit: string;\n StorageCity: string;\n StorageState: string;\n StorageZipCode: string;\n StorageIsResidential: boolean;\n\n ConstructionType: string;\n ConstructionYear: string;\n StorageLocationFlags: JewelryStorageLocationFlag[];\n\n HasAdditionalInsured: boolean;\n AdditionalInsuredFirstName: string;\n AdditionalInsuredLastName: string;\n\n IsCurrentlyInsured: boolean;\n CurrentInsurerName: string;\n}\n\nexport interface ChubbAddress {\n line1: string;\n line2: string;\n city: string;\n postalCode: string;\n stateOrProvinceName: string;\n}\n\nexport interface ChubbLoss {\n lossIdentifier: string;\n causeOfLossCode: string;\n sourceOfLossCode: string;\n causeOfLossDescription: string;\n dateOfLoss: string;\n lossPaidDate: string;\n lossPaidAmount: number;\n lossType: string;\n lossCatastrophe: boolean;\n lossStatus: string;\n}\nexport interface ChubbJewelryCriteria {\n ZipCode: string;\n PriorLosses: ChubbLoss[];\n\n StorageAddress: ChubbAddress;\n StorageIsResidential: boolean;\n\n ConstructionType: string;\n ResidenceType: string;\n\n AcceptConsumerDisclosure: string;\n\n HasAdditionalInsured: boolean;\n AdditionalInsuredFirstName: string;\n AdditionalInsuredLastName: string;\n AdditionalInsuredOccupation: string;\n AdditionalInsuredOccupationDetails: string;\n AdditionalInsuredDateOfBirth: { Day: string; Month: string; Year: string };\n\n IsCoveredItemsElevated: boolean;\n IsStoredInClimateControlledStorage: boolean;\n\n OccupationDetails: string;\n OtherOccupationDetails: string;\n\n CentralStationFireAlarm: boolean;\n TwentyFourHourSecurity: boolean;\n SignalContinuity: boolean;\n FullTimeCaretaker: boolean;\n GatedCommunity: boolean;\n GatedCommunityPatrol: boolean;\n FiftyPercentJewelryKeptInVault: boolean;\n SafeAtHome: boolean;\n HasInHouseEmployees: boolean;\n InHouseEmployees: string;\n InsuredTravel: string;\n AverageJewelryValueOnTravel: number;\n JewelrySafeguardOnTravel: string;\n JewelrySafeguardAtHomeInsuredOnTravel: string;\n}\n\nexport interface Underwriting {\n Criteria:\n | MarkelMotorCriteria\n | BikeCriteria\n | MarkelBikeCriteria\n | JewelryCriteria\n | ChubbJewelryCriteria;\n Insured: Insured;\n Underwriter?: string;\n}\n\nexport type OptionDetails =\n | BooleanOption\n | DiscreteOption\n | MultiDiscreteOption\n | RangeOption\n | TextOption;\n\nexport enum OptionType {\n boolean = 'boolean',\n discrete = 'discrete',\n multi_discrete = 'multi_discrete',\n range = 'range',\n text = 'text'\n}\n\nexport interface Option {\n ID: string;\n Name: string;\n Description: string;\n Required: boolean;\n Enabled: boolean;\n Type: OptionType;\n Option: OptionDetails;\n}\n\nexport interface BooleanOption {\n ValueLabel: string;\n}\n\nexport interface DiscreteOption {\n Value: string;\n AllowedValues: string[];\n AllowedValueDescriptions: Record;\n}\n\nexport interface MultiDiscreteOption {\n Values: string[];\n AllowedValues: string[];\n AllowedValueDescriptions?: Record;\n}\n\nexport interface RangeOption {\n Value: number;\n MinValue: number;\n MaxValue: number;\n Step: number;\n}\n\nexport interface TextOption {\n Value: string;\n Validators: string[];\n}\n\nexport enum Gender {\n male = 'male',\n female = 'female',\n non_binary = 'non_binary',\n other = 'other'\n}\n\nexport interface Insured {\n FirstName: string;\n LastName: string;\n Email: string;\n Phone?: string;\n Occupation?: string;\n Gender?: Gender;\n MaritalStatus?:\n | 'married'\n | 'single'\n | 'divorced'\n | 'separated'\n | 'widowed'\n | 'civil_union'\n | 'domestic_partnership';\n DateOfBirth?: { Day: string; Month: string; Year: string };\n\n AddressLine1?: string;\n AddressLine2?: string;\n AddressCity?: string;\n AddressState?: string;\n AddressZipCode?: string;\n}\n\n///////////////////////////////////////////////////////\n// Types corresponding to files and metadata //\n///////////////////////////////////////////////////////\n\nexport interface AttachmentFile {\n ID: string;\n Name: string;\n Type: string;\n Size: number;\n\n Attachment?: AttachmentDetails;\n}\n\nexport interface AttachmentDetails {\n FileID: string;\n RefID: string;\n Role: DefaultFileRoles;\n EntityType: string;\n\n CreatedAt: Date;\n UpdatedAt: Date;\n}\n\nexport enum DefaultFileRoles {\n jewelryAppraisal = 'JewelryAppraisal',\n purchaseReceipt = 'PurchaseReceipt',\n\n waiverClaimAttachment = 'WaiverClaimAttachment',\n waiverLDWDocument = 'LossDamageWaiver',\n signedRentalAgreement = 'SignedRentalAgreement',\n merchantRentalPDTA = 'ProgramDescriptionTermsAccepance',\n\n commercialDocument = 'CommercialDocument',\n unknown = 'Unknown'\n}\n\n///////////////////////////////////////////////////////\n// Types corresponding to claim models //\n///////////////////////////////////////////////////////\n\nexport enum ClaimState {\n unknown = '',\n awaitingProcessing = 'awaiting_processing',\n collectingInformation = 'collecting_information',\n settled = 'settled',\n denied = 'denied'\n}\n\nexport enum ClaimType {\n // General claim types\n unknown = '',\n theft = 'theft',\n damage = 'damage', // for electronics: includes cracked screen and spills\n injury = 'injury', // bike only\n liability = 'liability', // bike only\n other = 'other',\n\n // Electronics-specific claim types\n electricalBreakdown = 'electrical_breakdown', // only if caused by lightning\n mechanicalBreakdown = 'mechanical_breakdown', // only if warranty is added\n naturalDisaster = 'natural_disaster', // fire, flood, lightning, natural disaster, wind\n waterDamage = 'water_damage' // includes spill\n}\n\nexport interface Claim {\n ID: string;\n State: ClaimState;\n UserID: string;\n Policy: Policy;\n Product: Product;\n\n Types: ClaimType[];\n Sections: ClaimSection[];\n\n IncidentDate: Date;\n Attachments: Record;\n Data: Record;\n\n CreatedAt: Date;\n UpdatedAt: Date;\n}\n\nexport enum ClaimSectionItemType {\n checkbox = 'checkbox',\n select = 'select',\n text = 'text',\n date = 'date',\n location = 'location',\n longText = 'long_text',\n upload = 'upload',\n confirm = 'confirm'\n}\n\nexport type ClaimSectionItemDetails =\n | CheckboxItem\n | SelectItem\n | TextItem\n | DateItem\n | LocationItem\n | LongTextItem\n | UploadItem\n | ConfirmItem;\n\nexport interface ClaimSection {\n ID: string;\n Field: string;\n Title: string;\n Description: string;\n Items: ClaimSectionItem[];\n}\n\nexport interface ClaimSectionItem {\n SubField: string;\n SubTitle?: string;\n Description?: string;\n Type: ClaimSectionItemType;\n Item: ClaimSectionItemDetails;\n}\n\n// eslint-disable-next-line\nexport interface CheckboxItem {}\n\nexport interface SectionItemDescription {\n Title: string;\n Description: string;\n Value: string;\n Icon: string;\n}\n\nexport interface SelectItem {\n AllowedValues: SectionItemDescription[];\n}\n\nexport interface TextItem {\n Required?: boolean;\n}\n\n// eslint-disable-next-line\nexport interface DateItem {}\n\nexport interface LocationItem {\n AddressField: string;\n LongitudeField: string;\n LatitudeField: string;\n}\n\nexport interface LongTextItem {\n Field: string;\n}\n\nexport interface UploadItem {\n Name: string;\n Description: string;\n AllowedTypes: string[];\n MaxSize: number;\n NumRequired: number;\n}\n\nexport interface Confirmation {\n Title: string;\n Description: string;\n}\n\nexport interface ConfirmItem {\n Confirmation: Confirmation[];\n}\nexport interface ClaimAttachment {\n ID: string;\n Name: string;\n Type: string;\n Size: number;\n URL: string;\n}\n\n///////////////////////////////////////////////////////\n// Types corresponding to user models //\n///////////////////////////////////////////////////////\n\nexport interface User {\n ID: string;\n FirstName: string;\n LastName: string;\n Email: string;\n PublicHandle: string;\n CreatedAt: Date;\n}\n\nexport interface UserVerification {\n ID: string;\n Secret: string;\n Status: string;\n Error: string;\n CreatedAt: Date;\n}\n\nexport enum ReferralStatus {\n created = 'created',\n fulfilled = 'fulfilled'\n}\n\nexport interface UserReferral {\n ID: string;\n Referrer: {\n UserID: string;\n FirstName: string;\n LastName: string;\n Email: string;\n BusinessName: string;\n };\n Referee: {\n UserID: string;\n FirstName: string;\n LastName: string;\n Email: string;\n BusinessName: string;\n };\n Status: ReferralStatus;\n Reward: number;\n CreatedAt: Date;\n UpdatedAt: Date;\n}\n\nexport interface AccountSummary {\n PaymentMethods?: PaymentMethod[];\n PastStatements: Statement[];\n User?: User;\n Verification?: UserVerification;\n}\n\nexport enum VerificationStatus {\n RequiresInput = 'requires_input',\n Processing = 'processing',\n Verified = 'verified',\n Canceled = 'canceled'\n}\n\nexport interface PaymentMethod {\n ID: string;\n Brand: string;\n ExpMonth: number;\n ExpYear: number;\n LastFour: string;\n Default: boolean;\n}\n\nexport interface Statement {\n ID: string;\n\n StatementDate: Date;\n GeneratedAt: Date;\n\n PolicyStatements: PolicyStatement[];\n Currency: string;\n TaxRate: number;\n\n SubTotal: number;\n TaxTotal: number;\n Total: number;\n}\n\nexport interface PolicyStatement {\n Name: string;\n LineItems: LineItem[];\n Adjustments: LineItem[];\n Total: number;\n}\n\nexport interface LineItem {\n Title: string;\n Description: string;\n Price: Price;\n}\n\n///////////////////////////////////////////////////////\n// Types corresponding to general models //\n///////////////////////////////////////////////////////\n\nexport interface ValidationError {\n Field: string;\n SubField?: string;\n Message: string;\n}\n\n///////////////////////////////////////////////////////\n// Types corresponding to merchant models //\n///////////////////////////////////////////////////////\n\nexport interface Address {\n AddressLine1: string;\n AddressLine2?: string;\n\n // Level 1 administrative division depending on country. For example, this is\n // the state in the U.S., the province in Canada, etc.\n Zone: string;\n\n // Level 2 administrative division depending on country. For example, this is\n // the county in the U.S., prefectural city in China, division in India, etc.\n SubZone?: string;\n\n // Level 3 administrative division depending on country. For example, this is\n // the city in the U.S., a muncipality in Austria, a ward in Singapore, etc.\n City: string;\n\n // Country-specific mailing identifier, e.g. ZIP Code in the U.S., Post Code\n // in the U.K., etc.\n PostalCode: string;\n}\n\nexport enum PaymentMethodType {\n Electronic = 'Electronic',\n Check = 'Check',\n Unknown = ''\n}\n\nexport enum SalesChannelType {\n Online = 'Online',\n Dealer = 'Dealer',\n InStore = 'InStore',\n Rental = 'Rental'\n}\n\nexport enum OnlineSalesChannelType {\n Shopify = 'Shopify',\n WooCommerce = 'WooCommerce',\n BigCommerce = 'BigCommerce',\n Lightspeed = 'Lightspeed eCommerce',\n Checkfront = 'Checkfront',\n Adobe = 'Adobe Commerce (Magento)',\n Custom = 'Custom',\n Other = 'Other'\n}\n\nexport enum InStoreSalesChannelType {\n ShopifyPOS = 'Shopify PoS',\n LightspeedRSeries = 'Lightspeed R-Series',\n LightspeedXSeries = 'Lightspeed X-Series',\n Ascend = 'Ascend',\n Edge = 'EDGE',\n Square = 'Square',\n Other = 'Other'\n}\n\nexport enum BusinessOperationType {\n RetailECommerce = 'Retail or E-Commerce',\n Wholesale = 'Wholesale',\n Manufacturing = 'Manufacturing',\n ServiceOrRepair = 'Service or Repair',\n Rental = 'Rental',\n Other = 'Other'\n}\n\nexport enum BusinessInsuranceType {\n BusinessOwners = 'Business Owners',\n CommercialAuto = 'Commercial Auto',\n Cyber = 'Cyber',\n DirectorsAndOfficers = 'Directors and Officers',\n GeneralLiability = 'General Liability',\n JewelersBlock = 'Jewelers Block',\n Other = 'Other',\n Property = 'Property',\n Rental = 'Rental',\n ShippingAndTransportation = 'Shipping and Transportation',\n UmbrellaExcess = 'Umbrella or Excess',\n WorkersCompensation = 'Workers Compensation'\n}\n\nexport enum RentalProductType {\n BikesAndEbikes = 'Bikes and eBikes',\n Kayaks = 'Kayaks',\n Other = 'Other',\n Paddleboards = 'Paddleboards'\n}\n\nexport interface Personalization {\n // Toggle the various types of insurance in the onboarding flow\n BusinessInsuranceEnabled: boolean;\n ProductInsuranceEnabled: boolean;\n RentalInsuranceEnabled: boolean;\n\n // Business insurance settings\n BusinessOperationTypes?: BusinessOperationType[];\n BusinessOperationTypesOtherDesc: string;\n\n BusinessRevenueBreakdownRetail: string;\n BusinessRevenueBreakdownWholesale: string;\n BusinessRevenueBreakdownManufacturing: string;\n BusinessRevenueBreakdownServiceOrRepair: string;\n BusinessRevenueBreakdownRental: string;\n BusinessRevenueBreakdownOther: string;\n BusinessRevenueBreakdownRentalPercentGuided: string;\n\n BusinessTotalPayroll: string;\n\n BusinessManufactureOrWholesaleOwnBrand?: boolean;\n\n BusinessInsuranceTypes?: BusinessInsuranceType[];\n BusinessInsuranceTypesOtherDesc: string;\n\n BusinessNumberOfPriorLosses?: string;\n\n BusinessInsuranceFilloutFormSubmitted: boolean;\n\n // Personal insurance settings\n SalesChannels?: SalesChannelType[];\n\n OnlineSalesChannelType?: OnlineSalesChannelType;\n OnlineSalesChannelName?: string;\n ShopifyAdminURL?: string;\n\n InStoreSalesChannelType?: InStoreSalesChannelType;\n InStoreSalesChannelName?: string;\n\n RentalChannelName?: string;\n\n ProductAnnualRevenue: string;\n\n // Rental insurance settings\n RentalProductTypes?: RentalProductType[];\n RentalProductTypeOtherDesc: string;\n\n RentalMotorizedAssetsExceedEBikeClassification?: boolean;\n\n RentalMaximumAssetValue: string;\n RentalAverageAssetValue: string;\n\n RentalAnnualRevenue: string;\n RentalAnnualVolume: string;\n\n RentalPlatformName: string;\n RentalPointOfSaleName: string;\n}\n\nexport interface BusinessProfile {\n Name: string;\n DBA: string;\n Email: string;\n Phone: string;\n Domain: string;\n Address: Address;\n PaymentMethod: PaymentMethodType;\n\n ProductVerticals?: ProductType[];\n Personalization: Personalization;\n}\n\nexport interface Merchant {\n ID: string;\n MercuryRecipientID: string;\n BusinessProfile: BusinessProfile;\n CreatedAt: Date;\n PublicHandle: string;\n}\n\nexport interface MerchantUser {\n ID: string;\n MerchantID: string;\n\n FirstName: string;\n LastName: string;\n Email: string;\n Phone: string;\n\n ActivationToken?: string;\n CreatedAt: Date;\n}\n\nexport interface MerchantIntegration {\n ID: string;\n Type: MerchantIntegrationType;\n Settings: MerchantIntegrationSettings;\n Status: MerchantIntegrationStatus;\n}\n\nexport enum MerchantIntegrationStatus {\n active = 'Active',\n pending = 'Pending',\n disabled = 'Disabled',\n inactive = 'Inactive'\n}\n\nexport enum MerchantIntegrationType {\n qr_code = 'QRCode',\n referral_link = 'ReferralLink',\n embeddable_marketing_page = 'EmbeddableMarketingPage',\n shopify = 'Shopify',\n custom = 'Custom',\n checkfront = 'Checkfront',\n lsretail_rseries = 'LSRetailRSeries',\n woocommerce = 'WooCommerce',\n not_available = ''\n}\n\nexport type MerchantIntegrationSettings =\n | ShopifyIntegrationSettings\n | LSRetailRSeriesIntegrationSettings\n | QRCodeIntegrationSettings\n | ReferralLinkIntegrationSettings\n | EmbeddableMarketingPageIntegrationSettings\n | WooCommerceIntegrationSettings\n | CustomIntegrationSettings;\n\nexport interface ShopifyIntegrationSettings {\n ShopName: string;\n InsuranceProductID?: string;\n CartPageWidgetEnabled: boolean;\n OrderConfirmationPageWidgetEnabled: boolean;\n ProductPageAddToCartTriggerEnabled: boolean;\n ProductPageWidgetEnabled: boolean;\n UseOysterProductAsIndicator?: boolean;\n OrderConfirmationPageWidgetFirstLoad: Date;\n}\n\nexport interface LSRetailRSeriesIntegrationSettings {\n AccountID: string;\n AccountName: string;\n EmailMarketingEnabled: boolean;\n EmailMarketingRequiresOptIn: boolean;\n EmailMarketingCategoriesEnabled: string[] | null;\n}\n\nexport interface QRCodeIntegrationSettings {\n ReferralLink: string;\n BrandColor?: string;\n FirstDownload?: string;\n}\n\nexport interface ReferralLinkIntegrationSettings {\n ReferralLink: string;\n BrandColor?: string;\n FirstPageLoad?: string;\n}\n\nexport interface EmbeddableMarketingPageIntegrationSettings {\n ReferralLink: string;\n FirstPageLoad?: string;\n}\n\nexport interface WooCommerceIntegrationSettings {\n BaseURL: string;\n OysterVirtualProductID?: number;\n CartPageWidgetEnabled: boolean;\n OrderConfirmationPageWidgetEnabled: boolean;\n ProductPageAddToCartTriggerEnabled: boolean;\n ProductPageWidgetEnabled: boolean;\n}\n\nexport interface CustomIntegrationSettings {\n Data: string;\n}\n\nexport interface MerchantRentalConfiguration {\n ID: string;\n MerchantID: string;\n Details: MerchantRentalConfigurationDetails;\n}\n\nexport interface PublicMerchantRentalConfiguration {\n BillingMode: BillingMode;\n UnifiedWaiverFlowEnabled: boolean;\n AutoRenewWaiver: boolean;\n}\n\nexport enum BillingMode {\n Customer = 'Customer',\n Merchant = 'Merchant'\n}\n\nexport interface MerchantRentalConfigurationDetails {\n BillingMode: BillingMode;\n Pricing: RentalPricingConfiguration;\n State: RentalPolicyState;\n PolicyDocument: AttachmentFile;\n AutoRenewWaiver: boolean;\n UnifiedWaiverFlowEnabled: boolean;\n}\n\nexport enum RentalPolicyState {\n unknown = 'Unknown',\n registered = 'Registered',\n submitted = 'Submitted',\n approved = 'Approved',\n approved_blanket = 'ApprovedBlanketPolicy',\n denied = 'Denied'\n}\n\nexport interface RentalPricingConfiguration {\n Currency: string;\n AssetValueUnitDenominator: number;\n DailyRatingPerValueUnit: number;\n WeeklyRatingPerValueUnit: number;\n MonthlyRatingPerValueUnit: number;\n MinimumPremium: number;\n\n MerchantFeePercent: number;\n MerchantFeeAbs: number;\n MerchantDeductible: number;\n PlatformFeePercent: number;\n PlatformFeeAbs: number;\n}\n\nexport enum MercuryPaymentTypes {\n electronic = 'electronic',\n check = 'check',\n unknown = ''\n}\n\nexport interface ReferralLinkIntegration {\n MerchantName: string;\n IntegrationID: string;\n IntegrationType: MerchantIntegrationType;\n Settings: ReferralLinkIntegrationSettings;\n Disabled?: boolean;\n}\n\nexport enum FeatureFlagExperiments {\n oneliner_widget = 'force_render_oneliner_widget',\n single_click_flow = 'single_click_flow',\n show_logo_on_widget = 'show_logo_on_widget'\n}\n\nexport interface FeatureFlag {\n ID: string;\n Value: boolean;\n}\n\nexport enum MerchantAppType {\n rental = 'rental',\n unknown = ''\n}\n\nexport interface WaiverEntry {\n Waiver?: RentalWaiver;\n Booking?: RentalBooking;\n}\n\nexport enum WaiverState {\n pending = 'Pending',\n canceled = 'Canceled',\n active = 'Active',\n expired = 'Expired',\n upcoming_renewal = 'UpcomingRenewal',\n renewal_canceled = 'RenewalCanceled',\n unknown = ''\n}\n\nexport interface RentalWaiver {\n ID: string;\n MerchantID: string;\n RentalBookingID: string;\n State: WaiverState;\n Details: WaiverDetails;\n}\n\nexport interface WaiverDetails {\n BillingMode: BillingMode;\n WaiverReferenceNumber: string;\n Assets: WaiverAsset[];\n Premium: WaiverPremium;\n PremiumRounding: Price;\n SignatureReference?: AttachmentFile;\n}\n\nexport interface WaiverAsset {\n // Either Asset or BookingLineItem will be present\n Asset?: RentalAsset;\n Premium: WaiverPremium;\n Accessories: AssetAccessory[];\n BookingLineItem?: BookingLineItem;\n}\n\nexport interface WaiverPremium {\n Currency: string;\n Base: number;\n Total: number;\n\n ProcessingFeeAmount: number;\n MerchantFeeAmount: number;\n OysterFeeAmount: number;\n}\n\nexport interface RentalAsset {\n ID: string;\n MerchantID: string;\n\n Type: AssetType;\n Name: string;\n Description: string;\n Value: number;\n Details: AssetDetails;\n}\n\nexport enum AssetType {\n bicycle = 'Bicycle',\n kayak = 'Kayak',\n paddleboard = 'Paddleboard',\n scooter = 'Scooter',\n motorcycle = 'Motorcycle',\n atv = 'ATV',\n other = 'Other'\n}\n\nexport interface AssetDetails {\n SerialNumber: string;\n AvailableAccessories: AssetAccessory[];\n}\n\nexport interface AssetAccessory {\n Name: string;\n Value: number;\n}\n\nexport interface RentalBooking {\n ID: string;\n MerchantID: string;\n\n State: RentalBookingState;\n Details: RentalBookingDetails;\n StartTime: Date;\n EndTime: Date;\n}\n\nexport enum RentalBookingState {\n Unknown = '',\n Confirmed = 'Confirmed'\n}\n\nexport interface BookingLineItem {\n Name: string;\n SKU: string;\n ExternalID: string;\n SerialNumber: string;\n StartDate: Date;\n EndDate: Date;\n Total: number;\n SubTotal: number;\n TaxTotal: number;\n Quantity: number;\n}\n\nexport interface RentalBookingDetails {\n BookingReference: string;\n Insured: Insured;\n Assets?: RentalAsset[];\n Accessories: AssetAccessory[];\n AutoRenew: boolean;\n BookingPlatformDomain?: string;\n BookingLineItems?: BookingLineItem[];\n}\n\nexport interface RentalClaim {\n ID: string;\n MerchantID: string;\n RentalWaiverID: string;\n\n State: RentalClaimState;\n Details: RentalClaimDetails;\n}\n\nexport enum RentalClaimState {\n unknown = '',\n logged = 'Logged',\n submitted = 'Submitted',\n approved = 'Approved',\n denied = 'Denied',\n requires_information = 'RequiresInformation'\n}\n\nexport enum RentalDamageType {\n unknown = '',\n fire = 'Fire',\n theft = 'Theft',\n water = 'Water',\n accident = 'Accident',\n other = 'Other'\n}\n\nexport interface RentalClaimDetails {\n ClaimNumber: string;\n Assets: WaiverAsset[];\n\n IncidentDate: Date;\n IncidentCause: string;\n IncidentAddress: RentalClaimAddress;\n\n DamageType: RentalDamageType;\n\n PoliceReportFiled?: boolean;\n NameOfEmergencyDepartment?: string;\n ReportOrIncidentNumber?: string;\n SourceOfWaterDamage?: string;\n EquipmentExposedToRain?: boolean;\n HasOtherInsuranceCoverage?: boolean;\n}\n\nexport interface RentalClaimAddress {\n AddressLine1?: string;\n AddressLine2?: string;\n AddressCity?: string;\n AddressState?: string;\n AddressZipCode?: string;\n}\n\nexport enum TransactionType {\n credit = 'Credit',\n debit = 'Debit'\n}\n\nexport enum TransactionState {\n created = 'Created',\n pending = 'Pending',\n success = 'Success',\n failure = 'Failure',\n canceled = 'Canceled',\n unknown = ''\n}\n\nexport enum TransactionProcessor {\n stripe = 'Stripe',\n mercury_ach = 'MercuryACH',\n mercury_check = 'MercuryCheck'\n}\n\nexport interface Transaction {\n ID: string;\n PaymentAccountID: string;\n\n Type: TransactionType;\n State: TransactionState;\n Processor: TransactionProcessor;\n ProcessorID: string;\n\n Amount: number;\n Currency: string;\n\n CreatedAt: Date;\n UpdatedAt: Date;\n ClearedAt: Date;\n}\n\nexport enum BusinessLocationType {\n unknown = '',\n home = 'Home',\n buildingLeased = 'BuildingLeased',\n buildingOwned = 'BuildingOwned'\n}\n\nexport interface BusinessInformation {\n // Identity\n BusinessName: string;\n DBA: string;\n Email: string;\n Website: string;\n FirstName?: string;\n LastName?: string;\n PhoneNumber?: string;\n\n // Operation\n ProductVerticals: ProductType[];\n Operations?: BusinessOperationType[];\n InsuranceTypes?: BusinessInsuranceType[];\n NumberOfEmployees?: number;\n BusinessAgeInMonths?: number;\n OptInOysterBundle?: boolean;\n ManufactureOrWholesaleOwnBrand?: boolean;\n\n // Location\n MailingAddress?: Address;\n Address?: Address;\n SquareFootage?: number;\n LocationType?: BusinessLocationType;\n HasSprinklers?: boolean;\n BuildingValue?: number;\n BPPValue?: number;\n\n // Limits\n GLLimit?: number;\n BPPLimit?: number;\n BuildingLimit?: number;\n\n // Stats\n AnnualRevenue?: number;\n AnnualPayroll?: number;\n RetailRevenue?: number;\n WholesaleRevenue?: number;\n ManufacturingRevenue?: number;\n ServiceOrRepairRevenue?: number;\n RentalRevenue?: number;\n OtherRevenue?: number;\n RentalPercentGuided?: number;\n\n BPPDeductible?: number;\n BuildingDeductible?: number;\n\n PolicyStartDate?: Date;\n}\n\nexport enum WorflowStatus {\n Started = 'started',\n Completed = 'completed',\n Canceled = 'canceled',\n Failed = 'failed'\n}\n\nexport enum InsightSourceType {\n Shopify = 'shopify',\n PolicyDocument = 'policy_doc'\n}\n\nexport interface WorkflowError {\n Code: string;\n Desc: string;\n}\n\nexport interface Insights {\n ID: string;\n WorkflowID: string;\n Source: InsightSourceType;\n\n BusinessInformation: BusinessInformation;\n WorkflowStatus: WorflowStatus;\n WorkflowError: WorkflowError;\n}\n\nexport interface CommercialPolicyQuote {\n ID: string;\n\n Type: BusinessInsuranceType;\n Limit: number;\n Quote: Price;\n OysterPartnerQuote: Price;\n StartDate: Date;\n EndDate: Date;\n\n CreatedAt: Date;\n}\n\nexport interface BusinessInsuranceApplication {\n ID: string;\n MerchantID: string;\n\n BusinessInformation: BusinessInformation;\n Integrations?: MerchantIntegration[];\n FileIDs?: string[];\n\n // TODO: Rename this to Extractions\n Insights: Insights[];\n\n AggregatedInformation: BusinessInformation;\n\n Quotes?: CommercialPolicyQuote[];\n\n // TODO: Rename this to Insights\n AIInsights: string[];\n CreatedAt: Date;\n UpdatedAt: Date;\n}\n","import apm from '@oysterjs/core/apm';\n\nimport * as React from 'react';\nimport { MerchantUser, User } from '@oysterjs/types';\n\ninterface Auth {\n Token?: string;\n // TODO: the backend currently returns a separate field, `MerchantUser`\n // for merchant users. We should unify these based on the typing here.\n User?: T;\n MerchantUser?: MerchantUser;\n}\n\nexport type SetAuthFn = (auth: Auth) => void;\n\nexport const useAuth = (): [Auth, SetAuthFn] => {\n const getAuth = () => {\n const data = window.localStorage.getItem('oyster_token');\n if (data) {\n const auth: Auth = JSON.parse(data);\n apm().setUserContext({\n id: auth.User?.ID || auth.MerchantUser?.ID,\n email: auth.User?.Email || auth.MerchantUser?.Email\n });\n return auth;\n }\n\n return {};\n };\n\n const setAuth = (auth: Auth) => {\n apm().setUserContext({\n id: auth.User?.ID || auth.MerchantUser?.ID,\n email: auth.User?.Email || auth.MerchantUser?.Email\n });\n\n window.localStorage.setItem('oyster_token', JSON.stringify(auth));\n _setAuth(auth);\n };\n\n const [auth, _setAuth] = React.useState(getAuth());\n\n React.useEffect(() => {\n const updateAuth = () => setAuth(getAuth());\n window.addEventListener('storage', updateAuth);\n return () => window.removeEventListener('storage', updateAuth);\n }, []);\n\n return [auth, setAuth];\n};\n\nexport const getToken = (): string | null => {\n const data = window.localStorage.getItem('oyster_token');\n return !data ? null : JSON.parse(data)?.Token;\n};\n\nexport const getUser = (): T | null => {\n const data = window.localStorage.getItem('oyster_token');\n return !data ? null : JSON.parse(data)?.User || JSON.parse(data)?.MerchantUser;\n};\n\nexport const resetToken = (redirect?: string): void => {\n window.localStorage.removeItem('oyster_token');\n window.location.href = `/signin${redirect ? `?redirect=${encodeURIComponent(redirect)}` : ''}`;\n};\n","export type Environment = 'production' | 'staging' | 'dev' | 'local';\n\nexport type Service = 'getoyster' | 'dashboard' | 'partners' | 'oysterjs' | 'webhooks';\nexport type Backend = 'admin' | 'api' | 'merchant' | 'integrate' | 'metrics' | 'statics';\n\nconst getEnvironment = (): Environment => {\n if (typeof window === 'undefined') {\n return 'local';\n }\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n const environment = process.env.OYSTER_ENVIRONMENT || window?.oyster?.opts?.environment;\n switch (environment) {\n case 'production':\n case 'staging':\n case 'dev':\n case 'local':\n return environment;\n default:\n return 'local';\n }\n};\n\nconst getApiBaseUrl = (): string => {\n switch (getEnvironment()) {\n case 'production':\n return 'https://api.withoyster.com';\n case 'staging':\n return 'https://api.staging.withoyster.com';\n case 'dev':\n return devServiceUrl('api');\n case 'local':\n return 'http://localhost:8080';\n }\n};\n\nconst getAdminBaseUrl = (): string => {\n switch (getEnvironment()) {\n case 'production':\n return 'https://admin.oysterinc.net';\n case 'staging':\n return 'https://admin.staging.oysterinc.net';\n case 'dev':\n return devServiceUrl('admin');\n case 'local':\n return 'http://localhost:8080';\n }\n};\n\nconst getMerchantBaseUrl = (): string => {\n switch (getEnvironment()) {\n case 'production':\n return 'https://merchant.withoyster.com';\n case 'staging':\n return 'https://merchant.staging.withoyster.com';\n case 'dev':\n return devServiceUrl('merchant');\n case 'local':\n return 'http://localhost:8084';\n }\n};\n\nconst getMetricsBaseUrl = (): string => {\n switch (getEnvironment()) {\n case 'production':\n return 'https://mtx.withoyster.com';\n case 'staging':\n return 'https://mtx.staging.withoyster.com';\n case 'dev':\n return devServiceUrl('metrics');\n case 'local':\n return 'http://localhost:8083';\n }\n};\n\nconst getStaticsBaseUrl = (): string => {\n switch (getEnvironment()) {\n case 'production':\n return 'https://s.withoyster.com';\n case 'staging':\n return 'https://s.staging.withoyster.com';\n case 'dev':\n return devServiceUrl('statics');\n case 'local':\n return 'http://localhost:8081';\n }\n};\n\nconst getIntegrateBaseUrl = (): string => {\n switch (getEnvironment()) {\n case 'production':\n return 'https://integrate.withoyster.com';\n case 'staging':\n return 'https://integrate.staging.withoyster.com';\n case 'dev':\n return devServiceUrl('integrate');\n case 'local':\n return 'http://localhost:8085';\n }\n};\n\nconst getOysterJsBaseUrl = (): string => {\n switch (getEnvironment()) {\n case 'production':\n return 'https://js.withoyster.com';\n case 'staging':\n return 'https://js.staging.withoyster.com';\n case 'dev':\n return devServiceUrl('oysterjs');\n case 'local':\n return 'http://localhost:8001';\n }\n};\n\nconst getDashboardBaseUrl = (): string => {\n switch (getEnvironment()) {\n case 'production':\n return 'https://dashboard.withoyster.com';\n case 'staging':\n return 'https://dashboard.staging.withoyster.com';\n case 'dev':\n return devServiceUrl('dashboard');\n case 'local':\n return 'http://localhost:8180';\n }\n};\n\nconst getGetOysterBaseUrl = (): string => {\n switch (getEnvironment()) {\n case 'production':\n return 'https://get.withoyster.com';\n case 'staging':\n return 'https://get.staging.withoyster.com';\n case 'dev':\n return devServiceUrl('getoyster');\n case 'local':\n return 'http://localhost:8181';\n }\n};\n\nconst getPartnersBaseUrl = (): string => {\n switch (getEnvironment()) {\n case 'production':\n return 'https://partners.withoyster.com';\n case 'staging':\n return 'https://partners.staging.withoyster.com';\n case 'dev':\n return devServiceUrl('partners');\n case 'local':\n return 'http://localhost:8182';\n }\n};\n\nconst getWebhooksBaseUrl = (): string => {\n switch (getEnvironment()) {\n case 'production':\n return 'https://webhooks.withoyster.com';\n case 'staging':\n return 'https://webhooks.staging.withoyster.com';\n case 'dev':\n return devServiceUrl('webhooks');\n case 'local':\n return 'http://localhost:8080';\n }\n};\n\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore\nconst getServiceName = (): Service => (process.env.SERVICE_NAME as Service) || '';\n\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore\nconst getServiceVersion = (): string => process.env.SERVICE_VERSION || '';\n\nconst devServiceUrl = (service: string) => {\n // Get the user from the URL\n const [, user] = window.location.host.split('.');\n\n // Host will be in the format of {service}.{user}.dev.oysterinc.net\n return `https://${service}.${user}.dev.oysterinc.net`;\n};\n\nconst getSecret = (secretName: string, devSecret?: string) =>\n devSecret || window?.['oyster']?.['opts']?.[secretName];\n\n// Configuration defines the various configuration values that can\n// be set on the app\ntype StaticConfiguration = {\n readonly serviceName: Service;\n readonly serviceVersion: string;\n readonly environment: Environment;\n readonly serviceBaseUrl: { [K in Service]: string };\n readonly backendBaseUrl: { [K in Backend]: string };\n readonly secrets: {\n readonly mapboxPublicKey?: string;\n readonly shopifyPublicKey?: string;\n readonly stripePublicKey?: string;\n readonly coterieStripePublicKey?: string;\n readonly sentryDsn?: string;\n };\n};\n\ntype DynamicConfiguration = {\n readonly merchant?: {\n readonly id?: string;\n readonly apiKey?: string;\n readonly integrationId?: string;\n };\n};\n\nlet dynamicConfig: DynamicConfiguration = {};\nexport const configure = (opts: DynamicConfiguration) => {\n dynamicConfig = opts;\n};\n\nexport type Configuration = StaticConfiguration & DynamicConfiguration;\n\n// config builds and exports the configuration for usage.\nexport default (): Configuration => ({\n serviceName: getServiceName(),\n serviceVersion: getServiceVersion(),\n environment: getEnvironment(),\n serviceBaseUrl: {\n dashboard: getDashboardBaseUrl(),\n getoyster: getGetOysterBaseUrl(),\n partners: getPartnersBaseUrl(),\n oysterjs: getOysterJsBaseUrl(),\n webhooks: getWebhooksBaseUrl()\n },\n backendBaseUrl: {\n admin: getAdminBaseUrl(),\n api: getApiBaseUrl(),\n merchant: getMerchantBaseUrl(),\n integrate: getIntegrateBaseUrl(),\n metrics: getMetricsBaseUrl(),\n statics: getStaticsBaseUrl()\n },\n secrets: {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n mapboxPublicKey: getSecret('mapboxPublicKey', process.env.MAPBOX_PUBLIC_KEY),\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n shopifyPublicKey: getSecret('shopifyPublicKey', process.env.SHOPIFY_PUBLIC_KEY),\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n stripePublicKey: getSecret('stripePublicKey', process.env.STRIPE_PUBLIC_KEY),\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n coterieStripePublicKey: getSecret(\n 'coterieStripePublicKey',\n process.env.COTERIE_STRIPE_PUBLIC_KEY\n ),\n sentryDsn: process.env.SENTRY_DSN\n },\n ...dynamicConfig\n});\n","import { GraphQLErrors } from '@apollo/client/errors';\nimport { ValidationError } from '@oysterjs/types';\n\nexport const isValidationError = (err: unknown): err is ValidationError => {\n return (err as { Type?: string }).Type === 'validation_error';\n};\n\nexport const isUnderwritingError = (\n err: unknown\n): err is { Message: string; Type: string; Code: string } => {\n return (err as { Type?: string }).Type === 'underwriting_error';\n};\n\nexport const isType = (\n err: unknown,\n type: string\n): err is { Message: string; Type: string; Code: string } => {\n return (err as { Type?: string }).Type === type;\n};\n\nexport enum ErrorCode {\n unknown = '',\n unsupportedGeo = 'unsupported_geo',\n riskRejected = 'risk_rejected',\n alreadySubmitted = 'already_submitted',\n submissionFailed = 'submission_failed'\n}\n\nexport enum ErrorType {\n unknown = '',\n\n networkError = 'js.fetch.network_error',\n unknownApiError = 'js.api.unknown_error',\n\n validationError = 'validation_error',\n processingError = 'processing_error',\n underwritingError = 'underwriting_error',\n preconditionFailed = 'precondition_failed'\n}\n\nexport interface WrappedErrorOptions {\n code: ErrorCode;\n type: ErrorType;\n details?: string;\n metadata?: Record;\n cause?: WrappedError | Error;\n}\n\nexport class WrappedError extends Error {\n constructor(\n message: string,\n public opts: WrappedErrorOptions = { code: ErrorCode.unknown, type: ErrorType.unknown }\n ) {\n super(message);\n this.name = 'WrappedError';\n }\n\n type = () => {\n return this.opts.type;\n };\n\n code = () => {\n return this.opts.code;\n };\n\n details = () => {\n return this.opts.details;\n };\n\n cause = () => {\n return this.opts.cause;\n };\n\n metadata = () => {\n return this.opts.metadata || {};\n };\n\n getValidationError = () => {\n return {\n Field: this.opts.metadata?.Field || '',\n SubField: this.opts.metadata?.SubField || '',\n Message: this.message\n };\n };\n\n getGraphQLSchemaValidationError = () => {\n return {\n field: this.opts.metadata?.Field || '',\n subField: this.opts.metadata?.SubField || '',\n message: this.message\n };\n };\n\n // Figure out how to type this better\n // eslint-disable-next-line\n static fromApiError(err: any): WrappedError {\n const x = new WrappedError(err.Message, {\n code: err.Code || ErrorCode.unknown,\n type: err.Type || ErrorType.unknown,\n metadata: {\n Field: err.Field,\n SubField: err.SubField,\n RequestID: err.RequestID\n }\n });\n return x;\n }\n\n static fromValidationError(validationError: ValidationError): WrappedError {\n return new WrappedError(validationError.Message, {\n code: ErrorCode.unknown,\n type: ErrorType.validationError,\n metadata: {\n Field: validationError.Field,\n SubField: validationError.SubField || ''\n }\n });\n }\n\n static fromGraphQLError(errors: GraphQLErrors): WrappedError {\n return new WrappedError(errors.join(''), {\n code: ErrorCode.unknown,\n type: ErrorType.networkError\n });\n }\n\n // Figure out how to type this better\n // eslint-disable-next-line\n static asWrappedError(err: any): WrappedError {\n // TODO: this does not yet handle converting non-WrappedErrors\n // to WrappedError, as Javascript is weird about `instanceof`\n // mixed with catch blocks.\n return err;\n }\n}\n","import { getToken, resetToken } from '@oysterjs/core/auth';\nimport config from '@oysterjs/core/config';\nimport { ErrorCode, ErrorType, WrappedError } from '@oysterjs/core/errors';\n\nexport const getEncodedQueryString = (m: Record): string => {\n const params = new URLSearchParams();\n Object.entries(m).forEach(([key, value]) => {\n if (value) {\n params.set(key, value.toString());\n }\n });\n\n return params.toString();\n};\n\ninterface RequestOpts extends RequestInit {\n redirectUrl?: string;\n disableUnauthorizedRedirect?: boolean;\n}\n\nexport const Get = (path: RequestInfo, init?: RequestOpts) =>\n request(path, undefined, { ...init, method: 'GET' });\n\nexport const Post = (path: RequestInfo, json?: unknown, opts?: RequestOpts) =>\n request(path, json ? JSON.stringify(json) : undefined, { ...opts, method: 'POST' });\n\nexport const Put = (path: RequestInfo, json?: unknown, opts?: RequestOpts) =>\n request(path, json ? JSON.stringify(json) : undefined, { ...opts, method: 'PUT' });\n\nexport const Delete = (path: RequestInfo, json?: unknown, opts?: RequestOpts) =>\n request(path, json ? JSON.stringify(json) : undefined, { ...opts, method: 'DELETE' });\n\nconst request = (path: RequestInfo, json: string | undefined, opts: RequestOpts): Promise =>\n fetch(config().backendBaseUrl.api + path, {\n ...opts,\n mode: 'cors',\n credentials: 'include',\n headers: {\n ...(json ? { 'Content-Type': 'application/json' } : {}),\n ...(getToken() ? { Authorization: `Bearer ${getToken()}` } : {}),\n ...opts.headers\n },\n body: opts.body || json\n })\n .catch((err) => {\n throw new WrappedError('Error performing network request', {\n code: ErrorCode.unknown,\n type: ErrorType.networkError,\n details: `Error performing ${opts.method} ${path}`,\n cause: err\n });\n })\n .then((res) =>\n res.json().then((data) => {\n if (res.status < 300 && data) {\n return data;\n }\n\n if (!opts.disableUnauthorizedRedirect && res.status === 401) {\n resetToken(opts.redirectUrl);\n return data;\n }\n\n if (data.Error) {\n throw WrappedError.fromApiError(data.Error);\n }\n\n throw new WrappedError(`An unknown error with status ${res.status} occurred`, {\n type: ErrorType.unknownApiError,\n code: ErrorCode.unknown,\n details: data.toString()\n });\n })\n );\n","import apm from '@oysterjs/core/apm';\nimport { FeatureFlag, MerchantUser } from '@oysterjs/types';\nimport { Get, getEncodedQueryString } from './base';\nimport { getUser } from '@oysterjs/core/auth';\nimport config from '@oysterjs/core/config';\n\nconst getFlagOptions = () =>\n getEncodedQueryString({\n MerchantAPIKey: config().merchant?.apiKey,\n MerchantID: getUser()?.MerchantID,\n UserID: apm().getSession().userId\n });\n\nconst evaluateFlag = (flagId: string) => Get(`/flag/${flagId}?${getFlagOptions()}`);\n\nconst listFlags: () => Promise<{ FeatureFlags: FeatureFlag[] }> = () =>\n Get(`/flags?${getFlagOptions()}`);\n\nconst evaluateBooleanFlag = (flag: string) =>\n evaluateFlag<{ Enabled: boolean }>(flag).then((d) => d.Enabled);\n\nexport const shouldRender = () => evaluateBooleanFlag('render_widget');\n\nexport const shouldRenderPostCheckoutOffer = () =>\n evaluateBooleanFlag('render_post_checkout_offer');\n\nexport const enableDefaultInsurance = () => evaluateBooleanFlag('enable_default_insurance');\n\nexport const enableCartWidget = () => evaluateBooleanFlag('enable_cart_widget');\n\nexport const shouldRenderOnelinerWidget = () => evaluateBooleanFlag('force_render_oneliner_widget');\n\nexport const shouldRenderLandingPage = () => evaluateBooleanFlag('render_marketing_page');\n\nexport const enableCommercialOauth = () => evaluateBooleanFlag('enable_commercial_oauth');\n\nexport const enableCommercialDocumentExtraction = () =>\n evaluateBooleanFlag('enable_commercial_document_extraction');\n\nexport const allowCartWidgetObservables = () =>\n evaluateBooleanFlag('allow_cart_widget_observables');\n\nexport const retrieveAllFeatureFlags = (): Promise =>\n listFlags().then((res) => res.FeatureFlags);\n","import { FeatureFlag, FeatureFlagExperiments } from '@oysterjs/types';\nimport { retrieveAllFeatureFlags } from './flag';\n\nlet appConfiguration: AppConfiguration;\n\nexport interface AppConfiguration {\n ensureFeatureFlags(): Promise;\n getAll(): Promise;\n getAllSync(): FeatureFlag[];\n get(flagId: string): Promise;\n update(flagId: string, updateFunction: (featureFlag: FeatureFlag) => void): Promise;\n}\n\nclass MainAppConfiguration implements AppConfiguration {\n featureFlags: FeatureFlag[] = [];\n retrieveFlagsPromise: Promise | undefined;\n\n async ensureFeatureFlags(): Promise {\n if (this.featureFlags.length === 0) {\n if (!this.retrieveFlagsPromise) {\n // Set a 2 second timeout so we don't have missing events\n // if FF API is not available\n const timeoutPromise = new Promise((resolve) => {\n const timeoutId = setTimeout(() => {\n clearTimeout(timeoutId);\n resolve(undefined);\n }, 2000);\n });\n\n this.retrieveFlagsPromise = Promise.race([timeoutPromise, retrieveAllFeatureFlags()]);\n }\n\n // Update flags variable on resolve\n await this.retrieveFlagsPromise?.then((res) => {\n if (res) {\n this.featureFlags = res;\n }\n });\n\n // Clean up the promise\n this.retrieveFlagsPromise = undefined;\n }\n\n return;\n }\n\n async getAll(): Promise {\n await this.ensureFeatureFlags();\n return this.featureFlags;\n }\n\n getAllSync(): FeatureFlag[] {\n return this.featureFlags;\n }\n\n async get(flagId: string): Promise {\n await this.ensureFeatureFlags();\n return this.featureFlags.find((ff) => ff.ID === flagId);\n }\n\n async update(flagId: string, updateFunction: (featureFlag: FeatureFlag) => void): Promise {\n await this.ensureFeatureFlags();\n const updateIndex = this.featureFlags.findIndex((f) => f.ID === flagId);\n if (updateIndex >= 0) {\n // Update existing feature flags\n updateFunction(this.featureFlags[updateIndex]);\n } else {\n // Create a new feature flag and insert it\n const ff: FeatureFlag = {\n ID: flagId,\n Value: false\n };\n updateFunction(ff);\n\n this.featureFlags = [...this.featureFlags, ff];\n }\n\n return;\n }\n}\n\nexport class MockAppConfiguration implements AppConfiguration {\n featureFlags = [\n {\n ID: FeatureFlagExperiments.oneliner_widget,\n Value: true\n }\n ];\n\n async ensureFeatureFlags(): Promise {\n return;\n }\n async getAll(): Promise {\n return this.featureFlags;\n }\n getAllSync(): FeatureFlag[] {\n return this.featureFlags;\n }\n async get(flagId: string): Promise {\n return this.featureFlags.find((ff) => ff.ID === flagId);\n }\n async update(): Promise {\n // Do nothing for now\n return;\n }\n}\n\n// This needs to be called after apm has been initialized.\nexport const init = async () => {\n appConfiguration = new MainAppConfiguration();\n await appConfiguration.ensureFeatureFlags();\n};\n\nexport const setAppConfiguration = (config: AppConfiguration) => {\n appConfiguration = config;\n};\n\n// eslint-disable-next-line\n// @ts-ignore\nexport default (): AppConfiguration => appConfiguration;\n","import * as Sentry from '@sentry/react';\nimport appConfiguration from './api/appConfiguration';\nimport { ReferralSubChannel } from '@oysterjs/types';\nimport { getUser } from './auth';\nimport config from './config';\n\nclass Apm {\n constructor(private session: Session) {}\n\n // eslint-disable-next-line\n captureError(error: any) {\n if (Sentry.isInitialized()) {\n Sentry.captureException(error);\n }\n }\n\n setUserContext(userObject: { id?: string; email?: string }) {\n if (Sentry.isInitialized()) {\n Sentry.setUser(userObject);\n }\n this.session.userId = userObject.id?.toString() || this.session.userId;\n }\n\n setProductInfo(product) {\n if (!product) return;\n\n const key = `oyster_product_for_user_${this.getSession().userId}`;\n window.sessionStorage.setItem(key, JSON.stringify(product));\n\n let itemList: string[] = JSON.parse(\n window.sessionStorage.getItem('oyster_session_product_keys') || '[]'\n );\n\n if (itemList) {\n itemList = itemList.filter((item) => item !== key);\n window.sessionStorage.setItem(\n 'oyster_session_product_keys',\n JSON.stringify([...itemList, key])\n );\n } else {\n window.sessionStorage.setItem('oyster_session_product_keys', JSON.stringify([key]));\n }\n }\n\n getSession(): Session {\n return this.session;\n }\n\n getSessionId() {\n return Sentry.getReplay()?.getReplayId();\n }\n\n getProductInfo() {\n const key = `oyster_product_for_user_${this.getSession().userId}`;\n return JSON.parse(window.sessionStorage.getItem(key) || '\"\"');\n }\n\n clearProductInfo() {\n const key = `oyster_product_for_user_${this.getSession().userId}`;\n window.sessionStorage.removeItem(key);\n }\n\n setReferralSubChannel(subChannel?: ReferralSubChannel) {\n if (!subChannel) {\n this.clearReferralSubChannel();\n return;\n }\n\n const key = `oyster_referral_subchannel_for_user_${this.getSession().userId}`;\n window.sessionStorage.setItem(key, subChannel);\n }\n\n getReferralSubChannel(): ReferralSubChannel {\n const key = `oyster_referral_subchannel_for_user_${this.getSession().userId}`;\n return (window.sessionStorage.getItem(key) || ReferralSubChannel.unknown) as ReferralSubChannel;\n }\n\n clearReferralSubChannel() {\n const key = `oyster_referral_subchannel_for_user_${this.getSession().userId}`;\n window.sessionStorage.removeItem(key);\n }\n\n // eslint-disable-next-line\n async sendEvent(id: string, metadata?: Record) {\n const productType = this.getProductInfo();\n const data = {\n UserID: getUser()?.ID || this.session.userId,\n PageLoadID: this.session.pageLoadId\n };\n const featureFlags = await appConfiguration().getAll();\n\n // Get formatted feature flags\n let formattedFeatureFlags = {};\n featureFlags.forEach(\n (ff) =>\n (formattedFeatureFlags = {\n ...formattedFeatureFlags,\n [ff.ID]: ff.Value\n })\n );\n\n const updatedMetadata = {\n ...metadata,\n ProductType: productType,\n feature_flags: formattedFeatureFlags,\n referral_sub_channel: metadata?.referral_sub_channel || this.getReferralSubChannel() // Prioritize passed-in values\n };\n\n const url = `${config().backendBaseUrl.metrics}/event/${id}`;\n\n fetch(url, {\n method: 'POST',\n mode: 'cors',\n credentials: 'same-origin',\n headers: {\n 'Content-type': 'application/json',\n 'X-Merchant-API-Key': config().merchant?.apiKey || metadata?.merchant_api_key || '',\n 'X-Merchant-Integration-ID':\n config().merchant?.integrationId || metadata?.integration_id || ''\n },\n body: JSON.stringify({ ...data, Metadata: updatedMetadata })\n });\n }\n}\n\nlet apm: Apm;\n\ninterface Session {\n userId: string;\n pageLoadId: string;\n merchantId?: string;\n}\n\nconst initSession = (): Session => {\n const getRandomString = (len: number): string => {\n const characters = '0123456789abcdef';\n let str = '';\n for (let i = 0; i < len; i++) {\n str += characters.charAt(Math.floor(Math.random() * characters.length));\n }\n return str;\n };\n\n // Check if we have an authenticated user ID\n let userId = getUser()?.ID;\n\n // Check if we already stored an ID locally\n if (!userId) {\n userId = window.localStorage.getItem('oyster_user_id') || undefined;\n }\n\n // Otherwise, generate an ID and store it\n if (!userId) {\n userId = getRandomString(24);\n window.localStorage.setItem('oyster_user_id', userId);\n }\n\n return {\n userId,\n pageLoadId: getRandomString(24)\n };\n};\n\nexport const init = (): void => {\n const session = initSession();\n\n Sentry.init({\n dsn: config().secrets.sentryDsn,\n integrations: [\n Sentry.browserTracingIntegration(),\n Sentry.browserProfilingIntegration(),\n Sentry.captureConsoleIntegration({\n levels: ['error']\n }),\n Sentry.extraErrorDataIntegration(),\n Sentry.httpClientIntegration(),\n Sentry.reactRouterV5BrowserTracingIntegration({ history }),\n Sentry.replayIntegration({\n networkDetailAllowUrls: [\n window.location.origin,\n config().backendBaseUrl.api,\n config().backendBaseUrl.integrate,\n config().backendBaseUrl.merchant,\n config().backendBaseUrl.metrics,\n config().backendBaseUrl.statics\n ],\n networkCaptureBodies: true,\n networkRequestHeaders: ['X-Merchant-Integration-ID', 'X-Merchant-API-Key'],\n maskAllText: false,\n blockAllMedia: false\n }),\n Sentry.sessionTimingIntegration()\n ],\n\n autoSessionTracking: true,\n enabled: true,\n enableTracing: true,\n\n environment: config().environment,\n release: config().serviceVersion,\n\n // Set tracesSampleRate to 1.0 to capture 100%\n // of transactions for performance monitoring.\n tracesSampleRate: 1.0,\n\n // Set `tracePropagationTargets` to control for which URLs distributed tracing should be enabled\n tracePropagationTargets: [\n config().backendBaseUrl.api,\n config().backendBaseUrl.integrate,\n config().backendBaseUrl.merchant,\n config().backendBaseUrl.metrics,\n config().backendBaseUrl.statics\n ],\n\n // Capture Replay for 100% of all sessions,\n // plus for 100% of sessions with an error\n replaysSessionSampleRate: 1.0,\n replaysOnErrorSampleRate: 1.0\n });\n\n apm = new Apm(session);\n};\n\nexport const embeddedInit = (): void => {\n const session = initSession();\n apm = new Apm(session);\n};\n\n// eslint-disable-next-line\n// @ts-ignore\nexport default (): Apm => apm;\n","import styled from 'styled-components';\n\nconst OysterLogoContainer = styled.div<{ inline?: boolean }>`\n display: ${(props) => (props.inline ? 'inline-block' : 'flex')};\n vertical-align: middle;\n justify-content: left;\n align-items: center;\n padding: ${(props) => (props.inline ? '0' : '10px 10px 10px 0px')};\n\n svg {\n vertical-align: middle;\n }\n`;\n\nconst OysterWordMarkContainer = styled.div`\n display: inline-block;\n vertical-align: middle;\n justify-content: left;\n align-items: center;\n padding: 0;\n\n svg {\n vertical-align: middle;\n }\n`;\n\nexport const OysterLogo = ({\n scale,\n inline,\n verticalAlign,\n light,\n color,\n textTransform\n}: {\n scale?: number;\n verticalAlign?: string;\n inline?: boolean;\n light?: boolean;\n color?: string;\n textTransform?: string;\n}): JSX.Element => (\n \n \n \n \n \n \n \n \n \n);\n\nexport const OysterWordMark = ({\n scale,\n forceLight\n}: {\n scale?: number;\n forceLight?: boolean;\n}): JSX.Element => (\n \n \n \n \n \n \n \n \n \n);\n","import config from '@oysterjs/core/config';\nimport { PolicyType } from '@oysterjs/types';\nimport styled from 'styled-components';\n\nconst FooterStandardContainer = styled.div`\n display: flex;\n justify-content: space-between;\n\n @media (max-width: 500px) {\n flex-direction: column;\n justify-content: flex-start;\n align-items: center;\n gap: 10px;\n }\n`;\n\nconst FooterContainer = styled.div`\n padding: 20px 40px;\n display: flex;\n flex-direction: column;\n font-size: 0.8em;\n justify-content: space-between;\n gap: 10px;\n`;\n\nconst FooterLinksContainer = styled.div`\n display: flex;\n\n a {\n &:not(:last-child) {\n margin-right: 20px;\n }\n }\n`;\n\nconst OysterCopyright = (props: { color?: string }) => (\n
\n © 2024 Oyster Technologies, Inc.\n
\n);\n\nconst CustomFooter = (props: { policyType?: PolicyType }) => {\n switch (props.policyType) {\n case PolicyType.chubbJewelry:\n return (\n
\n Insurance described is offered by Oyster Insurance Agency, LLC (California license no.\n 6006158). Insurance is underwritten and provided by Federal Insurance Company (Whitehouse\n Station, NJ), a Chubb ® company. Chubb is the marketing name used to refer to\n subsidiaries of Chubb Limited providing insurance and related services. Coverage is\n subject to the language of the policies as actually issued.\n
\n );\n default:\n return <>;\n }\n};\n\nexport const FooterStandard = (props: { policyType?: PolicyType; color?: string }): JSX.Element => (\n \n \n \n \n \n Contact Us\n \n \n Terms\n \n \n Privacy\n \n \n \n \n \n);\n\nconst FooterMinimalContainer = styled.div`\n padding: 20px 40px;\n display: flex;\n font-size: 0.8em;\n justify-content: space-around;\n`;\n\nexport const FooterMinimal = (props: { color?: string }): JSX.Element => (\n \n \n \n);\n","import React from 'react';\nimport { PolicyType } from '@oysterjs/types';\nimport styled from 'styled-components';\nimport { FooterMinimal, FooterStandard } from './footer';\n\nconst PaneContainer = styled.div<{ gap: number }>`\n display: flex;\n flex-direction: row;\n width: 100%;\n gap: ${(props) => props.gap}px;\n\n @media (max-width: 700px) {\n display: block;\n }\n`;\n\nconst Pane = styled.div<{ gap: number; paneWidth: number }>`\n width: calc(${(props) => props.paneWidth}% - ${(props) => props.gap / 2}px);\n\n @media (max-width: 700px) {\n width: 100%;\n }\n`;\n\nexport const TwoPaneContainer: React.FunctionComponent<\n React.PropsWithChildren<{\n gap?: number;\n leftPaneWidth?: number;\n rightPane: JSX.Element;\n }>\n> = (props) => (\n \n \n {props.children}\n \n \n {props.rightPane}\n \n \n);\n\nexport enum FooterStyle {\n none,\n minimal,\n standard\n}\n\nconst PageContainerDiv = styled.div<{\n width?: number | string;\n marginTop?: number;\n marginRight?: number;\n marginLeft?: number;\n marginBottom?: number;\n}>`\n max-width: ${(props) =>\n props.width ? (isNaN(Number(props.width)) ? props.width : `${props.width}px`) : '1000px'};\n width: 100%;\n margin-top: ${(props) => (props.marginTop !== undefined ? `${props.marginTop}px` : 'auto')};\n margin-right: ${(props) => (props.marginRight !== undefined ? `${props.marginRight}px` : 'auto')};\n margin-bottom: ${(props) =>\n props.marginBottom !== undefined ? `${props.marginBottom}px` : 'auto'};\n margin-left: ${(props) => (props.marginLeft !== undefined ? `${props.marginLeft}px` : 'auto')};\n`;\n\nexport const PageContainer: React.FunctionComponent<\n React.PropsWithChildren<{\n width?: number | string;\n footerStyle?: FooterStyle;\n marginTop?: number;\n marginRight?: number;\n marginLeft?: number;\n marginBottom?: number;\n policyType?: PolicyType;\n footerTextColor?: string;\n }>\n> = (props) => {\n return (\n \n {props.children}\n {(props.footerStyle === undefined || props.footerStyle === FooterStyle.standard) && (\n \n )}\n {props.footerStyle === FooterStyle.minimal && }\n \n );\n};\n\nexport const PageSection = styled.div<{\n noBorder?: boolean;\n noPadding?: boolean;\n centered?: boolean;\n customPadding?: string;\n}>`\n padding: ${(props) =>\n props.customPadding ? props.customPadding : props.noPadding ? '0px 40px' : '20px 40px'};\n border-bottom: ${(props) => (props.noBorder ? '0' : '1px solid #f8f8f8')};\n ${(props) =>\n !props.centered\n ? ``\n : `\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n `}\n\n h2 {\n color: #666666;\n }\n`;\n","import * as React from 'react';\nimport styled from 'styled-components';\n\nimport apm from '@oysterjs/core/apm';\nimport { WrappedError } from '@oysterjs/core/errors';\nimport { IoCheckmark, IoClipboard } from 'react-icons/io5';\n\nconst ErrorIllustration = styled.img<{ forceMobile?: boolean }>`\n border-radius: 8px;\n width: 100%;\n max-width: ${(props) => (props.forceMobile ? '400px' : '300px')};\n transform: scaleX(-1);\n\n @media (max-width: 700px) {\n max-width: 250px;\n }\n\n @media (max-width: 600px) {\n max-width: 400px;\n }\n`;\n\nconst ErrorContainer = styled.div<{ forceMobile?: boolean }>`\n padding: 40px;\n display: flex;\n max-width: 1000px;\n box-sizing: border-box;\n margin: 0 auto;\n flex-direction: ${(props) => (props.forceMobile ? 'column' : 'row')};\n gap: 20px;\n align-items: flex-start;\n\n @media (max-width: 600px) {\n flex-direction: column;\n }\n`;\n\nconst ErrorDetailsContainer = styled.div`\n position: relative;\n border-radius: 8px;\n padding: 20px;\n box-sizing: border-box;\n max-width: 500px;\n width: 100%;\n background: #eaeaea;\n display: flex;\n flex-direction: column;\n gap: 20px;\n`;\n\nconst ErrorDetailContainer = styled.div`\n gap: 10px;\n`;\n\nconst ErrorDetailTitle = styled.div`\n text-transform: uppercase;\n color: #666666;\n font-size: 0.7em;\n font-weight: 600;\n padding-bottom: 4px;\n`;\nconst ErrorDetailDescription = styled.pre`\n font-size: 1em;\n font-family: monospace;\n white-space: pre-wrap;\n`;\n\nexport const ErrorDetail = (props: { title: string; description: string }) => (\n \n {props.title}\n {props.description}\n \n);\n\nconst ErrorCopyData = styled.button`\n border: 1px solid #e8e8e8;\n position: absolute;\n top: 0;\n right: 0;\n border-top-right-radius: 8px;\n border-bottom-left-radius: 8px;\n background: rgba(255, 255, 255, 0.65);\n color: #666666;\n padding: 10px;\n display: flex;\n justify-content: center;\n align-items: center;\n transition: 0.15s ease-in-out all;\n cursor: pointer;\n\n &:hover:not(.active) {\n background: rgba(255, 255, 255, 0.85);\n color: #333333;\n }\n\n &:active:not(.active) {\n background: rgba(255, 255, 255, 1);\n }\n`;\n\nexport const ErrorDetails = (\n props: React.PropsWithChildren & { errorData: string }>\n) => {\n const [copied, setCopied] = React.useState(false);\n\n React.useEffect(() => {\n if (!copied) {\n return;\n }\n\n navigator.clipboard.writeText(props.errorData);\n setTimeout(() => setCopied(false), 3000);\n }, [copied]);\n\n return (\n \n setCopied(true)}>\n {copied ? : }\n \n {props.children}\n \n );\n};\n\nconst ErrorComponent = (props: { error: Error | WrappedError; forceMobile?: boolean }) => {\n const message = props.error.toString();\n const transaction = apm().getSession().pageLoadId;\n const errorData = [\n { title: 'Error', description: message },\n { title: 'Transaction ID', description: transaction }\n ].filter(({ description }) => !!description);\n\n return (\n \n \n
\n

Something went wrong.

\n

\n There was an error while trying to perform your requested action. If the issue persists,\n please contact our team at{' '}\n support@withoyster.com and include this error\n for reference.\n

\n `${d.title}\\n${d.description}`).join('\\n\\n')}>\n {errorData.map((data) => (\n \n ))}\n \n
\n
\n );\n};\n\n/*\n position: relative;\n border-radius: 8px;\n padding: 20px;\n box-sizing: border-box;\n max-width: 500px;\n width: 100%;\n background: #eaeaea;\n display: flex;\n flex-direction: column;\n gap: 20px;\n */\n\nconst ErrorDetailsV2 = (\n props: React.PropsWithChildren & { errorData: string }>\n) => {\n const [copied, setCopied] = React.useState(false);\n\n React.useEffect(() => {\n if (!copied) {\n return;\n }\n\n navigator.clipboard.writeText(props.errorData);\n setTimeout(() => setCopied(false), 3000);\n }, [copied]);\n\n return (\n
\n setCopied(true)}>\n {copied ? : }\n \n {props.children}\n
\n );\n};\n\nexport const ErrorComponentV2 = (props: { error: Error | WrappedError; forceMobile?: boolean }) => {\n const message = props.error.toString();\n const transaction = apm().getSessionId();\n const errorData = [\n { title: 'Error', description: message },\n { title: 'Session ID', description: transaction || '' }\n ].filter(({ description }) => !!description);\n\n return (\n
\n

Application error

\n

Something went wrong

\n

\n There was an error while trying to perform your requested action. If the issue persists,\n please contact our team via the chat widget or via email at{' '}\n support@withoyster.com and include this error\n for reference.\n

\n\n
\n
\n `${d.title}\\n${d.description}`).join('\\n\\n')}\n >\n {errorData\n .filter((d) => !!d.description)\n .map((data) => (\n \n ))}\n \n
\n
\n
\n );\n};\n\nexport default class ErrorBoundary extends React.Component<\n React.PropsWithChildren<{ forceMobile?: boolean }>,\n { error?: Error | WrappedError }\n> {\n constructor(props) {\n super(props);\n this.state = {};\n }\n\n static getDerivedStateFromError(err: Error) {\n return { error: err };\n }\n\n componentDidCatch(error) {\n apm().captureError(error);\n }\n\n render(): React.ReactNode {\n if (this.state.error) {\n return ;\n }\n\n return this.props.children;\n }\n}\n","import styled from 'styled-components';\n\nimport { OysterLogo } from '@oysterjs/ui/Logo';\nimport { PageContainer, PageSection } from '@oysterjs/ui/Page';\nimport { ProductType } from '@oysterjs/types';\nimport ErrorBoundary from '@oysterjs/ui/ErrorBoundary';\n\nconst ProductsContainer = styled.div`\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n gap: 10px;\n`;\n\nconst ProductContainer = styled.div`\n border-radius: 20px;\n padding: 20px;\n border: 2px dotted #f2f2f2;\n transition: all 0.15s ease-in-out;\n cursor: pointer;\n`;\n\nconst ProductImage = styled.img`\n display: block;\n height: 120px;\n width: 120px;\n`;\n\nconst ProductLabel = styled.div`\n font-weight: 500;\n font-size: 1em;\n width: 100%;\n text-align: center;\n padding: 10px 0px;\n`;\n\nconst Product = (props: { name: string; image: string }) => (\n
\n \n \n \n {props.name}\n
\n);\n\nconst ProductSelectionPage = (props: {\n onContinue: (selectedProductType: ProductType) => void;\n}): JSX.Element => {\n const products = [\n {\n name: 'ATV',\n type: ProductType.offroad,\n src: 'https://dashboard.withoyster.com/images/atv_insurance.svg',\n hideD2C: true\n },\n {\n name: 'Motorcycle',\n type: ProductType.motorcycle,\n src: 'https://dashboard.withoyster.com/images/motorcycle_insurance.svg',\n hideD2C: true\n },\n {\n name: 'Bike/eBike',\n type: ProductType.bike,\n src: 'https://dashboard.withoyster.com/images/bike_insurance.svg'\n },\n {\n name: 'Jewelry',\n type: ProductType.jewelry,\n src: 'https://dashboard.withoyster.com/images/jewelry_insurance.svg'\n },\n {\n name: 'Phone',\n type: ProductType.phone,\n src: 'https://dashboard.withoyster.com/images/phone_insurance.svg'\n },\n {\n name: 'Electronics',\n type: ProductType.electronics,\n src: 'https://dashboard.withoyster.com/images/electronics_insurance.svg'\n }\n ];\n\n return (\n <>\n \n

What do you want to insure?

\n \n {products\n .filter((product) => !product.hideD2C)\n .map((product) => (\n {\n props.onContinue(product.type);\n }}\n >\n \n \n ))}\n \n
\n \n );\n};\n\nexport default (props: { onContinue: (selectedProductType: ProductType) => void }): JSX.Element => (\n \n \n \n \n \n \n \n \n);\n","import React from 'react';\nimport styled from 'styled-components';\nimport { NumericFormat, OnValueChange } from 'react-number-format';\n\nexport const ErrorDisplay = styled.div`\n font-size: 0.8em;\n margin-top: 5px;\n color: #d1344b;\n`;\n\nexport const TextInputComponent = styled.input<{\n error?: string | null | false | true;\n currency?: boolean | false;\n}>`\n border: 0;\n outline: none;\n\n border-radius: 20px;\n border: ${(props) => (props.error ? '2px solid #d1344b' : '2px solid #e8e8e8')};\n box-sizing: border-box;\n font-family: 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;\n font-size: 1em;\n padding: 8px 16px;\n\n padding-left: ${(props) => props.currency && '30px'};\n\n width: 100%;\n\n transition: 0.15s all;\n\n &:focus {\n border: 2px solid #0ea5e9;\n }\n\n &:focus + .suggestions-container {\n display: block;\n }\n`;\n\nexport const TextInput = React.forwardRef(\n (\n props: React.AllHTMLAttributes & {\n error?: string | null | false | true;\n currency?: boolean | false;\n },\n ref: React.Ref\n ) => (\n <>\n
\n {props.currency && (\n \n $\n \n )}\n \n {props.error && props.error !== true && {props.error}}\n
\n \n )\n);\n\nexport const CurrencyComponent = styled(NumericFormat)<{\n error?: string | null | false | true;\n currency?: boolean | false;\n}>`\n border: 0;\n outline: none;\n\n border-radius: 20px;\n border: ${(props) => (props.error ? '2px solid #d1344b' : '2px solid #e8e8e8')};\n box-sizing: border-box;\n font-family: 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;\n font-size: 1em;\n padding: 8px 16px;\n\n width: 100%;\n\n transition: 0.15s all;\n\n &:focus {\n border: 2px solid #0ea5e9;\n }\n\n &:focus + .suggestions-container {\n display: block;\n }\n`;\n\nexport const CurrencyInput = (\n props: React.AllHTMLAttributes & {\n error?: string | null | false | true;\n currency?: boolean | false;\n value?: string | number;\n onValueChange?: OnValueChange;\n style?: React.CSSProperties;\n }\n) => {\n return (\n
\n \n {props.error && props.error !== true && {props.error}}\n
\n );\n};\n\nconst SuggestionsContainer = styled.div`\n position: absolute;\n display: none;\n box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.25);\n box-sizing: border-box;\n border-radius: 8px;\n z-index: 100;\n background: white;\n font-size: 0.9em;\n\n &:hover {\n display: block;\n }\n\n ul {\n list-style: none;\n margin: 0;\n padding: 0;\n }\n\n ul li {\n padding: 10px 20px;\n\n &:first-child {\n border-top-left-radius: 8px;\n border-top-right-radius: 8px;\n }\n\n &:last-child {\n border-bottom-left-radius: 8px;\n border-bottom-right-radius: 8px;\n }\n\n &:hover {\n background: #0ea5e9;\n color: white;\n cursor: pointer;\n }\n }\n`;\n\nconst Suggestions = (props: {\n suggestions: string[];\n onSelect?: (suggestion: string, index: number) => void;\n}) => (\n \n
    \n {props.suggestions.map((s, i) => (\n
  • (props.onSelect ? props.onSelect(s, i) : null)}>\n {s}\n
  • \n ))}\n
\n
\n);\n\ninterface AutocompleteTextInputProps {\n suggestions: string[];\n id?: string;\n value?: string;\n error?: boolean;\n disabled?: boolean;\n onChange?: (value: string) => void;\n onSelectSuggestion?: (suggestion: string, index: number) => void;\n}\n\nexport const AutocompleteTextInput = (props: AutocompleteTextInputProps): JSX.Element => {\n const [selectedSuggestion, setSelectedSuggestion] = React.useState(false);\n return (\n
\n {\n setSelectedSuggestion(false);\n props.onChange?.(e.currentTarget.value);\n }}\n disabled={props.disabled}\n />\n {props.suggestions.length > 0 && !selectedSuggestion && (\n {\n setSelectedSuggestion(true);\n props.onSelectSuggestion?.(suggestion, index);\n }}\n />\n )}\n
\n );\n};\nexport const TextAreaInputComponent = styled.textarea<{ error?: string | null | false }>`\n border: 0;\n outline: none;\n\n border-radius: 20px;\n border: ${(props) => (props.error ? '2px solid #d1344b' : '2px solid #e8e8e8')};\n box-sizing: border-box;\n font-family: 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;\n font-size: 1em;\n padding: 12px 16px;\n\n width: 100%;\n\n transition: 0.15s all;\n\n &:focus {\n border: 2px solid #0ea5e9;\n }\n`;\n\nexport const TextAreaInput: React.FunctionComponent<\n React.AllHTMLAttributes & { error?: string | null | false }\n> = (props) => (\n <>\n \n {props.error && {props.error}}\n \n);\n","import * as React from 'react';\n\nimport styled from 'styled-components';\nimport { IoCheckmarkSharp } from 'react-icons/io5';\n\nconst CoverageItemContainer = styled.div`\n display: flex;\n flex-direction: row;\n width: 100%;\n max-width: calc(50% - 5px);\n\n @media (max-width: 520px) {\n max-width: 100%;\n }\n`;\n\nconst CoverageItemCheckboxContainer = styled.div`\n padding-right: 5px;\n`;\n\nconst CoverageItemDetailsContainer = styled.div`\n flex-direction: column;\n`;\n\nconst CoverageItemTitle = styled.div`\n font-weight: 600;\n padding-bottom: 5px;\n`;\n\nconst CoverageItemDescription = styled.div`\n color: #999999;\n font-size: 0.8em;\n`;\n\nconst CoverageItemCheckbox = styled.div`\n padding-right: 5px;\n color: #0ea5e9;\n font-size: 1.5em;\n`;\n\nexport const CoverageItem: React.FunctionComponent<\n React.PropsWithChildren<{\n title: string;\n description: string;\n icon?: JSX.Element;\n iconColor?: string;\n }>\n> = (props) => (\n \n \n \n {props.icon || }\n \n \n \n {props.title}\n \n {props.description}\n \n \n \n);\n\nconst CoveragePageSectionContainer = styled.div`\n padding: 18px 0px;\n flex: 1 1 0;\n`;\n\nconst CoveragePageSectionTitle = styled.div<{ textColor?: string }>`\n padding: 0px 0px 18px 0px;\n width: 100%;\n\n span {\n font-weight: 600;\n font-size: 0.8em;\n color: ${(props) => props.textColor || '#999999'};\n text-transform: uppercase;\n display: flex;\n align-items: center;\n width: 100%;\n gap: 0.5em;\n\n &::after {\n content: '';\n background-color: ${(props) => props.textColor || '#999999'};\n flex-grow: 1;\n height: 1px;\n }\n }\n`;\n\nconst CoveragePageSectionContent = styled.div`\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n gap: 24px 10px;\n`;\n\nexport const CoveragePageSection: React.FunctionComponent<\n React.PropsWithChildren<{ title?: string; color?: string }>\n> = (props) => (\n \n {props.title && (\n \n {props.title}\n \n )}\n {props.children}\n \n);\n","export const Spinner = ({\n color,\n size,\n centerHorizontal\n}: {\n color?: string;\n size?: number;\n centerHorizontal?: boolean;\n}): JSX.Element => (\n \n \n \n \n \n);\n","import * as React from 'react';\nimport styled from 'styled-components';\nimport { ErrorDisplay, TextInputComponent } from './text';\nimport { IoAdd } from 'react-icons/io5';\nimport { Spinner } from '../Spinner';\n\nconst SelectInput = styled.select<{ error?: string | false | null }>`\n border: 0;\n outline: none;\n appearance: none;\n background: white;\n\n border-radius: 20px;\n border: ${(props) => (props.error ? '2px solid #d1344b' : '2px solid #e8e8e8')};\n box-sizing: border-box;\n font-size: 1em;\n padding: 8px 40px 8px 20px;\n\n width: 100%;\n\n transition: 0.15s all;\n\n &:focus {\n border: 2px solid #0ea5e9;\n }\n\n ::placeholder {\n color: #aaaaaa;\n }\n\n font-family: 'Inter', 'Helvetica Neue', 'Arial', sans-serif;\n`;\n\nconst Carat = styled.div`\n display: block;\n position: absolute;\n cursor: pointer;\n right: 1rem;\n top: 50%;\n margin-top: -1px;\n width: 0;\n height: 0;\n border-top: 5px solid #999;\n border-right: 5px solid transparent;\n border-left: 5px solid transparent;\n pointer-events: none;\n`;\n\nexport interface SelectProps {\n initialSelected?: string; // denotes uncontrolled\n selected?: string; // denotes controlled\n disabled?: boolean;\n style?: React.CSSProperties;\n\n error?: string | false | null;\n options?: { value: string; displayValue?: string }[];\n onChange?: (value: string) => void;\n}\n\nexport const Select = (\n props: React.AllHTMLAttributes & SelectProps\n): JSX.Element => {\n return (\n
\n
\n {\n e.stopPropagation();\n }}\n onChange={(e) => {\n e.stopPropagation();\n if (props.onChange) {\n props.onChange(e.currentTarget.value);\n }\n }}\n >\n {(props.options || []).map((option) => (\n \n ))}\n \n \n
\n {props.error && {props.error}}\n
\n );\n};\n\nexport const UnstyledSelect = (props: SelectProps): JSX.Element => {\n const [selected, setSelected] = React.useState(props.selected || props.initialSelected);\n\n React.useEffect(() => {\n setSelected(props.selected);\n }, [props.selected]);\n\n return (\n {\n e.stopPropagation();\n }}\n onChange={(e) => {\n e.stopPropagation();\n if (!props.selected) {\n setSelected(e.currentTarget.value);\n }\n if (props.onChange) {\n props.onChange(e.currentTarget.value);\n }\n }}\n value={selected}\n disabled={props.disabled}\n >\n {(props.options || []).map((option) => (\n \n ))}\n \n );\n};\n\nconst SearchableSelectOptionsContainer = styled.div`\n position: absolute;\n display: none;\n box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.25);\n box-sizing: border-box;\n border-radius: 8px;\n margin-top: 4px;\n z-index: 100;\n background: white;\n width: 100%;\n\n &:hover {\n display: block;\n }\n\n ul {\n list-style: none;\n margin: 0;\n padding: 0;\n }\n\n ul li {\n padding: 10px 20px;\n font-size: 0.9em;\n\n &:first-child {\n border-top-left-radius: 8px;\n border-top-right-radius: 8px;\n }\n\n &:last-child {\n border-bottom-left-radius: 8px;\n border-bottom-right-radius: 8px;\n }\n\n &:hover {\n background: #0ea5e9;\n color: white;\n cursor: pointer;\n\n span.highlighted {\n background: none;\n font-weight: normal;\n }\n }\n }\n\n li.add-item {\n border-top: 2px solid #f2f2f2;\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 0.9em;\n font-weight: 600;\n\n &.loading {\n background: #f8f8f8;\n cursor: wait;\n color: #666666;\n }\n }\n\n span.highlighted {\n background: yellow;\n font-weight: 600;\n }\n`;\n\nconst SearchableSelectOptions = (props: {\n options: string[];\n inputText: string;\n onSelect: (option: string) => void;\n onAdd: (option: string) => Promise;\n}) => {\n const [loading, setLoading] = React.useState(false);\n\n const handleAddOption = async () => {\n if (loading) {\n return;\n }\n\n try {\n setLoading(true);\n await props.onAdd(props.inputText);\n } finally {\n setLoading(false);\n }\n };\n\n const caseInsensitiveIndexOf = (s: string, sub: string) =>\n s.toLowerCase().indexOf(sub.toLowerCase());\n\n // Ranked options are the options to display in the dropdown. This is filtered\n // and ordered in an intuitive way for the user.\n const rankedOptions = props.options\n // First, only show options that contain the input string as a complete substring\n // unless the input string is empty, in which case show all opions.\n .filter((option) => !props.inputText || caseInsensitiveIndexOf(option, props.inputText) !== -1)\n // Next, build the sort key for each option. First, sort by the position of the\n // first complete substring match (prefer options that have the input text earlier\n // in the option rather than later). If the positions are the same, break the tie\n // by sorting based on the index option, keeping them in the original order (this is\n // also known as a stable sort). The final element is the actual option to render.\n .map((s, i): [number, number, string] => [caseInsensitiveIndexOf(s, props.inputText), i, s])\n // Generic sort comparator that sorts ascending by each corresponding element in the\n // sort key, checking subsequent elements in the key when prior elements are equal.\n .sort((a, b) => {\n for (let i = 0; i < a.length && i < b.length; i++) {\n if (a[i] !== b[i]) {\n return a[i] < b[i] ? -1 : 1;\n }\n }\n return 0;\n });\n\n return (\n \n
    \n
    \n {rankedOptions.map(([, , option]) => (\n
  • props.onSelect?.(option)}>\n {[...option].map((char, i) => (\n = caseInsensitiveIndexOf(option, props.inputText) &&\n i < caseInsensitiveIndexOf(option, props.inputText) + props.inputText.length\n ? 'highlighted'\n : undefined\n }\n >\n {char}\n \n ))}\n
  • \n ))}\n
    \n {!props.options.some((opt) => opt === props.inputText) && props.inputText != '' && (\n
  • \n {loading && (\n <>\n Adding \"{props.inputText}\"...\n \n )}\n {!loading && (\n <>\n Add \"{props.inputText}\"\n \n )}\n
  • \n )}\n
\n
\n );\n};\n\ninterface SearchableSelectProps {\n options: string[];\n initialValue?: string;\n disabled?: boolean;\n error?: string;\n onChange?: (value: string) => void;\n onAddOption?: (value: string) => Promise;\n onSelectOption?: (value: string) => void;\n}\n\nexport const SearchableSelect = (props: SearchableSelectProps): JSX.Element => {\n const [selectedOption, setSelectedOption] = React.useState(false);\n const [value, setValue] = React.useState(props.initialValue);\n\n React.useEffect(() => {\n setValue(props.initialValue);\n }, [props.initialValue]);\n\n return (\n
\n {\n setSelectedOption(false);\n setValue(e.currentTarget.value.trim());\n props.onChange?.(e.currentTarget.value.trim());\n }}\n onFocus={() => setSelectedOption(false)}\n disabled={props.disabled}\n />\n {!selectedOption && (\n {\n setSelectedOption(true);\n setValue(value);\n props.onSelectOption?.(value);\n }}\n onAdd={async (value: string) => {\n if (!props.onAddOption) {\n return;\n }\n\n await props.onAddOption(value);\n setSelectedOption(true);\n setValue(value);\n }}\n />\n )}\n {props.error && {props.error}}\n
\n );\n};\n","import styled from 'styled-components';\nimport { useId } from 'react-id-generator';\n\nconst CheckboxContainer = styled.div<{ disabled?: boolean; checked?: boolean }>`\n .checkbox-symbol {\n pointer-events: none;\n user-select: none;\n }\n\n .checkbox-container {\n box-sizing: border-box;\n background: #ffffff;\n }\n\n .checkbox-container * {\n box-sizing: border-box;\n }\n\n .checkbox-input {\n position: absolute;\n visibility: hidden;\n }\n\n .checkbox {\n user-select: none;\n cursor: ${(props) => (props.disabled ? 'default' : 'pointer')};\n overflow: hidden;\n transition: all 0.15s ease;\n display: flex;\n }\n\n .checkbox span {\n vertical-align: middle;\n transform: translate3d(0, 0, 0);\n position: relative;\n margin-top: 2px;\n width: 16px;\n height: 16px;\n border-radius: 3px;\n transform: scale(1);\n border: 1px solid #cccfdb;\n transition: all 0.15s ease;\n background: ${(props) => (props.disabled ? '#ffffff' : props.checked ? '#0EA5E9' : 'white')};\n opacity: ${(props) => (props.disabled ? '0.3' : '1.0')};\n }\n\n .checkbox span svg {\n position: absolute;\n top: 3px;\n left: 2px;\n fill: none;\n stroke: #fff;\n stroke-dasharray: 16px;\n stroke-dashoffset: 16px;\n transition: all 0.15s ease;\n transform: translate3d(0, 0, 0);\n }\n\n .checkbox-input:checked + .checkbox span {\n background: #0ea5e9;\n border-color: #0ea5e9;\n animation: zoom-in-out 0.15s ease;\n }\n\n .checkbox-input:checked + .checkbox span svg {\n stroke-dashoffset: 0;\n }\n\n @keyframes zoom-in-out {\n 50% {\n transform: scale(0.9);\n }\n }\n`;\n\nexport interface CheckboxProps {\n label?: string;\n checked?: boolean;\n disabled?: boolean;\n onChange?: () => void;\n}\n\nexport const Checkbox = (props: CheckboxProps): JSX.Element => {\n const [id] = useId();\n return (\n \n
\n \n \n
\n
\n );\n};\n","import styled from 'styled-components';\nimport { useId } from 'react-id-generator';\n\nconst RadioContainer = styled.div<{ disabled?: boolean; checked?: boolean }>`\n .radio-symbol {\n pointer-events: none;\n user-select: none;\n }\n\n .radio-container {\n box-sizing: border-box;\n background: #ffffff;\n }\n\n .radio-container * {\n box-sizing: border-box;\n }\n\n .radio-input {\n position: absolute;\n visibility: hidden;\n }\n\n .radio {\n user-select: none;\n cursor: ${(props) => (props.disabled ? 'default' : 'pointer')};\n overflow: hidden;\n transition: all 0.15s ease;\n display: flex;\n }\n\n .radio span {\n vertical-align: middle;\n transform: translate3d(0, 0, 0);\n position: relative;\n margin-top: 2px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n transform: scale(1);\n border: 1px solid #cccfdb;\n transition: all 0.15s ease;\n background: ${(props) => (props.disabled ? '#666666' : props.checked ? '#0EA5E9' : 'white')};\n }\n\n .radio span svg {\n position: absolute;\n top: 2px;\n left: 2px;\n fill: none;\n stroke: #fff;\n stroke-dasharray: 16px;\n stroke-dashoffset: 16px;\n transition: all 0.15s ease;\n transform: translate3d(0, 0, 0);\n }\n\n .radio-input:checked + .radio span {\n background: ${(props) => (props.disabled ? '' : '#0EA5E9')};\n border-color: ${(props) => (props.disabled ? '#666666' : '#0EA5E9')};\n animation: zoom-in-out 0.15s ease;\n }\n\n .radio-input:checked + .radio span svg {\n stroke-dashoffset: 0;\n }\n\n @keyframes zoom-in-out {\n 50% {\n transform: scale(0.9);\n }\n }\n`;\n\nexport interface RadioProps {\n label: string;\n id?: string;\n name?: string;\n checked?: boolean;\n disabled?: boolean;\n onChange?: () => void;\n}\n\nexport const Radio = (props: RadioProps): JSX.Element => {\n const [id] = useId();\n return (\n \n
\n \n \n
\n
\n );\n};\n","import styled from 'styled-components';\n\nimport { Checkbox } from './checkbox';\nimport { Radio } from './radio';\n\nconst OptionContainer = styled.div<{ enabled: boolean; disabled?: boolean }>`\n transition: 0.15s all ease-in-out;\n box-sizing: border-box;\n cursor: ${(props) => (props.disabled ? 'default' : 'pointer')};\n\n display: flex;\n flex-direction: row;\n`;\n\nconst OptionCheckboxContainer = styled.div`\n width: 27px;\n padding-left: 3px;\n`;\n\nconst OptionImageContainer = styled.div`\n width: 60px;\n height: 60px;\n margin-right: 10px;\n border-radius: 10px;\n border: 1px solid #eeeeee;\n background: #eeeeee;\n\n img {\n width: 60px;\n object-fit: cover;\n border-radius: 10px;\n }\n`;\n\nconst OptionContentContainer = styled.div`\n flex: 1 1 0;\n`;\n\nconst OptionDescription = styled.div`\n color: #999999;\n font-size: 0.8em;\n margin-top: 5px;\n`;\n\nconst OptionContent = styled.div`\n color: #999999;\n font-size: 0.8em;\n margin-top: 5px;\n padding: 10px 0px 5px 0px;\n`;\n\nconst OptionHeaderContainer = styled.div`\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n font-weight: 600;\n`;\n\nconst OptionHeader = ({\n title,\n description\n}: {\n title: string;\n description?: string | JSX.Element;\n}) => (\n \n {title}\n {description}\n \n);\n\ninterface OptionGroupProps {\n multi?: boolean;\n loading?: boolean;\n name: string;\n options: Array<{\n title: string;\n description: string | JSX.Element;\n imageUrl?: string;\n titleRight?: string | JSX.Element;\n enabled?: boolean;\n disabled?: boolean;\n children?: JSX.Element;\n onSelect?: () => void;\n }>;\n}\n\nexport const OptionGroup = (props: OptionGroupProps): JSX.Element => (\n
\n {props.options.map((option) => (\n option.onSelect && option.onSelect()}\n >\n \n {props.multi ? (\n option.onSelect && option.onSelect()}\n />\n ) : (\n option.onSelect && option.onSelect()}\n />\n )}\n \n {option.imageUrl && (\n \n \n \n )}\n \n \n {option.description}\n {option.enabled && option.children && {option.children}}\n \n \n ))}\n
\n);\n","import * as React from 'react';\nimport { Link } from 'react-router-dom';\nimport styled from 'styled-components';\n\nimport { Spinner } from '../Spinner';\n\nconst UnstyledLink = styled.a`\n color: inherit;\n text-decoration: inherit;\n\n &:visited {\n color: inherit;\n text-decoration: inherit;\n }\n`;\n\nconst ButtonContainerComponent = styled.div`\n display: flex;\n flex-direction: row;\n gap: 0px 6px;\n\n width: 100%;\n justify-content: ${(props) => (props.center ? 'center' : 'left')};\n`;\n\ninterface ButtonContainerProps {\n center?: boolean;\n}\n\nexport const ButtonContainer = (\n props: React.PropsWithChildren & ButtonContainerProps>\n) => {props.children};\n\nconst ButtonComponent = styled.button<{\n primary?: boolean;\n loading?: boolean;\n}>`\n border: 0;\n\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 5px;\n\n padding: 10px 15px;\n border-radius: 30px;\n user-select: none;\n\n font-size: 0.8em;\n font-weight: 500;\n\n min-width: 100px;\n justify-content: center;\n\n color: ${(props) => (props.primary ? 'white' : '#333333')};\n background: ${(props) => (props.primary ? '#0ea5e9' : '#fafafa')};\n border: ${(props) => (props.primary ? '1px solid transparent' : '1px solid #DADADA')};\n font-family: 'Rubik', 'Helvetica Neue', 'Helvetica', Arial, sans-serif;\n\n opacity: ${(props) => (props.loading || props.disabled ? 0.6 : 1)};\n cursor: ${(props) => (props.loading || props.disabled ? 'default' : 'pointer')};\n\n transition: all 0.15s ease-in-out;\n\n &:active {\n transform: ${(props) => {\n if (props.loading || props.disabled) {\n return 'none';\n }\n return 'translateY(2px)';\n }};\n background: ${(props) => {\n if (props.loading || props.disabled) {\n return props.primary ? '#0ea5e9' : '#fafafa';\n }\n return props.primary ? '#269985' : 'rgb(212, 212, 212)';\n }};\n }\n\n &:first-child {\n margin-left: 0;\n }\n\n &:last-child {\n margin-right: 0;\n }\n`;\n\nconst ButtonDivComponent = styled.div<{\n primary?: boolean;\n loading?: boolean;\n disabled?: boolean;\n}>`\n border: 0;\n\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 5px;\n\n padding: 10px 15px;\n border-radius: 30px;\n user-select: none;\n\n font-size: 0.8em;\n font-weight: 500;\n\n color: ${(props) => (props.primary ? 'white' : '#333333')};\n background: ${(props) => (props.primary ? '#0ea5e9' : '#fafafa')};\n border: ${(props) => (props.primary ? '1px solid transparent' : '1px solid #DADADA')};\n font-family: 'Rubik', 'Helvetica Neue', 'Helvetica', Arial, sans-serif;\n\n opacity: ${(props) => (props.loading || props.disabled ? 0.6 : 1)};\n cursor: ${(props) => (props.loading || props.disabled ? 'default' : 'pointer')};\n\n transition: all 0.15s ease-in-out;\n\n :active {\n transform: translateY(2px);\n background: ${(props) => {\n if (props.loading || props.disabled) {\n return props.primary ? '#0ea5e9' : '#fafafa';\n }\n return props.primary ? '#269985' : 'rgb(212, 212, 212)';\n }};\n }\n\n :first-child {\n margin-left: 0;\n }\n\n :last-child {\n margin-right: 0;\n }\n`;\n\ninterface ButtonProps {\n primary?: boolean;\n loading?: boolean;\n disabled?: boolean;\n icon?: JSX.Element;\n leftIcon?: JSX.Element;\n style?: React.CSSProperties;\n}\n\ninterface LinkProps {\n href: string;\n target?: string;\n onClick?: () => void;\n}\n\nexport const ButtonLink = (props: React.PropsWithChildren) =>\n props.disabled ? (\n \n \n \n);\n\nexport const RequirementItemContainer = styled.div`\n padding: 16px 0px;\n box-sizing: border-box;\n display: flex;\n flex-direction: row;\n`;\n\nexport const RequirementCheckboxContainer = styled.div`\n width: 30px;\n`;\n\nexport const RequirementContentContainer = styled.div`\n width: calc(100% - 30px);\n`;\n\nexport const RequirementDescription = styled.div<{ marginTop?: string }>`\n color: #999999;\n font-size: 0.9em;\n margin-top: ${(props) => (props.marginTop ? props.marginTop : '5px')};\n`;\n\nexport const RequirementTitle = styled.div`\n font-weight: 500;\n`;\n\nexport const RequirementError = styled.div`\n font-size: 0.8em;\n margin-top: 10px;\n color: #d1344b;\n`;\n","import React from 'react';\nimport styled from 'styled-components';\nimport { useId } from 'react-id-generator';\n\nexport const FormContainer = styled.form`\n display: flex;\n flex-direction: column;\n gap: 16px;\n width: 100%;\n`;\n\nexport const FormRowContainer = styled.div<{\n breakMobile?: boolean;\n alignItems?: string;\n singleItem?: boolean;\n}>`\n display: flex;\n flex-direction: row;\n align-items: ${(props) => (props.alignItems ? props.alignItems : 'normal')};\n gap: 16px;\n width: 100%;\n max-width: ${(props) => (props.singleItem ? 'calc(50% - 8px)' : '100%')};\n\n @media (max-width: 600px) {\n max-width: 100%;\n flex-direction: ${(props) => (props.breakMobile ? 'column' : 'row')};\n }\n`;\n\nconst FormRowHeaderContainer = styled.div<{ paddingTop?: string }>`\n padding-top: ${(props) => (props.paddingTop ? props.paddingTop : '24px')};\n`;\n\nconst FormRowHeaderDescription = styled.div`\n color: #999999;\n font-size: 0.9em;\n padding-top: 4px;\n`;\n\nconst FormRowHeaderTitle = styled.div`\n font-size: 1.1em;\n font-weight: 500;\n`;\n\nconst FormRowError = styled.div`\n font-size: 0.8em;\n color: #d1344b;\n`;\n\ninterface FormRowProps {\n error?: string | false;\n breakMobile?: boolean;\n alignItems?: string;\n singleItem?: boolean;\n}\n\nexport const FormRow: React.FunctionComponent<\n React.PropsWithChildren & FormRowProps>\n> = (props) => (\n <>\n {props.children}\n {props.error && {props.error}}\n \n);\n\ninterface FormRowHeaderProps {\n title?: string;\n description?: string;\n paddingTop?: string;\n}\n\nexport const FormRowHeader: React.FunctionComponent> = (\n props\n) => (\n \n {props.title && {props.title}}\n {props.description && {props.description}}\n \n);\n\nconst FormColumnContainer = styled.div`\n display: flex;\n flex-direction: column;\n gap: 4px;\n flex: 1 1 0;\n`;\n\nconst FormColumnTitle = styled.label`\n font-weight: 500;\n`;\n\nconst FormColumnDescription = styled.div`\n padding-bottom: 8px;\n font-size: 0.8em;\n color: #666666;\n`;\n\nconst FormColumnContents = styled.div``;\n\n// Temporary enum to distinguish the desired style\n// on the merchant form vs. the consumer forms. we'll\n// unify these styles once we have a design language.\nexport enum FormColumnStyle {\n merchant = 'merchant'\n}\n\ninterface FormColumnProps {\n title?: string;\n description?: string;\n label?: string;\n optional?: boolean;\n formColumnStyle?: FormColumnStyle;\n}\n\nexport const FormColumn: React.FunctionComponent<\n React.PropsWithChildren & FormColumnProps>\n> = (props) => {\n const [id] = useId();\n\n return (\n \n \n {props.title || props.label}\n {props.optional && (opt.)}\n \n {props.description && {props.description}}\n \n {React.Children.map(props.children, (child) =>\n React.isValidElement(child) ? React.cloneElement(child as JSX.Element, { id }) : child\n )}\n \n \n );\n};\n","import React from 'react';\nimport { ErrorDisplay, TextInput } from './text';\nimport { useId } from 'react-id-generator';\n\ninterface DateOfBirthInputProps {\n onChange: (day: string, month: string, year: string, dateObject: Date) => void;\n initialValue?: [string, string, string];\n error?: string | null | false;\n}\n\nexport const DateOfBirthInput: React.FunctionComponent<\n React.PropsWithChildren\n> = (props) => {\n const dayRef = React.useRef(null);\n const monthRef = React.useRef(null);\n const yearRef = React.useRef(null);\n\n const nextRefMap = { month: dayRef, day: yearRef };\n const prevRefMap = { day: monthRef, year: dayRef };\n\n const [formData, setFormData] = React.useState({\n day: props.initialValue?.[0] || '',\n month: props.initialValue?.[1] || '',\n year: props.initialValue?.[2] || '',\n fieldToFocus: ''\n });\n\n React.useEffect(() => {\n if (formData.fieldToFocus) {\n nextRefMap[formData.fieldToFocus]?.current?.focus();\n setFormData({ ...formData, fieldToFocus: '' });\n }\n }, [formData]);\n\n const setData = (field: string, value: string) => {\n setFormData((prev) => {\n // We need to internally validate the value and do proper focus handling.\n // Right now we don't care about transferring excess values to the next input,\n // for example, user clicks on month and input '128', we'll only focus on the year field and\n // sanitize to '12' and we'll drop the '8' input.\n let sanitizedValue = value;\n switch (field) {\n case 'day':\n sanitizedValue = value.substring(0, 2);\n break;\n case 'month':\n sanitizedValue = value.substring(0, 2);\n break;\n case 'year':\n sanitizedValue = value.substring(0, 4);\n break;\n }\n\n // Update state and focus\n const newFormData = {\n ...prev,\n [field]: sanitizedValue,\n fieldToFocus: value.length > prev[field].length && sanitizedValue.length >= 2 ? field : ''\n };\n\n // Adds date object representation for the picker\n const dateObject = new Date(\n Date.UTC(\n Number(newFormData.year),\n Number(newFormData.month) - 1,\n Number(newFormData.day),\n 0,\n 0,\n 0,\n 0\n )\n );\n props.onChange(newFormData.day, newFormData.month, newFormData.year, dateObject);\n return newFormData;\n });\n };\n\n // Handles keyboard press, here we do backspace handling and also limit input to only numeric\n const handleKeyDown = (e: React.KeyboardEvent, field: string) => {\n if (!/[0-9]|Backspace|Tab|Delete|Arrow[Left|Right|Up|Down]/.test(e.key)) {\n e.preventDefault();\n }\n\n if (e.key === 'Backspace' || e.key === 'Delete') {\n if (formData[field] === '') {\n prevRefMap[field]?.current?.focus();\n }\n }\n };\n\n // Prepend leading 0s for single digit date and month\n const handleBlur = (e: React.FocusEvent, field: string) => {\n const inputValue = e.target.value;\n if (field !== 'year' && inputValue.length === 1) {\n setData(field, ('00' + inputValue).slice(-2));\n }\n };\n\n const [mmId, ddId, yyyyId] = useId(3);\n\n return (\n
\n
\n \n setData('month', e.currentTarget.value)}\n onKeyDown={(e) => handleKeyDown(e, 'month')}\n onBlur={(e) => handleBlur(e, 'month')}\n ref={monthRef}\n />\n \n setData('day', e.currentTarget.value)}\n onKeyDown={(e) => handleKeyDown(e, 'day')}\n onBlur={(e) => handleBlur(e, 'day')}\n ref={dayRef}\n />\n \n setData('year', e.currentTarget.value)}\n onKeyDown={(e) => handleKeyDown(e, 'year')}\n onBlur={(e) => handleBlur(e, 'year')} // Only here for consistency, does nothing at the moment\n ref={yearRef}\n />\n
\n {props.error && {props.error}}\n
\n );\n};\n","import * as React from 'react';\nimport styled from 'styled-components';\n\nimport { PageSection } from '@oysterjs/ui/Page';\nimport { CoveragePageSection, CoverageItem } from '@oysterjs/ui/CoverageItem';\nimport { PageProps } from './page';\nimport {\n ActivationSource,\n ActivationTrigger,\n BooleanOption,\n Cadence,\n DiscreteOption,\n Option,\n OptionType,\n Policy,\n PolicyType,\n Price\n} from '@oysterjs/types';\nimport { Select, UnstyledSelect } from '@oysterjs/ui/Form/select';\nimport { OptionGroup } from '@oysterjs/ui/Form/option';\nimport { Button, ButtonContainer } from '@oysterjs/ui/Button';\nimport { IoArrowBack, IoArrowForward } from 'react-icons/io5';\nimport { getSamplePolicyUrl } from '@oysterjs/core/statics';\nimport { getPolicyCoverageItems, getPolicyOptionalCoverageItem } from '@oysterjs/core/policy';\nimport { Spinner } from '@oysterjs/ui/Spinner';\nimport { useAnalytics } from '@oysterjs/core/analytics/analytics';\nimport { GetOysterEventNames } from '@oysterjs/core/analytics/googletags';\nimport { useHistory } from 'react-router';\nimport { ErrorType } from '@oysterjs/core/errors';\nimport {\n RequirementCheckboxContainer,\n RequirementContentContainer,\n RequirementDescription,\n RequirementItemContainer,\n RequirementTitle\n} from '@oysterjs/ui/common';\nimport { Checkbox } from '@oysterjs/ui/Form/checkbox';\nimport { TextInput } from '@oysterjs/ui/Form/text';\nimport { FormColumn, FormRow } from '@oysterjs/ui/Form/builder';\nimport { DateOfBirthInput } from '@oysterjs/ui/Form/dob';\n\nexport const getCoveragePage =\n (\n title: string,\n description: string,\n getPremium: (updatedPolicy?: Policy) => Promise,\n showLoading?: boolean,\n subDescription?: string,\n atomicPriceUpdate?: boolean\n ) =>\n (props: React.PropsWithChildren) => (\n \n );\n\nconst PolicyOptionBox = styled.div`\n padding-left: 30px;\n display: flex;\n flex-direction: column;\n`;\n\nconst CoveragePriceAmount = styled.span`\n font-weight: 600;\n font-size: 2em;\n vertical-align: baseline;\n`;\n\nconst CoveragePriceCadence = styled.span`\n text-transform: uppercase;\n display: block;\n font-size: 0.9em;\n`;\n\nexport const CoveragePrice = (props: {\n price: Price;\n cadence: Cadence;\n displayCadence?: Cadence;\n loading?: boolean;\n extraFooter?: string;\n}) => {\n const annualPrice =\n props.cadence === Cadence.month ? props.price.Amount * 12 : props.price.Amount;\n const displayCadence = props.displayCadence || Cadence.year;\n const displayPrice = displayCadence == Cadence.year ? annualPrice : annualPrice / 12;\n const showBilledAnnually = displayCadence === Cadence.month;\n\n return (\n
\n \n \n {new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: props.price.Currency,\n minimumFractionDigits: Number.isInteger(displayPrice) ? 0 : 2,\n maximumFractionDigits: 2\n }).format(displayPrice)}\n \n /{displayCadence}\n \n \n
\n \n {showBilledAnnually && (\n
\n Billed annually at{' '}\n {new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: props.price.Currency,\n minimumFractionDigits: 0,\n maximumFractionDigits: 0\n }).format(props.price.Amount)}\n /{props.cadence}\n {props.extraFooter ? '. ' + props.extraFooter : ''}\n
\n )}\n {!showBilledAnnually && props.extraFooter && (\n
{props.extraFooter}
\n )}\n \n );\n};\n\nexport const PolicyCoveragePage = (\n props: PageProps & {\n title?: string;\n description?: string | JSX.Element;\n subDescription?: string;\n addButtonText?: string;\n priceDisplayCadence?: Cadence;\n showLoading?: boolean;\n isWidget?: boolean;\n atomicPriceUpdate?: boolean;\n getPremium: (updatedPolicy?: Policy) => Promise;\n }\n) => {\n const [loading, setLoading] = React.useState(false);\n const [price, setPrice] = React.useState();\n const [underwriter, setUnderwriter] = React.useState('');\n const [inputChanged, setInputChanged] = React.useState(false);\n const analytics = useAnalytics();\n const history = useHistory();\n const showAltText =\n props.policy.Type == PolicyType.chubbJewelry &&\n props.policy.InsuredItems.reduce((partialSum, item) => partialSum + item.Price.Amount, 0) >\n 200000;\n\n const [year, month, day] = (props.policy.Coverage.ActivationTrigger?.StartDate || '')\n .split('-')\n .map((v) => v || '');\n\n React.useEffect(() => {\n if (!props.loading) {\n setInputChanged(false);\n }\n }, [props.loading]);\n\n const onUpdate = (updatedPolicy: Policy, forceUpdate?: boolean) =>\n props.atomicPriceUpdate\n ? props.onUpdate(updatedPolicy, false)\n : props.onUpdate(updatedPolicy, forceUpdate);\n\n const loadPrice = async (updatedPolicy?: Policy) =>\n props\n .getPremium(updatedPolicy)\n .then((res) => {\n analytics.event(GetOysterEventNames.ReviewCoverage, {\n policy_id: props.policy.ID,\n policy_type: props.policy.Type,\n value: res.Price?.Amount.toString() || '0.0',\n currency: res.Price?.Currency || 'USD',\n transaction_id: props.policy.ID\n });\n setPrice(res.Price);\n setUnderwriter(res.Underwriting.Underwriter || '');\n })\n .catch((error) => {\n if (error.type() == ErrorType.underwritingError) {\n switch (props.policy.Type) {\n case PolicyType.chubbJewelry:\n history.push(`/app/${props.policy.InsuredItems[0].Type}/v2/ineligible`);\n break;\n default:\n history.push(`/app/${props.policy.InsuredItems[0].Type}/ineligible`);\n }\n }\n })\n .finally(() => setLoading(false));\n\n // There's an underlying issue that causes this component to mount and\n // unmount repeatedly, so this patch prevents us from spawning very\n // expensive requests to the backend by waiting 1 second before sending\n // the request, and canceling the wait if the component unmounts.\n React.useEffect(() => {\n setLoading(true);\n const timeout = setTimeout(() => loadPrice().finally(() => setLoading(false)), 1000);\n return () => clearTimeout(timeout);\n }, []);\n\n const onUpdateCoverageItem = (item: Option) => {\n if (loading) {\n return;\n }\n setLoading(true);\n onUpdate(\n {\n ...props.policy,\n Coverage: {\n ...props.policy.Coverage,\n Options: props.policy.Coverage.Options.map((existingItem) =>\n existingItem.ID === item.ID ? item : existingItem\n )\n }\n },\n true\n )\n .then(loadPrice)\n .finally(() => setLoading(false));\n };\n\n const onUpdateDeductible = (deductible: string) => {\n if (loading) {\n return;\n }\n setLoading(true);\n onUpdate(\n {\n ...props.policy,\n Coverage: {\n ...props.policy.Coverage,\n Deductible: {\n ...props.policy.Coverage.Deductible,\n Value: deductible\n }\n }\n },\n true\n )\n .then(loadPrice)\n .finally(() => setLoading(false));\n };\n\n const onUpdateActivationTrigger = (trigger?: ActivationTrigger) => {\n setInputChanged(true);\n onUpdate(\n {\n ...props.policy,\n Coverage: {\n ...props.policy.Coverage,\n ActivationTrigger:\n !trigger && !props.policy.Coverage.ActivationTrigger\n ? {\n Source: ActivationSource.SetStartDate\n }\n : trigger\n }\n },\n false\n );\n };\n\n const handleToggle = (item: Option) => {\n onUpdateCoverageItem({\n ...item,\n Enabled: !item.Enabled\n });\n };\n\n const onUpdateCoverage = (item: Option) => {\n onUpdateCoverageItem({\n ...item,\n Option: item.Option\n });\n };\n\n const renderDescription = (item: Option) => {\n switch (item.Type) {\n case OptionType.boolean: {\n const opt = item.Option as BooleanOption;\n if (opt.ValueLabel) {\n return 'up to ' + (item.Option as BooleanOption).ValueLabel;\n }\n return undefined;\n }\n case OptionType.discrete: {\n const opt = item.Option as DiscreteOption;\n if (item.Enabled) {\n return (\n ({\n value: v\n }))}\n disabled={loading}\n onChange={(newValue) =>\n onUpdateCoverage({\n ...item,\n Option: {\n ...opt,\n Value: newValue\n }\n })\n }\n />\n );\n }\n return undefined;\n }\n default:\n return undefined;\n }\n };\n\n const coveragePage = (\n <>\n

{props.title}

\n {props.description && (\n

\n {props.description}{' '}\n \n See policy summary\n \n .\n

\n )}\n {props.subDescription &&

{props.subDescription}

}\n \n {getPolicyCoverageItems(props.policy.Type).map((item) => (\n \n ))}\n \n {props.policy.Coverage.Options?.length > 0 && (\n \n ({\n ...getPolicyOptionalCoverageItem(item.ID),\n enabled: item.Enabled,\n disabled: item.Required || loading,\n titleRight: renderDescription(item),\n onSelect: () => handleToggle(item)\n }))}\n />\n \n )}\n {props.policy.InsuredItems.some((i) => i.HasNotReceived) && (\n \n {\n e.stopPropagation();\n onUpdateActivationTrigger();\n }}\n >\n \n onUpdateActivationTrigger()}\n />\n \n \n Start coverage later\n \n Check this box to submit your application now but start your coverage at a later\n time. If this box is not checked, your coverage will be effective when your\n policy is approved.\n \n \n \n \n )}\n {props.policy.Coverage.ActivationTrigger && (\n \n \n \n {\n onUpdateActivationTrigger({\n Source: value\n });\n }}\n value={props.policy.Coverage.ActivationTrigger?.Source}\n />\n \n \n {props.policy.Coverage.ActivationTrigger.Source ==\n ActivationSource.TrackingNumber && (\n {\n onUpdateActivationTrigger({\n Source: ActivationSource.TrackingNumber,\n TrackingNumber: e.currentTarget.value\n });\n }}\n />\n )}\n {props.policy.Coverage.ActivationTrigger.Source == ActivationSource.SetStartDate && (\n {\n onUpdateActivationTrigger({\n Source: ActivationSource.SetStartDate,\n StartDate: year + '-' + month + '-' + day\n });\n }}\n initialValue={\n props.policy.Coverage.ActivationTrigger?.StartDate &&\n new Date(props.policy.Coverage.ActivationTrigger?.StartDate).getTime() > 0\n ? [day, month, year]\n : undefined\n }\n error={\n !inputChanged &&\n props.validationError?.SubField === 'StartDate' &&\n props.validationError?.Message\n }\n />\n )}\n \n \n {props.policy.Coverage.ActivationTrigger.Source == ActivationSource.TrackingNumber && (\n \n We will track your package and automatically activate your policy once your package\n has arrived.\n \n )}\n {props.policy.Coverage.ActivationTrigger.Source == ActivationSource.SetStartDate && (\n \n We will automatically start your policy on the date you select.\n \n )}\n {props.policy.Coverage.ActivationTrigger.Source == ActivationSource.ManuallyActivate && (\n \n You will be able to manually activate your policy on your dashboard at any time.\n \n )}\n \n )}\n
\n \n {!price && loading && }\n {price && (\n
\n \n {props.policy.Type === PolicyType.chubbJewelry && underwriter != '' && (\n
\n {'Insurance Underwritten By: ' +\n `${underwriter == 'FEDERAL' ? 'Federal Insurance Co' : underwriter}`}\n
\n )}\n {props.policy.Type === PolicyType.bike && (\n
\n Insurance described is offered by Oyster Insurance Agency, LLC (California license\n no. 6006158). Insurance is underwritten and provided by Markel Group Inc.\n
\n )}\n {showAltText && (\n
\n There are credits you may qualify for to improve this price.If you'd like to learn\n more, continuing does not commit you to finalizing your insurance purchase.\n
\n )}\n
\n )}\n
\n \n
\n ({\n value: v\n }))}\n onChange={(newValue) => onUpdateDeductible(newValue)}\n />\n {props.policy.Type != PolicyType.chubbJewelry && (\n
\n This is the maximum amount you'll pay out-of-pocket in the event of a claim.\n
\n )}\n
\n
\n
\n
\n \n {props.onBack && (\n \n )}\n }\n onClick={props.onNext}\n loading={props.loading}\n disabled={props.loading || loading}\n primary\n >\n {props.addButtonText || 'Add policy to cart'}\n \n \n
\n
\n \n );\n\n return (\n <>\n {!price && loading && props.showLoading && (\n \n

Verifying your details

\n

This may take a few seconds, please do not refresh or navigate away from this page.

\n \n \n \n
\n \n )}\n {(price || !props.showLoading) && !props.isWidget && (\n {coveragePage}\n )}\n {(price || !props.showLoading) && props.isWidget && <>{coveragePage}}\n \n );\n};\n","import * as React from 'react';\n\nimport { FormContainer, FormRow, FormColumn, FormRowHeader } from '@oysterjs/ui/Form/builder';\nimport { TextInput } from '@oysterjs/ui/Form/text';\nimport { Select } from '@oysterjs/ui/Form/select';\nimport { BikeFormData } from './collect';\nimport { BikeProduct } from '@oysterjs/types';\n\ninterface QuickQuoteProps {\n onPriceUpdate: (value: number, data: BikeFormData, err?: Error) => void;\n}\n\nexport const BikeQuickQuote = (props: React.PropsWithChildren) => {\n const [formData, setFormData] = React.useState({\n Price: '',\n Details: {} as BikeProduct\n });\n\n const setData = (fn: (prev: BikeFormData) => BikeFormData) => {\n setFormData((prev) => {\n const next = fn(prev);\n props.onPriceUpdate(getPrice(next), next);\n return next;\n });\n };\n\n const getPrice = (data: BikeFormData): number => {\n if (!data.ZipCode || !data.Price) {\n return 0;\n }\n const amount = parseFloat(data.Price);\n if (!amount || isNaN(amount)) {\n return 0;\n }\n return Math.max((data.Details.PowerSource ? 0.071 : 0.062) * amount, 100);\n };\n\n return (\n \n \n \n \n {\n const value = e.currentTarget.value;\n setData((prev) => ({ ...prev, ZipCode: value }));\n }}\n value={formData.ZipCode}\n inputMode=\"numeric\"\n autoComplete=\"postal-code\"\n />\n \n \n \n \n \n {\n const value = e.currentTarget.value;\n setData((prev) => ({ ...prev, Price: value }));\n }}\n value={formData.Price}\n inputMode=\"decimal\"\n />\n \n \n \n \n \n \n setData((prev) => ({\n ...prev,\n Details: { ...prev.Details, PowerSource: value }\n }))\n }\n />\n \n \n \n );\n};\n","import * as React from 'react';\n\nimport { FormContainer, FormRow, FormColumn, FormRowHeader } from '@oysterjs/ui/Form/builder';\nimport { TextInput } from '@oysterjs/ui/Form/text';\nimport { MotorProductType, ProductType } from '@oysterjs/types';\nimport { MotorFormData } from './collect';\n\ninterface QuickQuoteProps {\n productType: ProductType;\n onPriceUpdate: (value: number, data: MotorFormData) => void;\n}\n\nexport const MotorQuickQuote: React.FunctionComponent> = (\n props\n) => {\n const [, setFormData] = React.useState({\n ProductType: props.productType,\n ZipCode: '',\n Price: '',\n Details: {\n ModelYear: '',\n PurchaseYear: '',\n Make: '',\n Model: '',\n VIN: '',\n CCSize: '',\n Type: '' as MotorProductType,\n EstimatedAnnualMileage: ''\n }\n });\n\n const setData = (fn: (prev: MotorFormData) => MotorFormData) => {\n setFormData((prev) => {\n const next = fn(prev);\n props.onPriceUpdate(getPrice(next), next);\n return next;\n });\n };\n\n const getPrice = (data: MotorFormData): number => {\n if (!data.Price || isNaN(parseFloat(data.Price))) {\n return 0;\n }\n return Math.floor(parseFloat(data.Price) * 0.12 * (1 - Math.random() / 10));\n };\n\n return (\n \n \n \n \n {\n const value = e.currentTarget.value;\n setData((prev) => ({ ...prev, ZipCode: value }));\n }}\n inputMode=\"numeric\"\n autoComplete=\"postal-code\"\n />\n \n \n \n \n \n {\n const value = e.currentTarget.value;\n setData((prev) => ({ ...prev, Price: value }));\n }}\n inputMode=\"decimal\"\n />\n \n \n \n );\n};\n","import * as React from 'react';\n\nimport { FormContainer, FormRow, FormColumn, FormRowHeader } from '@oysterjs/ui/Form/builder';\nimport { TextInput } from '@oysterjs/ui/Form/text';\nimport { Select } from '@oysterjs/ui/Form/select';\nimport { JewelryFormData } from './collect';\nimport { JewelryProduct } from '@oysterjs/types';\n\ninterface QuickQuoteProps {\n onPriceUpdate: (value: number, data: JewelryFormData) => void;\n}\n\nexport const JewelryQuickQuote: React.FunctionComponent<\n React.PropsWithChildren\n> = (props) => {\n const [formData, setFormData] = React.useState({\n Price: '',\n Details: {} as JewelryProduct\n });\n\n const setData = (fn: (prev: JewelryFormData) => JewelryFormData) => {\n setFormData((prev) => {\n const next = fn(prev);\n props.onPriceUpdate(getPrice(next), next);\n return next;\n });\n };\n\n const getPrice = (data: JewelryFormData): number => {\n if (!data.ZipCode || !data.Price || !data.Details.Type) {\n return 0;\n }\n const amount = parseFloat(data.Price);\n if (!amount || isNaN(amount)) {\n return 0;\n }\n return Math.max(0.015 * amount, 25);\n };\n\n return (\n \n \n \n \n {\n const value = e.currentTarget.value;\n setData((prev) => ({ ...prev, ZipCode: value }));\n }}\n value={formData.ZipCode}\n inputMode=\"numeric\"\n autoComplete=\"postal-code\"\n />\n \n \n \n \n \n \n setData((prev) => ({\n ...prev,\n Details: { ...prev.Details, Type: value }\n }))\n }\n value={formData.Details.Type || ''}\n />\n \n \n \n \n \n {\n const value = e.currentTarget.value;\n setData((prev) => ({ ...prev, Price: value }));\n }}\n value={formData.Price}\n inputMode=\"decimal\"\n />\n \n \n \n );\n};\n","import * as React from 'react';\n\nimport { FormContainer, FormRow, FormColumn, FormRowHeader } from '@oysterjs/ui/Form/builder';\nimport { TextInput } from '@oysterjs/ui/Form/text';\nimport { Select } from '@oysterjs/ui/Form/select';\nimport { ElectronicsFormData } from './collect';\nimport { ProductType } from '@oysterjs/types';\nimport { ElectronicsProduct, ElectronicsType } from '@oysterjs/types';\n\ninterface QuickQuoteProps {\n productType: ProductType;\n onPriceUpdate: (value: number, data: ElectronicsFormData) => void;\n}\n\nexport const ElectronicsQuickQuote = (props: React.PropsWithChildren) => {\n const [formData, setFormData] = React.useState({\n ProductType: props.productType,\n Price: '',\n Details: {} as ElectronicsProduct\n });\n\n React.useEffect(() => {\n setData(() => ({\n ProductType: props.productType,\n Price: '',\n Details: {} as ElectronicsProduct\n }));\n }, [props.productType]);\n\n const setData = (fn: (prev: ElectronicsFormData) => ElectronicsFormData) => {\n setFormData((prev) => {\n const next = fn(prev);\n props.onPriceUpdate(getPrice(next), next);\n return next;\n });\n };\n\n const getPrice = (data: ElectronicsFormData): number => {\n if (!data.ZipCode || !data.Price || !(data.Details.Manufacturer || data.Details.Type)) {\n return 0;\n }\n const amount = parseFloat(data.Price);\n if (!amount || isNaN(amount)) {\n return 0;\n }\n return Math.max(0.11 * amount, 25);\n };\n\n return (\n \n \n \n \n {\n const value = e.currentTarget.value;\n setData((prev) => ({ ...prev, ZipCode: value }));\n }}\n value={formData.ZipCode}\n inputMode=\"numeric\"\n autoComplete=\"postal-code\"\n />\n \n \n \n \n \n {props.productType === ProductType.phone && (\n {\n setData((prev) => ({ ...prev, Details: { ...prev.Details, Manufacturer: value } }));\n }}\n value={formData.Details.Manufacturer}\n />\n )}\n {props.productType === ProductType.electronics && (\n {\n setData((prev) => ({ ...prev, Details: { ...prev.Details, Type: value } }));\n }}\n value={formData.Details.Type}\n />\n )}\n \n \n \n \n \n {\n const value = e.currentTarget.value;\n setData((prev) => ({ ...prev, Price: value }));\n }}\n value={formData.Price}\n inputMode=\"decimal\"\n />\n \n \n \n );\n};\n","import * as React from 'react';\nimport { useHistory } from 'react-router';\nimport apm from './apm';\n\ninterface UrlData {\n integrationID?: string;\n appData?: T;\n}\n\nexport const useUrlData = (): UrlData => {\n const history = useHistory();\n\n const getData = () => {\n const params = new URLSearchParams(history.location.search);\n\n try {\n const data = JSON.parse(\n window.atob(decodeURIComponent(params.get('d') || encodeURIComponent(window.btoa('{}'))))\n );\n const newData = {\n integrationID: data.I,\n appData: data.D\n };\n\n return newData;\n } catch (err) {\n apm().captureError(err);\n }\n };\n\n const [data, setData] = React.useState>(getData() || {});\n React.useEffect(() => setData(getData() || {}), [history.location]);\n\n return data;\n};\n\nexport const createUrlDataComponent = (d: UrlData): string => {\n return (\n 'd=' +\n encodeURIComponent(\n window.btoa(\n JSON.stringify({\n I: d.integrationID,\n D: d.appData\n })\n )\n )\n );\n};\n","import { getToken, resetToken } from '@oysterjs/core/auth';\nimport { ApolloClient, InMemoryCache, createHttpLink, from } from '@apollo/client';\nimport { removeTypenameFromVariables } from '@apollo/client/link/remove-typename';\nimport {\n DeepPartial,\n Merchant,\n MerchantUser,\n MerchantIntegration,\n ReferralLinkIntegration,\n PolicyTableValues,\n MerchantIntegrationType,\n MerchantRentalConfiguration,\n RentalWaiver,\n WaiverEntry,\n RentalAsset,\n RentalBooking,\n RentalClaim,\n AttachmentFile,\n ValidationError,\n Transaction,\n UserReferral,\n MerchantRentalConfigurationDetails,\n Insured,\n BusinessInformation,\n DefaultFileRoles,\n BusinessInsuranceApplication,\n PublicMerchantRentalConfiguration\n} from '@oysterjs/types';\nimport { Delete, Get, getEncodedQueryString, Post, Put } from './base';\nimport config from '../config';\n\nexport const getMerchantGraphQLClient = () => {\n const removeTypenameLink = removeTypenameFromVariables();\n const link = from([\n removeTypenameLink,\n createHttpLink({\n uri: `${config().backendBaseUrl.api}/merchant/graphql/query`,\n credentials: 'include',\n headers: {\n ...(getToken() ? { Authorization: `Bearer ${getToken()}` } : {})\n }\n })\n ]);\n return new ApolloClient({\n cache: new InMemoryCache(),\n link\n });\n};\n\nexport const createMerchant = (data: {\n Merchant: DeepPartial;\n MerchantUser: Partial;\n AccessCode: string;\n}) => Post('/merchant', data);\n\nexport const merchantUserSignInInit = (email: string, redirect?: string) =>\n Post(\n '/merchant/user/signin/init',\n { Email: email, Redirect: redirect },\n { disableUnauthorizedRedirect: true }\n );\n\nexport const merchantUserSignInComplete = (email: string, code: string) =>\n Post<{ Token: string; MerchantUser: MerchantUser }>(\n '/merchant/user/signin/complete',\n { Email: email, LoginCode: code },\n { disableUnauthorizedRedirect: true }\n );\n\nexport const merchantUserSignOut = () =>\n Post('/merchant/user/signout', { disableUnauthorizedRedirect: true });\n\nexport const getMerchantAccount = (redirectUrl?: string) =>\n Get<{\n Merchant: Merchant;\n MerchantUser: MerchantUser;\n PersonalizationError?: ValidationError;\n }>('/merchant', { redirectUrl });\n\nexport const getMerchantIntegrations = (redirectUrl?: string) =>\n Get<{\n ApiKey: string;\n Integrations: MerchantIntegration[];\n }>('/merchant/integration', { redirectUrl });\n\nexport const updateMerchantIntegration = (integration: MerchantIntegration) =>\n Put<{ Integration: MerchantIntegration }>(`/merchant/integration/${integration.ID}`, {\n Integration: integration\n });\n\nexport const updateMerchantAccount = (data: { Merchant: DeepPartial }) =>\n Put<{ Merchant: Merchant }>('/merchant', { Merchant: data.Merchant });\n\nexport const personalizeMerchant = (merchant: Merchant, discardUpdateOnError?: boolean) =>\n Post<{ Merchant: Merchant; NextValidationError?: ValidationError }>(\n `/merchant/personalize?${getEncodedQueryString({\n DiscardUpdateOnError: discardUpdateOnError\n })}`,\n {\n BusinessProfile: merchant.BusinessProfile\n }\n );\n\nexport const linkMerchantWithShopify = (\n shop: string,\n integrationData: string,\n redirectUrl: string\n) => {\n if (!getToken()) {\n resetToken(redirectUrl);\n return Promise.resolve();\n } else {\n return Post(\n '/merchant/link-integration',\n {\n Type: 'Shopify',\n ExternalID: shop,\n IntegrationData: integrationData\n },\n { redirectUrl }\n );\n }\n};\n\nexport const linkBIApplicationWithShopify = (\n shop: string,\n integrationData: string,\n redirectUrl: string,\n biApplicationID: string\n) => {\n return Post(\n '/merchant/link-bi-integration',\n {\n Type: 'Shopify',\n ExternalID: shop,\n IntegrationData: integrationData,\n BIApplicationID: biApplicationID\n },\n { redirectUrl }\n );\n};\n\nexport const linkMerchantWithLightspeedRetailRSeries = (\n _: string,\n integrationData: string,\n redirectUrl: string\n) => {\n if (!getToken()) {\n resetToken(redirectUrl);\n return Promise.resolve();\n } else {\n return Post(\n '/merchant/link-integration',\n {\n Type: 'LSRetailRSeries',\n IntegrationData: integrationData\n },\n { redirectUrl }\n );\n }\n};\n\nexport const linkMerchantWithCheckfront = (\n _: string,\n integrationData: string,\n redirectUrl: string\n) => {\n if (!getToken()) {\n resetToken(redirectUrl);\n return Promise.resolve();\n } else {\n return Post(\n '/merchant/link-integration',\n {\n Type: 'Checkfront',\n IntegrationData: integrationData\n },\n { redirectUrl }\n );\n }\n};\n\nexport const requestEmbeddablePage = () =>\n Get<{\n Integration: MerchantIntegration;\n }>('/merchant/integration/embeddable-page').then((res) => res.Integration);\n\nexport const requestCheckfront = () =>\n Get<{\n Integration: MerchantIntegration;\n }>('/merchant/integration/checkfront').then((res) => res.Integration);\n\nexport const requestQRCode = () =>\n Get<{\n Integration: MerchantIntegration;\n QRCode: string;\n }>('/merchant/integration/QRCode');\n\nexport const requestQRCodePdf = () =>\n Get<{ DocumentUrl: string; DocumentZip: string }>('/merchant/integration/QRCode/pdf');\n\nexport const getMerchantReferrals = (start: Date, end: Date) =>\n Get<{ Referrals: PolicyTableValues[] }>(\n `/merchant/referrals?startTime=${start.toISOString()}&endTime=${end.toISOString()}`\n );\n\nexport const getMerchantTransactions = (start: Date, end: Date) =>\n Get<{ Transactions: Transaction[] }>(\n `/merchant/transactions?startTime=${start.toISOString()}&endTime=${end.toISOString()}`\n );\n\nexport const getMerchantLink = (\n integrationType?: MerchantIntegrationType,\n externalID?: string,\n integrationID?: string\n) =>\n Get(\n `/merchant/referrallink?${getEncodedQueryString({\n externalID,\n integrationID,\n integrationType\n })}`\n );\n\nexport const getMerchantUsers = () => Get<{ Users: MerchantUser[] }>('/merchant/users');\nexport const createMerchantUser = (user: Partial) =>\n Post('/merchant/users', { MerchantUser: user });\n\n// RENTAL APIs\n\nexport const getMerchantRentalConfiguration = (): Promise<{\n Configuration?: MerchantRentalConfiguration;\n ReferralLink?: string;\n QRCodeBase64?: string;\n}> => Get('/merchant/rental');\n\nexport const getPublicMerchantRentalConfiguration = (\n integrationId: string\n): Promise<{\n Configuration: PublicMerchantRentalConfiguration;\n}> => Get('/merchant/rental/public', { headers: { 'X-Merchant-Integration-Id': integrationId } });\n\nexport const updateMerchantRentalConfiguration = (\n settings: DeepPartial\n): Promise<{\n Configuration: MerchantRentalConfiguration;\n}> => Put('/merchant/rental', { Settings: settings });\n\nexport const getMerchantRentalPaymentSetup = (): Promise<{\n PaymentMethod?: { BankName: string; LastFour: string };\n SetupIntentClientSecret?: string;\n}> => Get('/merchant/rental/payment');\n\nexport const getRentalDocuments = () =>\n Get<{ Metadata: AttachmentFile[] }>(`/merchant/rental/waiverdocuments`).then(\n (d) => d.Metadata || []\n );\n\nexport const uploadRentalDocuments = (files: File[]) => {\n const form = new FormData();\n files.forEach((file) => form.append('files[]', file));\n return Post<{ Metadata: AttachmentFile[] }>(`/merchant/rental/waiverdocuments`, undefined, {\n body: form\n }).then((d) => d.Metadata || []);\n};\n\nexport const deleteRentalDocument = (id: string) =>\n Delete<{ Metadata: AttachmentFile[] }>(`/merchant/rental/waiverdocuments/${id}`).then(\n (d) => d.Metadata || []\n );\n\nexport const getRentalWaivers = (): Promise =>\n Get<{\n Entries: WaiverEntry[];\n }>('/merchant/rental/waivers').then((d) => d.Entries);\n\nexport const getRentalWaiver = (waiverId: string): Promise =>\n Get<{\n Entry: WaiverEntry;\n }>(`/merchant/rental/waiver/${waiverId}`).then((d) => d.Entry);\n\nexport const getRentalBooking = (bookingId: string): Promise =>\n Get<{\n Entry: WaiverEntry;\n }>(`/merchant/rental/booking/${bookingId}`).then((d) => d.Entry);\n\nexport const createRentalBooking = (\n booking: RentalBooking,\n integrationId?: string\n): Promise<{ UpdatedBooking: RentalBooking; UpdatedWaiver?: RentalWaiver }> =>\n Post<{\n Booking: RentalBooking;\n Waiver: RentalWaiver;\n }>(\n '/merchant/rental/booking',\n { Booking: booking },\n { headers: { 'X-Merchant-Integration-Id': integrationId || '' } }\n ).then((d) => ({\n UpdatedBooking: d.Booking,\n UpdatedWaiver: d.Waiver\n }));\n\nexport const updateRentalBooking = (\n booking: RentalBooking,\n integrationId?: string\n): Promise<{ UpdatedBooking: RentalBooking; UpdatedWaiver?: RentalWaiver }> =>\n Put<{\n Booking: RentalBooking;\n Waiver: RentalWaiver;\n }>(\n `/merchant/rental/booking/${booking.ID}`,\n { Booking: booking },\n { headers: { 'X-Merchant-Integration-Id': integrationId || '' } }\n ).then((d) => ({\n UpdatedBooking: d.Booking,\n UpdatedWaiver: d.Waiver\n }));\n\nexport const getWaiverPricing = (\n booking: RentalBooking,\n waiver: RentalWaiver,\n integrationId?: string\n): Promise =>\n Post<{\n Waiver: RentalWaiver;\n }>(\n '/merchant/rental/waiver/price',\n { Booking: booking, Waiver: waiver },\n { headers: { 'X-Merchant-Integration-Id': integrationId || '' } }\n ).then((d) => d.Waiver);\n\nexport const createRentalWaiver = (\n booking: RentalBooking,\n waiver: RentalWaiver,\n sendEmail: boolean,\n integrationId?: string\n): Promise<{ UpdatedBooking: RentalBooking; UpdatedWaiver: RentalWaiver }> =>\n Post<{\n Booking: RentalBooking;\n Waiver: RentalWaiver;\n }>(\n '/merchant/rental/waiver',\n { Booking: booking, Waiver: waiver, SendEmail: sendEmail },\n { headers: { 'X-Merchant-Integration-Id': integrationId || '' } }\n ).then((d) => ({\n UpdatedBooking: d.Booking,\n UpdatedWaiver: d.Waiver\n }));\n\nexport const updateRentalWaiver = (\n booking: RentalBooking,\n waiver: RentalWaiver,\n sendEmail: boolean,\n integrationId?: string\n): Promise<{ UpdatedBooking: RentalBooking; UpdatedWaiver: RentalWaiver }> =>\n Put<{\n Booking: RentalBooking;\n Waiver: RentalWaiver;\n }>(\n '/merchant/rental/waiver',\n { Booking: booking, Waiver: waiver, SendEmail: sendEmail },\n { headers: { 'X-Merchant-Integration-Id': integrationId || '' } }\n ).then((d) => ({\n UpdatedBooking: d.Booking,\n UpdatedWaiver: d.Waiver\n }));\n\nexport const createMerchantRentalAsset = (asset: DeepPartial) =>\n Post<{ Asset: RentalAsset }>(`/merchant/rental/assets`, { Asset: asset }).then((r) => r.Asset);\n\nexport const deleteMerchantRentalAsset = (assetId: string) =>\n Delete(`/merchant/rental/assets/${assetId}`);\n\nexport const addRentalAssetSerialNumber = (\n assetId: string,\n serialNumber: string,\n integrationId?: string\n) =>\n Put<{ Asset: RentalAsset }>(\n `/merchant/rental/assets/${assetId}/serialnumber`,\n {\n SerialNumber: serialNumber\n },\n { headers: { 'X-Merchant-Integration-Id': integrationId || '' } }\n ).then((r) => r.Asset);\n\nexport const getRentalWaiverPaymentSession = (waiverId: string): Promise =>\n Post<{ StripeClientSecret: string }>(`/merchant/rental/waiver/${waiverId}/payment`).then((d) => {\n return d.StripeClientSecret;\n });\n\nexport const getRentalWaiverAgreementInformation = (\n waiverId: string\n): Promise<{\n SignedDate: Date;\n CustomerName: string;\n MerchantName: string;\n RentalBookingDate: Date;\n RentalBookingDurationInDays: number;\n}> => Post(`/merchant/rental/waiver/${waiverId}/agreement`);\n\nexport const completeRentalBooking = (\n bookingId: string,\n signatureData: string,\n insured?: DeepPartial\n): Promise =>\n Post<{\n Booking: RentalBooking;\n }>(`/merchant/rental/booking/${bookingId}/complete`, {\n Insured: insured,\n SignatureData: signatureData\n }).then((d) => d.Booking);\n\nexport const completeRentalWaiver = (\n waiverId: string,\n signatureData: string,\n insured?: DeepPartial\n): Promise =>\n Post<{\n Waiver: RentalWaiver;\n }>(`/merchant/rental/waiver/${waiverId}/complete`, {\n Insured: insured,\n SignatureData: signatureData\n }).then((d) => d.Waiver);\n\nexport const cancelBookingRenewal = (bookingId: string): Promise =>\n Post<{\n Booking: RentalBooking;\n }>(`/merchant/rental/booking/${bookingId}/cancel-renew`).then((d) => d.Booking);\n\nexport const getMerchantRentalAssets = (integrationId?: string): Promise =>\n Get<{\n Assets?: RentalAsset[];\n }>('/merchant/rental/assets', {\n headers: { 'X-Merchant-Integration-Id': integrationId || '' }\n }).then((d) => d.Assets || []);\n\nexport const getRentalBookingFiles = (id: string) =>\n Get<{\n Files: AttachmentFile[] | null;\n }>(`/merchant/rental/booking/${id}/files`).then((res) => res.Files);\n\nexport const getRentalClaimByWaiverId = (\n waiverId: string\n): Promise<{ Entry: WaiverEntry; Claim?: RentalClaim; Attachments: AttachmentFile[] }> =>\n Get<{\n Entry: WaiverEntry;\n Claim?: RentalClaim;\n Attachments: AttachmentFile[];\n }>(`/merchant/rental/waiver/${waiverId}/claim`);\n\nexport const getRentalClaim = (\n id: string\n): Promise<{ Entry: WaiverEntry; Claim: RentalClaim; Attachments: AttachmentFile[] }> =>\n Get<{\n Entry: WaiverEntry;\n Claim: RentalClaim;\n Attachments: AttachmentFile[];\n }>(`/merchant/rental/claim/${id}`);\n\nexport const createRentalClaim = (claim: RentalClaim): Promise =>\n Post<{ UpdatedClaim: RentalClaim }>(`/merchant/rental/claim`, {\n Claim: claim\n }).then((res) => res.UpdatedClaim);\n\nexport const updateRentalClaim = (id: string, claim: RentalClaim): Promise =>\n Put<{ UpdatedClaim: RentalClaim }>(`/merchant/rental/claim/${id}`, {\n Claim: claim\n }).then((res) => res.UpdatedClaim);\n\nexport const uploadClaimAttachments = (claimId: string, fileRole: string, files: File[]) => {\n const form = new FormData();\n files.forEach((file) => form.append('files[]', file));\n form.append('FileRole', fileRole);\n return Post<{ Metadata: AttachmentFile[] }>(\n `/merchant/rental/claim/${claimId}/attachment`,\n undefined,\n {\n body: form\n }\n ).then((d) => d.Metadata || []);\n};\n\nexport const getMerchantReferralList = () =>\n Get<{ Referrals: UserReferral[] }>('/merchant/referral/list').then((d) => d.Referrals || []);\n\nexport const getMerchantBIApplication = (applicationID: string) =>\n Get<{ Application: BusinessInsuranceApplication }>(`/merchant/bi/${applicationID}`).then(\n (d) => d.Application\n );\n\nexport const updateMerchantBIApplication = (\n applicationID: string,\n businessInformation: BusinessInformation\n) =>\n Put<{ Application: BusinessInsuranceApplication; NextValidationError?: ValidationError }>(\n `/merchant/bi/${applicationID}`,\n {\n BusinessInformation: businessInformation\n }\n );\n\nexport const createMerchantBIApplication = (businessInformation: BusinessInformation) =>\n Post<{ Application: BusinessInsuranceApplication; NextValidationError?: ValidationError }>(\n `/merchant/bi`,\n {\n BusinessInformation: businessInformation\n }\n );\n\nexport const uploadCommercialDocuments = (\n applicationID: string,\n fileRole: DefaultFileRoles,\n files: File[],\n existingFileIDs?: string[]\n) => {\n const form = new FormData();\n form.append('FileRole', fileRole);\n if (existingFileIDs) {\n form.append('ExistingFileIDs', JSON.stringify(existingFileIDs));\n }\n files.forEach((file) => form.append('files[]', file));\n return Post<{ Metadata: AttachmentFile[] }>(\n `/merchant/commercial/${applicationID}/documents`,\n undefined,\n {\n body: form\n }\n ).then((res) => res.Metadata);\n};\n\nexport const triggerCommercialDataExtraction = (applicationID: string) =>\n Post<{ Application: BusinessInsuranceApplication }>(\n `/merchant/commercial/${applicationID}/verify`,\n null\n );\n\nexport const retrieveCommercialDataExtraction = (applicationID: string, force: boolean) =>\n Get<{ Completed: boolean; Application: BusinessInsuranceApplication }>(\n `/merchant/commercial/${applicationID}/verify/retrieve?force=${force}`\n );\n\nexport const submitBusinessInsuraceApplication = (applicationID: string) =>\n Post<{ Redirect: string; Application: BusinessInsuranceApplication }>(\n `/merchant/bi/${applicationID}/complete`,\n null\n );\n","import { useCookies, Cookies } from 'react-cookie';\nimport {\n PolicyReferralChannelInfo,\n ReferralChannelType,\n ReferralLinkIntegration\n} from '@oysterjs/types';\nimport apm from '../apm';\n\nconst DEPRECATED_ATTRIBUTION_COOKIE = '_oysterc';\nconst ATTRIBUTION_COOKIE = 'oyster_attribution';\nconst TRACKING_COOKIE = 'oyster_direct';\nconst COOKIE_TYPE = 'type';\n\nexport enum CookieParameters {\n AttributionType = 'type',\n MerchantIntegrationId = 'merchant_integration_id',\n UtmSource = 'utm_source',\n UtmCampaign = 'utm_campaign',\n UtmMedium = 'utm_medium',\n UtmTerm = 'utm_term',\n GoogleId = 'gclid',\n FacebookId = 'fbclid',\n PageHistory = '_oh'\n}\n\nenum AttributionCookieType {\n Merchant = 'merchant',\n Marketing = 'marketing',\n Direct = 'direct'\n}\n\nconst getPageHistory = (params: URLSearchParams): string[] => {\n try {\n return JSON.parse(params.get(CookieParameters.PageHistory) || '[]');\n } catch (e) {\n apm().captureError(e);\n return [];\n }\n};\n\nconst getParametersString = (params: URLSearchParams): string => {\n const paramsCopy = new URLSearchParams(params);\n paramsCopy.delete(CookieParameters.PageHistory);\n return paramsCopy.toString();\n};\n\nexport const isMerchantReferral = (): boolean =>\n getCookieData().has(CookieParameters.MerchantIntegrationId);\n\nexport const useReferralChannel = (): PolicyReferralChannelInfo | undefined => {\n useCookies();\n const params = getCookieData();\n\n // Extract params if exists\n if (params) {\n let type = params.get(COOKIE_TYPE);\n if (!type) {\n // If type doesn't exist, we try to infer based on other existing parameters\n if (params.has(CookieParameters.MerchantIntegrationId)) {\n type = AttributionCookieType.Merchant;\n } else if (\n params.has(CookieParameters.UtmSource) &&\n params.has(CookieParameters.UtmCampaign)\n ) {\n type = AttributionCookieType.Marketing;\n } else if (params.has(CookieParameters.GoogleId) || params.has(CookieParameters.FacebookId)) {\n type = AttributionCookieType.Marketing;\n }\n }\n\n const ReferralSubChannel = apm().getReferralSubChannel();\n switch (type) {\n case AttributionCookieType.Merchant:\n return {\n ReferralChannel: ReferralChannelType.ReferralChannelMerchant,\n ReferralSubChannel,\n Settings: {\n MerchantID: '',\n IntegrationID: params.get(CookieParameters.MerchantIntegrationId) || ''\n }\n };\n case AttributionCookieType.Marketing: {\n let source = params.get(CookieParameters.UtmSource);\n let campaign = params.get(CookieParameters.UtmCampaign);\n if (!source) {\n if (params.has(CookieParameters.GoogleId)) {\n source = 'google';\n campaign = params.get(CookieParameters.GoogleId);\n } else if (params.has(CookieParameters.FacebookId)) {\n source = 'facebook';\n campaign = params.get(CookieParameters.FacebookId);\n }\n }\n\n return {\n ReferralChannel: ReferralChannelType.ReferralChannelMarketingCampaign,\n ReferralSubChannel,\n Settings: {\n Source: source || '',\n Campaign: campaign || '',\n Medium: params.get(CookieParameters.UtmMedium) || '',\n Term: params.get(CookieParameters.UtmTerm) || '',\n PageHistory: getPageHistory(params)\n }\n };\n }\n default:\n return {\n ReferralChannel: ReferralChannelType.ReferralChannelDirect,\n ReferralSubChannel,\n Settings: {\n Parameters: getParametersString(params),\n PageHistory: getPageHistory(params)\n }\n };\n }\n }\n\n return undefined;\n};\n\nexport const setMerchantAttribution = (integration: ReferralLinkIntegration) => {\n const cookies = new Cookies();\n const params = new URLSearchParams({\n [CookieParameters.AttributionType]: AttributionCookieType.Merchant,\n [CookieParameters.MerchantIntegrationId]: integration.IntegrationID\n });\n cookies.set(ATTRIBUTION_COOKIE, params.toString(), { path: '/' });\n};\n\nconst getCookieData = (): URLSearchParams => {\n const cookies = new Cookies();\n return [\n cookies.get(DEPRECATED_ATTRIBUTION_COOKIE) || '',\n cookies.get(TRACKING_COOKIE) || '',\n cookies.get(ATTRIBUTION_COOKIE) || ''\n ]\n .map((v) => new URLSearchParams(v))\n .reduce((prev, curr) => {\n curr.forEach((v, k) => prev.set(k, v));\n return prev;\n }, new URLSearchParams());\n};\n","import styled, { keyframes } from 'styled-components';\n\nexport const TextSkeleton = styled.span`\n display: inline;\n margin: 0px 5px;\n border-radius: 5px;\n padding: 0px 2px;\n background: rgba(255, 255, 255, 0.3);\n animation: ${keyframes`\n 0% {\n background: rgba(255, 255, 255, 0.3);\n }\n 25% {\n background: rgba(255, 255, 255, 0.45);\n }\n 100% {\n background: rgba(255, 255, 255, 0.3);\n }\n `} 2s infinite;\n color: transparent;\n font-weight: bold;\n word-break: break-all;\n`;\n","import { createTheme, Theme } from '@material-ui/core';\n\nexport const getTheme = (primaryColor: string, tonalOffset: number): Theme | null => {\n return createTheme({\n palette: {\n contrastThreshold: 4.5,\n tonalOffset: tonalOffset,\n primary: {\n main: primaryColor\n }\n }\n });\n};\n\nexport const isDark = (hexColor: string): boolean => {\n const m = hexColor.match(/^#?([\\da-f]{2})([\\da-f]{2})([\\da-f]{2})$/i);\n const red = m ? parseInt(m[1], 16) : -1;\n const green = m ? parseInt(m[2], 16) : -1;\n const blue = m ? parseInt(m[3], 16) : -1;\n return red >= 0 && green >= 0 && blue >= 0\n ? (0.2126 * red) / 255 + (0.7152 * green) / 255 + (0.0722 * blue) / 255 >= 0.5\n ? true\n : false\n : true; // default to dark\n};\n","import {\n AccountSummary,\n AttachmentFile,\n Policy,\n Product,\n Statement,\n UserReferral,\n UserVerification\n} from '@oysterjs/types';\nimport { Delete, Get, getEncodedQueryString, Post } from './base';\nimport { CreatePolicyOptions } from './sdk';\nimport config from '@oysterjs/core/config';\n\nexport const getPolicies = () => Get<{ Policies: Policy[] }>('/user/policies');\n\nexport const getPolicy = (policyId: string) =>\n Get<{ Policy: Policy }>(`/user/policies/${policyId}`);\n\nexport const userCreatePolicy = (products: Product[], opts?: CreatePolicyOptions) =>\n Post<{ Policy: Policy }>(\n `/user/policy?${getEncodedQueryString({\n AllowValidationErrors: opts?.allowValidationErrors\n })}`,\n {\n Products: products,\n Insured: opts?.insured,\n ReferralChannelInfo: opts?.referralChannel\n },\n {\n headers: {\n 'X-Merchant-API-Key': opts?.merchantApiKey || config().merchant?.apiKey || '',\n 'X-Merchant-Integration-ID': opts?.integrationID || ''\n }\n }\n );\n\nexport const getAccountSummary = (redirectUrl?: string) =>\n Get('/user/account', { redirectUrl });\n\nexport const confirmAccount = (redirectUrl?: string) =>\n Post('/user/account/confirm', undefined, { redirectUrl });\n\nexport const getAccountStatement = (statementId: string) =>\n Get<{ Statement: Statement }>(`/user/account/statements/${statementId}`);\n\nexport const retrieveVerificationSession = () =>\n Post<{ VerificationSession: UserVerification }>('/user/account/verifications');\n\nexport const deletePaymentMethod = (id: string) => Delete(`/user/account/payment_methods/${id}`);\n\nexport const makePaymentMethodDefault = (id: string) =>\n Post(`/user/account/payment_methods/${id}/default`);\n\nexport const createPaymentMethod = () =>\n Post<{ StripeClientSecret: string }>('/user/account/payment_methods').then(\n (d) => d.StripeClientSecret\n );\n\nexport const getReferralList = () =>\n Get<{ Referrals: UserReferral[] }>('/user/referrals').then((d) => d.Referrals || []);\n\nexport const expressInterest = (product: string) => Post('/user/interest', { Product: product });\n\nexport const userCreatePolicyAttachments = (policyId: string, fileRole: string, files: File[]) => {\n const form = new FormData();\n files.forEach((file) => form.append('files[]', file));\n form.append('FileRole', fileRole);\n return Post<{ Metadata: AttachmentFile[] }>(`/user/policy/${policyId}/attach`, undefined, {\n body: form\n }).then((d) => d.Metadata || []);\n};\n\nexport const userGetPaymentSession = (policyId: string): Promise =>\n Post<{ StripeClientSecret: string }>(`/user/policies/${policyId}/payment`).then((d) => {\n return d.StripeClientSecret;\n });\n\nexport const userCompletePolicy = (policyId: string) =>\n Post<{ Policy: Policy; Redirect?: string }>(`/user/policies/${policyId}/complete`, undefined, {\n headers: {\n 'X-Merchant-API-Key': config().merchant?.apiKey || ''\n }\n });\n\nexport const userIdentify = (email?: string, firstName?: string, lastName?: string) =>\n Post<{ Token: string; Email: string }>(`/user/identify`, {\n Email: email,\n FirstName: firstName,\n LastName: lastName\n });\n","import {\n AttachmentFile,\n Insured,\n Merchant,\n MerchantIntegration,\n Policy,\n PolicyReferralChannelInfo,\n Price,\n Product,\n ReferralSubChannel as ReferralSubChannel,\n ValidationError\n} from '@oysterjs/types';\nimport { Delete, Get, getEncodedQueryString, Post, Put } from './base';\nimport { userCreatePolicy } from './user';\nimport config from '@oysterjs/core/config';\nimport { userCompletePolicy, userGetPaymentSession } from './user';\n\nexport const sdkGetIntegrationConfig = () =>\n Get<{ Integration: MerchantIntegration; Merchant: Merchant }>(\n `/sdk/integration/${config().merchant?.integrationId || ''}`,\n {\n headers: { 'X-Merchant-API-Key': config().merchant?.apiKey || '' }\n }\n ).then((res) => res);\n\nexport const sdkGetPolicy = (policyId: string) =>\n Get<{ Policy: Policy }>(`/sdk/policy/${policyId}`, {\n headers: { 'X-Merchant-API-Key': config().merchant?.apiKey || '' }\n }).then((d) => d.Policy);\n\nexport interface CreatePolicyOptions {\n merchantApiKey?: string;\n integrationID?: string;\n insured?: Insured;\n allowValidationErrors?: boolean;\n referralChannel?: PolicyReferralChannelInfo;\n referralSubChannel?: ReferralSubChannel;\n}\n\nexport const sdkCreateQuote = (\n products: Product[],\n opts?: CreatePolicyOptions\n): Promise<{ Policy?: Policy }> =>\n Post(\n `/sdk/quote?${getEncodedQueryString({\n AllowValidationErrors: opts?.allowValidationErrors\n })}`,\n {\n Products: products,\n Insured: opts?.insured,\n ReferralChannelInfo: opts?.referralChannel,\n ReferralSubChannel: opts?.referralSubChannel\n },\n {\n headers: {\n 'X-Merchant-API-Key': opts?.merchantApiKey || config().merchant?.apiKey || '',\n 'X-Merchant-Integration-ID': opts?.integrationID || config().merchant?.integrationId || ''\n }\n }\n );\n\nexport const sdkCreatePolicy = (\n products: Product[],\n opts?: CreatePolicyOptions\n): Promise<{ Policy: Policy }> => {\n switch (config().serviceName) {\n case 'oysterjs':\n case 'getoyster':\n return Post<{ Policy: Policy }>(\n `/sdk/policy?${getEncodedQueryString({\n AllowValidationErrors: opts?.allowValidationErrors\n })}`,\n {\n Products: products,\n Insured: opts?.insured,\n ReferralChannelInfo: opts?.referralChannel,\n ReferralSubChannel: opts?.referralSubChannel\n },\n {\n headers: {\n 'X-Merchant-API-Key': opts?.merchantApiKey || config().merchant?.apiKey || '',\n 'X-Merchant-Integration-ID':\n opts?.integrationID || config().merchant?.integrationId || ''\n }\n }\n );\n\n case 'dashboard':\n return userCreatePolicy(products, opts);\n\n default:\n throw new Error(`Invalid service name for policy creation: ${config().serviceName}`);\n }\n};\n\nexport const sdkUpdatePolicy = (policy: Policy, isD2CFlow?: boolean) =>\n Put<{ Policy: Policy; NextValidationError?: ValidationError }>(\n `/sdk/policy/${policy.ID}`,\n { Policy: policy, IsD2CFlow: !!isD2CFlow },\n {\n headers: { 'X-Merchant-API-Key': config().merchant?.apiKey || '' }\n }\n );\n\nexport const sdkGetPolicyPremium = (policyId: string) =>\n Get<{ Premium: Price; Underwriter?: string }>(`/sdk/policy/${policyId}/premium`);\n\nexport const sdkValidatePolicy = (policyId: string): Promise =>\n Post<{ Policy: Policy }>(`/sdk/policy/${policyId}/partnerValidation`).then((d) => d.Policy);\n\nexport const sdkConfirmPolicy = (policyId: string, insured?: Insured): Promise =>\n Post<{ Policy: Policy }>(\n `/sdk/policy/${policyId}/confirm`,\n {\n Insured: insured\n },\n {\n headers: { 'X-Merchant-API-Key': config().merchant?.apiKey || '' }\n }\n ).then((d) => d.Policy);\n\nexport const sdkGetPaymentSession = (policyId: string): Promise => {\n switch (config().serviceName) {\n case 'oysterjs':\n case 'getoyster':\n return Post<{ StripeClientSecret: string }>(`/sdk/policy/${policyId}/payment`).then((d) => {\n return d.StripeClientSecret;\n });\n\n case 'dashboard':\n return userGetPaymentSession(policyId);\n\n default:\n throw new Error(`Invalid service name for payment session: ${config().serviceName}`);\n }\n};\n\nexport const sdkCompletePolicy = (policyId: string) => {\n switch (config().serviceName) {\n case 'oysterjs':\n case 'getoyster':\n return Post<{ Policy: Policy; Redirect?: string }>(\n `/sdk/policy/${policyId}/complete`,\n undefined,\n {\n headers: {\n 'X-Merchant-API-Key': config().merchant?.apiKey || ''\n }\n }\n );\n\n case 'dashboard':\n return userCompletePolicy(policyId);\n\n default:\n throw new Error(`Invalid service name for policy completion: ${config().serviceName}`);\n }\n};\n\nexport const sdkGetPolicyFilesMetadata = (policyId: string) => {\n return Get<{ Metadata: AttachmentFile[] }>(`/sdk/policy/${policyId}/files/metadata`).then(\n (d) => d.Metadata || []\n );\n};\n\nexport const sdkCreateTemporaryAttachments = (\n fileRole: string,\n files: File[],\n existingFileIDs?: string[]\n) => {\n const form = new FormData();\n files.forEach((file) => form.append('files[]', file));\n form.append('FileRole', fileRole);\n if (existingFileIDs) {\n form.append('ExistingFileIDs', JSON.stringify(existingFileIDs));\n }\n return Post<{ Metadata: AttachmentFile[] }>(`/sdk/attachments`, undefined, { body: form }).then(\n (d) => d.Metadata || []\n );\n};\n\nexport const sdkDeletePolicyAttachment = (policyId: string, fileID: string) => {\n return Delete<{ Metadata: AttachmentFile[] }>(\n `/user/policy/${policyId}/attachment/${fileID}`,\n undefined\n ).then((d) => d.Metadata || []);\n};\n","import * as React from 'react';\nimport { IoInformationCircleOutline } from 'react-icons/io5';\nimport styled from 'styled-components';\n\nconst BannerContainer = styled.div`\n background: #fff3f0;\n padding: 20px 20px;\n border-radius: 8px;\n box-sizing: border-box;\n width: 100%;\n\n transition: 0.15s all ease-in-out;\n\n display: flex;\n flex-direction: row;\n gap: 10px;\n`;\n\nconst ContentContainer = styled.div`\n width: 100%;\n display: flex;\n flex-direction: row;\n gap: 10px;\n\n @media (max-width: 600px) {\n flex-direction: column;\n }\n`;\n\nconst IconContainer = styled.div`\n font-size: 1.2em;\n`;\n\nconst TextContainer = styled.div`\n flex-grow: 1;\n`;\n\nconst ActionContainer = styled.div`\n min-width: 180px;\n height: 100%;\n display: flex;\n justify-content: flex-end;\n\n @media (max-width: 600px) {\n justify-content: flex-start;\n }\n`;\n\nconst TitleText = styled.div`\n font-weight: 500;\n font-size: 0.9em;\n padding-bottom: 5px;\n`;\n\nconst DescriptionText = styled.div`\n font-size: 0.8em;\n color: #555555;\n`;\n\ninterface BannerProps {\n title: string;\n description: string | JSX.Element;\n bannerAction?: JSX.Element;\n}\n\nexport const Banner: React.FunctionComponent<\n React.AllHTMLAttributes & BannerProps\n> = (props): JSX.Element => (\n \n \n \n \n \n \n {props.title}\n {props.description}\n \n {props.bannerAction && {props.bannerAction}}\n \n \n);\n","/**\n * Debounces a function\n */\nexport default ) => ReturnType>(func: F, waitFor: number) => {\n let timeout: NodeJS.Timeout;\n\n const debounced = (...args: Parameters) => {\n clearTimeout(timeout);\n timeout = setTimeout(() => func(...args), waitFor);\n };\n\n return debounced;\n};\n","import React from 'react';\nimport { Cookies } from 'react-cookie';\nimport { useLocation } from 'react-router';\nimport { userIdentify } from './api/user';\nimport debounce from './debounce';\n\n/**\n * The tracking script ID used to connect this instance to our account.\n */\nconst PORTAL_ID = '22383747';\n\n/**\n * Hubspot chat properties that can be accessed through the context.\n */\ninterface HubspotChatContextProps {\n /**\n * Manually identify the current user based on the provided information.\n */\n identify: (email: string, firstName?: string, lastName?: string) => void;\n}\n\n/**\n * Hubspot chat context which gives access to HubspotChatContextProps.\n */\nexport const HubspotChatContext = React.createContext(null);\n\n/**\n * Provider that allows child elements to access HubspotChatContext. Also\n * initializes and renders the chat widget.\n */\nexport const HubspotChatContextProvider = (props: React.PropsWithChildren) => {\n const chat = useHubspotChat(PORTAL_ID);\n return {props.children};\n};\n\n/**\n * Implements the interface to the Hubspot chat widget and manages its lifecycle.\n *\n * @param portalId the script ID to use to initialize Hubspot\n */\nexport const useHubspotChat = (portalId): HubspotChatContextProps => {\n // Indicates that the Hubspot script has loaded (not necessarily the widget)\n const [scriptLoaded, setScriptLoaded] = React.useState(false);\n // Indicates that we are manually identifying the user based on information\n // they have entered, and that this should take precedence over the absense\n // of any automatic user identification.\n const [manualIdentification, setManualIdentification] = React.useState(false);\n // Listen for path changes to reset the identification when the user navigates\n // between pages.\n const location = useLocation();\n\n /**\n * Updates the Hubspot identification of the user based on a response\n * from the API server. It returns true if the identification changed\n * and false otherwise.\n */\n const updateIdentification = (email: string, token: string): boolean => {\n if (\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n window.hsConversationsSettings.identificationEmail !== email ||\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n window.hsConversationsSettings.identificationToken !== token\n ) {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n if (email && email !== window.hsConversationsSettings.identificationEmail) {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n window._hsq = window._hsq || [];\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n window._hsq.push(['identify', { email }]);\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n } else if (!email && email !== window.hsConversationsSettings.identificationEmail) {\n new Cookies().remove('hubspotutk');\n }\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n window.hsConversationsSettings.identificationEmail = email;\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n window.hsConversationsSettings.identificationToken = token;\n\n return true;\n }\n\n return false;\n };\n\n const trackPageView = () => {\n const path = location.pathname + location.search;\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n window._hsq = window._hsq || [];\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n window._hsq.push(['setPath', path]);\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n window._hsq.push(['trackPageView']);\n };\n\n /**\n * Performs identification with the API server, using either the provided\n * credentials or falling back to the session cookie which authenticates\n * the current user. It returns true if the identification changed and false\n * otherwise.\n */\n const identify = async (\n email?: string,\n firstName?: string,\n lastName?: string,\n manual?: boolean\n ): Promise => {\n if (!scriptLoaded) {\n return false;\n }\n\n try {\n // get token\n const res = await userIdentify(email, firstName, lastName);\n setManualIdentification((prev) => prev || !!manual);\n return updateIdentification(res.Email, res.Token);\n } catch (e) {\n // Only reset the identification if the user was not identified manually\n if (manualIdentification || manual) {\n return false;\n }\n\n return updateIdentification('', '');\n }\n };\n\n /**\n * Resets the widget state and reloads it. Call this when the identification\n * changes in order to reflect the latest state. This function is debounced\n * so that the latest state is always available and to respect the 1-second\n * throttle that the Hubspot SDK applies.\n */\n const refreshWidget = debounce(() => {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n window.HubSpotConversations.clear({ resetWidget: true });\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n window.HubSpotConversations.widget.load();\n }, 500);\n\n /**\n * Effect that controls the initial load of the hook. It adds the\n * Hubspot script tag and sets `scriptLoaded` to `true` when the\n * script finishes loading and is ready.\n */\n React.useEffect(() => {\n // Set HubSpot chat settings\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n window.hsConversationsSettings = {\n loadImmediately: false,\n identificationEmail: '',\n identificationToken: ''\n };\n\n // Add event listener.\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n window.hsConversationsOnReady = [() => setScriptLoaded(true)];\n\n // Create script component.\n const script = document.createElement('script');\n script.src = `//js.hs-scripts.com/${portalId}.js`;\n script.defer = true;\n\n // Add the script to the DOM\n document.head.appendChild(script);\n\n // Cleanup when the component is component is unmounted\n return () => {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n window.hsConversationsOnReady = [];\n document.head.removeChild(script);\n };\n }, []);\n\n /**\n * Effect controls the initial load of the widget by identifying\n * the current user (using the automatic, cookie-based method) and\n * then loads the widget.\n */\n React.useEffect(() => {\n if (scriptLoaded) {\n identify().then(() => {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n window.HubSpotConversations.widget.load();\n });\n }\n }, [scriptLoaded]);\n\n /**\n * Effect controls subsequent page route changes by re-performing\n * identification and refreshing the widget if identification changes.\n */\n React.useEffect(() => {\n if (scriptLoaded) {\n identify()\n .then((updatedIdentification) => {\n if (updatedIdentification) {\n refreshWidget();\n }\n })\n .finally(trackPageView);\n }\n\n // This effect intentionally omits `loaded` from the array\n // so that identification and refresh only runs on subsequent\n // page changes. The effect above should take care of the\n // initial render and loading of the chat widget.\n }, [location.pathname, location.search]);\n\n return {\n identify: async (email, firstName, lastName) => {\n const updatedIdentification = await identify(email, firstName, lastName, true);\n if (updatedIdentification) {\n refreshWidget();\n }\n }\n };\n};\n","import * as React from 'react';\nimport styled from 'styled-components';\n\nimport { Spinner } from '../Spinner';\n\ninterface Props {\n request: Promise;\n exactlyOnce?: boolean;\n hideSpinner?: boolean;\n inline?: boolean;\n children: (data: T) => JSX.Element | null;\n}\n\nexport const LoadableContainer = styled.div<{ inline?: boolean }>`\n width: 100%;\n box-sizing: border-box;\n ${(props) =>\n props.inline\n ? ''\n : `\n padding: 40px 0px;display: flex;\n flex-direction: row;\n justify-content: center;\n `}\n`;\n\nexport function Loadable(props: Props): JSX.Element | null {\n const [data, setData] = React.useState(null);\n\n React.useEffect(() => {\n if (!props.exactlyOnce || !data) {\n props.request.then(setData);\n }\n }, [props.request]);\n\n if (!data) {\n return props.hideSpinner ? null : (\n \n \n \n );\n }\n\n return props.children(data);\n}\n","import styled from 'styled-components';\n\nconst SwitchContainer = styled.div``;\n\nconst SwitchLabel = styled.label<{ disabled?: boolean }>`\n position: relative;\n display: inline-block;\n width: 48px;\n height: 26px;\n\n opacity: ${(props) => (props.disabled ? 0.5 : 1.0)};\n transition: 0.15s all ease-in-out;\n\n input {\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .slider {\n position: absolute;\n cursor: ${(props) => (props.disabled ? 'default' : 'pointer')};\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n border: 1px solid rgba(200, 200, 200, 0.8);\n transition: 0.15s all ease-in-out;\n border-radius: 34px;\n background: white;\n }\n\n .slider:before {\n position: absolute;\n content: '';\n height: 18px;\n width: 18px;\n left: 4px;\n bottom: 3px;\n background-color: rgba(200, 200, 200, 0.8);\n transition: 0.15s all ease-in-out;\n border-radius: 50%;\n }\n\n input:checked + .slider {\n background-color: #0ea5e9;\n border: 1px solid #0ea5e9;\n }\n\n input:checked + .slider:before {\n transform: translateX(20px);\n background-color: white;\n }\n`;\n\nexport const Switch = (props: {\n enabled?: boolean;\n disabled?: boolean;\n loading?: boolean;\n onChange?: (checked: boolean) => void;\n}): JSX.Element => (\n \n \n {\n props.onChange?.(e.currentTarget.checked);\n }}\n />\n \n \n \n);\n","import styled from 'styled-components';\nimport {\n InStoreSalesChannelType,\n Merchant,\n OnlineSalesChannelType,\n Personalization,\n RentalProductType,\n SalesChannelType,\n ValidationError\n} from '@oysterjs/types';\nimport { FormColumn, FormContainer, FormRow, FormRowHeader } from '@oysterjs/ui/Form/builder';\nimport { Checkbox, CheckboxProps } from '@oysterjs/ui/Form/checkbox';\nimport { Radio, RadioProps } from '@oysterjs/ui/Form/radio';\nimport { TextInput } from '@oysterjs/ui/Form/text';\nimport { OptionGroup } from '@oysterjs/ui/Form/option';\nimport { Select } from '@oysterjs/ui/Form/select';\n\nexport interface PageProps {\n merchant: Merchant;\n onNext: () => Promise;\n onBack: ((e) => unknown) | null;\n onUpdate: (merchant: Merchant) => void;\n validationError?: ValidationError;\n loading: boolean;\n}\n\nexport const NColumnListContainer = styled.div<{ columns: number }>`\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n gap: 8px;\n\n & > * {\n flex: 1 0\n calc(\n ${(props) => Math.floor(100 / props.columns)}% -\n ${(props) => Math.ceil(8 / props.columns)}px\n );\n }\n`;\n\nexport const LabeledCheckbox = (props: CheckboxProps) => (\n
\n \n
{props.label}
\n
\n);\n\nconst LabeledRadio = (props: RadioProps) => (\n
\n \n
{props.label}
\n
\n);\n\nexport const MerchantPersonalizationProductInsuranceForm = (props: PageProps) => {\n const updatePersonalization = (fn: (prev: Personalization) => Personalization) =>\n props.onUpdate({\n ...props.merchant,\n BusinessProfile: {\n ...props.merchant.BusinessProfile,\n Personalization: fn(props.merchant.BusinessProfile.Personalization)\n }\n });\n\n return (\n {\n e.preventDefault();\n props.onNext();\n }}\n >\n \n \n \n \n updatePersonalization((prev) => ({\n ...prev,\n SalesChannels: !prev.SalesChannels?.includes(SalesChannelType.Online)\n ? [...(prev.SalesChannels || []), SalesChannelType.Online]\n : prev.SalesChannels.filter((v) => v !== SalesChannelType.Online)\n }))\n },\n {\n title: 'Owned Retail',\n description:\n 'You accept orders in-store with a physical point-of-sale system, like Lightspeed.',\n enabled: props.merchant.BusinessProfile.Personalization.SalesChannels?.includes(\n SalesChannelType.InStore\n ),\n onSelect: () =>\n updatePersonalization((prev) => ({\n ...prev,\n SalesChannels: !prev.SalesChannels?.includes(SalesChannelType.InStore)\n ? [...(prev.SalesChannels || []), SalesChannelType.InStore]\n : prev.SalesChannels.filter((v) => v !== SalesChannelType.InStore)\n }))\n },\n {\n title: 'Dealer',\n description: 'Your sales occur through authorized dealers or third-parties.',\n enabled: props.merchant.BusinessProfile.Personalization.SalesChannels?.includes(\n SalesChannelType.Dealer\n ),\n onSelect: () =>\n updatePersonalization((prev) => ({\n ...prev,\n SalesChannels: !prev.SalesChannels?.includes(SalesChannelType.Dealer)\n ? [...(prev.SalesChannels || []), SalesChannelType.Dealer]\n : prev.SalesChannels.filter((v) => v !== SalesChannelType.Dealer)\n }))\n },\n {\n title: 'Rental',\n description: 'Some of your products are available to rent or lease.',\n enabled: props.merchant.BusinessProfile.Personalization.SalesChannels?.includes(\n SalesChannelType.Rental\n ),\n onSelect: () =>\n updatePersonalization((prev) => ({\n ...prev,\n SalesChannels: !prev.SalesChannels?.includes(SalesChannelType.Rental)\n ? [...(prev.SalesChannels || []), SalesChannelType.Rental]\n : prev.SalesChannels.filter((v) => v !== SalesChannelType.Rental)\n }))\n }\n ]}\n />\n \n \n {props.merchant.BusinessProfile.Personalization.SalesChannels?.includes(\n SalesChannelType.Online\n ) && (\n <>\n \n \n \n \n updatePersonalization((prev) => ({\n ...prev,\n OnlineSalesChannelType: v\n }))\n }\n error={\n props.validationError?.Field === 'OnlineSalesChannelType' &&\n props.validationError?.Message\n }\n style={{ maxWidth: '360px' }}\n />\n \n \n \n )}\n {props.merchant.BusinessProfile.Personalization.SalesChannels?.includes(\n SalesChannelType.Online\n ) &&\n props.merchant.BusinessProfile.Personalization.OnlineSalesChannelType ===\n OnlineSalesChannelType.Other && (\n \n \n {\n const value = e.currentTarget.value;\n updatePersonalization((prev) => ({\n ...prev,\n OnlineSalesChannelName: value\n }));\n }}\n style={{ maxWidth: '360px' }}\n />\n \n \n )}\n {props.merchant.BusinessProfile.Personalization.SalesChannels?.includes(\n SalesChannelType.Online\n ) &&\n props.merchant.BusinessProfile.Personalization.OnlineSalesChannelType ===\n OnlineSalesChannelType.Shopify && (\n \n \n {\n const value = e.currentTarget.value;\n updatePersonalization((prev) => ({\n ...prev,\n ShopifyAdminURL: value\n }));\n }}\n style={{ maxWidth: '360px' }}\n />\n \n \n )}\n {props.merchant.BusinessProfile.Personalization.SalesChannels?.includes(\n SalesChannelType.InStore\n ) && (\n <>\n \n \n \n \n updatePersonalization((prev) => ({\n ...prev,\n InStoreSalesChannelType: v\n }))\n }\n error={\n props.validationError?.Field === 'InStoreSalesChannelType' &&\n props.validationError?.Message\n }\n style={{ maxWidth: '360px' }}\n />\n \n \n \n )}\n {props.merchant.BusinessProfile.Personalization.SalesChannels?.includes(\n SalesChannelType.InStore\n ) &&\n props.merchant.BusinessProfile.Personalization.InStoreSalesChannelType ===\n InStoreSalesChannelType.Other && (\n \n \n {\n const value = e.currentTarget.value;\n updatePersonalization((prev) => ({\n ...prev,\n InStoreSalesChannelName: value\n }));\n }}\n style={{ maxWidth: '360px' }}\n />\n \n \n )}\n {props.merchant.BusinessProfile.Personalization.SalesChannels?.includes(\n SalesChannelType.Rental\n ) && (\n \n \n {\n const value = e.currentTarget.value;\n updatePersonalization((prev) => ({\n ...prev,\n RentalChannelName: value\n }));\n }}\n style={{ maxWidth: '360px' }}\n />\n \n \n )}\n \n \n \n {\n updatePersonalization((personalization) => ({\n ...personalization,\n ProductAnnualRevenue: e.currentTarget.value\n }));\n }}\n />\n \n \n \n );\n};\n\nexport const MerchantPersonalizationRentalInsuranceForm = (props: PageProps) => {\n const updatePersonalization = (fn: (prev: Personalization) => Personalization) =>\n props.onUpdate({\n ...props.merchant,\n BusinessProfile: {\n ...props.merchant.BusinessProfile,\n Personalization: fn(props.merchant.BusinessProfile.Personalization)\n }\n });\n\n return (\n {\n e.preventDefault();\n props.onNext();\n }}\n >\n \n \n \n {[\n RentalProductType.BikesAndEbikes,\n RentalProductType.Kayaks,\n RentalProductType.Paddleboards,\n RentalProductType.Other\n ].map((operation) => (\n {\n const checked =\n props.merchant.BusinessProfile.Personalization.RentalProductTypes?.includes(\n operation\n );\n if (checked) {\n updatePersonalization((personalization) => ({\n ...personalization,\n RentalProductTypes: (personalization.RentalProductTypes || []).filter(\n (o) => o !== operation\n )\n }));\n } else {\n updatePersonalization((personalization) => ({\n ...personalization,\n RentalProductTypes: [...(personalization.RentalProductTypes || []), operation]\n }));\n }\n }}\n />\n ))}\n \n \n {props.merchant.BusinessProfile.Personalization.RentalProductTypes?.includes(\n RentalProductType.Other\n ) && (\n \n \n {\n const value = e.currentTarget.value;\n updatePersonalization((prev) => ({\n ...prev,\n RentalProductTypeOtherDesc: value\n }));\n }}\n style={{ maxWidth: '360px' }}\n />\n \n \n )}\n {props.merchant.BusinessProfile.Personalization.RentalProductTypes?.some((productType) =>\n [RentalProductType.BikesAndEbikes, RentalProductType.Other].includes(productType)\n ) && (\n <>\n \n \n \n \n \n updatePersonalization((personalization) => ({\n ...personalization,\n RentalMotorizedAssetsExceedEBikeClassification: true\n }))\n }\n />\n \n updatePersonalization((personalization) => ({\n ...personalization,\n RentalMotorizedAssetsExceedEBikeClassification: false\n }))\n }\n />\n \n \n \n \n )}\n \n \n \n {\n const value = e.currentTarget.value;\n updatePersonalization((prev) => ({\n ...prev,\n RentalMaximumAssetValue: value\n }));\n }}\n />\n \n \n {\n const value = e.currentTarget.value;\n updatePersonalization((prev) => ({\n ...prev,\n RentalAverageAssetValue: value\n }));\n }}\n />\n \n \n \n \n {\n const value = e.currentTarget.value;\n updatePersonalization((prev) => ({\n ...prev,\n RentalAnnualRevenue: value\n }));\n }}\n />\n \n \n {\n const value = e.currentTarget.value;\n updatePersonalization((prev) => ({\n ...prev,\n RentalAnnualVolume: value\n }));\n }}\n />\n \n \n\n \n \n \n {\n const value = e.currentTarget.value;\n updatePersonalization((prev) => ({\n ...prev,\n RentalPlatformName: value\n }));\n }}\n />\n \n \n {\n const value = e.currentTarget.value;\n updatePersonalization((prev) => ({\n ...prev,\n RentalPointOfSaleName: value\n }));\n }}\n />\n \n \n \n );\n};\n","import React from 'react';\n\nimport { FormColumn, FormContainer, FormRow, FormRowHeader } from '@oysterjs/ui/Form/builder';\nimport { ErrorDisplay, TextInput, TextInputComponent } from '@oysterjs/ui/Form/text';\nimport {\n AssetAccessory,\n DeepPartial,\n Price,\n PublicMerchantRentalConfiguration,\n RentalAsset,\n RentalBooking,\n RentalWaiver,\n ValidationError,\n WaiverState\n} from '@oysterjs/types';\nimport {\n addRentalAssetSerialNumber,\n createRentalBooking,\n createRentalWaiver,\n getMerchantRentalAssets,\n getRentalBooking,\n getWaiverPricing,\n updateRentalBooking,\n updateRentalWaiver\n} from '@oysterjs/core/api/merchant';\nimport { SearchableSelect, Select } from '@oysterjs/ui/Form/select';\nimport { LoadableContainer } from '@oysterjs/ui/Loadable';\nimport { Spinner } from '@oysterjs/ui/Spinner';\nimport { ErrorType, WrappedError } from '@oysterjs/core/errors';\nimport { Button, ButtonContainer } from '@oysterjs/ui/Button';\nimport { IoArrowForward } from 'react-icons/io5';\nimport { useHistory } from 'react-router';\nimport { Banner } from '@oysterjs/ui/Banner';\nimport { Switch } from '@oysterjs/ui/Form/switch';\nimport config from '@oysterjs/core/config';\nimport { LabeledCheckbox } from '@oysterjs/partners/pages/personalization/forms';\n\nconst generateBookingReference = () => `B${Math.floor(Math.random() * 10000000000)}`;\nconst minimumRentalDuration = (config: PublicMerchantRentalConfiguration) =>\n config.AutoRenewWaiver ? 7 : 1;\nconst minimumRentalDurationDays = (config: PublicMerchantRentalConfiguration) => {\n const duration = minimumRentalDuration(config);\n if (duration === 1) {\n return '1 day';\n } else {\n return `${duration} days`;\n }\n};\n\nexport const RentalApplication = (props: {\n integrationId: string;\n bookingId?: string;\n rentalConfig: PublicMerchantRentalConfiguration;\n}) => {\n const today = new Date(\n new Date().getFullYear(),\n new Date().getMonth(),\n new Date().getDate(),\n new Date().getHours()\n );\n const excludableAssetThreshold = 0.15;\n\n const history = useHistory();\n\n const [loading, setLoading] = React.useState(false);\n const [submitLoading, setSubmitLoading] = React.useState(false);\n const [initialLoading, setInitialLoading] = React.useState(false);\n\n const [error, setError] = React.useState();\n const [validationError, setValidationError] = React.useState();\n const [serialNumberErrors, setSerialNumberErrors] = React.useState<(boolean | undefined)[]>([]);\n\n const [booking, setBooking] = React.useState>({\n Details: {\n BookingReference: generateBookingReference(),\n AutoRenew: props.rentalConfig.AutoRenewWaiver\n },\n StartTime: today,\n EndTime: new Date(\n today.getFullYear(),\n today.getMonth(),\n today.getDate() + minimumRentalDuration(props.rentalConfig),\n today.getHours()\n )\n });\n\n const [waiver, setWaiver] = React.useState();\n\n // Indicates that the customer opted in to the rental theft/damage waiver. This is only\n // used if this is the unified signature flow, since otherwise the customer would be\n // filling out this form for the purpose of opting in.\n const [waiverOptedIn, setWaiverOptedIn] = React.useState(\n !props.rentalConfig.UnifiedWaiverFlowEnabled\n );\n const [waiverPremium, setWaiverPremium] = React.useState({ Amount: 10, Currency: 'usd' });\n\n const [initialDate, setInitialDate] = React.useState();\n const [durationDays, setDurationDays] = React.useState(\n minimumRentalDuration(props.rentalConfig).toString()\n );\n\n const [availableAssets, setAvailableAssets] = React.useState([]);\n const [selectedAssets, setSelectedAssets] = React.useState([]);\n const [excludedAssetAccessories, setExcludedAssetAccessories] = React.useState<\n Map\n >(new Map());\n\n // Create a list of unique asset names\n const assetNames = Object.keys(\n availableAssets.map((a) => a.Name).reduce((acc, curr) => ({ ...acc, [curr]: true }), {})\n );\n\n // For each unique asset name, create a list of all the serial numbers and associated asset IDs\n const assetSerialNumbers = Object.fromEntries(\n assetNames.map((name) => [\n name,\n availableAssets\n .filter((a) => a.Name === name)\n .map((a) => ({ serialNumber: a.Details.SerialNumber, assetId: a.ID }))\n .filter((s) => !!s)\n ])\n );\n\n const fetchData = async () => {\n setInitialLoading(true);\n\n try {\n // Fetch assets\n const assets = await getMerchantRentalAssets(props.integrationId);\n setAvailableAssets(assets || []);\n\n if (props.bookingId) {\n const { Booking, Waiver } = await getRentalBooking(props.bookingId);\n if (Booking) {\n setBooking(Booking);\n\n const end = new Date(Booking.EndTime || '');\n const start = new Date(Booking.StartTime || '');\n\n setInitialDate(start);\n setDurationDays(\n Math.max(\n (end.valueOf() - start.valueOf()) / 86400000,\n minimumRentalDuration(props.rentalConfig)\n ).toString()\n );\n setSelectedAssets(Booking.Details.Assets || []);\n setInitialExcludedAccessories(Booking);\n }\n if (Waiver) {\n setWaiver(Waiver);\n }\n } else {\n setInitialDate(today);\n setDurationDays(minimumRentalDuration(props.rentalConfig).toString());\n setSelectedAssets([assets[0]]);\n }\n } finally {\n setInitialLoading(false);\n }\n };\n\n const setInitialExcludedAccessories = (booking: RentalBooking) => {\n setExcludedAssetAccessories((prev) => {\n const excludedAccessoriesMap = new Map(prev);\n // Traverse all available accessories for booking assets\n const addedAccessories = [...(booking.Details.Accessories || []).map((a) => a.Name)];\n let searchIndex = 0;\n booking.Details.Assets?.forEach((asset, index) => {\n (asset.Details.AvailableAccessories || []).forEach((acc) => {\n if (!addedAccessories.includes(acc.Name, searchIndex)) {\n excludedAccessoriesMap.set(index, [...(excludedAccessoriesMap.get(index) || []), acc]);\n } else {\n searchIndex++;\n }\n });\n });\n\n return excludedAccessoriesMap;\n });\n };\n\n const updateWaiverPremium = async () => {\n const [updatedBooking, updatedWaiver] = getUpdatedBookingAndWaiver();\n const calculatedWaiver = await getWaiverPricing(\n updatedBooking,\n updatedWaiver,\n props.integrationId\n );\n setWaiverPremium({\n Amount: calculatedWaiver.Details.Premium.Total,\n Currency: calculatedWaiver.Details.Premium.Currency\n });\n };\n\n React.useEffect(() => {\n fetchData();\n }, []);\n\n React.useEffect(() => {\n updateWaiverPremium();\n }, [booking.StartTime, booking.EndTime, booking.MerchantID, selectedAssets]);\n\n // Update excluded accessories list size upon selected asset changes\n React.useEffect(() => {\n while (excludedAssetAccessories.size < selectedAssets.length) {\n excludedAssetAccessories.set(excludedAssetAccessories.size, []);\n }\n while (excludedAssetAccessories.size > selectedAssets.length) {\n excludedAssetAccessories.delete(excludedAssetAccessories.size - 1);\n }\n }, [selectedAssets]);\n\n //================================================================\n // BOOKING AND WAIVER LOGIC\n //================================================================\n\n const onChangeBooking = (\n block: (booking?: DeepPartial) => DeepPartial\n ) => {\n setValidationError(undefined);\n setBooking({ ...block(booking) });\n };\n\n const onBookingDateUpdate = (e?: React.ChangeEvent, duration?: number) => {\n let err: ValidationError | undefined = undefined;\n let localDate = new Date(booking.StartTime as Date);\n\n if (e?.currentTarget.value) {\n const value = e.currentTarget.value;\n localDate = new Date(value);\n\n if (isNaN(localDate.getTime())) {\n err = { Field: 'BookingDate', Message: 'Please enter a valid date' };\n } else if (localDate.getTime() < today.getTime()) {\n err = { Field: 'BookingDate', Message: 'Booking date cannot be in the past' };\n }\n }\n\n if (!isNaN(localDate.getTime())) {\n const d = duration || parseInt(durationDays || '0');\n const startTime = new Date(\n localDate.getFullYear(),\n localDate.getMonth(),\n localDate.getDate(),\n localDate.getHours(),\n localDate.getMinutes()\n );\n const endTime = new Date(\n localDate.getFullYear(),\n localDate.getMonth(),\n localDate.getDate() + d,\n localDate.getHours(),\n localDate.getMinutes()\n );\n\n onChangeBooking((prev) => ({\n ...prev,\n StartTime: startTime.toISOString(),\n EndTime: endTime.toISOString()\n }));\n }\n\n setValidationError(err);\n };\n\n const onUpdateBookingDuration = (e: React.ChangeEvent) => {\n const stringValue = e.currentTarget.value.trim();\n if (!stringValue) {\n setDurationDays('');\n onChangeBooking((prev) => ({ ...prev, EndTime: undefined }));\n setValidationError({ Field: 'BookingDuration', Message: 'Duration cannot be empty' });\n }\n\n const value = parseInt(stringValue);\n if (!isNaN(value) && value > 0) {\n setDurationDays(value.toString());\n onBookingDateUpdate(undefined, value);\n setValidationError(undefined);\n }\n\n if (!isNaN(value) && value <= 0) {\n setDurationDays(stringValue);\n onChangeBooking((prev) => ({ ...prev, EndTime: undefined }));\n setValidationError({\n Field: 'BookingDuration',\n Message: `Duration must be at least ${minimumRentalDurationDays(props.rentalConfig)}`\n });\n }\n };\n\n const localDateFormat = (d: Date) =>\n `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d\n .getDate()\n .toString()\n .padStart(2, '0')}T${d.getHours().toString().padStart(2, '0')}:${d\n .getMinutes()\n .toString()\n .padStart(2, '0')}`;\n\n //================================================================\n // SERIAL NUMBERS LOGIC\n //================================================================\n\n const setSerialNumberError = (index: number) =>\n setSerialNumberErrors((errors) => {\n const newErrors = [...errors];\n newErrors[index] = true;\n return newErrors;\n });\n\n const clearSerialNumberError = (index: number) =>\n setSerialNumberErrors((errors) => {\n const newErrors = [...errors];\n newErrors[index] = undefined;\n return newErrors;\n });\n\n const selectSerialNumber = (index: number, serialNumber: string, assets: RentalAsset[]) => {\n if (!serialNumber) {\n setSerialNumberError(index);\n return;\n }\n\n const selectedAsset = assets.find((ast) => ast.Details.SerialNumber === serialNumber);\n\n if (!selectedAsset) {\n setSerialNumberError(index);\n return;\n }\n\n setSelectedAssets((prev) => {\n const newAssets = [...prev];\n newAssets[index] = selectedAsset;\n return newAssets;\n });\n clearSerialNumberError(index);\n };\n\n const handleSelectSerialNumber = (index: number) => (serialNumber: string) =>\n selectSerialNumber(index, serialNumber, availableAssets);\n\n const handleAddSerialNumber = (index: number) => async (serialNumber: string) => {\n if (loading) {\n return;\n }\n\n setLoading(true);\n\n const assetId = selectedAssets[index]?.ID;\n if (!assetId) {\n throw new Error('Asset not found');\n }\n\n try {\n await addRentalAssetSerialNumber(assetId, serialNumber, props.integrationId);\n const assets = await getMerchantRentalAssets(props.integrationId);\n\n selectSerialNumber(index, serialNumber, assets);\n setAvailableAssets(assets);\n setError(undefined);\n } catch (e) {\n const err = WrappedError.asWrappedError(e);\n setError(err.message);\n } finally {\n setLoading(false);\n }\n };\n\n //================================================================\n // CREATE/UPDATE WAIVER LOGIC\n //================================================================\n\n const getUpdatedBookingAndWaiver = (): [RentalBooking, RentalWaiver] => [\n {\n ...booking,\n Details: {\n ...booking.Details,\n Assets: selectedAssets,\n Accessories: selectedAssets.flatMap((a, index) =>\n (a.Details.AvailableAccessories || []).filter(\n // Filter out accessories that are in exclusion list\n (a) =>\n !excludedAssetAccessories.get(index)?.find((excluded) => excluded.Name === a.Name)\n )\n )\n }\n } as unknown as RentalBooking,\n {\n ...waiver,\n State: WaiverState.pending,\n Details: {\n ...waiver?.Details,\n Assets: selectedAssets.map((a, index) => ({\n Asset: a,\n Accessories: (a.Details.AvailableAccessories || []).filter(\n // Filter out accessories that are in exclusion list\n (a) =>\n !excludedAssetAccessories.get(index)?.find((excluded) => excluded.Name === a.Name)\n )\n }))\n }\n } as unknown as RentalWaiver\n ];\n\n const onCreateOrUpdateWaiver = () => {\n let apiFunc = (booking: RentalBooking, waiver: RentalWaiver) =>\n !props.rentalConfig.UnifiedWaiverFlowEnabled || waiverOptedIn\n ? createRentalWaiver(booking, waiver, false, props.integrationId)\n : createRentalBooking(booking, props.integrationId);\n\n if (props.bookingId) {\n apiFunc = (booking: RentalBooking, waiver: RentalWaiver) =>\n !props.rentalConfig.UnifiedWaiverFlowEnabled || waiverOptedIn\n ? updateRentalWaiver(booking, waiver, false, props.integrationId)\n : updateRentalBooking(booking, props.integrationId);\n }\n\n // Check that selected assets have a serial number and that the\n // serial number matches an available asset.\n let hasSerialNumberError = false;\n for (let i = 0; i < selectedAssets.length; i++) {\n const serialNumber = selectedAssets[i].Details.SerialNumber;\n const asset = availableAssets.find((ast) => ast.Details.SerialNumber === serialNumber);\n\n if (!serialNumber || !asset) {\n setSerialNumberError(i);\n hasSerialNumberError = true;\n }\n }\n\n if (hasSerialNumberError || validationError) {\n return;\n }\n\n setError(undefined);\n setSubmitLoading(true);\n\n const [updatedBooking, updatedWaiver] = getUpdatedBookingAndWaiver();\n apiFunc(updatedBooking as unknown as RentalBooking, updatedWaiver as unknown as RentalWaiver)\n .then((res) => {\n const [, type, id, product] = history.location.pathname.split('/');\n history.replace(`/${type}/${id}/${product}/${res.UpdatedBooking.ID}`);\n if (res.UpdatedWaiver) {\n history.push(`/rental/waiver/${res.UpdatedWaiver.ID}/confirm`);\n } else {\n history.push(`/rental/booking/${res.UpdatedBooking.ID}/confirm`);\n }\n })\n .catch((e) => {\n const err = WrappedError.asWrappedError(e);\n if (err.type() === ErrorType.validationError) {\n setError(undefined);\n setValidationError(err.getValidationError());\n } else {\n setValidationError(undefined);\n setError(err.message);\n }\n })\n .finally(() => setSubmitLoading(false));\n };\n\n if (initialLoading) {\n return (\n \n \n \n );\n }\n\n return (\n e.preventDefault()}>\n \n \n \n \n {validationError?.Field === 'BookingDate' && (\n {validationError.Message}\n )}\n \n \n \n {validationError?.Field === 'BookingDuration' && (\n {validationError.Message}\n )}\n \n \n \n {selectedAssets.map((asset, index) => (\n
\n \n \n ({ value: name }))}\n value={asset.Name}\n onChange={(value) => {\n const selectedAsset = availableAssets.find((ast) => ast.Name === value);\n if (!selectedAsset) {\n throw new Error('Selected asset not found');\n }\n\n setSelectedAssets((prev) => {\n const newAssets = [...prev];\n newAssets[index] = selectedAsset;\n return newAssets;\n });\n }}\n />\n \n \n a.serialNumber).filter((s) => !!s) ||\n []\n }\n initialValue={asset.Details.SerialNumber}\n error={\n serialNumberErrors[index]\n ? 'Please choose a serial number or enter a new one and click \"add\"'\n : undefined\n }\n onChange={handleSelectSerialNumber(index)}\n onSelectOption={handleSelectSerialNumber(index)}\n onAddOption={handleAddSerialNumber(index)}\n />\n \n \n {(asset.Details.AvailableAccessories || [])\n .filter((a) => a.Value > excludableAssetThreshold * asset.Value)\n .map((accessory) => {\n return (\n \n \n a.Name === accessory.Name)\n }\n onChange={() => {\n setExcludedAssetAccessories((prev) => {\n let updatedExclusions: AssetAccessory[] = [];\n if (\n excludedAssetAccessories\n .get(index)\n ?.find((a) => a.Name === accessory.Name)\n ) {\n // If asset is already excluded, remove the exclusion\n updatedExclusions =\n prev.get(index)?.filter((a) => a.Name !== accessory.Name) || [];\n } else {\n // If asset is not excluded, add the exclusion\n updatedExclusions = [...(prev.get(index) || []), accessory];\n }\n\n const updatedMap = new Map(prev);\n updatedMap.set(index, updatedExclusions);\n return updatedMap;\n });\n }}\n />\n \n \n );\n })}\n
\n ))}\n \n \n {selectedAssets.length > 1 && (\n \n )}\n \n \n \n \n {\n const value = e.currentTarget.value;\n onChangeBooking((prev) => ({\n ...prev,\n Details: {\n ...prev?.Details,\n Insured: { ...prev?.Details?.Insured, FirstName: value }\n }\n }));\n }}\n autoComplete=\"given-name\"\n />\n \n \n {\n const value = e.currentTarget.value;\n onChangeBooking((prev) => ({\n ...prev,\n Details: {\n ...prev?.Details,\n Insured: { ...prev?.Details?.Insured, LastName: value }\n }\n }));\n }}\n autoComplete=\"family-name\"\n />\n \n \n \n \n {\n const value = e.currentTarget.value;\n onChangeBooking((prev) => ({\n ...prev,\n Details: {\n ...prev?.Details,\n Insured: { ...prev?.Details?.Insured, Email: value }\n }\n }));\n }}\n inputMode=\"email\"\n autoComplete=\"email\"\n />\n \n \n {\n const value = e.currentTarget.value;\n onChangeBooking((prev) => ({\n ...prev,\n Details: {\n ...prev?.Details,\n Insured: { ...prev?.Details?.Insured, Phone: value }\n }\n }));\n }}\n inputMode=\"tel\"\n autoComplete=\"tel\"\n />\n \n \n {props.rentalConfig.UnifiedWaiverFlowEnabled && (\n \n )}\n {props.rentalConfig.UnifiedWaiverFlowEnabled && (\n [\n a.Value,\n ...(a.Details.AvailableAccessories || []).map((aa) => aa.Value)\n ])\n .reduce((a, c) => a + c, 0),\n Currency: 'usd'\n }}\n waiverPremium={waiverPremium}\n waiverOptedIn={waiverOptedIn}\n setWaiverOptedIn={setWaiverOptedIn}\n />\n )}\n \n \n }\n loading={submitLoading}\n onClick={onCreateOrUpdateWaiver}\n >\n {!props.rentalConfig.UnifiedWaiverFlowEnabled\n ? 'Continue'\n : waiverOptedIn\n ? 'Continue to sign and pay'\n : 'Continue to sign'}\n \n \n \n\n {error && {error}}\n
\n );\n};\n\nconst RentalAgreement = (props: { integrationId: string }) => {\n const ref = React.useRef(null);\n\n const url =\n config().backendBaseUrl.statics +\n `/merchant/rental/waiverdocuments?IntegrationID=${props.integrationId}`;\n const embeddedUrl = `https://drive.google.com/viewerng/viewer?embedded=true&url=${encodeURIComponent(\n url\n )}`;\n\n React.useEffect(() => {\n const interval = setInterval(() => {\n if (ref.current) {\n ref.current.src = embeddedUrl;\n }\n }, 3000);\n\n if (ref.current) {\n ref.current.addEventListener('load', () => {\n clearInterval(interval);\n });\n }\n\n return () => clearInterval(interval);\n }, [ref.current]);\n\n return (\n <>\n \n \n