Skip to content

Add dismissible top announcement banner - #48

Merged
BotCoder254 merged 1 commit into
mainfrom
feat/event-driven-orchestration
Jul 17, 2026
Merged

Add dismissible top announcement banner#48
BotCoder254 merged 1 commit into
mainfrom
feat/event-driven-orchestration

Conversation

@BotCoder254

@BotCoder254 BotCoder254 commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Introduces a new AnnouncementBanner component rendered above the app shell with a message, optional external action link, and dismiss button. Dismissal is persisted per announcement ID in localStorage, so re-showing later only requires changing the ID. AppShell was updated to a column layout and now mounts the banner above the existing sidebar/canvas structure.

Summary by CodeRabbit

  • New Features
    • Added a dismissible announcement banner displayed at the top of the app.
    • Announcements can include an optional action link.
    • Dismissal preferences are remembered for future visits.
    • Added accessible status messaging and controls for improved usability.

Introduces a new `AnnouncementBanner` component rendered above the app shell with a message, optional external action link, and dismiss button. Dismissal is persisted per announcement ID in `localStorage`, so re-showing later only requires changing the ID. `AppShell` was updated to a column layout and now mounts the banner above the existing sidebar/canvas structure.
@netlify

netlify Bot commented Jul 17, 2026

Copy link
Copy Markdown

Deploy Preview for overup-app ready!

Name Link
🔨 Latest commit 35a0e78
🔍 Latest deploy log https://app.netlify.com/projects/overup-app/deploys/6a5a95c12fa3f00008752a60
😎 Deploy Preview https://deploy-preview-48--overup-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
overup Ready Ready Preview, Comment Jul 17, 2026 8:52pm

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a configurable, dismissible announcement banner with localStorage persistence and accessible controls, then renders it above the main application shell content.

Changes

Announcement banner

Layer / File(s) Summary
Persisted announcement banner behavior
src/components/layout/AnnouncementBanner.tsx
Adds a configurable banner that restores and persists dismissal state through localStorage, optionally renders an action link, and provides accessible status and dismiss controls.
Application shell placement
src/components/layout/AppShell.tsx
Renders the announcement banner above the main shell content and updates the flex wrapper structure to contain both areas.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AppShell
  participant AnnouncementBanner
  participant localStorage
  participant User
  AppShell->>AnnouncementBanner: Render banner
  AnnouncementBanner->>localStorage: Read dismissal flag
  localStorage-->>AnnouncementBanner: Return stored state
  User->>AnnouncementBanner: Click dismiss
  AnnouncementBanner->>localStorage: Persist dismissal flag
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a new dismissible announcement banner at the top of the app.
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
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/event-driven-orchestration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Summary

This PR introduces a dismissible announcement banner component with localStorage persistence. The implementation is well-structured with good accessibility practices, but there are 2 critical issues that must be fixed before merging:

Critical Issues

  1. Logic Error (Line 35): The banner is initialized as dismissed, preventing it from appearing on first load for new users
  2. Security Vulnerability (Lines 72-82): Missing URL validation creates an XSS risk if user-controlled data is passed to actionHref

Overall Assessment

The component design is solid with proper ARIA attributes, responsive styling, and error handling for localStorage. Once the initialization logic and URL validation are fixed, this will be ready to merge.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

actionHref = 'https://github.com/BotCoder254/overup',
}: AnnouncementBannerProps) {
const storageKey = `${STORAGE_PREFIX}${id}`;
const [dismissed, setDismissed] = useState(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛑 Logic Error: Initialize dismissed state to false instead of true. The current implementation causes the banner to never appear on first render because the useEffect runs after the initial render, so users with no prior dismissal never see the announcement. The useEffect will correctly set it to dismissed if needed after checking localStorage.

Suggested change
const [dismissed, setDismissed] = useState(true);
const [dismissed, setDismissed] = useState(false);

Comment on lines +72 to +82
{actionLabel && actionHref && (
<a
href={actionHref}
target="_blank"
rel="noopener noreferrer"
className="hidden shrink-0 items-center gap-1 rounded bg-white px-3 py-1.5 text-xs font-semibold text-primary transition-colors hover:bg-white/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-primary sm:inline-flex"
>
{actionLabel}
<ArrowUpRight size={14} aria-hidden="true" />
</a>
)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛑 Security Vulnerability: Validate the actionHref URL before rendering the anchor tag to prevent XSS attacks through javascript: protocol URIs. An attacker could inject malicious JavaScript if this prop accepts user-controlled input.1

Suggested change
{actionLabel && actionHref && (
<a
href={actionHref}
target="_blank"
rel="noopener noreferrer"
className="hidden shrink-0 items-center gap-1 rounded bg-white px-3 py-1.5 text-xs font-semibold text-primary transition-colors hover:bg-white/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-primary sm:inline-flex"
>
{actionLabel}
<ArrowUpRight size={14} aria-hidden="true" />
</a>
)}
{actionLabel && actionHref && (() => {
try {
const url = new URL(actionHref, window.location.origin);
if (!['http:', 'https:'].includes(url.protocol)) return null;
} catch {
return null;
}
return (
<a
href={actionHref}
target="_blank"
rel="noopener noreferrer"
className="hidden shrink-0 items-center gap-1 rounded bg-white px-3 py-1.5 text-xs font-semibold text-primary transition-colors hover:bg-white/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-primary sm:inline-flex"
>
{actionLabel}
<ArrowUpRight size={14} aria-hidden="true" />
</a>
);
})()}

Footnotes

  1. CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') - https://cwe.mitre.org/data/definitions/79.html

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@src/components/layout/AnnouncementBanner.tsx`:
- Around line 58-94: Update every rounded class in the AnnouncementBanner JSX,
including the icon container, action link, and dismiss button, from rounded to
rounded-md so all border radii follow the 6px guideline.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 107f8d1d-3140-40ba-8fc7-6782f7cebb86

📥 Commits

Reviewing files that changed from the base of the PR and between 6b5065c and 35a0e78.

📒 Files selected for processing (2)
  • src/components/layout/AnnouncementBanner.tsx
  • src/components/layout/AppShell.tsx

Comment on lines +58 to +94
return (
<div role="status" aria-live="polite" className="shrink-0 bg-primary text-white">
<div className="mx-auto flex items-center gap-2.5 px-3 py-2 sm:gap-3 sm:px-5">
<span
aria-hidden="true"
className="hidden shrink-0 rounded bg-white/15 p-1.5 sm:inline-flex"
>
<Megaphone size={16} />
</span>

<p className="min-w-0 flex-1 text-[13px] leading-snug line-clamp-2 sm:line-clamp-none sm:text-sm">
{message}
</p>

{actionLabel && actionHref && (
<a
href={actionHref}
target="_blank"
rel="noopener noreferrer"
className="hidden shrink-0 items-center gap-1 rounded bg-white px-3 py-1.5 text-xs font-semibold text-primary transition-colors hover:bg-white/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-primary sm:inline-flex"
>
{actionLabel}
<ArrowUpRight size={14} aria-hidden="true" />
</a>
)}

<button
type="button"
onClick={close}
aria-label="Dismiss announcement"
className="shrink-0 rounded p-1.5 text-white/80 transition-colors hover:bg-white/15 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-primary"
>
<X size={18} aria-hidden="true" />
</button>
</div>
</div>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Ensure border radius adheres to the 6px guideline.

The rounded class in Tailwind CSS typically applies a 4px border radius. As per coding guidelines, the React frontend should "keep border radius at 6px". Please update the rounded classes to rounded-md (which defaults to 6px) or rounded-[6px].

🎨 Proposed fixes for border radius
-          className="hidden shrink-0 rounded bg-white/15 p-1.5 sm:inline-flex"
+          className="hidden shrink-0 rounded-md bg-white/15 p-1.5 sm:inline-flex"
-            className="hidden shrink-0 items-center gap-1 rounded bg-white px-3 py-1.5 text-xs font-semibold text-primary transition-colors hover:bg-white/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-primary sm:inline-flex"
+            className="hidden shrink-0 items-center gap-1 rounded-md bg-white px-3 py-1.5 text-xs font-semibold text-primary transition-colors hover:bg-white/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-primary sm:inline-flex"
-          className="shrink-0 rounded p-1.5 text-white/80 transition-colors hover:bg-white/15 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-primary"
+          className="shrink-0 rounded-md p-1.5 text-white/80 transition-colors hover:bg-white/15 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-primary"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return (
<div role="status" aria-live="polite" className="shrink-0 bg-primary text-white">
<div className="mx-auto flex items-center gap-2.5 px-3 py-2 sm:gap-3 sm:px-5">
<span
aria-hidden="true"
className="hidden shrink-0 rounded bg-white/15 p-1.5 sm:inline-flex"
>
<Megaphone size={16} />
</span>
<p className="min-w-0 flex-1 text-[13px] leading-snug line-clamp-2 sm:line-clamp-none sm:text-sm">
{message}
</p>
{actionLabel && actionHref && (
<a
href={actionHref}
target="_blank"
rel="noopener noreferrer"
className="hidden shrink-0 items-center gap-1 rounded bg-white px-3 py-1.5 text-xs font-semibold text-primary transition-colors hover:bg-white/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-primary sm:inline-flex"
>
{actionLabel}
<ArrowUpRight size={14} aria-hidden="true" />
</a>
)}
<button
type="button"
onClick={close}
aria-label="Dismiss announcement"
className="shrink-0 rounded p-1.5 text-white/80 transition-colors hover:bg-white/15 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-primary"
>
<X size={18} aria-hidden="true" />
</button>
</div>
</div>
);
return (
<div role="status" aria-live="polite" className="shrink-0 bg-primary text-white">
<div className="mx-auto flex items-center gap-2.5 px-3 py-2 sm:gap-3 sm:px-5">
<span
aria-hidden="true"
className="hidden shrink-0 rounded-md bg-white/15 p-1.5 sm:inline-flex"
>
<Megaphone size={16} />
</span>
<p className="min-w-0 flex-1 text-[13px] leading-snug line-clamp-2 sm:line-clamp-none sm:text-sm">
{message}
</p>
{actionLabel && actionHref && (
<a
href={actionHref}
target="_blank"
rel="noopener noreferrer"
className="hidden shrink-0 items-center gap-1 rounded-md bg-white px-3 py-1.5 text-xs font-semibold text-primary transition-colors hover:bg-white/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-primary sm:inline-flex"
>
{actionLabel}
<ArrowUpRight size={14} aria-hidden="true" />
</a>
)}
<button
type="button"
onClick={close}
aria-label="Dismiss announcement"
className="shrink-0 rounded-md p-1.5 text-white/80 transition-colors hover:bg-white/15 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-primary"
>
<X size={18} aria-hidden="true" />
</button>
</div>
</div>
);
🤖 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 `@src/components/layout/AnnouncementBanner.tsx` around lines 58 - 94, Update
every rounded class in the AnnouncementBanner JSX, including the icon container,
action link, and dismiss button, from rounded to rounded-md so all border radii
follow the 6px guideline.

Source: Coding guidelines

@BotCoder254
BotCoder254 merged commit 4836fc3 into main Jul 17, 2026
9 of 11 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.

1 participant