-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(hub): created notifications carousel component (#13419)
ref: MANAGER-15114 Signed-off-by: Jacques Larique <[email protected]>
- Loading branch information
1 parent
61a4e59
commit c2c7c82
Showing
9 changed files
with
370 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { aapi } from '@ovh-ux/manager-core-api'; | ||
import { ApiEnvelope } from '@/types/apiEnvelope.type'; | ||
import { Notification, NotificationsList } from '@/types/notifications.type'; | ||
|
||
const hubNotificationStatuses = ['warning', 'error']; | ||
|
||
export const getNotifications: () => Promise<Notification[]> = async () => { | ||
const { data } = await aapi.get<ApiEnvelope<NotificationsList>>( | ||
`/hub/notifications`, | ||
); | ||
return ( | ||
data.data?.notifications.data || [] | ||
).filter((notification: Notification) => | ||
hubNotificationStatuses.includes(notification.level), | ||
); | ||
}; |
42 changes: 42 additions & 0 deletions
42
packages/manager/apps/hub/src/data/hooks/notifications/useNotifications.spec.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { PropsWithChildren } from 'react'; | ||
import { renderHook, waitFor } from '@testing-library/react'; | ||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; | ||
import { describe, it, vi } from 'vitest'; | ||
import { aapi as Api } from '@ovh-ux/manager-core-api'; | ||
import { useFetchHubNotifications } from '@/data/hooks/notifications/useNotifications'; | ||
import { ApiEnvelope } from '@/types/apiEnvelope.type'; | ||
import { NotificationsList } from '@/types/notifications.type'; | ||
|
||
const queryClient = new QueryClient(); | ||
|
||
const wrapper = ({ children }: PropsWithChildren) => ( | ||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider> | ||
); | ||
|
||
describe('useFetchHubNotifications', () => { | ||
it('should return notifications after extracting them from api envelope', async () => { | ||
const notifications: ApiEnvelope<NotificationsList> = { | ||
data: { | ||
notifications: { | ||
data: [], | ||
status: 'OK', | ||
}, | ||
}, | ||
status: 'OK', | ||
}; | ||
const getNotifications = vi | ||
.spyOn(Api, 'get') | ||
.mockReturnValue(Promise.resolve(notifications)); | ||
|
||
const { result } = renderHook(() => useFetchHubNotifications(), { | ||
wrapper, | ||
}); | ||
|
||
await waitFor(() => { | ||
expect(getNotifications).toHaveBeenCalled(); | ||
expect(result.current.data).toEqual( | ||
notifications.data.notifications.data, | ||
); | ||
}); | ||
}); | ||
}); |
12 changes: 12 additions & 0 deletions
12
packages/manager/apps/hub/src/data/hooks/notifications/useNotifications.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { useQuery } from '@tanstack/react-query'; | ||
import { AxiosError } from 'axios'; | ||
import { Notification } from '@/types/notifications.type'; | ||
import { getNotifications } from '@/data/api/notifications'; | ||
|
||
export const useFetchHubNotifications = () => | ||
useQuery<Notification[], AxiosError>({ | ||
queryKey: ['getHubNotifications'], | ||
queryFn: getNotifications, | ||
retry: 0, | ||
refetchOnWindowFocus: false, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import React from 'react'; | ||
import { describe, expect, it, vi } from 'vitest'; | ||
import { render } from '@testing-library/react'; | ||
import '@testing-library/jest-dom'; | ||
import * as reactShellClientModule from '@ovh-ux/manager-react-shell-client'; | ||
import { | ||
ShellContext, | ||
ShellContextType, | ||
} from '@ovh-ux/manager-react-shell-client'; | ||
import Layout from '@/pages/layout'; | ||
|
||
const shellContext = { | ||
environment: { | ||
getUser: vi.fn(), | ||
}, | ||
shell: { | ||
ux: { | ||
hidePreloader: vi.fn(), | ||
}, | ||
}, | ||
}; | ||
|
||
const renderComponent = () => | ||
render( | ||
<ShellContext.Provider | ||
value={(shellContext as unknown) as ShellContextType} | ||
> | ||
<Layout /> | ||
</ShellContext.Provider>, | ||
); | ||
|
||
const mockPath = '/foo'; | ||
|
||
vi.mock('react-router-dom', () => ({ | ||
useLocation: () => ({ | ||
pathname: mockPath, | ||
}), | ||
})); | ||
|
||
vi.mock('@ovh-ux/manager-react-shell-client', async (importOriginal) => { | ||
const original: typeof reactShellClientModule = await importOriginal(); | ||
return { | ||
...original, | ||
useOvhTracking: vi.fn(() => ({ | ||
trackPage: vi.fn(), | ||
trackClick: vi.fn(), | ||
trackCurrentPage: vi.fn(), | ||
usePageTracking: vi.fn(), | ||
})), | ||
useRouteSynchro: vi.fn(() => {}), | ||
}; | ||
}); | ||
|
||
describe('Form.page', () => { | ||
it('should render select LegalForms correctly when the sub is FR and legalForms is other', async () => { | ||
const { getByText } = renderComponent(); | ||
|
||
expect(getByText('Layout')).not.toBeNull(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import React, { useEffect, useContext } from 'react'; | ||
import { defineCurrentPage } from '@ovh-ux/request-tagger'; | ||
import { useLocation } from 'react-router-dom'; | ||
import { | ||
useOvhTracking, | ||
useRouteSynchro, | ||
ShellContext, | ||
} from '@ovh-ux/manager-react-shell-client'; | ||
|
||
export default function Layout() { | ||
const location = useLocation(); | ||
const { shell } = useContext(ShellContext); | ||
const { trackCurrentPage } = useOvhTracking(); | ||
useRouteSynchro(); | ||
|
||
useEffect(() => { | ||
defineCurrentPage(`app.dashboard`); | ||
}, []); | ||
|
||
useEffect(() => { | ||
trackCurrentPage(); | ||
}, [location]); | ||
|
||
useEffect(() => { | ||
shell.ux.hidePreloader(); | ||
}, []); | ||
|
||
return <div>Layout</div>; | ||
} |
138 changes: 138 additions & 0 deletions
138
packages/manager/apps/hub/src/pages/layout/NotificationsCarousel.component.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import { useState } from 'react'; | ||
import { | ||
OsdsIcon, | ||
OsdsMessage, | ||
OsdsText, | ||
} from '@ovhcloud/ods-components/react'; | ||
import { | ||
ODS_ICON_NAME, | ||
ODS_ICON_SIZE, | ||
ODS_MESSAGE_TYPE, | ||
ODS_TEXT_COLOR_INTENT, | ||
ODS_TEXT_LEVEL, | ||
ODS_TEXT_SIZE, | ||
} from '@ovhcloud/ods-components'; | ||
import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; | ||
import { useOvhTracking } from '@ovh-ux/manager-react-shell-client'; | ||
import { useFetchHubNotifications } from '@/data/hooks/notifications/useNotifications'; | ||
import { Notification, NotificationType } from '@/types/notifications.type'; | ||
|
||
const getMessageColor = (type: NotificationType) => { | ||
switch (type) { | ||
case NotificationType.Success: | ||
return ODS_TEXT_COLOR_INTENT.success; | ||
case NotificationType.Error: | ||
return ODS_TEXT_COLOR_INTENT.error; | ||
case NotificationType.Warning: | ||
return ODS_TEXT_COLOR_INTENT.warning; | ||
case NotificationType.Info: | ||
return ODS_TEXT_COLOR_INTENT.info; | ||
default: | ||
return ODS_TEXT_COLOR_INTENT.info; | ||
} | ||
}; | ||
|
||
const getMessageType = (type: NotificationType) => { | ||
switch (type) { | ||
case NotificationType.Success: | ||
return ODS_MESSAGE_TYPE.success; | ||
case NotificationType.Error: | ||
return ODS_MESSAGE_TYPE.error; | ||
case NotificationType.Warning: | ||
return ODS_MESSAGE_TYPE.warning; | ||
case NotificationType.Info: | ||
return ODS_MESSAGE_TYPE.info; | ||
default: | ||
return ODS_MESSAGE_TYPE.info; | ||
} | ||
}; | ||
|
||
const getTextColor = (type: NotificationType) => { | ||
switch (type) { | ||
case NotificationType.Success: | ||
return ODS_THEME_COLOR_INTENT.success; | ||
case NotificationType.Error: | ||
return ODS_THEME_COLOR_INTENT.error; | ||
case NotificationType.Warning: | ||
return ODS_THEME_COLOR_INTENT.warning; | ||
case NotificationType.Info: | ||
return ODS_THEME_COLOR_INTENT.info; | ||
default: | ||
return ODS_THEME_COLOR_INTENT.info; | ||
} | ||
}; | ||
|
||
export default function NotificationsCarousel() { | ||
const { trackClick } = useOvhTracking(); | ||
const { data: notifications } = useFetchHubNotifications(); | ||
const [currentIndex, setCurrentIndex] = useState(0); | ||
|
||
const showNextNotification = () => { | ||
setCurrentIndex( | ||
(previousIndex) => (previousIndex + 1) % notifications.length, | ||
); | ||
trackClick({ | ||
actionType: 'action', | ||
actions: ['hub', 'dashboard', 'alert', 'action'], | ||
}); | ||
}; | ||
|
||
return ( | ||
<> | ||
{notifications?.length > 0 && ( | ||
<OsdsMessage | ||
className={`rounded ${notifications?.length > 1 ? '!pb-8' : ''}`} | ||
role="alert" | ||
color={getMessageColor(notifications[currentIndex].level)} | ||
type={getMessageType(notifications[currentIndex].level)} | ||
data-testid="notifications_carousel" | ||
> | ||
<OsdsText | ||
data-testid="notification_content" | ||
color={getMessageColor(notifications[currentIndex].level)} | ||
level={ODS_TEXT_LEVEL.body} | ||
size={ODS_TEXT_SIZE._500} | ||
> | ||
<span | ||
dangerouslySetInnerHTML={{ | ||
__html: notifications[currentIndex].description, | ||
}} | ||
></span> | ||
</OsdsText> | ||
{notifications?.length > 1 && ( | ||
<> | ||
<OsdsIcon | ||
data-testid="next-notification-button" | ||
className="absolute top-1/2 right-4 -mt-6 cursor-pointer" | ||
name={ODS_ICON_NAME.ARROW_RIGHT} | ||
size={ODS_ICON_SIZE.sm} | ||
color={ODS_THEME_COLOR_INTENT.primary} | ||
onClick={showNextNotification} | ||
/> | ||
<div | ||
className="absolute block w-full text-center right-0 left-0 bottom-1" | ||
data-testid="notification-navigation" | ||
> | ||
{notifications.map( | ||
(notification: Notification, index: number) => ( | ||
<OsdsIcon | ||
key={`notification_selector_${notification.id}`} | ||
className={`inline-block cursor-pointer ${ | ||
index > 0 ? 'ml-2' : '' | ||
}`} | ||
name={ODS_ICON_NAME.SHAPE_DOT} | ||
size={ODS_ICON_SIZE.xxs} | ||
color={getTextColor(notification.level)} | ||
contrasted={currentIndex === index || undefined} | ||
onClick={() => setCurrentIndex((previousIndex) => index)} | ||
/> | ||
), | ||
)} | ||
</div> | ||
</> | ||
)} | ||
</OsdsMessage> | ||
)} | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.