diff --git a/ONBOARDING_FLOW_HOVER_STATES.md b/ONBOARDING_FLOW_HOVER_STATES.md new file mode 100644 index 0000000..f81a7cd --- /dev/null +++ b/ONBOARDING_FLOW_HOVER_STATES.md @@ -0,0 +1,21 @@ +# Onboarding Flow Hover States + +This document captures the hover-state refinements applied to the onboarding flow so the registration experience better matches the global Drips Wave theme. + +## Updated surfaces + +- Registration form inputs now lift slightly on hover, brighten to white, and use Pluto border and shadow accents. +- The primary onboarding CTA now uses Pluto theme hover, focus, and active feedback instead of a flat black-only treatment. +- Secondary onboarding links now transition to Pluto accent tones for a more consistent navigation affordance. +- The onboarding progress tracker now uses Pluto-tinted container, step, and badge hover states while preserving completion and error semantics. + +## Theme alignment + +- Reused the existing `pluto` Tailwind color scale already defined in [frontend/tailwind.config.js](/Users/marvellous/Desktop/Stellar_Payment_API/frontend/tailwind.config.js). +- Kept the existing monochrome foundation, then layered Pluto hover accents so the change feels consistent with the current Drips Wave visual language. +- Preserved keyboard focus visibility and active-state feedback for accessible non-mouse interaction. + +## Verification + +- Added Playwright coverage in [frontend/tests/e2e/onboarding-hover.spec.ts](/Users/marvellous/Desktop/Stellar_Payment_API/frontend/tests/e2e/onboarding-hover.spec.ts). +- Verified the new hover behavior against the register onboarding flow on both desktop and mobile Playwright projects. diff --git a/frontend/src/app/(public)/register/page.tsx b/frontend/src/app/(public)/register/page.tsx index c1e0b6d..3d9db6e 100644 --- a/frontend/src/app/(public)/register/page.tsx +++ b/frontend/src/app/(public)/register/page.tsx @@ -5,28 +5,36 @@ import GuestGuard from "@/components/GuestGuard"; export default function RegisterPage() { return ( -
-
-

Onboarding

-

Join PLUTO

-

- Create your merchant profile to start accepting modern payments and managing assets on the PLUTO infrastructure. -

-
+
+
+

+ Onboarding +

+

+ Join PLUTO +

+

+ Create your merchant profile to start accepting modern payments and + managing assets on the PLUTO infrastructure. +

+
-
- -
+
+ +
-
-

- Already have an account?{" "} - - Log in here - -

-
-
+ +
); } diff --git a/frontend/src/components/OnboardingProgressTracker.tsx b/frontend/src/components/OnboardingProgressTracker.tsx index 48a4bfe..6cd79c0 100644 --- a/frontend/src/components/OnboardingProgressTracker.tsx +++ b/frontend/src/components/OnboardingProgressTracker.tsx @@ -193,29 +193,29 @@ export const OnboardingProgressTracker: React.FC< {/* Container */}
{/* Header */}
-

+

{t("onboarding.title") || "Onboarding Progress"}

- + {progressPercentage}%
-

+

{t("onboarding.subtitle") || "Complete all required steps to finish setup"}

{/* Overall progress bar */} -
+
{/* Status text */} -

+

{sortedSteps.filter((s) => s.completed).length} of{" "} {sortedSteps.length} steps completed {isOnboardingComplete && ( - + {sortedSteps.map((step, index) => { const isCurrentStep = currentStep === step.id; - const isPastStep = sortedSteps.findIndex((s) => s.id === currentStep) > index; return ( {/* Step indicator */} @@ -284,12 +283,12 @@ export const OnboardingProgressTracker: React.FC< onClick={() => handleStepClick(step.id)} className={`relative flex-shrink-0 ${ compact ? "h-8 w-8" : "h-10 w-10" - } rounded-full border-2 font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 ${ + } rounded-full border-2 font-semibold transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-pluto-400 focus:ring-offset-2 ${ step.completed - ? "border-green-500 bg-green-50" + ? "border-pluto-500 bg-pluto-100 text-pluto-800 shadow-[0_10px_24px_rgba(74,111,165,0.14)]" : isCurrentStep - ? "border-blue-500 bg-blue-50" - : "border-gray-300 bg-gray-50 hover:border-gray-400" + ? "border-pluto-600 bg-pluto-50 text-pluto-700 shadow-[0_12px_28px_rgba(74,111,165,0.12)]" + : "border-pluto-200 bg-white text-pluto-700 group-hover:border-pluto-400 group-hover:bg-pluto-50 group-hover:text-pluto-800 group-hover:shadow-[0_10px_24px_rgba(13,27,46,0.08)]" }`} aria-label={`Step ${showStepNumbers ? index + 1 : ""}: ${step.title}${ step.completed ? ". Completed" : "" @@ -308,7 +307,7 @@ export const OnboardingProgressTracker: React.FC< animate="visible" >

{step.title} @@ -360,7 +359,7 @@ export const OnboardingProgressTracker: React.FC< )}

-

+

{step.description}

@@ -369,10 +368,10 @@ export const OnboardingProgressTracker: React.FC< +
)} ); @@ -402,7 +401,7 @@ export const OnboardingProgressTracker: React.FC< {isOnboardingComplete && sortedSteps.length > 0 && (
-

+

{t("onboarding.successTitle") || "Onboarding Complete!"}

-

+

{t("onboarding.successMessage") || "You have successfully completed all required onboarding steps."}

diff --git a/frontend/src/components/RegistrationForm.tsx b/frontend/src/components/RegistrationForm.tsx index cc37729..2b18831 100644 --- a/frontend/src/components/RegistrationForm.tsx +++ b/frontend/src/components/RegistrationForm.tsx @@ -14,6 +14,12 @@ import { Spinner } from "./ui/Spinner"; const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const BUSINESS_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9\s&'.,-]{1,79}$/; +const FIELD_BASE_CLASSES = + "rounded-2xl border bg-[#F9F9F9] p-4 text-sm font-bold text-[#0A0A0A] placeholder-[#A0A0A0] transition-all duration-200 focus:bg-white focus:outline-none motion-safe:hover:-translate-y-0.5"; +const FIELD_THEME_CLASSES = + "border-[#E8E8E8] hover:border-pluto-300 hover:bg-white hover:shadow-[0_14px_30px_rgba(13,27,46,0.06)] focus:border-pluto-500 focus:shadow-[0_0_0_1px_rgba(74,111,165,0.18),0_18px_36px_rgba(13,27,46,0.08)]"; +const FIELD_ERROR_CLASSES = + "border-red-500/50 hover:border-red-400 focus:border-red-500 focus:shadow-[0_0_0_1px_rgba(239,68,68,0.16),0_16px_32px_rgba(239,68,68,0.08)]"; // Memoized password strength indicator to prevent unnecessary re-renders const PasswordStrengthIndicator = React.memo( @@ -225,7 +231,7 @@ const RegistrationForm = React.memo(function RegistrationForm() { Enter Dashboard @@ -271,10 +277,10 @@ const RegistrationForm = React.memo(function RegistrationForm() { aria-describedby={ businessNameError ? "business-name-error" : undefined } - className={`rounded-2xl border bg-[#F9F9F9] p-4 text-sm font-bold text-[#0A0A0A] placeholder-[#A0A0A0] transition-all focus:bg-white focus:outline-none ${ + className={`${FIELD_BASE_CLASSES} ${ businessNameError - ? "border-red-500/50 focus:border-red-500" - : "border-[#E8E8E8] focus:border-[#0A0A0A]" + ? FIELD_ERROR_CLASSES + : FIELD_THEME_CLASSES }`} placeholder="PLUTO Merchant" /> @@ -308,10 +314,10 @@ const RegistrationForm = React.memo(function RegistrationForm() { }} aria-invalid={Boolean(emailError)} aria-describedby={emailError ? "primary-email-error" : undefined} - className={`rounded-2xl border bg-[#F9F9F9] p-4 text-sm font-bold text-[#0A0A0A] placeholder-[#A0A0A0] transition-all focus:bg-white focus:outline-none ${ + className={`${FIELD_BASE_CLASSES} ${ emailError - ? "border-red-500/50 focus:border-red-500" - : "border-[#E8E8E8] focus:border-[#0A0A0A]" + ? FIELD_ERROR_CLASSES + : FIELD_THEME_CLASSES }`} placeholder="owner@business.com" /> @@ -344,10 +350,10 @@ const RegistrationForm = React.memo(function RegistrationForm() { }} aria-invalid={Boolean(passwordError)} aria-describedby={passwordError ? "password-error" : undefined} - className={`rounded-2xl border bg-[#F9F9F9] p-4 text-sm font-bold text-[#0A0A0A] placeholder-[#A0A0A0] transition-all focus:bg-white focus:outline-none ${ + className={`${FIELD_BASE_CLASSES} ${ passwordError - ? "border-red-500/50 focus:border-red-500" - : "border-[#E8E8E8] focus:border-[#0A0A0A]" + ? FIELD_ERROR_CLASSES + : FIELD_THEME_CLASSES }`} placeholder="••••••••" /> @@ -387,10 +393,10 @@ const RegistrationForm = React.memo(function RegistrationForm() { aria-describedby={ notificationEmailError ? "notification-email-error" : undefined } - className={`rounded-2xl border bg-[#F9F9F9] p-4 text-sm font-bold text-[#0A0A0A] placeholder-[#A0A0A0] transition-all focus:bg-white focus:outline-none ${ + className={`${FIELD_BASE_CLASSES} ${ notificationEmailError - ? "border-red-500/50 focus:border-red-500" - : "border-[#E8E8E8] focus:border-[#0A0A0A]" + ? FIELD_ERROR_CLASSES + : FIELD_THEME_CLASSES }`} placeholder="alerts@business.com" /> @@ -409,15 +415,19 @@ const RegistrationForm = React.memo(function RegistrationForm() { diff --git a/frontend/tests/e2e/onboarding-hover.spec.ts b/frontend/tests/e2e/onboarding-hover.spec.ts new file mode 100644 index 0000000..5c48480 --- /dev/null +++ b/frontend/tests/e2e/onboarding-hover.spec.ts @@ -0,0 +1,44 @@ +import { expect, test } from "@playwright/test"; + +test.describe("Onboarding Flow Hover States", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/register"); + await page.waitForLoadState("networkidle"); + }); + + test("uses Pluto-themed hover states for onboarding fields and actions", async ({ + page, + }) => { + const businessNameInput = page.locator("#businessName"); + await businessNameInput.hover(); + + await expect(businessNameInput).toHaveCSS( + "border-top-color", + "rgb(138, 175, 212)" + ); + + const boxShadow = await businessNameInput.evaluate((element) => + window.getComputedStyle(element).boxShadow + ); + expect(boxShadow).not.toBe("none"); + + await page.locator("#businessName").fill("Pluto Labs"); + await page.locator("#email").fill("owner@pluto.test"); + await page.locator("#notificationEmail").fill("alerts@pluto.test"); + await page.locator("#password").fill("VeryStrongPassword!123"); + + const submitButton = page.getByRole("button", { + name: "Create Professional Profile", + }); + await expect(submitButton).toBeEnabled(); + await submitButton.hover(); + await expect(submitButton).toHaveCSS( + "background-color", + "rgb(45, 74, 122)" + ); + + const loginLink = page.getByRole("link", { name: "Log in here" }); + await loginLink.hover(); + await expect(loginLink).toHaveCSS("color", "rgb(61, 100, 148)"); + }); +});