Phase 3.1: Add Exchange page UI

New /exchange page for Bitcoin trading:
- Buy/Sell direction toggle with visual feedback
- Amount slider (€100-€3000 in €20 increments)
- Live price display with auto-refresh every 60s
- Direction-specific pricing (buy +5%, sell -5%)
- Sats amount displayed in BTC format (0.XX XXX XXX)
- Date selection with availability indicators
- Time slot selection grid
- Confirmation panel with trade summary

Regenerate frontend types from updated OpenAPI schema.
This commit is contained in:
counterweight 2025-12-22 19:59:42 +01:00
parent 811fdf2663
commit 361dc8764d
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
2 changed files with 1379 additions and 49 deletions

View file

@ -124,35 +124,6 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/exchange/price": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get Exchange Price
* @description Get the current BTC/EUR price for trading.
*
* Returns the latest price from the database. If no price exists or the price
* is stale, attempts to fetch a fresh price from Bitfinex.
*
* The response includes:
* - market_price: The raw price from the exchange
* - agreed_price: The price with admin premium applied
* - is_stale: Whether the price is older than 5 minutes
* - config: Trading configuration (min/max EUR, increment)
*/
get: operations["get_exchange_price_api_exchange_price_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/profile": {
parameters: {
query?: never;
@ -465,10 +436,257 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/exchange/price": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get Exchange Price
* @description Get the current BTC/EUR price for trading.
*
* Returns the latest price from the database. If no price exists or the price
* is stale, attempts to fetch a fresh price from Bitfinex.
*
* The response includes:
* - market_price: The raw price from the exchange
* - agreed_price: The price with admin premium applied
* - is_stale: Whether the price is older than 5 minutes
* - config: Trading configuration (min/max EUR, increment)
*/
get: operations["get_exchange_price_api_exchange_price_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/exchange": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Create Exchange
* @description Create a new exchange trade booking.
*
* Validates:
* - Slot is on a valid date and time boundary
* - Slot is within admin availability
* - Slot is not already booked
* - Price is not stale
* - EUR amount is within configured limits
*/
post: operations["create_exchange_api_exchange_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/trades": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get My Trades
* @description Get the current user's exchanges, sorted by date (newest first).
*/
get: operations["get_my_trades_api_trades_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/trades/{exchange_id}/cancel": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Cancel My Trade
* @description Cancel one of the current user's exchanges.
*/
post: operations["cancel_my_trade_api_trades__exchange_id__cancel_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/admin/trades/upcoming": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get Upcoming Trades
* @description Get all upcoming booked trades, sorted by slot time ascending.
*/
get: operations["get_upcoming_trades_api_admin_trades_upcoming_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/admin/trades/past": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get Past Trades
* @description Get past trades with optional filters.
*
* Filters:
* - status: Filter by exchange status
* - start_date, end_date: Filter by slot_start date range
* - user_search: Search by user email (partial match)
*/
get: operations["get_past_trades_api_admin_trades_past_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/admin/trades/{exchange_id}/complete": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Complete Trade
* @description Mark a trade as completed. Only possible after slot time has passed.
*/
post: operations["complete_trade_api_admin_trades__exchange_id__complete_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/admin/trades/{exchange_id}/no-show": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Mark No Show
* @description Mark a trade as no-show. Only possible after slot time has passed.
*/
post: operations["mark_no_show_api_admin_trades__exchange_id__no_show_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/admin/trades/{exchange_id}/cancel": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Admin Cancel Trade
* @description Cancel any trade (admin only).
*/
post: operations["admin_cancel_trade_api_admin_trades__exchange_id__cancel_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
/**
* AdminExchangeResponse
* @description Response model for admin exchange view (includes user contact).
*/
AdminExchangeResponse: {
/** Id */
id: number;
/** User Id */
user_id: number;
/** User Email */
user_email: string;
user_contact: components["schemas"]["ExchangeUserContact"];
/**
* Slot Start
* Format: date-time
*/
slot_start: string;
/**
* Slot End
* Format: date-time
*/
slot_end: string;
/** Direction */
direction: string;
/** Eur Amount */
eur_amount: number;
/** Sats Amount */
sats_amount: number;
/** Market Price Eur */
market_price_eur: number;
/** Agreed Price Eur */
agreed_price_eur: number;
/** Premium Percentage */
premium_percentage: number;
/** Status */
status: string;
/**
* Created At
* Format: date-time
*/
created_at: string;
/** Cancelled At */
cancelled_at: string | null;
/** Completed At */
completed_at: string | null;
};
/**
* AdminUserResponse
* @description Minimal user info for admin dropdowns.
@ -627,6 +845,82 @@ export interface components {
/** Error */
error?: string | null;
};
/**
* ExchangeRequest
* @description Request to create an exchange trade.
*/
ExchangeRequest: {
/**
* Slot Start
* Format: date-time
*/
slot_start: string;
/** Direction */
direction: string;
/** Eur Amount */
eur_amount: number;
};
/**
* ExchangeResponse
* @description Response model for an exchange trade.
*/
ExchangeResponse: {
/** Id */
id: number;
/** User Id */
user_id: number;
/** User Email */
user_email: string;
/**
* Slot Start
* Format: date-time
*/
slot_start: string;
/**
* Slot End
* Format: date-time
*/
slot_end: string;
/** Direction */
direction: string;
/** Eur Amount */
eur_amount: number;
/** Sats Amount */
sats_amount: number;
/** Market Price Eur */
market_price_eur: number;
/** Agreed Price Eur */
agreed_price_eur: number;
/** Premium Percentage */
premium_percentage: number;
/** Status */
status: string;
/**
* Created At
* Format: date-time
*/
created_at: string;
/** Cancelled At */
cancelled_at: string | null;
/** Completed At */
completed_at: string | null;
};
/**
* ExchangeUserContact
* @description User contact info for admin view.
*/
ExchangeUserContact: {
/** Email */
email: string;
/** Contact Email */
contact_email: string | null;
/** Telegram */
telegram: string | null;
/** Signal */
signal: string | null;
/** Nostr Npub */
nostr_npub: string | null;
};
/** HTTPValidationError */
HTTPValidationError: {
/** Detail */
@ -1050,26 +1344,6 @@ export interface operations {
};
};
};
get_exchange_price_api_exchange_price_get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ExchangePriceResponse"];
};
};
};
};
get_profile_api_profile_get: {
parameters: {
query?: never;
@ -1593,4 +1867,255 @@ export interface operations {
};
};
};
get_exchange_price_api_exchange_price_get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ExchangePriceResponse"];
};
};
};
};
create_exchange_api_exchange_post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["ExchangeRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ExchangeResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_my_trades_api_trades_get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ExchangeResponse"][];
};
};
};
};
cancel_my_trade_api_trades__exchange_id__cancel_post: {
parameters: {
query?: never;
header?: never;
path: {
exchange_id: number;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ExchangeResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_upcoming_trades_api_admin_trades_upcoming_get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AdminExchangeResponse"][];
};
};
};
};
get_past_trades_api_admin_trades_past_get: {
parameters: {
query?: {
status?: string | null;
start_date?: string | null;
end_date?: string | null;
user_search?: string | null;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AdminExchangeResponse"][];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
complete_trade_api_admin_trades__exchange_id__complete_post: {
parameters: {
query?: never;
header?: never;
path: {
exchange_id: number;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AdminExchangeResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
mark_no_show_api_admin_trades__exchange_id__no_show_post: {
parameters: {
query?: never;
header?: never;
path: {
exchange_id: number;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AdminExchangeResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
admin_cancel_trade_api_admin_trades__exchange_id__cancel_post: {
parameters: {
query?: never;
header?: never;
path: {
exchange_id: number;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AdminExchangeResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
}