Skip to content

[DONT MERGE] Add mocked dashboard app#1236

Open
Sharqiewicz wants to merge 19 commits into
stagingfrom
feature/dashboard-app-staging
Open

[DONT MERGE] Add mocked dashboard app#1236
Sharqiewicz wants to merge 19 commits into
stagingfrom
feature/dashboard-app-staging

Conversation

@Sharqiewicz

@Sharqiewicz Sharqiewicz commented Jun 23, 2026

Copy link
Copy Markdown
Member

Adds a new apps/dashboard sub-app (mocked) with auth, onboarding, recipients, transactions, and transfer UI.

Changes

  • New apps/dashboard/ Vite + shadcn/ui app
  • Root package.json: build:dashboard / dev:dashboard scripts wired in
  • apps/frontend/_redirects: SPA fallback for /dashboard/*

Branch is rebuilt on top of the current staging tip so it compares cleanly.

@netlify

netlify Bot commented Jun 23, 2026

Copy link
Copy Markdown

Deploy Preview for vortex-sandbox failed. Why did it fail? →

Name Link
🔨 Latest commit 0ef41d1
🔍 Latest deploy log https://app.netlify.com/projects/vortex-sandbox/deploys/6a4d53d98e508d0008b32a30

@netlify

netlify Bot commented Jun 23, 2026

Copy link
Copy Markdown

Deploy Preview for vortexfi failed. Why did it fail? →

Name Link
🔨 Latest commit 0ef41d1
🔍 Latest deploy log https://app.netlify.com/projects/vortexfi/deploys/6a4d53d98e85c4000871e178

@Sharqiewicz Sharqiewicz changed the title Add mocked dashboard app [DONT MERGE] Add mocked dashboard app Jun 23, 2026
Sharqiewicz and others added 18 commits June 24, 2026 16:16
…hain/vortex into feature/dashboard-app-staging
… dual-write)

Realizes the complete data model from the two architecture docs in one migration
set, per the amended plan (migrate data once, cut reads over next, keep legacy
tables as read-only backup):

- 038 customer_entities + backfill (one individual entity per profile)
- 039 partners split: partner_pricing_configs, dedup to unique-name partners,
  FK repoint (quotes/assignments) before fold, partners_legacy snapshot,
  assignments partner_id collapse, idempotent vortex re-seed
- 040 provider_customers + kyc_cases + backfills from mykobo_customers,
  alfredpay_customers, tax_ids (owned rows only; statuses verbatim; raw tax ids
  replaced by sha256 hash + masked display; ownerless rows quarantined)
- 041 api_keys.partner_id FK (backfill from partner_name) + scopes + revoked_at
  (user_id from 034 already is the doc's profile_id)
- 042 recipient_invitations (link-based, D1) + sender_recipients +
  recipient_payout_references (thin provider pointer, D3)
- 043 notifications + notification_preferences

All new tables ENABLE ROW LEVEL SECURITY (Supabase default privileges grant
anon/authenticated ALL on future tables; RLS-no-policy denies PostgREST).
New status columns are VARCHAR + CHECK (plan D6).

Verified: full chain 001-043 clean on fresh PG16; backfills exercised against
seeded legacy fixtures (dup alfredpay rows, IND+BUS same country, multi-tax-id
user, empty-string subaccount sentinel, orphan tax_id, divergent per-direction
partner activity, ghost partner_name key) with all assertions passing.

NOTE: not independently deployable — existing code still reads the pre-split
partners shape; the cutover commits follow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
zod was added to packages/shared and @vortexfi/sdk pinned shared to =0.2.0
without a lockfile update; the missing zod entry broke bun build:shared.
Pure bun install output — no package.json changes here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Existing functionality unchanged; pricing now resolves through the split
schema (migrate-once cutover, legacy columns unread):

- New partners/partner-pricing.service: findPartnerWithPricing merges the
  unique-name partner row with its per-direction pricing config — the exact
  semantics of the pre-split Partner.findOne({name|id, isActive, rampType})
- partner-resolution: assignment branch reads the collapsed
  profile_partner_assignments.partner_id (buy/sell pair now unread backup)
- quote-fees: single pricing-config lookup replaces the findAll loop;
  CalculateFeeComponentsRequest.partnerName renamed to partnerId (it always
  carried an id)
- discount helpers: ActivePartner sourced from pricing configs; in-memory
  dynamic-difference state keyed by (partner_id, ramp_type) to preserve the
  per-direction isolation the split rows used to give for free
- feeDistribution: vortex + markup payout addresses read from pricing configs
- apiKeyAuth: secret/public keys resolve partners via api_keys.partner_id FK;
  partner_name never read; user-scoped branch keys off partner_id IS NULL
- admin controllers: unique-name partner resolution; assignments write a
  single partner_id; api-key CRUD keyed by partner_id; revocation stamps
  revoked_at; ambiguity 409 retired (structurally impossible)
- factories: createTestPartner creates partner + config (findOrCreate by
  unique name); seedVortexPartners = 1 partner + BUY/SELL configs;
  updatePartnerPricing helper for per-direction test overrides

Verified: full API suite green (414 pass / 0 fail) including the golden
pricing test (fee parity through the split), corridor scenarios and
sdk-contract suites. Security-spec updated in the same change set:
01-auth/api-keys.md, 03-ramp-engine/{profile-partner-pricing,
discount-mechanism,fee-integrity,quote-lifecycle}.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mers/kyc_cases

Migrate-once cutover of the EUR (Mykobo) and BRL (Avenia) provider identity;
existing functionality unchanged (full suite green: 415 pass / 0 fail):

- customer-entity.service: getOrCreateCustomerEntityForProfile — eager create
  in verify-otp (outside the OTP error mapping so a DB failure cannot surface
  as "Invalid OTP") + lazy fallback on every entity-scoped read (covers users
  with pre-existing sessions who never re-verify)
- mykobo-customer.service: KYC mirror upserts/reads provider_customers
  (provider=mykobo, provider_customer_id = last-synced email); module path and
  exports unchanged (integration-test mock seam preserved)
- avenia-customer.service (new): hash/mask helpers, tax-hash lookups,
  kyc_cases upkeep, idempotent Requested→Accepted/Rejected outcome transition
- avenia-account/brla.controller/ramp.service/phase handlers/worker: all
  tax_ids reads keyed by tax_reference_hash against provider_customers;
  ownership checks compare the effective user's customer_entity; SDK-matched
  error strings byte-identical ("Subaccount not found", "KYC invalid", ...)
- createSubaccount keeps claim-on-create: quarantined (ownerless) legacy
  tax_ids rows are adoptable via a one-time read-only probe of the backup;
  owned legacy rows still 409
- Ownership gaps closed (the real remaining Phase 5): fetchSubaccountKycStatus
  (which also writes status transitions) and getSelfieLivenessUrl now enforce
  account ownership. Known residual: getKybAttemptStatus cannot be bound
  server-side (attempt response carries no subaccount linkage)
- Migration 040 amended (pre-deploy): provider_customers.tax_reference retains
  the raw normalized tax id — documented deviation from the "no raw tax IDs"
  non-goal; it is the join/aggregation key for in-flight ramp state and
  already persists in ramp_states.state JSONB
- Quote-provenance writes dropped (write-only columns); tests updated
  (brla.controller.test rewritten for the new model incl. legacy-adoption
  scenarios; factories create provider_customers; unique-subaccount fix in
  sdk-contract)

Security-spec updated in the same change set: 05-integrations/brla.md
(+ cutover section documenting the raw-tax-reference deviation and the KYB
residual), 05-integrations/mykobo.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…odels

Completes the provider cutover (full suite green: 415 pass / 0 fail):

- alfredpay-customer.service (new): findAlfredpayCustomer resolves the
  caller's customer_entity and preserves the legacy updatedAt-DESC tie-break;
  a legacy-shaped view keeps the controller's status machine verbatim
  (alfredPayId/type/status accessors); status transitions mirror into the
  account's kyc_cases row; createAlfredpayCustomer maps country → rail
- alfredpay.controller: all 22 lookup/create sites ported (mechanical);
  status vocabulary unchanged — and UPDATE_REQUIRED writes now succeed
  (the legacy Postgres enum lacked the value the shared enum had)
- alfredpay.helpers/quote resolvers: customerType ASC keeps the type-ASC
  precedence; KYC gates read provider_customers
- Removed models with zero remaining readers: AlfredPayCustomer,
  MykoboCustomer, KycLevel2 (tables stay as read-only backup). TaxId model
  retained solely for the createSubaccount legacy-adoption probe

Security-spec updated: 05-integrations/alfredpay.md (+ cutover section),
07-operations/api-surface.md and 03-ramp-engine/quote-lifecycle.md
resolution chains.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dashboard backend surface (plan §7/§8/D5), reading the new tables directly:

- /v1/recipients: link-token invites (sha256-hashed at rest, 14-day TTL,
  optional email binding), token-bound acceptance creating the
  sender_recipients relationship, listing, nickname/block/archive PATCH,
  and the transfer-eligibility gate with per-check blocking reasons.
- /v1/notifications: owner-scoped feed + unread count, read/read-all,
  preferences (defaults on first read). emitNotification never throws;
  email dispatch (D7) deliberately not wired yet.
- GET /v1/onboarding/status: per-entity aggregate over provider_customers
  + kyc_cases with a normalized approved/pending/rejected state.

New security specs 03-ramp-engine/recipient-transfers.md and
07-operations/notifications.md; plan Phase 1 marked landed (§7.1 payout
instrument mechanism still open, so verified payout references have no
production writer yet).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 0 core connection plus the swap-execution engine, ported from the
widget's ramp machine per plan §9:

- Real auth: /v1/auth request-otp/verify-otp/refresh via a ported
  AuthService (dashboard-scoped storage keys) + api-client with bearer
  auth and single-flight 401 refresh; login page drops the fake OTP.
- Real quotes: POST /v1/quotes with a two-pass inversion so the
  payout-driven form ("recipient gets X") maps onto the input-driven
  quote endpoint.
- transfer.machine.ts: the widget's RegisterRamp → UpdateRamp/sign →
  StartRamp → RampFollowUp core as a dashboard-owned XState machine —
  register with fresh ephemerals, presign ephemeral txs client-side
  (signUnsignedTransactions), sign user-owned txs with the wagmi wallet
  (typed-data permits + squidRouter broadcasts, EVM-only), start, then
  poll to terminal. An app-lifetime actor keeps polling after the form
  unmounts and feeds the transactions table.
- CORS: allow :5174 in development; dashboard gains @vortexfi/shared
  (runtime signing helpers), .env.example, esnext build target.

AssetHub payin is disabled (no substrate wallet in the dashboard).
Alfredpay/BRL offramp additionalData stays incomplete until the §7.1
payout-instrument capture lands — EUR is the fully-wirable corridor.
Recipients/notifications/onboarding pages still run on mocks; wiring
them to the new /v1 endpoints is the remaining §9-D work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Code-verified per corridor: registration is structurally a self-offramp —
destinations are already sender-bound on mykobo (anchor-side IBAN) and
alfredpay (fiatAccountId scoped to the derived customer); only BRL takes a
third-party destination (pix key + receiverTaxId consistency check).
Sender→recipient transfers therefore need a second principal in
registration (resolve the payout side from the recipient's provider
identity), not a bolted-on eligibility check. Recorded as a pressing
open decision (plan §12.7) and corrected in the recipient-transfers spec.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
import { AlfredPayCountry, AlfredPayStatus } from "@vortexfi/shared";
import { emitNotification } from "../api/services/notifications/notification.service";
import KycCase from "../models/kycCase.model";
import ProviderCustomer, { AveniaKycStatus } from "../models/providerCustomer.model";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants