{"version":3,"file":"js/194-22d642f01a40e963ebe7.js","mappings":"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","sources":["webpack://realm_app/./app/javascript/apis/propertyPlanApi.ts","webpack://realm_app/./app/javascript/layouts/components/Footer/styles.module.scss?c350","webpack://realm_app/./app/javascript/layouts/components/Footer/index.tsx","webpack://realm_app/./app/javascript/non-rendering/Authentication.tsx","webpack://realm_app/./app/javascript/pages/Dashboard/Activity/Alert.tsx","webpack://realm_app/./app/javascript/recoil/baseProjectTemplates.ts","webpack://realm_app/./app/javascript/recoil/projectTemplates.ts","webpack://realm_app/./app/javascript/utils/useIsMounted.ts","webpack://realm_app/./app/javascript/recoil/projects.ts","webpack://realm_app/./app/javascript/apis/propertyApi.ts","webpack://realm_app/./app/javascript/recoil/properties.ts","webpack://realm_app/./app/javascript/recoil/propertyPlans.ts","webpack://realm_app/./app/javascript/recoil/user.ts","webpack://realm_app/./app/javascript/utils/propertyPath.ts","webpack://realm_app/./app/javascript/utils/sentry.ts","webpack://realm_app/./app/javascript/utils/sharedAction.ts"],"sourcesContent":["import { getJSON, postJSON, patchJSON, deleteJSON } from 'utils/fetch'\n\nimport { PropertyPlan as PropertyPlanBase, CPropertyPlan, UPropertyPlan } from 'recoil/propertyPlans'\nimport { Project, CProject, UProject, Update, UpdateResult } from 'recoil/projects'\nimport { ProjectTemplate } from 'recoil/projectTemplates'\n\nexport interface PropertyPlan extends PropertyPlanBase {\n projects: Array\n}\n\nfunction path(...pathParts: Array): string {\n return (\n '/' +\n pathParts\n .map((p) => {\n const ret = p.endsWith('/') ? p.substring(0, p.length - 1) : p\n return ret.startsWith('/') ? ret.substring(1) : ret\n })\n .join('/')\n )\n}\n\nexport default class PropertyPlanAPI {\n private pathPrefix: string\n\n constructor(propertyId: number) {\n this.pathPrefix = `/api/v1/properties/${propertyId}`\n }\n\n /* Property Plan */\n async listPropertyPlans(): Promise> {\n return await getJSON(path(this.pathPrefix, '/property_plans'))\n }\n\n async getPropertyPlan(propertyPlanId: number): Promise {\n return await getJSON(path(this.pathPrefix, `/property_plans/${propertyPlanId}`))\n }\n\n async createPropertyPlan(propertyPlan: CPropertyPlan): Promise {\n const res = await postJSON(path(this.pathPrefix, '/property_plans'), propertyPlan as Record)\n\n if (res.isError) {\n throw new Error(`Non-200 status code: ${res.code}.`)\n }\n\n return res.jsonBody\n }\n\n async updatePropertyPlan(propertyPlanId: number, propertyPlan: UPropertyPlan): Promise {\n const res = await patchJSON(\n path(this.pathPrefix, `/property_plans/${propertyPlanId}`),\n propertyPlan as Record\n )\n\n if (res.isError) {\n throw new Error(`Non-200 status code: ${res.code}.`)\n }\n return res.jsonBody\n }\n\n async deletePropertyPlan(propertyPlanId: number): Promise {\n const res = await deleteJSON(path(this.pathPrefix, `/property_plans/${propertyPlanId}`))\n\n // 404 on a delete is 'ok'\n if (res.isError && res.code != 404) {\n throw new Error(`Non-200 status code: ${res.code}.`)\n }\n return res.jsonBody || null\n }\n\n /* Projects */\n async listProjects(propertyPlanId: number): Promise> {\n return await getJSON(path(this.pathPrefix, `/property_plans/${propertyPlanId}/projects`))\n }\n\n async createProject(propertyPlanId: number, project: CProject): Promise {\n const res = await postJSON(\n path(this.pathPrefix, `/property_plans/${propertyPlanId}/projects`),\n project as unknown as Record\n )\n\n if (res.isError) {\n throw new Error(`Non-200 status code: ${res.code}.`)\n }\n return res.jsonBody\n }\n\n async updateProject(propertyPlanId: number, projectId: number, project: UProject): Promise {\n const res = await patchJSON(\n path(this.pathPrefix, `/property_plans/${propertyPlanId}/projects/${projectId}`),\n project as Record\n )\n\n if (res.isError) {\n throw new Error(`Non-200 status code: ${res.code}.`)\n }\n return res.jsonBody\n }\n\n async deleteProject(propertyPlanId: number, projectId: number): Promise {\n const res = await deleteJSON(path(this.pathPrefix, `/property_plans/${propertyPlanId}/projects/${projectId}`))\n\n // 404 on a delete is 'ok'\n if (res.isError && res.code != 404) {\n throw new Error(`Non-200 status code: ${res.code}.`)\n }\n return res.jsonBody || null\n }\n\n async batchUpdateProjects(propertyPlanId: number, updates: Array): Promise> {\n const res = await patchJSON(path(this.pathPrefix, `/property_plans/${propertyPlanId}/projects`), { batch: updates })\n\n if (res.isError) {\n throw new Error(`Non-200 status code: ${res.code}.`)\n }\n return res.jsonBody\n }\n\n async previewUpdateProject(\n propertyPlanId: number,\n projectId: number,\n project: UProject\n ): Promise<{ project: Project; property_plan: PropertyPlan }> {\n const res = await postJSON(\n path(this.pathPrefix, `/property_plans/${propertyPlanId}/projects/${projectId}/previews`),\n project as Record\n )\n\n if (res.isError) {\n throw new Error(`Non-200 status code: ${res.code}.`)\n }\n return res.jsonBody\n }\n\n /* Project Templates */\n async listProjectTemplates(propertyPlanId?: number): Promise> {\n // Decide if this is for a basic projectTemplates list based off of property\n // or if it is for a specific property plan.\n let subPath = '/project_templates'\n if (propertyPlanId) subPath = `/property_plans/${propertyPlanId}${subPath}`\n return await getJSON(path(this.pathPrefix, subPath))\n }\n}\n","// extracted by mini-css-extract-plugin\nexport default {\"footerContainer\":\"VlZb9XWwXOxLwCefRMD1\",\"footer\":\"ZEaQHdRfTp1Yc8HBbZt5\",\"colophon\":\"ETUjnCdpk3nqOquDZ0qI\",\"logo\":\"DnLblCHP5CGVUDKpFSNh\",\"social\":\"GFtfU0Gga6WbOnIqd5XS\"};","import React, { FC } from 'react'\n\nimport LogoIcon from 'svgs/logo'\nimport InstagramIcon from 'svgs/social/instagram'\nimport FacebookIcon from 'svgs/social/facebook'\nimport TwitterIcon from 'svgs/social/twitter'\nimport LinkedInIcon from 'svgs/social/linkedin'\n\nimport styles from './styles.module.scss'\n\nconst Footer: FC = () => (\n
\n
\n
\n \n

\n {'© '}\n {new Date().getFullYear()}\n {' Realm. All Rights Reserved.'}\n

\n
\n \n
\n
\n)\n\nexport default Footer\n","import React, { FC, PropsWithChildren, useEffect } from 'react'\nimport { useRecoilValue, useSetRecoilState, useRecoilState } from 'recoil'\n\nimport {\n LoginState,\n loginState as rLoginState,\n loginChecked as rLoginChecked,\n loginError as rLoginError,\n userState,\n getUser,\n userState as rUserState,\n User,\n} from 'recoil/user'\nimport { propertiesState, listProperties, propertiesErrorState } from 'recoil/properties'\n\nexport { LoginState } // Reexport here so people don't have to hunt for it.\n\ninterface UseAuth {\n state: LoginState\n checked: boolean\n error: Error | null\n user: User | null\n}\n\nexport function useAuth(): UseAuth {\n const loginState = useRecoilValue(rLoginState)\n const loginChecked = useRecoilValue(rLoginChecked)\n const loginError = useRecoilValue(rLoginError)\n const user = useRecoilValue(rUserState)\n\n return {\n checked: loginChecked,\n error: loginError,\n state: loginState,\n user,\n }\n}\n\nconst Authentication: FC = ({ children }) => {\n const setUser = useSetRecoilState(userState)\n const setProperties = useSetRecoilState(propertiesState)\n const setPropertiesError = useSetRecoilState(propertiesErrorState)\n const setLoginState = useSetRecoilState(rLoginState)\n const [loginChecked, setLoginChecked] = useRecoilState(rLoginChecked)\n const setLoginError = useSetRecoilState(rLoginError)\n\n // Begin the login process.\n useEffect(() => {\n // We only want to run this the first time.\n if (loginChecked) return\n\n const _ = async () => {\n // Start logging in.\n setLoginState(LoginState.LoggingIn)\n try {\n const user = await getUser()\n setUser(user)\n\n // Start downloading some data.\n setLoginState(LoginState.Loading)\n\n try {\n // We want to prefetch some data on login\n\n // Grab our properties\n const properties = await listProperties()\n setProperties(properties)\n } catch (e) {\n // Don't let these errors fall to login exception handler\n // We will log in and not have property plan data\n setPropertiesError(e)\n const win = window as any\n win.Sentry ? win.Sentry.captureException(e) : console.error(e)\n }\n\n // We're in!\n setLoginState(LoginState.LoggedIn)\n } catch (err) {\n // Save the error for possible use later.\n if (err['error']) setLoginError(err['error'])\n setLoginState(LoginState.LoggedOut)\n } finally {\n // At this point we've checked everything\n setLoginChecked(true)\n }\n }\n _()\n }, [loginChecked, setLoginChecked, setLoginError, setLoginState, setProperties, setPropertiesError, setUser])\n\n return <>{children}\n}\n\nexport default Authentication\n","import React, { FC } from 'react'\nimport { Alert as IAlert } from 'recoil/properties'\n\nexport interface AlertProps {\n alert: IAlert\n}\n\nconst Alert: FC = ({ alert }) => {\n return (\n
\n \"\"\n
{alert.message}
\n
\n )\n}\n\nexport default Alert\n","import { useEffect, useCallback, useMemo } from 'react'\nimport { atom, useRecoilState } from 'recoil'\n\nimport sharedAction from 'utils/sharedAction'\n\nimport PropertyPlanApi from 'apis/propertyPlanApi'\n\nimport { Property } from './properties'\n\nimport { ProjectTemplate } from './projectTemplates'\n\nexport const baseProjectTemplatesState = atom | null>({\n key: 'BaseProjectTemplates',\n default: null,\n})\n\nconst baseProjectTemplatesIsLoadingState = atom({\n key: 'BaseProjectTemplates_isLoading',\n default: 0,\n})\n\nexport const useBaseProjectTemplates = (\n property: Property | undefined\n): {\n latestProjectTemplates: Array\n projectTemplates: Array\n isLoading: boolean\n refreshProjectTemplates: () => Promise\n} => {\n const [projectTemplates, setProjectTemplates] = useRecoilState(baseProjectTemplatesState)\n const [isLoading, setIsLoading] = useRecoilState(baseProjectTemplatesIsLoadingState)\n\n useEffect(() => {\n const runme = async () => {\n if (!property) return\n // If we don't have our templates yet...\n if (projectTemplates == null) {\n setIsLoading((p) => p + 1)\n\n // Get our templates from the API\n const newProjectTemplates = await sharedAction(['listProjectTemplates', property.id], () =>\n listProjectTemplates(property.id)\n )\n\n // Store in recoil\n setProjectTemplates(newProjectTemplates)\n\n setIsLoading((p) => p - 1)\n }\n }\n runme()\n }, [setProjectTemplates, projectTemplates, property, setIsLoading])\n\n const refreshProjectTemplates = useCallback(async () => {\n if (!property) return\n setIsLoading((p) => p + 1)\n\n // Get our templates from the API\n const newProjectTemplates = await sharedAction(['listProjectTemplates', property.id], () =>\n listProjectTemplates(property.id)\n )\n\n // Store in recoil\n setProjectTemplates(newProjectTemplates)\n\n setIsLoading((p) => p - 1)\n }, [setProjectTemplates, property, setIsLoading])\n\n const latestProjectTemplates = useMemo(() => {\n return (projectTemplates || []).filter((template) => !template.deprecated)\n }, [projectTemplates])\n\n return {\n latestProjectTemplates,\n projectTemplates: projectTemplates || [],\n isLoading: isLoading > 0,\n refreshProjectTemplates,\n }\n}\n\nexport const useRecommendations = (property: Property | undefined): ProjectTemplate[] => {\n const { projectTemplates } = useBaseProjectTemplates(property)\n\n // Provide recommendations that are based off top projects\n // Top projects is already sorted and curated.\n const recommendations = useMemo(() => {\n return (\n property?.top_projects\n .filter((top) => top.additional_score >= 0)\n .reduce((prev, top) => {\n // We could have failed to get projectTemplates altogether.\n if (!projectTemplates) return prev\n\n const template = projectTemplates.find((pt) => pt.id == top.id)\n // If we failed to get projectTemplates, or there was a change between\n // getting the property and the templates, we may not find the template.\n if (template) {\n // Use score from top_projects, not the template.\n return prev.concat([Object.assign({}, template, { additional_score: top.additional_score }) as never])\n }\n return prev\n }, []) || []\n )\n }, [property, projectTemplates])\n\n return recommendations\n}\n\nexport const listProjectTemplates = async (propertyId: number): Promise => {\n const api = new PropertyPlanApi(propertyId)\n return await api.listProjectTemplates()\n}\n","import { useEffect, useCallback, useMemo } from 'react'\nimport { atom, useRecoilState } from 'recoil'\n//import { getJSON } from 'utils/fetch'\n\nimport sharedAction from 'utils/sharedAction'\n\nimport PropertyPlanApi from 'apis/propertyPlanApi'\n\nimport { useProperties } from './properties'\nimport { usePropertyPlans } from './propertyPlans'\n\nexport enum AvailableErrorType {\n Duplicate = 'duplicate',\n ExceedsSqFootage = 'exceeds_sq_footage',\n ExceedsSqFootageFloor1 = 'exceeds_sq_footage_floor_1',\n ExceedsSqFootageFloor2 = 'exceeds_sq_footage_floor_2',\n NoGarage = 'no_garage',\n NoBasement = 'no_basement',\n NoAttic = 'no_attic',\n ExceedsADUCount = 'exceeds_adu_count',\n ExceedsADUSize = 'exceeds_adu_size',\n}\n\nexport interface ProjectTemplateBase {\n id: number\n kind: string\n name: string\n icon?: string\n icon_svg?: string\n deprecated: boolean\n\n additional_score: number\n additional_home_value: number\n cost_estimate: number\n cost_estimate_low: number\n cost_estimate_high: number\n\n plural_copy: string\n determiner_copy: string\n action_copy: string\n singular_copy: string\n possessive_copy: string\n general_copy: string\n}\n\nexport interface ProjectTemplate extends ProjectTemplateBase {\n description?: string\n\n available: {\n success: boolean\n error?: {\n code: AvailableErrorType\n message?: string\n }\n }\n\n expires_in?: number\n is_related_to_renovation?: boolean\n\n tags: Array\n\n default_option: ProjectCustomizationOption\n position?: number\n}\n\nexport interface ProjectPart {\n id: string\n size_type: SizeType\n size: { min: number; avg: number; max: number }\n scale?: { min: number; avg: number; max: number }\n customizations?: Array\n}\nexport enum SizeType {\n UnitCount = 'unit_count',\n Length = 'length',\n Area = 'area',\n}\n\nexport interface ProjectCostLine extends ProjectPart {\n customizations: never\n}\nexport interface ProjectSpecification extends ProjectPart {\n customizations: Array\n}\n\nexport interface ProjectCustomization {\n id: number\n key: string\n name: string\n description: string\n details?: string\n\n inclusive: boolean\n required: boolean\n invisible: boolean\n show_in_overview: boolean\n\n options: Array\n}\n\nexport interface ProjectCustomizationOption {\n id: number\n name: string\n description?: string\n show_chat_cta: boolean\n show_size: boolean\n show_scale: boolean\n size_name?: string\n size_description?: string\n lock: LockType\n part: ProjectPart\n defaults?: Array\n image?: string\n}\nexport enum LockType {\n Unlocked = 'unlocked',\n DefaultsOnly = 'defaults_only',\n Locked = 'locked',\n}\n\nexport interface ProjectDefaultSelection {\n id: number\n name: string\n description: string\n is_default: boolean\n select_default: boolean\n selection: ProjectSelection\n image?: string\n}\n\nexport interface ProjectSelection {\n id?: number // Optional here, because the client will construct selections\n key: string\n value: string\n size: [number, number]\n scale?: number\n selections?: Array\n cost_estimate?: number // Read only value. Don't use for equality.\n}\n\nexport function cloneProjectSelection(selection: ProjectSelection): ProjectSelection {\n // Leaving the Id in is fine, even if we're clone from a defaultSelection's\n // selection; any selections (even named) on the server side that don't exist\n // on the parent already, will be treated as new selections and given new ids.\n const ret = {\n ...selection,\n size: [selection.size[0], selection.size[1]] as [number, number],\n }\n if (selection.selections) ret.selections = selection.selections.map(cloneProjectSelection)\n return ret\n}\n\nexport function generateProjectSelection(key: string, option: ProjectCustomizationOption): ProjectSelection {\n // If there are some defaults for this option, then go with one of those.\n if (option.defaults && option.defaults.length > 0) {\n const preferred: any = option.defaults.find((d) => d.is_default) || option.defaults[0]\n delete preferred.id // Might as well delete the ID in this explicit case, even if we don't need to.\n return cloneProjectSelection(preferred.selection)\n }\n // If there are no defaults, we need to construct a selection from scratch\n const ret: ProjectSelection = {\n key: key,\n value: option.part.id,\n size: [option.part.size.avg, 1], // For now, always provide the identity for y\n }\n if (option.part.scale) ret.scale = option.part.scale.avg\n if (option.part.customizations && option.part.customizations.length > 0) {\n // If there are customizations for the part, then generate selections for them.\n const subSelections = option.part.customizations\n .map((customization) => {\n if (customization.options && customization.options.length) {\n return generateProjectSelection(customization.key, customization.options[0])\n }\n return null\n })\n .filter((subSelection) => subSelection != null)\n ret.selections = subSelections as any\n }\n return ret\n}\n\nexport const projectTemplatesState = atom | null>({\n key: 'ProjectTemplates',\n default: null,\n})\n\nconst projectTemplatesIsLoadingState = atom({\n key: 'ProjectTemplates_isLoading',\n default: 0,\n})\n\nexport const projectPartsState = atom | null>({\n key: 'ProjectParts',\n default: null,\n})\n\nexport function extractParts(rawTemplates: Array): {\n templates: Array\n parts: Array\n} {\n const walkParts = (prev: { [id: string]: ProjectPart }, part: ProjectPart): { [id: string]: ProjectPart } => {\n if (part.customizations) {\n // Leaves will be added first.\n\n // Find all possible (parts) children\n const children = part.customizations.reduce((cPrev, customization) => {\n return customization.options.reduce((oPrev: any, option) => oPrev.concat([option.part]), cPrev)\n }, [])\n // And walk down them\n prev = children.reduce(walkParts, prev)\n }\n\n // Add this part.\n return Object.assign({}, prev, { [part.id]: part })\n }\n\n // Go through each raw template, extract any specifications and cost lines, replace\n // them with references to the known objects\n const results = rawTemplates.reduce(\n (prev, template) => {\n // If it isn't customizable, nothing to extract.\n if (!template.default_option) return { templates: prev.templates.concat(template), parts: prev.parts }\n // Otherwise, grab our parts.\n const allParts = walkParts({}, template.default_option.part)\n // And send them back.\n return {\n // make sure this is the part our template uses.\n templates: prev.templates.concat([\n Object.assign({}, template, { part: allParts[template.default_option.part.id] }),\n ]),\n parts: allParts,\n }\n },\n { templates: [], parts: {} } as {\n templates: Array\n parts: { [id: string]: ProjectPart }\n }\n )\n return {\n templates: results.templates,\n parts: Object.keys(results.parts).map((key) => results.parts[key]),\n }\n}\n\nexport const useProjectTemplates = (): {\n latestProjectTemplates: Array\n projectTemplates: Array\n projectParts: Array\n isLoading: boolean\n refreshProjectTemplates: () => Promise\n} => {\n const { selectedProperty } = useProperties()\n const { selectedPropertyPlan } = usePropertyPlans()\n const [projectTemplates, setProjectTemplates] = useRecoilState(projectTemplatesState)\n const [projectParts, setProjectPart] = useRecoilState(projectPartsState)\n const [isLoading, setIsLoading] = useRecoilState(projectTemplatesIsLoadingState)\n\n useEffect(() => {\n if (!selectedProperty || !selectedPropertyPlan) return\n const _ = async () => {\n // If we don't have our templates yet...\n if (projectTemplates == null) {\n setIsLoading((p) => p + 1)\n\n // Get our templates from the API\n const newProjectTemplates = await sharedAction(\n ['listProjectTemplates', selectedProperty.id, selectedPropertyPlan.id],\n () => listProjectTemplates(selectedProperty.id, selectedPropertyPlan.id)\n )\n\n const allParts: {\n templates: Array\n parts: Array\n } = extractParts(newProjectTemplates)\n\n // Store in recoil\n setProjectPart(allParts.parts)\n setProjectTemplates(allParts.templates)\n\n setIsLoading((p) => p - 1)\n }\n }\n _()\n }, [setProjectTemplates, setProjectPart, projectTemplates, selectedProperty, selectedPropertyPlan, setIsLoading])\n\n const refreshProjectTemplates = useCallback(async () => {\n if (!selectedProperty || !selectedPropertyPlan) return\n setIsLoading((p) => p + 1)\n\n // Get our templates from the API\n const newProjectTemplates = await sharedAction(\n ['listProjectTemplates', selectedProperty.id, selectedPropertyPlan.id],\n () => listProjectTemplates(selectedProperty.id, selectedPropertyPlan.id)\n )\n\n const allParts: {\n templates: Array\n parts: Array\n } = extractParts(newProjectTemplates)\n\n // Store in recoil\n setProjectPart(allParts.parts)\n setProjectTemplates(allParts.templates)\n\n setIsLoading((p) => p - 1)\n }, [setProjectTemplates, setProjectPart, selectedProperty, selectedPropertyPlan, setIsLoading])\n\n const latestProjectTemplates = useMemo(() => {\n return (projectTemplates || []).filter((template) => !template.deprecated)\n }, [projectTemplates])\n\n return {\n latestProjectTemplates: latestProjectTemplates,\n projectTemplates: projectTemplates || [],\n projectParts: projectParts || [],\n isLoading: isLoading > 0,\n refreshProjectTemplates,\n }\n}\n\nexport const listProjectTemplates = async (propertyId: number, propertyPlanId: number): Promise => {\n const api = new PropertyPlanApi(propertyId)\n return await api.listProjectTemplates(propertyPlanId)\n}\n","import { useRef, useEffect } from 'react'\n\n/**\n * Use this for when we have async gaps in a callback\n * that updates local state and potentially causes the\n * component to unmount (this should be rare).\n *\n * Usage:\n * const isMounted = useIsMounted()\n * // Then in a callback somewhere\n * if(isMounted()) {\n * // do state update\n * }\n *\n */\nconst useIsMounted: () => () => boolean = () => {\n const isMountedRef = useRef(true)\n useEffect(() => {\n return () => {\n isMountedRef.current = false\n }\n }, [isMountedRef])\n return () => isMountedRef.current\n}\n\nexport default useIsMounted\n","import { useState, useEffect, useCallback } from 'react'\nimport { atom, useRecoilState } from 'recoil'\n//import { getJSON } from 'utils/fetch'\n\nimport sharedAction from 'utils/sharedAction'\nimport useIsMounted from 'utils/useIsMounted'\n\nimport PropertyPlanApi from 'apis/propertyPlanApi'\n\nimport { useProperties } from './properties'\n\nimport { usePropertyPlans } from './propertyPlans'\nimport {\n useProjectTemplates,\n ProjectTemplate,\n ProjectSelection,\n AvailableErrorType,\n cloneProjectSelection,\n} from './projectTemplates'\n\nexport interface Project {\n id: number\n // property_plan_id: number // Right now, we don't care what property_plan it belongs to.\n\n project_template_id: number\n\n square_footage?: number\n square_footage_from_lot: number\n increases_sq_ft: boolean\n exempt_from_zoning: boolean\n\n kind: string\n icon?: string\n icon_svg?: string\n name: string\n description?: string\n customized: boolean\n\n tier?: string\n size?: string\n scope?: string\n\n additional_score: number\n additional_home_value: number\n cost_estimate_low: number\n cost_estimate_high: number\n cost_estimate: number\n\n available: {\n success: boolean\n error?: {\n code: AvailableErrorType\n message?: string\n }\n }\n\n selection?: ProjectSelection\n}\n\nexport interface CProject {\n project_template_id: number\n}\n\nexport interface UProject {\n name?: string\n customized?: boolean\n selection?: ProjectSelection\n}\n\nexport interface Update {\n id: number\n method: 'delete'\n}\n\nexport interface UpdateResult {\n status: number\n error?: string\n project?: Project\n}\n\nexport interface TempProject extends Project {\n _temporary: boolean\n}\n\nexport const projectsState = atom | null>({\n key: 'projects',\n default: null,\n})\n\nconst deletingProjectsState = atom>({\n key: 'deletingProjects',\n default: [],\n})\n\nconst tempProjectsState = atom>({\n key: 'tempProjects',\n default: [],\n})\n\nfunction createTempProject(id: number, template?: ProjectTemplate): TempProject | null {\n if (template && template.default_option) {\n return createTempFromCustomizable(id, template)\n }\n return null\n}\n\nfunction createTempFromCustomizable(id: number, template: ProjectTemplate): TempProject {\n // Find the default selection\n const defaultSelection = template?.default_option?.defaults\n ? template.default_option.defaults.find((d) => d.is_default) || template.default_option.defaults[0]\n : { selection: {} }\n\n return Object.assign(\n {\n id: id,\n\n project_template_id: template.id,\n\n square_footage_from_lot: 0,\n increases_sq_ft: false,\n exempt_from_zoning: false,\n\n kind: template.kind,\n icon: template.icon,\n icon_svg: template.icon_svg,\n name: template.name,\n customized: false,\n description: template.description,\n\n additional_score: template.additional_score,\n additional_home_value: 0,\n cost_estimate_low: 0,\n cost_estimate_high: 0,\n cost_estimate: 0,\n\n selection: cloneProjectSelection(defaultSelection.selection as any),\n\n // Assume availability from template\n available: Object.assign({}, template.available),\n },\n { _temporary: true }\n )\n}\n\nexport const useProjects = (): {\n projects: Array\n isLoading: boolean\n addProject: (project: CProject) => Promise\n removeProject: (projectId: number) => Promise\n removeProjects: (projectIds: Array) => Promise | null>\n updateProject: (projectId: number, project: UProject) => Promise\n refreshProjects: () => Promise | null>\n} => {\n const isMounted = useIsMounted()\n const { selectedProperty } = useProperties()\n const { selectedPropertyPlan, refreshPropertyPlan, setIsInvalid: setPropertyPlanAsInvalid } = usePropertyPlans()\n const [projects, setProjects] = useRecoilState(projectsState)\n const [deletingProjects, setDeletingProjects] = useRecoilState(deletingProjectsState)\n const [tempProjects, setTempProjects] = useRecoilState(tempProjectsState)\n const { projectTemplates: templates, refreshProjectTemplates } = useProjectTemplates()\n\n const [isLoading, setIsLoading] = useState(false)\n\n const refreshProjects = useCallback(async () => {\n if (!selectedProperty || !selectedPropertyPlan) return null\n if (isMounted()) setIsLoading(true)\n\n // Get our projects from the API\n const newProjects = await sharedAction(['listProjects', selectedProperty.id, selectedPropertyPlan.id], () =>\n listProjects(selectedProperty.id, selectedPropertyPlan.id)\n )\n\n // Store in recoil\n setProjects(newProjects)\n\n if (isMounted()) setIsLoading(false)\n\n return newProjects\n }, [setProjects, selectedProperty, selectedPropertyPlan, isMounted])\n\n const addProject = useCallback(\n async (project: CProject): Promise => {\n if (!selectedProperty || !selectedPropertyPlan) return null\n // Insert a temporary place holder. We can make a guess based on\n // the templates we already have.\n const template = templates.find((t) => t.id == project.project_template_id)\n // Find out next available temp Id\n const nextTempId = tempProjects.reduce((prev, p) => Math.min(prev, p?.id || 0), 0) - 1\n let tempNewProject: TempProject | null = createTempProject(nextTempId, template)\n\n if (tempNewProject) {\n setTempProjects((projects) => [...projects, tempNewProject])\n }\n\n // Also lets invalidate propertyPlan to indicate that we're loading in a new one\n setPropertyPlanAsInvalid(true)\n try {\n // Call our API\n const newProject = await createProject(selectedProperty.id, selectedPropertyPlan.id, project)\n\n // In order that we do not show a 'duplicate' project temporarilly,\n // Update our temp project so that our temporary id matches our new\n // project's Id.\n const oldTempId = tempNewProject?.id\n tempNewProject = Object.assign({}, tempNewProject, { id: newProject.id })\n setTempProjects((projects) => projects.map((p) => (p?.id == oldTempId ? tempNewProject : p)))\n\n await Promise.all([\n // Now we need to also fetch the updated property plan\n refreshPropertyPlan(),\n // And ALL the projects\n refreshProjects(),\n // And more templates\n refreshProjectTemplates(),\n ])\n // Return the project added.\n return newProject\n } finally {\n // Succeed or fail, we need to remove our temp project.\n setTempProjects((projects) =>\n // Don't include any of our temps\n projects.filter((p) => p?.id != tempNewProject?.id)\n )\n\n // Always return the PP to a valid state when we're done.\n setPropertyPlanAsInvalid(false)\n }\n },\n [\n selectedProperty,\n selectedPropertyPlan,\n refreshProjectTemplates,\n refreshProjects,\n refreshPropertyPlan,\n setPropertyPlanAsInvalid,\n setTempProjects,\n tempProjects,\n templates,\n ]\n )\n\n const _updateProject = useCallback(\n async (projectId: number, projectUpdate: UProject): Promise => {\n if (!selectedProperty || !selectedPropertyPlan) return null\n // Find and save the one we're updating\n const preUpdateProject = projects?.find((p) => p?.id == projectId)\n // If we found the project, update it now locally. If not\n // we'll just add the updated project afterwards.\n if (preUpdateProject) {\n // Find the template\n const template = templates.find((t) => t.id == preUpdateProject.project_template_id)\n let temp: TempProject\n if (template?.default_option) {\n const selection = projectUpdate.selection || preUpdateProject.selection\n const name = projectUpdate.name || preUpdateProject.name\n const customized = projectUpdate.customized || preUpdateProject.customized\n\n temp = Object.assign({}, preUpdateProject, {\n project_template_id: template.id,\n\n selection: selection,\n\n kind: template.kind,\n name: name,\n description: template.description,\n customized: customized,\n\n /* The subsequent scores can't be guess until we get stuff back\n so don't override them. We'll indicate they're calculating in\n the UI eventually\n */\n _temporary: true,\n // additional_score:\n // additional_home_value:\n // cost_estimate_low:\n // cost_estimate_high:\n // cost_estimate:\n })\n }\n // Create a temp copy with a modified copy of it now.\n setTempProjects((projects) => [...projects, temp])\n }\n // Also lets invalidate propertyPlan to indicate that we're loading in a new one\n setPropertyPlanAsInvalid(true)\n try {\n // Call our API\n const updatedProject = await updateProject(\n selectedProperty.id,\n selectedPropertyPlan.id,\n projectId,\n projectUpdate\n )\n\n await Promise.all([\n // if we succeeded in updating the project, then we need to also fetch the\n // updated property plan and replace the temp with our updated project\n refreshPropertyPlan(selectedPropertyPlan.id),\n // And ALL the projects\n refreshProjects(),\n // And more templates\n refreshProjectTemplates(),\n ])\n\n // Return the project object updated.\n return updatedProject\n } finally {\n // Succeed or fail, remove the temp.\n setTempProjects((projects) => projects.filter((p) => p?.id != preUpdateProject?.id))\n\n // Always return the PP to a valid state when we're done.\n setPropertyPlanAsInvalid(false)\n }\n },\n [\n projects,\n setPropertyPlanAsInvalid,\n templates,\n setTempProjects,\n selectedProperty,\n selectedPropertyPlan,\n refreshPropertyPlan,\n refreshProjects,\n refreshProjectTemplates,\n ]\n )\n\n const removeProject = useCallback(\n async (projectId: number): Promise => {\n if (!selectedProperty || !selectedPropertyPlan) return null\n // Lets invalidate propertyPlan to indicate that we're loading in a new one\n setPropertyPlanAsInvalid(true)\n // Add projectId to the list of deleted projects.\n setDeletingProjects((ids) => [...ids, projectId])\n try {\n // Call our API\n const deletedProject = await deleteProject(selectedProperty.id, selectedPropertyPlan.id, projectId)\n\n // These should done concurrently.\n try {\n await Promise.all([\n // if we succeeded in removing the project, then we need to also\n // fetch the updated property plan\n refreshPropertyPlan(selectedPropertyPlan.id),\n // And ALL the projects\n refreshProjects(),\n // And more templates\n refreshProjectTemplates(),\n ])\n } catch (err) {\n // I mean... right now we don't have the best error handling.\n // If we failed to fetch the updated plan, lets at least log this\n console.error('Failed to refresh plan info during \"removeProject\":', err)\n }\n\n // Return the project object deleted.\n return deletedProject\n } finally {\n // Try or fail, we need to drop the id from our delete list.\n setDeletingProjects((ids) => ids.filter((id) => id != projectId))\n // Always return the PP to a valid state when we're done.\n setPropertyPlanAsInvalid(false)\n }\n },\n [\n selectedProperty,\n selectedPropertyPlan,\n refreshProjectTemplates,\n refreshProjects,\n refreshPropertyPlan,\n setDeletingProjects,\n setPropertyPlanAsInvalid,\n ]\n )\n\n const removeProjects = useCallback(\n async (projectIds: Array): Promise | null> => {\n if (!selectedProperty || !selectedPropertyPlan) return null\n // Lets invalidate propertyPlan to indicate that we're loading in a new one\n setPropertyPlanAsInvalid(true)\n\n // Add all project ids to our list of deletes.\n setDeletingProjects((ids) => [...ids, ...projectIds])\n\n try {\n // Call our API\n const deleteResults = await batchUpdateProject(\n selectedProperty.id,\n selectedPropertyPlan.id,\n projectIds.map((id) => ({\n id: id,\n method: 'delete',\n }))\n )\n const isFailure = (r) => r.status != 200 && r.status != 404\n const isSuccess = (r) => !isFailure(r)\n\n // if we succeeded in removing any one project, then we need to also\n // fetch the updated property plan\n if (deleteResults.some(isSuccess)) {\n // These should done concurrently.\n try {\n await Promise.all([\n refreshPropertyPlan(selectedPropertyPlan.id),\n // And ALL the projects\n refreshProjects(),\n // And more templates\n refreshProjectTemplates(),\n ])\n } catch (err) {\n // I mean... right now we don't have the best error handling.\n // If we failed to fetch the updated plan, lets at least log this\n console.error('Failed to refresh plan info during \"removeProjects\":', err)\n }\n }\n\n // Return the project objects deleted (null for those that don't)\n if (deleteResults) return deleteResults.map((r) => (isSuccess(r) ? r.project || null : null))\n // If we failed the entire request, just return all null.\n return projectIds.map(() => null)\n } finally {\n // Try or fail, we need to drop the ids from our delete list.\n setDeletingProjects((ids) => ids.filter((id) => projectIds.indexOf(id) == -1))\n\n // Always return the PP to a valid state when we're done.\n setPropertyPlanAsInvalid(false)\n }\n },\n [\n selectedProperty,\n selectedPropertyPlan,\n refreshProjectTemplates,\n refreshProjects,\n refreshPropertyPlan,\n setDeletingProjects,\n setPropertyPlanAsInvalid,\n ]\n )\n\n useEffect(() => {\n const runme = async () => {\n // If we don't have our projects yet...\n if (projects == null) {\n await refreshProjects()\n }\n }\n runme()\n }, [projects, refreshProjects])\n\n // Construct our list of projects based on the temps and deletes.\n const origProjects = projects || []\n const retProjects = origProjects\n // Replace existing with temps\n .map((project) => tempProjects.find((t) => t?.id == project?.id) || project)\n // Add unfound temps at end.\n .concat(tempProjects.filter((t) => origProjects.findIndex((p) => p?.id == t?.id) == -1))\n // Remove deleted projects\n .filter((project) => deletingProjects.indexOf(project?.id || 0) == -1)\n\n return {\n projects: retProjects as any,\n isLoading,\n addProject,\n removeProject,\n removeProjects,\n updateProject: _updateProject,\n refreshProjects,\n }\n}\n\nexport const listProjects = async (propertyId: number, propertyPlanId: number): Promise> => {\n const api = new PropertyPlanApi(propertyId)\n return await api.listProjects(propertyPlanId)\n}\n\nexport const createProject = async (\n propertyId: number,\n propertyPlanId: number,\n project: CProject\n): Promise => {\n const api = new PropertyPlanApi(propertyId)\n return await api.createProject(propertyPlanId, project)\n}\n\nexport const updateProject = async (\n propertyId: number,\n propertyPlanId: number,\n projectId: number,\n project: UProject\n): Promise => {\n const api = new PropertyPlanApi(propertyId)\n return await api.updateProject(propertyPlanId, projectId, project)\n}\n\nexport const deleteProject = async (\n propertyId: number,\n propertyPlanId: number,\n projectId: number\n): Promise => {\n const api = new PropertyPlanApi(propertyId)\n return await api.deleteProject(propertyPlanId, projectId)\n}\n\nexport const batchUpdateProject = async (\n propertyId: number,\n propertyPlanId: number,\n updates: Array\n): Promise> => {\n const api = new PropertyPlanApi(propertyId)\n return await api.batchUpdateProjects(propertyPlanId, updates)\n}\n","import { getJSON, postJSON, patchJSON, deleteJSON } from 'utils/fetch'\n\nimport { poll } from 'utils/poll'\nimport { PropertyHead, Property, CProperty, UProperty, CreatePropertyErrorType } from 'recoil/properties'\n\nexport function path(...pathParts: Array): string {\n return (\n '/' +\n pathParts\n .map((p) => {\n const ret = p.endsWith('/') ? p.substring(0, p.length - 1) : p\n return ret.startsWith('/') ? ret.substring(1) : ret\n })\n .join('/')\n )\n}\n\nexport interface PropertyPollResponse {\n error?: Record\n result?: { id: number }\n}\n\nconst QualificationStatuses = {\n qualified: 'qualified',\n unserviceable: 'unserviceable',\n}\n\nexport default class PropertyAPI {\n private pathPrefix: string\n\n constructor() {\n this.pathPrefix = `/api/v1`\n }\n\n /* Property */\n async listProperties(): Promise> {\n return await getJSON(path(this.pathPrefix, `/properties`))\n }\n\n async getProperty(propertyId: number): Promise {\n return await getJSON(path(this.pathPrefix, `/properties/${propertyId}`))\n }\n\n async createProperty(property: CProperty): Promise {\n // Creating a property is a multi-step process involving polling.\n\n // First start the property creation.\n const res = await postJSON(path(this.pathPrefix, `/properties`), property as Record)\n\n if (res.isError) {\n const error = new Error(`Non-200 status code: ${res.code}.`)\n throw Object.assign(error, { type: CreatePropertyErrorType.CreateStartError, body: res.jsonBody })\n }\n // Also an error state. We need to be able to poll for our results.\n else if (!res.jsonBody.pollable_job_token) {\n const error = new Error(`Create Property result missing 'pollable_job_token'.`)\n throw Object.assign(error, { type: CreatePropertyErrorType.MissingPollToken, body: res.jsonBody })\n }\n\n // Then begin polling for completion\n const token = res.jsonBody.pollable_job_token\n\n let pollResultResponse: PropertyPollResponse\n try {\n pollResultResponse = await poll({\n fn: async () => await this.getPropertyPoll(token),\n validate: (pollResponse: PropertyPollResponse) => !!(pollResponse.error || pollResponse.result),\n interval: 2000,\n maxAttempts: 10,\n })\n } catch (err) {\n const error = new Error(`Create Property poll timed out: ${err}`)\n throw Object.assign(error, { type: CreatePropertyErrorType.PollTimeout })\n }\n\n if (pollResultResponse.error) {\n const error = new Error(`Create Property poll returned failure: ${pollResultResponse.error}`)\n throw Object.assign(error, { type: CreatePropertyErrorType.PollError })\n }\n\n // Then get the new property.\n const id = pollResultResponse.result?.id\n if (!id) {\n const error = new Error(`Create Property poll returned no id: ${pollResultResponse.result}`)\n throw Object.assign(error, { type: CreatePropertyErrorType.PollError })\n }\n let newProperty\n try {\n newProperty = await this.getProperty(id)\n } catch (err) {\n const error = new Error(`Failed to retrieve new property: ${err}.`)\n throw Object.assign(error, { type: CreatePropertyErrorType.RetrievalFailure })\n }\n\n // Make sure the property is servicable.\n if (newProperty.qualification_status === QualificationStatuses.unserviceable) {\n const error = new Error(`Property is unservicable.`)\n throw Object.assign(error, { type: CreatePropertyErrorType.UnservicableProperty })\n }\n\n return newProperty\n }\n\n async updateProperty(propertyId: number, property: UProperty): Promise {\n const res = await patchJSON(path(this.pathPrefix, `/properties/${propertyId}`), property as any)\n\n if (res.isError) {\n const error = new Error(`Non-200 status code: ${res.code}.`)\n throw Object.assign(error, { body: res.jsonBody })\n }\n return res.jsonBody\n }\n\n async deleteProperty(propertyId: number): Promise {\n const res = await deleteJSON(path(this.pathPrefix, `/properties/${propertyId}`))\n\n // 404 on a delete is 'ok'\n if (res.isError && res.code != 404) {\n const error = new Error(`Non-200 status code: ${res.code}.`)\n throw Object.assign(error, { body: res.jsonBody })\n }\n return res.jsonBody || null\n }\n\n private async getPropertyPoll(token: string): Promise {\n return await getJSON(path(this.pathPrefix, `/pollable_jobs/${token}`))\n }\n}\n","import { useCallback, useEffect } from 'react'\nimport type { LocationDescriptor, Location } from 'history'\nimport { useParams, useRouteMatch, useLocation, generatePath, useHistory } from 'react-router-dom'\nimport { atom, selector, useRecoilValue, useSetRecoilState, useRecoilState } from 'recoil'\nimport { postJSON, postFile } from 'utils/fetch'\nimport { propertyPlansState, listPropertyPlans } from 'recoil/propertyPlans'\nimport { projectsState } from 'recoil/projects'\nimport { projectTemplatesState, projectPartsState, ProjectTemplateBase, ProjectTemplate } from 'recoil/projectTemplates'\nimport {\n baseProjectTemplatesState,\n listProjectTemplates as listBaseProjectTemplates,\n} from 'recoil/baseProjectTemplates'\nimport sharedAction from 'utils/sharedAction'\nimport { propertyFromParam, paramFromProperty, PROPERTY_PATH } from 'utils/propertyPath'\nimport PropertyAPI from 'apis/propertyApi'\nimport { TimeToStart } from 'recoil/onboarding'\n\nexport interface Attachment {\n name: string\n id: number\n download_url: string\n}\n\nexport interface Photo {\n id: number\n name: string\n url: string\n}\n\nexport interface PropertyDocs {\n home_appraisal: Attachment\n home_inspection_report: Attachment\n hoa_ccrs: Attachment\n}\n\nexport interface RentometerPayload {\n address: string\n latitude: string\n longitude: string\n bedrooms: number\n baths: string\n building_type: string\n mean: number\n median: number\n min: number\n max: number\n percentile_25: number\n percentile_75: number\n std_dev: number\n samples: number\n radius_miles: number\n quickview_url: string\n}\n\nexport interface RelativeToNeighbors {\n value_position: number | null\n potential_position: number | null\n}\n\nexport type PropertyFeature =\n | 'stainless_steel_appliances'\n | 'stone_counters'\n | 'custom_cabinets'\n | 'stainless_steel_counters'\n | 'stone_floors'\n | 'tile_floors'\n | 'wood_floors'\n | 'double_vanity'\n | 'soaking_tub'\n | 'jetted_tub'\n | 'walkin_shower'\n | 'steam_shower'\n | 'fire_pit'\n | 'home_gym'\n | 'fireplace'\n | 'deck'\n | 'patio'\n | 'finished_attic'\n | 'finished_basement'\n\nexport enum HomeownerGoal {\n AddSpace = 'add_space',\n UpdateExistingSpaces = 'upgrade_existing_spaces',\n EarnRentalIncome = 'earn_rental_income',\n SetBudget = 'set_budget',\n LearnFinances = 'learn_finances',\n CompareNeighborhood = 'compare_neighborhood',\n\n FindContractors = 'find_contractors',\n FindProjectCosts = 'find_project_costs',\n WorkWithAdvisor = 'work_with_advisor',\n SeeHomePossibilities = 'see_home_possibilities',\n}\n\nexport interface Renovation {\n id: number\n year: number\n kind: string\n}\n\nexport type RealtorStatus =\n | 'this-is-my-listing'\n | 'i-have-a-buyer'\n | 'im-trying-to-win-this-client'\n | 'i-live-here'\n | 'i-sold-this-home'\n\nexport interface PropertyHead {\n id: number\n attom_id: string\n name: string\n lat: string\n long: string\n delivery_line_1: string\n delivery_line_2: string | null\n street_name: string\n county: string\n city: string\n state: string\n zip5: string\n zip9: string\n cbsa_code: string\n bedrooms: number\n total_bathrooms: number\n square_footage: number\n potential_value: number | null\n estimated_value: number\n realtor_status: RealtorStatus\n fips_county_code: number\n _isHead: boolean\n avatar?: Photo\n marketplace: boolean\n}\n\nexport interface TopProject extends ProjectTemplateBase {\n sq_footage: number\n project_template: ProjectTemplate\n}\n\nexport interface Alert {\n id: number\n kind: string\n image_url: string\n message: string\n payload: any\n visible_at: string\n property_id?: number\n}\n\nexport interface Contractor {\n propertyaddressstate: string\n statecountyfips: string\n propertyaddresscity: string\n contractor: string\n count: string\n project_1: string\n count_1: string\n project_2: string\n count_2: string\n project_3: string\n count_3: string\n}\n\nexport interface Property extends PropertyHead {\n bathrooms: number | null\n half_bathrooms: number\n year_built: number\n lot_size: number\n additional_units_allowed: number | null\n max_adu_square_footage: number | null\n garage_car_count: number | null\n monthly_overpayment: number | null\n loan_max: number\n equity: number\n equity_state: 'normal' | 'low_data' | 'missing_data' | 'no_data' | 'no_mortgage'\n monthly_equity_change: number\n best_rate_payment: number\n best_rate: number\n attachments: Attachment[]\n existing_guest_suite_or_adu: boolean\n relative_to_neighbors: RelativeToNeighbors | null\n renovation_year: number\n average_nearby_renovation_year: number\n parcel_geometry_geom: GeoJSON.MultiPolygon | null\n primary_buildable_area_geom: GeoJSON.Polygon | null\n score: number | null\n qualification_status: string | null\n photos: Photo[]\n blended_mortgage: {\n monthly_payment: number | null\n principle: number | null\n rate: number | null\n term: number | null\n }\n top_projects: Array\n zoned_buildable_area: {\n remaining_square_footage: number | null\n allowed_floors: Array\n caveats: Array\n }\n number_of_stories: number\n hoa_name: string\n pool: 0 | 1\n docs: PropertyDocs\n version: number\n basement_square_footage: number | null\n features: PropertyFeature[]\n renovations: Renovation[]\n open_home_goal?: string\n home_goals: HomeownerGoal[]\n edit_state?: {\n dirty: boolean\n original_avm: number\n }\n updated_at: Date\n backyard_gym: boolean\n backyard_home_office: boolean\n deck: boolean\n fencing: boolean\n firepit: boolean\n hvac_central_air: boolean\n hvac_heating: boolean\n landscaping: boolean\n outdoor_kitchen: boolean\n solar: boolean\n water_heater: boolean\n attic: 0 | 1\n finished_attic: 0 | 1\n basement: 0 | 1\n finished_basement: 0 | 1\n activity: Alert[]\n contractors: Contractor[]\n fips_county_code: number\n}\n\nexport interface CProperty {\n place_id?: string\n attom_id?: string\n property_id?: string\n}\n\nexport enum CreatePropertyErrorType {\n CreateStartError,\n MissingPollToken,\n PollTimeout,\n PollError,\n RetrievalFailure,\n UnservicableProperty,\n}\n\nexport interface UProperty {\n name?: string\n bedrooms?: number\n bathrooms?: number\n existing_guest_suite_or_adu?: boolean\n half_bathrooms?: number\n square_footage?: number\n number_of_stories?: number\n garage_car_count?: number\n hoa_name?: string\n lot_size?: number\n pool?: number\n backyard_gym?: boolean\n backyard_home_office?: boolean\n deck?: boolean\n fencing?: boolean\n firepit?: boolean\n hvac_central_air?: boolean\n hvac_heating?: boolean\n landscaping?: boolean\n outdoor_kitchen?: boolean\n solar?: boolean\n water_heater?: boolean\n attic?: number\n finished_attic?: number\n basement?: number\n finished_basement?: number\n features?: PropertyFeature[]\n home_goals?: HomeownerGoal[] | TimeToStart[]\n open_home_goal?: string\n}\n\nexport const propertiesState = atom | null>({\n key: 'Properties',\n default: null,\n})\n\nexport const propertiesErrorState = atom({\n key: 'propertiesError',\n default: null,\n})\n\nconst loadedPropertyIdState = atom({\n key: 'LoadedPropertyId',\n default: null,\n})\n\nconst lastPropertyState = atom({\n key: 'LastPropertyId',\n default: null,\n effects_UNSTABLE: [\n ({ setSelf, onSet }) => {\n const key = 'lastPropertyId'\n const savedValue = localStorage.getItem(key)\n if (savedValue != null) {\n let nValue: number | null = parseInt(savedValue)\n if (isNaN(nValue)) nValue = null\n setSelf(nValue)\n }\n\n onSet((newValue) => {\n if (newValue == null) {\n localStorage.removeItem(key)\n } else {\n localStorage.setItem(key, newValue + '')\n }\n })\n },\n ],\n})\n\nfunction constructRedirect(path: string | null, location: Location, action: string): LocationDescriptor | null {\n if (!path) return null\n let redirect: string | LocationDescriptor = path\n // Preserve querystring\n if (location.search) redirect += location.search\n // And hash\n if (location.hash) redirect += location.hash\n // And state\n if (location.state || action) {\n redirect = {\n pathname: redirect,\n state: Object.assign({ redirectOriginalAction: action }, location.state),\n }\n }\n return redirect\n}\n\nexport const usePropertyLoader = (): {\n isLoading: boolean\n property: Property | PropertyHead | null\n redirect: LocationDescriptor | null\n} => {\n const { propertyId: sPropertyId } = useParams<{ propertyId: string }>()\n const match = useRouteMatch()\n const location = useLocation()\n const history = useHistory()\n const [properties, setProperties] = useRecoilState(propertiesState)\n const [loadedPropertyId, setLoadedPropertyId] = useRecoilState(loadedPropertyIdState)\n const setPropertyPlans = useSetRecoilState(propertyPlansState)\n const setProjects = useSetRecoilState(projectsState)\n const setProjectParts = useSetRecoilState(projectPartsState)\n const setProjectTemplates = useSetRecoilState(projectTemplatesState)\n const setBaseProjectTemplates = useSetRecoilState(baseProjectTemplatesState)\n const setLastPropertyId = useSetRecoilState(lastPropertyState)\n\n // Make sure propertyId Is ok and valid.\n let property = propertyFromParam(properties || [], sPropertyId)\n\n let redirect: string | null = null\n\n // If we didn't find a property, check if they tried to load with a\n // property Id, and update the redirect path accordingly.\n if (!property) {\n const propertyId = parseInt(sPropertyId)\n if (!isNaN(propertyId)) {\n property = (properties || []).find((p) => p.id == propertyId) || null\n if (property) {\n // Use our addr as the id, and suggest the redirect.\n redirect = generatePath(\n match.path,\n Object.assign({}, match.params, { propertyId: paramFromProperty(properties || [], property) })\n )\n }\n }\n }\n // If we have a property, and it is a single property,\n // ensure our path is a single property path.\n if (property && (properties || []).length == 1) {\n if (match.path.startsWith(PROPERTY_PATH)) {\n redirect = generatePath(match.path.replace(PROPERTY_PATH, ''), match.params)\n }\n }\n\n const propertyId = property?.id\n\n // Any time propertyId doesn't match the loadedPropertyId,\n // we need to 'load' the property and its resources\n useEffect(() => {\n let cancel = false\n if (loadedPropertyId != propertyId) {\n console.debug(`loaded property id: [${loadedPropertyId}], property id: [${propertyId}]`)\n ;(async () => {\n if (cancel) return\n\n // Clear out any property specific info\n setPropertyPlans(null)\n setProjects(null)\n setProjectParts(null)\n setProjectTemplates(null)\n setBaseProjectTemplates(null)\n\n // Only load things if we have a propertyId set.\n if (propertyId != null) {\n const results = await Promise.all([\n sharedAction(['getProperty', propertyId], () => getProperty(propertyId)),\n sharedAction(['listPropertyPlans', propertyId], () => listPropertyPlans(propertyId)),\n sharedAction(['listProjectTemplates', propertyId], () => listBaseProjectTemplates(propertyId)),\n ])\n if (cancel) return // Check for cancel after each async call\n\n // Update the target property with the full property.\n setProperties((properties) => (properties || []).map((p) => (p.id == results[0].id ? results[0] : p)))\n setPropertyPlans(results[1])\n setBaseProjectTemplates(results[2])\n\n setLastPropertyId(propertyId)\n }\n // Last update the loadedPropertyId\n setLoadedPropertyId(propertyId || null)\n })()\n }\n\n return () => {\n cancel = true\n }\n }, [\n propertyId,\n loadedPropertyId,\n setProperties,\n setLoadedPropertyId,\n setPropertyPlans,\n setProjects,\n setProjectParts,\n setProjectTemplates,\n setBaseProjectTemplates,\n setLastPropertyId,\n ])\n\n // We probably need to return some sort of loading error as well.\n\n // We then can use usePropertyLoader in 'HasProperty' to 'load wait'\n // while we load the property; and can probably eliminate it from\n // authentication.\n\n // Can we use usePropertyLoader in useProperties?\n\n return {\n isLoading: propertyId != loadedPropertyId,\n property: property,\n redirect: constructRedirect(redirect, location, history.action),\n }\n}\n\nexport const useProperties = (): {\n selectedProperty: Property | null\n lastProperty: Property | PropertyHead | null\n properties: Array | null\n\n createProperty: (property: CProperty) => Promise\n updateSelectedProperty: (property: Partial) => Promise\n refreshProperty: (property: Property) => Promise\n deleteProperty: (propertyId: number) => Promise\n attachDocToSelectedProperty: (type: keyof PropertyDocs | 'photos', file: File) => Promise\n deleteDocFromSelectedProperty: (type: keyof PropertyDocs | 'photos', fileId?: number) => Promise\n attachFilesToSelectedProperty: (files: File[]) => Promise\n deleteFileFromSelectedProperty: (fileId?: number) => Promise\n} => {\n const { property } = usePropertyLoader()\n const properties = useRecoilValue(propertiesState)\n const setProperties = useSetRecoilState(propertiesState)\n const lastPropertyId = useRecoilValue(lastPropertyState)\n\n const setPropertyPlans = useSetRecoilState(propertyPlansState)\n const setProjects = useSetRecoilState(projectsState)\n const setProjectParts = useSetRecoilState(projectPartsState)\n const setProjectTemplates = useSetRecoilState(projectTemplatesState)\n const setBaseProjectTemplates = useSetRecoilState(baseProjectTemplatesState)\n\n const selectedProperty = property && !property._isHead ? (property as Property) : null\n\n const lastProperty = selectedProperty || (properties || []).find((prop) => prop.id == lastPropertyId) || null\n\n const _createProperty = useCallback(\n async (property: CProperty) => {\n const newProperty = await createProperty(property)\n setProperties((properties) => {\n // It is possible that 'create' returns to us a property\n // that already exists in our list. If so, we want to *replace*\n // that existing property rather than adding this new one in.\n properties = (properties || []).map((old_prop) => (old_prop.id == newProperty.id ? newProperty : old_prop))\n if (!properties.find((old_prop) => old_prop.id == newProperty.id)) {\n properties = properties.concat([newProperty])\n }\n return properties\n })\n return newProperty\n },\n [setProperties]\n )\n\n const updateSelectedProperty = useCallback(\n async (property: UProperty) => {\n if (!selectedProperty?.id) return null\n\n const newProperty = await updateProperty(selectedProperty.id, property)\n setProperties((properties) => (properties || []).map((p) => (p.id == newProperty.id ? newProperty : p)))\n\n // Updating a property can impact projects and values; these need to be refreshed.\n // First clear out any property specific info impacted\n setPropertyPlans(null)\n setProjects(null)\n setProjectParts(null)\n setProjectTemplates(null)\n setBaseProjectTemplates(null)\n\n // Reload them\n const results = await Promise.all([\n sharedAction(['listPropertyPlans', selectedProperty.id], () => listPropertyPlans(selectedProperty.id)),\n sharedAction(['listProjectTemplates', selectedProperty.id], () =>\n listBaseProjectTemplates(selectedProperty.id)\n ),\n ])\n\n setPropertyPlans(results[0])\n setBaseProjectTemplates(results[1])\n\n return newProperty\n },\n [\n selectedProperty,\n setBaseProjectTemplates,\n setProjectParts,\n setProjectTemplates,\n setProjects,\n setProperties,\n setPropertyPlans,\n ]\n )\n\n const refreshProperty = useCallback(\n async (property: Property) => {\n const refreshedProperty = await getProperty(property.id)\n setProperties((properties) =>\n (properties || []).map((p) => (p.id == refreshedProperty.id ? refreshedProperty : p))\n )\n return refreshedProperty\n },\n [setProperties]\n )\n\n const _deleteProperty = useCallback(\n async (propertyId: number) => {\n // Keep track of the property we're removing, and where\n const oldPropertyIndex = (properties || []).findIndex((p) => p.id != propertyId)\n const oldProperty = (properties || [])[oldPropertyIndex]\n // Remove property immediately from our list.\n setProperties((properties) => (properties || []).filter((p) => p.id != propertyId))\n // Then remove it for realsies.\n let deletedProperty: null | Property | PropertyHead = null\n try {\n deletedProperty = await deleteProperty(propertyId)\n } catch (e) {\n // if we failed, add it back in.\n setProperties((properties) => (properties || []).splice(oldPropertyIndex, 0, oldProperty))\n throw e\n }\n\n return deletedProperty\n },\n [setProperties, properties]\n )\n\n const attachDocToSelectedProperty = useCallback(\n async (type: keyof PropertyDocs | 'photos', file: File) => {\n if (!selectedProperty?.id) return null\n\n const newProperty = await attachDoc(selectedProperty.id, type, file)\n setProperties((properties) => (properties || []).map((p) => (p.id == newProperty.id ? newProperty : p)))\n return newProperty\n },\n [selectedProperty, setProperties]\n )\n\n const attachFilesToSelectedProperty = useCallback(\n async (files: File[]) => {\n if (!selectedProperty?.id) return null\n\n const newProperty = await attachFiles(selectedProperty.id, files)\n setProperties((properties) => (properties || []).map((p) => (p.id == newProperty.id ? newProperty : p)))\n return newProperty\n },\n [selectedProperty, setProperties]\n )\n\n const deleteFileFromSelectedProperty = useCallback(\n async (fileId?: number) => {\n if (!selectedProperty?.id) return null\n\n const newProperty = await deleteFile(selectedProperty.id, fileId)\n setProperties((properties) => (properties || []).map((p) => (p.id == newProperty.id ? newProperty : p)))\n return newProperty\n },\n [selectedProperty, setProperties]\n )\n\n const deleteDocFromSelectedProperty = useCallback(\n async (type: keyof PropertyDocs | 'photos', fileId?: number) => {\n if (!selectedProperty?.id) return null\n\n const newProperty = await deleteDoc(selectedProperty.id, type, fileId)\n setProperties((properties) => (properties || []).map((p) => (p.id == newProperty.id ? newProperty : p)))\n return newProperty\n },\n [selectedProperty, setProperties]\n )\n\n return {\n selectedProperty,\n lastProperty,\n properties,\n createProperty: _createProperty,\n updateSelectedProperty,\n refreshProperty,\n deleteProperty: _deleteProperty,\n attachDocToSelectedProperty,\n deleteDocFromSelectedProperty,\n attachFilesToSelectedProperty,\n deleteFileFromSelectedProperty,\n }\n}\n\nexport const hasNoPropertiesState = selector({\n key: 'HasNoProperties',\n get: ({ get }) => {\n const properties = get(propertiesState)\n return (properties || []).length === 0\n },\n})\n\nexport const listProperties = async (): Promise> => {\n const api = new PropertyAPI()\n const properties = await api.listProperties()\n properties.forEach((prop) => (prop._isHead = true))\n return properties\n}\n\nexport const getProperty = async (id: number): Promise => {\n const api = new PropertyAPI()\n const property = await api.getProperty(id)\n property._isHead = false\n return property\n}\n\nexport const updateProperty = async (propertyId: number, property: UProperty): Promise => {\n const api = new PropertyAPI()\n const newProperty = await api.updateProperty(propertyId, property)\n newProperty._isHead = false\n return newProperty\n}\n\nconst createProperty = async (property: CProperty): Promise => {\n const api = new PropertyAPI()\n const newProperty = await api.createProperty(property)\n newProperty._isHead = false\n return newProperty\n}\n\nconst deleteProperty = async (propertyId: number): Promise => {\n const api = new PropertyAPI()\n const oldProperty = await api.deleteProperty(propertyId)\n if (oldProperty) oldProperty._isHead = false\n return oldProperty\n}\n\nconst attachFiles = async (propertyId: number, files: File[]): Promise => {\n let res\n for (const file of files) {\n const formData = new FormData()\n formData.append('file', file)\n const lastRes = await postFile(`/api/v1/properties/${propertyId}/attach_file`, formData)\n if (lastRes.isError) {\n const error = new Error(`Non-200 status code: ${lastRes.code}.`)\n throw Object.assign(error, { body: lastRes.jsonBody })\n }\n res = lastRes\n }\n return res.jsonBody\n}\n\nconst attachDoc = async (propertyId: number, type: keyof PropertyDocs | 'photos', file: File): Promise => {\n const formData = new FormData()\n formData.append('file', file)\n formData.append('type', type)\n const res = await postFile(`/api/v1/properties/${propertyId}/attach_file`, formData)\n\n if (res.isError) {\n const error = new Error(`Non-200 status code: ${res.code}.`)\n throw Object.assign(error, { body: res.jsonBody })\n }\n return res.jsonBody\n}\n\nconst deleteFile = async (propertyId: number, fileId?: number): Promise => {\n const body = fileId ? { file_id: fileId } : {}\n const res = await postJSON(`/api/v1/properties/${propertyId}/delete_file`, body)\n\n if (res.isError) {\n const error = new Error(`Non-200 status code: ${res.code}.`)\n throw Object.assign(error, { body: res.jsonBody })\n }\n return res.jsonBody\n}\n\nconst deleteDoc = async (\n propertyId: number,\n type: keyof PropertyDocs | 'photos',\n fileId?: number\n): Promise => {\n const body = fileId ? { file_id: fileId, type: type } : { type: type }\n const res = await postJSON(`/api/v1/properties/${propertyId}/delete_file`, body)\n\n if (res.isError) {\n const error = new Error(`Non-200 status code: ${res.code}.`)\n throw Object.assign(error, { body: res.jsonBody })\n }\n return res.jsonBody\n}\n","import { useEffect, useCallback } from 'react'\nimport { atom, useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'\nimport type { LocationDescriptor, Location } from 'history'\nimport { useParams, useRouteMatch, useLocation, generatePath, useHistory } from 'react-router-dom'\n\nimport sharedAction from 'utils/sharedAction'\nimport propertyPath from 'utils/propertyPath'\n\nimport PropertyPlanApi from 'apis/propertyPlanApi'\n\nimport { useProperties } from './properties'\nimport { listProjects, projectsState } from './projects'\nimport {\n listProjectTemplates,\n projectTemplatesState,\n projectPartsState,\n extractParts,\n ProjectTemplate,\n ProjectPart,\n} from './projectTemplates'\n\nexport interface PropertyPlan {\n id: number\n property_id: number\n name: string\n score: number\n estimated_value: number\n square_footage: number\n cost_estimate: number\n cost_estimate_low: number\n cost_estimate_high: number\n}\n\nexport interface CPropertyPlan {\n name?: string\n}\n\nexport interface UPropertyPlan {\n name?: string\n}\n\nexport const propertyPlansState = atom | null>({\n key: 'PropertyPlans',\n default: null,\n})\n\nconst loadedPropertyPlanIdState = atom({\n key: 'LoadedPropertyPlanId',\n default: null,\n})\n\nconst lastSelectedPropertyPlanIdState = atom({\n key: 'LastSelectedPropertyPlanIdState',\n default: null,\n})\n\n// Similar to a 'loading' state, but allows an outside actor\n// to keep the usePropertyPlan in a 'loading' state\n// until they're ready to release it.\n// This state is used by usePropertyPlans implementors\n// and not by usePropertyPlanLoader implementors.\nconst propertyPlanIsInvalidState = atom({\n key: 'PropertyPlan_isInvalid',\n default: 0,\n})\n\nfunction constructRedirect(path: string | null, location: Location, action: string): LocationDescriptor | null {\n if (!path) return null\n let redirect: LocationDescriptor = {\n pathname: path,\n search: location.search,\n hash: location.hash,\n }\n if (location.state || action) {\n redirect = { ...redirect, state: Object.assign({ redirectOriginalAction: action }, location.state) }\n }\n return redirect\n}\n\n// Separate out accessing property plan and plans\n// from the loader, so the loader doesn't need to be accessed in\n// all components (which can cause performance issues)\nconst useGetPropertyPlans = (): {\n propertyPlans: Array | null\n propertyPlan: PropertyPlan | null\n selectedPropertyPlanId: string\n isLoading: boolean\n} => {\n const params = useParams<{ propertyPlanId: string }>()\n const propertyPlans = useRecoilValue(propertyPlansState)\n const lastSelectedPropertyPlanId = useRecoilValue(lastSelectedPropertyPlanIdState)\n const loadedPropertyPlanId = useRecoilValue(loadedPropertyPlanIdState)\n const selectedPropertyPlanId = params.propertyPlanId\n // Make sure propertyPlanId Is ok and valid.\n const propertyPlanId = selectedPropertyPlanId ? parseInt(selectedPropertyPlanId) : lastSelectedPropertyPlanId\n let propertyPlan = !isNaN(propertyPlanId || 0) ? propertyPlans?.find((p) => p?.id == propertyPlanId) : null\n // No current property plan? Then pick the first one in the list.\n // We should always have at least one.\n if (!propertyPlan) {\n propertyPlan = propertyPlans && propertyPlans[0]\n }\n\n return {\n propertyPlans,\n propertyPlan,\n selectedPropertyPlanId,\n isLoading: loadedPropertyPlanId != propertyPlan?.id,\n }\n}\n\nexport const usePropertyPlanLoader = (): {\n isLoading: boolean\n propertyPlan: PropertyPlan | null\n redirect: LocationDescriptor | null\n} => {\n const match = useRouteMatch()\n const location = useLocation()\n const history = useHistory()\n const { propertyPlan, selectedPropertyPlanId, isLoading } = useGetPropertyPlans()\n const { selectedProperty: property, properties } = useProperties()\n const [loadedPropertyPlanId, setLoadedPropertyPlanId] = useRecoilState(loadedPropertyPlanIdState)\n const setLastSelectedPropertyPlanId = useSetRecoilState(lastSelectedPropertyPlanIdState)\n const setProjects = useSetRecoilState(projectsState)\n const setProjectParts = useSetRecoilState(projectPartsState)\n const setProjectTemplates = useSetRecoilState(projectTemplatesState)\n\n const propertyPlanId = propertyPlan?.id\n\n // We will only need to redirect if our propertyPlanId isn't what\n // we said it should be.\n let redirect: string | null = null\n if (property && propertyPlanId && selectedPropertyPlanId != `${propertyPlanId}`) {\n redirect = generatePath(\n `${propertyPath(properties || [], property)}/property-plans/:propertyPlanId`,\n Object.assign({}, match.params, { propertyPlanId: propertyPlanId })\n )\n }\n\n // Any time propertyPlanId doesn't match the loadedPropertyPlanId,\n // we need to 'load' the propertyPlan and its resources.\n // Make sure we wait until after we've redirected though...\n useEffect(() => {\n if (!property) return\n let cancel = false\n if (loadedPropertyPlanId != propertyPlanId && !redirect) {\n console.debug(`loaded propertyPlan id: [${loadedPropertyPlanId}], propertyPlan id: [${propertyPlanId}]`)\n ;(async () => {\n if (cancel) return\n\n // Clear out any propertyPlan specific info\n setProjects(null)\n setProjectParts(null)\n setProjectTemplates(null)\n\n // Only load things if we have a propertyId set.\n if (propertyPlanId != null) {\n const results = await Promise.all([\n sharedAction(['listProjects', property.id, propertyPlanId], () =>\n listProjects(property.id, propertyPlanId)\n ),\n sharedAction(['listProjectTemplates', property.id, propertyPlanId], () =>\n listProjectTemplates(property.id, propertyPlanId)\n ),\n ])\n if (cancel) return // Check for cancel after each async call\n\n // I'm not suuuper happy about this logic not being isolated.\n const allParts: {\n templates: Array\n parts: Array\n } = extractParts(results[1])\n\n setProjects(results[0])\n setProjectParts(allParts.parts)\n setProjectTemplates(allParts.templates)\n\n // Always update our last selected when we change.\n setLastSelectedPropertyPlanId(propertyPlanId)\n // Last update the loadedPropertyPlanId\n setLoadedPropertyPlanId(propertyPlanId)\n }\n })()\n }\n\n return () => {\n cancel = true\n }\n }, [\n property,\n loadedPropertyPlanId,\n propertyPlanId,\n setLoadedPropertyPlanId,\n setProjects,\n setProjectParts,\n setProjectTemplates,\n setLastSelectedPropertyPlanId,\n redirect,\n ])\n\n return {\n isLoading,\n propertyPlan,\n redirect: constructRedirect(redirect, location, history.action),\n }\n}\n\nexport const usePropertyPlans = (): {\n propertyPlans: Array | null\n selectedPropertyPlan: PropertyPlan | null\n isLoading: boolean\n isSwitching: boolean\n setIsInvalid: (isInvalid: boolean) => void\n addPropertyPlan: (propertyPlan: CPropertyPlan) => Promise\n removePropertyPlan: (propertyPlanId: number) => Promise\n updatePropertyPlan: (propertyPlanId: number, propertyPlan: UPropertyPlan) => Promise\n refreshPropertyPlan: (propertyPlanId?: number) => Promise\n} => {\n const { selectedProperty } = useProperties()\n const { propertyPlan, isLoading } = useGetPropertyPlans()\n const [propertyPlans, setPropertyPlans] = useRecoilState(propertyPlansState)\n const [isInvalid, setIsInvalid] = useRecoilState(propertyPlanIsInvalidState)\n\n const selectedPropertyPlan = propertyPlan\n\n const refreshPropertyPlan = useCallback(\n async (propertyPlanId?: number) => {\n if (!selectedPropertyPlan || !selectedProperty) return\n if (!propertyPlanId) propertyPlanId = selectedPropertyPlan.id\n const refreshedPropertyPlan = await sharedAction(['getPropertyPlan', selectedProperty.id, propertyPlanId], () =>\n getPropertyPlan(selectedProperty.id, propertyPlanId || 0)\n )\n setPropertyPlans(\n (plans) => plans?.map((p) => (p?.id == refreshedPropertyPlan.id ? refreshedPropertyPlan : p)) || null\n )\n },\n [selectedProperty, selectedPropertyPlan, setPropertyPlans]\n )\n\n const addPropertyPlan = useCallback(\n async (newPropertyPlan: CPropertyPlan): Promise => {\n if (!selectedProperty) return null\n // Call our API\n const propertyPlan = await createPropertyPlan(selectedProperty.id, newPropertyPlan)\n\n // Adding a new property plan doesn't need to refresh anything specific,\n // like changing a property plan would.\n\n // We do need to add to our collection however.\n // It is possible we have had a property plan returned that is an existing one.\n // If so, do a replacement. Otherwise, do an add.\n setPropertyPlans((plans) => {\n const newPlans = plans?.concat([]) || []\n const index = newPlans.findIndex((plan) => plan?.id == propertyPlan.id)\n if (index > -1) newPlans[index] = propertyPlan\n else newPlans.push(propertyPlan)\n return newPlans\n })\n\n // Return the property plan added.\n return propertyPlan\n },\n [selectedProperty, setPropertyPlans]\n )\n\n const removePropertyPlan = useCallback(\n async (propertyPlanId: number): Promise => {\n if (!selectedProperty) return null\n // Call our API\n const propertyPlan = await deletePropertyPlan(selectedProperty.id, propertyPlanId)\n\n // Removing an old property plan doesn't need to refresh anything specific,\n // like changing a property plan would.\n\n // We do need to remove from our collection however.\n setPropertyPlans((plans) => plans?.filter((plan) => plan?.id != propertyPlan?.id) || null)\n\n // Return the property plan deleted.\n return propertyPlan\n },\n [selectedProperty, setPropertyPlans]\n )\n\n const _updatePropertyPlan = useCallback(\n async (propertyPlanId: number, uPropertyPlan: UPropertyPlan): Promise => {\n if (!selectedProperty) return null\n // Call our API\n const propertyPlan = await updatePropertyPlan(selectedProperty.id, propertyPlanId, uPropertyPlan)\n\n // Updating a property plan doesn't need to refresh anything specific,\n // like changing the projects in a property plan would.\n\n // We should refresh the copy in our collection however.\n setPropertyPlans((plans) => plans?.map((plan) => (plan?.id == propertyPlan.id ? propertyPlan : plan)) || null)\n\n // Return the property plan added.\n return propertyPlan\n },\n [selectedProperty, setPropertyPlans]\n )\n\n const _setIsInvalid = useCallback(\n (isInvalid: boolean) => {\n setIsInvalid((i) => i + (isInvalid ? 1 : -1))\n },\n [setIsInvalid]\n )\n\n return {\n isLoading: isLoading || isInvalid > 0,\n isSwitching: isLoading,\n propertyPlans,\n selectedPropertyPlan,\n addPropertyPlan,\n removePropertyPlan,\n updatePropertyPlan: _updatePropertyPlan,\n refreshPropertyPlan,\n setIsInvalid: _setIsInvalid,\n }\n}\n\nexport const getPropertyPlan = async (propertyId: number, propertyPlanId: number): Promise => {\n const api = new PropertyPlanApi(propertyId)\n return await api.getPropertyPlan(propertyPlanId)\n}\n\nexport const listPropertyPlans = async (propertyId: number): Promise> => {\n const api = new PropertyPlanApi(propertyId)\n return await api.listPropertyPlans()\n}\n\nexport const createPropertyPlan = async (propertyId: number, propertyPlan: CPropertyPlan): Promise => {\n const api = new PropertyPlanApi(propertyId)\n return await api.createPropertyPlan(propertyPlan)\n}\n\nexport const updatePropertyPlan = async (\n propertyId: number,\n propertyPlanId: number,\n propertyPlan: UPropertyPlan\n): Promise => {\n const api = new PropertyPlanApi(propertyId)\n return await api.updatePropertyPlan(propertyPlanId, propertyPlan)\n}\n\nexport const deletePropertyPlan = async (propertyId: number, propertyPlanId: number): Promise => {\n const api = new PropertyPlanApi(propertyId)\n return await api.deletePropertyPlan(propertyPlanId)\n}\n","import { useCallback } from 'react'\nimport { atom, useRecoilState } from 'recoil'\nimport { identify, track } from 'utils/analytics'\nimport { FetchResponse, getJSON, patchJSON } from 'utils/fetch'\nimport { postJSON } from 'utils/fetch'\n\nimport { BudgetOption, OnboardingState, OutsideBidSelection, UserGoal } from 'recoil/onboarding'\n\nexport interface UserStub {\n id: number\n email: string\n full_name: string\n homeowner_type: 'resident' | 'investment' | 'buy' | 'curious'\n phone_number: string\n entered_address: string | null\n completed_profile: boolean\n sha1_email: string\n referred_by_user: number | null\n created_at: string\n}\n\nexport interface User extends UserStub {\n entered_zip5: string | null\n primary_income: number | null\n secondary_income: number | null\n misc_income: number | null\n mortgage_payment: number | null\n car_payment: number | null\n child_support: number | null\n alimony: number | null\n misc_reoccurring_debts: number | null\n has_password: boolean\n credit_score: number | null\n credit_score_high: number | null\n credit_score_low: number | null\n credit_selection: number | null\n payment_due: boolean\n profile_image: string\n unconfirmed_email: string | null\n social_facebook: string | null\n social_instagram: string | null\n social_linkedin: string | null\n social_twitter: string | null\n project_customized: boolean\n outside_bids: {\n name: string\n url: string\n id: number\n size: string\n }[]\n}\n\nexport interface UUser {\n full_name?: string\n email?: string\n unconfirmed_email?: string\n phone_number?: string\n\n current_password?: string\n password?: string\n password_confirmation?: string\n\n homeowner_type?: 'resident' | 'investment' | 'buy' | 'curious'\n entered_address?: string\n hear_about_us?: string\n referred_by_user?: number\n signup_message?: string\n goals?: Array\n outside_bids_status?: OutsideBidSelection\n\n receives_partner_communications?: boolean\n completed_profile?: boolean\n project_customized?: boolean\n\n social_facebook?: string\n social_instagram?: string\n social_linkedin?: string\n social_twitter?: string\n\n primary_income?: number\n secondary_income?: number\n misc_income?: number\n mortgage_payment?: number\n car_payment?: number\n child_support?: number\n alimony?: number\n misc_reoccurring_debts?: number\n additional_cash_to_invest?: number\n credit_score?: number\n credit_selection?: number\n}\n\nexport const userState = atom({\n key: 'User',\n default: null,\n effects_UNSTABLE: [\n ({ onSet }) => {\n onSet(async (user) => {\n if (!user) return\n identify(user)\n })\n },\n ],\n})\n\nexport enum LoginState {\n LoggedOut,\n LoggingIn,\n Loading,\n LoggedIn,\n}\n\nexport const loginState = atom({\n key: 'LoginState',\n default: LoginState.LoggedOut,\n})\n\nexport const loginChecked = atom({\n key: 'LoginChecked',\n default: false,\n})\n\nexport const loginError = atom({\n key: 'LoginError',\n default: null,\n})\n\nexport const getUser = (): Promise => {\n return getJSON('/api/v1/users/me', { cache: 'no-store' })\n}\n\nexport const markAccessActivityFeed = (property_id?: string | number): Promise => {\n return patchJSON('/api/v1/users/mark_access_activity_feed', { property_id })\n}\n\nexport type NonAppSignupType = 'homeshow' | 'non-homeshow-event' | 'deposit' | 'unserviceable' | 'other'\n\nexport interface SignUpState {\n full_name: string\n ambassador_name: string\n email: string\n password?: string\n entered_address: string\n\n onboarding: OnboardingState\n\n propertyId?: number\n attomId?: string\n placeId?: string\n\n phoneNumber?: string\n non_app_signup_type?: NonAppSignupType\n bypass_signin?: boolean\n\n utmParams?: Record\n}\n\ninterface CUser {\n full_name: string\n ambassador_name?: string\n email: string\n password?: string\n entered_address: string\n\n phone_number?: string\n goals?: UserGoal[]\n homeowner_type?: 'resident' | 'investment' | 'buy' | 'curious'\n referred_by_user?: number\n signup_message?: string\n non_app_signup_type?: NonAppSignupType\n outside_bids_status?: OutsideBidSelection\n budget?: BudgetOption\n\n completed_profile: boolean\n utm_params?: Record\n sfdc_lead_attrs?: Record\n}\n\nexport interface SignUpResponse {\n user: UserStub\n property_id?: number\n pollable_job_token?: string\n}\n\nexport async function signUp(state: SignUpState, campaignId?: string): Promise {\n if (state.password && state.password.length < 8) {\n throw { password: ['Password must be at least 8 characters.'] }\n }\n\n const userBody: CUser = {\n full_name: state.full_name,\n ambassador_name: state.ambassador_name,\n email: state.email,\n password: state.password,\n entered_address: state.entered_address,\n non_app_signup_type: state.non_app_signup_type,\n outside_bids_status: state.onboarding.outsideBids,\n budget: state.onboarding.budget,\n completed_profile: true,\n utm_params: state.utmParams,\n }\n\n if (state.phoneNumber) {\n userBody.phone_number = state.phoneNumber\n }\n\n if (state.propertyId) {\n userBody.entered_address = `property_id:${state.propertyId}`\n } else if (state.attomId) {\n userBody.entered_address = `attom_id:${state.attomId}`\n } else if (state.placeId) {\n userBody.entered_address = `place_id:${state.placeId}`\n }\n\n if (!userBody['entered_address']) {\n throw { entered_address: ['Must pick a result'] }\n }\n\n if (state.onboarding.goals) {\n userBody.goals = state.onboarding.goals\n }\n\n if (state.onboarding.homeownerType) {\n userBody.homeowner_type = state.onboarding.homeownerType\n }\n\n if (state.onboarding.propertyShare) {\n userBody.referred_by_user = state.onboarding.propertyShare.user_id\n }\n\n if (state.onboarding.additionalInfo) {\n userBody.signup_message = state.onboarding.additionalInfo\n }\n\n const sfdcLeadAttrs = {}\n\n if (state.onboarding.projectType == 'wildfire') sfdcLeadAttrs['Wildfire_Rebuild__c'] = true\n else if (state.onboarding.projectType == 'new-build') sfdcLeadAttrs['Project_Type__c'] = ['New Build']\n\n userBody.sfdc_lead_attrs = sfdcLeadAttrs\n\n const response = await postJSON('/users', {\n user: userBody,\n property_id: state.propertyId,\n campaign_id: campaignId,\n bypass_signin: state.bypass_signin,\n })\n if (response.isError) {\n await track('signup error', {\n error: 'error creating user',\n data: response.jsonBody,\n })\n throw response.jsonBody\n }\n\n const signupResponse: SignUpResponse = response.jsonBody\n\n // call identify before tracking the signup event so that we have Realm's User Id already in place.\n await identify(signupResponse.user)\n\n await track(\n 'signup',\n {\n $email: state.email,\n $name: state.full_name,\n address: state.entered_address,\n via: 'form',\n signupDate: signupResponse.user.created_at,\n is_marketplace: state.onboarding.marketplace,\n },\n {\n id: 24230,\n args: {\n orderId: `signup-${signupResponse.user.id}`,\n customerId: signupResponse.user.id,\n customerEmail: signupResponse.user.sha1_email,\n },\n }\n )\n\n return signupResponse\n}\n\nfunction dropOnewayProperties(user: UUser): any {\n const temp = { ...user }\n delete temp.password\n delete temp.password_confirmation\n delete temp.current_password\n return temp\n}\n\nexport const useUser = (): {\n user: User\n updateUser: (user: UUser) => Promise<{ user: User; pollableJobToken?: string }>\n} => {\n const [user, setUser] = useRecoilState(userState)\n\n return {\n user: user as User,\n updateUser: useCallback(\n async (userUpdate: UUser, campaignId = null) => {\n const oldUser = { ...user }\n // Update the user object immediately with a temp\n setUser((prev) => ({ ...prev, ...dropOnewayProperties(userUpdate) }))\n\n let newUser: User, pollableJobToken: string | undefined\n try {\n const results = await updateUser(userUpdate, campaignId)\n newUser = results[0]\n pollableJobToken = results[1]\n setUser(newUser)\n } catch (err) {\n // If we failed to update the user, revert the temp changes.\n setUser(oldUser as User)\n throw err\n }\n\n return { user: newUser, pollableJobToken }\n },\n [user, setUser]\n ),\n }\n}\n\nexport const updateUser = async (user: UUser, campaignId = null): Promise<[User, string | undefined]> => {\n const res = await postJSON('/api/v1/users/me', { user: user, campaign_id: campaignId })\n\n if (res.isError) {\n const error = new Error(`Non-200 status code: ${res.code}.`)\n throw Object.assign(error, { body: res.jsonBody })\n }\n\n // When updating a user, it is possible to trigger the creation of a property,\n // so we actually need to return both the new user, and a pollable token.\n return [res.jsonBody.user as User, res.jsonBody.pollable_job_token]\n}\n","import { generatePath } from 'react-router-dom'\n\nimport { PropertyHead, Property } from 'recoil/properties'\n\nexport const PROPERTY_ROOT = '/properties'\nexport const PROPERTY_ID_PATH_PART = 'propertyId'\nexport const PROPERTY_PATH = `${PROPERTY_ROOT}/:${PROPERTY_ID_PATH_PART}`\n\nfunction encodeProperty(property: PropertyHead, withId = false): string {\n // Pull the info we'll use.\n let value = property.delivery_line_1.toLowerCase()\n\n // First do text replacements\n value = value.replace(/\\s/g, '-').replace(/[^a-z0-9-]/g, '')\n\n // Then uri encode to cover the rest (shouldn't catch anything)\n value = encodeURIComponent(value)\n\n return withId ? `${value}-${property.id}` : value\n}\n\nexport function paramFromProperty(allProperties: Array, property: PropertyHead): string {\n // Encode our address\n const param = encodeProperty(property)\n const paramWithId = encodeProperty(property, true)\n\n // If there is another property who's two possible params would match\n // either of our two possible param, then use the param with the id.\n if (\n allProperties.some(\n (p) =>\n p.id != property.id &&\n ([param, paramWithId].includes(encodeProperty(p)) || [param, paramWithId].includes(encodeProperty(p, true)))\n )\n )\n return paramWithId\n return param\n}\n\nexport function propertyFromParam(\n allProperties: Array,\n param: string\n): PropertyHead | Property | null {\n // If no param was supplied, they may be a single property user\n if (!param) {\n if (allProperties.length == 1) return allProperties[0]\n else return null\n }\n\n // Make sure param is all lowercase.\n param = param.toLowerCase()\n\n // First try to find it with the name and id.\n const matchesWithId = allProperties.filter((p) => encodeProperty(p, true) == param)\n\n // If we find one, that has to be our match.\n if (matchesWithId.length == 1) return matchesWithId[0]\n // If we find more than one, treat it as not finding any\n\n // If we didn't find any, then it must be name without the id\n const matchesWithoutId = allProperties.filter((p) => encodeProperty(p) == param)\n\n // If we find one, that has to be our match.\n if (matchesWithoutId.length == 1) return matchesWithoutId[0]\n // If we find more than one, treat it as not finding any\n\n // If we didn't find any, then no match exists.\n return null\n}\n\nexport default function propertyPath(allProperties: Array, property: PropertyHead): string {\n // If we only have one property, the root is easy enough\n if (allProperties.length == 1) return ''\n return generatePath(PROPERTY_PATH, {\n [PROPERTY_ID_PATH_PART]: paramFromProperty(allProperties, property),\n })\n}\n","import * as Sentry from '@sentry/browser'\n\nif (process.env.ENABLE_SENTRY) {\n const release = `realm-app-${process.env.HEROKU_RELEASE_VERSION || 'dev'}`\n Sentry.init({\n dsn: process.env.SENTRY_DSN,\n release,\n allowUrls: [\n 'analytics.realmhome.com',\n 'bam.nr-data.net',\n 'cdn.mxpnl.com',\n 'connect.facebook.net',\n 'edge.fullstory.com',\n 'fast.wistia.com',\n 'googleads.g.doubleclick.net',\n 'js-agent.newrelic.com',\n 'js.intercomcdn.com',\n 'm.stripe.com',\n 'maps.googleapis.com',\n 's.pinimg.com',\n 'script.hotjar.com',\n 'widget.intercom.io',\n 'www.google-analytics.com',\n 'www.googleadservices.com',\n 'www.googletagmanager.com',\n ],\n ignoreErrors: [\n 'ResizeObserver loop limit exceeded',\n 'TypeError: cancelled',\n 'TypeError: Cancelled',\n 'TypeError: Illegal invocation',\n 'TypeError: Failed to fetch',\n 'TypeError: NetworkError when attempting to fetch resource.',\n ],\n })\n}\n","const sharedActionMap: { [key: string]: Array<{ resolve: any; reject: any }> } = {}\n\nfunction combineKeys(keys: Array): string {\n return keys.join('_')\n}\n\n/**\n * Allows only one call to 'fn' at a time for a given 'key'.\n * Subsequent calls made before 'fn' finishes will be blocked, and\n * given the results of 'fn' at the time 'fn' finishes executing.\n * @param key The Key to use to group this sharedAction together\n * @param fn The function we wish to execute.\n */\nfunction sharedAction(key: string, fn: () => Promise): Promise\n/**\n * Allows only one call to 'fn' at a time for a given 'key' set.\n * Subsequent calls made before 'fn' finishes will be blocked, and\n * given the results of 'fn' at the time 'fn' finishes executing.\n * @param keys The Keys to use to group this sharedAction together.\n * all keys will be used to create a unique group. Keys will\n * not overlap with a similar set of keys. For example,\n * [\"a\",\"b\",\"c\"] will NOT lock out (or be locked out by)\n * [\"a\",\"b\"]\n * @param fn The function we wish to execute.\n */\nfunction sharedAction(keys: Array, fn: () => Promise): Promise\nfunction sharedAction(keys: string | Array, fn: () => Promise): Promise {\n const finalKeys = typeof keys === 'string' ? keys : combineKeys(keys)\n\n return new Promise((resolve, reject) => {\n if (sharedActionMap[finalKeys]) sharedActionMap[finalKeys].push({ resolve, reject })\n else {\n sharedActionMap[finalKeys] = [{ resolve, reject }]\n fn()\n // Ideally we would use 'finally()' to delete the map entry,\n // instead of trapping the exception to ensure we clean up.\n // However some actively used browser implementations still don't\n // have 'finally' (in this case, safari 10.x)\n .then((result) => {\n // Trap any error and rethrow it after we delete the key.\n let trap = null\n try {\n sharedActionMap[finalKeys].map((r) => r.resolve(result))\n } catch (err) {\n trap = err\n }\n delete sharedActionMap[finalKeys]\n if (trap) throw trap\n })\n .catch((err) => {\n // Trap any error and rethrow it after we delete the key.\n let trap = null\n try {\n sharedActionMap[finalKeys].map((r) => r.reject(err))\n } catch (err) {\n trap = err\n }\n delete sharedActionMap[finalKeys]\n if (trap) throw trap\n })\n }\n })\n}\n\nexport interface AttemptResult {\n success: boolean\n results?: T\n}\n\n/**\n * Similar to sharedAction, this call first tests if the key is locked. If it is\n * it will wait and return the results wrapped in an AttemptResult, with 'success'\n * set to true. If the key is not locked, it will immediately return with an\n * empty AttemptResult, with 'success' set to false.\n * @param key The Key to use to group this sharedAction together\n */\nexport function attemptSharedAction(keys: string): Promise>\n/**\n * Similar to sharedAction, this call first tests if the key is locked. If it is\n * it will wait and return the results wrapped in an AttemptResult, with 'success'\n * set to true. If the key is not locked, it will immediately return with an\n * empty AttemptResult, with 'success' set to false.\n * @param keys The Keys to use to group this sharedAction together.\n * all keys will be used to create a unique group. Keys will\n * not overlap with a similar set of keys. For example,\n * [\"a\",\"b\",\"c\"] will NOT lock out (or be locked out by)\n * [\"a\",\"b\"]\n */\nexport function attemptSharedAction(keys: Array): Promise>\nexport function attemptSharedAction(keys: string | Array): Promise> {\n const finalKeys = typeof keys === 'string' ? keys : combineKeys(keys)\n\n return new Promise((resolve, reject) => {\n if (sharedActionMap[finalKeys])\n sharedActionMap[finalKeys].push({ resolve: (results: T) => ({ success: true, results }), reject })\n else {\n // If there is no entry for this key, we can resolve immediately as unsuccessful\n resolve({ success: false })\n }\n })\n}\n\nexport default sharedAction\n"],"names":["path","pathParts","map","p","ret","endsWith","substring","length","startsWith","join","PropertyPlanAPI","constructor","propertyId","this","pathPrefix","listPropertyPlans","getPropertyPlan","propertyPlanId","createPropertyPlan","propertyPlan","res","isError","Error","code","jsonBody","updatePropertyPlan","deletePropertyPlan","listProjects","createProject","project","updateProject","projectId","deleteProject","batchUpdateProjects","updates","batch","previewUpdateProject","listProjectTemplates","subPath","className","Date","getFullYear","href","target","rel","useAuth","loginState","checked","error","state","user","children","setUser","setProperties","setPropertiesError","setLoginState","loginChecked","setLoginChecked","setLoginError","useEffect","LoggingIn","Loading","properties","e","win","window","Sentry","captureException","console","LoggedIn","err","LoggedOut","alert","alt","src","image_url","message","baseProjectTemplatesState","key","default","baseProjectTemplatesIsLoadingState","useBaseProjectTemplates","property","projectTemplates","setProjectTemplates","isLoading","setIsLoading","newProjectTemplates","id","refreshProjectTemplates","useCallback","latestProjectTemplates","useMemo","filter","template","deprecated","api","AvailableErrorType","SizeType","cloneProjectSelection","selection","size","selections","generateProjectSelection","option","defaults","preferred","find","d","is_default","value","part","avg","scale","customizations","subSelections","customization","options","subSelection","projectTemplatesState","projectTemplatesIsLoadingState","projectPartsState","extractParts","rawTemplates","walkParts","prev","reduce","cPrev","oPrev","concat","Object","assign","results","default_option","templates","parts","allParts","keys","useProjectTemplates","selectedProperty","selectedPropertyPlan","projectParts","setProjectPart","isMountedRef","useRef","current","projectsState","deletingProjectsState","tempProjectsState","createTempProject","defaultSelection","project_template_id","square_footage_from_lot","increases_sq_ft","exempt_from_zoning","kind","icon","icon_svg","name","customized","description","additional_score","additional_home_value","cost_estimate_low","cost_estimate_high","cost_estimate","available","_temporary","createTempFromCustomizable","useProjects","isMounted","refreshPropertyPlan","setIsInvalid","setPropertyPlanAsInvalid","projects","setProjects","deletingProjects","setDeletingProjects","tempProjects","setTempProjects","useState","refreshProjects","newProjects","sharedAction","addProject","t","tempNewProject","Math","min","newProject","oldTempId","Promise","all","_updateProject","projectUpdate","preUpdateProject","temp","updatedProject","removeProject","ids","deletedProject","removeProjects","projectIds","deleteResults","batchUpdateProject","method","isFailure","r","status","isSuccess","some","indexOf","origProjects","findIndex","QualificationStatuses","PropertyAPI","listProperties","getProperty","createProperty","type","CreatePropertyErrorType","CreateStartError","body","pollable_job_token","MissingPollToken","token","pollResultResponse","poll","fn","getPropertyPoll","validate","pollResponse","result","interval","maxAttempts","PollTimeout","PollError","newProperty","RetrievalFailure","qualification_status","UnservicableProperty","updateProperty","deleteProperty","propertiesState","propertiesErrorState","loadedPropertyIdState","lastPropertyState","effects_UNSTABLE","setSelf","onSet","savedValue","localStorage","getItem","nValue","parseInt","isNaN","newValue","removeItem","setItem","constructRedirect","location","action","redirect","search","hash","pathname","redirectOriginalAction","usePropertyLoader","sPropertyId","match","history","loadedPropertyId","setLoadedPropertyId","setPropertyPlans","propertyPlans","setProjectParts","setBaseProjectTemplates","baseProjectTemplates","setLastPropertyId","params","replace","cancel","debug","useProperties","lastPropertyId","_isHead","lastProperty","prop","_createProperty","old_prop","updateSelectedProperty","refreshProperty","refreshedProperty","_deleteProperty","oldPropertyIndex","oldProperty","deletedProperty","splice","attachDocToSelectedProperty","file","attachDoc","attachFilesToSelectedProperty","files","attachFiles","deleteFileFromSelectedProperty","fileId","deleteFile","deleteDocFromSelectedProperty","deleteDoc","hasNoPropertiesState","get","forEach","formData","FormData","append","lastRes","file_id","propertyPlansState","loadedPropertyPlanIdState","lastSelectedPropertyPlanIdState","propertyPlanIsInvalidState","useGetPropertyPlans","lastSelectedPropertyPlanId","loadedPropertyPlanId","selectedPropertyPlanId","usePropertyPlanLoader","setLoadedPropertyPlanId","setLastSelectedPropertyPlanId","usePropertyPlans","isInvalid","refreshedPropertyPlan","plans","addPropertyPlan","newPropertyPlan","newPlans","index","plan","push","removePropertyPlan","_updatePropertyPlan","uPropertyPlan","_setIsInvalid","i","isSwitching","userState","LoginState","loginError","getUser","cache","markAccessActivityFeed","property_id","signUp","campaignId","password","userBody","full_name","ambassador_name","email","entered_address","non_app_signup_type","outside_bids_status","onboarding","outsideBids","budget","completed_profile","utm_params","utmParams","phoneNumber","phone_number","attomId","placeId","goals","homeownerType","homeowner_type","propertyShare","referred_by_user","user_id","additionalInfo","signup_message","sfdcLeadAttrs","projectType","sfdc_lead_attrs","response","campaign_id","bypass_signin","data","signupResponse","$email","$name","address","via","signupDate","created_at","is_marketplace","marketplace","args","orderId","customerId","customerEmail","sha1_email","useUser","updateUser","userUpdate","oldUser","newUser","pollableJobToken","password_confirmation","current_password","dropOnewayProperties","PROPERTY_ROOT","PROPERTY_ID_PATH_PART","PROPERTY_PATH","encodeProperty","withId","delivery_line_1","toLowerCase","encodeURIComponent","paramFromProperty","allProperties","param","paramWithId","includes","propertyFromParam","matchesWithId","matchesWithoutId","propertyPath","release","dsn","allowUrls","ignoreErrors","sharedActionMap","combineKeys","finalKeys","resolve","reject","then","trap","catch"],"sourceRoot":""}