2023-05-24 22:25:20 +12:00
import {
Drawer ,
ListItemButton ,
ListItemIcon ,
ListItemText ,
Toolbar ,
Divider ,
List ,
Alert ,
AlertTitle ,
Badge ,
CircularProgress ,
Link ,
ListSubheader ,
Portal ,
Tooltip ,
Typography ,
Box ,
2023-06-15 00:31:31 +12:00
IconButton ,
Button ,
2023-05-24 22:25:20 +12:00
} from "@mui/material" ;
2022-02-26 06:46:22 +13:00
import * as React from "react" ;
2023-01-10 14:37:13 +13:00
import { useContext , useState } from "react" ;
2022-02-26 06:46:22 +13:00
import ChatBubbleOutlineIcon from "@mui/icons-material/ChatBubbleOutline" ;
2022-12-16 16:07:04 +13:00
import Person from "@mui/icons-material/Person" ;
2022-02-26 06:46:22 +13:00
import SettingsIcon from "@mui/icons-material/Settings" ;
import AddIcon from "@mui/icons-material/Add" ;
2023-01-05 16:47:12 +13:00
import { useLocation , useNavigate } from "react-router-dom" ;
2023-02-11 15:19:44 +13:00
import { ChatBubble , MoreVert , NotificationsOffOutlined , Send } from "@mui/icons-material" ;
2022-03-11 16:58:24 +13:00
import ArticleIcon from "@mui/icons-material/Article" ;
2022-06-13 08:38:33 +12:00
import { Trans , useTranslation } from "react-i18next" ;
2023-01-05 16:47:12 +13:00
import CelebrationIcon from "@mui/icons-material/Celebration" ;
2022-02-26 06:46:22 +13:00
import SubscribeDialog from "./SubscribeDialog" ;
2022-06-30 07:57:56 +12:00
import { openUrl , topicDisplayName , topicUrl } from "../app/utils" ;
2022-03-10 17:28:55 +13:00
import routes from "./routes" ;
2022-03-05 05:08:32 +13:00
import { ConnectionState } from "../app/Connection" ;
2022-03-07 16:37:13 +13:00
import subscriptionManager from "../app/SubscriptionManager" ;
2022-03-09 05:33:17 +13:00
import notifier from "../app/Notifier" ;
2022-03-10 17:28:55 +13:00
import config from "../app/config" ;
2022-12-16 16:07:04 +13:00
import session from "../app/Session" ;
2023-01-31 07:10:45 +13:00
import accountApi , { Permission , Role } from "../app/AccountApi" ;
2023-01-10 09:40:46 +13:00
import UpgradeDialog from "./UpgradeDialog" ;
2023-01-10 14:37:13 +13:00
import { AccountContext } from "./App" ;
2023-01-24 14:04:04 +13:00
import { PermissionDenyAll , PermissionRead , PermissionReadWrite , PermissionWrite } from "./ReserveIcons" ;
2023-02-11 15:19:44 +13:00
import { SubscriptionPopup } from "./SubscriptionPopup" ;
2023-06-29 06:26:54 +12:00
import { useNotificationPermissionListener } from "./hooks" ;
2022-02-26 06:46:22 +13:00
2022-03-09 10:56:41 +13:00
const navWidth = 280 ;
2022-02-26 06:46:22 +13:00
const Navigation = ( props ) => {
2022-02-27 04:14:43 +13:00
const navigationList = < NavList { ...props } / > ;
2022-02-26 06:46:22 +13:00
return (
2022-05-03 11:30:29 +12:00
< Box component = "nav" role = "navigation" sx = { { width : { sm : Navigation . width } , flexShrink : { sm : 0 } } } >
2022-02-26 06:46:22 +13:00
{ /* Mobile drawer; only shown if menu icon clicked (mobile open) and display is small */ }
< Drawer
variant = "temporary"
2022-05-04 07:09:20 +12:00
role = "menubar"
2022-02-26 06:46:22 +13:00
open = { props . mobileDrawerOpen }
onClose = { props . onMobileDrawerToggle }
ModalProps = { { keepMounted : true } } // Better open performance on mobile.
sx = { {
display : { xs : "block" , sm : "none" } ,
"& .MuiDrawer-paper" : { boxSizing : "border-box" , width : navWidth } ,
} }
>
{ navigationList }
< / Drawer >
{ /* Big screen drawer; persistent, shown if screen is big */ }
< Drawer
open
variant = "permanent"
2022-05-04 07:09:20 +12:00
role = "menubar"
2022-02-26 06:46:22 +13:00
sx = { {
display : { xs : "none" , sm : "block" } ,
"& .MuiDrawer-paper" : { boxSizing : "border-box" , width : navWidth } ,
} }
>
{ navigationList }
< / Drawer >
2022-03-09 05:33:17 +13:00
< / Box >
2022-02-26 06:46:22 +13:00
) ;
} ;
Navigation . width = navWidth ;
const NavList = ( props ) => {
2022-04-08 11:11:51 +12:00
const { t } = useTranslation ( ) ;
2022-03-05 10:10:04 +13:00
const navigate = useNavigate ( ) ;
const location = useLocation ( ) ;
2023-01-10 14:37:13 +13:00
const { account } = useContext ( AccountContext ) ;
2022-02-26 17:25:04 +13:00
const [ subscribeDialogKey , setSubscribeDialogKey ] = useState ( 0 ) ;
2022-02-26 06:46:22 +13:00
const [ subscribeDialogOpen , setSubscribeDialogOpen ] = useState ( false ) ;
2022-03-06 16:33:34 +13:00
2022-02-26 17:25:04 +13:00
const handleSubscribeReset = ( ) => {
2022-02-26 06:46:22 +13:00
setSubscribeDialogOpen ( false ) ;
2022-02-26 17:25:04 +13:00
setSubscribeDialogKey ( ( prev ) => prev + 1 ) ;
} ;
2022-03-06 16:33:34 +13:00
2022-03-02 15:23:12 +13:00
const handleSubscribeSubmit = ( subscription ) => {
2022-03-07 15:39:20 +13:00
console . log ( ` [Navigation] New subscription: ${ subscription . id } ` , subscription ) ;
2022-02-26 17:25:04 +13:00
handleSubscribeReset ( ) ;
2022-03-10 17:28:55 +13:00
navigate ( routes . forSubscription ( subscription ) ) ;
2022-02-26 06:46:22 +13:00
} ;
2022-03-06 16:33:34 +13:00
2023-01-03 16:21:11 +13:00
const handleAccountClick = ( ) => {
accountApi . sync ( ) ; // Dangle!
navigate ( routes . account ) ;
} ;
2023-01-31 07:10:45 +13:00
const isAdmin = account ? . role === Role . ADMIN ;
2023-01-17 10:35:37 +13:00
const isPaid = account ? . billing ? . subscription ;
const showUpgradeBanner = config . enable _payments && ! isAdmin && ! isPaid ;
2022-03-03 10:16:30 +13:00
const showSubscriptionsList = props . subscriptions ? . length > 0 ;
2023-06-29 06:26:54 +12:00
const showNotificationPermissionRequired = useNotificationPermissionListener ( ( ) => notifier . notRequested ( ) ) ;
const showNotificationPermissionDenied = useNotificationPermissionListener ( ( ) => notifier . denied ( ) ) ;
2023-05-25 07:36:01 +12:00
const showNotificationIOSInstallRequired = notifier . iosSupportedButInstallRequired ( ) ;
const showNotificationBrowserNotSupportedBox = ! showNotificationIOSInstallRequired && ! notifier . browserSupported ( ) ;
2022-06-13 08:38:33 +12:00
const showNotificationContextNotSupportedBox = notifier . browserSupported ( ) && ! notifier . contextSupported ( ) ; // Only show if notifications are generally supported in the browser
2023-05-25 07:36:01 +12:00
2023-06-02 23:22:54 +12:00
const alertVisible =
showNotificationPermissionRequired ||
2023-05-25 07:36:01 +12:00
showNotificationPermissionDenied ||
showNotificationIOSInstallRequired ||
showNotificationBrowserNotSupportedBox ||
2023-06-02 23:22:54 +12:00
showNotificationContextNotSupportedBox ;
2022-03-06 16:33:34 +13:00
2022-02-26 06:46:22 +13:00
return (
< >
2022-03-05 10:10:04 +13:00
< Toolbar sx = { { display : { xs : "none" , sm : "block" } } } / >
2023-06-02 23:22:54 +12:00
< List component = "nav" sx = { { paddingTop : alertVisible ? "0" : "" } } >
2023-06-29 06:26:54 +12:00
{ showNotificationPermissionRequired && < NotificationPermissionRequired / > }
2023-05-25 07:36:01 +12:00
{ showNotificationPermissionDenied && < NotificationPermissionDeniedAlert / > }
2022-06-13 08:38:33 +12:00
{ showNotificationBrowserNotSupportedBox && < NotificationBrowserNotSupportedAlert / > }
{ showNotificationContextNotSupportedBox && < NotificationContextNotSupportedAlert / > }
2023-05-25 07:36:01 +12:00
{ showNotificationIOSInstallRequired && < NotificationIOSInstallRequiredAlert / > }
2023-06-02 23:22:54 +12:00
{ alertVisible && < Divider / > }
2022-02-27 04:14:43 +13:00
{ ! showSubscriptionsList && (
2023-01-05 16:47:12 +13:00
< ListItemButton onClick = { ( ) => navigate ( routes . app ) } selected = { location . pathname === config . app _root } >
2022-12-16 16:07:04 +13:00
< ListItemIcon >
2022-03-10 17:28:55 +13:00
< ChatBubble / >
2022-03-05 10:10:04 +13:00
< / ListItemIcon >
2022-04-08 13:46:33 +12:00
< ListItemText primary = { t ( "nav_button_all_notifications" ) } / >
2022-03-11 16:58:24 +13:00
< / ListItemButton >
2022-04-04 14:58:44 +12:00
) }
{ showSubscriptionsList && (
< >
2022-04-08 13:46:33 +12:00
< ListSubheader > { t ( "nav_topics_title" ) } < / ListSubheader >
2022-02-26 06:46:22 +13:00
< ListItemButton onClick = { ( ) => navigate ( routes . app ) } selected = { location . pathname === config . app _root } >
2022-03-05 10:10:04 +13:00
< ListItemIcon >
< ChatBubble / >
< / ListItemIcon >
2022-04-08 11:11:51 +12:00
< ListItemText primary = { t ( "nav_button_all_notifications" ) } / >
2022-02-26 06:46:22 +13:00
< / ListItemButton >
2022-02-26 17:25:04 +13:00
< SubscriptionList subscriptions = { props . subscriptions } selectedSubscription = { props . selectedSubscription } / >
2022-02-27 08:22:21 +13:00
< Divider sx = { { my : 1 } } / >
2022-02-26 06:46:22 +13:00
< / >
2023-05-24 07:13:01 +12:00
) }
2022-12-16 16:07:04 +13:00
{ session . exists ( ) && (
2023-01-03 16:21:11 +13:00
< ListItemButton onClick = { handleAccountClick } selected = { location . pathname === routes . account } >
2023-01-10 09:40:46 +13:00
< ListItemIcon >
2023-05-24 07:13:01 +12:00
< Person / >
2023-01-10 09:40:46 +13:00
< / ListItemIcon >
2022-12-16 16:07:04 +13:00
< ListItemText primary = { t ( "nav_button_account" ) } / >
2023-01-10 09:40:46 +13:00
< / ListItemButton >
2023-05-24 07:13:01 +12:00
) }
2022-03-10 17:28:55 +13:00
< ListItemButton onClick = { ( ) => navigate ( routes . settings ) } selected = { location . pathname === routes . settings } >
2023-01-10 09:40:46 +13:00
< ListItemIcon >
2022-03-05 10:10:04 +13:00
< SettingsIcon / >
2023-01-10 09:40:46 +13:00
< / ListItemIcon >
2022-04-08 13:46:33 +12:00
< ListItemText primary = { t ( "nav_button_settings" ) } / >
2023-01-10 09:40:46 +13:00
< / ListItemButton >
2022-03-11 16:58:24 +13:00
< ListItemButton onClick = { ( ) => openUrl ( "/docs" ) } >
2023-01-10 09:40:46 +13:00
< ListItemIcon >
2022-03-11 16:58:24 +13:00
< ArticleIcon / >
2023-01-10 09:40:46 +13:00
< / ListItemIcon >
2022-04-08 13:46:33 +12:00
< ListItemText primary = { t ( "nav_button_documentation" ) } / >
2023-01-10 09:40:46 +13:00
< / ListItemButton >
2022-04-04 14:58:44 +12:00
< ListItemButton onClick = { ( ) => props . onPublishMessageClick ( ) } >
2023-01-10 09:40:46 +13:00
< ListItemIcon >
2023-05-24 07:13:01 +12:00
< Send / >
2023-01-10 09:40:46 +13:00
< / ListItemIcon >
2022-04-08 13:46:33 +12:00
< ListItemText primary = { t ( "nav_button_publish_message" ) } / >
2023-01-10 09:40:46 +13:00
< / ListItemButton >
2022-02-26 06:46:22 +13:00
< ListItemButton onClick = { ( ) => setSubscribeDialogOpen ( true ) } >
2023-01-10 09:40:46 +13:00
< ListItemIcon >
2023-05-24 07:13:01 +12:00
< AddIcon / >
2023-01-10 09:40:46 +13:00
< / ListItemIcon >
2022-04-08 13:46:33 +12:00
< ListItemText primary = { t ( "nav_button_subscribe" ) } / >
2023-01-10 09:40:46 +13:00
< / ListItemButton >
2023-01-05 16:47:12 +13:00
{ showUpgradeBanner && < UpgradeBanner / > }
2023-05-24 07:13:01 +12:00
< / List >
2022-03-01 10:56:38 +13:00
< SubscribeDialog
key = { ` subscribeDialog ${ subscribeDialogKey } ` } // Resets dialog when canceled/closed
2022-02-26 06:46:22 +13:00
open = { subscribeDialogOpen }
2022-02-27 04:14:43 +13:00
subscriptions = { props . subscriptions }
2022-02-26 17:25:04 +13:00
onCancel = { handleSubscribeReset }
onSuccess = { handleSubscribeSubmit }
2023-05-24 07:13:01 +12:00
/ >
< / >
2022-02-26 06:46:22 +13:00
) ;
} ;
2022-02-26 17:25:04 +13:00
2023-01-10 09:40:46 +13:00
const UpgradeBanner = ( ) => {
2023-02-12 04:49:37 +13:00
const { t } = useTranslation ( ) ;
2023-01-17 10:35:37 +13:00
const [ dialogKey , setDialogKey ] = useState ( 0 ) ;
2023-01-10 09:40:46 +13:00
const [ dialogOpen , setDialogOpen ] = useState ( false ) ;
2023-01-17 10:35:37 +13:00
const handleClick = ( ) => {
setDialogKey ( ( k ) => k + 1 ) ;
setDialogOpen ( true ) ;
} ;
2023-01-10 09:40:46 +13:00
return (
< Box
sx = { {
position : "fixed" ,
width : ` ${ Navigation . width - 1 } px ` ,
bottom : 0 ,
mt : "auto" ,
background : "linear-gradient(150deg, rgba(196, 228, 221, 0.46) 0%, rgb(255, 255, 255) 100%)" ,
} }
>
< Divider / >
2023-01-17 10:35:37 +13:00
< ListItemButton onClick = { handleClick } sx = { { pt : 2 , pb : 2 } } >
2023-01-10 09:40:46 +13:00
< ListItemIcon >
< CelebrationIcon sx = { { color : "#55b86e" } } fontSize = "large" / >
< / ListItemIcon >
< ListItemText
sx = { { ml : 1 } }
2023-02-12 04:49:37 +13:00
primary = { t ( "nav_upgrade_banner_label" ) }
secondary = { t ( "nav_upgrade_banner_description" ) }
2023-01-10 09:40:46 +13:00
primaryTypographyProps = { {
style : {
fontWeight : 500 ,
2023-01-10 11:56:51 +13:00
fontSize : "1.1rem" ,
2023-01-10 09:40:46 +13:00
background : "-webkit-linear-gradient(45deg, #09009f, #00ff95 80%)" ,
WebkitBackgroundClip : "text" ,
WebkitTextFillColor : "transparent" ,
} ,
} }
2023-01-10 11:56:51 +13:00
secondaryTypographyProps = { {
style : {
fontSize : "1rem" ,
} ,
} }
2023-01-10 09:40:46 +13:00
/ >
< / ListItemButton >
< UpgradeDialog key = { ` upgradeDialog ${ dialogKey } ` } open = { dialogOpen } onCancel = { ( ) => setDialogOpen ( false ) } / >
< / Box >
) ;
} ;
2022-02-27 04:14:43 +13:00
const SubscriptionList = ( props ) => {
2023-01-12 15:38:10 +13:00
const sortedSubscriptions = props . subscriptions
. filter ( ( s ) => ! s . internal )
. sort ( ( a , b ) => ( topicUrl ( a . baseUrl , a . topic ) < topicUrl ( b . baseUrl , b . topic ) ? - 1 : 1 ) ) ;
2022-02-26 06:46:22 +13:00
return (
< >
2022-03-06 02:52:52 +13:00
{ sortedSubscriptions . map ( ( subscription ) => (
2022-03-05 05:08:32 +13:00
< SubscriptionItem
2022-03-03 10:16:30 +13:00
key = { subscription . id }
2022-03-05 05:08:32 +13:00
subscription = { subscription }
2022-03-05 10:10:04 +13:00
selected = { props . selectedSubscription && props . selectedSubscription . id === subscription . id }
2022-03-05 05:08:32 +13:00
/ >
) ) }
2022-02-26 06:46:22 +13:00
< / >
) ;
} ;
2022-03-05 05:08:32 +13:00
const SubscriptionItem = ( props ) => {
2022-05-04 07:09:20 +12:00
const { t } = useTranslation ( ) ;
2022-03-05 10:10:04 +13:00
const navigate = useNavigate ( ) ;
2023-02-01 15:39:30 +13:00
const [ menuAnchorEl , setMenuAnchorEl ] = useState ( null ) ;
2022-03-05 05:08:32 +13:00
const { subscription } = props ;
2022-03-08 10:36:49 +13:00
const iconBadge = subscription . new <= 99 ? subscription . new : "99+" ;
2022-06-30 07:57:56 +12:00
const displayName = topicDisplayName ( subscription ) ;
const ariaLabel = subscription . state === ConnectionState . Connecting ? ` ${ displayName } ( ${ t ( "nav_button_connecting" ) } ) ` : displayName ;
2023-02-01 15:39:30 +13:00
const icon =
subscription . state === ConnectionState . Connecting ? (
< CircularProgress size = "24px" / >
) : (
< Badge badgeContent = { iconBadge } invisible = { subscription . new === 0 } color = "primary" >
< ChatBubbleOutlineIcon / >
< / Badge >
) ;
2022-03-07 16:37:13 +13:00
const handleClick = async ( ) => {
2022-03-10 17:28:55 +13:00
navigate ( routes . forSubscription ( subscription ) ) ;
2022-03-07 16:37:13 +13:00
await subscriptionManager . markNotificationsRead ( subscription . id ) ;
} ;
2023-02-01 15:39:30 +13:00
2022-03-05 05:08:32 +13:00
return (
2023-02-01 16:12:16 +13:00
< >
< ListItemButton onClick = { handleClick } selected = { props . selected } aria - label = { ariaLabel } aria - live = "polite" >
< ListItemIcon > { icon } < / ListItemIcon >
< ListItemText
primary = { displayName }
primaryTypographyProps = { {
style : { overflow : "hidden" , textOverflow : "ellipsis" } ,
} }
/ >
{ subscription . reservation ? . everyone && (
< ListItemIcon edge = "end" sx = { { minWidth : "26px" } } >
{ subscription . reservation ? . everyone === Permission . READ _WRITE && (
< Tooltip title = { t ( "prefs_reservations_table_everyone_read_write" ) } >
< PermissionReadWrite size = "small" / >
< / Tooltip >
) }
{ subscription . reservation ? . everyone === Permission . READ _ONLY && (
< Tooltip title = { t ( "prefs_reservations_table_everyone_read_only" ) } >
< PermissionRead size = "small" / >
< / Tooltip >
) }
{ subscription . reservation ? . everyone === Permission . WRITE _ONLY && (
< Tooltip title = { t ( "prefs_reservations_table_everyone_write_only" ) } >
< PermissionWrite size = "small" / >
< / Tooltip >
) }
{ subscription . reservation ? . everyone === Permission . DENY _ALL && (
< Tooltip title = { t ( "prefs_reservations_table_everyone_deny_all" ) } >
< PermissionDenyAll size = "small" / >
< / Tooltip >
) }
< / ListItemIcon >
) }
{ subscription . mutedUntil > 0 && (
< ListItemIcon edge = "end" sx = { { minWidth : "26px" } } aria - label = { t ( "nav_button_muted" ) } >
< Tooltip title = { t ( "nav_button_muted" ) } >
< NotificationsOffOutlined / >
< / Tooltip >
< / ListItemIcon >
) }
< ListItemIcon edge = "end" sx = { { minWidth : "26px" } } >
2023-02-12 04:52:19 +13:00
< IconButton
size = "small"
onMouseDown = { ( e ) => e . stopPropagation ( ) }
onClick = { ( e ) => {
e . stopPropagation ( ) ;
setMenuAnchorEl ( e . currentTarget ) ;
} }
>
2023-02-01 16:12:16 +13:00
< MoreVert fontSize = "small" / >
< / IconButton >
2023-01-04 05:28:04 +13:00
< / ListItemIcon >
2023-02-01 16:12:16 +13:00
< / ListItemButton >
< Portal >
< SubscriptionPopup subscription = { subscription } anchor = { menuAnchorEl } onClose = { ( ) => setMenuAnchorEl ( null ) } / >
< / Portal >
< / >
2022-03-05 05:08:32 +13:00
) ;
} ;
2023-06-29 06:26:54 +12:00
const NotificationPermissionRequired = ( ) => {
2023-06-02 23:22:54 +12:00
const { t } = useTranslation ( ) ;
2023-06-14 14:03:00 +12:00
const requestPermission = async ( ) => {
await notifier . maybeRequestPermission ( ) ;
} ;
2023-06-02 23:22:54 +12:00
return (
2023-06-14 14:03:00 +12:00
< Alert severity = "warning" sx = { { paddingTop : 2 } } >
2023-06-02 23:22:54 +12:00
< AlertTitle > { t ( "alert_notification_permission_required_title" ) } < / AlertTitle >
2023-06-14 14:03:00 +12:00
< Typography gutterBottom > { t ( "alert_notification_permission_required_description" ) } < / Typography >
< Button sx = { { float : "right" } } color = "inherit" size = "small" onClick = { requestPermission } >
{ t ( "alert_notification_permission_required_button" ) }
< / Button >
2023-06-02 23:22:54 +12:00
< / Alert >
) ;
} ;
2023-05-25 07:36:01 +12:00
const NotificationPermissionDeniedAlert = ( ) => {
const { t } = useTranslation ( ) ;
return (
2023-06-02 23:22:54 +12:00
< Alert severity = "warning" sx = { { paddingTop : 2 } } >
< AlertTitle > { t ( "alert_notification_permission_denied_title" ) } < / AlertTitle >
< Typography gutterBottom > { t ( "alert_notification_permission_denied_description" ) } < / Typography >
< / Alert >
2023-05-25 07:36:01 +12:00
) ;
} ;
const NotificationIOSInstallRequiredAlert = ( ) => {
2022-04-08 13:46:33 +12:00
const { t } = useTranslation ( ) ;
2022-03-02 10:22:47 +13:00
return (
2023-06-14 14:03:00 +12:00
< Alert severity = "warning" sx = { { paddingTop : 2 } } >
< AlertTitle > { t ( "alert_notification_ios_install_required_title" ) } < / AlertTitle >
< Typography gutterBottom > { t ( "alert_notification_ios_install_required_description" ) } < / Typography >
< / Alert >
2022-03-02 10:22:47 +13:00
) ;
} ;
2022-06-13 08:38:33 +12:00
const NotificationBrowserNotSupportedAlert = ( ) => {
2022-04-08 13:46:33 +12:00
const { t } = useTranslation ( ) ;
2022-03-11 12:11:12 +13:00
return (
2023-06-02 23:22:54 +12:00
< Alert severity = "warning" sx = { { paddingTop : 2 } } >
< AlertTitle > { t ( "alert_not_supported_title" ) } < / AlertTitle >
< Typography gutterBottom > { t ( "alert_not_supported_description" ) } < / Typography >
< / Alert >
2022-03-11 12:11:12 +13:00
) ;
} ;
2022-06-13 08:38:33 +12:00
const NotificationContextNotSupportedAlert = ( ) => {
const { t } = useTranslation ( ) ;
return (
2023-06-02 23:22:54 +12:00
< Alert severity = "warning" sx = { { paddingTop : 2 } } >
< AlertTitle > { t ( "alert_not_supported_title" ) } < / AlertTitle >
< Typography gutterBottom >
< Trans
i18nKey = "alert_not_supported_context_description"
components = { {
mdnLink : < Link href = "https://developer.mozilla.org/en-US/docs/Web/API/notification" target = "_blank" rel = "noopener" / > ,
} }
/ >
< / Typography >
< / Alert >
2022-06-13 08:38:33 +12:00
) ;
} ;
2022-02-26 06:46:22 +13:00
export default Navigation ;