From e0319b5daebbae88b942ba55f6891ce0e2ecaf29 Mon Sep 17 00:00:00 2001 From: Anso Date: Wed, 25 Mar 2026 10:27:31 -0400 Subject: [PATCH] feat(auth): redesign Login and Setup pages with split-panel branding layout (#153) Replace plain Card-based Login and Setup forms with a professional split-panel layout: always-dark branding panel (dot grid texture, logo, tagline, cyan accent line) on the left, theme-aware form on the right. Mobile collapses to single column with compact logo header. - Add optional admin email field on Setup for license recovery - Backend accepts and stores admin_email in setup endpoint - Fix data-stacks-loaded attribute forwarding (wrap in div, use string values) --- CHANGELOG.md | 12 ++++ backend/src/index.ts | 6 +- frontend/src/App.tsx | 12 +--- frontend/src/components/EditorLayout.tsx | 4 +- frontend/src/components/Login.tsx | 73 +++++++++++++------ frontend/src/components/Setup.tsx | 92 ++++++++++++++++++------ 6 files changed, 146 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf5b72b5..a47aecc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +* **auth:** Login and Setup pages redesigned with split-panel branding layout (dark branding panel + theme-aware form) +* **auth:** Optional admin email field on Setup for future license recovery +* **ui:** Mobile-responsive login/setup with compact logo header + +### Fixed + +* **e2e:** Use explicit `data-stacks-loaded` string values for reliable attribute selector matching + ## [0.3.1](https://github.com/AnsoCode/Sencho/compare/v0.3.0...v0.3.1) (2026-03-25) diff --git a/backend/src/index.ts b/backend/src/index.ts index 836e9004..8d59f877 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -277,7 +277,7 @@ app.post('/api/auth/setup', authRateLimiter, async (req: Request, res: Response) return; } - const { username, password, confirmPassword } = req.body; + const { username, password, confirmPassword, admin_email } = req.body; // Validation if (!username || !password || !confirmPassword) { @@ -307,6 +307,10 @@ app.post('/api/auth/setup', authRateLimiter, async (req: Request, res: Response) dbSvc.updateGlobalSetting('auth_password_hash', passwordHash); dbSvc.updateGlobalSetting('auth_jwt_secret', jwtSecret); + if (admin_email && typeof admin_email === 'string') { + dbSvc.updateGlobalSetting('admin_email', admin_email.trim()); + } + // Issue JWT and log user in const token = jwt.sign({ username }, jwtSecret, { expiresIn: '24h' }); res.cookie(COOKIE_NAME, token, getCookieOptions(req)); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fdd4f8ae..3bb1da70 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -17,19 +17,11 @@ function AppContent() { } if (needsSetup) { - return ( -
- -
- ); + return ; } if (!isAuthenticated) { - return ( -
- -
- ); + return ; } return ( diff --git a/frontend/src/components/EditorLayout.tsx b/frontend/src/components/EditorLayout.tsx index aaa62b73..169d7c90 100644 --- a/frontend/src/components/EditorLayout.tsx +++ b/frontend/src/components/EditorLayout.tsx @@ -1009,7 +1009,8 @@ export default function EditorLayout() {

STACKS

- +
+ {isLoading ? (
@@ -1059,6 +1060,7 @@ export default function EditorLayout() { )) )} +
diff --git a/frontend/src/components/Login.tsx b/frontend/src/components/Login.tsx index 0f90b103..22bbf4c7 100644 --- a/frontend/src/components/Login.tsx +++ b/frontend/src/components/Login.tsx @@ -2,13 +2,6 @@ import { useState } from 'react'; import { useAuth } from '@/context/AuthContext'; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -28,26 +21,64 @@ export function Login({ setIsLoading(true); const result = await login(username, password); - + if (!result.success) { setError(result.error || 'Login failed'); } - + setIsLoading(false); }; return ( -
- - - Sencho - - Enter your credentials to access the dashboard - - - +
+ {/* ── Left: Branding Panel (desktop only) ── */} +
+ {/* Dot grid texture */} +
+ + {/* Branding content */} +
+ Sencho +
+

Sencho

+

Docker Compose Management

+
+
+ + {/* Brand accent line */} +
+
+ + {/* ── Right: Form Panel ── */} +
+ {/* Mobile logo header */} +
+ Sencho + Sencho + Sencho +
+ +
+
+

Welcome back

+

+ Sign in to your Sencho instance +

+
+
-
+
- - +
+
); } diff --git a/frontend/src/components/Setup.tsx b/frontend/src/components/Setup.tsx index b680352a..1fec3159 100644 --- a/frontend/src/components/Setup.tsx +++ b/frontend/src/components/Setup.tsx @@ -1,13 +1,6 @@ import { useState } from 'react'; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -23,6 +16,7 @@ export function Setup({ const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); + const [adminEmail, setAdminEmail] = useState(''); const [error, setError] = useState(''); const [isLoading, setIsLoading] = useState(false); @@ -55,7 +49,12 @@ export function Setup({ 'Content-Type': 'application/json', }, credentials: 'include', - body: JSON.stringify({ username, password, confirmPassword }), + body: JSON.stringify({ + username, + password, + confirmPassword, + admin_email: adminEmail || undefined, + }), }); const data = await response.json(); @@ -73,17 +72,55 @@ export function Setup({ }; return ( -
- - - Welcome to Sencho - - Create your admin account to get started - - - +
+ {/* ── Left: Branding Panel (desktop only) ── */} +
+ {/* Dot grid texture */} +
+ + {/* Branding content */} +
+ Sencho +
+

Sencho

+

Docker Compose Management

+
+
+ + {/* Brand accent line */} +
+
+ + {/* ── Right: Form Panel ── */} +
+ {/* Mobile logo header */} +
+ Sencho + Sencho + Sencho +
+ +
+
+

Create your account

+

+ Set up the admin credentials for your Sencho instance +

+
+
-
+
setConfirmPassword(e.target.value)} />
+
+ + setAdminEmail(e.target.value)} + /> +

+ Used for license recovery. Never shared with third parties. +

+
{error && (
{error} @@ -125,8 +177,8 @@ export function Setup({
- - +
+
); }