Skip to content

feat: Enhance UI components with improved styling and functionality - #572

Merged
Dprof-in-tech merged 2 commits into
mainfrom
fix/noblocks-refund-account-input-styling
Jul 7, 2026
Merged

feat: Enhance UI components with improved styling and functionality#572
Dprof-in-tech merged 2 commits into
mainfrom
fix/noblocks-refund-account-input-styling

Conversation

@sundayonah

@sundayonah sundayonah commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR polishes the Noblocks Play homepage promo experience and fixes several UI layering and form-feedback issues introduced while aligning the banner/modal with Figma.

Noblocks Play promo (homepage)

  • Adds PlayPromoButton to AppLayout so a compact Play CTA remains in the navbar area after the promo banner is dismissed.
  • Refines PlayPromoBanner layout for mobile and desktop:
    • Mobile: shorter Join CTA, adjusted typography/spacing for headline and subcopy, larger dismiss (X) control.
    • Desktop: Join a league + dismiss aligned under navbar Network and Settings using the same max-w-screen-2xl container.
  • Improves PlayPromoModal dismiss behavior and persistence:
    • Modal dismiss is stored in localStorage (noblocks_play_promo_dismissed) so it only shows once per visitor.
    • Banner dismiss is stored separately (noblocks_play_promo_banner_dismissed) and drives usePlayPromoBannerVisible() for layout spacing and PlayPromoButton visibility.
    • Modal renders only after client mount to avoid hydration/click issues; overlay uses pointer-events-none with interactive content on pointer-events-auto.
  • Fixes z-index/stacking conflicts between the promo banner, player collage, and navbar:
    • Banner strip stays below the navbar (z-[30]).
    • Player/trophy collage is rendered on a separate fixed layer (z-[55]) so the cup tip and heads can pop above the strip.
    • Navbar logo dropdown is portaled to document.body at z-[60] so it remains visible above the collage.

Navbar

  • Refines logo dropdown behavior with fixed positioning, resize/scroll repositioning, and improved hover bridge / mouse-leave handling between the trigger and portaled menu.

Refund account modal

  • Updates AddRefundAccountModal to show dynamic error styling on the account number input (border-red-500) and inline InputError when validation fails.

Play experience

  • Updates PlayShell copy/labels so navigation back to swap is clearer (Home on mobile, Back to homepage on desktop).

Impacts

  • UI-only changes; no API or contract changes.
  • No breaking changes expected for consumers of Noblocks routes/components.

Alternatives considered

  • Raising only navbar or banner z-index without splitting collage/dropdown layers — rejected because it caused either the cup tip or logo dropdown to be clipped/hidden.
  • Keeping the logo dropdown inside the navbar stacking context — rejected because the promo collage layer painted over it.

References

No linked issues for this PR.

Testing

Manual verification on homepage with fantasyEnabled enabled:

  • Promo modal opens once, Play now and X remain clickable after page load; dismiss persists across refresh.
  • Promo banner shows under navbar; player collage cup tip is visible above the strip.
  • Hover/open Noblocks logo dropdown while banner is visible — menu appears above collage and is fully clickable.
  • Dismiss banner via X or Join/Join a league — banner hides, PlayPromoButton appears, spacing updates correctly.
  • Mobile banner: Join, X, headline, and subcopy spacing look correct.
  • Desktop banner: Join a league + X align under Network/Settings.
  • AddRefundAccountModal: invalid account number shows red border + error message.
  • /play: Back to homepage / Home link returns to /.
Screenshot 2026-07-07 161948 Screenshot 2026-07-07 161932 Screenshot 2026-07-07 161910 Screenshot 2026-07-07 161850 Screenshot 2026-07-07 161819

Checklist

  • I have added documentation and tests for new/changed functionality in this PR
  • All active GitHub checks for tests, formatting, and security are passing
  • The correct base branch is being used, if not main

By submitting a PR, I agree to Paycrest's Contributor Code of Conduct and Contribution Guide.


If you want this committed or opened as a PR via gh, switch to Agent mode and I can do that for you.

Summary by CodeRabbit

  • New Features

    • Added a Play promo button and banner to the homepage.
    • Improved Play navigation labels for small screens.
  • Bug Fixes

    • Improved the navbar logo dropdown behavior so it remains open when moving between the related menu areas.
    • Made the Play promo banner dismissal persist across sessions and browser tabs.
    • Updated account number input styling to clearly show and highlight validation errors.

- Updated AddRefundAccountModal to include dynamic error styling for account number input.
- Added PlayPromoButton to AppLayout for better user engagement on the homepage.
- Refined Navbar dropdown behavior with improved positioning and event handling.
- Enhanced PlayPromoModal with better dismiss functionality and local storage management for banner visibility.
- Updated PlayShell component to clarify navigation back to the homepage.
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot is not enabled for this team, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes update refund input error styling, portaled navbar dropdown behavior, Play promo modal/banner persistence and layout, and homepage/header wiring for Play promo entry points.

Changes

UI and interaction updates

Layer / File(s) Summary
Refund account input error styling
app/components/AddRefundAccountModal.tsx
Account number input border switches to red styling via classNames(...) when accountNumberError is set.
Navbar portaled logo dropdown
app/components/Navbar.tsx
Adds positioning state and refs, hover-close helpers, and replaces inline dropdown rendering with a portaled fixed-position dropdown and bridge.
Play promo modal and banner rework
app/components/PlayPromo.tsx
Reworks modal backdrop/collage wrappers, adds persisted banner dismissal, gates modal/banner rendering on client mount, restructures banner layout with a separate collage overlay and mobile close button, updates collage cropping, and adds the Play CTA arrow icon.
AppLayout and PlayShell wiring
app/components/AppLayout.tsx, app/components/play/PlayShell.tsx
Imports and renders PlayPromoButton on the homepage, adjusts the banner container margin, and changes the PlayShell home link labels for responsive display.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • paycrest/noblocks#197: Both PRs make substantial code-level changes to the Navbar logo dropdown trigger, alignment, hover handling, and outside-close behavior.
  • paycrest/noblocks#562: The Play promo modal/banner refactor and homepage wiring build directly on the same Play promo components and layout paths.
  • paycrest/noblocks#414: Both PRs touch app/components/AppLayout.tsx import and layout wiring.

Suggested reviewers: chibie, onahprosper, 5ran6

Poem

A rabbit hopped through code today,
Dropdowns now portal far away 🐇
The banner remembers when you say goodbye,
Stored in localStorage, no need to ask why.
Play promo glows with a little new arrow,
Home or back home — whichever path narrow.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is generic and doesn't clearly summarize the main change set. Use a more specific title like 'Polish Noblocks Play promo banner, navbar dropdown, and refund form feedback.'
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description follows the template with Description, References, Testing, and Checklist sections filled out.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
app/components/AppLayout.tsx (1)

56-61: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

mb-16 and md:mb-[64px] are the same value — the responsive override is a no-op.

Tailwind's default spacing scale makes mb-16 equal to 64px, so md:mb-[64px] never changes anything from the base mb-16. If distinct mobile/desktop offsets were intended (banner heights differ slightly: 65px mobile vs 64px desktop, both plus mt-1), this doesn't achieve that.

🔧 Example if distinct spacing was intended
               className={`relative ${showPlayPromoBanner
-                ? "mb-16 md:mb-[64px]"
+                ? "mb-[69px] md:mb-[68px]"
                  : config.maintenanceEnabled
                    ? "mb-16"
                    : ""
                }`}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/AppLayout.tsx` around lines 56 - 61, The `showPlayPromoBanner`
branch in `AppLayout` uses `mb-16 md:mb-[64px]`, but those values are identical
so the `md` override has no effect. Update the `className` logic in `AppLayout`
to use a truly different desktop offset if the banner spacing is meant to change
across breakpoints, keeping the conditional structure around
`showPlayPromoBanner` and `config.maintenanceEnabled` intact.
app/components/AddRefundAccountModal.tsx (1)

276-296: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Wire aria-invalid/aria-describedby to the visual error state.

The border now reflects accountNumberError visually, but the input isn't programmatically associated with the error for assistive tech. Screen reader users won't hear the validation message when returning to fix the field.

♿️ Suggested fix
                 <input
                   id="refund-account-number"
                   type="text"
                   inputMode="numeric"
                   autoComplete="off"
                   value={accountNumber}
                   onChange={(e) => {
                     setAccountNumber(e.target.value);
                     setFormError(null);
                   }}
                   placeholder={getOfframpAccountIdentifierPlaceholder(currency, selectedInstitution?.type)}
+                  aria-invalid={!!accountNumberError}
+                  aria-describedby={accountNumberError ? "refund-account-number-error" : undefined}
                   className={classNames(
                     "w-full rounded-xl border bg-white px-3.5 py-3 text-sm text-neutral-900 outline-none transition-colors placeholder:text-neutral-400 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/25 dark:bg-[`#202020`] dark:text-white dark:placeholder:text-white/40 dark:focus:border-blue-500 dark:focus:ring-blue-500/35",
                     accountNumberError
                       ? "border-red-500 dark:border-red-500"
                       : "border-neutral-200 dark:border-white/[0.12]",
                   )}
                 />
                 {accountNumberError ? (
-                  <InputError message={accountNumberError} />
+                  <InputError id="refund-account-number-error" message={accountNumberError} />
                 ) : null}

(InputError would need to forward an id prop; not shown in this diff.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/AddRefundAccountModal.tsx` around lines 276 - 296, The refund
account number field in AddRefundAccountModal shows a visual error state via
accountNumberError, but it is not linked to the validation message for assistive
technologies. Update the input’s accessibility props so the
refund-account-number field sets aria-invalid when accountNumberError is present
and points aria-describedby to the rendered InputError message. To do that, make
InputError support a stable id and connect it from the input and the existing
accountNumberError conditional rendering.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/components/Navbar.tsx`:
- Around line 161-173: The logo dropdown only closes via hover/trigger
interaction, so touch users can’t dismiss it by tapping outside. Add an
outside-press fallback in Navbar.tsx by wiring a pointerdown or mousedown
document listener alongside closeLogoDropdownUnlessHovered and isDropdownOpen,
and have it call setIsDropdownOpen(false) when the event target is outside both
dropdownRef and portaledDropdownRef. Use the existing refs and the
closeLogoDropdownUnlessHovered logic to keep the behavior consistent.

In `@app/components/PlayPromo.tsx`:
- Around line 120-135: usePlayPromoBannerVisible currently hardcodes visible to
true, which causes returning users to briefly see the promo banner until the
effect syncs with storage. Update the hook to initialize state from
isBannerDismissed() using a lazy useState initializer, and keep the existing
sync effect/event listeners in place so PlayPromoButton and other consumers read
the correct initial visibility from the start.
- Line 187: The close animation in PlayPromo’s Dialog is not being played
because the dialog unmounts immediately when isOpen turns false, so the exit
state never has a chance to run. Update the Dialog rendering block by wrapping
it in AnimatePresence and marking the Dialog as static so it stays mounted
through the animation, or remove the exit variant from the motion element if you
do not want an unmount animation. Use the PlayPromo component’s Dialog and
AnimatePresence/motion setup to apply the fix.

---

Nitpick comments:
In `@app/components/AddRefundAccountModal.tsx`:
- Around line 276-296: The refund account number field in AddRefundAccountModal
shows a visual error state via accountNumberError, but it is not linked to the
validation message for assistive technologies. Update the input’s accessibility
props so the refund-account-number field sets aria-invalid when
accountNumberError is present and points aria-describedby to the rendered
InputError message. To do that, make InputError support a stable id and connect
it from the input and the existing accountNumberError conditional rendering.

In `@app/components/AppLayout.tsx`:
- Around line 56-61: The `showPlayPromoBanner` branch in `AppLayout` uses `mb-16
md:mb-[64px]`, but those values are identical so the `md` override has no
effect. Update the `className` logic in `AppLayout` to use a truly different
desktop offset if the banner spacing is meant to change across breakpoints,
keeping the conditional structure around `showPlayPromoBanner` and
`config.maintenanceEnabled` intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 56a63fd7-5fd8-4c69-b0dc-400346ebeebc

📥 Commits

Reviewing files that changed from the base of the PR and between a56433b and 44036d6.

📒 Files selected for processing (5)
  • app/components/AddRefundAccountModal.tsx
  • app/components/AppLayout.tsx
  • app/components/Navbar.tsx
  • app/components/PlayPromo.tsx
  • app/components/play/PlayShell.tsx

Comment thread app/components/Navbar.tsx
Comment thread app/components/PlayPromo.tsx
Comment thread app/components/PlayPromo.tsx
…ience

- Added event handling in Navbar to close dropdown when clicking outside.
- Updated PlayPromoModal to utilize AnimatePresence for smoother animations and improved dismiss functionality.
- Enhanced visibility management for the PlayPromo banner with local storage integration.
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot is not enabled for this team, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/components/PlayPromo.tsx (1)

120-140: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Start ready as false on the first render.

ready is derived from typeof window !== "undefined", so the server returns false while the client's first hydration render returns true. AppLayout uses this hook to decide whether to render PlayPromoBanner/PlayPromoButton, which can make the server and client trees diverge and bring back the banner flash this gate is meant to prevent. Initialize ready to false on both passes and flip it in useEffect.

🔧 Fix
-  const [ready, setReady] = useState(() => typeof window !== "undefined");
+  const [ready, setReady] = useState(false);

The same pattern in PlayPromoBanner should use the same initializer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/PlayPromo.tsx` around lines 120 - 140,
`usePlayPromoBannerVisible` is initializing `ready` differently on server and
client, which can cause hydration mismatch and banner flash; start `ready` as
false on the first render and set it to true inside the existing `useEffect`
after syncing visibility. Make the same initializer change in `PlayPromoBanner`
wherever it uses the same ready-gating pattern, so both components use a
consistent client-only flip after mount.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@app/components/PlayPromo.tsx`:
- Around line 120-140: `usePlayPromoBannerVisible` is initializing `ready`
differently on server and client, which can cause hydration mismatch and banner
flash; start `ready` as false on the first render and set it to true inside the
existing `useEffect` after syncing visibility. Make the same initializer change
in `PlayPromoBanner` wherever it uses the same ready-gating pattern, so both
components use a consistent client-only flip after mount.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 84b26246-11f7-47a5-a514-f4dae6dcfca8

📥 Commits

Reviewing files that changed from the base of the PR and between 44036d6 and cab8b63.

📒 Files selected for processing (2)
  • app/components/Navbar.tsx
  • app/components/PlayPromo.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/components/Navbar.tsx

@Dprof-in-tech Dprof-in-tech left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM!

@Dprof-in-tech
Dprof-in-tech merged commit 03bf66b into main Jul 7, 2026
2 checks passed
@Dprof-in-tech
Dprof-in-tech deleted the fix/noblocks-refund-account-input-styling branch July 7, 2026 18:37
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.

2 participants