Setting the file. One moment.
Use Rental Duration · Wix Vibe Headless · wix/skills · Skills Docs
ContentsBack to the top of the page 22.10
Post Detail
references/bookings/app/hooks/useRentalDuration.js
references/bookings/app/hooks/ useRentalDuration.js
JavaScript · 128 lines · 5 KB
14 // branch of its own — `isRental(service)` decides whether to show the length step at all.
15 import { useCallback, useEffect, useState } from "react" ;
16 import {
17 dailyEndOptions,
18 isRental,
19 listEndOptions,
20 previewRentalPrice,
21 rentalDuration,
22 } from "@/rest/wix-bookings-rentals" ;
23
24 /** "1 hr 30 min" / "2 days" — the label for one length option. */
25 function lengthLabel ( startLocal , endLocal , unit ) {
26 if (unit === "DAY" ) {
27 const days = Math. round (( new Date ( `${ endLocal }Z` ) - new Date ( `${ startLocal }Z` )) / 86400000 );
28 return days === 1 ? "1 day" : `${ days } days` ;
29 }
30 const minutes = Math. round (( new Date ( `${ endLocal }Z` ) - new Date ( `${ startLocal }Z` )) / 60000 );
31 const h = Math. floor (minutes / 60 );
32 const m = minutes % 60 ;
33 return [h ? `${ h } hr` : "" , m ? `${ m } min` : "" ]. filter (Boolean). join ( " " ) || "0 min" ;
34 }
35
36 /**
37 * @param {object|null} service The service from useServiceDetail — may be null while loading.
38 * @param {object|null} startSlot The slot the customer picked (its localStartDate is the start).
39 * @param {string|null} timeZone The zone the slot list came back in.
40 * @returns {{
41 * isRental: boolean,
42 * duration: { unit: "HOUR"|"DAY", min: number, max: number, step: number }|null,
43 * options: { localEndDate: string, label: string }[],
44 * selected: object|null,
45 * select: (option: object) => void,
46 * endDate: string|null, // pass as the booking's localEndDate
47 * price: { total: string, currency: string|null }|null,
48 * loading: boolean,
49 * error: string|null,
50 * }}
51 */
52 export function useRentalDuration ( service , startSlot , timeZone ) {
53 const [ options , setOptions ] = useState ([]);
54 const [ selected , setSelected ] = useState ( null );
55 const [ price , setPrice ] = useState ( null );
56 const [ loading , setLoading ] = useState ( false );
57 const [ error , setError ] = useState ( null );
58
59 const rental = isRental (service);
60 const duration = rentalDuration (service);
61 const start = startSlot?.localStartDate ?? null ;
62 // Forward the chosen slot's own location to end-options.
63 const location = startSlot?.location ?? null ;
64 const serviceId = service?._id || service?.id || null ;
65
66 // A new start invalidates the lengths that were valid for the previous one.
67 useEffect (() => {
68 setSelected ( null );
69 setPrice ( null );
70 if ( ! rental || ! start || ! serviceId) {
71 setOptions ([]);
72 return ;
73 }
74 let alive = true ;
75 setLoading ( true );
76 setError ( null );
77 const load = duration.unit === "DAY"
78 ? dailyEndOptions (service, { localStartDate: start, timeZone })
79 . then (( list ) => list. map (( o ) => ({ localEndDate: o.localEndDate, label: lengthLabel (start, o.localEndDate, "DAY" ) })))
80 : listEndOptions (serviceId, { localStartDate: start, location, timeZone })
81 . then (({ endOptions }) =>
82 endOptions. map (( o ) => ({ localEndDate: o.localEndDate, label: lengthLabel (start, o.localEndDate, "HOUR" ) })),
83 );
84 load
85 . then (( list ) => {
86 if ( ! alive) return ;
87 setOptions (list);
88 setLoading ( false );
89 })
90 . catch (( e ) => {
91 if ( ! alive) return ;
92 // Fail loudly — an empty length list and a failed call look identical on screen
93 // otherwise, and the second one is a bug the visitor should not absorb silently.
94 setError (e?.message || "Could not load available lengths." );
95 setOptions ([]);
96 setLoading ( false );
97 });
98 return () => {
99 alive = false ;
100 };
101 // eslint-disable-next-line react-hooks/exhaustive-deps
102 }, [rental, serviceId, start, timeZone, duration?.unit]);
103
104 // The price comes from the server for the chosen length — never multiply the rate locally.
105 const select = useCallback (
106 ( option ) => {
107 setSelected (option);
108 setPrice ( null );
109 if ( ! option || ! serviceId || ! start || ! timeZone) return ;
110 previewRentalPrice (serviceId, { localStartDate: start, localEndDate: option.localEndDate, timeZone })
111 . then (( p ) => setPrice ({ total: p.total, currency: p.currency }))
112 . catch (( e ) => setError (e?.message || "Could not price that length." ));
113 },
114 [serviceId, start, timeZone],
115 );
116
117 return {
118 isRental: rental,
119 duration,
120 options,
121 selected,
122 select,
123 endDate: selected?.localEndDate ?? null ,
124 price,
125 loading,
126 error,
127 };
128 }