ادخل لعالم التمكين والفهم
-تسجيل دخول آمن وسريع عبر رمز الواتساب المعتمد
-ادخل لعالم التمكين والفهم
+تسجيل دخول آمن وسريع عبر رمز الواتساب المعتمد
+diff --git a/backend/app/Core/RedisClient.php b/backend/app/Core/RedisClient.php index 1adf7a9..2dbb926 100644 --- a/backend/app/Core/RedisClient.php +++ b/backend/app/Core/RedisClient.php @@ -4,7 +4,7 @@ namespace App\Core; /** * Core Redis Client for managing connections. - * Strict environment variable enforcement (No default fallbacks). + * Handles Sessions, Rate Limiting, OTP, and Caching. */ class RedisClient { @@ -18,7 +18,7 @@ class RedisClient if (self::$instance === null) { $host = getenv('REDIS_HOST'); $port = getenv('REDIS_PORT'); - $password = getenv('REDIS_PASSWORD') ?: null; + $password = getenv('REDIS_PASSWORD'); $missing = []; if ($host === false || $host === '') $missing[] = 'REDIS_HOST'; @@ -36,10 +36,19 @@ class RedisClient throw new \RuntimeException("Could not connect to Redis server at {$host}:{$port}"); } - // Authenticate if password is provided - if ($password) { - if (!$redis->auth($password)) { - throw new \RuntimeException("Redis authentication failed for host {$host}:{$port}."); + // Authenticate ONLY if a non-empty password is provided in environment + if (!empty($password)) { + try { + if (!$redis->auth($password)) { + throw new \RuntimeException("Redis authentication failed with provided REDIS_PASSWORD."); + } + } catch (\RedisException $authEx) { + // If server does not have a password configured, catch and report clearly + if (str_contains($authEx->getMessage(), 'no password is set') || str_contains($authEx->getMessage(), 'without any password')) { + error_log("⚠️ [Redis Notice] REDIS_PASSWORD was provided in .env, but Redis server on {$host}:{$port} is configured without password."); + } else { + throw $authEx; + } } } diff --git a/backend/app/Views/StudentPortal.php b/backend/app/Views/StudentPortal.php index 3ac7f3a..627ee22 100644 --- a/backend/app/Views/StudentPortal.php +++ b/backend/app/Views/StudentPortal.php @@ -17,31 +17,521 @@ class StudentPortal - - - - + + * { + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: 'Cairo', system-ui, -apple-system, sans-serif; + } + + body { + background-color: var(--bg-dark); + color: var(--text-primary); + min-height: 100vh; + display: flex; + flex-direction: column; + justify-content: space-between; + background-image: + radial-gradient(circle at 15% 15%, rgba(0, 245, 212, 0.05) 0%, transparent 40%), + radial-gradient(circle at 85% 85%, rgba(255, 209, 102, 0.04) 0%, transparent 40%), + linear-gradient(to right, rgba(255, 255, 255, 0.02) 1px, transparent 1px), + linear-gradient(to bottom, rgba(255, 255, 255, 0.02) 1px, transparent 1px); + background-size: 100% 100%, 100% 100%, 32px 32px, 32px 32px; + } + + .container { + max-width: 1140px; + margin: 0 auto; + padding: 0 20px; + width: 100%; + } + + /* Header */ + header { + border-bottom: 1px solid var(--border); + background-color: rgba(11, 19, 43, 0.95); + backdrop-filter: blur(12px); + position: sticky; + top: 0; + z-index: 50; + } + + .header-content { + height: 68px; + display: flex; + align-items: center; + justify-content: space-between; + } + + .logo-area { + display: flex; + align-items: center; + gap: 12px; + text-decoration: none; + } + + .logo-badge { + width: 42px; + height: 42px; + border-radius: 12px; + background: linear-gradient(135deg, var(--accent-cyan), var(--accent-cyan-dark)); + display: flex; + align-items: center; + justify-content: center; + color: var(--bg-dark); + font-size: 20px; + font-weight: 900; + box-shadow: 0 0 20px rgba(0, 245, 212, 0.3); + } + + .brand-title { + font-size: 20px; + font-weight: 900; + color: #FFFFFF; + letter-spacing: 0.5px; + } + + .portal-pill { + font-size: 11px; + font-weight: 700; + padding: 2px 10px; + border-radius: 999px; + background: rgba(0, 245, 212, 0.12); + color: var(--accent-cyan); + border: 1px solid rgba(0, 245, 212, 0.35); + margin-right: 8px; + } + + .nav-link { + font-size: 13px; + color: var(--text-secondary); + text-decoration: none; + display: flex; + align-items: center; + gap: 6px; + transition: 0.2s; + } + + .nav-link:hover { + color: var(--accent-gold); + } + + .nav-link-highlight { + color: var(--accent-gold); + font-weight: 800; + text-decoration: underline; + } + + /* Main Content */ + main { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + padding: 40px 0; + } + + /* Auth Card */ + .auth-card-wrapper { + width: 100%; + max-width: 460px; + margin: 0 auto; + } + + .auth-header { + text-align: center; + margin-bottom: 24px; + } + + .icon-box { + width: 64px; + height: 64px; + border-radius: 18px; + background: var(--bg-card); + border: 1px solid rgba(0, 245, 212, 0.4); + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--accent-cyan); + margin-bottom: 16px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); + } + + .auth-title { + font-size: 24px; + font-weight: 900; + color: #FFFFFF; + margin-bottom: 6px; + } + + .auth-subtitle { + font-size: 13px; + color: var(--text-muted); + line-height: 1.6; + } + + .auth-box { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 20px; + padding: 32px; + box-shadow: 0 16px 40px rgba(0, 0, 0, 0.4); + position: relative; + } + + /* Forms & Inputs */ + .form-group { + margin-bottom: 20px; + } + + .form-label { + display: block; + font-size: 12px; + font-weight: 700; + color: var(--text-secondary); + margin-bottom: 8px; + } + + .input-text { + width: 100%; + background: var(--bg-input); + border: 1px solid var(--border); + border-radius: 12px; + padding: 12px 16px; + font-size: 14px; + color: #FFFFFF; + outline: none; + transition: border-color 0.2s, box-shadow 0.2s; + } + + .input-text:focus { + border-color: var(--accent-cyan); + box-shadow: 0 0 0 3px rgba(0, 245, 212, 0.15); + } + + .input-text::placeholder { + color: #52607D; + } + + .phone-input-group { + display: flex; + align-items: center; + direction: ltr; + position: relative; + } + + .country-badge { + position: absolute; + left: 10px; + background: #121A33; + border: 1px solid var(--border); + border-radius: 8px; + padding: 6px 10px; + font-size: 12px; + font-weight: 700; + color: var(--accent-cyan); + display: flex; + align-items: center; + gap: 4px; + user-select: none; + } + + .input-phone { + padding-left: 96px !important; + font-family: monospace; + font-size: 15px; + letter-spacing: 1px; + } + + .form-hint { + font-size: 11px; + color: var(--text-muted); + margin-top: 6px; + display: block; + } + + /* Buttons */ + .btn-primary { + width: 100%; + padding: 14px; + background: linear-gradient(135deg, var(--accent-cyan), var(--accent-cyan-dark)); + color: var(--bg-dark); + border: none; + border-radius: 12px; + font-size: 15px; + font-weight: 800; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + box-shadow: 0 8px 20px rgba(0, 245, 212, 0.25); + transition: transform 0.15s, opacity 0.15s; + } + + .btn-primary:hover { + opacity: 0.95; + transform: translateY(-1px); + } + + .btn-primary:active { + transform: translateY(0); + } + + .btn-primary:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + } + + .btn-secondary { + background: none; + border: none; + color: var(--text-muted); + font-size: 12px; + cursor: pointer; + padding: 6px 0; + transition: color 0.2s; + } + + .btn-secondary:hover { + color: #FFFFFF; + } + + .btn-link-cyan { + color: var(--accent-cyan); + font-weight: 700; + text-decoration: underline; + } + + .otp-input { + text-align: center; + font-size: 26px; + font-family: monospace; + letter-spacing: 10px; + font-weight: 900; + color: var(--accent-cyan); + border-color: rgba(0, 245, 212, 0.5); + padding: 14px; + } + + /* Alerts */ + .alert { + padding: 12px 16px; + border-radius: 12px; + font-size: 12.5px; + margin-bottom: 20px; + display: flex; + align-items: center; + gap: 8px; + line-height: 1.5; + } + + .alert-error { + background: rgba(239, 68, 68, 0.12); + border: 1px solid rgba(239, 68, 68, 0.35); + color: #FCA5A5; + } + + .alert-success { + background: rgba(0, 245, 212, 0.12); + border: 1px solid rgba(0, 245, 212, 0.35); + color: var(--accent-cyan); + } + + .auth-footer { + margin-top: 24px; + padding-top: 16px; + border-top: 1px solid rgba(255, 255, 255, 0.08); + text-align: center; + font-size: 11.5px; + color: var(--text-muted); + } + + /* Student Dashboard */ + .dashboard-hero { + background: linear-gradient(135deg, var(--bg-card), var(--bg-card-hover)); + border: 1px solid var(--border); + border-radius: 24px; + padding: 32px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; + flex-wrap: wrap; + margin-bottom: 28px; + } + + .readiness-box { + background: rgba(11, 19, 43, 0.85); + border: 1px solid var(--border); + border-radius: 16px; + padding: 16px 20px; + display: flex; + align-items: center; + gap: 16px; + min-width: 240px; + } + + .gauge-circle { + width: 56px; + height: 56px; + border-radius: 50%; + border: 4px solid var(--accent-cyan); + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + font-weight: 900; + color: var(--accent-cyan); + box-shadow: 0 0 20px rgba(0, 245, 212, 0.3); + } + + .cards-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 20px; + margin-bottom: 28px; + } + + .feature-card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 20px; + padding: 24px; + transition: border-color 0.2s, transform 0.2s; + cursor: pointer; + } + + .feature-card:hover { + border-color: var(--accent-cyan); + transform: translateY(-2px); + } + + .quiz-demo-box { + background: var(--bg-card); + border: 1px solid rgba(0, 245, 212, 0.4); + border-radius: 24px; + padding: 32px; + margin-bottom: 28px; + } + + .interactive-quiz-container { + background: rgba(11, 19, 43, 0.9); + border: 1px solid var(--border); + border-radius: 16px; + padding: 24px; + margin-top: 16px; + } + + .quiz-option { + width: 100%; + text-align: right; + padding: 12px 16px; + border-radius: 12px; + border: 1px solid var(--border); + background: var(--bg-card); + color: var(--text-secondary); + font-size: 13px; + font-weight: 700; + margin-bottom: 10px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + transition: border-color 0.2s; + } + + .quiz-option:hover { + border-color: var(--accent-cyan); + } + + .quiz-option.correct { + background: rgba(16, 185, 129, 0.15); + border-color: #10B981; + color: #6EE7B7; + } + + .quiz-option.incorrect { + background: rgba(239, 68, 68, 0.15); + border-color: #EF4444; + color: #FCA5A5; + } + + /* Watermark */ + @keyframes bounce-watermark { + 0%, 100% { transform: translate(10px, 10px); } + 50% { transform: translate(120px, 60px); } + } + .watermark-anim { + position: fixed; + bottom: 20px; + left: 20px; + z-index: 99; + pointer-events: none; + opacity: 0.35; + animation: bounce-watermark 12s infinite ease-in-out; + background: rgba(0, 0, 0, 0.7); + border: 1px solid rgba(255, 255, 255, 0.2); + padding: 4px 10px; + border-radius: 6px; + font-family: monospace; + font-size: 11px; + color: var(--accent-cyan); + } + + /* Footer */ + footer { + border-top: 1px solid rgba(255, 255, 255, 0.08); + padding: 24px 0; + background: var(--bg-dark); + color: var(--text-muted); + font-size: 12px; + } + + .footer-content { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 12px; + } + + .spinner { + width: 18px; + height: 18px; + border: 3px solid rgba(11, 19, 43, 0.3); + border-top-color: var(--bg-dark); + border-radius: 50%; + animation: spin 0.8s linear infinite; + } + + @keyframes spin { + to { transform: rotate(360deg); } + } + + + - - -
+ - -تسجيل دخول آمن وسريع عبر رمز الواتساب المعتمد
-تسجيل دخول آمن وسريع عبر رمز الواتساب المعتمد
+