Initial commit: Trading Portal - FastAPI + React + PostgreSQL
This commit is contained in:
Executable
+170
@@ -0,0 +1,170 @@
|
||||
const en: Record<string, string> = {
|
||||
// ── Nav ──
|
||||
'nav.tradingPortal': 'Trading Portal',
|
||||
'nav.profile': '👤 {name}',
|
||||
'nav.panelToggle': 'Toggle panel',
|
||||
'nav.logout': '🚪 Logout',
|
||||
|
||||
// ── Login ──
|
||||
'login.title': 'Trading Portal',
|
||||
'login.subtitle': 'Sign in to your account',
|
||||
'login.username': 'Username',
|
||||
'login.password': 'Password',
|
||||
'login.submit': 'Sign In',
|
||||
'login.signing': 'Signing in...',
|
||||
'login.error': 'Invalid username or password',
|
||||
|
||||
// ── Dashboard ──
|
||||
'dash.chart': '📈 Chart',
|
||||
'dash.watchlist': '👁️ Watchlist',
|
||||
'dash.order': '📝 Order',
|
||||
'dash.signals': '📡 Signals',
|
||||
'dash.quickBuy': '⚡ Quick Buy',
|
||||
'dash.quickSell': '⚡ Quick Sell',
|
||||
'dash.tradeSize': 'Trade Size (USDT)',
|
||||
|
||||
// ── ChartToolbar ──
|
||||
'chart.exchange': 'Exchange',
|
||||
'chart.symbol': 'Symbol',
|
||||
'chart.search': 'Search symbols...',
|
||||
'chart.loading': 'Loading...',
|
||||
'chart.noData': 'No data',
|
||||
'chart.error': 'Error loading symbols',
|
||||
|
||||
// ── Watchlist ──
|
||||
'wl.title': '👁️ Watchlist',
|
||||
'wl.search': 'Search symbols...',
|
||||
'wl.add': '+',
|
||||
'wl.remove': '✕',
|
||||
'wl.loading': 'Loading...',
|
||||
'wl.empty': 'No symbols in watchlist. Click + to add.',
|
||||
'wl.error': 'Failed to load watchlist',
|
||||
|
||||
// ── Order Panel ──
|
||||
'order.buy': 'Buy',
|
||||
'order.sell': 'Sell',
|
||||
'order.limit': 'Limit',
|
||||
'order.market': 'Market',
|
||||
'order.stop': 'Stop',
|
||||
'order.amount': 'Amount',
|
||||
'order.price': 'Price',
|
||||
'order.total': 'Total',
|
||||
'order.place': 'Place Order',
|
||||
'order.placing': 'Placing...',
|
||||
'order.success': '✅ Order placed successfully',
|
||||
'order.loginRequired': '🔒 Login required',
|
||||
'order.noKey': '⚠️ No API key configured',
|
||||
'order.balance': 'Balance: {amount}',
|
||||
'order.available': 'Available: {amount}',
|
||||
|
||||
// ── Signals ──
|
||||
'sig.title': '📡 Live Signals',
|
||||
'sig.strongBuy': '🚀 STRONG BUY',
|
||||
'sig.buy': '📈 BUY',
|
||||
'sig.strongSell': '🔻 STRONG SELL',
|
||||
'sig.sell': '📉 SELL',
|
||||
'sig.cautionLong': '⚠️ CAUTION (LONG)',
|
||||
'sig.cautionShort': '⚠️ CAUTION (SHORT)',
|
||||
'sig.squeeze': '⚡ SQUEEZE ALERT',
|
||||
'sig.noSignal': 'No signals yet — waiting for new candles...',
|
||||
'sig.loading': 'Loading signals...',
|
||||
'sig.price': 'Price',
|
||||
'sig.rsi': 'RSI',
|
||||
'sig.bb': 'BB',
|
||||
'sig.signal': 'Signal',
|
||||
'sig.time': 'Time',
|
||||
|
||||
// ── Profile ──
|
||||
'profile.tabs.info': 'ℹ️ Info',
|
||||
'profile.tabs.keys': '🔑 API Keys',
|
||||
'profile.tabs.history': '📜 History',
|
||||
'profile.tabs.prefs': '⚙️ Settings',
|
||||
'profile.tabs.sessions': '🔐 Sessions',
|
||||
|
||||
'profile.info.title': 'Profile Info',
|
||||
'profile.info.email': 'Email',
|
||||
'profile.info.displayName': 'Display Name',
|
||||
'profile.info.save': 'Save Profile',
|
||||
'profile.info.saved': '✅ Profile saved ✓',
|
||||
'profile.info.error': 'Save failed',
|
||||
|
||||
'profile.password.title': 'Change Password',
|
||||
'profile.password.old': 'Current Password',
|
||||
'profile.password.new': 'New Password',
|
||||
'profile.password.confirm': 'Confirm Password',
|
||||
'profile.password.btn': 'Change Password',
|
||||
'profile.password.validLength': '8+ characters',
|
||||
'profile.password.validNumber': 'Need 1 number',
|
||||
'profile.password.validSpecial': 'Need 1 special char',
|
||||
'profile.password.fillAll': 'Fill all fields',
|
||||
'profile.password.success': '✅ Password changed ✓',
|
||||
|
||||
'profile.keys.title': 'API Keys',
|
||||
'profile.keys.add': '+ Add Key',
|
||||
'profile.keys.edit': '✏️',
|
||||
'profile.keys.delete': '🗑️',
|
||||
'profile.keys.test': 'Test',
|
||||
'profile.keys.empty': 'No API keys configured',
|
||||
'profile.keys.exchange': 'Exchange',
|
||||
'profile.keys.key': 'API Key',
|
||||
'profile.keys.secret': 'Secret',
|
||||
'profile.keys.passphrase': 'Passphrase',
|
||||
'profile.keys.saving': 'Saving...',
|
||||
'profile.keys.addTitle': 'Add API Key',
|
||||
'profile.keys.editTitle': 'Edit API Key',
|
||||
'profile.keys.deleteConfirm': 'Are you sure you want to delete this key?',
|
||||
|
||||
'profile.prefs.title': '⚙️ Settings',
|
||||
'profile.prefs.exchange': 'Default Exchange',
|
||||
'profile.prefs.timeframe': 'Default Timeframe',
|
||||
'profile.prefs.theme': 'Theme',
|
||||
'profile.prefs.upColor': 'Up Color',
|
||||
'profile.prefs.downColor': 'Down Color',
|
||||
'profile.prefs.notifSignal': '🔔 Signal Notifications',
|
||||
'profile.prefs.notifTrade': '💬 Trade Execution',
|
||||
'profile.prefs.notifTradeDesc': 'Gửi thông báo Telegram khi có lệnh thật được đặt',
|
||||
'profile.prefs.language': 'Language',
|
||||
'profile.prefs.tradeSize': '💰 Trade Size (USDT)',
|
||||
'profile.prefs.tradeSizeDesc': 'Số USDT dùng cho mỗi lệnh — Signal trades & Auto trade',
|
||||
'profile.prefs.autoTrade': '🤖 Auto Trade',
|
||||
'profile.prefs.autoTradeDesc': 'Tự động đặt lệnh Spot mua/bán thật — cần chọn token bên dưới',
|
||||
'profile.prefs.autoTradeWarning': '⚠️ Auto Trade đang BẬT — hệ thống sẽ tự động đặt lệnh market thật khi có signal',
|
||||
'profile.prefs.autoTradeTokens': '🪙 Auto Trade Tokens',
|
||||
'profile.prefs.autoTradeTokensDesc': 'Chọn token muốn Auto Trade — chỉ những token được chọn mới được auto trade',
|
||||
'profile.prefs.searchToken': '🔍 Tìm token...',
|
||||
'profile.prefs.noTokens': '⚠️ Chưa chọn token nào — Auto Trade sẽ không hoạt động',
|
||||
'profile.prefs.tracking': '📊 Auto Trades đang theo dõi',
|
||||
'profile.prefs.trackingDesc': '{n} cặp — click ✕ để bỏ theo dõi',
|
||||
'profile.prefs.trackingPaused': '📊 Auto Trades (tạm dừng)',
|
||||
'profile.prefs.trackingPausedDesc': 'Auto trade đang tắt — {n} cặp đã lưu',
|
||||
'profile.prefs.save': '💾 Save Settings',
|
||||
'profile.prefs.saved': '✅ Settings saved ✓',
|
||||
'profile.prefs.spot': 'SPOT',
|
||||
|
||||
'profile.sessions.title': 'Active Sessions',
|
||||
'profile.sessions.current': '(current)',
|
||||
'profile.sessions.revoke': 'Revoke',
|
||||
'profile.sessions.empty': 'No active sessions',
|
||||
'profile.sessions.browser': 'Browser',
|
||||
'profile.sessions.os': 'OS',
|
||||
'profile.sessions.device': 'Device',
|
||||
'profile.sessions.ip': 'IP',
|
||||
'profile.sessions.created': 'Created',
|
||||
|
||||
'profile.history.signals': '📈 Signal Trades',
|
||||
'profile.history.real': '💹 Real Trades',
|
||||
'profile.history.empty': 'No trades yet',
|
||||
'profile.history.side': 'Side',
|
||||
'profile.history.symbol': 'Symbol',
|
||||
'profile.history.price': 'Price',
|
||||
'profile.history.qty': 'Qty',
|
||||
'profile.history.pnl': 'PnL',
|
||||
'profile.history.time': 'Time',
|
||||
|
||||
// ── Signal History ──
|
||||
'sigHistory.pnl': 'Total PnL',
|
||||
'sigHistory.winRate': 'Win Rate',
|
||||
'sigHistory.trades': '{n} trades',
|
||||
};
|
||||
|
||||
export default en;
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
import { createContext, useContext, type ReactNode, type FC } from 'react';
|
||||
import en from './en';
|
||||
import vi from './vi';
|
||||
|
||||
const locales = { en, vi } as const;
|
||||
type Lang = keyof typeof locales;
|
||||
|
||||
type TContext = {
|
||||
lang: Lang;
|
||||
t: (key: string, vars?: Record<string, string | number>) => string;
|
||||
setLang: (l: Lang) => void;
|
||||
};
|
||||
|
||||
const Ctx = createContext<TContext>({
|
||||
lang: 'en',
|
||||
t: (k) => k,
|
||||
setLang: () => {},
|
||||
});
|
||||
|
||||
export const useT = () => useContext(Ctx);
|
||||
|
||||
type Props = { lang: Lang; setLang: (l: Lang) => void; children: ReactNode };
|
||||
export const TProvider: FC<Props> = ({ lang, setLang, children }) => {
|
||||
const t = (key: string, vars?: Record<string, string | number>): string => {
|
||||
const dict = locales[lang] ?? en;
|
||||
let msg = (dict as Record<string, string>)[key];
|
||||
if (msg === undefined) msg = (en as Record<string, string>)[key] ?? key;
|
||||
if (vars) {
|
||||
for (const [k, v] of Object.entries(vars)) {
|
||||
msg = msg.replace(`{${k}}`, String(v));
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
return <Ctx.Provider value={{ lang, t, setLang }}>{children}</Ctx.Provider>;
|
||||
};
|
||||
Executable
+170
@@ -0,0 +1,170 @@
|
||||
const vi: Record<string, string> = {
|
||||
// ── Nav ──
|
||||
'nav.tradingPortal': '📊 Trading Portal',
|
||||
'nav.profile': '👤 {name}',
|
||||
'nav.panelToggle': 'Mở/tắt bảng',
|
||||
'nav.logout': '🚪 Đăng xuất',
|
||||
|
||||
// ── Login ──
|
||||
'login.title': 'Trading Portal',
|
||||
'login.subtitle': 'Đăng nhập tài khoản',
|
||||
'login.username': 'Tên đăng nhập',
|
||||
'login.password': 'Mật khẩu',
|
||||
'login.submit': 'Đăng nhập',
|
||||
'login.signing': 'Đang đăng nhập...',
|
||||
'login.error': 'Sai tên đăng nhập hoặc mật khẩu',
|
||||
|
||||
// ── Dashboard ──
|
||||
'dash.chart': '📈 Biểu đồ',
|
||||
'dash.watchlist': '👁️ Danh sách',
|
||||
'dash.order': '📝 Đặt lệnh',
|
||||
'dash.signals': '📡 Tín hiệu',
|
||||
'dash.quickBuy': '⚡ Mua nhanh',
|
||||
'dash.quickSell': '⚡ Bán nhanh',
|
||||
'dash.tradeSize': 'Khối lượng (USDT)',
|
||||
|
||||
// ── ChartToolbar ──
|
||||
'chart.exchange': 'Sàn',
|
||||
'chart.symbol': 'Cặp',
|
||||
'chart.search': 'Tìm symbol...',
|
||||
'chart.loading': 'Đang tải...',
|
||||
'chart.noData': 'Không có dữ liệu',
|
||||
'chart.error': 'Lỗi tải symbol',
|
||||
|
||||
// ── Watchlist ──
|
||||
'wl.title': '👁️ Danh sách',
|
||||
'wl.search': 'Tìm symbol...',
|
||||
'wl.add': '+',
|
||||
'wl.remove': '✕',
|
||||
'wl.loading': 'Đang tải...',
|
||||
'wl.empty': 'Chưa có symbol nào. Click + để thêm.',
|
||||
'wl.error': 'Lỗi tải danh sách',
|
||||
|
||||
// ── Order Panel ──
|
||||
'order.buy': 'Mua',
|
||||
'order.sell': 'Bán',
|
||||
'order.limit': 'Giới hạn',
|
||||
'order.market': 'Thị trường',
|
||||
'order.stop': 'Dừng',
|
||||
'order.amount': 'Số lượng',
|
||||
'order.price': 'Giá',
|
||||
'order.total': 'Tổng',
|
||||
'order.place': 'Đặt lệnh',
|
||||
'order.placing': 'Đang đặt...',
|
||||
'order.success': '✅ Đặt lệnh thành công',
|
||||
'order.loginRequired': '🔒 Cần đăng nhập',
|
||||
'order.noKey': '⚠️ Chưa cấu hình API key',
|
||||
'order.balance': 'Số dư: {amount}',
|
||||
'order.available': 'Khả dụng: {amount}',
|
||||
|
||||
// ── Signals ──
|
||||
'sig.title': '📡 Tín hiệu trực tiếp',
|
||||
'sig.strongBuy': '🚀 MUA MẠNH',
|
||||
'sig.buy': '📈 MUA',
|
||||
'sig.strongSell': '🔻 BÁN MẠNH',
|
||||
'sig.sell': '📉 BÁN',
|
||||
'sig.cautionLong': '⚠️ THẬN TRỌNG (LONG)',
|
||||
'sig.cautionShort': '⚠️ THẬN TRỌNG (SHORT)',
|
||||
'sig.squeeze': '⚡ SQUEEZE ALERT',
|
||||
'sig.noSignal': 'Chưa có tín hiệu — đang chờ nến mới...',
|
||||
'sig.loading': 'Đang tải tín hiệu...',
|
||||
'sig.price': 'Giá',
|
||||
'sig.rsi': 'RSI',
|
||||
'sig.bb': 'BB',
|
||||
'sig.signal': 'Tín hiệu',
|
||||
'sig.time': 'Thời gian',
|
||||
|
||||
// ── Profile ──
|
||||
'profile.tabs.info': 'ℹ️ Thông tin',
|
||||
'profile.tabs.keys': '🔑 API Keys',
|
||||
'profile.tabs.history': '📜 Lịch sử',
|
||||
'profile.tabs.prefs': '⚙️ Cài đặt',
|
||||
'profile.tabs.sessions': '🔐 Phiên đăng nhập',
|
||||
|
||||
'profile.info.title': 'Thông tin cá nhân',
|
||||
'profile.info.email': 'Email',
|
||||
'profile.info.displayName': 'Tên hiển thị',
|
||||
'profile.info.save': 'Lưu thông tin',
|
||||
'profile.info.saved': '✅ Đã lưu thông tin ✓',
|
||||
'profile.info.error': 'Lỗi lưu thông tin',
|
||||
|
||||
'profile.password.title': 'Đổi mật khẩu',
|
||||
'profile.password.old': 'Mật khẩu hiện tại',
|
||||
'profile.password.new': 'Mật khẩu mới',
|
||||
'profile.password.confirm': 'Xác nhận mật khẩu',
|
||||
'profile.password.btn': 'Đổi mật khẩu',
|
||||
'profile.password.validLength': 'Tối thiểu 8 ký tự',
|
||||
'profile.password.validNumber': 'Cần ít nhất 1 số',
|
||||
'profile.password.validSpecial': 'Cần ít nhất 1 ký tự đặc biệt',
|
||||
'profile.password.fillAll': 'Điền đầy đủ thông tin',
|
||||
'profile.password.success': '✅ Đổi mật khẩu thành công ✓',
|
||||
|
||||
'profile.keys.title': 'API Keys',
|
||||
'profile.keys.add': '+ Thêm Key',
|
||||
'profile.keys.edit': '✏️',
|
||||
'profile.keys.delete': '🗑️',
|
||||
'profile.keys.test': 'Kiểm tra',
|
||||
'profile.keys.empty': 'Chưa có API key nào',
|
||||
'profile.keys.exchange': 'Sàn',
|
||||
'profile.keys.key': 'API Key',
|
||||
'profile.keys.secret': 'Secret Key',
|
||||
'profile.keys.passphrase': 'Passphrase',
|
||||
'profile.keys.saving': 'Đang lưu...',
|
||||
'profile.keys.addTitle': 'Thêm API Key',
|
||||
'profile.keys.editTitle': 'Sửa API Key',
|
||||
'profile.keys.deleteConfirm': 'Bạn chắc chắn muốn xoá key này?',
|
||||
|
||||
'profile.prefs.title': '⚙️ Cài đặt',
|
||||
'profile.prefs.exchange': 'Sàn mặc định',
|
||||
'profile.prefs.timeframe': 'Khung thời gian',
|
||||
'profile.prefs.theme': 'Giao diện',
|
||||
'profile.prefs.upColor': 'Màu tăng',
|
||||
'profile.prefs.downColor': 'Màu giảm',
|
||||
'profile.prefs.notifSignal': '🔔 Thông báo tín hiệu',
|
||||
'profile.prefs.notifTrade': '💬 Thực hiện lệnh',
|
||||
'profile.prefs.notifTradeDesc': 'Gửi thông báo Telegram khi có lệnh thật được đặt',
|
||||
'profile.prefs.language': 'Ngôn ngữ',
|
||||
'profile.prefs.tradeSize': '💰 Khối lượng (USDT)',
|
||||
'profile.prefs.tradeSizeDesc': 'Số USDT dùng cho mỗi lệnh — Signal trades & Auto trade',
|
||||
'profile.prefs.autoTrade': '🤖 Auto Trade',
|
||||
'profile.prefs.autoTradeDesc': 'Tự động đặt lệnh Spot mua/bán thật — cần chọn token bên dưới',
|
||||
'profile.prefs.autoTradeWarning': '⚠️ Auto Trade đang BẬT — hệ thống sẽ tự động đặt lệnh market thật khi có signal',
|
||||
'profile.prefs.autoTradeTokens': '🪙 Token Auto Trade',
|
||||
'profile.prefs.autoTradeTokensDesc': 'Chọn token muốn Auto Trade — chỉ những token được chọn mới được auto trade',
|
||||
'profile.prefs.searchToken': '🔍 Tìm token...',
|
||||
'profile.prefs.noTokens': '⚠️ Chưa chọn token nào — Auto Trade sẽ không hoạt động',
|
||||
'profile.prefs.tracking': '📊 Auto Trades đang theo dõi',
|
||||
'profile.prefs.trackingDesc': '{n} cặp — click ✕ để bỏ theo dõi',
|
||||
'profile.prefs.trackingPaused': '📊 Auto Trades (tạm dừng)',
|
||||
'profile.prefs.trackingPausedDesc': 'Auto trade đang tắt — {n} cặp đã lưu',
|
||||
'profile.prefs.save': '💾 Lưu cài đặt',
|
||||
'profile.prefs.saved': '✅ Đã lưu cài đặt ✓',
|
||||
'profile.prefs.spot': 'SPOT',
|
||||
|
||||
'profile.sessions.title': 'Phiên đăng nhập',
|
||||
'profile.sessions.current': '(hiện tại)',
|
||||
'profile.sessions.revoke': 'Thu hồi',
|
||||
'profile.sessions.empty': 'Không có phiên nào',
|
||||
'profile.sessions.browser': 'Trình duyệt',
|
||||
'profile.sessions.os': 'HĐH',
|
||||
'profile.sessions.device': 'Thiết bị',
|
||||
'profile.sessions.ip': 'IP',
|
||||
'profile.sessions.created': 'Tạo lúc',
|
||||
|
||||
'profile.history.signals': '📈 Lệnh tín hiệu',
|
||||
'profile.history.real': '💹 Lệnh thật',
|
||||
'profile.history.empty': 'Chưa có lệnh nào',
|
||||
'profile.history.side': 'Lệnh',
|
||||
'profile.history.symbol': 'Cặp',
|
||||
'profile.history.price': 'Giá',
|
||||
'profile.history.qty': 'SL',
|
||||
'profile.history.pnl': 'PnL',
|
||||
'profile.history.time': 'Thời gian',
|
||||
|
||||
// ── Signal History ──
|
||||
'sigHistory.pnl': 'Tổng PnL',
|
||||
'sigHistory.winRate': 'Tỉ lệ thắng',
|
||||
'sigHistory.trades': '{n} lệnh',
|
||||
};
|
||||
|
||||
export default vi;
|
||||
Reference in New Issue
Block a user