diff --git a/src/components/features/dashboard/KpiCard.jsx b/src/components/features/dashboard/KpiCard.jsx index c05eda7..967d2e2 100644 --- a/src/components/features/dashboard/KpiCard.jsx +++ b/src/components/features/dashboard/KpiCard.jsx @@ -17,32 +17,20 @@ export const KpiCard = ({ ? formatPercentage(value) : String(value ?? ''); - const resolvedTone = highlighted ? 'highlight' : tone; + const cardVariant = highlighted ? 'highlight' : 'default'; - const cardClassName = - resolvedTone === 'highlight' - ? 'bg-gradient-to-r from-primary to-[#4a9d8e] text-white' - : resolvedTone === 'neutral' - ? 'bg-[rgba(101,167,165,0.06)] border border-border-accent' - : 'bg-dark-card'; + const titleClassName = highlighted ? 'text-sm text-[#8dc8bf]' : 'text-sm text-text-muted'; - const titleClassName = - resolvedTone === 'highlight' - ? 'text-sm text-white/90' - : resolvedTone === 'neutral' - ? 'text-sm text-text-muted' - : 'text-sm text-text-muted'; - - const valueClassName = - resolvedTone === 'highlight' - ? 'text-3xl font-bold text-white' - : resolvedTone === 'neutral' - ? 'text-2xl font-semibold text-text-primary' - : 'text-2xl font-semibold text-text-primary'; + const valueClassName = highlighted + ? 'text-3xl font-bold text-text-primary' + : tone === 'neutral' + ? 'text-2xl font-semibold text-text-primary' + : 'text-2xl font-semibold text-text-primary'; return (

{title}

diff --git a/src/components/features/deposits/DepositOptionCard.jsx b/src/components/features/deposits/DepositOptionCard.jsx index c292478..4dac9b5 100644 --- a/src/components/features/deposits/DepositOptionCard.jsx +++ b/src/components/features/deposits/DepositOptionCard.jsx @@ -1,6 +1,5 @@ import { useState } from 'react'; import { Card } from '../../ui/Card'; -import { Button } from '../../ui/Button'; import { useTranslation } from 'react-i18next'; const COPYABLE_KEYS = [ @@ -31,11 +30,11 @@ export const DepositOptionCard = ({ option }) => { const detailEntries = Object.entries(details).filter(([, v]) => v); return ( - +

{option.label}

- + {option.currency}
@@ -48,13 +47,13 @@ export const DepositOptionCard = ({ option }) => {

{value}

{COPYABLE_KEYS.includes(key) && ( - + )}
))} diff --git a/src/components/features/requests/DepositForm.jsx b/src/components/features/requests/DepositForm.jsx index 6dfea42..9b2ade2 100644 --- a/src/components/features/requests/DepositForm.jsx +++ b/src/components/features/requests/DepositForm.jsx @@ -212,7 +212,7 @@ export const DepositForm = ({ userEmail, depositOptions = [] }) => { ref={fileInputRef} accept="image/jpeg,image/png,image/webp,application/pdf" onChange={handleFileChange} - className="block w-full text-sm text-text-muted file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-primary/10 file:text-primary hover:file:bg-primary/20 cursor-pointer" + className="block w-full text-sm text-text-muted file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-[rgba(101,167,165,0.10)] file:text-[#8dc8bf] hover:file:bg-[rgba(101,167,165,0.18)] cursor-pointer" /> {attachment && (

@@ -221,7 +221,7 @@ export const DepositForm = ({ userEmail, depositOptions = [] }) => { )} -

+

{t('deposits.processingHoursTitle')}

• {t('deposits.processingHoursLine1')}

• {t('deposits.processingHoursLine2')}

@@ -232,10 +232,10 @@ export const DepositForm = ({ userEmail, depositOptions = [] }) => { role="alert" className={`rounded-lg p-4 ${ message.type === 'error' - ? 'bg-[rgba(239,83,80,0.15)] text-error' + ? 'badge-rejected' : message.type === 'success' - ? 'bg-[rgba(76,175,80,0.15)] text-success' - : 'bg-[rgba(101,167,165,0.15)] text-info' + ? 'badge-completed' + : 'info-box' }`} >

{message.text}

diff --git a/src/components/features/requests/WithdrawalForm.jsx b/src/components/features/requests/WithdrawalForm.jsx index b4c67ed..f3aab28 100644 --- a/src/components/features/requests/WithdrawalForm.jsx +++ b/src/components/features/requests/WithdrawalForm.jsx @@ -105,7 +105,7 @@ export const WithdrawalForm = ({ userEmail, currentBalance }) => { {formatCurrency(confirmModal.withdrawalAmount)}
{confirmModal.hasFee ? ( -
+
Comisión de trading ({confirmModal.feePercentage}%) {formatCurrency(confirmModal.feeAmount)}
@@ -115,7 +115,7 @@ export const WithdrawalForm = ({ userEmail, currentBalance }) => {

)} {confirmModal.hasFee && ( -
+
Total debitado del portfolio {formatCurrency(confirmModal.withdrawalAmount + confirmModal.feeAmount)} @@ -193,7 +193,7 @@ export const WithdrawalForm = ({ userEmail, currentBalance }) => { /> )} -
+

{t('withdrawals.processingHoursTitle')}

• {t('withdrawals.processingHoursLine1')}

• {t('withdrawals.processingHoursLine2')}

@@ -204,9 +204,7 @@ export const WithdrawalForm = ({ userEmail, currentBalance }) => {
{message.text} diff --git a/src/components/ui/Button.jsx b/src/components/ui/Button.jsx index f8e419b..4638f1d 100644 --- a/src/components/ui/Button.jsx +++ b/src/components/ui/Button.jsx @@ -10,12 +10,15 @@ export const Button = ({ 'px-6 py-3 rounded-lg font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-dark-bg disabled:opacity-50 disabled:cursor-not-allowed'; const variants = { - primary: 'bg-primary text-white hover:bg-primary/80 focus:ring-primary', + primary: + 'bg-[rgba(101,167,165,0.15)] text-[#8dc8bf] border border-[rgba(101,167,165,0.35)] hover:bg-[rgba(101,167,165,0.25)] hover:border-[rgba(101,167,165,0.55)] focus:ring-primary', secondary: - 'bg-accent-dim text-white border border-[rgba(101,167,165,0.45)] hover:bg-accent-glow hover:border-primary focus:ring-primary', - danger: 'bg-error text-white hover:bg-error/80 focus:ring-error', + 'bg-[rgba(101,167,165,0.10)] text-[#8dc8bf] border border-[rgba(101,167,165,0.35)] hover:bg-[rgba(101,167,165,0.18)] hover:border-[rgba(101,167,165,0.55)] focus:ring-primary', + danger: + 'bg-[rgba(196,107,107,0.14)] text-[#c46b6b] border border-[rgba(196,107,107,0.18)] hover:bg-[rgba(196,107,107,0.22)] focus:ring-error', outline: - 'border border-[rgba(101,167,165,0.45)] text-primary bg-accent-dim hover:bg-accent-glow hover:border-primary focus:ring-primary', + 'bg-[rgba(101,167,165,0.10)] text-[#8dc8bf] border border-[rgba(101,167,165,0.35)] hover:bg-[rgba(101,167,165,0.18)] hover:border-[rgba(101,167,165,0.55)] focus:ring-primary', + copy: 'btn-copy text-xs focus:ring-primary', }; return ( diff --git a/src/components/ui/Button.test.jsx b/src/components/ui/Button.test.jsx index f73e303..2bb63b7 100644 --- a/src/components/ui/Button.test.jsx +++ b/src/components/ui/Button.test.jsx @@ -18,25 +18,25 @@ describe('Button', () => { it('applies primary variant styles by default', () => { render(); const button = screen.getByText('Primary'); - expect(button).toHaveClass('bg-primary'); + expect(button).toHaveClass('focus:ring-primary'); }); it('applies secondary variant styles', () => { render(); const button = screen.getByText('Secondary'); - expect(button).toHaveClass('bg-accent-dim'); + expect(button).toHaveClass('focus:ring-primary'); }); it('applies danger variant styles', () => { render(); const button = screen.getByText('Danger'); - expect(button).toHaveClass('bg-error'); + expect(button).toHaveClass('focus:ring-error'); }); it('applies outline variant styles', () => { render(); const button = screen.getByText('Outline'); - expect(button).toHaveClass('bg-accent-dim'); + expect(button).toHaveClass('focus:ring-primary'); }); it('disables button when disabled prop is true', () => { diff --git a/src/components/ui/Card.jsx b/src/components/ui/Card.jsx index 90f196c..444139c 100644 --- a/src/components/ui/Card.jsx +++ b/src/components/ui/Card.jsx @@ -1,8 +1,13 @@ -export const Card = ({ children, className = '', title }) => { +export const Card = ({ children, className = '', title, variant = 'default' }) => { + const variantClass = + variant === 'highlight' + ? 'winbit-card--highlight' + : variant === 'compact' + ? 'winbit-card--compact' + : 'winbit-card'; + return ( -
+
{title &&

{title}

} {children}
diff --git a/src/components/ui/Card.test.jsx b/src/components/ui/Card.test.jsx index ee92c64..a8db7be 100644 --- a/src/components/ui/Card.test.jsx +++ b/src/components/ui/Card.test.jsx @@ -27,6 +27,6 @@ describe('Card', () => { it('has default card styles', () => { const { container } = render(Content); const card = container.firstChild; - expect(card).toHaveClass('bg-dark-card', 'rounded-lg', 'border-border-dark'); + expect(card).toHaveClass('winbit-card'); }); }); diff --git a/src/index.css b/src/index.css index 76bdca2..8b020fc 100644 --- a/src/index.css +++ b/src/index.css @@ -4,7 +4,7 @@ @layer base { body { - background: #0a0a0a; + background: #0d0f10; color: #e8e8e8; font-family: 'Montserrat', @@ -18,7 +18,7 @@ input, select, textarea { - background: rgba(20, 20, 20, 0.5); + background: rgba(15, 18, 18, 0.8); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 0.5rem; padding: 0.75rem 1rem; @@ -51,4 +51,111 @@ .container { @apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8; } + + .winbit-card { + background: linear-gradient(180deg, rgba(18, 24, 24, 0.92) 0%, rgba(15, 18, 18, 0.96) 100%); + border: 1px solid rgba(101, 167, 165, 0.22); + border-radius: 8px; + padding: 28px 26px; + box-shadow: + inset 3px 0 0 rgba(101, 167, 165, 0.22), + 0 10px 28px rgba(0, 0, 0, 0.22); + } + + .winbit-card--highlight { + background: linear-gradient(180deg, rgba(28, 40, 40, 0.94) 0%, rgba(20, 28, 28, 0.98) 100%); + border: 1px solid rgba(101, 167, 165, 0.32); + border-radius: 8px; + padding: 28px 26px; + box-shadow: + inset 4px 0 0 rgba(101, 167, 165, 0.3), + 0 12px 30px rgba(0, 0, 0, 0.24); + } + + .winbit-card--compact { + background: linear-gradient(180deg, rgba(18, 24, 24, 0.92) 0%, rgba(15, 18, 18, 0.96) 100%); + border: 1px solid rgba(101, 167, 165, 0.22); + border-radius: 8px; + padding: 20px 22px; + box-shadow: + inset 3px 0 0 rgba(101, 167, 165, 0.22), + 0 10px 28px rgba(0, 0, 0, 0.22); + } + + .section-subtitle { + color: #65a7a5; + padding-bottom: 8px; + border-bottom: 1px solid rgba(101, 167, 165, 0.35); + font-size: 0.95rem; + margin-top: 4px; + } + + .badge-completed { + background: rgba(101, 167, 165, 0.14); + color: #8dc8bf; + border: 1px solid rgba(101, 167, 165, 0.2); + } + + .badge-pending { + background: rgba(194, 170, 114, 0.14); + color: #c2aa72; + border: 1px solid rgba(194, 170, 114, 0.18); + } + + .badge-rejected { + background: rgba(196, 107, 107, 0.14); + color: #c46b6b; + border: 1px solid rgba(196, 107, 107, 0.18); + } + + .badge-cancelled { + background: rgba(20, 20, 20, 0.55); + color: #888888; + border: 1px solid rgba(255, 255, 255, 0.08); + } + + .info-box { + background: rgba(101, 167, 165, 0.1); + border: 1px solid rgba(101, 167, 165, 0.22); + border-radius: 8px; + padding: 20px; + } + + .row-positive { + background: rgba(101, 167, 165, 0.1); + } + + .row-positive:hover { + background: rgba(101, 167, 165, 0.16); + } + + .row-negative { + background: rgba(196, 107, 107, 0.1); + } + + .row-negative:hover { + background: rgba(196, 107, 107, 0.16); + } + + .row-fee { + background: rgba(101, 167, 165, 0.1); + } + + .row-fee:hover { + background: rgba(101, 167, 165, 0.16); + } + + .btn-copy { + background: rgba(101, 167, 165, 0.1); + border: 1px solid rgba(101, 167, 165, 0.35); + color: #8dc8bf; + border-radius: 6px; + padding: 8px 16px; + transition: all 0.2s ease; + } + + .btn-copy:hover { + background: rgba(101, 167, 165, 0.18); + border-color: rgba(101, 167, 165, 0.55); + } } diff --git a/src/pages/DashboardPage.jsx b/src/pages/DashboardPage.jsx index 368be21..5d1ce0c 100644 --- a/src/pages/DashboardPage.jsx +++ b/src/pages/DashboardPage.jsx @@ -76,14 +76,44 @@ const getLastUpdateDate = () => { return `${yDay} ${MONTHS_SHORT[yMonth]} ${yYear} - 18:00 (UTC-3)`; }; -const formatChartAxisDate = (isoDate) => { +const getLastUpdateIsoLabel = () => { + const now = new Date(); + const arFormatter = new Intl.DateTimeFormat('en-US', { + timeZone: AR_TZ, + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + hour12: false, + }); + const parts = {}; + for (const { type, value } of arFormatter.formatToParts(now)) { + parts[type] = value; + } + + const currentHour = parseInt(parts.hour, 10); + const day = parseInt(parts.day, 10); + const month = parseInt(parts.month, 10); + const year = parseInt(parts.year, 10); + + if (currentHour >= 18) { + return `${day} ${MONTHS_SHORT[month - 1]}`; + } + + const yesterday = new Date(Date.UTC(year, month - 1, day - 1)); + return `${yesterday.getUTCDate()} ${MONTHS_SHORT[yesterday.getUTCMonth()]}`; +}; + +const formatChartAxisDate = (isoDate, overrideLabel) => { + if (overrideLabel) return overrideLabel; if (!isoDate) return ''; const d = new Date(`${isoDate}T12:00:00.000Z`); if (isNaN(d.getTime())) return ''; return `${d.getUTCDate()} ${MONTHS_SHORT[d.getUTCMonth()]}`; }; -const PortfolioLineChart = ({ series, title }) => { +const PortfolioLineChart = ({ series, title, endAxisLabel }) => { const [hoveredPoint, setHoveredPoint] = useState(null); const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 }); const width = 900; @@ -208,7 +238,7 @@ const PortfolioLineChart = ({ series, title }) => { > - + @@ -291,11 +321,13 @@ const PortfolioLineChart = ({ series, title }) => { {hoveredPoint && (
@@ -306,11 +338,7 @@ const PortfolioLineChart = ({ series, title }) => {
{series[0]?.date ? formatChartAxisDate(series[0].date) : ''} - - {series[series.length - 1]?.date - ? formatChartAxisDate(series[series.length - 1].date) - : ''} - + {endAxisLabel || formatChartAxisDate(series[series.length - 1]?.date)}
); @@ -387,6 +415,7 @@ export const DashboardPage = () => { }, [fullSeries, rangeKey]); const lastUpdate = useMemo(() => getLastUpdateDate(), []); + const endAxisLabel = useMemo(() => getLastUpdateIsoLabel(), []); if (loading) { return ( @@ -418,23 +447,23 @@ export const DashboardPage = () => {

{t('dashboard.welcomeBack', { name: formatName(data.name) })}

-

{t('dashboard.subtitle')}

+

{t('dashboard.subtitle')}

- {/* Row 1: Capital invertido + Valor del portafolio */} + {/* Row 1: Valor del portfolio + Capital invertido */}
- +
{/* Row 2: Resultado histórico (%) + Resultado histórico (USD) */} @@ -452,7 +481,7 @@ export const DashboardPage = () => { />
- {/* Row 3: Resultado 2026 (%) + Resultado 2026 (USD) */} + {/* Row 3: Resultado estrategia actual (%) + Resultado estrategia actual (USD) */}
{ />
-
+

@@ -493,8 +519,8 @@ export const DashboardPage = () => { onClick={() => setRangeKey(opt.key)} className={`rounded-full border px-3 py-1 text-sm font-medium transition-colors ${ isActive - ? 'bg-primary text-white border-primary' - : 'bg-dark-section text-text-muted border-border-dark hover:border-primary hover:text-primary' + ? 'bg-[rgba(101,167,165,0.18)] text-[#8dc8bf] border-[rgba(101,167,165,0.35)]' + : 'bg-transparent text-text-muted border-[rgba(255,255,255,0.08)] hover:border-[rgba(101,167,165,0.35)] hover:text-primary' }`} > {opt.label} @@ -507,9 +533,13 @@ export const DashboardPage = () => { {historyLoading ? (
{t('dashboard.chart.loading')}
) : series.length >= 2 ? ( - + ) : ( -
+
{t('dashboard.chart.noData')}
)} diff --git a/src/pages/HistoryPage.jsx b/src/pages/HistoryPage.jsx index 2668168..3ff6d91 100644 --- a/src/pages/HistoryPage.jsx +++ b/src/pages/HistoryPage.jsx @@ -58,13 +58,12 @@ const formatMonthYearSpace = (dateStr) => { return mm && yyyy ? `${mm} ${yyyy}` : ''; }; -/** Formats YYYY-MM as "Ene 2026" (es) or "Jan 2026" (en). Returns label as-is if not YYYY-MM. */ const formatPeriodLabel = (label, t) => { if (!label || typeof label !== 'string') return label ?? ''; const m = label.match(/^(\d{4})-(\d{2})$/); if (!m) return label; const year = parseInt(m[1], 10); - const monthIndex = parseInt(m[2], 10) - 1; // 0..11 + const monthIndex = parseInt(m[2], 10) - 1; const monthKey = `history.monthsShort.${monthIndex}`; const monthAbbrev = t(monthKey); return monthAbbrev && !monthAbbrev.includes('.') ? `${monthAbbrev} ${year}` : label; @@ -89,10 +88,8 @@ export const HistoryPage = () => { const translateMovement = (movement) => { const raw = String(movement ?? '').trim(); const rawUpper = raw.toUpperCase(); - // Strip any weird/invisible characters (e.g. NBSP, zero-width) and keep only A-Z and underscores. const canonicalUpper = rawUpper.replace(/[^A-Z_]/g, ''); - // Hard guard: always translate this movement (including common typo with one "m"). if (canonicalUpper === 'REFERRAL_COMMISSION' || canonicalUpper === 'REFERRAL_COMISSION') { return t('history.movement.referral_commission', 'Comisión por referido'); } @@ -119,14 +116,10 @@ export const HistoryPage = () => { if (m === 'deposit_reversal' || m === 'deposit_reversa' || m === 'depósito_revertido') { return t('history.movement.deposit_reversal', 'Depósito revertido'); } - // Accept multiple formats: - // - REFERRAL_COMMISSION (backend enum) - // - referral_commission - // - referral commission / referral-commission const refKey = m.replace(/[\s-]+/g, '_'); const looksLikeReferral = canonicalUpper === 'REFERRAL_COMMISSION' || - canonicalUpper === 'REFERRAL_COMISSION' || // common typo (one "m") + canonicalUpper === 'REFERRAL_COMISSION' || rawUpper === 'REFERRAL COMMISSION' || (rawUpper.includes('REFERRAL') && rawUpper.includes('COMMISSION')); @@ -236,12 +229,12 @@ export const HistoryPage = () => { const statusPillClass = (status) => { const s = normalize(status); if (s === 'rechazado' || s === 'rejected') { - return 'bg-[rgba(239,83,80,0.15)] text-error'; + return 'badge-rejected'; } if (s === 'pendiente' || s === 'pending') { - return 'bg-[rgba(255,152,0,0.15)] text-warning'; + return 'badge-pending'; } - return 'bg-primary text-white'; + return 'badge-completed'; }; const movementKind = (movement) => { @@ -251,7 +244,6 @@ export const HistoryPage = () => { return null; }; - // DEPOSIT_REVERSAL stores positive amount; display as negative (outflow) const displayAmount = (row) => { const m = normalize(row?.movement); if (m === 'deposit_reversal') return -Math.abs(Number(row?.amount) || 0); @@ -308,18 +300,18 @@ export const HistoryPage = () => { const m = normalize(row?.movement); if (m === 'trading_fee' || m === 'trading_fee_adjustment') { - return 'bg-[rgba(101,167,165,0.15)] hover:bg-[rgba(101,167,165,0.25)]'; + return 'row-fee'; } if (m === 'operating_result') { const pct = Number(row?.operatingResultPercent); const sign = Number.isFinite(pct) ? pct : Number(row?.amount); - if (sign > 0) return 'bg-[rgba(76,175,80,0.15)] hover:bg-[rgba(76,175,80,0.25)]'; - if (sign < 0) return 'bg-[rgba(239,83,80,0.15)] hover:bg-[rgba(239,83,80,0.25)]'; - return 'bg-dark-section hover:bg-accent-dim'; + if (sign > 0) return 'row-positive'; + if (sign < 0) return 'row-negative'; + return 'hover:bg-[rgba(101,167,165,0.08)]'; } - return 'hover:bg-accent-dim'; + return 'hover:bg-[rgba(101,167,165,0.08)]'; }; const translatedError = (() => { @@ -395,7 +387,7 @@ export const HistoryPage = () => {

{t('history.title')}

-

{t('history.subtitle')}

+

{t('history.subtitle')}

{rows.length === 0 ? ( @@ -409,10 +401,7 @@ export const HistoryPage = () => { {/* Mobile cards */}
{mobileVisibleRows.map((row, idx) => ( -
+
@@ -487,7 +476,7 @@ export const HistoryPage = () => {