mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-19 00:01:14 +00:00
0d92f726f9
Backend root cause:
The frontend client omits ?limit= when it would equal the default
(DEFAULT_PAGINATION_LIMIT = 50). validate.Limit("") treated empty as
invalid and returned errx.ErrLimit → 400 on /contacts/search and
/campaigns. ContactsTable derived isLoading from `!contacts`, which
stays true forever when the query errors, so the page hung in the
skeleton state instead of surfacing the error.
Fix:
- validate.Limit now accepts "" and returns LimitDefault = 50, in
sync with the frontend constant. The frontend's omission semantics
("don't send the param when it's the default") was already correct;
it was the validator that was wrong.
Frontend:
- ContactsTable: use isPending/isError/refetch directly from react-query
instead of deriving from `contacts`. New explicit error block renders
inside the body with: red alert tile, server error message, Try-again
button (with spinner during refetch), and Reload-page fallback.
- Campaigns page: same error UI promoted from the old EmptyBlock CTA
to a prominent block — alert tile + message + retry + reload.
New ContactFilters sheet (was the legacy 800px poppins drawer):
- 420px right-side panel matching the rest of the theme.
- Sticky 48px header with "Filters · N active" eyebrow + close.
- Sticky 48px footer with Reset / Cancel / Apply (slate-900 primary).
- Hairline-divided SectionBars between groups: Search, Custom field
filters, Sort, Subscription, Campaign membership, Dates.
- Custom field rows pair TextInput + FILTER_TYPES popover + value
input + remove button — all 28px tall.
- Sort: SelectButton popover + asc/desc toggle.
- Subscription: 3-state pill toggle (Any / Subscribed / Unsubscribed).
- Min/max campaign rows: checkbox toggle + number input + suffix.
- Date rows: checkbox toggle + native date input.
- Draft state mirrors parent until Apply, so editing filters doesn't
trigger refetches mid-build.
29 lines
808 B
Go
29 lines
808 B
Go
package validate
|
|
|
|
import (
|
|
"strconv"
|
|
|
|
"github.com/warmbly/warmbly/internal/config"
|
|
"github.com/warmbly/warmbly/internal/errx"
|
|
)
|
|
|
|
// LimitDefault matches the frontend's DEFAULT_PAGINATION_LIMIT. The
|
|
// client omits ?limit= when it'd be sending the default, so the empty
|
|
// string here means "use the default", not "invalid". Treating it as
|
|
// invalid is what 400'd the campaigns and contacts listings — they
|
|
// stayed in loading state forever because the page didn't handle the
|
|
// error and the client kept retrying.
|
|
const LimitDefault int32 = 50
|
|
|
|
func Limit(limit string) (int32, *errx.Error) {
|
|
if limit == "" {
|
|
return LimitDefault, nil
|
|
}
|
|
i, err := strconv.ParseInt(limit, 10, 32)
|
|
if err != nil || i > config.LimitMax || i < config.LimitMin {
|
|
return 0, errx.ErrLimit
|
|
}
|
|
|
|
return int32(i), nil
|
|
}
|