PageSourceSearch

https://kuto.app/static/useRetailers-D6zsGYqW.js

js kuto.app collected 2026-10-07 04:06:12 UTC 13,215 bytes, 529 lines download raw bytes

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.