Skip to content

Commit

Permalink
feat(service-portal): Notifications on minarsidur (#13566)
Browse files Browse the repository at this point in the history
* WIP notifications

* update header buttons

* Update notifications load more

* Notifications to header menu

* Add badge and linkbutton

* Mark As Read

* enable notification service and workers

* feature deploy trigger

* Update charts and api infra

* Update resolver and ui w seen and read

* Update org logo loader

* Update notifitication logo display from org.

* Minor cleanup

* Try logo url

* Audit

* Add notification UI and resolver feature flag

* Use document scope

* Add circle option to action card. Add clickable whole line bubble notification

* fix logo width

* cleanup after pr comments

* Single resolver queries audit contain id

* Update click action url

* Update names after code review suggestion

* Update after code review suggestions. Add relative island.is link support

---------

Co-authored-by: Rafn Árnason <[email protected]>
Co-authored-by: brynjarorng <[email protected]>
  • Loading branch information
3 people authored Apr 22, 2024
1 parent d442285 commit 3612a21
Show file tree
Hide file tree
Showing 42 changed files with 1,124 additions and 228 deletions.
9 changes: 4 additions & 5 deletions apps/api/infra/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const serviceSetup = (services: {
sessionsApi: ServiceBuilder<'services-sessions'>
authAdminApi: ServiceBuilder<'services-auth-admin-api'>
universityGatewayApi: ServiceBuilder<'services-university-gateway'>
userNotificationService: ServiceBuilder<'services-user-notification'>
}): ServiceBuilder<'api'> => {
return service('api')
.namespace('islandis')
Expand All @@ -68,6 +69,9 @@ export const serviceSetup = (services: {
APPLICATION_SYSTEM_API_URL: ref(
(h) => `http://${h.svc(services.appSystemApi)}`,
),
USER_NOTIFICATION_API_URL: ref(
(h) => `http://${h.svc(services.userNotificationService)}`,
),
ICELANDIC_NAMES_REGISTRY_BACKEND_URL: ref(
(h) => `http://${h.svc(services.icelandicNameRegistryBackend)}`,
),
Expand Down Expand Up @@ -162,11 +166,6 @@ export const serviceSetup = (services: {
staging: 'https://identity-server.staging01.devland.is',
prod: 'https://innskra.island.is',
},
USER_NOTIFICATION_CLIENT_URL: {
dev: 'http://user-notification-xrd.internal.dev01.devland.is',
staging: 'http://user-notification-xrd.internal.staging01.devland.is',
prod: 'https://user-notification-xrd.internal.island.is',
},
MUNICIPALITIES_FINANCIAL_AID_BACKEND_URL: {
dev: 'http://web-financial-aid-backend',
staging: 'http://web-financial-aid-backend',
Expand Down
26 changes: 8 additions & 18 deletions apps/service-portal/src/components/Header/Header.css.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { style, styleVariants } from '@vanilla-extract/css'
import { globalStyle, style, styleVariants } from '@vanilla-extract/css'
import {
SERVICE_PORTAL_HEADER_HEIGHT_LG,
SERVICE_PORTAL_HEADER_HEIGHT_SM,
Expand Down Expand Up @@ -40,22 +40,12 @@ export const closeButton = style({
},
})

export const badge = styleVariants({
active: {
position: 'absolute',
top: 10,
right: 13,
height: theme.spacing[1],
width: theme.spacing[1],
borderRadius: '50%',
backgroundColor: theme.color.red400,
...themeUtils.responsiveStyle({
md: {
top: 14,
},
}),
},
inactive: {
display: 'none',
export const overview = style({})

globalStyle(`${overview} svg`, {
'@media': {
[`screen and (max-width: ${theme.breakpoints.sm - 1}px)`]: {
marginLeft: '0 !important',
},
},
})
96 changes: 60 additions & 36 deletions apps/service-portal/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import {
Box,
Hidden,
Expand All @@ -15,35 +15,44 @@ import { useLocale } from '@island.is/localization'
import { UserLanguageSwitcher, UserMenu } from '@island.is/shared/components'
import { m } from '@island.is/service-portal/core'
import { Link } from 'react-router-dom'
import { useListDocuments } from '@island.is/service-portal/graphql'
import cn from 'classnames'
import { theme } from '@island.is/island-ui/theme'
import { helperStyles, theme } from '@island.is/island-ui/theme'
import { useWindowSize } from 'react-use'
import { PortalPageLoader } from '@island.is/portals/core'
import { useAuth } from '@island.is/auth/react'
import Sidemenu from '../Sidemenu/Sidemenu'
import { DocumentsPaths } from '@island.is/service-portal/documents'
import NotificationButton from '../Notifications/NotificationButton'
import { useFeatureFlagClient } from '@island.is/react/feature-flags'
export type MenuTypes = 'side' | 'user' | 'notifications' | undefined

interface Props {
position: number
sideMenuOpen: boolean
setSideMenuOpen: (set: boolean) => void
}
export const Header = ({ position, sideMenuOpen, setSideMenuOpen }: Props) => {
export const Header = ({ position }: Props) => {
const { formatMessage } = useLocale()
const [userMenuOpen, setUserMenuOpen] = useState(false)
const { unreadCounter } = useListDocuments()
const [menuOpen, setMenuOpen] = useState<MenuTypes>()
const { width } = useWindowSize()
const ref = useRef<HTMLButtonElement>(null)
const isMobile = width < theme.breakpoints.md
const { userInfo: user } = useAuth()
const badgeActive: keyof typeof styles.badge =
unreadCounter > 0 ? 'active' : 'inactive'

const closeUserMenu = (state: boolean) => {
setSideMenuOpen(false)
setUserMenuOpen(state)
}
// Notification feature flag. Remove after feature is live.
const [enableNotificationFlag, setEnableNotificationFlag] =
useState<boolean>(false)
const featureFlagClient = useFeatureFlagClient()
useEffect(() => {
const isFlagEnabled = async () => {
const ffEnabled = await featureFlagClient.getValue(
`isServicePortalNotificationsPageEnabled`,
false,
)
if (ffEnabled) {
setEnableNotificationFlag(ffEnabled as boolean)
}
}
isFlagEnabled()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

return (
<div className={styles.placeholder}>
Expand Down Expand Up @@ -84,6 +93,7 @@ export const Header = ({ position, sideMenuOpen, setSideMenuOpen }: Props) => {
flexWrap="nowrap"
marginLeft={[1, 1, 2]}
>
{user && <UserLanguageSwitcher user={user} />}
<Hidden below="md">
<Box marginRight={[1, 1, 2]} position="relative">
<Link to={DocumentsPaths.ElectronicDocumentsRoot}>
Expand All @@ -95,50 +105,64 @@ export const Header = ({ position, sideMenuOpen, setSideMenuOpen }: Props) => {
iconType="outline"
type="span"
unfocusable
>
{!isMobile && formatMessage(m.documents)}
</Button>
/>

<span className={helperStyles.srOnly}>
{formatMessage(m.documents)}
</span>
</Link>
<Box
borderRadius="circle"
className={cn(styles.badge[badgeActive])}
/>
</Box>
</Hidden>

{user && <UserLanguageSwitcher user={user} />}
{/* Display X icon instead of dots if open in mobile*/}
{enableNotificationFlag && (
<NotificationButton
setMenuState={(val: MenuTypes) => setMenuOpen(val)}
showMenu={menuOpen === 'notifications'}
/>
)}

<Box marginRight={[1, 1, 2]}>
<Box className={styles.overview} marginRight={[1, 1, 2]}>
<Button
variant="utility"
colorScheme="white"
icon={sideMenuOpen && isMobile ? 'close' : 'dots'}
icon={
menuOpen === 'side' && isMobile ? 'close' : 'dots'
}
onClick={() => {
sideMenuOpen && isMobile
? setSideMenuOpen(false)
: setSideMenuOpen(true)
setUserMenuOpen(false)
menuOpen === 'side' && isMobile
? setMenuOpen(undefined)
: setMenuOpen('side')
}}
ref={ref}
>
{formatMessage(m.overview)}
<Hidden below="sm">
{formatMessage(m.overview)}
</Hidden>
</Button>
</Box>

<Sidemenu
setSideMenuOpen={(set: boolean) => setSideMenuOpen(set)}
sideMenuOpen={sideMenuOpen}
setSideMenuOpen={(set: boolean) =>
setMenuOpen(set ? 'side' : undefined)
}
sideMenuOpen={menuOpen === 'side'}
rightPosition={
ref.current?.getBoundingClientRect().right
}
/>

{/* Display X button instead if open in mobile*/}
<UserMenu
setUserMenuOpen={closeUserMenu}
setUserMenuOpen={(set: boolean) =>
setMenuOpen(
set
? 'user'
: menuOpen === 'user'
? undefined
: menuOpen,
)
}
showLanguageSwitcher={false}
userMenuOpen={userMenuOpen}
userMenuOpen={menuOpen === 'user'}
/>
</Box>
</Hidden>
Expand Down
7 changes: 1 addition & 6 deletions apps/service-portal/src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export const Layout: FC<React.PropsWithChildren<unknown>> = ({ children }) => {
useNamespaces(['service.portal', 'global', 'portals'])
const activeModule = useActiveModule()
const { pathname } = useLocation()
const [sideMenuOpen, setSideMenuOpen] = useState(false)

const navigation = useDynamicRoutesWithNavigation(MAIN_NAVIGATION)
const activeParent = navigation?.children?.find((item) => {
Expand All @@ -39,11 +38,7 @@ export const Layout: FC<React.PropsWithChildren<unknown>> = ({ children }) => {
{globalBanners.length > 0 && (
<GlobalAlertBannerSection ref={ref} banners={globalBanners} />
)}
<Header
setSideMenuOpen={(set: boolean) => setSideMenuOpen(set)}
sideMenuOpen={sideMenuOpen}
position={height && globalBanners.length > 0 ? height : 0}
/>
<Header position={height && globalBanners.length > 0 ? height : 0} />

{!isFullwidth && activeParent && (
<NarrowLayout
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { useEffect, useRef, useState } from 'react'
import cn from 'classnames'
import { Box, Button } from '@island.is/island-ui/core'
import { useLocale } from '@island.is/localization'
import { theme } from '@island.is/island-ui/theme'
import { useWindowSize } from 'react-use'
import { m } from '@island.is/service-portal/core'
import NotificationMenu from './NotificationMenu'
import { MenuTypes } from '../Header/Header'
import * as styles from './Notifications.css'
import {
useGetUserNotificationsOverviewQuery,
useMarkAllNotificationsAsSeenMutation,
} from '@island.is/service-portal/information'

interface Props {
setMenuState: (val: MenuTypes) => void
showMenu?: boolean
}

const NotificationButton = ({ setMenuState, showMenu = false }: Props) => {
const { formatMessage } = useLocale()
const [hasMarkedLocally, setHasMarkedLocally] = useState(false)
const [markAllAsSeen] = useMarkAllNotificationsAsSeenMutation()
const { width } = useWindowSize()
const isMobile = width < theme.breakpoints.md
const ref = useRef<HTMLButtonElement>(null)

const { data } = useGetUserNotificationsOverviewQuery({
variables: {
input: {
limit: 5,
},
},
})

const showBadge =
!!data?.userNotificationsOverview?.unseenCount && !hasMarkedLocally

useEffect(() => {
if (showMenu && showBadge) {
markAllAsSeen()
setHasMarkedLocally(true)
}
}, [showMenu, showBadge])

return (
<Box position="relative" marginRight={[1, 1, 2]}>
<Button
variant="utility"
colorScheme="white"
icon={showMenu && isMobile ? 'close' : 'notifications'}
iconType="outline"
onClick={() => {
showMenu && isMobile
? setMenuState(undefined)
: setMenuState('notifications')
}}
ref={ref}
aria-label={formatMessage(m.notifications)}
/>
{data?.userNotificationsOverview?.data.length ? (
<Box
borderRadius="circle"
className={cn({ [styles.badge]: showBadge })}
/>
) : undefined}
<NotificationMenu
closeNotificationMenu={() => setMenuState(undefined)}
sideMenuOpen={showMenu}
rightPosition={ref.current?.getBoundingClientRect().right}
data={data}
/>
</Box>
)
}

export default NotificationButton
Loading

0 comments on commit 3612a21

Please sign in to comment.