camper/web/templates/public/booking/fields.gohtml

255 lines
20 KiB
Plaintext
Raw Normal View History

<!--
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
SPDX-FileCopyrightText: 2023 Oriol Carbonell <info@oriolcarbonell.cat>
SPDX-License-Identifier: AGPL-3.0-only
-->
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.publicPage*/ -}}
{{ with .Form -}}
<fieldset>
Add payment relation and use it to compute the booking’s cart I had to add the payment concept separate from the booking, unlike other eCommerce solutions that subsume the two into a single “order”, like WooCommerce, because bookings should be done in a separate Camper instance that will sync to the public instance, but the payment is done by the public instance. There will be a queue or something between the public and the private instance to pass along the booking information once the payment is complete, but the public instance still needs to keep track of payments without creating bookings. To compute the total for that payment i had to do the same as was doing until now for the cart. To prevent duplications, or having functions with complex return types, i now create a “draft” payment while the user is filling in the form, and compute the cart there; from Go i only have to retrieve the data from the relation, that simplifies the work, actually. Since the payment is computed way before customers enter their details, i can not have that data in the same payment relation, unless i allow NULL values. Allowing NULL values means that i can create a payment without customer, thus i moved all customer details to a separate relation. It still allows payment without customer, but at least there are no NULL values. Draft payments should be removed after a time, but i believe this needs to be done in a cronjob or similar, not in the Go application. To update the same payment while filling the same booking form, i now have a hidden field with the payment slug. A competent developer would have used a cookie or something like that; i am not competent.
2024-02-12 04:21:00 +00:00
<input type="hidden" name="{{ .PaymentSlug.Name }}" value="{{ .PaymentSlug.Val }}">
<fieldset class="accommodation"
data-hx-get="/{{ currentLocale }}/booking" data-hx-trigger="change"
>
<legend>{{( pgettext "Accommodation" "title" )}}</legend>
{{ range .CampsiteType.Options -}}
<label><input type="radio" name="{{ $.Form.CampsiteType.Name }}" value="{{ .Value }}"
{{- if $.Form.CampsiteType.IsSelected .Value }} checked{{ end -}}
> {{ .Label }}</label><br>
{{- end }}
{{ template "error-message" .CampsiteType }}
</fieldset>
{{ with .Dates -}}
<fieldset class="booking-period"
data-hx-get="/{{ currentLocale }}/booking" data-hx-trigger="change"
>
<legend>{{( pgettext "Booking Period" "title" )}}</legend>
{{ with .ArrivalDate -}}
<label>
{{( pgettext "Arrival date" "input" )}}<br>
<input type="date" required
min="{{ formatDateAttr .MinDate }}"
max="{{ formatDateAttr .MaxDate }}"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{ template "error-message" . }}
{{- end }}
{{ with .DepartureDate -}}
<label>
{{( pgettext "Departure date" "input" )}}<br>
<input type="date" required
min="{{ formatDateAttr .MinDate }}"
max="{{ formatDateAttr .MaxDate }}"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{ template "error-message" . }}
{{- end }}
</fieldset>
{{- end }}
{{ with $guests := .Guests -}}
<fieldset class="guests campsite-options"
data-hx-get="/{{ currentLocale }}/booking" data-hx-trigger="change"
>
<legend>{{( pgettext "Guests" "title" )}}</legend>
{{ with .NumberAdults -}}
<label>
{{( pgettext "Adults aged 17 or older" "input" )}}<br>
<input type="number" required
name="{{ .Name }}" value="{{ .Val }}"
min="1"{{if not $guests.OverflowAllowed }} max="{{ $guests.MaxGuests }}"{{ end }}
{{ template "error-attrs" . }}
><br>
</label>
{{ template "error-message" . }}
{{- end }}
{{ with .NumberTeenagers -}}
<label>
{{( pgettext "Teenagers from 11 to 16 years old" "input" )}}<br>
<input type="number" required
name="{{ .Name }}" value="{{ .Val }}"
min="0"{{if not $guests.OverflowAllowed }} max="{{ $guests.MaxGuests | dec }}"{{ end }}
{{ template "error-attrs" . }}
><br>
</label>
{{ template "error-message" . }}
{{- end }}
{{ with .NumberChildren -}}
<label>
{{( pgettext "Children from 2 to 10 years old" "input" )}}<br>
<input type="number" required
name="{{ .Name }}" value="{{ .Val }}"
min="0"{{if not $guests.OverflowAllowed }} max="{{ $guests.MaxGuests | dec }}"{{ end }}
{{ template "error-attrs" . }}
><br>
</label>
{{ template "error-message" . }}
{{- end }}
{{ if .Overflow -}}
<small>{{( gettext "Note: Due to guest capacity, we have added more accomodations to the booking, but we <strong>cannot</strong> guarantee that they will be next to each other." ) | raw}}</small>
{{- end }}
{{ if .Error -}}
<p class="error">{{ .Error }}</p>
{{- end }}
{{ if .NumberDogs -}}
{{ with .NumberDogs -}}
<label>
{{( pgettext "Dogs" "input" )}}<br>
<input type="number" required
name="{{ .Name }}" value="{{ .Val }}" min="0"
{{ template "error-attrs" . }}
><br>
</label>
{{ template "error-message" . }}
{{- end }}
{{- else -}}
<small>{{( gettext "Note: This accommodation does <strong>not</strong> allow dogs.") | raw }}</small>
{{- end }}
</fieldset>
{{- end }}
{{ with .Options -}}
<fieldset class="campsite-options"
data-hx-get="/{{ currentLocale }}/booking" data-hx-trigger="change"
>
<legend>{{ .Legend }}</legend>
{{ with .ZonePreferences -}}
<label>
<span>
{{( pgettext "Area preferences (optional)" "input" )}}
<a href="/{{ currentLocale }}/campground?zones"
target="_blank">{{( gettext "Campground map" )}}</a>
</span><br>
<input type="text"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{ template "error-message" . }}
{{- end }}
{{ range .Options -}}
<label>
{{ .Label }}<br>
<input type="number" required
name="{{ .Input.Name }}" value="{{ .Input.Val }}"
min="{{ .Min }}" max="{{ .Max }}"
{{ template "error-attrs" .Input }}
><br>
</label>
{{ template "error-message" .Input }}
{{- end }}
</fieldset>
{{- end }}
{{ with .Customer -}}
<fieldset class="customer-details">
<legend>{{( pgettext "Customer Details" "title" )}}</legend>
{{ with .FullName -}}
<label>
{{( pgettext "Full name" "input" )}}<br>
<input type="text" required autocomplete="name" minlength="2"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{ template "error-message" . }}
{{- end }}
{{ with .Address -}}
<label>
{{( pgettext "Address (optional)" "input" )}}<br>
<input type="text" autocomplete="billing street-address"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{ template "error-message" . }}
{{- end }}
{{ with .PostalCode -}}
<label>
{{( pgettext "Postcode (optional)" "input" )}}<br>
<input type="text" autocomplete="billing postal-code"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{ template "error-message" . }}
{{- end }}
{{ with .City -}}
<label>
{{( pgettext "Town or village (optional)" "input" )}}<br>
<input type="text"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{ template "error-message" . }}
{{- end }}
{{ with .Country -}}
<label>
{{( pgettext "Country" "input" )}}<br>
<select name="{{ .Name }}"
required autocomplete="country">
<option>{{( gettext "Choose a country" )}}</option>
{{ template "error-attrs" . }}>{{ template "list-options" . }}
</select><br>
</label>
{{ template "error-message" . }}
{{- end }}
{{ with .Email -}}
<label>
{{( pgettext "Email" "input" )}}<br>
<input type="email" required autocomplete="email"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{ template "error-message" . }}
{{- end }}
{{ with .Phone -}}
<label>
{{( pgettext "Phone" "input" )}}<br>
<input type="tel" required autocomplete="tel"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{ template "error-message" . }}
{{- end }}
{{ with .ACSICard -}}
<label class="full-row">
<input type="checkbox" name="{{ .Name }}" {{ if .Checked}}checked{{ end }}
{{ template "error-attrs" . }}
> {{( pgettext "ACSI card? (optional)" "input" )}}</label><br>
{{ template "error-message" . }}
{{- end }}
{{ with .Agreement -}}
<label class="full-row">
<input type="checkbox" required name="{{ .Name }}" {{ if .Checked}}checked{{ end }}
{{ template "error-attrs" . }}
> {{ printf ( pgettext "I have read and I accept %[1]sthe reservation conditions%[2]s" "input" ) (printf "<a href=\"/%s/legal/reservation\" rel=\"terms-of-service\" target=\"_blank\">" currentLocale) (print "</a>") | raw }}
</label><br>
{{ template "error-message" . }}
{{- end }}
</fieldset>
{{- end }}
</fieldset>
{{ with .Cart -}}
<footer>
<dl class="cart">
{{ range .Lines -}}
<div>
<dt>{{ .Units}}x{{( pgettext .Concept "cart" )}}</dt>
<dd>{{ formatPrice .Subtotal }}</dd>
</div>
{{- end }}
<div class="total">
<dt>{{( pgettext "Total" "cart" )}}</dt>
<dd>{{ formatPrice .Total }}</dd>
</div>
</dl>
<div class="credit-cards">
<!-- @formatter:off -->
<svg role="img" viewBox="0 0 42.3907 13.69036"><title>VISA</title><path d="M16.09513.24198l-5.54984,13.24128h-3.62085L4.19335,2.91611c-.16581-.65087-.30998-.88932-.81425-1.16353-.8233-.44665-2.18289-.86569-3.37909-1.12578L.08125.24198h5.82847c.74294,0,1.41081.49456,1.57949,1.35009l1.44238,7.66208L12.49636.24176h3.59878v.00022ZM30.28226,9.1601c.01457-3.49479-4.83251-3.68731-4.79918-5.24847.01038-.47513.46276-.98028,1.45298-1.10922.4908-.06425,1.8431-.11326,3.37689.59258l.60164-2.80793c-.82419-.29916-1.88461-.58706-3.20401-.58706-3.38572,0-5.76842,1.79983-5.78851,4.37704-.02186,1.90625,1.7007,2.96998,2.99847,3.60341,1.33508.64866,1.78305,1.0655,1.77797,1.64572-.00949.88821-1.06484,1.28032-2.05108,1.29578-1.72167.02649-2.72072-.46585-3.51709-.83611l-.62084,2.90088c.80034.36716,2.27761.68752,3.80918.70364,3.59855,0,5.95255-1.77753,5.96359-4.53026M39.22267,13.48326h3.16803L39.62538.24198h-2.92406c-.65749,0-1.2121.38284-1.45761.97145l-5.14007,12.26983h3.59679l.71402-1.97778h4.3947l.41353,1.97778ZM35.40068,8.79161l1.80292-4.97161,1.03768,4.97161h-2.8406ZM20.98925.24198l-2.83244,13.24128h-3.42524L17.56511.24198h3.42414Z" fill="#1434cb" stroke-width="0"/></svg>
<svg role="img" viewBox="0 0 48 48"><title>MasterCard</title><path d="M31.09306,9.96519c7.83455,0,14.18572,6.2836,14.18572,14.03481s-6.35117,14.03481-14.18572,14.03481c-2.58375,0-5.00617-.68341-7.09266-1.87749l.06674-.03867c4.20318-2.43597,7.02592-6.95117,7.02592-12.11864s-2.82274-9.68267-7.02592-12.11864l-.06715-.03867h0c2.08689-1.19408,4.50931-1.87749,7.09306-1.87749Z" fill="#f5a623" fill-rule="evenodd" stroke-width="0"/><path d="M16.90694,38.03481c-7.83456,0-14.18572-6.2836-14.18572-14.03481s6.35116-14.03481,14.18572-14.03481c2.58376,0,5.00617.68341,7.09266,1.87749l-.06674.03867c-4.20318,2.43597-7.02592,6.95117-7.02592,12.11864s2.82274,9.68267,7.02592,12.11864l.06715.03867h0c-2.08689,1.19408-4.50931,1.87749-7.09306,1.87749Z" fill="#e00034" fill-rule="evenodd" stroke-width="0"/><path d="M24.00184,11.84385c4.23911,2.42694,7.09102,6.96204,7.09102,12.15615s-2.85191,9.72921-7.09102,12.15615c-4.24187-2.42549-7.0947-6.96121-7.0947-12.15615,0-5.16747,2.82274-9.68267,7.02591-12.11864l.06878-.0375Z" fill="#ff5a00" fill-rule="evenodd" stroke-width="0"/></svg>
<svg role="img" viewBox="0 0 48 48"><title>Maestro</title><path d="M15.44756,42.31041v-2.15301c.05204-.70249-.48134-1.31392-1.18383-1.35945-.05204-.00651-.11058-.00651-.16261,0-.48134-.03252-.94316.20164-1.20334.61143-.24067-.39678-.66997-.63094-1.13179-.61143-.40328-.01951-.78705.17562-1.00821.50736v-.4228h-.74802v3.4279h.75453v-1.89933c-.06505-.43581.24067-.84559.68298-.90413.05204-.00651.10407-.00651.16261-.00651.49435,0,.74802.32523.74802.90413v1.90583h.75453v-1.89933c-.05854-.44231.25368-.84559.69599-.90413.04553-.00651.09757-.00651.1431-.00651.50736,0,.75453.32523.75453.90413v1.90583h.74152ZM19.64299,40.59971v-1.7172h-.75453v.41629c-.25368-.33173-.65696-.52036-1.07976-.50085-.98869,0-1.79526.80656-1.79526,1.79526s.80656,1.79525,1.79526,1.79525c.41629.01301.81957-.16912,1.07976-.50085v.41629h.74802l.00651-1.70419ZM16.87205,40.59971c.03252-.5724.52687-1.00821,1.09927-.97568.5724.03252,1.0082.52687.97568,1.09927-.03252.54638-.48134.97568-1.02772.97568-.5659.01301-1.03422-.4358-1.04723-1.0017v-.09757h0ZM35.59865,38.80445c.24717,0,.48784.04553.7155.1366.22115.08456.41629.21465.58541.37726.16912.16261.29921.35775.39028.5724.18863.46182.18863.97568,0,1.4375-.09106.21465-.22116.40979-.39028.5724-.16912.16261-.36426.2927-.58541.37726-.47483.18213-.9952.18213-1.47003,0-.21465-.08456-.41629-.21465-.5789-.37726-.16261-.16261-.29271-.35775-.38377-.5724-.18863-.46182-.18863-.97568,0-1.4375.09106-.21465.22115-.40979.38377-.5724.16912-.16261.36426-.2927.5789-.37726.23416-.09757.48784-.14961.74802-.14961l.0065.01301ZM35.59865,39.51995c-.1431,0-.2927.02602-.4293.07805-.13009.05204-.24717.13009-.33824.22766-.09757.10407-.17562.22766-.22766.35775-.11058.2862-.11058.60492,0,.89112.05204.1366.13009.25368.22766.35775.09757.09757.21465.17562.33824.22766.27319.10407.5789.10407.8521,0,.1366-.05204.25368-.12359.35775-.22766.09757-.10407.17562-.22766.22766-.35775.11058-.2862.11058-.60492,0-.89112-.05204-.1366-.13009-.25368-.22766-.35775-.10407-.09757-.22766-.17562-.35775-.22766-.1366-.06505-.2797-.09757-.4293-.09757l.0065.01951ZM23.70183,40.59971c0-1.07975-.66997-1.79525-1.62614-1.79525-.98869.01301-1.78225.82608-1.76924,1.82127.01301.98869.82608,1.78225,1.82127,1.76924.51386.01951,1.00821-.15611,1.40498-.48134l-.35775-.55289c-.2862.22766-.63745.35125-.9952.35775-.51386.04553-.96918-.32523-1.02772-.83909h2.54328c.00651-.09106.00651-.18213.00651-.2797ZM21.14554,40.294c.02602-.47483.4293-.83909.90413-.82608.46833-.01301.8586.36425.87161.83258h0l-1.77574-.0065ZM26.84353,39.75412c-.32523-.18863-.69599-.2927-1.07975-.2927-.40979,0-.65046.14961-.65046.40328s.26018.2927.57891.33824l.35775.05204c.74802.11058,1.19684.4228,1.19684,1.02772s-.5724,1.11878-1.56109,1.11878c-.52687.01301-1.04723-.1431-1.48304-.44231l.35775-.57891c.33173.24717.72851.37076,1.1383.35775.50736,0,.78055-.14961.78055-.41629,0-.19514-.19514-.29921-.60492-.35775l-.35775-.05204c-.76754-.11058-1.18383-.45532-1.18383-1.01471,0-.68298.55939-1.09927,1.43751-1.09927.48784-.01951.97568.11058,1.39848.35775l-.32523.59842ZM30.42754,39.56549h-1.21635v1.54808c0,.35775.12359.5724.49435.5724.23416-.0065.46182-.07155.66346-.19514l.21465.63745c-.2797.17562-.60492.26669-.93015.26669-.88462,0-1.19033-.47483-1.19033-1.26839v-1.56109h-.69599v-.68298h.69599v-1.04073h.75453v1.04073h1.21635v.68298h-.00651ZM33.00984,38.79795c.18213,0,.35775.03252.53337.09106l-.22766.7155c-.1496-.05854-.30571-.09106-.46833-.08456-.48784,0-.7155.31872-.7155.88462v1.91884h-.74802v-3.4279h.74152v.41629c.18863-.31872.53337-.51386.90413-.50085l-.01951-.01301ZM38.17446,41.80956c.04553,0,.09106.00651.1366.02602.03903.01951.07805.03903.11057.07155.03252.03252.05854.06505.07806.11058.03903.08456.03903.18213,0,.26669-.01951.03903-.04553.07806-.07806.11058-.03252.03252-.07155.05204-.11057.07155-.04553.01951-.09106.02602-.1366.02602-.1366,0-.26669-.08456-.32523-.20815-.03903-.08456-.03903-.18213,0-.26669.01951-.03903.04553-.07805.07805-.11058.03252-.03252.07155-.05204.11058-.07155.03903-.01301.07805-.02602.11708-.02602h.01951ZM38.17446,42.41448c.03903,0,.07155-.00651.10407-.01951s.05854-.03252.08456-.05854c.10408-.104
<!-- @formatter:on -->
</div>
<button type="submit"{{if not .Enabled }} disabled{{ end }}>{{( pgettext "Book" "action" )}} <span>→</span>
</button>
</footer>
{{- end }}
{{- end }}