Skip to content

๐ŸชŸ Broker Modals

All broker-related modal dialogs. Each is built on ModalBase.

graph TD
    MB["ModalBase"]

    BM["BrokerModal"] --> BF["BrokerForm"]
    BM --> MB

    BSM["BrokerSharingModal"] --> BSP["BrokerSharingPanel"]
    BSM --> MB
    INFO["Broker detail<br/><small>Info tab</small>"] --> BSP
    BSP --> USS["UserSearchSelect<br/><small>add user</small>"]
    BSP --> SDC["SemiDonutChart<br/><small>owner shares</small>"]

    BIM["BrokerImportFilesModal"] --> FU["FileUploader"]
    BIM --> FT["FilesTable"]
    BIM --> MB

    DBD["DeleteBrokerDialog"] --> MB

    style MB fill:#f3e5f5,stroke:#7b1fa2

โœ๏ธ BrokerModal

Modal wrapper (BrokerModal.svelte) around BrokerForm for creating or editing a broker. The broker list and detail pages, the Files page, the Import Wizard and the transaction form (to create a broker inline) all mount it.

  • Create โ€” POST /brokers with a one-item list. On success the new broker is merged into brokerStore (mergeBrokers), a success toast reads Broker "{name}" created., then oncreated({id}) and onclose() run.
  • Edit โ€” PATCH /brokers/{id} with the form values. On success the patched fields are merged into brokerStore, then onupdated({id}) and onclose() run; there is no toast.
  • Errors appear in the dialog's InfoBanner. When a create fails on a duplicate name, the message is localized and carries recovery guidance (Duplicate names). The error, the touched flag and the discard prompt are reset whenever a new dialog opens, and the answer to a request from an earlier dialog is ignored.
  • Closing with unsaved edits asks Discard Changes? (Continue Editing / Discard & Close); nothing closes while a save is running.

๐Ÿ“‹ Props

Prop Type Description
isOpen boolean Modal visibility
mode 'create' \| 'edit' Defaults to 'create'
brokerId number \| null Broker updated in edit mode
initialData object Values pre-filled in the form (name, description, portal_url, icon_url, default_import_plugin, allow_cash_overdraft, allow_asset_shorting, is_active, opened_at)
zIndex number Defaults to 50; raised when the modal is stacked over another one
tourPreview boolean Non-writing preview: the form is inert, has no submit button, and the dialog closes without the discard prompt
onclose, oncreated, onupdated callbacks oncreated and onupdated receive {id}

๐Ÿค BrokerSharingModal

Sharing a broker with other users, with RBAC roles and ownership shares.

Broker Sharing Modal

The sharing UI is one component, BrokerSharingPanel.svelte, mounted in two places:

Host Mounted as Read-only when
Broker list (routes/(app)/brokers/+page.svelte) BrokerSharingModal: the panel inside ModalBase, opened from a broker card's share action the user is not the broker's OWNER; always for a discovery card (a broker the user has no access to)
Broker detail, Info tab (routes/(app)/brokers/[id]/+page.svelte) the panel itself, without modal chrome the user is not the broker's OWNER

BrokerSharingModal adds only the title (Share Broker โ€” {name}), the X button and the unsaved-changes prompt.

๐Ÿงฉ BrokerSharingPanel

Prop Type Description
brokerId number Broker whose access list is loaded; changing it reloads the list
readOnly boolean Shows the list without any editing control
onChanged () => void Called after a successful save or self-service action, so the host can reload
onCancel () => void Optional. Renders Cancel next to Save and is called after a successful save. The modal passes its close handler; the Info tab passes nothing
hasChanges boolean, $bindable true while the staged list differs from the loaded one

โšก Features

  • Ownership chart โ€” a SemiDonutChart of the owners' shares with the Allocated and Available totals. When the owners' shares exceed 100% a warning banner appears; Save stays available and the server refuses the list.
  • Owners, Editors, Viewers โ€” three columns, one chip per user with role and share.
  • Add user (the + under the chart) โ€” the candidates come from GET /users/search (excluding users who already have access) and are picked with UserSearchSelect, built on SearchSelect. The endpoint matches the username only (ILIKE) and never returns e-mail addresses. The role defaults to Viewer; only an Owner gets a share, capped at what is still unallocated.
  • Edit and remove โ€” a chip opens the role and share editor, which also removes the user after a confirmation. The last Owner can be neither removed nor demoted.
  • Your access โ€” the user's own row offers Leave broker (Leave and delete broker for the last Owner) and, for an Editor, Switch to viewer. Both ask for confirmation and then act at once โ€” they are not staged โ€” and both stay available in read-only mode.

๐Ÿ’พ Staged save, Reset and closing

Adding, editing and removing users only change the panel's local list; nothing is written before Save Configuration.

  • Save Configuration sends the whole list in one PUT /brokers/{id}/access, shares as 0โ€“1 fractions. It is disabled while nothing changed, during a save, and until the current list has loaded: a failed load shows a blocking error with Retry instead of an empty, editable list. On success the saved list becomes the new baseline, onChanged runs, a toast reads Access configuration saved, and onCancel is called. A refused save is shown inline in the panel and keeps the draft.
  • Why a save is refused. BrokerService.bulk_update_access() rejects a list when the caller is not an Owner (403; superusers are exempt), and with 400 when no Owner would remain, when the shares add up to more than 1, when a user appears twice, or when a user does not exist. The schema (BRAccessBulkItem) also rejects a share above 0 on an Editor or a Viewer, and an empty list is a 422.
  • Reset (the โ†บ button, shown only with unsaved changes) puts back the list as loaded.
  • After a save, the two hosts differ. The panel awaits a tick() before calling onCancel, so the bound hasChanges is already false: in the list-page modal a successful save closes the dialog without the discard prompt. The Info tab passes no onCancel, so the panel stays open and editable after saving.
  • Closing with unsaved changes. The modal asks Discard Changes? (Discard & Close) on X, Cancel, Escape or a backdrop click. The Info tab binds hasChanges but does not use it yet: switching tab unmounts the panel and drops unsaved changes without a prompt.
  • Read-only (non-owners): the chips are disabled, and there is no add button, Reset, Save or Cancel.

๐ŸŒ API Calls

  • GET /brokers/{id}/access โ€” load the current access list
  • PUT /brokers/{id}/access โ€” replace it with the staged list
  • PATCH /brokers/{id}/access/me โ€” switch the current user from Editor to Viewer
  • DELETE /brokers/{id}/access/me โ€” leave the broker; when the last Owner leaves, the broker is deleted
  • GET /users/search โ€” candidates for Add user

See Broker Sharing (User Guide) and Access Control (RBAC) for details.


๐Ÿ“ฅ BrokerImportFilesModal

Modal for the BRIM report files of one broker, opened from the broker detail page (Uploaded Reports in its Transactions tab). It stores and manages files; parsing them is the job of the Import Wizard.

โšก Features

  • Lists the broker's files in a FilesTable (type="brim", without the broker column), with preview (FilePreviewModal) and single or bulk delete; a bulk delete asks for confirmation.
  • Uploads through FileUploader (.csv, .xlsx, .xls, several at once). A pending file can be renamed before upload (FileEditModal). The files of one upload share a batch_id (generateUUID()), so a report-set plugin can read them as one report set. The upload stops at the first failing file and names it in the error banner.
  • Closing with files picked but not uploaded asks for confirmation (Pending Uploads).
  • The modal does not check the user's role: a Viewer also sees the upload area and the delete actions, and the server refuses them with 403 (EDITOR or OWNER access required on the broker).
  • A link opens the Files page filtered on this broker (/files?tab=brim&broker=<id>).

๐ŸŒ API Calls

  • GET /brokers/import/files?broker_ids=<id> โ€” list the broker's files
  • POST /brokers/import/upload โ€” upload one file (multipart: file, broker_id, batch_id)
  • DELETE /brokers/import/files/{file_id} โ€” delete one file

For details on the BRIM plugin system, see Registry & Plugin System.


๐Ÿ—‘๏ธ DeleteBrokerDialog

A ModalBase dialog (DeleteBrokerDialog.svelte) driven by the broker list page (confirmDelete in routes/(app)/brokers/+page.svelte):

  • It first asks to confirm the deletion of the named broker. Delete dispatches confirm with {force: false}, and the page calls DELETE /brokers?ids=<id>&force=false.
  • When the broker still has transactions, the server refuses and the dialog switches to its blocked state (blocked, transactionCount): it says how many transactions the broker holds and offers Go to transactions (viewTransactions, which opens /transactions?broker_id=<id>) or Delete broker and transactions (confirm with {force: true}).
  • On success the page evicts the broker from brokerStore (invalidateBroker), closes the dialog, shows a success toast โ€” Broker "{name}" deleted., or Broker "{name}" and {count} transactions deleted. after a force delete โ€” and reloads the list. A failure ends in an error toast (Could not delete broker).

No cash-operation modal

Deposits and withdrawals have no dialog of their own: they are ordinary transactions. The broker detail page opens the bulk transaction workspace (TransactionBulkModal, intent {action: 'create'}) with the broker pre-selected through defaultBrokerId.