feat(ui): Volume Contracts configuration page - #369
Open
prajjwalkumar17 wants to merge 2 commits into
Open
Conversation
Adds a Volume Contracts page (Routing section of the sidebar, beta badge) for configuring volume-commitment contract documents through the existing /routing/* endpoints with algorithm_for: volume_commitment: - Builder covering the whole DSL: routing mode, tolerance (pp), metric, currency + amount units, expected daily traffic, optional forecast/steering interval overrides, and per-PSP contracts with lumpsum (target + flat/percentage reward) and tiered (rebate ladder with a single targeted retroactive tier, enforced in the form) archetypes, billing cycle + IANA timezone. - Document list with active badge, expandable stored-JSON view, and the activate/deactivate/delete lifecycle with confirm dialogs, mirroring the Rule-Based page's patterns (SWR + POST fetchers, cache revalidation, permission gating via routing:write). - Reuses the existing UI kit (Card/InsetPanel/Button/Badge/Combobox/ SearchableSelect/ConfirmDialog/typography) so the page matches the rest of the dashboard. - types/api.ts: VolumeContractConfig types + volume_contract in the algorithm type unions. - EuclidRulesPage now excludes volume_contract documents from its rules list (they live on their own page). Verified against a local backend: create canonicalizes (5pp -> 500 bps, major -> minor units), activate/deactivate round-trips, tsc + vite build green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SearchableSelect's compact cond-select trigger (built for the dense Euclid condition builder) and unstyled Combobox inputs looked cramped next to the regular form fields. SearchableSelect gains an opt-in triggerClassName prop that swaps the compact trigger for full input styling (block-level, no 10rem label truncation); existing call sites are untouched. The volume contracts page passes its shared input class to every dropdown so selects, comboboxes and text inputs render at identical height, padding and type size. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Dashboard UI for the volume-commitment contract DSL introduced in #367. New Volume Contracts entry in the sidebar's Routing section (beta badge) at
/routing/volume-contracts, styled identically to the other routing pages and built from the existing UI kit (Card/InsetPanel/Button/Badge/Combobox/SearchableSelect/ConfirmDialog, SWR + POST fetchers,routing:writepermission gating). Stacked on the #367 branch; merge that first.types/api.tsgainsVolumeContractConfigtypes andvolume_contractin the algorithm type unions;EuclidRulesPagenow excludes volume-contract documents from its rules list (they have their own page).No new endpoints: everything rides
POST /routing/{create,list,list/active,activate,deactivate,delete}withalgorithm_for: volume_commitmentandalgorithm: { type: "volume_contract", data: … }.DSL parameter reference
Document (merchant-level)
schema_version11writablerouting_modepace_guarded|volume_commitmenttolerance"5pp"|"550bps"| int bpstolerance_bps; ≤ 2000 bpsmetricgmv|volumegmvcurrency.denominationcurrency.amount_unitsmajor|minorminorminorexpected_daily_trafficforecast_interval_secssteering_interval_secsvolume_contractsPer-PSP contract
id[A-Za-z0-9_-]{1,64}, unique in documentconnectorstatusactive|inactiveactivebilling_cycle.typecalendar_month|calendar_quarter|calendar_yearbilling_cycle.anchorbilling_cycle.timezonebilling_cycle.prorationfull_periodfull_periodarchetypelumpsum|tiered(|min_commitment)scopelumpsumterms (archetype A)targetreward.kindflat|percentagereward.value.flat_amountflatreward.value.rebate_bpspercentagetieredterms (archetype C) — per tierkindretroactive|marginalrate.rebate_bps/rate.rate_bpskind; 1–10000thresholdtargetedfalseretroactiverebate_lag_days0rebate_settlementcash|credit_notecashAmounts are JSON integers or decimal strings in the declared
amount_units; the backend stores everything canonicalized to integer minor units. No derived values (goals, rewards, period ends) are computed by the UI or stored — resolution stays the engine's job.Verified locally
Against a live backend (postgres) + vite: logged in, created a document from the form ($6M target / $15k flat / $800k daily traffic in major units, 5pp tolerance), confirmed the stored JSON came back canonicalized (
tolerance_bps: 500,target: 600000000,expected_daily_traffic: 80000000,amount_units: minor), activated it (Active badge, mapper slot), deactivate/delete guarded by confirm dialogs.tsc --noEmitandnpm run buildgreen;website/distregenerated per repo convention.