From 06cbf8fb9d2516f7f7a6ee78d10c58db1dadab20 Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Thu, 26 Feb 2026 14:36:22 +0000 Subject: [PATCH] feat(auth,dashboard): 2FA mobile session persistence and sparkline cards - Persist 2FA state in sessionStorage to survive mobile page reloads - Reuse existing valid OTP on re-login to avoid rate limiting and duplicate emails - Add embedded sparkline charts to SolduriCompactCard with expand toggle - Mobile dashboard redesigned: 2 pages with enriched compact cards + cashflow type - Login UI simplified: remove gradient bg, subtitle, icon; use design tokens - Focus OTP input when session is restored from 2FA state Co-Authored-By: Claude Sonnet 4.6 --- shared/auth/routes.py | 53 ++- src/assets/css/components/cards.css | 9 +- src/assets/css/patterns/dashboard.css | 14 +- .../dashboard/cards/CashFlowMetricCard.vue | 15 +- .../dashboard/cards/ClientiBalanceCard.vue | 11 +- .../dashboard/cards/FurnizoriBalanceCard.vue | 11 +- .../dashboard/cards/TreasuryDualCard.vue | 32 +- .../components/solduri/SolduriCompactCard.vue | 441 +++++++++++++++++- src/modules/reports/views/DashboardView.vue | 117 +++-- src/shared/components/LoginView.vue | 41 +- src/shared/stores/auth.js | 72 +++ src/shared/styles/login.css | 82 ++-- src/views/LoginWrapper.vue | 4 +- 13 files changed, 654 insertions(+), 248 deletions(-) diff --git a/shared/auth/routes.py b/shared/auth/routes.py index b2af2f3..09ca3f7 100644 --- a/shared/auth/routes.py +++ b/shared/auth/routes.py @@ -406,34 +406,45 @@ def create_auth_router( # Pas 4: Dacă are email → trimitem OTP (2FA) if user_email: - code = await create_otp(user_email, actual_username, login_data.server_id) + # Check for existing valid OTP (mobile page reload scenario) + existing_entry = get_otp_entry(user_email) - if code is None: - # Rate limited - raise HTTPException( - status_code=status.HTTP_429_TOO_MANY_REQUESTS, - detail="Prea multe cereri de cod. Așteptați 10 minute și încercați din nou." + if existing_entry: + # OTP already exists and is valid — skip generation and email + logger.info( + f"[2FA] Reusing existing OTP for {user_email[:3]}*** " + f"(user='{actual_username}', skipping email)" ) + else: + # Generate new OTP + code = await create_otp(user_email, actual_username, login_data.server_id) - # Trimitem emailul - try: - from backend.modules.telegram.utils.email_service import get_email_service - email_service = get_email_service() - email_sent = await email_service.send_auth_code(user_email, code, actual_username) - - if not email_sent: - logger.error(f"[2FA] Failed to send OTP email to {user_email[:3]}***") + if code is None: + # Rate limited raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="Nu s-a putut trimite codul de verificare. Încercați din nou." + status_code=status.HTTP_429_TOO_MANY_REQUESTS, + detail="Prea multe cereri de cod. Așteptați 10 minute și încercați din nou." ) - logger.info(f"[2FA] OTP sent to {user_email[:3]}*** for user '{actual_username}'") + # Trimitem emailul + try: + from backend.modules.telegram.utils.email_service import get_email_service + email_service = get_email_service() + email_sent = await email_service.send_auth_code(user_email, code, actual_username) - except ImportError: - # Email service nu e disponibil — fallback la login direct - logger.warning("[2FA] Email service not available, falling back to direct login") - user_email = None + if not email_sent: + logger.error(f"[2FA] Failed to send OTP email to {user_email[:3]}***") + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail="Nu s-a putut trimite codul de verificare. Încercați din nou." + ) + + logger.info(f"[2FA] OTP sent to {user_email[:3]}*** for user '{actual_username}'") + + except ImportError: + # Email service nu e disponibil — fallback la login direct + logger.warning("[2FA] Email service not available, falling back to direct login") + user_email = None # Pas 5: Dacă 2FA activ → returnăm cerere de cod if user_email: diff --git a/src/assets/css/components/cards.css b/src/assets/css/components/cards.css index 6a873a2..b71fc1d 100644 --- a/src/assets/css/components/cards.css +++ b/src/assets/css/components/cards.css @@ -367,14 +367,12 @@ .metric-card { background: var(--surface-card); - border: 1px solid var(--surface-border); border-radius: var(--card-radius); - padding: var(--card-padding, 1.5rem); + padding: var(--space-xs); transition: all var(--transition-fast); - min-height: var(--card-min-height, 200px); display: flex; flex-direction: column; - gap: var(--card-gap, 1rem); + gap: 2px; } .metric-card:hover { @@ -425,8 +423,7 @@ /* Responsive */ @media (max-width: 768px) { .metric-card { - min-height: calc(var(--card-min-height, 200px) - 40px); - padding: var(--card-padding-sm, 1rem); + padding: var(--space-xs); } .metric-value { diff --git a/src/assets/css/patterns/dashboard.css b/src/assets/css/patterns/dashboard.css index d45a6cc..1b44a16 100644 --- a/src/assets/css/patterns/dashboard.css +++ b/src/assets/css/patterns/dashboard.css @@ -54,7 +54,7 @@ .metrics-row { display: grid; grid-template-columns: repeat(2, 1fr); - gap: var(--space-lg); + gap: var(--space-md); margin-bottom: var(--space-xl); } @@ -66,16 +66,16 @@ /* ===== Breakdown Patterns ===== */ .breakdown-section { - padding-top: var(--space-lg); - border-top: 1px solid var(--color-border); - margin-top: var(--space-lg); + padding-top: var(--space-xs); + border-top: 1px dotted var(--color-border); + margin-top: var(--space-xs); } .breakdown-item { display: flex; justify-content: space-between; align-items: center; - padding: var(--space-sm) 0; + padding: 2px 0; } .breakdown-label { @@ -99,7 +99,7 @@ .breakdown-subitem { display: flex; justify-content: space-between; - padding: var(--space-xs) 0; + padding: 2px 0; } .breakdown-sublabel { @@ -187,5 +187,5 @@ .breakdown-divider { height: 1px; background: var(--color-border); - margin: var(--space-md) 0; + margin: var(--space-xs) 0; } diff --git a/src/modules/reports/components/dashboard/cards/CashFlowMetricCard.vue b/src/modules/reports/components/dashboard/cards/CashFlowMetricCard.vue index ff76c2e..55a49cc 100644 --- a/src/modules/reports/components/dashboard/cards/CashFlowMetricCard.vue +++ b/src/modules/reports/components/dashboard/cards/CashFlowMetricCard.vue @@ -610,10 +610,7 @@ onBeforeUnmount(() => { diff --git a/src/modules/reports/views/DashboardView.vue b/src/modules/reports/views/DashboardView.vue index 9d3b947..dd171e3 100644 --- a/src/modules/reports/views/DashboardView.vue +++ b/src/modules/reports/views/DashboardView.vue @@ -49,9 +49,9 @@
- - - + + + - + - - - - - - - -