Add login and register dialogs, user dropdown menu with profile link and logout option, and integrate with session management hook. Prevent hydration mismatches by conditionally rendering auth elements after mount. Update package-lock.json with new @esbuild dependencies for build tooling.
242 lines
5.6 KiB
TypeScript
242 lines
5.6 KiB
TypeScript
/**
|
|
* Queries de transactions (transações do sistema de escrow)
|
|
* Operações para a tabela transactions
|
|
*/
|
|
|
|
import { query } from "../index";
|
|
|
|
export interface Transaction {
|
|
transaction_id: number;
|
|
listing_id: number | null;
|
|
buyer_id: number;
|
|
seller_id: number;
|
|
item_name: string;
|
|
quantity: number;
|
|
server_name: string;
|
|
amount: number;
|
|
payment_method: "pix" | "tibia_coins" | "gold_coins" | null;
|
|
escrow_fee: number | null;
|
|
transaction_status:
|
|
| "pending"
|
|
| "deposited"
|
|
| "item_delivered"
|
|
| "completed"
|
|
| "disputed"
|
|
| "cancelled"
|
|
| "refunded";
|
|
created_at: Date;
|
|
deposited_at: Date | null;
|
|
delivered_at: Date | null;
|
|
completed_at: Date | null;
|
|
buyer_confirmed: boolean;
|
|
seller_confirmed: boolean;
|
|
buyer_notes: string | null;
|
|
seller_notes: string | null;
|
|
}
|
|
|
|
export interface CreateTransactionData {
|
|
listing_id?: number;
|
|
buyer_id: number;
|
|
seller_id: number;
|
|
item_name: string;
|
|
quantity: number;
|
|
server_name: string;
|
|
amount: number;
|
|
payment_method: "pix" | "tibia_coins" | "gold_coins";
|
|
escrow_fee?: number;
|
|
}
|
|
|
|
export interface UpdateTransactionStatusData {
|
|
transaction_status: Transaction["transaction_status"];
|
|
deposited_at?: Date;
|
|
delivered_at?: Date;
|
|
completed_at?: Date;
|
|
buyer_confirmed?: boolean;
|
|
seller_confirmed?: boolean;
|
|
buyer_notes?: string;
|
|
seller_notes?: string;
|
|
}
|
|
|
|
/**
|
|
* Cria uma nova transação
|
|
*/
|
|
export async function createTransaction(
|
|
data: CreateTransactionData
|
|
): Promise<Transaction> {
|
|
// Calcular escrow_fee se não fornecido (2% do valor)
|
|
const escrowFee = data.escrow_fee ?? Number((data.amount * 0.02).toFixed(2));
|
|
|
|
const result = await query<Transaction>(
|
|
`
|
|
INSERT INTO transactions (
|
|
listing_id, buyer_id, seller_id, item_name, quantity,
|
|
server_name, amount, payment_method, escrow_fee
|
|
)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
RETURNING *
|
|
`,
|
|
[
|
|
data.listing_id || null,
|
|
data.buyer_id,
|
|
data.seller_id,
|
|
data.item_name,
|
|
data.quantity,
|
|
data.server_name,
|
|
data.amount,
|
|
data.payment_method,
|
|
escrowFee,
|
|
]
|
|
);
|
|
|
|
return result.rows[0];
|
|
}
|
|
|
|
/**
|
|
* Busca transação por ID
|
|
*/
|
|
export async function getTransactionById(
|
|
transactionId: number
|
|
): Promise<Transaction | null> {
|
|
const result = await query<Transaction>(
|
|
`
|
|
SELECT * FROM transactions WHERE transaction_id = $1
|
|
`,
|
|
[transactionId]
|
|
);
|
|
|
|
return result.rows[0] || null;
|
|
}
|
|
|
|
/**
|
|
* Atualiza status da transação
|
|
*/
|
|
export async function updateTransactionStatus(
|
|
transactionId: number,
|
|
data: UpdateTransactionStatusData
|
|
): Promise<Transaction | null> {
|
|
const updates: string[] = [];
|
|
const params: any[] = [];
|
|
let paramIndex = 1;
|
|
|
|
updates.push(`transaction_status = $${paramIndex}`);
|
|
params.push(data.transaction_status);
|
|
paramIndex++;
|
|
|
|
if (data.deposited_at !== undefined) {
|
|
updates.push(`deposited_at = $${paramIndex}`);
|
|
params.push(data.deposited_at);
|
|
paramIndex++;
|
|
}
|
|
|
|
if (data.delivered_at !== undefined) {
|
|
updates.push(`delivered_at = $${paramIndex}`);
|
|
params.push(data.delivered_at);
|
|
paramIndex++;
|
|
}
|
|
|
|
if (data.completed_at !== undefined) {
|
|
updates.push(`completed_at = $${paramIndex}`);
|
|
params.push(data.completed_at);
|
|
paramIndex++;
|
|
}
|
|
|
|
if (data.buyer_confirmed !== undefined) {
|
|
updates.push(`buyer_confirmed = $${paramIndex}`);
|
|
params.push(data.buyer_confirmed);
|
|
paramIndex++;
|
|
}
|
|
|
|
if (data.seller_confirmed !== undefined) {
|
|
updates.push(`seller_confirmed = $${paramIndex}`);
|
|
params.push(data.seller_confirmed);
|
|
paramIndex++;
|
|
}
|
|
|
|
if (data.buyer_notes !== undefined) {
|
|
updates.push(`buyer_notes = $${paramIndex}`);
|
|
params.push(data.buyer_notes);
|
|
paramIndex++;
|
|
}
|
|
|
|
if (data.seller_notes !== undefined) {
|
|
updates.push(`seller_notes = $${paramIndex}`);
|
|
params.push(data.seller_notes);
|
|
paramIndex++;
|
|
}
|
|
|
|
params.push(transactionId);
|
|
const result = await query<Transaction>(
|
|
`
|
|
UPDATE transactions
|
|
SET ${updates.join(", ")}
|
|
WHERE transaction_id = $${paramIndex}
|
|
RETURNING *
|
|
`,
|
|
params
|
|
);
|
|
|
|
return result.rows[0] || null;
|
|
}
|
|
|
|
/**
|
|
* Busca transações de um usuário (comprador ou vendedor)
|
|
*/
|
|
export async function getTransactionsByUser(
|
|
userId: number,
|
|
filters: {
|
|
role?: "buyer" | "seller" | "both";
|
|
status?: Transaction["transaction_status"];
|
|
page?: number;
|
|
pageSize?: number;
|
|
} = {}
|
|
): Promise<{ transactions: Transaction[]; total: number }> {
|
|
const { role = "both", status, page = 1, pageSize = 20 } = filters;
|
|
|
|
const conditions: string[] = [];
|
|
const params: any[] = [];
|
|
let paramIndex = 1;
|
|
|
|
if (role === "buyer") {
|
|
conditions.push(`buyer_id = $${paramIndex}`);
|
|
} else if (role === "seller") {
|
|
conditions.push(`seller_id = $${paramIndex}`);
|
|
} else {
|
|
conditions.push(`(buyer_id = $${paramIndex} OR seller_id = $${paramIndex})`);
|
|
}
|
|
params.push(userId);
|
|
paramIndex++;
|
|
|
|
if (status) {
|
|
conditions.push(`transaction_status = $${paramIndex}`);
|
|
params.push(status);
|
|
paramIndex++;
|
|
}
|
|
|
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
|
|
// Contar total
|
|
const countResult = await query<{ count: string }>(
|
|
`SELECT COUNT(*) as count FROM transactions ${whereClause}`,
|
|
params
|
|
);
|
|
const total = parseInt(countResult.rows[0].count, 10);
|
|
|
|
// Buscar transações
|
|
const offset = (page - 1) * pageSize;
|
|
const result = await query<Transaction>(
|
|
`
|
|
SELECT * FROM transactions
|
|
${whereClause}
|
|
ORDER BY created_at DESC
|
|
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
|
|
`,
|
|
[...params, pageSize, offset]
|
|
);
|
|
|
|
return {
|
|
transactions: result.rows,
|
|
total,
|
|
};
|
|
}
|
|
|