1import { ak as Hours, v as Debug, a as useFirebaseApp, r as reactExports, b as useFirestoreHelper, d as deleteField } from "./index-B4tqMR8N.js"; 2import { u as useResettableState } from "./useResettableState-Ds-O-ieJ.js"; 3import { u as unflattenObjectProperty } from "./flatten-CqOrFiSL.js"; 4import { u as useKutoCollection } from "./useKutoCollection-DEQLSD9O.js"; 5const DEFAULT_MIN_FINANCED_AMOUNT = 1e4; 6const PREAPPROVAL_EXPIRATION = Hours(24); 7const StandardFinancingTerms = { 8 biweekly4: { 9 chargeCount: 4, 10 chargeSchedule: "bi-weekly", 11 apr: 0 12 } 13 // monthly6: { 14 // chargeCount: 6, 15 // chargeSchedule: "monthly", 16 // apr: 15_00, 17 // }, 18 // monthly12: { 19 // chargeCount: 12, 20 // chargeSchedule: "monthly", 21 // apr: 20_00, 22 // }, 23}; 24function getClassicMonthlyPaymentAmount(amount, apr, chargeCount) { 25 const P = amount; 26 const i = apr / 1e4 / 12; 27 const n = chargeCount; 28 const A = P * (i * Math.pow(1 + i, n)) / (Math.pow(1 + i, n) - 1); 29 return A; 30} 31function getPeriodicPaymentAmount(amount, terms) { 32 if (terms.chargeSchedule === "monthly") { 33 let downPayment = 0; 34 let cycles = 0; 35 while (true) { 36 if (cycles++ > 50) 37 break; 38 const proposedPayment = getClassicMonthlyPaymentAmount(amount - downPayment, terms.apr, terms.chargeCount - 1); 39 const roundedDownPayment = Math.round(downPayment); 40 const roundedProposedPayment = Math.round(proposedPayment); 41 if (roundedDownPayment === roundedProposedPayment) { 42 return roundedProposedPayment; 43 } else { 44 downPayment = (downPayment + proposedPayment) / 2; 45 } 46 } 47 throw new Error("Could not find down payment amount."); 48 } else if (terms.chargeSchedule === "bi-weekly") { 49 if (terms.apr > 0) { 50 throw new Error("Bi-weekly financing with APR is not supported."); 51 } 52 return Math.floor(amount / terms.chargeCount); 53 } 54 throw new Error("Unknown charge schedule: ".concat(terms.chargeSchedule)); 55} 56function getTotalInterest(amount, terms) { 57 if (!terms.apr) { 58 return 0; 59 } 60 return getPeriodicPaymentAmount(amount, terms) * terms.chargeCount - amount; 61} 62function isPreapprovalValid({ 63 created, 64 deleted 65}) { 66 if (deleted) 67 return false; 68 if (Date.now() > created + PREAPPROVAL_EXPIRATION) { 69 return false; 70 } 71 return true; 72} 73const MAX_RETAILER_DISTANCE = 250; 74const MAX_RETAILER_DISTANCE_NEW_AREA = 5e3; 75const MAX_LOCATION_AGE = Hours(2); 76const MAX_RETAILER_EDITORIAL_LENGTH = 160; 77const MOBILE_RETAILER_EXPIRATION = Hours(18); 78function isMobileRetailerOpen(retailer) { 79 return retailer.status !== "inactive" && retailer.created > Date.now() - MOBILE_RETAILER_EXPIRATION; 80} 81function getRetailerImage(retailerOrId, image) { 82 if (typeof retailerOrId === "string") { 83 if (!image) 84 return null; 85 const { 86 version, 87 ...rest 88 } = image; 89 return { 90 path: "retailerImages/".concat(retailerOrId, "/").concat(version), 91 ...rest 92 }; 93 } else { 94 if (!retailerOrId.image) 95 return null; 96 const { 97 version, 98 ...rest 99 } = retailerOrId.image; 100 return { 101 path: "retailerImages/".concat(retailerOrId.id, "/").concat(version), 102 ...rest 103 }; 104 } 105} 106function getRetailerAboutImage(retailerId, image) { 107 const { 108 version, 109 ...rest 110 } = image; 111 return { 112 path: "retailerAboutImages/".concat(retailerId, "/").concat(version), 113 ...rest 114 }; 115} 116function isRetailerAboutValid(about) { 117 return !!about && !!about.text; 118} 119function isRetailerUrlValid(url) { 120 const regex = /^(https?:\/\/)([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/; 121 return !!url && regex.test(url); 122} 123function isRetailerEditorialValid(editorial) { 124 return !!editorial && !!editorial.text && editorial.text.length <= MAX_RETAILER_EDITORIAL_LENGTH; 125} 126function formatRetailerAddress(retailer, { 127 format = "short" 128} = {}) { 129 var _a, _b; 130 if (!retailer.address) 131 return null; 132 const parts = retailer.address.split(", "); 133 parts.pop(); 134 if (format === "short") { 135 parts.pop(); 136 parts.pop(); 137 } 138 if (((_a = parts[0]) == null ? void 0 : _a.includes(retailer.name)) && // Make sure we don't accidentally chop off a string that begins with a number 139 !((_b = parts[0]) == null ? void 0 : _b.match(/^\d{1,6} /))) { 140 parts.shift(); 141 } 142 return parts.join(", "); 143} 144function formatRetailerUrl({ 145 url 146}, { 147 format = "long" 148} = {}) { 149 if (format === "short") { 150 try { 151 if (!(url == null ? void 0 : url.startsWith("http"))) { 152 url = "http://" + url; 153 } 154 return new URL(url).hostname; 155 } catch (error) { 156 return null; 157 } 158 } 159 return url || null; 160} 161function getGoogleMapsLink(placeId, location) { 162 return "https://www.google.com/maps/search/?api=1&query=".concat(location.lat, ",").concat(location.lng, "&query_place_id=").concat(placeId); 163} 164function getRetailerTipStyle({ 165 tipStyle, 166 keepTheChange 167}) { 168 if (tipStyle) { 169 return tipStyle; 170 } 171 if (keepTheChange) { 172 return "keepTheChange"; 173 } 174 return "prompt"; 175} 176function getRetailerFullName({ 177 name, 178 variant 179}) { 180 if (variant) { 181 return "".concat(name, " (").concat(variant, ")"); 182 } else { 183 return name; 184 } 185} 186function getRetailerMinFinanceAmount({ 187 financingMinimum 188}) { 189 return financingMinimum != null ? financingMinimum : DEFAULT_MIN_FINANCED_AMOUNT; 190} 191function getRetailerStub(retailer) { 192 const { 193 id, 194 name, 195 variant, 196 location, 197 image, 198 category, 199 categoryFilter, 200 sandbox, 201 tipping, 202 tipStyle 203 } = retailer; 204 const pushPay = "pushPay" in retailer ? retailer.pushPay : false;
205 return { 206 id, 207 name, 208 variant: variant != null ? variant : "", 209 location: location != null ? location : null, 210 image, 211 ...category ? { 212 category 213 } : null, 214 ...categoryFilter ? { 215 categoryFilter 216 } : null, 217 ...sandbox ? { 218 sandbox 219 } : null, 220 ...pushPay ? { 221 pushPay 222 } : null, 223 ...tipping ? { 224 tipping 225 } : null, 226 ...tipStyle ? { 227 tipStyle 228 } : null 229 }; 230} 231const RetailerFunnel = { 232 objectType: "retailers", 233 steps: { 234 viewed: { 235 title: "Viewed" 236 }, 237 selected: { 238 title: "Selected" 239 }, 240 clicked: { 241 title: "Clicked" 242 }, 243 paid: { 244 title: "Paid" 245 } 246 } 247}; 248const debug = Debug("firewing:document"); 249function useFirestoreDocument(ref, deps, { loaded } = {}) { 250 const app = useFirebaseApp(); 251 const persistenceEnabled = app().firestore().persistenceEnabled; 252 const [value, setValue] = useResettableState(void 0, deps); 253 reactExports.useEffect(() => { 254 if (loaded) 255 return; 256 const resolved = ref(app); 257 if (resolved) { 258 let snapshotHandler = function(snapshot) { 259 if (!persistenceEnabled && !snapshot.exists() && snapshot.metadata.fromCache) { 260 setValue(void 0); 261 } else { 262 setValue(snapshotToObject(snapshot)); 263 } 264 }, errorHandler = function(error) { 265 console.error("Error loading " + descriptor + "\n" + error.stack); 266 }; 267 const descriptor = resolved.path; 268 debug("Loading " + descriptor); 269 return resolved.onSnapshot({ includeMetadataChanges: true }, snapshotHandler, errorHandler); 270 } else if (!resolved) { 271 setValue(resolved === null ? null : void 0); 272 } 273 }, deps); 274 return loaded || value; 275} 276function snapshotToObject(snapshot) { 277 if (snapshot.exists()) { 278 return { ...snapshot.data(), id: snapshot.id }; 279 } else { 280 return null; 281 } 282} 283function useRetailers() { 284 const helper = useFirestoreHelper(); 285 const collection = useKutoCollection("retailers"); 286 const shifts = useKutoCollection("shifts"); 287 return { 288 collection, 289 shifts, 290 doc: (retailerId) => retailerId ? collection().doc(retailerId) : collection().doc(), 291 async getOne(retailerId) { 292 return helper.getOne(collection().doc(retailerId)); 293 }, 294 queryByTeam({ 295 teamId 296 }) { 297 return collection().where("teamId", "==", teamId); 298 }, 299 queryActive() { 300 return collection().where("status", "==", "active"); 301 }, 302 queryByStatusAndListed({ 303 status, 304 listed 305 }) { 306 const query = Array.isArray(status) ? collection().where("status", "in", status) : collection().where("status", "==", status); 307 if (listed !== void 0) { 308 return query.where("unlisted", "==", !listed); 309 } else { 310 return query.where("unlisted", "in", [true, false]); 311 } 312 }, 313 querySandbox() { 314 return collection().where("sandbox", "==", true); 315 }, 316 queryByLoyaltyProgram({ 317 programId 318 }) { 319 return collection().where("loyaltyProgram.id", "==", programId); 320 }, 321 async updateName({ 322 batch, 323 retailerId, 324 name, 325 variant 326 }) { 327 await helper.update(batch, collection().doc(retailerId), null, { 328 name, 329 variant 330 }); 331 }, 332 async updateStatus({ 333 batch, 334 retailer, 335 status 336 }) { 337 return helper.update(batch, collection().doc(retailer.id), retailer, { 338 status 339 }); 340 }, 341 async updateTipStyle({ 342 batch, 343 retailer, 344 tipStyle 345 }) { 346 return helper.update(batch, collection().doc(retailer.id), retailer, { 347 tipStyle, 348 keepTheChange: tipStyle === "keepTheChange" 349 }); 350 }, 351 async updateTipping({ 352 batch, 353 retailer, 354 tipping, 355 keepTheChange 356 }) { 357 return helper.update(batch, collection().doc(retailer.id), retailer, { 358 tipping, 359 keepTheChange 360 }); 361 }, 362 async updateCustomTipPrompt({ 363 retailer, 364 batch, 365 ...tipPrompt 366 }) { 367 return helper.merge(batch, collection().doc(retailer.id), retailer, { 368 customTipPrompt: tipPrompt 369 }); 370 }, 371 async updateAbout({ 372 retailer, 373 batch, 374 about 375 }) { 376 return helper.update(batch, collection().doc(retailer.id), retailer, { 377 about 378 }); 379 }, 380 async updateEditorial({ 381 retailer, 382 batch, 383 editorial 384 }) { 385 return helper.update(batch, collection().doc(retailer.id), retailer, { 386 editorial 387 }); 388 }, 389 async updateContact({ 390 retailer, 391 batch, 392 phone, 393 url, 394 instagram, 395 address 396 }) { 397 return helper.update(batch, collection().doc(retailer.id), retailer, { 398 ...phone !== void 0 ? { 399 phone 400 } : null, 401 ...url !== void 0 ? { 402 url 403 } : null, 404 ...instagram !== void 0 ? { 405 instagram 406 } : null, 407 ...address !== void 0 ? { 408 address 409 } : null 410 }); 411 }, 412 async updatePushPay({ 413 batch, 414 retailer, 415 pushPay 416 }) { 417 return helper.update(batch, collection().doc(retailer.id), retailer, { 418 pushPay 419 }); 420 }, 421 async updateFinancing({ 422 batch, 423 retailer, 424 financing 425 }) { 426 return helper.update(batch, collection().doc(retailer.id), retailer, { 427 financing 428 }); 429 }, 430 async updatePaging({ 431 batch, 432 retailerId, 433 paging 434 }) { 435 await helper.update(batch, collection().doc(retailerId), null, { 436 paging 437 }); 438 }, 439 async updatePhoneOrders({ 440 batch, 441 retailerId, 442 phoneOrders 443 }) { 444 await helper.update(batch, collection().doc(retailerId), null, { 445 phoneOrders 446 }); 447 }, 448 async updatePhone({ 449 batch, 450 retailerId, 451 phone 452 }) { 453 await helper.update(batch, collection().doc(retailerId), null, { 454 phone 455 }); 456 }, 457 async updateUnlisted({ 458 batch, 459 retailerId, 460 unlisted 461 }) { 462 await helper.update(batch, collection().doc(retailerId), null, { 463 unlisted 464 }); 465 }, 466 async updateLocation({ 467 batch, 468 retailerId, 469 location 470 }) { 471 await helper.update(batch, collection().doc(retailerId), null, { 472 location: location != null ? location : deleteField() 473 }); 474 }, 475 async createShift({ 476 batch, 477 teamId, 478 retailerId, 479 userProfiles 480 }) { 481 return await helper.create(batch, shifts(), { 482 teamId, 483 retailerId, 484 userProfiles: unflattenObjectProperty(userProfiles) 485 }); 486 }, 487 async getOneShift({ 488 shiftId 489 }) { 490 return await helper.getOne(shifts().doc(shiftId)); 491 }, 492 async updateShift({ 493 batch, 494 retailerId, 495 shiftId 496 }) { 497 await helper.update(batch, collection().doc(retailerId), null, { 498 shiftId 499 }); 500 } 501 }; 502} 503export { 504 DEFAULT_MIN_FINANCED_AMOUNT as D, 505 MOBILE_RETAILER_EXPIRATION as M, 506 RetailerFunnel as R, 507 StandardFinancingTerms as S, 508 useFirestoreDocument as a, 509 getRetailerFullName as b, 510 getGoogleMapsLink as c, 511 getPeriodicPaymentAmount as d, 512 getTotalInterest as e, 513 formatRetailerAddress as f, 514 getRetailerImage as g, 515 getRetailerTipStyle as h, 516 getRetailerStub as i, 517 isMobileRetailerOpen as j, 518 MAX_RETAILER_DISTANCE as k, 519 MAX_LOCATION_AGE as l, 520 MAX_RETAILER_DISTANCE_NEW_AREA as m, 521 getRetailerAboutImage as n, 522 isRetailerUrlValid as o, 523 formatRetailerUrl as p, 524 getRetailerMinFinanceAmount as q, 525 isPreapprovalValid as r, 526 isRetailerEditorialValid as s, 527 isRetailerAboutValid as t, 528 useRetailers as u
vendor: 3 bytes, line 529
529};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.