From f15e405759ceda24773d53f1c9862d2ee22a3c88 Mon Sep 17 00:00:00 2001 From: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com> Date: Wed, 10 Jun 2026 13:49:27 +0100 Subject: [PATCH] changes to the login and signup, similar to in the saas repo (#6577) Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> --- .../public/locales/en-GB/translation.toml | 5 +- .../modern-logo/LoginDarkModeHeader.svg | 5 + .../modern-logo/LoginLightModeHeader.svg | 6 + .../tests/live/authentication-login.spec.ts | 7 +- .../editor/src/proprietary/routes/Login.tsx | 34 +- .../editor/src/proprietary/routes/Signup.tsx | 19 +- .../proprietary/routes/authShared/auth.css | 87 +++++ frontend/editor/src/saas/routes/Login.tsx | 300 ++++++++++++------ frontend/editor/src/saas/routes/Signup.tsx | 143 +++++---- .../src/saas/routes/authShared/saas-auth.css | 98 +++++- .../src/saas/routes/login/OAuthButtons.tsx | 20 +- .../editor/src/saas/styles/saas-theme.css | 4 +- 12 files changed, 534 insertions(+), 194 deletions(-) create mode 100644 frontend/editor/public/modern-logo/LoginDarkModeHeader.svg create mode 100644 frontend/editor/public/modern-logo/LoginLightModeHeader.svg diff --git a/frontend/editor/public/locales/en-GB/translation.toml b/frontend/editor/public/locales/en-GB/translation.toml index 39db57af9..71c77f866 100644 --- a/frontend/editor/public/locales/en-GB/translation.toml +++ b/frontend/editor/public/locales/en-GB/translation.toml @@ -4471,7 +4471,7 @@ alreadyHaveAccount = "Already have an account?" choosePassword = "Choose a password" confirmPassword = "Confirm password" confirmPasswordPlaceholder = "Re-enter your password" -createAccount = "Create Account" +createAccount = "Create account" creating = "Creating Account..." email = "Email address" emailPlaceholder = "Enter your email address" @@ -4554,6 +4554,7 @@ sending = "Sending…" sendMagicLink = "Send Magic Link" sendResetLink = "Send reset link" sessionExpired = "Your session has expired. Please sign in again." +createAccount = "Create an account" signin = "Sign in" signInAnonymously = "Sign Up as a Guest" signingIn = "Signing in..." @@ -6885,6 +6886,8 @@ passwordsDoNotMatch = "Passwords do not match" passwordTooShort = "Password must be at least 6 characters long" pleaseFillAllFields = "Please fill in all fields" signUp = "Sign Up" +signUpWith = "Sign up with" +skip = "Skip" subtitle = "Join Stirling PDF to get started" title = "Create an account" unexpectedError = "Unexpected error: {{message}}" diff --git a/frontend/editor/public/modern-logo/LoginDarkModeHeader.svg b/frontend/editor/public/modern-logo/LoginDarkModeHeader.svg new file mode 100644 index 000000000..59d84449f --- /dev/null +++ b/frontend/editor/public/modern-logo/LoginDarkModeHeader.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/editor/public/modern-logo/LoginLightModeHeader.svg b/frontend/editor/public/modern-logo/LoginLightModeHeader.svg new file mode 100644 index 000000000..6b7c6fa8c --- /dev/null +++ b/frontend/editor/public/modern-logo/LoginLightModeHeader.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/editor/src/core/tests/live/authentication-login.spec.ts b/frontend/editor/src/core/tests/live/authentication-login.spec.ts index 310dd5e6f..a8a6a75cf 100644 --- a/frontend/editor/src/core/tests/live/authentication-login.spec.ts +++ b/frontend/editor/src/core/tests/live/authentication-login.spec.ts @@ -19,12 +19,7 @@ test.describe("1. Authentication and Login", () => { .first(), ).toBeVisible(); - // Step 3: Confirm the heading for "Sign In" / "Login" is visible - await expect( - page.getByRole("heading", { name: /sign in|login|masuk/i }), - ).toBeVisible(); - - // Step 4: Confirm a "Username" text input field is present and empty + // Step 3: Confirm a "Username" text input field is present and empty const usernameInput = page.locator("#email"); await expect(usernameInput).toBeVisible(); await expect(usernameInput).toHaveValue(""); diff --git a/frontend/editor/src/proprietary/routes/Login.tsx b/frontend/editor/src/proprietary/routes/Login.tsx index 85c42fce8..c334bef8d 100644 --- a/frontend/editor/src/proprietary/routes/Login.tsx +++ b/frontend/editor/src/proprietary/routes/Login.tsx @@ -17,12 +17,11 @@ import { useDocumentMeta } from "@app/hooks/useDocumentMeta"; import AuthLayout from "@app/routes/authShared/AuthLayout"; import { useBackendProbe } from "@app/hooks/useBackendProbe"; import apiClient from "@app/services/apiClient"; -import { BASE_PATH } from "@app/constants/app"; +import { BASE_PATH, withBasePath } from "@app/constants/app"; import { type OAuthProvider } from "@app/auth/oauthTypes"; import { updateSupportedLanguages } from "@app/i18n"; // Import login components -import LoginHeader from "@app/routes/login/LoginHeader"; import ErrorMessage from "@app/routes/login/ErrorMessage"; import EmailPasswordForm from "@app/routes/login/EmailPasswordForm"; import OAuthButtons, { @@ -74,7 +73,6 @@ export default function Login() { const autoLoginErrorRecorded = useRef(false); const isUserPassAllowed = loginMethod === "all" || loginMethod === "normal"; const isSsoOnlyMode = loginMethod !== "all" && loginMethod !== "normal"; - const isSingleSsoOnly = !isUserPassAllowed && enabledProviders.length === 1; const AUTO_LOGIN_ATTEMPTS_KEY = "stirling_sso_auto_login_attempts"; const AUTO_LOGIN_ERRORS_KEY = "stirling_sso_auto_login_errors"; @@ -461,7 +459,6 @@ export default function Login() { // If backend isn't ready yet, show a lightweight status screen instead of the form if (backendProbe.status !== "up" && !loginDisabled) { - const backendTitle = t("backendStartup.notFoundTitle", "Backend not found"); const handleRetry = async () => { const result = await backendProbe.probe(); if (result.status === "up") { @@ -471,7 +468,18 @@ export default function Login() { }; return ( - +
+ Stirling PDF + Stirling PDF +
- +
+ Stirling PDF + Stirling PDF +
{/* Success message */} {successMessage && ( diff --git a/frontend/editor/src/proprietary/routes/Signup.tsx b/frontend/editor/src/proprietary/routes/Signup.tsx index 28676a085..9773106c4 100644 --- a/frontend/editor/src/proprietary/routes/Signup.tsx +++ b/frontend/editor/src/proprietary/routes/Signup.tsx @@ -5,10 +5,9 @@ import { useDocumentMeta } from "@app/hooks/useDocumentMeta"; import { useAuth } from "@app/auth/UseSession"; import AuthLayout from "@app/routes/authShared/AuthLayout"; import "@app/routes/authShared/auth.css"; -import { BASE_PATH } from "@app/constants/app"; +import { BASE_PATH, withBasePath } from "@app/constants/app"; // Import signup components -import LoginHeader from "@app/routes/login/LoginHeader"; import ErrorMessage from "@app/routes/login/ErrorMessage"; import DividerWithText from "@app/components/shared/DividerWithText"; import SignupForm from "@app/routes/signup/SignupForm"; @@ -92,10 +91,18 @@ export default function Signup() { return ( - +
+ Stirling PDF + Stirling PDF +
diff --git a/frontend/editor/src/proprietary/routes/authShared/auth.css b/frontend/editor/src/proprietary/routes/authShared/auth.css index e0abf4e7a..080c28092 100644 --- a/frontend/editor/src/proprietary/routes/authShared/auth.css +++ b/frontend/editor/src/proprietary/routes/authShared/auth.css @@ -615,3 +615,90 @@ background-color: #af3434 !important; opacity: 0.6 !important; } + +/* ── Logo block ─────────────────────────────────────────────────────── */ +.auth-logo-block { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 2rem; + margin-top: 0.5rem; + animation: authFadeUp 0.4s ease both; +} + +.auth-logo-header { + height: 8rem; + width: auto; +} + +.auth-logo-header--dark { + display: none; +} + +/* ── Page entrance animation ────────────────────────────────────────── */ +@keyframes authFadeUp { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* ── Expandable trigger button state ────────────────────────────────── */ +.auth-expandable-trigger { + transition: + background-color 180ms ease, + box-shadow 180ms ease, + border-color 180ms ease; +} + +.auth-expandable-trigger--active { + border-color: #af3434 !important; + box-shadow: 0 0 0 3px rgba(175, 52, 52, 0.12); +} + +/* ── Animated expand/collapse via grid-template-rows ───────────────── */ +.auth-expand-grid { + display: grid; + grid-template-rows: 0fr; + transition: + grid-template-rows 280ms cubic-bezier(0.4, 0, 0.2, 1), + opacity 220ms ease; + opacity: 0; +} + +.auth-expand-grid--open { + grid-template-rows: 1fr; + opacity: 1; +} + +.auth-expand-inner { + overflow: hidden; + min-height: 0; +} + +/* ── Icon+label group — keeps icons vertically aligned across buttons ── */ +.oauth-btn-group { + display: inline-flex; + align-items: center; + min-width: 12.5rem; +} + +.oauth-btn-label { + flex: 1; + text-align: center; +} + +/* ── Icon helpers inside oauth-button-fullwidth ─────────────────────── */ +.auth-at-icon { + font-size: 1.25rem; + line-height: 1; + margin-right: 0.5rem; + display: inline-block; + width: 1.75rem; + text-align: center; + flex-shrink: 0; +} diff --git a/frontend/editor/src/saas/routes/Login.tsx b/frontend/editor/src/saas/routes/Login.tsx index 848792147..4be5ed9b5 100644 --- a/frontend/editor/src/saas/routes/Login.tsx +++ b/frontend/editor/src/saas/routes/Login.tsx @@ -7,17 +7,18 @@ import { useDocumentMeta } from "@app/hooks/useDocumentMeta"; import AuthLayout from "@app/routes/authShared/AuthLayout"; import "@app/routes/authShared/auth.css"; import "@app/routes/authShared/saas-auth.css"; -import GuestSignInButton from "@app/routes/authShared/GuestSignInButton"; +import { + absoluteWithBasePath, + getBaseUrl, + withBasePath, +} from "@app/constants/app"; +import LinkRoundedIcon from "@mui/icons-material/LinkRounded"; // Import login components -import LoginHeader from "@app/routes/login/LoginHeader"; import ErrorMessage from "@app/routes/login/ErrorMessage"; import EmailPasswordForm from "@app/routes/login/EmailPasswordForm"; -import MagicLinkForm from "@app/routes/login/MagicLinkForm"; import OAuthButtons from "@app/routes/login/OAuthButtons"; -import DividerWithText from "@app/components/shared/DividerWithText"; import LoggedInState from "@app/routes/login/LoggedInState"; -import { absoluteWithBasePath, getBaseUrl } from "@app/constants/app"; export default function Login() { const navigate = useNavigate(); @@ -25,11 +26,13 @@ export default function Login() { const { t } = useTranslation(); const [isSigningIn, setIsSigningIn] = useState(false); const [error, setError] = useState(null); - const [showMagicLink, setShowMagicLink] = useState(false); + const [showMagicLinkForm, setShowMagicLinkForm] = useState(false); const [showEmailForm, setShowEmailForm] = useState(false); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [magicLinkEmail, setMagicLinkEmail] = useState(""); + const [magicLinkSent, setMagicLinkSent] = useState(false); + // Prefill email from query param (e.g. after password reset) useEffect(() => { try { @@ -37,6 +40,7 @@ export default function Login() { const emailFromQuery = url.searchParams.get("email"); if (emailFromQuery) { setEmail(emailFromQuery); + setShowEmailForm(true); } } catch (_) { // ignore @@ -174,12 +178,9 @@ export default function Login() { setError(error.message); } else { setError(null); - alert(t("login.magicLinkSent", { email: magicLinkEmail })); - setMagicLinkEmail(""); - setShowMagicLink(false); + setMagicLinkSent(true); } } catch (err) { - console.error("[Login] Unexpected error:", err); setError( t("login.unexpectedError", { message: err instanceof Error ? err.message : "Unknown error", @@ -190,10 +191,6 @@ export default function Login() { } }; - const handleForgotPassword = () => { - navigate("/auth/reset"); - }; - const handleAnonymousSignIn = async () => { try { setIsSigningIn(true); @@ -227,115 +224,210 @@ export default function Login() { } }; + const toggleEmailForm = () => { + setShowEmailForm((v) => !v); + setShowMagicLinkForm(false); + setMagicLinkSent(false); + }; + + const toggleMagicLink = () => { + setShowMagicLinkForm((v) => !v); + setShowEmailForm(false); + setMagicLinkSent(false); + }; + return ( - - + + {/* Centered logo */} +
+ Stirling PDF + Stirling PDF +
- {/* OAuth first */} - + {/* OAuth + magic link group — single flex column so gap is uniform */} +
+ - {/* Divider between OAuth and Email */} - + {/* Magic link button + its expandable form as one unit */} +
+ - {/* Sign in with email button (primary color to match signup CTA) */} -
+
+
+
+ {magicLinkSent ? ( +

+ {t("login.magicLinkSent", { email: magicLinkEmail })} +

+ ) : ( +
+ setMagicLinkEmail(e.target.value)} + onKeyDown={(e) => + e.key === "Enter" && + !isSigningIn && + signInWithMagicLink() + } + className="auth-input" + /> + +
+ )} +
+
+
+
+
+ + {/* Email & Password button */} + + + {/* Email form — animated expand */} +
+
+
+ + +
+
+
+ + {/* Skip */} +
- {showEmailForm && ( - - )} - - {showEmailForm && ( -
- -
- )} - - {/* Divider then Guest */} - - - - -
- - + {/* Bottom */} +
- - {/* Magic link form renders on demand */} - {showMagicLink && ( -
- -
- )} ); } diff --git a/frontend/editor/src/saas/routes/Signup.tsx b/frontend/editor/src/saas/routes/Signup.tsx index d772cbe37..724bf455a 100644 --- a/frontend/editor/src/saas/routes/Signup.tsx +++ b/frontend/editor/src/saas/routes/Signup.tsx @@ -4,18 +4,15 @@ import { signInAnonymously } from "@app/auth/supabase"; import { useAuth } from "@app/auth/UseSession"; import { useTranslation } from "@app/hooks/useTranslation"; import { useDocumentMeta } from "@app/hooks/useDocumentMeta"; -import { getBaseUrl } from "@app/constants/app"; +import { getBaseUrl, withBasePath } from "@app/constants/app"; import AuthLayout from "@app/routes/authShared/AuthLayout"; import "@app/routes/authShared/auth.css"; import "@app/routes/authShared/saas-auth.css"; -import GuestSignInButton from "@app/routes/authShared/GuestSignInButton"; import { alert } from "@app/components/toast"; // Import signup components -import LoginHeader from "@app/routes/login/LoginHeader"; import ErrorMessage from "@app/routes/login/ErrorMessage"; import OAuthButtons from "@app/routes/login/OAuthButtons"; -import DividerWithText from "@app/components/shared/DividerWithText"; import SignupForm from "@app/routes/signup/SignupForm"; import { useSignupFormValidation, @@ -184,84 +181,112 @@ export default function Signup() { return ( - + {/* Centered logo */} +
+ Stirling PDF + Stirling PDF +
- {/* OAuth first */} -
+ {/* OAuth providers */} +
- {/* Divider between OAuth and Email */} -
- + {/* Email & Password button */} + + + {/* Email form — animated expand */} +
+
+
+ +
+
- {/* Use Email Instead button (toggles email form) */} -
+ {/* Skip */} +
- {showEmailForm && ( - - )} - -
- -
- - - - {/* Bottom row */} -
+ {/* Bottom */} +
diff --git a/frontend/editor/src/saas/routes/authShared/saas-auth.css b/frontend/editor/src/saas/routes/authShared/saas-auth.css index 5073a24c9..9a8943fc2 100644 --- a/frontend/editor/src/saas/routes/authShared/saas-auth.css +++ b/frontend/editor/src/saas/routes/authShared/saas-auth.css @@ -14,7 +14,7 @@ justify-content: center; padding: 0.75rem 1rem; border: 1px solid #d1d5db; - border-radius: 0.625rem; + border-radius: 100px; background-color: #ffffff; font-size: 1rem; font-weight: 600; @@ -22,6 +22,10 @@ cursor: pointer; gap: 0.5rem; box-shadow: 0 0.125rem 0.375rem rgba(0, 0, 0, 0.04); + transition: + background-color 150ms ease, + box-shadow 150ms ease, + border-color 150ms ease; } .oauth-button-fullwidth:disabled { @@ -29,6 +33,11 @@ opacity: 0.6; } +.oauth-button-fullwidth:hover:not(:disabled) { + background-color: #fafafa; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} + .auth-dropdown-wrapper { position: relative; } @@ -72,3 +81,90 @@ color: #9c2f30; border: 2px solid currentColor; } + +/* ── Logo block ─────────────────────────────────────────────────────── */ +.auth-logo-block { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 2rem; + margin-top: 0.5rem; + animation: authFadeUp 0.4s ease both; +} + +.auth-logo-header { + height: 8rem; + width: auto; +} + +.auth-logo-header--dark { + display: none; +} + +/* ── Page entrance animation ────────────────────────────────────────── */ +@keyframes authFadeUp { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* ── Expandable trigger button state ────────────────────────────────── */ +.auth-expandable-trigger { + transition: + background-color 180ms ease, + box-shadow 180ms ease, + border-color 180ms ease; +} + +.auth-expandable-trigger--active { + border-color: #af3434 !important; + box-shadow: 0 0 0 3px rgba(175, 52, 52, 0.12); +} + +/* ── Animated expand/collapse via grid-template-rows ───────────────── */ +.auth-expand-grid { + display: grid; + grid-template-rows: 0fr; + transition: + grid-template-rows 280ms cubic-bezier(0.4, 0, 0.2, 1), + opacity 220ms ease; + opacity: 0; +} + +.auth-expand-grid--open { + grid-template-rows: 1fr; + opacity: 1; +} + +.auth-expand-inner { + overflow: hidden; + min-height: 0; +} + +/* ── Icon+label group — keeps icons vertically aligned across buttons ── */ +.oauth-btn-group { + display: inline-flex; + align-items: center; + min-width: 12.5rem; +} + +.oauth-btn-label { + flex: 1; + text-align: center; +} + +/* ── Icon helpers inside oauth-button-fullwidth ─────────────────────── */ +.auth-at-icon { + font-size: 1.25rem; + line-height: 1; + margin-right: 0.5rem; + display: inline-block; + width: 1.75rem; + text-align: center; + flex-shrink: 0; +} diff --git a/frontend/editor/src/saas/routes/login/OAuthButtons.tsx b/frontend/editor/src/saas/routes/login/OAuthButtons.tsx index e016c125e..717f96b9a 100644 --- a/frontend/editor/src/saas/routes/login/OAuthButtons.tsx +++ b/frontend/editor/src/saas/routes/login/OAuthButtons.tsx @@ -17,6 +17,7 @@ interface OAuthButtonsProps { isSubmitting: boolean; layout?: "vertical" | "grid" | "icons" | "fullwidth"; enabledProviders?: string[]; // List of enabled provider IDs from backend + labelPrefix?: string; } export default function OAuthButtons({ @@ -24,6 +25,7 @@ export default function OAuthButtons({ isSubmitting, layout = "vertical", enabledProviders: _enabledProviders = [], + labelPrefix = "", }: OAuthButtonsProps) { const { t } = useTranslation(); @@ -92,12 +94,18 @@ export default function OAuthButtons({ className="oauth-button-fullwidth" title={p.label} > - {p.label} - {p.label} + + {p.label} + + {labelPrefix} + {p.label} + + ))}
diff --git a/frontend/editor/src/saas/styles/saas-theme.css b/frontend/editor/src/saas/styles/saas-theme.css index 9d670c98d..2aad69c54 100644 --- a/frontend/editor/src/saas/styles/saas-theme.css +++ b/frontend/editor/src/saas/styles/saas-theme.css @@ -35,7 +35,7 @@ --auth-label-text: #2b3230; --auth-button-bg: #af3434; --auth-button-text: #ffffff; - --auth-magic-button-bg: #8b5cf6; + --auth-magic-button-bg: #af3434; --auth-magic-button-text: #ffffff; /* Light-only auth colors (no dark mode equivalents) used for login/signup */ @@ -45,7 +45,7 @@ --auth-label-text-light-only: #2b3230; --auth-button-bg-light-only: #af3434; --auth-button-text-light-only: #ffffff; - --auth-magic-button-bg-light-only: #8b5cf6; + --auth-magic-button-bg-light-only: #af3434; --auth-magic-button-text-light-only: #ffffff; --auth-bg-color-light-only: #ffffff; --auth-card-bg-light-only: #ffffff;