Compare commits

..

No commits in common. "a0b7672e9eda3ac3ae03f416e2e51b2c4e921a0c" and "508de011160cc311fa33d6e2ee176152310fb1cc" have entirely different histories.

10 changed files with 74 additions and 109 deletions

View file

@ -25,18 +25,19 @@ dev: ## Build and run the dev container with live reload (Air)
$(DOCKER_RUN_ENV) $(NAME):dev $(DOCKER_RUN_ENV) $(NAME):dev
test: ## Run Go tests inside the running dev container test: ## Run Go tests inside the running dev container
go test ./... docker exec $(NAME) go test ./...
up-deps: ## Update Go dependencies on host up-deps: ## Update Go dependencies on host
go get -u ./... go get -u ./...
format: ## Generate templ files and format code locally format: ## Generate templ files and format code (dev container must be running)
templ generate internal/view docker exec $(NAME) templ generate internal/view
templ fmt . docker exec $(NAME) templ fmt .
go fmt ./... docker exec $(NAME) go fmt ./...
lint: ## Lint the code using golangci-lint locally lint: ## Lint the code using golangci-lint (dev container must be running)
golangci-lint run ./... docker exec $(NAME) golangci-lint run ./...
stop: ## Stop the dev container stop: ## Stop the dev container
-@docker stop $(NAME) >/dev/null 2>&1 || true -@docker stop $(NAME) >/dev/null 2>&1 || true

View file

@ -74,10 +74,9 @@ use a cloud alternative such as Railway, fly.io, _etc._
# Stripe configuration (optional until you enable automatic sync) # Stripe configuration (optional until you enable automatic sync)
APP_STRIPE_SECRET_KEY=sk_test_your_key APP_STRIPE_SECRET_KEY=sk_test_your_key
APP_STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret APP_STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret
APP_STRIPE_ACCOUNT_ID=acct_your_account_id
``` ```
Leave the Stripe variables blank to continue using manual cash entry only. When set, Rentease will pull payments from Stripe, process webhooks sent to `/webhooks/stripe`, and expose a manual sync endpoint at `POST /api/stripe/sync` (protected by the existing API key middleware). Providing `APP_STRIPE_ACCOUNT_ID` also enables dashboard links for synchronized card payments. Leave the Stripe variables blank to continue using manual cash entry only. When set, Rentease will pull payments from Stripe, process webhooks sent to `/webhooks/stripe`, and expose a manual sync endpoint at `POST /api/stripe/sync` (protected by the existing API key middleware).
5. Start the application 5. Start the application

View file

@ -3,18 +3,17 @@ package config
import "os" import "os"
type Host struct { type Host struct {
Items map[string]HostItem Items map[string]HostItem
Name string Name string
Address string Address string
City string City string
ZipCode string ZipCode string
PhoneNumber string PhoneNumber string
Email string Email string
InvoicePrefix string InvoicePrefix string
PaymentMethods []PaymentMethod PaymentMethods []PaymentMethod
Platforms []Platform Platforms []Platform
CustomerSeed int CustomerSeed int
StripeAccountID string
} }
type HostItem struct { type HostItem struct {
@ -31,17 +30,16 @@ type HostItem struct {
func NewHost() *Host { func NewHost() *Host {
return &Host{ return &Host{
Name: "VillaFleurie", Name: "VillaFleurie",
Address: "4 rue Gerty Archimede", Address: "4 rue Gerty Archimede",
City: "Le Gosier", City: "Le Gosier",
ZipCode: "97190", ZipCode: "97190",
PhoneNumber: "+590 690 44 15 30", PhoneNumber: "+590 690 44 15 30",
Email: "location.villafleurie@gmail.com", Email: "location.villafleurie@gmail.com",
CustomerSeed: 239, CustomerSeed: 239,
InvoicePrefix: "VFNI", InvoicePrefix: "VFNI",
PaymentMethods: []PaymentMethod{"Card", "Cash", "Cheque", "Transfer"}, // TODO: add to DB PaymentMethods: []PaymentMethod{"Card", "Cash", "Cheque", "Transfer"}, // TODO: add to DB
Platforms: []Platform{"Booking", "AirBnb", "TripAdvisor", "Other"}, // TODO: add to DB Platforms: []Platform{"Booking", "AirBnb", "TripAdvisor", "Other"}, // TODO: add to DB
StripeAccountID: os.Getenv("APP_STRIPE_ACCOUNT_ID"),
Items: map[string]HostItem{ // TODO: move to DB Items: map[string]HostItem{ // TODO: move to DB
"T2": { "T2": {
Name: "T2", Name: "T2",

View file

@ -63,23 +63,17 @@ func handleBookingListPage(bs *booking.Service, hc *config.Host) http.HandlerFun
} }
} }
func paymentViewModelFromBookingPayment(p booking.Payment, stripeAccountID string) *view.PaymentViewModel { func paymentViewModelFromBookingPayment(p booking.Payment) *view.PaymentViewModel {
stripeStatus := "" stripeStatus := ""
if p.StripeStatus != nil { if p.StripeStatus != nil {
stripeStatus = *p.StripeStatus stripeStatus = *p.StripeStatus
} }
var stripeDashboardURL string
if string(p.PaymentMethod) == "Card" && stripeAccountID != "" && p.StripePaymentID != nil && *p.StripePaymentID != "" {
stripeDashboardURL = fmt.Sprintf("https://dashboard.stripe.com/%s/payments/%s", stripeAccountID, *p.StripePaymentID)
}
return &view.PaymentViewModel{ return &view.PaymentViewModel{
Amount: strconv.FormatFloat(p.Amount, 'f', 2, 64), Amount: strconv.FormatFloat(p.Amount, 'f', 2, 64),
PaymentMethod: string(p.PaymentMethod), PaymentMethod: string(p.PaymentMethod),
PaymentUrl: fmt.Sprintf("%s/%d", constant.RoutePayment, p.ID), PaymentUrl: fmt.Sprintf("%s/%d", constant.RoutePayment, p.ID),
StripeStatus: stripeStatus, StripeStatus: stripeStatus,
StripeDashboardURL: stripeDashboardURL,
} }
} }
@ -205,7 +199,7 @@ func handleBookingPage(bs *booking.Service, hc *config.Host) http.HandlerFunc {
} }
}), }),
Payments: u.Map(b.Payments, func(p booking.Payment) view.PaymentViewModel { Payments: u.Map(b.Payments, func(p booking.Payment) view.PaymentViewModel {
return *paymentViewModelFromBookingPayment(p, hc.StripeAccountID) return *paymentViewModelFromBookingPayment(p)
}), }),
}, },
Total: strconv.FormatFloat(u.Reduce(b.Items, func(i booking.Item, sum float64) float64 { Total: strconv.FormatFloat(u.Reduce(b.Items, func(i booking.Item, sum float64) float64 {
@ -499,7 +493,7 @@ func handleItemUpdate(bs *booking.Service) http.HandlerFunc {
} }
} }
func handlePaymentUpdate(bs *booking.Service, hc *config.Host) http.HandlerFunc { func handlePaymentUpdate(bs *booking.Service) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
@ -523,7 +517,7 @@ func handlePaymentUpdate(bs *booking.Service, hc *config.Host) http.HandlerFunc
p := bs.UpdatePayment(id, amount, r.FormValue("paymentMethod")) p := bs.UpdatePayment(id, amount, r.FormValue("paymentMethod"))
if err := renderTempl(w, http.StatusOK, view.PaymentLine(paymentViewModelFromBookingPayment(*p, hc.StripeAccountID))); err != nil { if err := renderTempl(w, http.StatusOK, view.PaymentLine(paymentViewModelFromBookingPayment(*p))); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
} }
} }

View file

@ -7,12 +7,11 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
u "github.com/rjNemo/underscore" u "github.com/rjNemo/underscore"
"github.com/rjNemo/rentease/internal/config"
"github.com/rjNemo/rentease/internal/service/booking" "github.com/rjNemo/rentease/internal/service/booking"
"github.com/rjNemo/rentease/internal/view" "github.com/rjNemo/rentease/internal/view"
) )
func handleCreatePayment(bs *booking.Service, hc *config.Host) http.HandlerFunc { func handleCreatePayment(bs *booking.Service) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
@ -45,7 +44,7 @@ func handleCreatePayment(bs *booking.Service, hc *config.Host) http.HandlerFunc
component := view.PaymentList( component := view.PaymentList(
u.Map(nb.Payments, func(p booking.Payment) *view.PaymentViewModel { u.Map(nb.Payments, func(p booking.Payment) *view.PaymentViewModel {
return paymentViewModelFromBookingPayment(p, hc.StripeAccountID) return paymentViewModelFromBookingPayment(p)
}), }),
) )
@ -55,7 +54,7 @@ func handleCreatePayment(bs *booking.Service, hc *config.Host) http.HandlerFunc
} }
} }
func handlePaymentForm(bs *booking.Service, hc *config.Host) http.HandlerFunc { func handlePaymentForm(bs *booking.Service) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(chi.URLParam(r, "id")) id, err := strconv.Atoi(chi.URLParam(r, "id"))
if err != nil { if err != nil {
@ -64,7 +63,7 @@ func handlePaymentForm(bs *booking.Service, hc *config.Host) http.HandlerFunc {
} }
p := bs.OnePayment(id) p := bs.OnePayment(id)
form := view.PaymentForm(paymentViewModelFromBookingPayment(*p, hc.StripeAccountID)) form := view.PaymentForm(paymentViewModelFromBookingPayment(*p))
if err := renderTempl(w, http.StatusOK, form); err != nil { if err := renderTempl(w, http.StatusOK, form); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
} }

View file

@ -43,9 +43,9 @@ func (s *Server) MountHandlers() {
r.Get("/reports/do", handleReportCompute(s.bs, s.hc)) r.Get("/reports/do", handleReportCompute(s.bs, s.hc))
r.Get("/reports/pdf", handlePdfCreateReport(s.bs)) r.Get("/reports/pdf", handlePdfCreateReport(s.bs))
r.Post("/payments/{id}", handleCreatePayment(s.bs, s.hc)) r.Post("/payments/{id}", handleCreatePayment(s.bs))
r.Put("/payments/{id}", handlePaymentUpdate(s.bs, s.hc)) r.Put("/payments/{id}", handlePaymentUpdate(s.bs))
r.Get("/payments/{id}", handlePaymentForm(s.bs, s.hc)) r.Get("/payments/{id}", handlePaymentForm(s.bs))
}) })
} }

View file

@ -10,17 +10,11 @@ func (bs Service) ParseFromAPI(rawContent string) (*Booking, error) {
return nil, err return nil, err
} }
items := b.Items itm := b.Items[0]
b = bs.Create(b.From, b.To, b.Name, b.PhoneNumber, b.Email, string(b.Platform), b.CustomerNumber, b.PlatformFees, b.ExternalID) b = bs.Create(b.From, b.To, b.Name, b.PhoneNumber, b.Email, string(b.Platform), b.CustomerNumber, b.PlatformFees, b.ExternalID)
hostItems := config.NewHost().Items if item, ok := config.NewHost().Items[itm.Item]; ok {
for _, itm := range items { bs.CreateItem(b.ID, item, itm.Quantity, itm.Price, itm.PaymentMethod, b.CustomerNumber, string(b.Platform))
hostItem, ok := hostItems[itm.Item]
if !ok {
continue
}
bs.CreateItem(b.ID, hostItem, itm.Quantity, itm.Price, itm.PaymentMethod, b.CustomerNumber, string(b.Platform))
} }
return b, nil return b, nil

View file

@ -30,9 +30,8 @@ type BookingViewModel struct {
} }
type PaymentViewModel struct { type PaymentViewModel struct {
Amount string Amount string
PaymentMethod string PaymentMethod string
PaymentUrl string PaymentUrl string
StripeStatus string StripeStatus string
StripeDashboardURL string
} }

View file

@ -7,16 +7,7 @@ templ PaymentLine(payment *PaymentViewModel) {
<td>- { payment.Amount }</td> <td>- { payment.Amount }</td>
<td> <td>
{ payment.PaymentMethod } { payment.PaymentMethod }
if payment.StripeDashboardURL != "" { <span class="badge badge-outline badge-sm ml-2">{ payment.StripeStatus }</span>
<a
href={ payment.StripeDashboardURL }
target="_blank"
rel="noreferrer noopener"
class="badge badge-outline badge-sm ml-2"
>
View in Stripe
</a>
}
</td> </td>
<td></td> <td></td>
<td class="flex gap-2"> <td class="flex gap-2">

View file

@ -55,43 +55,33 @@ func PaymentLine(payment *PaymentViewModel) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " <span class=\"badge badge-outline badge-sm ml-2\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if payment.StripeDashboardURL != "" { var templ_7745c5c3_Var4 string
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<a href=\"") templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(payment.StripeStatus)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/payment.templ`, Line: 10, Col: 73}
}
var templ_7745c5c3_Var4 templ.SafeURL
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(payment.StripeDashboardURL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/payment.templ`, Line: 12, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"badge badge-outline badge-sm ml-2\">View in Stripe</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</td><td></td><td class=\"flex gap-2\"><button class=\"btn btn-sm btn-outline\" hx-get=\"") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</span></td><td></td><td class=\"flex gap-2\"><button class=\"btn btn-sm btn-outline\" hx-get=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var5 string var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(payment.PaymentUrl) templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(payment.PaymentUrl)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/payment.templ`, Line: 25, Col: 31} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/payment.templ`, Line: 16, Col: 31}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" hx-target=\"closest tr\" hx-swap=\"outerHTML\">Edit</button></td></tr>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" hx-target=\"closest tr\" hx-swap=\"outerHTML\">Edit</button></td></tr>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -120,46 +110,46 @@ func PaymentForm(payment *PaymentViewModel) templ.Component {
templ_7745c5c3_Var6 = templ.NopComponent templ_7745c5c3_Var6 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<tr class=\"hover\"><form hx-put=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<tr class=\"hover\"><form hx-put=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var7 string var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(payment.PaymentUrl) templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(payment.PaymentUrl)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/payment.templ`, Line: 37, Col: 35} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/payment.templ`, Line: 28, Col: 35}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" id=\"edit-payment\" hx-target=\"closest tr\" hx-swap=\"outerHTML\"><td></td><td></td><td><input class=\"input input-bordered input-sm w-full\" type=\"number\" inputmode=\"decimal\" step=\"0.01\" value=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" id=\"edit-payment\" hx-target=\"closest tr\" hx-swap=\"outerHTML\"><td></td><td></td><td><input class=\"input input-bordered input-sm w-full\" type=\"number\" inputmode=\"decimal\" step=\"0.01\" value=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var8 string var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(payment.Amount) templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(payment.Amount)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/payment.templ`, Line: 46, Col: 27} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/payment.templ`, Line: 37, Col: 27}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" name=\"amount\" form=\"edit-payment\"></td><td><input class=\"input input-bordered input-sm w-full\" value=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" name=\"amount\" form=\"edit-payment\"></td><td><input class=\"input input-bordered input-sm w-full\" value=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var9 string var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(payment.PaymentMethod) templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(payment.PaymentMethod)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/payment.templ`, Line: 54, Col: 34} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/payment.templ`, Line: 45, Col: 34}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" name=\"paymentMethod\" form=\"edit-payment\"></td><td></td><td><button class=\"btn btn-sm btn-primary\" type=\"submit\" form=\"edit-payment\">Save</button></td></form></tr>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" name=\"paymentMethod\" form=\"edit-payment\"></td><td></td><td><button class=\"btn btn-sm btn-primary\" type=\"submit\" form=\"edit-payment\">Save</button></td></form></tr>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -188,7 +178,7 @@ func PaymentList(payments []*PaymentViewModel) templ.Component {
templ_7745c5c3_Var10 = templ.NopComponent templ_7745c5c3_Var10 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<tbody id=\"payment-lines\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<tbody id=\"payment-lines\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -200,7 +190,7 @@ func PaymentList(payments []*PaymentViewModel) templ.Component {
} }
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</tbody>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</tbody>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }