From 419163ebcf7e17101c3a967c608b841d0aed79c3 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Oct 2025 17:04:29 +0300 Subject: [PATCH 1/9] PM-2133 - dismissable banner --- src/apps/platform/src/PlatformApp.tsx | 3 +- src/apps/platform/src/providers/Providers.tsx | 6 +- .../ChallengeDetailsPage.tsx | 10 +++ .../core/lib/profile/profile-context/index.ts | 2 +- .../profile-context/profile.context.tsx | 4 +- src/libs/shared/lib/components/index.ts | 1 + .../notifications/Notifications.container.tsx | 19 +++++ .../notifications/Notifications.context.tsx | 74 +++++++++++++++++++ .../lib/components/notifications/index.ts | 2 + .../notifications/localstorage.utils.ts | 7 ++ src/libs/ui/lib/components/index.ts | 1 + .../components/notification/Notification.tsx | 19 +++++ .../banner/NotificationBanner.module.scss | 26 +++++++ .../banner/NotificationBanner.stories.tsx | 33 +++++++++ .../banner/NotificationBanner.tsx | 36 +++++++++ .../components/notification/banner/index.ts | 1 + .../ui/lib/components/notification/index.ts | 2 + 17 files changed, 241 insertions(+), 5 deletions(-) create mode 100644 src/libs/shared/lib/components/notifications/Notifications.container.tsx create mode 100644 src/libs/shared/lib/components/notifications/Notifications.context.tsx create mode 100644 src/libs/shared/lib/components/notifications/index.ts create mode 100644 src/libs/shared/lib/components/notifications/localstorage.utils.ts create mode 100644 src/libs/ui/lib/components/notification/Notification.tsx create mode 100644 src/libs/ui/lib/components/notification/banner/NotificationBanner.module.scss create mode 100644 src/libs/ui/lib/components/notification/banner/NotificationBanner.stories.tsx create mode 100644 src/libs/ui/lib/components/notification/banner/NotificationBanner.tsx create mode 100644 src/libs/ui/lib/components/notification/banner/index.ts create mode 100644 src/libs/ui/lib/components/notification/index.ts diff --git a/src/apps/platform/src/PlatformApp.tsx b/src/apps/platform/src/PlatformApp.tsx index dd89390fb..e8d9480d5 100644 --- a/src/apps/platform/src/PlatformApp.tsx +++ b/src/apps/platform/src/PlatformApp.tsx @@ -1,7 +1,7 @@ import { FC } from 'react' import { toast, ToastContainer } from 'react-toastify' -import { useViewportUnitsFix } from '~/libs/shared' +import { useViewportUnitsFix, NotificationsContainer } from '~/libs/shared' import { AppFooter } from './components/app-footer' import { AppHeader } from './components/app-header' @@ -14,6 +14,7 @@ const PlatformApp: FC<{}> = () => { return ( +
diff --git a/src/apps/platform/src/providers/Providers.tsx b/src/apps/platform/src/providers/Providers.tsx index b16709bb2..b7064659d 100644 --- a/src/apps/platform/src/providers/Providers.tsx +++ b/src/apps/platform/src/providers/Providers.tsx @@ -1,7 +1,7 @@ import { FC, ReactNode } from 'react' import { authUrlLogout, ProfileProvider } from '~/libs/core' -import { ConfigContextProvider } from '~/libs/shared' +import { ConfigContextProvider, NotificationProvider } from '~/libs/shared' import { PlatformRouterProvider } from './platform-router.provider' @@ -13,7 +13,9 @@ const Providers: FC = props => ( - {props.children} + + {props.children} + diff --git a/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx b/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx index bd44eb851..066de86c4 100644 --- a/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx +++ b/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx @@ -60,6 +60,7 @@ import { } from '../../../config/routes.config' import styles from './ChallengeDetailsPage.module.scss' +import { useNotification } from '~/libs/shared' interface Props { className?: string @@ -226,6 +227,7 @@ const computePhaseCompletionFromScreenings = ( // eslint-disable-next-line complexity export const ChallengeDetailsPage: FC = (props: Props) => { + const { showBannerNotification, removeNotification } = useNotification(); const [searchParams, setSearchParams] = useSearchParams() const location = useLocation() const navigate = useNavigate() @@ -1323,6 +1325,14 @@ export const ChallengeDetailsPage: FC = (props: Props) => { : undefined const shouldShowChallengeMetaRow = Boolean(statusLabel) || trackTypePills.length > 0 + useEffect(() => { + const notification = showBannerNotification({ + 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.', + }) + return () => notification && removeNotification(notification.id); + }, [showBannerNotification]); + return ( = createContext(defaultProfileContextData) +export const useProfileContext = () => useContext(profileContext); + export default profileContext diff --git a/src/libs/shared/lib/components/index.ts b/src/libs/shared/lib/components/index.ts index ac827849a..8b9d29e69 100644 --- a/src/libs/shared/lib/components/index.ts +++ b/src/libs/shared/lib/components/index.ts @@ -3,6 +3,7 @@ export * from './modals' export * from './profile-picture' export * from './input-skill-selector' export * from './member-skill-editor' +export * from './notifications' export * from './skill-pill' export * from './expandable-list' export * from './grouped-skills-ui' diff --git a/src/libs/shared/lib/components/notifications/Notifications.container.tsx b/src/libs/shared/lib/components/notifications/Notifications.container.tsx new file mode 100644 index 000000000..1e809dcfd --- /dev/null +++ b/src/libs/shared/lib/components/notifications/Notifications.container.tsx @@ -0,0 +1,19 @@ +import { FC } from 'react' + +import { Notification } from '~/libs/ui' + +import { useNotification } from './Notifications.context'; + +const NotificationsContainer: FC = () => { + const { notifications, removeNotification } = useNotification(); + + return ( +
+ {notifications.map(n => ( + + ))} +
+ ) +} + +export default NotificationsContainer diff --git a/src/libs/shared/lib/components/notifications/Notifications.context.tsx b/src/libs/shared/lib/components/notifications/Notifications.context.tsx new file mode 100644 index 000000000..323ec6972 --- /dev/null +++ b/src/libs/shared/lib/components/notifications/Notifications.context.tsx @@ -0,0 +1,74 @@ +import React, { createContext, useContext, useState, useCallback, ReactNode } from "react"; +import { useProfileContext } from "~/libs/core"; +import { dismiss, wasDismissed } from "./localstorage.utils"; + +export type NotificationType = "success" | "error" | "info" | "warning" | "banner"; + +export interface Notification { + id: string; + type: NotificationType; + message: string; + duration?: number; // in ms +} + +type NotifyPayload = string | (Partial & { message: string }) + +interface NotificationContextType { + notifications: Notification[]; + notify: (message: NotifyPayload, type?: NotificationType, duration?: number) => Notification | void; + showBannerNotification: (message: NotifyPayload) => Notification | void; + removeNotification: (id: string) => void; +} + +const NotificationContext = createContext(undefined); + +export const useNotification = (): NotificationContextType => { + const context = useContext(NotificationContext); + if (!context) throw new Error("useNotification must be used within a NotificationProvider"); + return context; +}; + +export const NotificationProvider: React.FC<{ + children: ReactNode, +}> = ({ children }) => { + const profileCtx = useProfileContext() + const uuid = profileCtx.profile?.userId ?? 'annon'; + const [notifications, setNotifications] = useState([]); + + const removeNotification = useCallback((id: string, persist?: boolean) => { + setNotifications(prev => prev.filter(n => n.id !== id)); + if (persist) { + dismiss(id); + } + }, []); + + const notify = useCallback( + (message: NotifyPayload, type: NotificationType = "info", duration = 3000) => { + const id = `${uuid}[${typeof message === 'string' ? message : message.id}]`; + const newNotification: Notification = typeof message === 'string' ? { id, message, type, duration } : { type, duration, ...message, id }; + + if (wasDismissed(id)) { + return; + } + + setNotifications(prev => [...prev, newNotification]); + + if (duration > 0) { + setTimeout(() => removeNotification(id), duration); + } + + return newNotification; + }, + [uuid] + ); + + const showBannerNotification = useCallback(( + message: NotifyPayload, + ) => notify(message, 'banner', 0), [notify]); + + return ( + + {children} + + ); +}; diff --git a/src/libs/shared/lib/components/notifications/index.ts b/src/libs/shared/lib/components/notifications/index.ts new file mode 100644 index 000000000..d2eaff448 --- /dev/null +++ b/src/libs/shared/lib/components/notifications/index.ts @@ -0,0 +1,2 @@ +export { default as NotificationsContainer } from './Notifications.container' +export * from './Notifications.context' diff --git a/src/libs/shared/lib/components/notifications/localstorage.utils.ts b/src/libs/shared/lib/components/notifications/localstorage.utils.ts new file mode 100644 index 000000000..46e776cb8 --- /dev/null +++ b/src/libs/shared/lib/components/notifications/localstorage.utils.ts @@ -0,0 +1,7 @@ +export const wasDismissed = (id: string): boolean => ( + (localStorage.getItem(`dismissed[${id}]`)) !== null +) + +export const dismiss = (id: string): void => { + localStorage.setItem(`dismissed[${id}]`, JSON.stringify(true)) +} diff --git a/src/libs/ui/lib/components/index.ts b/src/libs/ui/lib/components/index.ts index 306c469d6..2e0d2f067 100644 --- a/src/libs/ui/lib/components/index.ts +++ b/src/libs/ui/lib/components/index.ts @@ -3,6 +3,7 @@ export * from './content-layout' export * from './default-member-icon' // NOTE: for some reason, modals needs to be imported prior to form export * from './modals' +export * from './notification' export * from './form' export * from './loading-spinner' export * from './page-divider' diff --git a/src/libs/ui/lib/components/notification/Notification.tsx b/src/libs/ui/lib/components/notification/Notification.tsx new file mode 100644 index 000000000..712d3a83c --- /dev/null +++ b/src/libs/ui/lib/components/notification/Notification.tsx @@ -0,0 +1,19 @@ +import { FC } from 'react' + +import { NotificationBanner } from './banner' + +interface NotificationProps { + notification: { message: string; id: string; type: string } + onClose: (id: string, save?: boolean) => void +} + +const Notification: FC = props => { + + if (props.notification.type === 'banner') { + return props.onClose(props.notification.id, save)} /> + } + + return null; +} + +export default Notification diff --git a/src/libs/ui/lib/components/notification/banner/NotificationBanner.module.scss b/src/libs/ui/lib/components/notification/banner/NotificationBanner.module.scss new file mode 100644 index 000000000..3fa146f8f --- /dev/null +++ b/src/libs/ui/lib/components/notification/banner/NotificationBanner.module.scss @@ -0,0 +1,26 @@ +@import '../../../styles/includes'; + +.wrap { + background: #60267D; + color: $tc-white; + + .inner { + max-width: $xxl-min; + padding: $sp-3 0; + @include pagePaddings; + margin: 0 auto; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + @include ltemd { + display: block; + position: relative; + } + } +} + +.close { + cursor: pointer; + color: $tc-white; +} diff --git a/src/libs/ui/lib/components/notification/banner/NotificationBanner.stories.tsx b/src/libs/ui/lib/components/notification/banner/NotificationBanner.stories.tsx new file mode 100644 index 000000000..4560fe417 --- /dev/null +++ b/src/libs/ui/lib/components/notification/banner/NotificationBanner.stories.tsx @@ -0,0 +1,33 @@ +/* eslint-disable no-underscore-dangle */ +/* eslint-disable camelcase */ + +import { Meta, StoryObj } from '@storybook/react' + +import NotificationBanner from './NotificationBanner' + +const meta: Meta = { + argTypes: { + persistent: { + defaultValue: false, + description: 'Set to true to allow clicks inside the tooltip', + }, + content: { + description: 'Content displayed inside the tooltip', + }, + }, + component: NotificationBanner, + excludeStories: /.*Decorator$/, + tags: ['autodocs'], + title: 'Components/NotificationBanner', +} + +export default meta + +type Story = StoryObj; + +export const Primary: Story = { + args: { + // children: , + content: 'Help tooltip', + }, +} diff --git a/src/libs/ui/lib/components/notification/banner/NotificationBanner.tsx b/src/libs/ui/lib/components/notification/banner/NotificationBanner.tsx new file mode 100644 index 000000000..04dfd28b9 --- /dev/null +++ b/src/libs/ui/lib/components/notification/banner/NotificationBanner.tsx @@ -0,0 +1,36 @@ +import { FC, ReactNode, useCallback } from 'react' + +import styles from './NotificationBanner.module.scss' +import { InformationCircleIcon, XCircleIcon } from '@heroicons/react/outline' + +interface NotificationBannerProps { + persistent?: boolean + content: ReactNode + icon?: ReactNode + onClose?: (save?: boolean) => void +} + +const NotificationBanner: FC = props => { + + return ( +
+
+ {props.icon || ( +
+ +
+ )} + + {props.content} + + {!props.persistent && ( +
props.onClose?.(true)}> + +
+ )} +
+
+ ) +} + +export default NotificationBanner diff --git a/src/libs/ui/lib/components/notification/banner/index.ts b/src/libs/ui/lib/components/notification/banner/index.ts new file mode 100644 index 000000000..51f3cf392 --- /dev/null +++ b/src/libs/ui/lib/components/notification/banner/index.ts @@ -0,0 +1 @@ +export { default as NotificationBanner } from './NotificationBanner' diff --git a/src/libs/ui/lib/components/notification/index.ts b/src/libs/ui/lib/components/notification/index.ts new file mode 100644 index 000000000..ef0ca420e --- /dev/null +++ b/src/libs/ui/lib/components/notification/index.ts @@ -0,0 +1,2 @@ +export * from './banner' +export { default as Notification } from './Notification' From 6f9bb5681a651671b8c88bc9b18a2147cf314416 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Oct 2025 17:06:22 +0300 Subject: [PATCH 2/9] update pr reviewer --- .github/workflows/code_reviewer-updated.yml | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/code_reviewer-updated.yml diff --git a/.github/workflows/code_reviewer-updated.yml b/.github/workflows/code_reviewer-updated.yml new file mode 100644 index 000000000..cc270edc1 --- /dev/null +++ b/.github/workflows/code_reviewer-updated.yml @@ -0,0 +1,22 @@ +name: AI PR Reviewer Updated + +on: + pull_request: + types: + - opened + - synchronize +permissions: + pull-requests: write +jobs: + tc-ai-pr-review: + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - name: TC AI PR Reviewer + uses: topcoder-platform/tc-ai-pr-reviewer@prompt-update + 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 }} + exclude: '**/*.json, **/*.md, **/*.jpg, **/*.png, **/*.jpeg, **/*.bmp, **/*.webp' # Optional: exclude patterns separated by commas From 1fc2ecd0e1e3b7cea822ce99fc2affbaaf061bc2 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Oct 2025 17:33:42 +0300 Subject: [PATCH 3/9] lint fixes --- src/apps/platform/src/PlatformApp.tsx | 2 +- .../ChallengeDetailsPage.tsx | 12 +-- .../profile-context/profile.context.tsx | 2 +- .../notifications/Notifications.container.tsx | 4 +- .../notifications/Notifications.context.tsx | 77 +++++++++++-------- .../components/notification/Notification.tsx | 14 +++- .../banner/NotificationBanner.stories.tsx | 10 +-- .../banner/NotificationBanner.tsx | 8 +- 8 files changed, 78 insertions(+), 51 deletions(-) diff --git a/src/apps/platform/src/PlatformApp.tsx b/src/apps/platform/src/PlatformApp.tsx index e8d9480d5..e9c34cceb 100644 --- a/src/apps/platform/src/PlatformApp.tsx +++ b/src/apps/platform/src/PlatformApp.tsx @@ -1,7 +1,7 @@ import { FC } from 'react' import { toast, ToastContainer } from 'react-toastify' -import { useViewportUnitsFix, NotificationsContainer } from '~/libs/shared' +import { NotificationsContainer, useViewportUnitsFix } from '~/libs/shared' import { AppFooter } from './components/app-footer' import { AppHeader } from './components/app-header' diff --git a/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx b/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx index 066de86c4..e8cde8672 100644 --- a/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx +++ b/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx @@ -13,6 +13,7 @@ import { TableLoading } from '~/apps/admin/src/lib' import { handleError } from '~/apps/admin/src/lib/utils' import { EnvironmentConfig } from '~/config' import { BaseModal, Button, InputCheckbox, InputText } from '~/libs/ui' +import { NotificationContextType, useNotification } from '~/libs/shared' import { useFetchScreeningReview, @@ -60,7 +61,6 @@ import { } from '../../../config/routes.config' import styles from './ChallengeDetailsPage.module.scss' -import { useNotification } from '~/libs/shared' interface Props { className?: string @@ -227,7 +227,7 @@ const computePhaseCompletionFromScreenings = ( // eslint-disable-next-line complexity export const ChallengeDetailsPage: FC = (props: Props) => { - const { showBannerNotification, removeNotification } = useNotification(); + const { showBannerNotification, removeNotification }: NotificationContextType = useNotification() const [searchParams, setSearchParams] = useSearchParams() const location = useLocation() const navigate = useNavigate() @@ -1328,10 +1328,12 @@ export const ChallengeDetailsPage: FC = (props: Props) => { useEffect(() => { const notification = showBannerNotification({ 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.', + message: `AI Review Scores are advisory only to provide immediate, + educational, and actionable feedback to members. + AI Review Scores are not influence winner selection.`, }) - return () => notification && removeNotification(notification.id); - }, [showBannerNotification]); + return () => notification && removeNotification(notification.id) + }, [showBannerNotification]) return ( = createContext(defaultProfileContextData) -export const useProfileContext = () => useContext(profileContext); +export const useProfileContext = (): ProfileContextData => useContext(profileContext) export default profileContext diff --git a/src/libs/shared/lib/components/notifications/Notifications.container.tsx b/src/libs/shared/lib/components/notifications/Notifications.container.tsx index 1e809dcfd..b71134aba 100644 --- a/src/libs/shared/lib/components/notifications/Notifications.container.tsx +++ b/src/libs/shared/lib/components/notifications/Notifications.container.tsx @@ -2,10 +2,10 @@ import { FC } from 'react' import { Notification } from '~/libs/ui' -import { useNotification } from './Notifications.context'; +import { NotificationContextType, useNotification } from './Notifications.context' const NotificationsContainer: FC = () => { - const { notifications, removeNotification } = useNotification(); + const { notifications, removeNotification }: NotificationContextType = useNotification() return (
diff --git a/src/libs/shared/lib/components/notifications/Notifications.context.tsx b/src/libs/shared/lib/components/notifications/Notifications.context.tsx index 323ec6972..70eb89e9b 100644 --- a/src/libs/shared/lib/components/notifications/Notifications.context.tsx +++ b/src/libs/shared/lib/components/notifications/Notifications.context.tsx @@ -1,8 +1,10 @@ -import React, { createContext, useContext, useState, useCallback, ReactNode } from "react"; -import { useProfileContext } from "~/libs/core"; -import { dismiss, wasDismissed } from "./localstorage.utils"; +import React, { createContext, ReactNode, useCallback, useContext, useMemo, useState } from 'react' -export type NotificationType = "success" | "error" | "info" | "warning" | "banner"; +import { useProfileContext } from '~/libs/core' + +import { dismiss, wasDismissed } from './localstorage.utils' + +export type NotificationType = 'success' | 'error' | 'info' | 'warning' | 'banner'; export interface Notification { id: string; @@ -13,62 +15,77 @@ export interface Notification { type NotifyPayload = string | (Partial & { message: string }) -interface NotificationContextType { +export interface NotificationContextType { notifications: Notification[]; notify: (message: NotifyPayload, type?: NotificationType, duration?: number) => Notification | void; showBannerNotification: (message: NotifyPayload) => Notification | void; removeNotification: (id: string) => void; } -const NotificationContext = createContext(undefined); +const NotificationContext = createContext(undefined) export const useNotification = (): NotificationContextType => { - const context = useContext(NotificationContext); - if (!context) throw new Error("useNotification must be used within a NotificationProvider"); - return context; -}; + const context = useContext(NotificationContext) + if (!context) throw new Error('useNotification must be used within a NotificationProvider') + return context +} export const NotificationProvider: React.FC<{ children: ReactNode, -}> = ({ children }) => { +}> = props => { const profileCtx = useProfileContext() - const uuid = profileCtx.profile?.userId ?? 'annon'; - const [notifications, setNotifications] = useState([]); + const uuid = profileCtx.profile?.userId ?? 'annon' + const [notifications, setNotifications] = useState([]) const removeNotification = useCallback((id: string, persist?: boolean) => { - setNotifications(prev => prev.filter(n => n.id !== id)); + setNotifications(prev => prev.filter(n => n.id !== id)) if (persist) { - dismiss(id); + dismiss(id) } - }, []); + }, []) const notify = useCallback( - (message: NotifyPayload, type: NotificationType = "info", duration = 3000) => { - const id = `${uuid}[${typeof message === 'string' ? message : message.id}]`; - const newNotification: Notification = typeof message === 'string' ? { id, message, type, duration } : { type, duration, ...message, id }; + (message: NotifyPayload, type: NotificationType = 'info', duration = 3000) => { + const id = `${uuid}[${typeof message === 'string' ? message : message.id}]` + const newNotification: Notification + = typeof message === 'string' + ? { duration, id, message, type } + : { duration, type, ...message, id } if (wasDismissed(id)) { - return; + return undefined } - setNotifications(prev => [...prev, newNotification]); + setNotifications(prev => [...prev, newNotification]) if (duration > 0) { - setTimeout(() => removeNotification(id), duration); + setTimeout(() => removeNotification(id), duration) } - return newNotification; + return newNotification }, - [uuid] - ); + [uuid], + ) const showBannerNotification = useCallback(( message: NotifyPayload, - ) => notify(message, 'banner', 0), [notify]); + ) => notify(message, 'banner', 0), [notify]) + + const ctxValue = useMemo(() => ({ + notifications, + notify, + removeNotification, + showBannerNotification, + }), [ + notifications, + notify, + removeNotification, + showBannerNotification, + ]) return ( - - {children} + + {props.children} - ); -}; + ) +} diff --git a/src/libs/ui/lib/components/notification/Notification.tsx b/src/libs/ui/lib/components/notification/Notification.tsx index 712d3a83c..ffffa1439 100644 --- a/src/libs/ui/lib/components/notification/Notification.tsx +++ b/src/libs/ui/lib/components/notification/Notification.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react' +import { FC, useCallback } from 'react' import { NotificationBanner } from './banner' @@ -8,12 +8,20 @@ interface NotificationProps { } const Notification: FC = props => { + const handleClose = useCallback((save?: boolean) => { + props.onClose(props.notification.id, save) + }, [props.onClose]) if (props.notification.type === 'banner') { - return props.onClose(props.notification.id, save)} /> + return ( + + ) } - return null; + return <> } export default Notification diff --git a/src/libs/ui/lib/components/notification/banner/NotificationBanner.stories.tsx b/src/libs/ui/lib/components/notification/banner/NotificationBanner.stories.tsx index 4560fe417..8af3170e9 100644 --- a/src/libs/ui/lib/components/notification/banner/NotificationBanner.stories.tsx +++ b/src/libs/ui/lib/components/notification/banner/NotificationBanner.stories.tsx @@ -1,19 +1,16 @@ -/* eslint-disable no-underscore-dangle */ -/* eslint-disable camelcase */ - import { Meta, StoryObj } from '@storybook/react' import NotificationBanner from './NotificationBanner' const meta: Meta = { argTypes: { + content: { + description: 'Content displayed inside the tooltip', + }, persistent: { defaultValue: false, description: 'Set to true to allow clicks inside the tooltip', }, - content: { - description: 'Content displayed inside the tooltip', - }, }, component: NotificationBanner, excludeStories: /.*Decorator$/, @@ -27,7 +24,6 @@ type Story = StoryObj; export const Primary: Story = { args: { - // children: , content: 'Help tooltip', }, } diff --git a/src/libs/ui/lib/components/notification/banner/NotificationBanner.tsx b/src/libs/ui/lib/components/notification/banner/NotificationBanner.tsx index 04dfd28b9..7b9b058c1 100644 --- a/src/libs/ui/lib/components/notification/banner/NotificationBanner.tsx +++ b/src/libs/ui/lib/components/notification/banner/NotificationBanner.tsx @@ -1,8 +1,9 @@ import { FC, ReactNode, useCallback } from 'react' -import styles from './NotificationBanner.module.scss' import { InformationCircleIcon, XCircleIcon } from '@heroicons/react/outline' +import styles from './NotificationBanner.module.scss' + interface NotificationBannerProps { persistent?: boolean content: ReactNode @@ -11,6 +12,9 @@ interface NotificationBannerProps { } const NotificationBanner: FC = props => { + const handleClose = useCallback(() => { + props.onClose?.(true) + }, [props.onClose]) return (
@@ -24,7 +28,7 @@ const NotificationBanner: FC = props => { {props.content} {!props.persistent && ( -
props.onClose?.(true)}> +
)} From 7b7d7b77338d44eb2a2f65b9b9f599c2336d8e86 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Oct 2025 17:49:51 +0300 Subject: [PATCH 4/9] PM-2133 - update desktop styles --- .../notifications/Notifications.container.tsx | 3 ++- .../notifications/Notifications.context.tsx | 1 + .../NotificationsContainer.module.scss | 7 ++++++ .../components/notification/Notification.tsx | 10 +++++++-- .../banner/NotificationBanner.module.scss | 22 ++++++++++++++----- .../banner/NotificationBanner.tsx | 8 ++++--- 6 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 src/libs/shared/lib/components/notifications/NotificationsContainer.module.scss diff --git a/src/libs/shared/lib/components/notifications/Notifications.container.tsx b/src/libs/shared/lib/components/notifications/Notifications.container.tsx index b71134aba..5aa329be7 100644 --- a/src/libs/shared/lib/components/notifications/Notifications.container.tsx +++ b/src/libs/shared/lib/components/notifications/Notifications.container.tsx @@ -3,12 +3,13 @@ import { FC } from 'react' import { Notification } from '~/libs/ui' import { NotificationContextType, useNotification } from './Notifications.context' +import styles from './NotificationsContainer.module.scss' const NotificationsContainer: FC = () => { const { notifications, removeNotification }: NotificationContextType = useNotification() return ( -
+
{notifications.map(n => ( ))} diff --git a/src/libs/shared/lib/components/notifications/Notifications.context.tsx b/src/libs/shared/lib/components/notifications/Notifications.context.tsx index 70eb89e9b..23dbcf558 100644 --- a/src/libs/shared/lib/components/notifications/Notifications.context.tsx +++ b/src/libs/shared/lib/components/notifications/Notifications.context.tsx @@ -9,6 +9,7 @@ export type NotificationType = 'success' | 'error' | 'info' | 'warning' | 'banne export interface Notification { id: string; type: NotificationType; + icon?: ReactNode message: string; duration?: number; // in ms } diff --git a/src/libs/shared/lib/components/notifications/NotificationsContainer.module.scss b/src/libs/shared/lib/components/notifications/NotificationsContainer.module.scss new file mode 100644 index 000000000..9cbc394ef --- /dev/null +++ b/src/libs/shared/lib/components/notifications/NotificationsContainer.module.scss @@ -0,0 +1,7 @@ +@import "@libs/ui/styles/includes"; + +.wrap { + position: relative; + width: 100%; + z-index: 1000; +} diff --git a/src/libs/ui/lib/components/notification/Notification.tsx b/src/libs/ui/lib/components/notification/Notification.tsx index ffffa1439..ab616cb1f 100644 --- a/src/libs/ui/lib/components/notification/Notification.tsx +++ b/src/libs/ui/lib/components/notification/Notification.tsx @@ -1,9 +1,14 @@ -import { FC, useCallback } from 'react' +import { FC, ReactNode, useCallback } from 'react' import { NotificationBanner } from './banner' interface NotificationProps { - notification: { message: string; id: string; type: string } + notification: { + icon?: ReactNode; + id: string; + message: string; + type: string; +} onClose: (id: string, save?: boolean) => void } @@ -15,6 +20,7 @@ const Notification: FC = props => { if (props.notification.type === 'banner') { return ( diff --git a/src/libs/ui/lib/components/notification/banner/NotificationBanner.module.scss b/src/libs/ui/lib/components/notification/banner/NotificationBanner.module.scss index 3fa146f8f..e9aab0a7b 100644 --- a/src/libs/ui/lib/components/notification/banner/NotificationBanner.module.scss +++ b/src/libs/ui/lib/components/notification/banner/NotificationBanner.module.scss @@ -4,23 +4,35 @@ background: #60267D; color: $tc-white; + font-family: "Nunito Sans", sans-serif; + font-size: 14px; + line-height: 20px; + .inner { max-width: $xxl-min; - padding: $sp-3 0; + padding: $sp-2 0; @include pagePaddings; margin: 0 auto; width: 100%; display: flex; justify-content: space-between; align-items: center; - @include ltemd { - display: block; - position: relative; - } } } .close { cursor: pointer; color: $tc-white; + flex: 0 0; + margin-left: auto; + border-radius: 50%; + border: 2px solid white; + @include ltemd { + margin-left: $sp-3; + } +} + +.icon { + flex: 0 0; + margin-right: $sp-2; } diff --git a/src/libs/ui/lib/components/notification/banner/NotificationBanner.tsx b/src/libs/ui/lib/components/notification/banner/NotificationBanner.tsx index 7b9b058c1..66900057a 100644 --- a/src/libs/ui/lib/components/notification/banner/NotificationBanner.tsx +++ b/src/libs/ui/lib/components/notification/banner/NotificationBanner.tsx @@ -1,6 +1,8 @@ import { FC, ReactNode, useCallback } from 'react' -import { InformationCircleIcon, XCircleIcon } from '@heroicons/react/outline' +import { InformationCircleIcon } from '@heroicons/react/outline' + +import { IconOutline } from '../../svgs' import styles from './NotificationBanner.module.scss' @@ -20,7 +22,7 @@ const NotificationBanner: FC = props => {
{props.icon || ( -
+
)} @@ -29,7 +31,7 @@ const NotificationBanner: FC = props => { {!props.persistent && (
- +
)}
From a6d033a04b5a78d53a3174d25490e88a27f31390 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Oct 2025 18:38:59 +0300 Subject: [PATCH 5/9] PM-2133 - PR feedback --- .../lib/components/notifications/localstorage.utils.ts | 6 ++++-- src/libs/ui/lib/components/notification/Notification.tsx | 2 +- .../notification/banner/NotificationBanner.stories.tsx | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/libs/shared/lib/components/notifications/localstorage.utils.ts b/src/libs/shared/lib/components/notifications/localstorage.utils.ts index 46e776cb8..c33d2ff72 100644 --- a/src/libs/shared/lib/components/notifications/localstorage.utils.ts +++ b/src/libs/shared/lib/components/notifications/localstorage.utils.ts @@ -1,7 +1,9 @@ +const lsKeyPrefix = 'notificationDismissed' + export const wasDismissed = (id: string): boolean => ( - (localStorage.getItem(`dismissed[${id}]`)) !== null + (localStorage.getItem(`${lsKeyPrefix}[${id}]`)) !== null ) export const dismiss = (id: string): void => { - localStorage.setItem(`dismissed[${id}]`, JSON.stringify(true)) + localStorage.setItem(`${lsKeyPrefix}[${id}]`, JSON.stringify(true)) } diff --git a/src/libs/ui/lib/components/notification/Notification.tsx b/src/libs/ui/lib/components/notification/Notification.tsx index ab616cb1f..34fe01595 100644 --- a/src/libs/ui/lib/components/notification/Notification.tsx +++ b/src/libs/ui/lib/components/notification/Notification.tsx @@ -15,7 +15,7 @@ interface NotificationProps { const Notification: FC = props => { const handleClose = useCallback((save?: boolean) => { props.onClose(props.notification.id, save) - }, [props.onClose]) + }, [props.onClose, props.notification.id]) if (props.notification.type === 'banner') { return ( diff --git a/src/libs/ui/lib/components/notification/banner/NotificationBanner.stories.tsx b/src/libs/ui/lib/components/notification/banner/NotificationBanner.stories.tsx index 8af3170e9..127e09970 100644 --- a/src/libs/ui/lib/components/notification/banner/NotificationBanner.stories.tsx +++ b/src/libs/ui/lib/components/notification/banner/NotificationBanner.stories.tsx @@ -5,11 +5,11 @@ import NotificationBanner from './NotificationBanner' const meta: Meta = { argTypes: { content: { - description: 'Content displayed inside the tooltip', + description: 'Content displayed inside the notification banner', }, persistent: { defaultValue: false, - description: 'Set to true to allow clicks inside the tooltip', + description: 'Set to true to hide the close icon button', }, }, component: NotificationBanner, From 53baf43a6e1249e86ac93d12db5effac450c8c7b Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Oct 2025 18:54:16 +0300 Subject: [PATCH 6/9] update workflow --- ...{code_reviewer-updated.yml => code_reviewer-complete-diff.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{code_reviewer-updated.yml => code_reviewer-complete-diff.yml} (100%) diff --git a/.github/workflows/code_reviewer-updated.yml b/.github/workflows/code_reviewer-complete-diff.yml similarity index 100% rename from .github/workflows/code_reviewer-updated.yml rename to .github/workflows/code_reviewer-complete-diff.yml From 8f364833fd40f8b63f8169ab34eb87f28c12ebb8 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Oct 2025 22:30:03 +0300 Subject: [PATCH 7/9] test buddy v2 --- .github/workflows/code_reviewer-complete-diff.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_reviewer-complete-diff.yml b/.github/workflows/code_reviewer-complete-diff.yml index cc270edc1..89472466f 100644 --- a/.github/workflows/code_reviewer-complete-diff.yml +++ b/.github/workflows/code_reviewer-complete-diff.yml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v3 - name: TC AI PR Reviewer - uses: topcoder-platform/tc-ai-pr-reviewer@prompt-update + 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 }} From b9fe4ffcf12a5ebcaecc885ebe01d7e371e41e5f Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Oct 2025 23:01:03 +0300 Subject: [PATCH 8/9] use only pr buddy v2 --- .../workflows/code_reviewer-complete-diff.yml | 22 ------------------- .github/workflows/code_reviewer.yml | 2 +- 2 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 .github/workflows/code_reviewer-complete-diff.yml diff --git a/.github/workflows/code_reviewer-complete-diff.yml b/.github/workflows/code_reviewer-complete-diff.yml deleted file mode 100644 index 89472466f..000000000 --- a/.github/workflows/code_reviewer-complete-diff.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: AI PR Reviewer Updated - -on: - pull_request: - types: - - opened - - synchronize -permissions: - pull-requests: write -jobs: - tc-ai-pr-review: - runs-on: ubuntu-latest - steps: - - name: Checkout Repo - uses: actions/checkout@v3 - - - name: TC AI PR Reviewer - 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 }} - exclude: '**/*.json, **/*.md, **/*.jpg, **/*.png, **/*.jpeg, **/*.bmp, **/*.webp' # Optional: exclude patterns separated by commas diff --git a/.github/workflows/code_reviewer.yml b/.github/workflows/code_reviewer.yml index 02f198a18..40e24b681 100644 --- a/.github/workflows/code_reviewer.yml +++ b/.github/workflows/code_reviewer.yml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v3 - name: TC AI PR Reviewer - uses: topcoder-platform/tc-ai-pr-reviewer@master + 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 }} From ca1e43278967045178ea065bf32de92db4f2340e Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Fri, 17 Oct 2025 09:08:25 +0300 Subject: [PATCH 9/9] typo fix --- .github/workflows/code_reviewer.yml | 2 +- .../ChallengeDetailsPage/ChallengeDetailsPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code_reviewer.yml b/.github/workflows/code_reviewer.yml index 40e24b681..02f198a18 100644 --- a/.github/workflows/code_reviewer.yml +++ b/.github/workflows/code_reviewer.yml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v3 - name: TC AI PR Reviewer - uses: topcoder-platform/tc-ai-pr-reviewer@prompt-v2 + uses: topcoder-platform/tc-ai-pr-reviewer@master 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 }} diff --git a/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx b/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx index e8cde8672..29e8b058d 100644 --- a/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx +++ b/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx @@ -1330,7 +1330,7 @@ export const ChallengeDetailsPage: FC = (props: Props) => { 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.`, + AI Review Scores do not influence winner selection.`, }) return () => notification && removeNotification(notification.id) }, [showBannerNotification])