Skip to content

Conversation

@vas3a
Copy link
Collaborator

@vas3a vas3a commented Oct 16, 2025

Related JIRA Ticket:

https://topcoder.atlassian.net/browse/PM-2133 - dismissable banner

What's in this PR?

Created component for notification banner that's also dismisable. Also created context for notifications so we can easily use them across platform-ui. This is heavily extendable. I only created the basics, but it can easily be extended to other type of notifications/banners.

image image

@vas3a vas3a requested a review from kkartunov October 16, 2025 14:54
id: 'ai-review-scores-warning',
message: `AI Review Scores are advisory only to provide immediate,
educational, and actionable feedback to members.
AI Review Scores are not influence winner selection.`,

Choose a reason for hiding this comment

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

The message text contains a grammatical error. It should be 'AI Review Scores do not influence winner selection.' instead of 'AI Review Scores are not influence winner selection.'

id: 'ai-review-scores-warning',
message: `AI Review Scores are advisory only to provide immediate,
educational, and actionable feedback to members.
AI Review Scores are not influence winner selection.`,

Choose a reason for hiding this comment

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

[❗❗ correctness]
Typo in the message: 'AI Review Scores are not influence winner selection.' should be 'AI Review Scores do not influence winner selection.'

const Notification: FC<NotificationProps> = props => {
const handleClose = useCallback((save?: boolean) => {
props.onClose(props.notification.id, save)
}, [props.onClose])

Choose a reason for hiding this comment

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

The dependency array for useCallback should include props.notification.id to ensure that the callback is updated if the id changes.

- opened
- synchronize
permissions:
pull-requests: write

Choose a reason for hiding this comment

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

[⚠️ security]
Consider specifying more granular permissions instead of pull-requests: write to follow the principle of least privilege. This can help minimize potential security risks.

uses: topcoder-platform/tc-ai-pr-reviewer@prompt-v2
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # The GITHUB_TOKEN is there by default so you just need to keep it like it is and not necessarily need to add it as secret as it will throw an error. [More Details](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret)
LAB45_API_KEY: ${{ secrets.LAB45_API_KEY }}

Choose a reason for hiding this comment

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

[💡 maintainability]
The exclude pattern uses a comma-separated list, which is correct, but ensure that the patterns are comprehensive enough to cover all file types you intend to exclude. Consider adding comments to clarify the purpose of excluding these specific file types.

const canViewChallenge = isAdmin || isUserResource
const statusLabel = isPastReviewDetail
? formatChallengeStatusLabel(challengeInfo?.status)
: undefined

Choose a reason for hiding this comment

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

[⚠️ correctness]
The useEffect hook that displays the banner notification does not include removeNotification in its dependency array. This could lead to stale closures if removeNotification changes. Consider adding removeNotification to the dependency array.

const shouldShowChallengeMetaRow = Boolean(statusLabel) || trackTypePills.length > 0

useEffect(() => {
const notification = showBannerNotification({

Choose a reason for hiding this comment

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

[❗❗ correctness]
The message in showBannerNotification contains a typo: 'AI Review Scores are not influence winner selection.' should be 'AI Review Scores do not influence winner selection.'

const { notifications, removeNotification }: NotificationContextType = useNotification()

return (
<div className={styles.wrap}>

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Consider adding a type for the notifications array elements to ensure type safety and improve maintainability. This will help catch errors if the structure of notification objects changes in the future.

const [notifications, setNotifications] = useState<Notification[]>([])

const removeNotification = useCallback((id: string, persist?: boolean) => {
setNotifications(prev => prev.filter(n => n.id !== id))

Choose a reason for hiding this comment

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

[⚠️ correctness]
The id generation logic using uuid and message.id assumes that message.id is unique and defined when message is an object. Consider adding a check or fallback for cases where message.id might be undefined to avoid potential id collisions.

}, [])

const notify = useCallback(
(message: NotifyPayload, type: NotificationType = 'info', duration = 3000) => {

Choose a reason for hiding this comment

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

[⚠️ performance]
The setTimeout function used for removing notifications after a certain duration does not clear the timeout if the component unmounts before the timeout completes. This could lead to memory leaks. Consider storing the timeout ID and clearing it in a cleanup function.

@@ -0,0 +1,9 @@
const lsKeyPrefix = 'notificationDismissed'

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Consider using a more descriptive prefix for lsKeyPrefix to avoid potential key collisions in localStorage. This will improve maintainability and reduce the risk of overwriting unrelated data.

const lsKeyPrefix = 'notificationDismissed'

export const wasDismissed = (id: string): boolean => (
(localStorage.getItem(`${lsKeyPrefix}[${id}]`)) !== null

Choose a reason for hiding this comment

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

[⚠️ design]
The use of localStorage directly ties this utility to the browser environment. If this utility is intended to be used in environments other than the browser, consider abstracting the storage mechanism to improve portability.

interface NotificationProps {
notification: {
icon?: ReactNode;
id: string;

Choose a reason for hiding this comment

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

[⚠️ correctness]
Consider using a union type for notification.type to restrict it to known values like 'banner'. This will help catch errors at compile time if an unsupported type is used.

onClose={handleClose}
/>
)
}

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Returning an empty fragment (<></>) when the notification type is not 'banner' might not be the best approach. Consider handling other types explicitly or logging a warning for unsupported types to aid debugging.

@import '../../../styles/includes';

.wrap {
background: #60267D;

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Consider using a CSS variable or a SCSS variable for the background color #60267D to improve maintainability and consistency across the project.

color: $tc-white;
flex: 0 0;
margin-left: auto;
border-radius: 50%;

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The use of border: 2px solid white could be replaced with $tc-white to ensure consistency with the color scheme defined in your SCSS variables.

description: 'Content displayed inside the notification banner',
},
persistent: {
defaultValue: false,

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Consider adding a default value for the content prop to ensure the component behaves predictably even if no content is provided. This can improve maintainability and prevent potential runtime errors.

}

const NotificationBanner: FC<NotificationBannerProps> = props => {
const handleClose = useCallback(() => {

Choose a reason for hiding this comment

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

[💡 performance]
Using useCallback here is unnecessary unless handleClose is passed to a child component that relies on referential equality for optimization. Consider removing it to simplify the code.

)}

{props.content}

Choose a reason for hiding this comment

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

[⚠️ design]
Using onClick on a div for the close button may not be accessible for keyboard users. Consider using a button element instead to improve accessibility.

@vas3a
Copy link
Collaborator Author

vas3a commented Oct 16, 2025

closing as there's too much noise in the comments

@vas3a vas3a closed this Oct 16, 2025
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