fix: restore test-used functions, silence tx.Rollback closed errors, prune knip dead code
CI / Frontend deps check (push) Successful in 22s
CI / Go vulnerabilities (push) Successful in 32s
CI / Go build (push) Successful in 32s
CI / go mod tidy (push) Successful in 13s
CI / Knip (push) Failing after 33s
CI / Frontend build (push) Successful in 1m12s
CI / Svelte strict check (push) Has been skipped
CI / Frontend QC (audit) (push) Has been skipped
CI / Frontend QC (typecheck) (push) Has been skipped
CI / Frontend QC (lint) (push) Has been skipped
CI / Go vet (push) Successful in 57s
CI / golangci-lint (push) Successful in 1m8s
CI / Tests (prod) (push) Successful in 1m45s
CI / Tests (dev) (push) Successful in 2m5s
CI / Race (prod) (push) Successful in 3m27s
CI / Race (dev) (push) Successful in 4m52s

Restore processImage (images.go) and nonDepositPaymentType (handlers.go) with //nolint:unused — used in test files.
Fix 97 tx.Rollback defers to silently discard expected "tx is closed" error after commit.
Frontend: remove 44 unused shadcn-svelte files, 2 dead components, 9 stale npm deps, prune unused exports.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-07-09 19:20:25 +01:00
co-authored by Sisyphus
parent 9e6c415cc4
commit 407de74b51
87 changed files with 534 additions and 4594 deletions
+4 -4
View File
@@ -49,7 +49,7 @@ func RevokeJTI(ctx context.Context, jti string, expiresAt time.Time) {
return
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -98,7 +98,7 @@ func CleanupRevokedJTIs(ctx context.Context) (int, error) {
return 0, err
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -208,7 +208,7 @@ func GenerateRefreshToken(ctx context.Context, userID string, role string) (stri
return "", fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -241,7 +241,7 @@ func VerifyRefreshToken(ctx context.Context, tokenString string) (userID string,
return "", "", fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+3 -3
View File
@@ -382,7 +382,7 @@ func UpdateCustomService(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -418,7 +418,7 @@ func PromoteCustomService(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -519,7 +519,7 @@ func DeleteCustomService(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+3 -3
View File
@@ -281,7 +281,7 @@ func CreateDiscountCampaign(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -518,7 +518,7 @@ func UpdateDiscountCampaign(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -646,7 +646,7 @@ func DeleteDiscountCampaign(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+3 -3
View File
@@ -102,7 +102,7 @@ func CreatePatchTest(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -182,7 +182,7 @@ func UpdatePatchTest(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -215,7 +215,7 @@ func DeletePatchTest(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+1 -1
View File
@@ -246,7 +246,7 @@ func UpdateBusinessSettings(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+4 -4
View File
@@ -220,7 +220,7 @@ func RegisterHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -381,7 +381,7 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -430,7 +430,7 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -652,7 +652,7 @@ func VerifyCodeHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+1 -1
View File
@@ -178,7 +178,7 @@ func AdminReserveSlotHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+8 -8
View File
@@ -1449,7 +1449,7 @@ func UpdateBookingServicesHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -2230,7 +2230,7 @@ func CreateBookingHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -2440,7 +2440,7 @@ func EditBookingHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -2598,7 +2598,7 @@ func ProgressBookingHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -3017,7 +3017,7 @@ func ConfirmBookingHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -3256,7 +3256,7 @@ func DeleteBookingHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -3347,7 +3347,7 @@ func DeleteBookingHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -4185,7 +4185,7 @@ func AdminRescheduleBookingHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+7 -7
View File
@@ -45,7 +45,7 @@ func UserCancelBookingHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -172,7 +172,7 @@ func AdminCancelBookingHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -709,7 +709,7 @@ func AdminCreateBookingForUserHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -1255,7 +1255,7 @@ func DeleteEditRequestHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -1433,7 +1433,7 @@ func RequestEditHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -1801,7 +1801,7 @@ func AdminApproveEditRequestHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -2090,7 +2090,7 @@ func AdminRejectEditRequestHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+2 -2
View File
@@ -181,7 +181,7 @@ func ReserveSlotHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -255,7 +255,7 @@ func ReserveSlotHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -244,7 +244,7 @@ func AcknowledgeNotification(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+7 -7
View File
@@ -356,7 +356,7 @@ func CreateGiftCard(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -463,7 +463,7 @@ func TopUpGiftCard(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -577,7 +577,7 @@ func TransferGiftCard(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -681,7 +681,7 @@ func RedeemGiftCard(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -816,7 +816,7 @@ func GetUserGiftCardBalanceAdmin(w http.ResponseWriter, r *http.Request) {
log.Printf("Failed to begin transaction: %v", err)
} else {
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -927,7 +927,7 @@ func BuyGiftCard(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -1203,7 +1203,7 @@ func ClaimExpiredBalance(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+24 -6
View File
@@ -351,7 +351,7 @@ func CreateTerminalPayment(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -623,7 +623,7 @@ func GetCheckoutStatus(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -822,7 +822,7 @@ func CreateBookingPayment(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -1418,6 +1418,24 @@ func buildSplitRecords(primary PaymentRecord, reqPaymentType string, info *Booki
return records
}
// nonDepositPaymentType picks the right label for the non-deposit portion of a
// split payment, following the same rules as the frontend's handlePayFull:
// 'balance' when some payment already exists, 'full' when covering everything,
// 'partial' when leaving a remainder.
//nolint:unused
func nonDepositPaymentType(reqType string, totalPaidAfterThis float64, thisPortion float64, bookingTotal float64) string {
if totalPaidAfterThis >= bookingTotal {
if totalPaidAfterThis-thisPortion > 0 {
return "balance"
}
return "full"
}
if reqType == "full" || reqType == "deposit" {
return "partial"
}
return "partial"
}
func GetUserPaymentMethods(w http.ResponseWriter, r *http.Request) {
userID, ok := r.Context().Value(mw.UserIDKey).(string)
if !ok || userID == "" {
@@ -1601,7 +1619,7 @@ func RefundPayment(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -1947,7 +1965,7 @@ func AcquirePaymentLock(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -1998,7 +2016,7 @@ func ReleasePaymentLock(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+1 -1
View File
@@ -98,7 +98,7 @@ func ApplyLoyaltyRedemption(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+1 -1
View File
@@ -293,7 +293,7 @@ func ProcessCancellationRefund(
return &calc, nil
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+1 -1
View File
@@ -470,7 +470,7 @@ func (s *PaymentService) DeletePaymentMethod(ctx context.Context, cardID, userID
return err
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+2 -2
View File
@@ -117,7 +117,7 @@ func CreateTillSale(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -506,7 +506,7 @@ func GetTillCheckoutStatus(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+23 -2
View File
@@ -23,6 +23,7 @@ import (
"time"
"github.com/go-chi/chi/v5"
"github.com/kovidgoyal/imaging"
)
const MaxInputLength = 256
@@ -42,6 +43,26 @@ func mimeTypeForField(fieldName string) string {
}
}
// processImage strips metadata and auto-orients the image
//nolint:unused
func processImage(data []byte, quality int) ([]byte, error) {
// Decode the image - this automatically applies EXIF orientation
// and strips all metadata (EXIF, GPS, etc.)
img, err := imaging.Decode(bytes.NewReader(data), imaging.AutoOrientation(true))
if err != nil {
return nil, fmt.Errorf("failed to decode image: %w", err)
}
// Encode to JPEG without any metadata
var buf bytes.Buffer
err = imaging.Encode(&buf, img, imaging.JPEG, imaging.JPEGQuality(quality))
if err != nil {
return nil, fmt.Errorf("failed to encode image: %w", err)
}
return buf.Bytes(), nil
}
// validateInputLength returns an error if input exceeds max length
func validateInputLength(input string) error {
@@ -867,7 +888,7 @@ func UploadImage(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -1000,7 +1021,7 @@ func DeleteImage(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+1 -1
View File
@@ -95,7 +95,7 @@ func UpdateDefaultHours(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -185,7 +185,7 @@ func CreateExceptionalGroup(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -262,7 +262,7 @@ func DeleteExceptionalGroup(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -330,7 +330,7 @@ func UpdateExceptionalApplications(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -17,7 +17,7 @@ func NotifyUnpaidOneWeek(ctx context.Context) (int, error) {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -86,7 +86,7 @@ func NotifyUnpaidOneMonth(ctx context.Context) (int, error) {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -155,7 +155,7 @@ func TransitionDiscountCampaigns(ctx context.Context) (int, error) {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -201,7 +201,7 @@ func CleanupExpiredVerificationCodes(ctx context.Context) (int, error) {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -230,7 +230,7 @@ func CleanupExpiredRefreshTokens(ctx context.Context) (int, error) {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+11 -11
View File
@@ -152,7 +152,7 @@ func CreateTimeBlocker(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -197,7 +197,7 @@ func DeleteTimeBlocker(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -379,7 +379,7 @@ func CleanupOldReservations(ctx context.Context) (int, error) {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -410,7 +410,7 @@ func AnonymizeStaleGuestAccounts(ctx context.Context) (int, error) {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -506,7 +506,7 @@ func CleanupExpiredLoyaltyRedemptions(ctx context.Context) (int, error) {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -554,7 +554,7 @@ func CleanupExpiredFinancialRecords(ctx context.Context) (int, error) {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -670,7 +670,7 @@ func CleanupExpiredDeposits(ctx context.Context) (int, error) {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -802,7 +802,7 @@ func CleanupExpiredGiftCards(ctx context.Context) (int, error) {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -904,7 +904,7 @@ func CleanupIdleAccounts(ctx context.Context) (int, error) {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -1013,7 +1013,7 @@ func CleanupOldIdempotencyKeys(ctx context.Context) (int, error) {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -1068,7 +1068,7 @@ func CleanupOldNameHistory(ctx context.Context) (int, error) {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+3 -3
View File
@@ -66,7 +66,7 @@ func ToggleService(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -143,7 +143,7 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -237,7 +237,7 @@ func DeleteServiceHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+2 -2
View File
@@ -103,7 +103,7 @@ func GetCurrentAndNextHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -713,7 +713,7 @@ func GetTodayAppointmentsHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+2 -2
View File
@@ -105,7 +105,7 @@ func DeleteAccountHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -130,7 +130,7 @@ func DeleteAccountHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(ctx); err != nil {
if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+1 -1
View File
@@ -113,7 +113,7 @@ func CreateGuestUserHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+18 -18
View File
@@ -318,9 +318,9 @@ func UpdateProfileHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
slog.Error("failed to rollback transaction", "err", err)
}
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
// If first or last name changed (via user edit), track the old names in history
@@ -725,9 +725,9 @@ func ChangePasswordHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
slog.Error("failed to rollback transaction", "err", err)
}
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
_, err = tx.Exec(r.Context(), `UPDATE users SET password_hash = $1, updated_at = NOW() WHERE id = $2`, string(newHash), userID)
@@ -863,9 +863,9 @@ func AddPatchTestHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
slog.Error("failed to rollback transaction", "err", err)
}
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
_, err = tx.Exec(r.Context(), `
@@ -958,9 +958,9 @@ func DeletePatchTestHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
slog.Error("failed to rollback transaction", "err", err)
}
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
result, err := tx.Exec(r.Context(), `
@@ -1064,9 +1064,9 @@ func UploadProfilePictureHandler(w http.ResponseWriter, r *http.Request) {
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
slog.Error("failed to rollback transaction", "err", err)
}
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
_, err = tx.Exec(r.Context(), `UPDATE users SET profile_pic_url = $1 WHERE id = $2`, url, userID)
@@ -1185,9 +1185,9 @@ func UpdateNotificationPreferencesHandler(w http.ResponseWriter, r *http.Request
return
}
defer func() {
if err := tx.Rollback(r.Context()); err != nil {
slog.Error("failed to rollback transaction", "err", err)
}
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
if exists {
+6 -6
View File
@@ -183,7 +183,7 @@ func (s *BaseService) CreateContact(addressBookID int, userID string, input Cont
return err
}
defer func() {
if err := tx.Rollback(context.Background()); err != nil {
if err := tx.Rollback(context.Background()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -217,7 +217,7 @@ func (s *BaseService) UpdateContact(addressBookID int, uri string, input Contact
return err
}
defer func() {
if err := tx.Rollback(context.Background()); err != nil {
if err := tx.Rollback(context.Background()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -242,7 +242,7 @@ func (s *BaseService) DeleteContact(addressBookID int, uri string) error {
return err
}
defer func() {
if err := tx.Rollback(context.Background()); err != nil {
if err := tx.Rollback(context.Background()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -267,7 +267,7 @@ func (s *BaseService) CreateEvent(calendarID int, input EventInput) error {
return err
}
defer func() {
if err := tx.Rollback(context.Background()); err != nil {
if err := tx.Rollback(context.Background()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -306,7 +306,7 @@ func (s *BaseService) UpdateEvent(calendarID int, uid string, input EventInput)
return err
}
defer func() {
if err := tx.Rollback(context.Background()); err != nil {
if err := tx.Rollback(context.Background()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
@@ -336,7 +336,7 @@ func (s *BaseService) DeleteEvent(calendarID int, uid string) error {
return err
}
defer func() {
if err := tx.Rollback(context.Background()); err != nil {
if err := tx.Rollback(context.Background()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
+28 -1577
View File
File diff suppressed because it is too large Load Diff
+1 -10
View File
@@ -21,7 +21,6 @@
"@eslint/js": "^9.22.0",
"@internationalized/date": "^3.11.0",
"@lucide/svelte": "^1.17.0",
"@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/adapter-static": "^3.0.9",
"@sveltejs/kit": "^2.22.0",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
@@ -33,29 +32,23 @@
"eslint": "^9.22.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^3.0.0",
"formsnap": "^2.0.1",
"globals": "^17.3.0",
"jsdom": "^29.1.1",
"mode-watcher": "^1.1.0",
"prettier": "^3.8.1",
"prettier-plugin-svelte": "^3.5.0",
"prettier-plugin-tailwindcss": "^0.7.2",
"runed": "^0.37.1",
"shadcn-svelte": "^1.1.1",
"svelte": "^5.0.0",
"svelte-check": "^4.4.3",
"svelte-easy-crop": "^5.0.0",
"svelte-sonner": "^1.0.7",
"svelte-toolbelt": "^0.10.6",
"sveltekit-superforms": "^2.27.1",
"tailwind-merge": "^3.5.0",
"tailwind-variants": "^3.2.2",
"tailwindcss": "^4.2.0",
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.20.0",
"vite": "^7.0.4",
"vitest": "^4.1.9"
"vite": "^7.0.4"
},
"dependencies": {
"@discourse/jxl": "^1.3.0",
@@ -65,8 +58,6 @@
"@zxcvbn-ts/core": "^3.0.4",
"@zxcvbn-ts/language-common": "^3.0.4",
"@zxcvbn-ts/language-en": "^3.0.2",
"cropperjs": "^1.6.2",
"esbuild": "^0.28.1",
"heic2any": "^0.0.4",
"maplibre-gl": "^5.24.0"
}
@@ -1,70 +0,0 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button/index.js';
import * as Card from '$lib/components/ui/card/index.js';
import Calendar from '$lib/components/ui/calendar/calendar.svelte';
import { CalendarDate, getLocalTimeZone } from '@internationalized/date';
let value = $state<CalendarDate | undefined>(new CalendarDate(2025, 6, 12));
let selectedTime = $state<string | null>('10:00');
const bookedDates = Array.from({ length: 3 }, (_, i) => new CalendarDate(2025, 6, 17 + i));
const timeSlots = Array.from({ length: 37 }, (_, i) => {
const totalMinutes = i * 15;
const hour = Math.floor(totalMinutes / 60) + 9;
const minute = totalMinutes % 60;
return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
});
</script>
<Card.Root class="gap-0 p-0">
<Card.Content class="relative p-0 md:pr-48">
<div class="p-6">
<Calendar
type="single"
bind:value
isDateUnavailable={(date) => bookedDates.some((d) => d.compare(date) === 0)}
class="bg-transparent p-0 [--cell-size:--spacing(10)] data-unavailable:line-through data-unavailable:opacity-100 md:[--cell-size:--spacing(12)] [&_[data-outside-month]]:hidden"
weekdayFormat="short"
/>
</div>
<div
class="inset-y-0 right-0 no-scrollbar flex max-h-72 w-full scroll-pb-6 flex-col gap-4 overflow-y-auto border-t p-6 md:absolute md:max-h-none md:w-48 md:border-t-0 md:border-l"
>
<div class="grid gap-2">
{#each timeSlots as time (time)}
<Button
variant={selectedTime === time ? 'default' : 'outline'}
onclick={() => (selectedTime = time)}
class="w-full"
>
{time}
</Button>
{/each}
</div>
</div>
</Card.Content>
<Card.Footer class="flex flex-col gap-4 border-t px-6 !py-5 md:flex-row">
<div class="text-sm">
{#if value && selectedTime}
Your meeting is booked for
<span class="font-medium">
{value.toDate(getLocalTimeZone()).toLocaleDateString('en-US', {
weekday: 'long',
day: 'numeric',
month: 'short'
})}
</span>
at <span class="font-medium">{selectedTime}</span>.
{:else}
Select a date and time for your meeting.
{/if}
</div>
<Button
disabled={!value || !selectedTime}
class="w-full md:ml-auto md:w-auto"
variant="outline"
>
Continue
</Button>
</Card.Footer>
</Card.Root>
File diff suppressed because it is too large Load Diff
@@ -1,17 +0,0 @@
<script lang="ts">
import { Avatar as AvatarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: AvatarPrimitive.FallbackProps = $props();
</script>
<AvatarPrimitive.Fallback
bind:ref
data-slot="avatar-fallback"
class={cn('flex size-full items-center justify-center rounded-full bg-muted', className)}
{...restProps}
/>
@@ -1,17 +0,0 @@
<script lang="ts">
import { Avatar as AvatarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: AvatarPrimitive.ImageProps = $props();
</script>
<AvatarPrimitive.Image
bind:ref
data-slot="avatar-image"
class={cn('aspect-square size-full', className)}
{...restProps}
/>
@@ -1,17 +0,0 @@
<script lang="ts">
import { Avatar as AvatarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: AvatarPrimitive.RootProps = $props();
</script>
<AvatarPrimitive.Root
bind:ref
data-slot="avatar"
class={cn('relative flex size-8 shrink-0 overflow-hidden rounded-full', className)}
{...restProps}
/>
@@ -1,13 +0,0 @@
import Root from './avatar.svelte';
import Image from './avatar-image.svelte';
import Fallback from './avatar-fallback.svelte';
export {
Root,
Image,
Fallback,
//
Root as Avatar,
Image as AvatarImage,
Fallback as AvatarFallback
};
@@ -1,8 +0,0 @@
<script lang="ts">
import * as Button from '$lib/components/ui/button/index.js';
const { ...restProps }: Record<string, unknown> = $props();
let ref = $state<HTMLElement | null>(null);
</script>
<Button.Root bind:ref type="submit" {...restProps} />
@@ -1,17 +0,0 @@
<script lang="ts">
import * as FormPrimitive from 'formsnap';
import { cn, type WithoutChild } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: WithoutChild<FormPrimitive.DescriptionProps> = $props();
</script>
<FormPrimitive.Description
bind:ref
data-slot="form-description"
class={cn('text-sm text-muted-foreground', className)}
{...restProps}
/>
@@ -1,24 +0,0 @@
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPathLeaves<T>">
import * as FormPrimitive from 'formsnap';
import type { FormPathLeaves } from 'sveltekit-superforms';
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef, type WithoutChildren } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
form,
name,
children: childrenProp,
...restProps
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> &
FormPrimitive.ElementFieldProps<T, U> = $props();
</script>
<FormPrimitive.ElementField {form} {name}>
{#snippet children({ constraints, errors, tainted, value })}
<div bind:this={ref} class={cn('space-y-2', className)} {...restProps}>
{@render childrenProp?.({ constraints, errors, tainted, value: value as T[U] })}
</div>
{/snippet}
</FormPrimitive.ElementField>
@@ -1,30 +0,0 @@
<script lang="ts">
import * as FormPrimitive from 'formsnap';
import { cn, type WithoutChild } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
errorClasses,
children: childrenProp,
...restProps
}: WithoutChild<FormPrimitive.FieldErrorsProps> & {
errorClasses?: string | undefined | null;
} = $props();
</script>
<FormPrimitive.FieldErrors
bind:ref
class={cn('text-sm font-medium text-destructive', className)}
{...restProps}
>
{#snippet children({ errors, errorProps })}
{#if childrenProp}
{@render childrenProp({ errors, errorProps })}
{:else}
{#each errors as error (error)}
<div {...errorProps} class={cn(errorClasses)}>{error}</div>
{/each}
{/if}
{/snippet}
</FormPrimitive.FieldErrors>
@@ -1,24 +0,0 @@
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
import * as FormPrimitive from 'formsnap';
import type { FormPath } from 'sveltekit-superforms';
import { cn, type WithElementRef, type WithoutChildren } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
form,
name,
children: childrenProp,
...restProps
}: FormPrimitive.FieldProps<T, U> &
WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> = $props();
</script>
<FormPrimitive.Field {form} {name}>
{#snippet children({ constraints, errors, tainted, value })}
<div bind:this={ref} data-slot="form-item" class={cn('space-y-2', className)} {...restProps}>
{@render childrenProp?.({ constraints, errors, tainted, value: value as T[U] })}
</div>
{/snippet}
</FormPrimitive.Field>
@@ -1,15 +0,0 @@
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
import * as FormPrimitive from 'formsnap';
import type { FormPath } from 'sveltekit-superforms';
import { cn, type WithoutChild } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
form,
name,
...restProps
}: WithoutChild<FormPrimitive.FieldsetProps<T, U>> = $props();
</script>
<FormPrimitive.Fieldset bind:ref {form} {name} class={cn('space-y-2', className)} {...restProps} />
@@ -1,24 +0,0 @@
<script lang="ts">
import * as FormPrimitive from 'formsnap';
import { Label } from '$lib/components/ui/label/index.js';
import { cn, type WithoutChild } from '$lib/utils.js';
let {
ref = $bindable(null),
children,
class: className,
...restProps
}: WithoutChild<FormPrimitive.LabelProps> = $props();
</script>
<FormPrimitive.Label {...restProps} bind:ref>
{#snippet child({ props })}
<Label
{...props}
data-slot="form-label"
class={cn('data-[fs-error]:text-destructive', className)}
>
{@render children?.()}
</Label>
{/snippet}
</FormPrimitive.Label>
@@ -1,16 +0,0 @@
<script lang="ts">
import * as FormPrimitive from 'formsnap';
import { cn, type WithoutChild } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: WithoutChild<FormPrimitive.LegendProps> = $props();
</script>
<FormPrimitive.Legend
bind:ref
class={cn('text-sm leading-none font-medium data-[fs-error]:text-destructive', className)}
{...restProps}
/>
@@ -1,33 +0,0 @@
import * as FormPrimitive from 'formsnap';
import Description from './form-description.svelte';
import Label from './form-label.svelte';
import FieldErrors from './form-field-errors.svelte';
import Field from './form-field.svelte';
import Fieldset from './form-fieldset.svelte';
import Legend from './form-legend.svelte';
import ElementField from './form-element-field.svelte';
import Button from './form-button.svelte';
const Control = FormPrimitive.Control;
export {
Field,
Control,
Label,
Button,
FieldErrors,
Description,
Fieldset,
Legend,
ElementField,
//
Field as FormField,
Control as FormControl,
Description as FormDescription,
Label as FormLabel,
FieldErrors as FormFieldErrors,
Fieldset as FormFieldset,
Legend as FormLegend,
ElementField as FormElementField,
Button as FormButton
};
@@ -1,34 +0,0 @@
<script lang="ts">
import { type ButtonElementProps, Button } from '$lib/components/ui/button';
import { useImageCropperCancel } from './image-cropper.svelte.js';
import Trash2Icon from '@lucide/svelte/icons/trash-2';
let {
ref = $bindable(null),
variant = 'outline',
size = 'sm',
onclick,
...rest
}: ButtonElementProps = $props();
const cancelState = useImageCropperCancel();
</script>
<Button
{...rest}
bind:ref
{size}
{variant}
onclick={(
e: MouseEvent & {
currentTarget: EventTarget & HTMLButtonElement;
}
) => {
onclick?.(e);
cancelState.onclick();
}}
>
<Trash2Icon />
<span>Cancel</span>
</Button>
@@ -1,19 +0,0 @@
<script lang="ts">
import { cn } from '$lib/utils.js';
import type { ImageCropperControlsProps } from './types';
let {
ref = $bindable(null),
class: className,
children,
...rest
}: ImageCropperControlsProps = $props();
</script>
<div
{...rest}
bind:this={ref}
class={cn('flex w-full place-items-center justify-center gap-2', className)}
>
{@render children?.()}
</div>
@@ -1,34 +0,0 @@
<script lang="ts">
import { type ButtonElementProps, Button } from '$lib/components/ui/button';
import { useImageCropperCrop } from './image-cropper.svelte.js';
import CropIcon from '@lucide/svelte/icons/crop';
let {
ref = $bindable(null),
variant = 'default',
size = 'sm',
onclick,
...rest
}: ButtonElementProps = $props();
const cropState = useImageCropperCrop();
</script>
<Button
{...rest}
bind:ref
{size}
{variant}
onclick={(
e: MouseEvent & {
currentTarget: EventTarget & HTMLButtonElement;
}
) => {
onclick?.(e);
cropState.onclick();
}}
>
<CropIcon />
<span>Crop</span>
</Button>
@@ -1,26 +0,0 @@
<script lang="ts">
import Cropper from 'svelte-easy-crop';
import { useImageCropperCropper } from './image-cropper.svelte.js';
import type { ImageCropperCropperProps } from './types.js';
const {
cropShape = 'round',
aspect = 1,
showGrid = false,
...rest
}: ImageCropperCropperProps = $props();
const cropperState = useImageCropperCropper();
</script>
<!-- This needs to be relative https://github.com/ValentinH/svelte-easy-crop#basic-usage -->
<div class="relative h-full w-full">
<Cropper
{...rest}
{cropShape}
{aspect}
{showGrid}
image={cropperState.rootState.tempUrl}
oncropcomplete={cropperState.onCropComplete}
/>
</div>
@@ -1,25 +0,0 @@
<script lang="ts">
import * as Dialog from '$lib/components/ui/dialog';
import { cn } from '$lib/utils.js';
import { useImageCropperDialog } from './image-cropper.svelte.js';
import type { ImageCropperDialogProps } from './types';
const { children, class: className, ...rest }: ImageCropperDialogProps = $props();
const dialogState = useImageCropperDialog();
</script>
<Dialog.Root bind:open={dialogState.rootState.open}>
<Dialog.Content
{...rest}
hideClose
class={cn(
'min-h-96 max-w-full rounded-none border-x-0 sm:max-w-lg sm:rounded-lg sm:border-x',
className
)}
>
<div class="flex flex-col gap-4">
{@render children?.()}
</div>
</Dialog.Content>
</Dialog.Root>
@@ -1,25 +0,0 @@
<script lang="ts">
import * as Avatar from '$lib/components/ui/avatar';
import type { ImageCropperPreviewProps } from './types';
import { useImageCropperPreview } from './image-cropper.svelte.js';
import UploadIcon from '@lucide/svelte/icons/upload';
import { cn } from '$lib/utils.js';
const { child, class: className }: ImageCropperPreviewProps = $props();
const previewState = useImageCropperPreview();
</script>
{#if child}
{@render child({ src: previewState.rootState.src })}
{:else}
<Avatar.Root
class={cn('size-20 ring-2 ring-accent ring-offset-2 ring-offset-background', className)}
>
<Avatar.Image src={previewState.rootState.src} />
<Avatar.Fallback>
<UploadIcon class="size-4" />
<span class="sr-only">Upload image</span>
</Avatar.Fallback>
</Avatar.Root>
{/if}
@@ -1,12 +0,0 @@
<script lang="ts">
import { useImageCropperTrigger } from './image-cropper.svelte.js';
import type { ImageCropperUploadTriggerProps } from './types';
let { ref = $bindable(null), children, ...rest }: ImageCropperUploadTriggerProps = $props();
const triggerState = useImageCropperTrigger();
</script>
<label {...rest} bind:this={ref} for={triggerState.rootState.id} class="hover:cursor-pointer">
{@render children?.()}
</label>
@@ -1,43 +0,0 @@
<script lang="ts">
import { box } from 'svelte-toolbelt';
import { useImageCropperRoot } from './image-cropper.svelte.js';
import type { ImageCropperRootProps } from './types';
import { onDestroy } from 'svelte';
import { useId } from 'bits-ui';
let {
id = useId(),
src = $bindable(''),
onCropped = () => {},
onUnsupportedFile = () => {},
children,
...rest
}: ImageCropperRootProps = $props();
const rootState = useImageCropperRoot({
id: box.with(() => id),
src: box.with(
() => src,
(v) => (src = v)
),
onCropped: box.with(() => onCropped),
onUnsupportedFile: box.with(() => onUnsupportedFile)
});
onDestroy(() => rootState.dispose());
</script>
{@render children?.()}
<input
{...rest}
onchange={(e) => {
const file = e.currentTarget.files?.[0];
if (!file) return;
rootState.onUpload(file);
// reset so that we can reupload the same file
(e.target! as HTMLInputElement).value = '';
}}
type="file"
{id}
style="display: none;"
/>
@@ -1,167 +0,0 @@
import type { ReadableBoxedValues, WritableBoxedValues } from 'svelte-toolbelt';
import { Context } from 'runed';
import type { CropArea, DispatchEvents } from 'svelte-easy-crop';
import { getCroppedImg } from './utils';
// https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img#supported_image_formats
export const VALID_IMAGE_TYPES = [
'image/apng',
'image/avif',
'image/gif',
'image/jpeg',
'image/png',
'image/svg+xml',
'image/webp'
];
export type ImageCropperRootProps = WritableBoxedValues<{
src: string;
}> &
ReadableBoxedValues<{
id: string;
onCropped: (url: string) => void;
onUnsupportedFile: (file: File) => void;
}>;
class ImageCropperRootState {
#createdUrls = $state<string[]>([]);
open = $state(false);
tempUrl = $state<string>();
pixelCrop = $state<CropArea>();
constructor(readonly opts: ImageCropperRootProps) {
this.onUpload = this.onUpload.bind(this);
this.onCancel = this.onCancel.bind(this);
this.onCrop = this.onCrop.bind(this);
this.dispose = this.dispose.bind(this);
}
onUpload(file: File) {
if (!VALID_IMAGE_TYPES.includes(file.type)) {
this.opts.onUnsupportedFile.current(file);
return;
}
this.tempUrl = URL.createObjectURL(file);
this.#createdUrls.push(this.tempUrl);
this.open = true;
}
onCancel() {
this.tempUrl = undefined;
this.open = false;
this.pixelCrop = undefined;
}
async onCrop() {
if (!this.pixelCrop || !this.tempUrl) return;
this.opts.src.current = await getCroppedImg(this.tempUrl, this.pixelCrop);
this.open = false;
this.opts.onCropped.current(this.opts.src.current);
}
get src() {
return this.opts.src.current;
}
get id() {
return this.opts.id.current;
}
dispose() {
for (const url of this.#createdUrls) {
URL.revokeObjectURL(url);
}
}
}
export type ImageCropperTriggerProps = ReadableBoxedValues<{
id?: string;
}>;
class ImageCropperTriggerState {
constructor(readonly rootState: ImageCropperRootState) {}
}
class ImageCropperPreviewState {
constructor(readonly rootState: ImageCropperRootState) {}
}
class ImageCropperDialogState {
constructor(readonly rootState: ImageCropperRootState) {}
}
class ImageCropperCropperState {
constructor(readonly rootState: ImageCropperRootState) {
this.onCropComplete = this.onCropComplete.bind(this);
}
onCropComplete(e: DispatchEvents['cropcomplete']) {
this.rootState.pixelCrop = e.pixels;
}
}
class ImageCropperCropState {
constructor(readonly rootState: ImageCropperRootState) {
this.onclick = this.onclick.bind(this);
}
onclick() {
this.rootState.onCrop();
}
}
class ImageCropperCancelState {
constructor(readonly rootState: ImageCropperRootState) {
this.onclick = this.onclick.bind(this);
}
onclick() {
this.rootState.onCancel();
}
}
const ImageCropperRootContext = new Context<ImageCropperRootState>('ImageCropper.Root');
export const useImageCropperRoot = (props: ImageCropperRootProps) => {
return ImageCropperRootContext.set(new ImageCropperRootState(props));
};
export const useImageCropperTrigger = () => {
const rootState = ImageCropperRootContext.get();
return new ImageCropperTriggerState(rootState);
};
export const useImageCropperPreview = () => {
const rootState = ImageCropperRootContext.get();
return new ImageCropperPreviewState(rootState);
};
export const useImageCropperDialog = () => {
const rootState = ImageCropperRootContext.get();
return new ImageCropperDialogState(rootState);
};
export const useImageCropperCropper = () => {
const rootState = ImageCropperRootContext.get();
return new ImageCropperCropperState(rootState);
};
export const useImageCropperCrop = () => {
const rootState = ImageCropperRootContext.get();
return new ImageCropperCropState(rootState);
};
export const useImageCropperCancel = () => {
const rootState = ImageCropperRootContext.get();
return new ImageCropperCancelState(rootState);
};
@@ -1,13 +0,0 @@
import Root from './image-cropper.svelte';
import UploadTrigger from './image-cropper-upload-trigger.svelte';
import Preview from './image-cropper-preview.svelte';
import Dialog from './image-cropper-dialog.svelte';
import Cropper from './image-cropper-cropper.svelte';
import Controls from './image-cropper-controls.svelte';
import Crop from './image-cropper-crop.svelte';
import Cancel from './image-cropper-cancel.svelte';
import { getFileFromUrl } from './utils';
export { Root, UploadTrigger, Preview, Dialog, Cropper, Controls, Crop, Cancel, getFileFromUrl };
export type * from './types';
@@ -1,44 +0,0 @@
import type {
AvatarRootProps,
DialogContentProps,
WithChildren,
WithoutChild,
WithoutChildren
} from 'bits-ui';
import type { Snippet } from 'svelte';
import type { CropperProps } from 'svelte-easy-crop';
import type { HTMLAttributes, HTMLInputAttributes } from 'svelte/elements';
export type ImageCropperRootPropsWithoutHTML = WithChildren<{
id?: string;
src?: string;
onCropped?: (url: string) => void;
onUnsupportedFile?: (file: File) => void;
}>;
export type ImageCropperRootProps = ImageCropperRootPropsWithoutHTML & HTMLInputAttributes;
export type ImageCropperDialogProps = DialogContentProps;
export type ImageCropperCropperProps = Omit<Partial<CropperProps>, 'oncropcomplete' | 'image'>;
export type ImageCropperControlsWithoutHTML = WithChildren<{
ref?: HTMLDivElement | null;
}>;
export type ImageCropperControlsProps = ImageCropperControlsWithoutHTML &
WithoutChildren<HTMLAttributes<HTMLDivElement>>;
export type ImageCropperPreviewPropsWithoutHTML = {
child?: Snippet<[{ src: string }]>;
};
export type ImageCropperPreviewProps = ImageCropperPreviewPropsWithoutHTML &
WithoutChild<AvatarRootProps>;
export type ImageCropperUploadTriggerPropsWithoutHTML = WithChildren<{
ref?: HTMLLabelElement | null;
}>;
export type ImageCropperUploadTriggerProps = ImageCropperUploadTriggerPropsWithoutHTML &
WithoutChildren<HTMLAttributes<HTMLLabelElement>>;
@@ -1,85 +0,0 @@
import type { CropArea } from 'svelte-easy-crop';
export const getFileFromUrl = async (url: string, fileName = 'cropped.png'): Promise<File> => {
// Fetch the file data from the URL
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch resource: ${response.status} ${response.statusText}`);
}
// Convert the response into a Blob
const blob = await response.blob();
// Create and return a File. You can set a custom type if needed.
return new File([blob], fileName, { type: blob.type });
};
const createImage = (url: string): Promise<HTMLImageElement> => {
return new Promise<HTMLImageElement>((resolve, reject) => {
const image = new Image();
image.addEventListener('load', () => resolve(image));
image.addEventListener('error', (error) => reject(error));
image.setAttribute('crossOrigin', 'anonymous'); // needed to avoid cross-origin issues on CodeSandbox
image.src = url;
});
};
const getRadianAngle = (degreeValue: number) => {
return (degreeValue * Math.PI) / 180;
};
/** Gets the cropped image from the src using the cropped area
*
* @param imageSrc
* @param pixelCrop
* @param rotation
* @returns
*/
export const getCroppedImg = async (
imageSrc: string,
pixelCrop: CropArea,
rotation = 0
): Promise<string> => {
const image = await createImage(imageSrc);
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) {
throw new Error('Error getting 2d rendering context');
}
const maxSize = Math.max(image.width, image.height);
const safeArea = 2 * ((maxSize / 2) * Math.sqrt(2));
// set each dimensions to double largest dimension to allow for a safe area for the
// image to rotate in without being clipped by canvas context
canvas.width = safeArea;
canvas.height = safeArea;
// translate canvas context to a central location on image to allow rotating around the center.
ctx.translate(safeArea / 2, safeArea / 2);
ctx.rotate(getRadianAngle(rotation));
ctx.translate(-safeArea / 2, -safeArea / 2);
// draw rotated image and store data.
ctx.drawImage(image, safeArea / 2 - image.width * 0.5, safeArea / 2 - image.height * 0.5);
const data = ctx.getImageData(0, 0, safeArea, safeArea);
// set canvas width to final desired crop size - this will clear existing context
canvas.width = pixelCrop.width;
canvas.height = pixelCrop.height;
// paste generated rotate image with correct offsets for x,y crop values.
ctx.putImageData(
data,
Math.round(0 - safeArea / 2 + image.width * 0.5 - pixelCrop.x),
Math.round(0 - safeArea / 2 + image.height * 0.5 - pixelCrop.y)
);
return new Promise((resolve) => {
canvas.toBlob((file) => {
resolve(URL.createObjectURL(file!));
}, 'image/png');
});
};
@@ -3,11 +3,4 @@ export type { MapViewport } from './Map.svelte';
export { default as MapMarker } from './MapMarker.svelte';
export { default as MarkerContent } from './MarkerContent.svelte';
export { default as MarkerPopup } from './MarkerPopup.svelte';
export { default as MarkerTooltip } from './MarkerTooltip.svelte';
export { default as MarkerLabel } from './MarkerLabel.svelte';
export { default as MapControls } from './MapControls.svelte';
export { default as MapPopup } from './MapPopup.svelte';
export { default as MapRoute } from './MapRoute.svelte';
export { default as MapClusterLayer } from './MapClusterLayer.svelte';
export { default as MapArc } from './MapArc.svelte';
export type { MapArcDatum, MapArcEvent, MapArcProps } from './MapArc.svelte';
@@ -1,37 +0,0 @@
import { Select as SelectPrimitive } from 'bits-ui';
import Group from './select-group.svelte';
import Label from './select-label.svelte';
import Item from './select-item.svelte';
import Content from './select-content.svelte';
import Trigger from './select-trigger.svelte';
import Separator from './select-separator.svelte';
import ScrollDownButton from './select-scroll-down-button.svelte';
import ScrollUpButton from './select-scroll-up-button.svelte';
import GroupHeading from './select-group-heading.svelte';
const Root = SelectPrimitive.Root;
export {
Root,
Group,
Label,
Item,
Content,
Trigger,
Separator,
ScrollDownButton,
ScrollUpButton,
GroupHeading,
//
Root as Select,
Group as SelectGroup,
Label as SelectLabel,
Item as SelectItem,
Content as SelectContent,
Trigger as SelectTrigger,
Separator as SelectSeparator,
ScrollDownButton as SelectScrollDownButton,
ScrollUpButton as SelectScrollUpButton,
GroupHeading as SelectGroupHeading
};
@@ -1,40 +0,0 @@
<script lang="ts">
import { Select as SelectPrimitive } from 'bits-ui';
import SelectScrollUpButton from './select-scroll-up-button.svelte';
import SelectScrollDownButton from './select-scroll-down-button.svelte';
import { cn, type WithoutChild } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
sideOffset = 4,
portalProps,
children,
...restProps
}: WithoutChild<SelectPrimitive.ContentProps> & {
portalProps?: SelectPrimitive.PortalProps;
} = $props();
</script>
<SelectPrimitive.Portal {...portalProps}>
<SelectPrimitive.Content
bind:ref
{sideOffset}
data-slot="select-content"
class={cn(
'relative z-50 max-h-(--bits-select-content-available-height) min-w-[8rem] origin-(--bits-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:translate-y-1 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:-translate-x-1 data-[side=left]:slide-in-from-right-2 data-[side=right]:translate-x-1 data-[side=right]:slide-in-from-left-2 data-[side=top]:-translate-y-1 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
className
)}
{...restProps}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
class={cn(
'h-(--bits-select-anchor-height) w-full min-w-(--bits-select-anchor-width) scroll-my-1 p-1'
)}
>
{@render children?.()}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
@@ -1,21 +0,0 @@
<script lang="ts">
import { Select as SelectPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
import type { ComponentProps } from 'svelte';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: ComponentProps<typeof SelectPrimitive.GroupHeading> = $props();
</script>
<SelectPrimitive.GroupHeading
bind:ref
data-slot="select-group-heading"
class={cn('px-2 py-1.5 text-xs text-muted-foreground', className)}
{...restProps}
>
{@render children?.()}
</SelectPrimitive.GroupHeading>
@@ -1,7 +0,0 @@
<script lang="ts">
import { Select as SelectPrimitive } from 'bits-ui';
let { ref = $bindable(null), ...restProps }: SelectPrimitive.GroupProps = $props();
</script>
<SelectPrimitive.Group data-slot="select-group" {...restProps} />
@@ -1,38 +0,0 @@
<script lang="ts">
import CheckIcon from '@lucide/svelte/icons/check';
import { Select as SelectPrimitive } from 'bits-ui';
import { cn, type WithoutChild } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
value,
label,
children: childrenProp,
...restProps
}: WithoutChild<SelectPrimitive.ItemProps> = $props();
</script>
<SelectPrimitive.Item
bind:ref
{value}
data-slot="select-item"
class={cn(
"relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className
)}
{...restProps}
>
{#snippet children({ selected, highlighted })}
<span class="absolute right-2 flex size-3.5 items-center justify-center">
{#if selected}
<CheckIcon class="size-4" />
{/if}
</span>
{#if childrenProp}
{@render childrenProp({ selected, highlighted })}
{:else}
{label || value}
{/if}
{/snippet}
</SelectPrimitive.Item>
@@ -1,20 +0,0 @@
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {} = $props();
</script>
<div
bind:this={ref}
data-slot="select-label"
class={cn('px-2 py-1.5 text-xs text-muted-foreground', className)}
{...restProps}
>
{@render children?.()}
</div>
@@ -1,20 +0,0 @@
<script lang="ts">
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
import { Select as SelectPrimitive } from 'bits-ui';
import { cn, type WithoutChildrenOrChild } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: WithoutChildrenOrChild<SelectPrimitive.ScrollDownButtonProps> = $props();
</script>
<SelectPrimitive.ScrollDownButton
bind:ref
data-slot="select-scroll-down-button"
class={cn('flex cursor-default items-center justify-center py-1', className)}
{...restProps}
>
<ChevronDownIcon class="size-4" />
</SelectPrimitive.ScrollDownButton>
@@ -1,20 +0,0 @@
<script lang="ts">
import ChevronUpIcon from '@lucide/svelte/icons/chevron-up';
import { Select as SelectPrimitive } from 'bits-ui';
import { cn, type WithoutChildrenOrChild } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: WithoutChildrenOrChild<SelectPrimitive.ScrollUpButtonProps> = $props();
</script>
<SelectPrimitive.ScrollUpButton
bind:ref
data-slot="select-scroll-up-button"
class={cn('flex cursor-default items-center justify-center py-1', className)}
{...restProps}
>
<ChevronUpIcon class="size-4" />
</SelectPrimitive.ScrollUpButton>
@@ -1,18 +0,0 @@
<script lang="ts">
import type { Separator as SeparatorPrimitive } from 'bits-ui';
import { Separator } from '$lib/components/ui/separator/index.js';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: SeparatorPrimitive.RootProps = $props();
</script>
<Separator
bind:ref
data-slot="select-separator"
class={cn('pointer-events-none -mx-1 my-1 h-px bg-border', className)}
{...restProps}
/>
@@ -1,29 +0,0 @@
<script lang="ts">
import { Select as SelectPrimitive } from 'bits-ui';
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
import { cn, type WithoutChild } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
children,
size = 'default',
...restProps
}: WithoutChild<SelectPrimitive.TriggerProps> & {
size?: 'sm' | 'default';
} = $props();
</script>
<SelectPrimitive.Trigger
bind:ref
data-slot="select-trigger"
data-size={size}
class={cn(
"flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none select-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
className
)}
{...restProps}
>
{@render children?.()}
<ChevronDownIcon class="size-4 opacity-50" />
</SelectPrimitive.Trigger>
@@ -1,28 +0,0 @@
import Root from './table.svelte';
import Body from './table-body.svelte';
import Caption from './table-caption.svelte';
import Cell from './table-cell.svelte';
import Footer from './table-footer.svelte';
import Head from './table-head.svelte';
import Header from './table-header.svelte';
import Row from './table-row.svelte';
export {
Root,
Body,
Caption,
Cell,
Footer,
Head,
Header,
Row,
//
Root as Table,
Body as TableBody,
Caption as TableCaption,
Cell as TableCell,
Footer as TableFooter,
Head as TableHead,
Header as TableHeader,
Row as TableRow
};
@@ -1,20 +0,0 @@
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
</script>
<tbody
bind:this={ref}
data-slot="table-body"
class={cn('[&_tr:last-child]:border-0', className)}
{...restProps}
>
{@render children?.()}
</tbody>
@@ -1,20 +0,0 @@
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
</script>
<caption
bind:this={ref}
data-slot="table-caption"
class={cn('mt-4 text-sm text-muted-foreground', className)}
{...restProps}
>
{@render children?.()}
</caption>
@@ -1,23 +0,0 @@
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLTdAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLTdAttributes> = $props();
</script>
<td
bind:this={ref}
data-slot="table-cell"
class={cn(
'bg-clip-padding p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0',
className
)}
{...restProps}
>
{@render children?.()}
</td>
@@ -1,20 +0,0 @@
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
</script>
<tfoot
bind:this={ref}
data-slot="table-footer"
class={cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', className)}
{...restProps}
>
{@render children?.()}
</tfoot>
@@ -1,23 +0,0 @@
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLThAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLThAttributes> = $props();
</script>
<th
bind:this={ref}
data-slot="table-head"
class={cn(
'h-10 bg-clip-padding px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0',
className
)}
{...restProps}
>
{@render children?.()}
</th>
@@ -1,20 +0,0 @@
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
</script>
<thead
bind:this={ref}
data-slot="table-header"
class={cn('[&_tr]:border-b', className)}
{...restProps}
>
{@render children?.()}
</thead>
@@ -1,23 +0,0 @@
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLTableRowElement>> = $props();
</script>
<tr
bind:this={ref}
data-slot="table-row"
class={cn(
'border-b transition-colors data-[state=selected]:bg-muted hover:[&,&>svelte-css-wrapper]:[&>th,td]:bg-muted/50',
className
)}
{...restProps}
>
{@render children?.()}
</tr>
@@ -1,22 +0,0 @@
<script lang="ts">
import type { HTMLTableAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLTableAttributes> = $props();
</script>
<div data-slot="table-container" class="relative w-full overflow-x-auto">
<table
bind:this={ref}
data-slot="table"
class={cn('w-full caption-bottom text-sm', className)}
{...restProps}
>
{@render children?.()}
</table>
</div>
-1
View File
@@ -1 +0,0 @@
// place files you want to import through the `$lib` alias in this folder.
+26 -125
View File
@@ -111,125 +111,12 @@ export function findAllLunchGaps(
return gaps.sort((a, b) => b - a);
}
/**
* Find the largest gap in the middle window after accounting for bookings
* Returns the duration in minutes of the largest gap
*/
export function findLargestLunchGap(
middleWindowStart: number,
middleWindowEnd: number,
existingBookings: TimeSlot[],
proposedBooking?: TimeSlot
): number {
const gaps = findAllLunchGaps(
middleWindowStart,
middleWindowEnd,
existingBookings,
proposedBooking
);
return gaps.length > 0 ? gaps[0] : 0;
}
export const LUNCH_MINIMUM_USER = 60;
export const LUNCH_MINIMUM_ADMIN = 30;
export const LUNCH_WARNING_THRESHOLD = 60;
export const LUNCH_MIN_DAY_DURATION = 5 * 60; // 300 minutes — skip lunch for days ≤ 5h
/**
* Check lunch protection should apply at all for a day of this duration
*/
export function shouldApplyLunchProtection(dayStartTime: string, dayEndTime: string): boolean {
const dayDuration = timeToMinutes(dayEndTime) - timeToMinutes(dayStartTime);
return dayDuration > LUNCH_MIN_DAY_DURATION;
}
/**
* Check if a proposed booking slot violates lunch protection
*/
export function checkLunchProtection(
dayStartTime: string,
dayEndTime: string,
existingBookings: TimeSlot[],
proposedSlotStart: string,
proposedSlotEnd: string,
isAdmin: boolean
): LunchProtectionResult {
if (!shouldApplyLunchProtection(dayStartTime, dayEndTime)) {
return { isBlocked: false, showWarning: false };
}
const { windowStart, windowEnd } = calculateMiddleWindow(dayStartTime, dayEndTime);
const windowDuration = windowEnd - windowStart;
const minimumRequired = isAdmin ? LUNCH_MINIMUM_ADMIN : LUNCH_MINIMUM_USER;
if (windowDuration < minimumRequired) {
return { isBlocked: false, showWarning: false };
}
const proposedBooking: TimeSlot = {
startTime: proposedSlotStart,
endTime: proposedSlotEnd
};
const gapWithout = findLargestLunchGap(windowStart, windowEnd, existingBookings);
const gapsWith = findAllLunchGaps(windowStart, windowEnd, existingBookings, proposedBooking);
const gapWith = gapsWith[0] ?? 0;
if (isAdmin) {
if (gapWith < LUNCH_MINIMUM_ADMIN && gapWith < gapWithout) {
return {
isBlocked: true,
showWarning: false,
warningMessage: `This booking would leave no lunch break (minimum 30 minutes required).`
};
}
if (gapWith < LUNCH_WARNING_THRESHOLD && gapWith < gapWithout) {
const startH = parseInt(proposedSlotStart.split(':')[0], 10);
const startM = parseInt(proposedSlotStart.split(':')[1], 10);
const endH = parseInt(proposedSlotEnd.split(':')[0], 10);
const endM = parseInt(proposedSlotEnd.split(':')[1], 10);
const fmt = (h: number, m: number) => {
const p = h >= 12 ? 'PM' : 'AM';
return `${h % 12 || 12}:${String(m).padStart(2, '0')} ${p}`;
};
const viableGaps = gapsWith.filter((g) => g >= LUNCH_MINIMUM_ADMIN);
if (viableGaps.length >= 2 && gapsWith.every((g) => g >= LUNCH_MINIMUM_ADMIN)) {
const sorted = [...viableGaps].sort((a, b) => b - a);
if (sorted[0] === sorted[1]) {
return {
isBlocked: false,
showWarning: true,
warningMessage: `This booking (${fmt(startH, startM)}${fmt(endH, endM)}) would split your suggested lunch break into two ${sorted[0]} minute blocks.`
};
}
return {
isBlocked: false,
showWarning: true,
warningMessage: `This booking (${fmt(startH, startM)}${fmt(endH, endM)}) would split your suggested lunch break into ${sorted[0]} and ${sorted[1]} minute blocks.`
};
}
return {
isBlocked: false,
showWarning: true,
warningMessage: `This booking (${fmt(startH, startM)}${fmt(endH, endM)}) would reduce lunch break to ${gapWith} minutes.`
};
}
return { isBlocked: false, showWarning: false };
} else {
if (gapWith < LUNCH_MINIMUM_USER && gapWith < gapWithout) {
return {
isBlocked: true,
showWarning: false,
warningMessage: `This booking would leave insufficient lunch break (minimum 1 hour required).`
};
}
return { isBlocked: false, showWarning: false };
}
return dayDuration > 300;
}
/**
@@ -294,7 +181,7 @@ export function getLunchProtectionForSlots(
const { windowStart, windowEnd } = calculateMiddleWindow(dayStartTime, dayEndTime);
const windowDuration = windowEnd - windowStart;
const minimumRequired = isAdmin ? LUNCH_MINIMUM_ADMIN : LUNCH_MINIMUM_USER;
const minimumRequired = isAdmin ? 30 : 60;
if (windowDuration < minimumRequired) {
return results;
@@ -315,17 +202,31 @@ export function getLunchProtectionForSlots(
const slotStartStr = minutesToTime(slotStart);
const slotEndStr = minutesToTime(slotStart + slotDuration);
const result = checkLunchProtection(
dayStartTime,
dayEndTime,
existingBookings,
slotStartStr,
slotEndStr,
isAdmin
);
const proposedBooking: TimeSlot = {
startTime: slotStartStr,
endTime: slotEndStr
};
if (result.isBlocked || result.showWarning) {
results.set(slotStartStr, result);
const gapsWith = findAllLunchGaps(windowStart, windowEnd, existingBookings, proposedBooking);
const gapWith = gapsWith[0] ?? 0;
if (isAdmin) {
if (gapWith < 30) {
results.set(slotStartStr, {
isBlocked: true,
showWarning: false,
warningMessage: 'This booking would leave no lunch break (minimum 30 minutes required).'
});
}
} else {
if (gapWith < 60) {
results.set(slotStartStr, {
isBlocked: true,
showWarning: false,
warningMessage:
'This booking would leave insufficient lunch break (minimum 1 hour required).'
});
}
}
}
-17
View File
@@ -141,17 +141,6 @@ export interface Booking {
duration_minutes: number;
}
export interface LoyaltyRedemption {
id: string;
user_id: string;
stamps_redeemed: number;
status: 'pending' | 'applied' | 'expired';
applied_to_booking_id?: string;
redeemed_at: string;
applied_at?: string;
expires_at: string;
}
export type CampaignType = 'time_based' | 'milestone';
export type MilestoneType = 'per_user_booking_count' | 'global_booking_count' | 'anniversary';
export type MilestoneUnit = 'bookings' | 'months' | 'years';
@@ -192,9 +181,3 @@ export interface BookingDiscount {
discount_amount: number;
applied_at: string;
}
export interface CampaignStats {
campaign: DiscountCampaign;
total_discount_amount: number;
booking_count: number;
}
-38
View File
@@ -9,28 +9,6 @@ export interface UserSavedCard {
is_default: boolean;
}
export interface Payment {
id: string;
booking_id: string;
payment_type: 'deposit' | 'full' | 'tip' | 'balance' | 'partial';
payment_method: 'online_square' | 'in_person_card' | 'cash' | 'giftcard' | 'discount';
vendor_code: string | null;
invoice_number: number | null;
status: 'pending' | 'completed' | 'failed' | 'refunded';
amount: number;
is_vat_applicable: boolean;
vat_rate: number | null;
vat_amount: number | null;
net_amount: number | null;
user_saved_card_id: string | null;
square_payment_id: string | null;
idempotency_key: string | null;
fees: number;
created_at: string;
updated_at: string;
created_by: string | null;
}
export interface Refund {
id: string;
payment_id: string;
@@ -42,19 +20,3 @@ export interface Refund {
created_by: string | null;
created_at: string;
}
export interface PaymentSummary {
total_amount: number;
paid_amount: number;
refunded_amount: number;
remaining_amount: number;
total_vat_amount: number;
total_net_amount: number;
payments: Payment[];
refunds: Refund[];
}
export interface CheckoutResponse {
checkout_id: string;
status: 'PENDING' | 'COMPLETED' | 'FAILED';
}
-13
View File
@@ -11,16 +11,3 @@ export interface BusinessSettings {
gift_card_expiry_months: number;
voucher_type: 'SPV' | 'MPV';
}
export interface UpdateBusinessSettingsRequest {
business_name?: string;
business_address?: string;
business_phone?: string | null;
business_email?: string | null;
vat_registration_number?: string | null;
is_vat_registered?: boolean;
default_vat_rate?: number;
website_url?: string | null;
gift_card_expiry_months?: number;
voucher_type?: 'SPV' | 'MPV';
}
-39
View File
@@ -53,36 +53,6 @@ export function formatDateISO(d: Date, timeZone = 'Europe/London'): string {
return d.toLocaleDateString('en-CA', { timeZone });
}
/**
* Format a date value to "Weekday, Month Day" only (no time).
*
* Example: "2026-05-29" → "Friday, May 29"
*/
export function formatDate(date: Date | string): string {
const d = typeof date === 'string' ? new Date(date) : date;
return d.toLocaleDateString('en-US', {
weekday: 'long',
month: 'short',
day: 'numeric',
timeZone: 'Europe/London'
});
}
/**
* Format a time value to "HH:MM AM/PM".
*
* Example: new Date("2026-05-29T14:30:00Z") → "2:30 PM"
*/
export function formatTime(date: Date | string): string {
const d = typeof date === 'string' ? new Date(date) : date;
return d.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
hour12: true,
timeZone: 'Europe/London'
});
}
/**
* Calculate age from a date-of-birth string.
* Returns the number of full years, or null if DOB is invalid.
@@ -110,12 +80,3 @@ export function calculateAge(dateOfBirth: string | undefined | null): number | n
}
return age;
}
/**
* Format a 12-character gift card code into display format (XXXX-XXXX-XXXX).
* Example: "a3f1c9d2b4e8" → "A3F1-C9D2-B4E8"
*/
export function formatCardCode(id: string): string {
if (id.length !== 12) return id;
return `${id.slice(0, 4)}-${id.slice(4, 8)}-${id.slice(8, 12)}`.toUpperCase();
}
+9 -37
View File
@@ -42,29 +42,6 @@ export function parseWallClockDate(iso: string): Date {
return new Date(iso);
}
/**
* Formats a UTC ISO datetime string as a human-readable time (e.g. "10:00").
* Always shows the wall-clock time, NOT shifted by timezone.
*/
export function formatWallClockTime(iso: string): string {
const d = parseWallClockDate(iso);
return d.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', hour12: false });
}
/**
* Formats a UTC ISO datetime string as a human-readable date (e.g. "Monday, 15 June 2026").
* Always shows the wall-clock date, NOT shifted by timezone.
*/
export function formatWallClockDate(iso: string): string {
const d = parseWallClockDate(iso);
return d.toLocaleDateString('en-GB', {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric'
});
}
/**
* Returns a CalendarDate representing today's date in the Europe/London timezone.
* This ensures "is today" comparisons are correct even during BST (British Summer Time)
@@ -118,18 +95,6 @@ export function timeToMinutes(time: string): number {
return hours * 60 + minutes;
}
export function calculatePreviousTime(time: string): string {
const [hours, minutes] = time.split(':').map(Number);
const totalMinutes = hours * 60 + minutes - 15;
return `${String(Math.floor(totalMinutes / 60)).padStart(2, '0')}:${String(totalMinutes % 60).padStart(2, '0')}`;
}
export function normalizeTime(time: string): string {
const parts = time.split(':');
if (parts.length < 2) return time.padStart(5, '0');
return `${parts[0].padStart(2, '0')}:${parts[1].padStart(2, '0')}`;
}
export function getDayWithOrdinal(date: CalendarDate): string {
const monthName = new SvelteDate(date.year, date.month - 1, date.day).toLocaleDateString(
'en-GB',
@@ -280,7 +245,9 @@ export function generateGroupedTimeSlots(
if (isAvailable) {
if (currentUnavailableStart !== null) {
const unavailStart = lastAvailableEndTime || currentUnavailableStart;
const groupEnd = calculatePreviousTime(timeStr);
const prevParts = timeStr.split(':').map(Number);
const prevTotal = prevParts[0] * 60 + prevParts[1] - 15;
const groupEnd = `${String(Math.floor(prevTotal / 60)).padStart(2, '0')}:${String(prevTotal % 60).padStart(2, '0')}`;
if (timeToMinutes(unavailStart) < timeToMinutes(groupEnd)) {
grouped.push({
type: 'unavailable',
@@ -310,7 +277,12 @@ export function generateGroupedTimeSlots(
grouped.push({
type: 'unavailable',
startTime: lastAvail ? lastAvail.endTime : currentUnavailableStart,
endTime: normalizeTime(dayWH.endTime),
endTime: (() => {
const p = dayWH.endTime.split(':');
return p.length < 2
? p[0].padStart(5, '0')
: `${p[0].padStart(2, '0')}:${p[1].padStart(2, '0')}`;
})(),
isGrouped: true
});
}
-14
View File
@@ -12,17 +12,3 @@ export function sanitizeText(text: string): string {
};
return text.replace(/[&<>"']/g, (ch) => map[ch] || ch);
}
/**
* Creates a sanitized error message from an API Response.
* Reads the response body as text and sanitizes it.
*/
export async function apiErrorText(response: Response): Promise<string> {
try {
const text = await response.text();
if (!text) return 'An error occurred';
return sanitizeText(text.slice(0, 200)); // limit length
} catch {
return 'An error occurred';
}
}
+32
View File
@@ -0,0 +1,32 @@
declare namespace svelteNative.JSX {
// Every namespace eligible for use needs to implement the following two functions
function mapElementTag(
tag: string
): any;
function createElement<Elements extends IntrinsicElements, Key extends keyof Elements>(
element: Key | undefined | null, attrs: Elements[Key]
): any;
function createElement<Elements extends IntrinsicElements, Key extends keyof Elements, T>(
element: Key | undefined | null, attrEnhancers: T, attrs: Elements[Key] & T
): any;
/* svelte specific */
interface ElementClass {
$$prop_def: any;
}
interface ElementAttributesProperty {
$$prop_def: any; // specify the property name to use
}
// Add empty IntrinsicAttributes to prevent fallback to the one in the JSX namespace
interface IntrinsicAttributes {
}
interface IntrinsicElements {
[name: string]: { [name: string]: any };
}
}
+290
View File
@@ -0,0 +1,290 @@
// Whenever a ambient declaration changes, its number should be increased
// This way, we avoid the situation where multiple ambient versions of svelte2tsx
// are loaded and their declarations conflict each other
// See https://github.com/sveltejs/language-tools/issues/1059 for an example bug that stems from it
// If you change anything in this file, think about whether or not it should be backported to svelte-shims.d.ts
type AConstructorTypeOf<T, U extends any[] = any[]> = new (...args: U) => T;
/** @internal PRIVATE API, DO NOT USE */
type SvelteActionReturnType = {
update?: (args: any) => void,
destroy?: () => void
} | void
/** @internal PRIVATE API, DO NOT USE */
type SvelteTransitionConfig = {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
}
/** @internal PRIVATE API, DO NOT USE */
type SvelteTransitionReturnType = SvelteTransitionConfig | (() => SvelteTransitionConfig)
/** @internal PRIVATE API, DO NOT USE */
type SvelteAnimationReturnType = {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
}
/** @internal PRIVATE API, DO NOT USE */
type SvelteWithOptionalProps<Props, Keys extends keyof Props> = Omit<Props, Keys> & Partial<Pick<Props, Keys>>;
/** @internal PRIVATE API, DO NOT USE */
type SvelteAllProps = { [index: string]: any }
/** @internal PRIVATE API, DO NOT USE */
type SveltePropsAnyFallback<Props> = {[K in keyof Props]: Props[K] extends never ? never : Props[K] extends undefined ? any : Props[K]}
/** @internal PRIVATE API, DO NOT USE */
type SvelteSlotsAnyFallback<Slots> = {[K in keyof Slots]: {[S in keyof Slots[K]]: Slots[K][S] extends undefined ? any : Slots[K][S]}}
/** @internal PRIVATE API, DO NOT USE */
type SvelteRestProps = { [index: string]: any }
/** @internal PRIVATE API, DO NOT USE */
type SvelteSlots = { [index: string]: any }
/** @internal PRIVATE API, DO NOT USE */
type SvelteStore<T> = { subscribe: (run: (value: T) => any, invalidate?: any) => any }
// Forces TypeScript to look into the type which results in a better representation of it
// which helps for error messages and is necessary for d.ts file transformation so that
// no ambient type references are left in the output
/** @internal PRIVATE API, DO NOT USE */
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
/** @internal PRIVATE API, DO NOT USE */
type KeysMatching<Obj, V> = {[K in keyof Obj]-?: Obj[K] extends V ? K : never}[keyof Obj]
/** @internal PRIVATE API, DO NOT USE */
declare type __sveltets_2_CustomEvents<T> = {[K in KeysMatching<T, CustomEvent>]: T[K] extends CustomEvent ? T[K]['detail']: T[K]}
declare function __sveltets_2_ensureRightProps<Props>(props: Props): {};
declare function __sveltets_2_instanceOf<T = any>(type: AConstructorTypeOf<T>): T;
declare function __sveltets_2_allPropsType(): SvelteAllProps
declare function __sveltets_2_restPropsType(): SvelteRestProps
declare function __sveltets_2_slotsType<Slots, Key extends keyof Slots>(slots: Slots): Record<Key, boolean>;
// Overload of the following two functions is necessary.
// An empty array of optionalProps makes OptionalProps type any, which means we lose the prop typing.
// optionalProps need to be first or its type cannot be infered correctly.
declare function __sveltets_2_partial<Props = {}, Events = {}, Slots = {}, Exports = {}, Bindings = string>(
render: {props: Props, events: Events, slots: Slots, exports?: Exports, bindings?: Bindings }
): {props: Expand<SveltePropsAnyFallback<Props>>, events: Events, slots: Expand<SvelteSlotsAnyFallback<Slots>>, exports?: Exports, bindings?: Bindings }
declare function __sveltets_2_partial<Props = {}, Events = {}, Slots = {}, Exports = {}, Bindings = string, OptionalProps extends keyof Props = any>(
optionalProps: OptionalProps[],
render: {props: Props, events: Events, slots: Slots, exports?: Exports, bindings?: Bindings }
): {props: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>>, events: Events, slots: Expand<SvelteSlotsAnyFallback<Slots>>, exports?: Exports, bindings?: Bindings }
declare function __sveltets_2_partial_with_any<Props = {}, Events = {}, Slots = {}, Exports = {}, Bindings = string>(
render: {props: Props, events: Events, slots: Slots, exports?: Exports, bindings?: Bindings }
): {props: Expand<SveltePropsAnyFallback<Props> & SvelteAllProps>, events: Events, slots: Expand<SvelteSlotsAnyFallback<Slots>>, exports?: Exports, bindings?: Bindings }
declare function __sveltets_2_partial_with_any<Props = {}, Events = {}, Slots = {}, Exports = {}, Bindings = string, OptionalProps extends keyof Props = any>(
optionalProps: OptionalProps[],
render: {props: Props, events: Events, slots: Slots, exports?: Exports, bindings?: Bindings }
): {props: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps> & SvelteAllProps>, events: Events, slots: Expand<SvelteSlotsAnyFallback<Slots>>, exports?: Exports, bindings?: Bindings }
declare function __sveltets_2_with_any<Props = {}, Events = {}, Slots = {}, Exports = {}, Bindings = string>(
render: {props: Props, events: Events, slots: Slots, exports?: Exports, bindings?: Bindings }
): {props: Expand<Props & SvelteAllProps>, events: Events, slots: Slots, exports?: Exports, bindings?: Bindings }
declare function __sveltets_2_with_any_event<Props = {}, Events = {}, Slots = {}, Exports = {}, Bindings = string>(
render: {props: Props, events: Events, slots: Slots, exports?: Exports, bindings?: Bindings }
): {props: Props, events: Events & {[evt: string]: CustomEvent<any>;}, slots: Slots, exports?: Exports, bindings?: Bindings }
declare function __sveltets_2_store_get<T = any>(store: SvelteStore<T>): T
declare function __sveltets_2_store_get<Store extends SvelteStore<any> | undefined | null>(store: Store): Store extends SvelteStore<infer T> ? T : Store;
declare function __sveltets_2_any(dummy: any): any;
declare function __sveltets_2_invalidate<T>(getValue: () => T): T
declare function __sveltets_2_mapWindowEvent<K extends keyof HTMLBodyElementEventMap>(
event: K
): HTMLBodyElementEventMap[K];
declare function __sveltets_2_mapBodyEvent<K extends keyof WindowEventMap>(
event: K
): WindowEventMap[K];
declare function __sveltets_2_mapElementEvent<K extends keyof HTMLElementEventMap>(
event: K
): HTMLElementEventMap[K];
declare function __sveltets_2_bubbleEventDef<Events, K extends keyof Events>(
events: Events, eventKey: K
): Events[K];
declare function __sveltets_2_bubbleEventDef(
events: any, eventKey: string
): any;
declare const __sveltets_2_customEvent: CustomEvent<any>;
declare function __sveltets_2_toEventTypings<Typings>(): {[Key in keyof Typings]: CustomEvent<Typings[Key]>};
declare function __sveltets_2_unionType<T1, T2>(t1: T1, t2: T2): T1 | T2;
declare function __sveltets_2_unionType<T1, T2, T3>(t1: T1, t2: T2, t3: T3): T1 | T2 | T3;
declare function __sveltets_2_unionType<T1, T2, T3, T4>(t1: T1, t2: T2, t3: T3, t4: T4): T1 | T2 | T3 | T4;
declare function __sveltets_2_unionType(...types: any[]): any;
declare function __sveltets_2_createSvelte2TsxComponent<Props extends {}, Events extends {}, Slots extends {}>(
render: {props: Props, events: Events, slots: Slots }
): typeof import("svelte").SvelteComponent<Props, Events, Slots>;
declare function __sveltets_2_unwrapArr<T>(arr: ArrayLike<T>): T
declare function __sveltets_2_unwrapPromiseLike<T>(promise: PromiseLike<T> | T): T
// v2
declare function __sveltets_2_createCreateSlot<Slots = Record<string, Record<string, any>>>(): <SlotName extends keyof Slots>(slotName: SlotName, attrs: Slots[SlotName]) => Record<string, any>;
declare function __sveltets_2_createComponentAny(props: Record<string, any>): import("svelte").SvelteComponent<any, any, any>;
declare function __sveltets_2_any(...dummy: any[]): any;
declare function __sveltets_2_empty(...dummy: any[]): {};
declare function __sveltets_2_union<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10>(t1:T1,t2?:T2,t3?:T3,t4?:T4,t5?:T5,t6?:T6,t7?:T7,t8?:T8,t9?:T9,t10?:T10): T1 & T2 & T3 & T4 & T5 & T6 & T7 & T8 & T9 & T10;
declare function __sveltets_2_nonNullable<T>(type: T): NonNullable<T>;
declare function __sveltets_2_cssProp(prop: Record<string, any>): {};
// @ts-ignore Svelte v3/v4 don't have this
declare function __sveltets_2_ensureSnippet(val: ReturnType<import('svelte').Snippet> | undefined | null): any;
/** @internal PRIVATE API, DO NOT USE */
type __sveltets_2_SvelteAnimationReturnType = {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
}
declare var __sveltets_2_AnimationMove: { from: DOMRect, to: DOMRect }
declare function __sveltets_2_ensureAnimation(animationCall: __sveltets_2_SvelteAnimationReturnType): {};
/** @internal PRIVATE API, DO NOT USE */
type __sveltets_2_SvelteActionReturnType = {
update?: (args: any) => void,
destroy?: () => void,
$$_attributes?: Record<string, any>,
} | void
declare function __sveltets_2_ensureAction<T extends __sveltets_2_SvelteActionReturnType>(actionCall: T): T extends {$$_attributes?: any} ? T['$$_attributes'] : {};
/** @internal PRIVATE API, DO NOT USE */
type __sveltets_2_SvelteTransitionConfig = {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
}
/** @internal PRIVATE API, DO NOT USE */
type __sveltets_2_SvelteTransitionReturnType = __sveltets_2_SvelteTransitionConfig | (() => __sveltets_2_SvelteTransitionConfig)
declare function __sveltets_2_ensureTransition(transitionCall: __sveltets_2_SvelteTransitionReturnType): {};
// Includes undefined and null for all types as all usages also allow these
declare function __sveltets_2_ensureType<T>(type: AConstructorTypeOf<T>, el: T | undefined | null): {};
declare function __sveltets_2_ensureType<T1, T2>(type1: AConstructorTypeOf<T1>, type2: AConstructorTypeOf<T2>, el: T1 | T2 | undefined | null): {};
// The following is necessary because there are two clashing errors that can't be solved at the same time
// when using Svelte2TsxComponent, more precisely the event typings in
// __sveltets_2_ensureComponent<T extends new (..) => _SvelteComponent<any,||any||<-this,any>>(type: T): T;
// If we type it as "any", we have an error when using sth like {a: CustomEvent<any>}
// If we type it as "{}", we have an error when using sth like {[evt: string]: CustomEvent<any>}
// If we type it as "unknown", we get all kinds of follow up errors which we want to avoid
// Therefore introduce two more base classes just for this case.
/**
* Ambient type only used for intellisense, DO NOT USE IN YOUR PROJECT
*/
declare type ATypedSvelteComponent = {
/**
* @internal This is for type checking capabilities only
* and does not exist at runtime. Don't use this property.
*/
$$prop_def: any;
/**
* @internal This is for type checking capabilities only
* and does not exist at runtime. Don't use this property.
*/
$$events_def: any;
/**
* @internal This is for type checking capabilities only
* and does not exist at runtime. Don't use this property.
*/
$$slot_def: any;
$on(event: string, handler: any): () => void;
}
/**
* Ambient type only used for intellisense, DO NOT USE IN YOUR PROJECT.
*
* If you're looking for the type of a Svelte Component, use `SvelteComponent` and `ComponentType` instead:
*
* ```ts
* import type { ComponentType, SvelteComponent } from "svelte";
* let myComponentConstructor: ComponentType<SvelteComponent> = ..;
* ```
*/
declare type ConstructorOfATypedSvelteComponent = new (args: {target: any, props?: any}) => ATypedSvelteComponent
// Usage note: Cannot properly transform generic function components to class components due to TypeScript limitations
declare function __sveltets_2_ensureComponent<
T extends
| ConstructorOfATypedSvelteComponent
| (typeof import('svelte') extends { mount: any }
? // @ts-ignore svelte.Component doesn't exist in Svelte 4
import('svelte').Component<any, any, any>
: never)
| null
| undefined
>(
type: T
): NonNullable<
T extends ConstructorOfATypedSvelteComponent
? T
: typeof import('svelte') extends { mount: any }
? // @ts-ignore svelte.Component doesn't exist in Svelte 4
T extends import('svelte').Component<
infer Props extends Record<string, any>,
infer Exports extends Record<string, any>,
infer Bindings extends string
>
? new (
options: import('svelte').ComponentConstructorOptions<Props>
) => import('svelte').SvelteComponent<Props, Props['$$events'], Props['$$slots']> &
Exports & { $$bindings: Bindings }
: never
: never
>;
declare function __sveltets_2_ensureArray<T extends ArrayLike<unknown> | Iterable<unknown>>(
// Svelte 5 allows undefined or null here, Svelte 4 doesn't
array: T | (typeof import('svelte') extends { mount: any } ? (undefined | null) : never)
): T extends ArrayLike<infer U> ? U[] : T extends Iterable<infer U> ? Iterable<U> : any[];
type __sveltets_2_PropsWithChildren<Props, Slots> = Props &
(Slots extends { default: any }
// This is unfortunate because it means "accepts no props" turns into "accepts any prop"
// but the alternative is non-fixable type errors because of the way TypeScript index
// signatures work (they will always take precedence and make an impossible-to-satisfy children type).
? Props extends Record<string, never>
? any
: { children?: any }
: {});
declare function __sveltets_2_runes_constructor<Props extends {}>(render: {props: Props }): import("svelte").ComponentConstructorOptions<Props>;
declare function __sveltets_2_get_set_binding<T>(get: (() => T) | null | undefined, set: (t: T) => void): T;
declare function __sveltets_$$bindings<Bindings extends string[]>(...bindings: Bindings): Bindings[number];
declare function __sveltets_2_fn_component<
Props extends Record<string, any>, Exports extends Record<string, any>, Bindings extends string
// @ts-ignore Svelte 5 only
>(klass: {props: Props, exports?: Exports, bindings?: Bindings }): import('svelte').Component<Props, Exports, Bindings>;
interface __sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & { $$bindings?: Bindings } & Exports;
(internal: unknown, props: Props extends Record<string, never> ? {$$events?: Events, $$slots?: Slots} : Props & {$$events?: Events, $$slots?: Slots}): Exports & { $set?: any, $on?: any };
z_$$bindings?: Bindings;
}
declare function __sveltets_2_isomorphic_component<
Props extends Record<string, any>, Events extends Record<string, any>, Slots extends Record<string, any>, Exports extends Record<string, any>, Bindings extends string
>(klass: {props: Props, events: Events, slots: Slots, exports?: Exports, bindings?: Bindings }): __sveltets_2_IsomorphicComponent<Props, Events, Slots, Exports, Bindings>;
declare function __sveltets_2_isomorphic_component_slots<
Props extends Record<string, any>, Events extends Record<string, any>, Slots extends Record<string, any>, Exports extends Record<string, any>, Bindings extends string
>(klass: {props: Props, events: Events, slots: Slots, exports?: Exports, bindings?: Bindings }): __sveltets_2_IsomorphicComponent<__sveltets_2_PropsWithChildren<Props, Slots>, Events, Slots, Exports, Bindings>;