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:
parent
811fdf2663
commit
361dc8764d
2 changed files with 1379 additions and 49 deletions
|
|
@ -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"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue