Skip to content

feat(login): redesign login page with responsive split layout#34

Merged
cevheri merged 6 commits intolibredb:devfrom
yusuf-gundogdu:feat/responsive-login-redesign
Mar 7, 2026
Merged

feat(login): redesign login page with responsive split layout#34
cevheri merged 6 commits intolibredb:devfrom
yusuf-gundogdu:feat/responsive-login-redesign

Conversation

@yusuf-gundogdu
Copy link

@yusuf-gundogdu yusuf-gundogdu commented Mar 2, 2026

Description

Redesigns the login page with a modern split-panel layout while preserving all existing authentication logic untouched.

Left panel (desktop only): Branding area with gradient background, feature highlights, and supported database badges.
Right panel: Login form — identical functionality, refined presentation.
Mobile: Left panel hidden, compact branding shown above the form with smooth responsive transition.

Type of Change

  • New feature (non-breaking change which adds functionality)

Changes Made

src/app/login/login-form.tsx

  • Split-panel layout: left branding (hidden on mobile via hidden lg:flex), right login form
  • OIDC mode: added ShieldCheck icon, "Single Sign-On" description, and security badges
  • SSO window: opens provider login side-by-side on desktop (window.open right half), falls back to full-page redirect on mobile
  • Accessibility: changed mobile branding from <h1> to <h2> to avoid duplicate h1 tags

src/app/api/auth/oidc/login/route.ts

  • Stores oidc-mode cookie when ?mode=popup is present, so the callback knows whether to close the window or redirect

src/app/api/auth/oidc/callback/route.ts

  • Reads oidc-mode cookie: returns window.close() in popup mode, redirects normally otherwise
  • Cleans up oidc-mode cookie in both success and error paths

tests/components/LoginPage.test.tsx

  • Fixed test: getByTextgetAllByText for "LibreDB Studio" since the split layout renders it in multiple locations

What is NOT changed

  • No backend/auth logic changes (JWT, session, role mapping — all untouched)
  • No new dependencies added
  • No CI/CD configuration changes
  • No database or API changes

Testing

  • I have tested this locally
  • I have added/updated tests
  • All existing tests pass

Test Environment

  • LibreDB Studio Version: 0.7.1
  • Browser: Chrome
  • OS: macOS
  • Bun Version: 1.3.10

Test Results

  • Lint: 0 errors, 0 warnings
  • TypeScript: clean
  • Build: clean
  • Component tests: 15/15 groups, 1130 tests passed
  • E2E tests: passed

Checklist

  • My code follows the project's code style guidelines
  • I have performed a self-review of my code
  • My changes generate no new warnings
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published

yusuf gundogdu and others added 2 commits March 4, 2026 00:46
Add a modern split-panel login page inspired by contemporary SaaS designs.

- Left panel: gradient branding section with hero text, feature highlights,
  and supported database badges (hidden on mobile)
- Right panel: login form with preserved authentication functionality
- Mobile: compact branding header with login form and database pills
- All existing auth logic (local + OIDC) remains untouched

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Change mobile branding heading from h1 to h2 to maintain single h1
  per page for proper accessibility hierarchy
- Update LoginPage test to use getAllByText for 'LibreDB Studio' since
  the split-panel layout now renders the title in multiple locations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@yusuf-gundogdu yusuf-gundogdu force-pushed the feat/responsive-login-redesign branch 2 times, most recently from 96fdd8b to bcb6cc8 Compare March 3, 2026 22:22
On desktop, the SSO provider's login page now opens as a separate
window positioned on the right half of the screen, creating a
side-by-side experience with the main login page on the left.
On mobile, the flow gracefully falls back to a full-page redirect.

Changes:
- Add handleSSO function with responsive window management
- Enrich OIDC card UI with ShieldCheck icon and security badges
- Store popup mode flag via cookie for callback coordination
- Auto-close SSO window on completion and verify auth state
- Clean up oidc-mode cookie in both success and error paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Redesigns the login page into a responsive split-panel layout and adds a desktop popup-based OIDC SSO flow (with server-side callback support) while keeping existing local login behavior intact.

Changes:

  • Updated LoginForm UI to a split branding/form layout, including a new desktop popup SSO flow and mobile fallback redirect.
  • Added oidc-mode cookie handling in OIDC login + callback routes to support popup closing behavior.
  • Adjusted the login page component test to account for multiple rendered “LibreDB Studio” titles.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/app/login/login-form.tsx New split layout and popup SSO handling logic for OIDC mode.
src/app/api/auth/oidc/login/route.ts Sets oidc-mode cookie for popup flows; closes window on popup-mode config error.
src/app/api/auth/oidc/callback/route.ts Reads/clears oidc-mode and returns window.close() HTML for popup flows.
tests/components/LoginPage.test.tsx Updates title assertion to handle multiple occurrences.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +57 to +61
fetch('/api/auth/me').then(res => res.json()).then(data => {
if (data.authenticated) {
router.push(data.role === 'admin' ? '/admin' : '/');
}
}).catch(() => {});
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/api/auth/me returns { authenticated: true, user: session } (see src/app/api/auth/me/route.ts), but this code reads data.role. That will always be undefined, causing admins to be redirected to / after popup SSO. Read the role from data.user.role (and consider guarding for non-200 responses).

Suggested change
fetch('/api/auth/me').then(res => res.json()).then(data => {
if (data.authenticated) {
router.push(data.role === 'admin' ? '/admin' : '/');
}
}).catch(() => {});
fetch('/api/auth/me')
.then(res => {
if (!res.ok) {
return null;
}
return res.json();
})
.then(data => {
if (data && data.authenticated) {
const role = data.user?.role;
router.push(role === 'admin' ? '/admin' : '/');
}
})
.catch(() => {});

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +63
// Listen for SSO window close
const checkSSO = setInterval(() => {
if (!ssoWindow || ssoWindow.closed) {
clearInterval(checkSSO);
setIsLoading(false);
router.refresh();
// Check if auth succeeded
fetch('/api/auth/me').then(res => res.json()).then(data => {
if (data.authenticated) {
router.push(data.role === 'admin' ? '/admin' : '/');
}
}).catch(() => {});
}
}, 500);
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The polling interval created by setInterval isn’t cleaned up if the component unmounts (e.g., navigation away while the popup is still open). That can lead to state updates on an unmounted component. Store the interval id (e.g., in a ref) and clear it in a useEffect cleanup, and/or stop polling when navigation occurs.

Copilot uses AI. Check for mistakes.
sameSite: 'lax',
maxAge: 300,
path: '/',
});
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oidc-mode is set as a standalone cookie and callback trusts it to decide whether to window.close(). If the user closes the popup before completing auth, the cookie can remain for up to 5 minutes; a subsequent non-popup OIDC flow in the same browser could then hit the callback and incorrectly return window.close() instead of redirecting. Consider binding the mode flag to the current OIDC state (e.g., store popup:<state> and validate it in the callback), and/or explicitly clear any stale oidc-mode cookie when mode !== popup.

Suggested change
});
});
} else {
// Clear any stale popup mode flag so non-popup flows are not misclassified
cookieStore.set('oidc-mode', '', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 0,
path: '/',
});

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +79
// Check if this was a popup login
const isPopup = cookieStore.get('oidc-mode')?.value === 'popup';

// Clean up cookies
cookieStore.delete('oidc-state');
cookieStore.delete('oidc-mode');

if (isPopup) {
// Close popup and let the parent window detect the auth via /api/auth/me
return new NextResponse(
'<html><body><script>window.close();</script></body></html>',
{ headers: { 'Content-Type': 'text/html' } }
);
}
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Popup handling is determined solely by cookieStore.get('oidc-mode') === 'popup'. To avoid incorrectly treating a non-popup callback as a popup (e.g., due to a stale cookie from an abandoned popup attempt), validate that the mode cookie corresponds to the current login attempt (tie it to the decrypted OIDC state), and fall back to the normal redirect when it doesn’t match.

Copilot uses AI. Check for mistakes.
cevheri and others added 3 commits March 7, 2026 22:39
Popup-based SSO flow (window.open + window.moveTo/resizeTo) is unreliable
because modern browsers block window manipulation APIs. Revert to the
proven full-page redirect approach while keeping all UI improvements
(split-panel layout, responsive design, security badges).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace violet/purple/emerald gradient with zinc-950 base and blue accent
glows to match the app's premium dark aesthetic. Update feature content
to align with libredb.org website messaging. Changes:

- Background: zinc-950 + subtle blue-950/cyan-950 gradient overlay
- Accent color: blue-400 (matching app's primary) instead of emerald
- Feature cards: glassmorphism (bg-white/[0.03], border-white/[0.05])
- Content: website-aligned copy (7+ DB engines, AI-native, zero install)
- Mobile: blue-tinted branding icon matching desktop panel
- Ambient orbs: blue/cyan instead of purple/emerald

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mobile footer was showing only 5 databases while desktop panel listed
all 7. Sync both lists to show the complete set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cevheri cevheri merged commit 9d5254f into libredb:dev Mar 7, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants