mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 09:13:39 -05:00
Merge pull request #18763 from overleaf/ii-bs5-projects-notifications
[web] BS5 notifications in projects and welcome pages GitOrigin-RevId: 25780bb64660ef41c41c007f94f70df273cac716
This commit is contained in:
parent
34311ce0dc
commit
7b47acc486
35 changed files with 388 additions and 527 deletions
|
@ -415,22 +415,6 @@ async function projectListPage(req, res, next) {
|
||||||
logger.error({ err: error }, 'Failed to get individual subscription')
|
logger.error({ err: error }, 'Failed to get individual subscription')
|
||||||
}
|
}
|
||||||
|
|
||||||
let newNotificationStyle
|
|
||||||
try {
|
|
||||||
const newNotificationStyleAssignment =
|
|
||||||
await SplitTestHandler.promises.getAssignment(
|
|
||||||
req,
|
|
||||||
res,
|
|
||||||
'new-notification-style'
|
|
||||||
)
|
|
||||||
newNotificationStyle = newNotificationStyleAssignment.variant === 'enabled'
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(
|
|
||||||
{ err: error },
|
|
||||||
'failed to get "new-notification-style" split test assignment'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await SplitTestHandler.promises.getAssignment(req, res, 'paywall-cta')
|
await SplitTestHandler.promises.getAssignment(req, res, 'paywall-cta')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -473,7 +457,6 @@ async function projectListPage(req, res, next) {
|
||||||
groupName: subscription.teamName,
|
groupName: subscription.teamName,
|
||||||
})),
|
})),
|
||||||
hasIndividualRecurlySubscription,
|
hasIndividualRecurlySubscription,
|
||||||
newNotificationStyle,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,6 @@ block append meta
|
||||||
meta(name="ol-showLATAMBanner" data-type="boolean" content=showLATAMBanner)
|
meta(name="ol-showLATAMBanner" data-type="boolean" content=showLATAMBanner)
|
||||||
meta(name="ol-groupSubscriptionsPendingEnrollment" data-type="json" content=groupSubscriptionsPendingEnrollment)
|
meta(name="ol-groupSubscriptionsPendingEnrollment" data-type="json" content=groupSubscriptionsPendingEnrollment)
|
||||||
meta(name="ol-hasIndividualRecurlySubscription" data-type="boolean" content=hasIndividualRecurlySubscription)
|
meta(name="ol-hasIndividualRecurlySubscription" data-type="boolean" content=hasIndividualRecurlySubscription)
|
||||||
meta(name="ol-newNotificationStyle" data-type="boolean" content=newNotificationStyle)
|
|
||||||
meta(name="ol-groupSsoSetupSuccess" data-type="boolean" content=groupSsoSetupSuccess)
|
meta(name="ol-groupSsoSetupSuccess" data-type="boolean" content=groupSsoSetupSuccess)
|
||||||
|
|
||||||
block content
|
block content
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { useEffect, useState, useMemo } from 'react'
|
import { useEffect, useState, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ProjectsActionModal from './projects-action-modal'
|
import ProjectsActionModal from './projects-action-modal'
|
||||||
import Icon from '../../../../shared/components/icon'
|
|
||||||
import ProjectsList from './projects-list'
|
import ProjectsList from './projects-list'
|
||||||
import { isLeavableProject, isDeletableProject } from '../../util/project'
|
import { isLeavableProject, isDeletableProject } from '../../util/project'
|
||||||
|
import Notification from '@/shared/components/notification'
|
||||||
|
|
||||||
type DeleteLeaveProjectModalProps = Pick<
|
type DeleteLeaveProjectModalProps = Pick<
|
||||||
React.ComponentProps<typeof ProjectsActionModal>,
|
React.ComponentProps<typeof ProjectsActionModal>,
|
||||||
|
@ -70,10 +70,10 @@ function DeleteLeaveProjectModal({
|
||||||
projects={projectsToLeave}
|
projects={projectsToLeave}
|
||||||
projectsToDisplay={projectsToLeaveDisplay}
|
projectsToDisplay={projectsToLeaveDisplay}
|
||||||
/>
|
/>
|
||||||
<div className="project-action-alert alert alert-warning">
|
<Notification
|
||||||
<Icon type="exclamation-triangle" fw />{' '}
|
content={t('this_action_cannot_be_undone')}
|
||||||
{t('this_action_cannot_be_undone')}
|
type="warning"
|
||||||
</div>
|
/>
|
||||||
</ProjectsActionModal>
|
</ProjectsActionModal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ProjectsActionModal from './projects-action-modal'
|
import ProjectsActionModal from './projects-action-modal'
|
||||||
import Icon from '../../../../shared/components/icon'
|
|
||||||
import ProjectsList from './projects-list'
|
import ProjectsList from './projects-list'
|
||||||
|
import Notification from '@/shared/components/notification'
|
||||||
|
|
||||||
type DeleteProjectModalProps = Pick<
|
type DeleteProjectModalProps = Pick<
|
||||||
React.ComponentProps<typeof ProjectsActionModal>,
|
React.ComponentProps<typeof ProjectsActionModal>,
|
||||||
|
@ -41,10 +41,10 @@ function DeleteProjectModal({
|
||||||
>
|
>
|
||||||
<p>{t('about_to_delete_projects')}</p>
|
<p>{t('about_to_delete_projects')}</p>
|
||||||
<ProjectsList projects={projects} projectsToDisplay={projectsToDisplay} />
|
<ProjectsList projects={projects} projectsToDisplay={projectsToDisplay} />
|
||||||
<div className="project-action-alert alert alert-warning">
|
<Notification
|
||||||
<Icon type="exclamation-triangle" fw />{' '}
|
content={t('this_action_cannot_be_undone')}
|
||||||
{t('this_action_cannot_be_undone')}
|
type="warning"
|
||||||
</div>
|
/>
|
||||||
</ProjectsActionModal>
|
</ProjectsActionModal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ProjectsActionModal from './projects-action-modal'
|
import ProjectsActionModal from './projects-action-modal'
|
||||||
import Icon from '../../../../shared/components/icon'
|
|
||||||
import ProjectsList from './projects-list'
|
import ProjectsList from './projects-list'
|
||||||
|
import Notification from '@/shared/components/notification'
|
||||||
|
|
||||||
type LeaveProjectModalProps = Pick<
|
type LeaveProjectModalProps = Pick<
|
||||||
React.ComponentProps<typeof ProjectsActionModal>,
|
React.ComponentProps<typeof ProjectsActionModal>,
|
||||||
|
@ -41,10 +41,10 @@ function LeaveProjectModal({
|
||||||
>
|
>
|
||||||
<p>{t('about_to_leave_projects')}</p>
|
<p>{t('about_to_leave_projects')}</p>
|
||||||
<ProjectsList projects={projects} projectsToDisplay={projectsToDisplay} />
|
<ProjectsList projects={projects} projectsToDisplay={projectsToDisplay} />
|
||||||
<div className="project-action-alert alert alert-warning">
|
<Notification
|
||||||
<Icon type="exclamation-triangle" fw />{' '}
|
content={t('this_action_cannot_be_undone')}
|
||||||
{t('this_action_cannot_be_undone')}
|
type="warning"
|
||||||
</div>
|
/>
|
||||||
</ProjectsActionModal>
|
</ProjectsActionModal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import { memo, useEffect, useState } from 'react'
|
import { memo, useEffect, useState } from 'react'
|
||||||
import { Alert, Modal } from 'react-bootstrap'
|
import { Modal } from 'react-bootstrap'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Project } from '../../../../../../types/project/dashboard/api'
|
import { Project } from '../../../../../../types/project/dashboard/api'
|
||||||
import AccessibleModal from '../../../../shared/components/accessible-modal'
|
|
||||||
import { getUserFacingMessage } from '../../../../infrastructure/fetch-json'
|
import { getUserFacingMessage } from '../../../../infrastructure/fetch-json'
|
||||||
import useIsMounted from '../../../../shared/hooks/use-is-mounted'
|
import useIsMounted from '../../../../shared/hooks/use-is-mounted'
|
||||||
import * as eventTracking from '../../../../infrastructure/event-tracking'
|
import * as eventTracking from '../../../../infrastructure/event-tracking'
|
||||||
import { isSmallDevice } from '../../../../infrastructure/event-tracking'
|
import { isSmallDevice } from '../../../../infrastructure/event-tracking'
|
||||||
import getMeta from '@/utils/meta'
|
|
||||||
import Notification from '@/shared/components/notification'
|
import Notification from '@/shared/components/notification'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
import OLModal, {
|
||||||
|
OLModalBody,
|
||||||
|
OLModalFooter,
|
||||||
|
OLModalHeader,
|
||||||
|
} from '@/features/ui/components/ol/ol-modal'
|
||||||
|
|
||||||
type ProjectsActionModalProps = {
|
type ProjectsActionModalProps = {
|
||||||
title?: string
|
title?: string
|
||||||
|
@ -68,64 +72,44 @@ function ProjectsActionModal({
|
||||||
}, [action, showModal])
|
}, [action, showModal])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccessibleModal
|
<OLModal
|
||||||
animation
|
animation
|
||||||
show={showModal}
|
show={showModal}
|
||||||
onHide={handleCloseModal}
|
onHide={handleCloseModal}
|
||||||
id="action-project-modal"
|
id="action-project-modal"
|
||||||
backdrop="static"
|
backdrop="static"
|
||||||
>
|
>
|
||||||
<Modal.Header closeButton>
|
<OLModalHeader closeButton>
|
||||||
<Modal.Title>{title}</Modal.Title>
|
<Modal.Title>{title}</Modal.Title>
|
||||||
</Modal.Header>
|
</OLModalHeader>
|
||||||
<Modal.Body>{children}</Modal.Body>
|
<OLModalBody>
|
||||||
<Modal.Footer>
|
{children}
|
||||||
{!isProcessing &&
|
{!isProcessing &&
|
||||||
errors.length > 0 &&
|
errors.length > 0 &&
|
||||||
errors.map((e, i) => <ErrorNotification error={e} key={i} />)}
|
errors.map((error, i) => (
|
||||||
<button className="btn btn-secondary" onClick={handleCloseModal}>
|
<div className="notification-list" key={i}>
|
||||||
|
<Notification
|
||||||
|
type="error"
|
||||||
|
title={error.projectName}
|
||||||
|
content={getUserFacingMessage(error.error) as string}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</OLModalBody>
|
||||||
|
<OLModalFooter>
|
||||||
|
<OLButton variant="secondary" onClick={handleCloseModal}>
|
||||||
{t('cancel')}
|
{t('cancel')}
|
||||||
</button>
|
</OLButton>
|
||||||
<button
|
<OLButton
|
||||||
className="btn btn-danger"
|
variant="danger"
|
||||||
onClick={() => handleActionForProjects(projects)}
|
onClick={() => handleActionForProjects(projects)}
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
>
|
>
|
||||||
{t('confirm')}
|
{t('confirm')}
|
||||||
</button>
|
</OLButton>
|
||||||
</Modal.Footer>
|
</OLModalFooter>
|
||||||
</AccessibleModal>
|
</OLModal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrorNotificationProps = {
|
|
||||||
error: any
|
|
||||||
}
|
|
||||||
|
|
||||||
function ErrorNotification({ error }: ErrorNotificationProps) {
|
|
||||||
const newNotificationStyle = getMeta('ol-newNotificationStyle')
|
|
||||||
|
|
||||||
if (newNotificationStyle) {
|
|
||||||
return (
|
|
||||||
// `notification-list` sets the margin-bottom correctly also when used individually in each notification.
|
|
||||||
// Once the legacy alerts are cleaned up we should move the styled div up to the notification list container.
|
|
||||||
<div className="notification-list">
|
|
||||||
<Notification
|
|
||||||
type="error"
|
|
||||||
title={error.projectName}
|
|
||||||
content={getUserFacingMessage(error.error) as string}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Alert bsStyle="danger" className="text-center" aria-live="polite">
|
|
||||||
<b>{error.projectName}</b>
|
|
||||||
<br />
|
|
||||||
{getUserFacingMessage(error.error)}
|
|
||||||
</Alert>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(ProjectsActionModal)
|
export default memo(ProjectsActionModal)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
Alert,
|
|
||||||
Button,
|
Button,
|
||||||
ControlLabel,
|
ControlLabel,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
@ -18,7 +17,6 @@ import { getUserFacingMessage } from '../../../../infrastructure/fetch-json'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
import { isSmallDevice } from '../../../../infrastructure/event-tracking'
|
import { isSmallDevice } from '../../../../infrastructure/event-tracking'
|
||||||
import Notification from '@/shared/components/notification'
|
import Notification from '@/shared/components/notification'
|
||||||
import getMeta from '@/utils/meta'
|
|
||||||
|
|
||||||
type RenameProjectModalProps = {
|
type RenameProjectModalProps = {
|
||||||
handleCloseModal: () => void
|
handleCloseModal: () => void
|
||||||
|
@ -36,7 +34,6 @@ function RenameProjectModal({
|
||||||
const { error, isError, isLoading, runAsync } = useAsync()
|
const { error, isError, isLoading, runAsync } = useAsync()
|
||||||
const { toggleSelectedProject, updateProjectViewData } =
|
const { toggleSelectedProject, updateProjectViewData } =
|
||||||
useProjectListContext()
|
useProjectListContext()
|
||||||
const newNotificationStyle = getMeta('ol-newNotificationStyle')
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showModal) {
|
if (showModal) {
|
||||||
|
@ -99,19 +96,14 @@ function RenameProjectModal({
|
||||||
<Modal.Title>{t('rename_project')}</Modal.Title>
|
<Modal.Title>{t('rename_project')}</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
{isError &&
|
{isError && (
|
||||||
(newNotificationStyle ? (
|
<div className="notification-list">
|
||||||
<div className="notification-list">
|
<Notification
|
||||||
<Notification
|
type="error"
|
||||||
type="error"
|
content={getUserFacingMessage(error) as string}
|
||||||
content={getUserFacingMessage(error) as string}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
)}
|
||||||
) : (
|
|
||||||
<Alert bsStyle="danger" className="text-center" aria-live="polite">
|
|
||||||
{getUserFacingMessage(error)}
|
|
||||||
</Alert>
|
|
||||||
))}
|
|
||||||
<form id="rename-project-form" onSubmit={handleSubmit}>
|
<form id="rename-project-form" onSubmit={handleSubmit}>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<ControlLabel htmlFor="rename-project-form-name">
|
<ControlLabel htmlFor="rename-project-form-name">
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { Alert } from 'react-bootstrap'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import useAsync from '../../../../shared/hooks/use-async'
|
import useAsync from '../../../../shared/hooks/use-async'
|
||||||
import {
|
import {
|
||||||
|
@ -8,7 +7,6 @@ import {
|
||||||
} from '../../../../infrastructure/fetch-json'
|
} from '../../../../infrastructure/fetch-json'
|
||||||
import { useRefWithAutoFocus } from '../../../../shared/hooks/use-ref-with-auto-focus'
|
import { useRefWithAutoFocus } from '../../../../shared/hooks/use-ref-with-auto-focus'
|
||||||
import { useLocation } from '../../../../shared/hooks/use-location'
|
import { useLocation } from '../../../../shared/hooks/use-location'
|
||||||
import getMeta from '@/utils/meta'
|
|
||||||
import Notification from '@/shared/components/notification'
|
import Notification from '@/shared/components/notification'
|
||||||
import {
|
import {
|
||||||
OLModalBody,
|
OLModalBody,
|
||||||
|
@ -42,7 +40,6 @@ function ModalContentNewProjectForm({ onCancel, template = 'none' }: Props) {
|
||||||
const [projectName, setProjectName] = useState('')
|
const [projectName, setProjectName] = useState('')
|
||||||
const { isLoading, isError, error, runAsync } = useAsync<NewProjectData>()
|
const { isLoading, isError, error, runAsync } = useAsync<NewProjectData>()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const newNotificationStyle = getMeta('ol-newNotificationStyle')
|
|
||||||
|
|
||||||
const createNewProject = () => {
|
const createNewProject = () => {
|
||||||
runAsync(
|
runAsync(
|
||||||
|
@ -77,17 +74,14 @@ function ModalContentNewProjectForm({ onCancel, template = 'none' }: Props) {
|
||||||
</OLModalHeader>
|
</OLModalHeader>
|
||||||
|
|
||||||
<OLModalBody>
|
<OLModalBody>
|
||||||
{isError &&
|
{isError && (
|
||||||
(newNotificationStyle ? (
|
<div className="notification-list">
|
||||||
<div className="notification-list">
|
<Notification
|
||||||
<Notification
|
type="error"
|
||||||
type="error"
|
content={getUserFacingMessage(error) as string}
|
||||||
content={getUserFacingMessage(error) as string}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
)}
|
||||||
) : (
|
|
||||||
<Alert bsStyle="danger">{getUserFacingMessage(error)}</Alert>
|
|
||||||
))}
|
|
||||||
<OLForm onSubmit={handleSubmit}>
|
<OLForm onSubmit={handleSubmit}>
|
||||||
<OLFormControl
|
<OLFormControl
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { memo, useEffect, useState } from 'react'
|
||||||
import Notification from './notification'
|
import Notification from './notification'
|
||||||
import customLocalStorage from '@/infrastructure/local-storage'
|
import customLocalStorage from '@/infrastructure/local-storage'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
|
||||||
function AccessibilitySurveyBanner() {
|
function AccessibilitySurveyBanner() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
@ -27,18 +28,18 @@ function AccessibilitySurveyBanner() {
|
||||||
return (
|
return (
|
||||||
<Notification
|
<Notification
|
||||||
className="sr-only"
|
className="sr-only"
|
||||||
bsStyle="info"
|
type="info"
|
||||||
onDismiss={handleClose}
|
onDismiss={handleClose}
|
||||||
body={<p>{t('help_improve_screen_reader_fill_out_this_survey')}</p>}
|
content={<p>{t('help_improve_screen_reader_fill_out_this_survey')}</p>}
|
||||||
action={
|
action={
|
||||||
<a
|
<OLButton
|
||||||
className="btn btn-secondary btn-sm pull-right btn-info"
|
variant="secondary"
|
||||||
href="https://docs.google.com/forms/d/e/1FAIpQLSdxKP_biRXvrkmJzlBjMwI_qPSuv4NbBvYUzSOc3OOTIOTmnQ/viewform"
|
href="https://docs.google.com/forms/d/e/1FAIpQLSdxKP_biRXvrkmJzlBjMwI_qPSuv4NbBvYUzSOc3OOTIOTmnQ/viewform"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
{t('take_survey')}
|
{t('take_survey')}
|
||||||
</a>
|
</OLButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
import classnames from 'classnames'
|
|
||||||
|
|
||||||
function Action({ className, ...props }: React.ComponentProps<'div'>) {
|
|
||||||
return (
|
|
||||||
<div className={classnames('notification-action', className)} {...props} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Action
|
|
|
@ -1,9 +0,0 @@
|
||||||
import classnames from 'classnames'
|
|
||||||
|
|
||||||
function Body({ className, ...props }: React.ComponentProps<'div'>) {
|
|
||||||
return (
|
|
||||||
<div className={classnames('notification-body', className)} {...props} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Body
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
GroupsAndEnterpriseBannerVariant,
|
GroupsAndEnterpriseBannerVariant,
|
||||||
GroupsAndEnterpriseBannerVariants,
|
GroupsAndEnterpriseBannerVariants,
|
||||||
} from '../../../../../../types/project/dashboard/notification'
|
} from '../../../../../../types/project/dashboard/notification'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
|
||||||
type urlForVariantsType = {
|
type urlForVariantsType = {
|
||||||
[key in GroupsAndEnterpriseBannerVariant]: string // eslint-disable-line no-unused-vars
|
[key in GroupsAndEnterpriseBannerVariant]: string // eslint-disable-line no-unused-vars
|
||||||
|
@ -31,7 +32,6 @@ export default function GroupsAndEnterpriseBanner() {
|
||||||
const groupsAndEnterpriseBannerVariant = getMeta(
|
const groupsAndEnterpriseBannerVariant = getMeta(
|
||||||
'ol-groupsAndEnterpriseBannerVariant'
|
'ol-groupsAndEnterpriseBannerVariant'
|
||||||
)
|
)
|
||||||
const newNotificationStyle = getMeta('ol-newNotificationStyle')
|
|
||||||
|
|
||||||
const hasDismissedGroupsAndEnterpriseBanner = hasRecentlyDismissedBanner()
|
const hasDismissedGroupsAndEnterpriseBanner = hasRecentlyDismissedBanner()
|
||||||
|
|
||||||
|
@ -73,23 +73,19 @@ export default function GroupsAndEnterpriseBanner() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="info"
|
type="info"
|
||||||
onDismiss={handleClose}
|
onDismiss={handleClose}
|
||||||
body={<BannerContent variant={groupsAndEnterpriseBannerVariant} />}
|
content={<BannerContent variant={groupsAndEnterpriseBannerVariant} />}
|
||||||
action={
|
action={
|
||||||
<a
|
<OLButton
|
||||||
className={
|
variant="secondary"
|
||||||
newNotificationStyle
|
|
||||||
? 'btn btn-secondary btn-sm'
|
|
||||||
: 'pull-right btn btn-info btn-sm'
|
|
||||||
}
|
|
||||||
href={contactSalesUrl}
|
href={contactSalesUrl}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
onClick={handleClickContact}
|
onClick={handleClickContact}
|
||||||
>
|
>
|
||||||
{t('contact_sales')}
|
{t('contact_sales')}
|
||||||
</a>
|
</OLButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { useTranslation, Trans } from 'react-i18next'
|
import { useTranslation, Trans } from 'react-i18next'
|
||||||
import { Button } from 'react-bootstrap'
|
|
||||||
import Icon from '../../../../../../shared/components/icon'
|
import Icon from '../../../../../../shared/components/icon'
|
||||||
import getMeta from '../../../../../../utils/meta'
|
import getMeta from '../../../../../../utils/meta'
|
||||||
import useAsync from '../../../../../../shared/hooks/use-async'
|
import useAsync from '../../../../../../shared/hooks/use-async'
|
||||||
|
@ -12,6 +11,8 @@ import { UserEmailData } from '../../../../../../../../types/user-email'
|
||||||
import { Institution } from '../../../../../../../../types/institution'
|
import { Institution } from '../../../../../../../../types/institution'
|
||||||
import { useLocation } from '../../../../../../shared/hooks/use-location'
|
import { useLocation } from '../../../../../../shared/hooks/use-location'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
import Notification from '@/features/project-list/components/notifications/notification'
|
||||||
|
|
||||||
type ReconfirmAffiliationProps = {
|
type ReconfirmAffiliationProps = {
|
||||||
email: UserEmailData['email']
|
email: UserEmailData['email']
|
||||||
|
@ -24,7 +25,6 @@ function ReconfirmAffiliation({
|
||||||
}: ReconfirmAffiliationProps) {
|
}: ReconfirmAffiliationProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { samlInitPath } = getMeta('ol-ExposedSettings')
|
const { samlInitPath } = getMeta('ol-ExposedSettings')
|
||||||
const newNotificationStyle = getMeta('ol-newNotificationStyle')
|
|
||||||
const { error, isLoading, isError, isSuccess, runAsync } = useAsync()
|
const { error, isLoading, isError, isSuccess, runAsync } = useAsync()
|
||||||
const [hasSent, setHasSent] = useState(false)
|
const [hasSent, setHasSent] = useState(false)
|
||||||
const [isPending, setIsPending] = useState(false)
|
const [isPending, setIsPending] = useState(false)
|
||||||
|
@ -57,91 +57,108 @@ function ReconfirmAffiliation({
|
||||||
|
|
||||||
if (hasSent) {
|
if (hasSent) {
|
||||||
return (
|
return (
|
||||||
<div className="w-100">
|
<Notification
|
||||||
<Trans
|
type="info"
|
||||||
i18nKey="please_check_your_inbox_to_confirm"
|
content={
|
||||||
components={[<b />]} // eslint-disable-line react/jsx-key
|
|
||||||
values={{ institutionName: institution.name }}
|
|
||||||
shouldUnescape
|
|
||||||
tOptions={{ interpolation: { escapeValue: true } }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{isLoading ? (
|
|
||||||
<>
|
<>
|
||||||
<Icon type="refresh" spin fw /> {t('sending')}…
|
<Trans
|
||||||
|
i18nKey="please_check_your_inbox_to_confirm"
|
||||||
|
components={[<b />]} // eslint-disable-line react/jsx-key
|
||||||
|
values={{ institutionName: institution.name }}
|
||||||
|
shouldUnescape
|
||||||
|
tOptions={{ interpolation: { escapeValue: true } }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isError && (
|
||||||
|
<>
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
{rateLimited
|
||||||
|
? t('too_many_requests')
|
||||||
|
: t('generic_something_went_wrong')}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
}
|
||||||
<Button
|
action={
|
||||||
|
<OLButton
|
||||||
|
variant="link"
|
||||||
|
onClick={handleRequestReconfirmation}
|
||||||
className="btn-inline-link"
|
className="btn-inline-link"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
onClick={handleRequestReconfirmation}
|
isLoading={isLoading}
|
||||||
|
bs3Props={{
|
||||||
|
loading: isLoading ? (
|
||||||
|
<>
|
||||||
|
<Icon type="refresh" spin fw /> {t('sending')}…
|
||||||
|
</>
|
||||||
|
) : null,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t('resend_confirmation_email')}
|
{t('resend_confirmation_email')}
|
||||||
</Button>
|
</OLButton>
|
||||||
)}
|
}
|
||||||
{isError && (
|
/>
|
||||||
<>
|
|
||||||
<br />
|
|
||||||
<div>
|
|
||||||
{rateLimited
|
|
||||||
? t('too_many_requests')
|
|
||||||
: t('generic_something_went_wrong')}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-100">
|
<Notification
|
||||||
{!newNotificationStyle && <Icon type="warning" />}
|
type="info"
|
||||||
<Button
|
content={
|
||||||
bsStyle="info"
|
|
||||||
bsSize="sm"
|
|
||||||
className="btn-reconfirm"
|
|
||||||
onClick={handleRequestReconfirmation}
|
|
||||||
disabled={isLoading || isPending}
|
|
||||||
>
|
|
||||||
{isLoading ? (
|
|
||||||
<>
|
|
||||||
<Icon type="refresh" spin fw /> {t('sending')}…
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
t('confirm_affiliation')
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
<Trans
|
|
||||||
i18nKey="are_you_still_at"
|
|
||||||
components={[<b />]} // eslint-disable-line react/jsx-key
|
|
||||||
values={{ institutionName: institution.name }}
|
|
||||||
shouldUnescape
|
|
||||||
tOptions={{ interpolation: { escapeValue: true } }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Trans
|
|
||||||
i18nKey="please_reconfirm_institutional_email"
|
|
||||||
/* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
|
|
||||||
components={[<a href={`/user/settings?remove=${email}`} />]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="/learn/how-to/Institutional_Email_Reconfirmation"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{t('learn_more')}
|
|
||||||
</a>
|
|
||||||
{isError && (
|
|
||||||
<>
|
<>
|
||||||
<br />
|
<Trans
|
||||||
<div>
|
i18nKey="are_you_still_at"
|
||||||
{rateLimited
|
components={[<b />]} // eslint-disable-line react/jsx-key
|
||||||
? t('too_many_requests')
|
values={{ institutionName: institution.name }}
|
||||||
: t('generic_something_went_wrong')}
|
shouldUnescape
|
||||||
</div>
|
tOptions={{ interpolation: { escapeValue: true } }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Trans
|
||||||
|
i18nKey="please_reconfirm_institutional_email"
|
||||||
|
/* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
|
||||||
|
components={[<a href={`/user/settings?remove=${email}`} />]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="/learn/how-to/Institutional_Email_Reconfirmation"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{t('learn_more')}
|
||||||
|
</a>
|
||||||
|
{isError && (
|
||||||
|
<>
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
{rateLimited
|
||||||
|
? t('too_many_requests')
|
||||||
|
: t('generic_something_went_wrong')}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
}
|
||||||
</div>
|
action={
|
||||||
|
<OLButton
|
||||||
|
variant="secondary"
|
||||||
|
bs3Props={{
|
||||||
|
loading:
|
||||||
|
isLoading || isPending ? (
|
||||||
|
<>
|
||||||
|
<Icon type="refresh" spin fw /> {t('sending')}…
|
||||||
|
</>
|
||||||
|
) : null,
|
||||||
|
}}
|
||||||
|
isLoading={isLoading || isPending}
|
||||||
|
disabled={isLoading || isPending}
|
||||||
|
onClick={handleRequestReconfirmation}
|
||||||
|
>
|
||||||
|
{t('confirm_affiliation')}
|
||||||
|
</OLButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,17 +12,10 @@ function ReconfirmationInfo() {
|
||||||
<>
|
<>
|
||||||
{allInReconfirmNotificationPeriods.map(userEmail =>
|
{allInReconfirmNotificationPeriods.map(userEmail =>
|
||||||
userEmail.affiliation?.institution ? (
|
userEmail.affiliation?.institution ? (
|
||||||
<Notification
|
<ReconfirmAffiliation
|
||||||
|
email={userEmail.email}
|
||||||
|
institution={userEmail.affiliation.institution}
|
||||||
key={`reconfirmation-period-email-${userEmail.email}`}
|
key={`reconfirmation-period-email-${userEmail.email}`}
|
||||||
bsStyle="info"
|
|
||||||
body={
|
|
||||||
<div className="reconfirm-notification">
|
|
||||||
<ReconfirmAffiliation
|
|
||||||
email={userEmail.email}
|
|
||||||
institution={userEmail.affiliation.institution}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
)}
|
)}
|
||||||
|
@ -31,9 +24,9 @@ function ReconfirmationInfo() {
|
||||||
userEmail.affiliation?.institution ? (
|
userEmail.affiliation?.institution ? (
|
||||||
<Notification
|
<Notification
|
||||||
key={`samlIdentifier-email-${userEmail.email}`}
|
key={`samlIdentifier-email-${userEmail.email}`}
|
||||||
bsStyle="info"
|
type="info"
|
||||||
onDismiss={() => {}}
|
onDismiss={() => {}}
|
||||||
body={
|
content={
|
||||||
<ReconfirmationInfoSuccess
|
<ReconfirmationInfoSuccess
|
||||||
institution={userEmail.affiliation?.institution}
|
institution={userEmail.affiliation?.institution}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { useTranslation, Trans } from 'react-i18next'
|
import { useTranslation, Trans } from 'react-i18next'
|
||||||
import { Button } from 'react-bootstrap'
|
|
||||||
import Notification from '../notification'
|
import Notification from '../notification'
|
||||||
import Icon from '../../../../../shared/components/icon'
|
import Icon from '../../../../../shared/components/icon'
|
||||||
import getMeta from '../../../../../utils/meta'
|
import getMeta from '../../../../../utils/meta'
|
||||||
|
@ -13,6 +12,7 @@ import {
|
||||||
import GroupInvitationNotification from './group-invitation/group-invitation'
|
import GroupInvitationNotification from './group-invitation/group-invitation'
|
||||||
import IEEERetirementBanner from '../ieee-retirement-banner'
|
import IEEERetirementBanner from '../ieee-retirement-banner'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
|
||||||
function Common() {
|
function Common() {
|
||||||
const notifications = getMeta('ol-notifications') || []
|
const notifications = getMeta('ol-notifications') || []
|
||||||
|
@ -37,7 +37,6 @@ function CommonNotification({ notification }: CommonNotificationProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { samlInitPath } = getMeta('ol-ExposedSettings')
|
const { samlInitPath } = getMeta('ol-ExposedSettings')
|
||||||
const user = getMeta('ol-user')
|
const user = getMeta('ol-user')
|
||||||
const newNotificationStyle = getMeta('ol-newNotificationStyle')
|
|
||||||
const { isLoading, isSuccess, error, runAsync } = useAsync<
|
const { isLoading, isSuccess, error, runAsync } = useAsync<
|
||||||
never,
|
never,
|
||||||
FetchError
|
FetchError
|
||||||
|
@ -63,9 +62,9 @@ function CommonNotification({ notification }: CommonNotificationProps) {
|
||||||
<>
|
<>
|
||||||
{templateKey === 'notification_project_invite' ? (
|
{templateKey === 'notification_project_invite' ? (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="info"
|
type="info"
|
||||||
onDismiss={() => id && handleDismiss(id)}
|
onDismiss={() => id && handleDismiss(id)}
|
||||||
body={
|
content={
|
||||||
accepted ? (
|
accepted ? (
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="notification_project_invite_accepted_message"
|
i18nKey="notification_project_invite_accepted_message"
|
||||||
|
@ -89,42 +88,36 @@ function CommonNotification({ notification }: CommonNotificationProps) {
|
||||||
}
|
}
|
||||||
action={
|
action={
|
||||||
accepted ? (
|
accepted ? (
|
||||||
<Button
|
<OLButton
|
||||||
bsStyle={newNotificationStyle ? null : 'info'}
|
variant="secondary"
|
||||||
bsSize="sm"
|
|
||||||
className={
|
|
||||||
newNotificationStyle ? 'btn-secondary' : 'pull-right'
|
|
||||||
}
|
|
||||||
href={`/project/${notification.messageOpts.projectId}`}
|
href={`/project/${notification.messageOpts.projectId}`}
|
||||||
>
|
>
|
||||||
{t('open_project')}
|
{t('open_project')}
|
||||||
</Button>
|
</OLButton>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<OLButton
|
||||||
bsStyle={newNotificationStyle ? null : 'info'}
|
variant="secondary"
|
||||||
className={
|
bs3Props={{
|
||||||
newNotificationStyle ? 'btn-secondary' : 'pull-right'
|
loading: isLoading ? (
|
||||||
}
|
<>
|
||||||
bsSize="sm"
|
<Icon type="spinner" spin /> {t('joining')}…
|
||||||
|
</>
|
||||||
|
) : null,
|
||||||
|
}}
|
||||||
|
isLoading={isLoading}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
onClick={() => handleAcceptInvite(notification)}
|
onClick={() => handleAcceptInvite(notification)}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{t('join_project')}
|
||||||
<>
|
</OLButton>
|
||||||
<Icon type="spinner" spin /> {t('joining')}…
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
t('join_project')
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : templateKey === 'wfh_2020_upgrade_offer' ? (
|
) : templateKey === 'wfh_2020_upgrade_offer' ? (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="info"
|
type="info"
|
||||||
onDismiss={() => id && handleDismiss(id)}
|
onDismiss={() => id && handleDismiss(id)}
|
||||||
body={
|
content={
|
||||||
<>
|
<>
|
||||||
Important notice: Your free WFH2020 upgrade came to an end on June
|
Important notice: Your free WFH2020 upgrade came to an end on June
|
||||||
30th 2020. We're still providing a number of special initiatives
|
30th 2020. We're still providing a number of special initiatives
|
||||||
|
@ -132,21 +125,19 @@ function CommonNotification({ notification }: CommonNotificationProps) {
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
action={
|
action={
|
||||||
<Button
|
<OLButton
|
||||||
bsStyle={newNotificationStyle ? null : 'info'}
|
variant="secondary"
|
||||||
bsSize="sm"
|
|
||||||
className={newNotificationStyle ? 'btn-secondary' : 'pull-right'}
|
|
||||||
href="https://www.overleaf.com/events/wfh2020"
|
href="https://www.overleaf.com/events/wfh2020"
|
||||||
>
|
>
|
||||||
View
|
View
|
||||||
</Button>
|
</OLButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : templateKey === 'notification_ip_matched_affiliation' ? (
|
) : templateKey === 'notification_ip_matched_affiliation' ? (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="info"
|
type="info"
|
||||||
onDismiss={() => id && handleDismiss(id)}
|
onDismiss={() => id && handleDismiss(id)}
|
||||||
body={
|
content={
|
||||||
<>
|
<>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="looks_like_youre_at"
|
i18nKey="looks_like_youre_at"
|
||||||
|
@ -193,10 +184,8 @@ function CommonNotification({ notification }: CommonNotificationProps) {
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
action={
|
action={
|
||||||
<Button
|
<OLButton
|
||||||
bsStyle={newNotificationStyle ? null : 'info'}
|
variant="secondary"
|
||||||
bsSize="sm"
|
|
||||||
className={newNotificationStyle ? 'btn-secondary' : 'pull-right'}
|
|
||||||
href={
|
href={
|
||||||
notification.messageOpts.ssoEnabled
|
notification.messageOpts.ssoEnabled
|
||||||
? `${samlInitPath}?university_id=${notification.messageOpts.institutionId}&auto=/project`
|
? `${samlInitPath}?university_id=${notification.messageOpts.institutionId}&auto=/project`
|
||||||
|
@ -206,14 +195,14 @@ function CommonNotification({ notification }: CommonNotificationProps) {
|
||||||
{notification.messageOpts.ssoEnabled
|
{notification.messageOpts.ssoEnabled
|
||||||
? t('link_account')
|
? t('link_account')
|
||||||
: t('add_affiliation')}
|
: t('add_affiliation')}
|
||||||
</Button>
|
</OLButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : templateKey === 'notification_tpds_file_limit' ? (
|
) : templateKey === 'notification_tpds_file_limit' ? (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="danger"
|
type="error"
|
||||||
onDismiss={() => id && handleDismiss(id)}
|
onDismiss={() => id && handleDismiss(id)}
|
||||||
body={
|
content={
|
||||||
<>
|
<>
|
||||||
Error: Your project {notification.messageOpts.projectName} has
|
Error: Your project {notification.messageOpts.projectName} has
|
||||||
gone over the 2000 file limit using an integration (e.g. Dropbox
|
gone over the 2000 file limit using an integration (e.g. Dropbox
|
||||||
|
@ -223,21 +212,16 @@ function CommonNotification({ notification }: CommonNotificationProps) {
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
action={
|
action={
|
||||||
<Button
|
<OLButton variant="secondary" href="/user/settings">
|
||||||
bsStyle={newNotificationStyle ? null : 'danger'}
|
|
||||||
bsSize="sm"
|
|
||||||
className={newNotificationStyle ? 'btn-secondary' : 'pull-right'}
|
|
||||||
href="/user/settings"
|
|
||||||
>
|
|
||||||
Account Settings
|
Account Settings
|
||||||
</Button>
|
</OLButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : templateKey === 'notification_dropbox_duplicate_project_names' ? (
|
) : templateKey === 'notification_dropbox_duplicate_project_names' ? (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="warning"
|
type="warning"
|
||||||
onDismiss={() => id && handleDismiss(id)}
|
onDismiss={() => id && handleDismiss(id)}
|
||||||
body={
|
content={
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
<Trans
|
<Trans
|
||||||
|
@ -267,9 +251,9 @@ function CommonNotification({ notification }: CommonNotificationProps) {
|
||||||
) : templateKey ===
|
) : templateKey ===
|
||||||
'notification_dropbox_unlinked_due_to_lapsed_reconfirmation' ? (
|
'notification_dropbox_unlinked_due_to_lapsed_reconfirmation' ? (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="info"
|
type="info"
|
||||||
onDismiss={() => id && handleDismiss(id)}
|
onDismiss={() => id && handleDismiss(id)}
|
||||||
body={
|
content={
|
||||||
<>
|
<>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="dropbox_unlinked_premium_feature"
|
i18nKey="dropbox_unlinked_premium_feature"
|
||||||
|
@ -299,9 +283,9 @@ function CommonNotification({ notification }: CommonNotificationProps) {
|
||||||
<IEEERetirementBanner id={id} />
|
<IEEERetirementBanner id={id} />
|
||||||
) : templateKey === 'notification_personal_and_group_subscriptions' ? (
|
) : templateKey === 'notification_personal_and_group_subscriptions' ? (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="warning"
|
type="warning"
|
||||||
onDismiss={() => id && handleDismiss(id)}
|
onDismiss={() => id && handleDismiss(id)}
|
||||||
body={
|
content={
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="notification_personal_and_group_subscriptions"
|
i18nKey="notification_personal_and_group_subscriptions"
|
||||||
/* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
|
/* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
|
||||||
|
@ -311,9 +295,9 @@ function CommonNotification({ notification }: CommonNotificationProps) {
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="info"
|
type="info"
|
||||||
onDismiss={() => id && handleDismiss(id)}
|
onDismiss={() => id && handleDismiss(id)}
|
||||||
body={html}
|
content={html}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { Button } from 'react-bootstrap'
|
|
||||||
import Notification from '../notification'
|
import Notification from '../notification'
|
||||||
import Icon from '../../../../../shared/components/icon'
|
import Icon from '../../../../../shared/components/icon'
|
||||||
import getMeta from '../../../../../utils/meta'
|
import getMeta from '../../../../../utils/meta'
|
||||||
|
@ -11,6 +10,7 @@ import {
|
||||||
} from '../../../../../infrastructure/fetch-json'
|
} from '../../../../../infrastructure/fetch-json'
|
||||||
import { UserEmailData } from '../../../../../../../types/user-email'
|
import { UserEmailData } from '../../../../../../../types/user-email'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
|
||||||
const ssoAvailable = ({ samlProviderId, affiliation }: UserEmailData) => {
|
const ssoAvailable = ({ samlProviderId, affiliation }: UserEmailData) => {
|
||||||
const { hasSamlFeature, hasSamlBeta } = getMeta('ol-ExposedSettings')
|
const { hasSamlFeature, hasSamlBeta } = getMeta('ol-ExposedSettings')
|
||||||
|
@ -96,8 +96,8 @@ function ConfirmEmailNotification({ userEmail }: { userEmail: UserEmailData }) {
|
||||||
if (emailHasLicenceAfterConfirming(userEmail) && isOnFreeOrIndividualPlan()) {
|
if (emailHasLicenceAfterConfirming(userEmail) && isOnFreeOrIndividualPlan()) {
|
||||||
return (
|
return (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="info"
|
type="info"
|
||||||
body={
|
content={
|
||||||
<div data-testid="notification-body">
|
<div data-testid="notification-body">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<>
|
<>
|
||||||
|
@ -112,12 +112,6 @@ function ConfirmEmailNotification({ userEmail }: { userEmail: UserEmailData }) {
|
||||||
i18nKey="one_step_away_from_professional_features"
|
i18nKey="one_step_away_from_professional_features"
|
||||||
components={[<strong />]} // eslint-disable-line react/jsx-key
|
components={[<strong />]} // eslint-disable-line react/jsx-key
|
||||||
/>
|
/>
|
||||||
<button
|
|
||||||
className="pull-right btn btn-info btn-sm"
|
|
||||||
onClick={() => handleResendConfirmationEmail(userEmail)}
|
|
||||||
>
|
|
||||||
{t('resend_email')}
|
|
||||||
</button>
|
|
||||||
<br />
|
<br />
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="institution_has_overleaf_subscription"
|
i18nKey="institution_has_overleaf_subscription"
|
||||||
|
@ -133,14 +127,22 @@ function ConfirmEmailNotification({ userEmail }: { userEmail: UserEmailData }) {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
action={
|
||||||
|
<OLButton
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => handleResendConfirmationEmail(userEmail)}
|
||||||
|
>
|
||||||
|
{t('resend_email')}
|
||||||
|
</OLButton>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="warning"
|
type="warning"
|
||||||
body={
|
content={
|
||||||
<div data-testid="pro-notification-body">
|
<div data-testid="pro-notification-body">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<>
|
<>
|
||||||
|
@ -154,13 +156,13 @@ function ConfirmEmailNotification({ userEmail }: { userEmail: UserEmailData }) {
|
||||||
{t('please_confirm_email', {
|
{t('please_confirm_email', {
|
||||||
emailAddress: userEmail.email,
|
emailAddress: userEmail.email,
|
||||||
})}{' '}
|
})}{' '}
|
||||||
<Button
|
<OLButton
|
||||||
bsStyle="link"
|
variant="link"
|
||||||
className="btn-inline-link"
|
|
||||||
onClick={() => handleResendConfirmationEmail(userEmail)}
|
onClick={() => handleResendConfirmationEmail(userEmail)}
|
||||||
|
className="btn-inline-link"
|
||||||
>
|
>
|
||||||
({t('resend_confirmation_email')})
|
{t('resend_confirmation_email')}
|
||||||
</Button>
|
</OLButton>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { Button } from 'react-bootstrap'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import type { Dispatch, SetStateAction } from 'react'
|
import type { Dispatch, SetStateAction } from 'react'
|
||||||
import Notification from '../../notification'
|
import Notification from '../../notification'
|
||||||
import { GroupInvitationStatus } from './hooks/use-group-invitation-notification'
|
import { GroupInvitationStatus } from './hooks/use-group-invitation-notification'
|
||||||
import type { NotificationGroupInvitation } from '../../../../../../../../types/project/dashboard/notification'
|
import type { NotificationGroupInvitation } from '../../../../../../../../types/project/dashboard/notification'
|
||||||
import getMeta from '@/utils/meta'
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
|
||||||
type GroupInvitationCancelIndividualSubscriptionNotificationProps = {
|
type GroupInvitationCancelIndividualSubscriptionNotificationProps = {
|
||||||
setGroupInvitationStatus: Dispatch<SetStateAction<GroupInvitationStatus>>
|
setGroupInvitationStatus: Dispatch<SetStateAction<GroupInvitationStatus>>
|
||||||
|
@ -20,37 +19,32 @@ export default function GroupInvitationCancelIndividualSubscriptionNotification(
|
||||||
notification,
|
notification,
|
||||||
}: GroupInvitationCancelIndividualSubscriptionNotificationProps) {
|
}: GroupInvitationCancelIndividualSubscriptionNotificationProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const newNotificationStyle = getMeta('ol-newNotificationStyle')
|
|
||||||
const {
|
const {
|
||||||
messageOpts: { inviterName },
|
messageOpts: { inviterName },
|
||||||
} = notification
|
} = notification
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="info"
|
type="info"
|
||||||
onDismiss={dismissGroupInviteNotification}
|
onDismiss={dismissGroupInviteNotification}
|
||||||
body={t('invited_to_group_have_individual_subcription', { inviterName })}
|
content={t('invited_to_group_have_individual_subcription', {
|
||||||
|
inviterName,
|
||||||
|
})}
|
||||||
action={
|
action={
|
||||||
<div className="group-invitation-cancel-subscription-notification-buttons">
|
<>
|
||||||
<Button
|
<OLButton
|
||||||
bsStyle={newNotificationStyle ? null : 'info'}
|
variant="secondary"
|
||||||
bsSize="sm"
|
|
||||||
className={newNotificationStyle ? 'me-1 btn-secondary' : 'me-1'}
|
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setGroupInvitationStatus(GroupInvitationStatus.AskToJoin)
|
setGroupInvitationStatus(GroupInvitationStatus.AskToJoin)
|
||||||
}
|
}
|
||||||
|
className="me-1"
|
||||||
>
|
>
|
||||||
{t('not_now')}
|
{t('not_now')}
|
||||||
</Button>
|
</OLButton>
|
||||||
<Button
|
<OLButton variant="secondary" onClick={cancelPersonalSubscription}>
|
||||||
bsStyle={newNotificationStyle ? null : 'info'}
|
|
||||||
className={newNotificationStyle ? 'btn-secondary' : ''}
|
|
||||||
bsSize="sm"
|
|
||||||
onClick={cancelPersonalSubscription}
|
|
||||||
>
|
|
||||||
{t('cancel_my_subscription')}
|
{t('cancel_my_subscription')}
|
||||||
</Button>
|
</OLButton>
|
||||||
</div>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { Button } from 'react-bootstrap'
|
|
||||||
import { useTranslation, Trans } from 'react-i18next'
|
import { useTranslation, Trans } from 'react-i18next'
|
||||||
import Notification from '../../notification'
|
import Notification from '../../notification'
|
||||||
import type { NotificationGroupInvitation } from '../../../../../../../../types/project/dashboard/notification'
|
import type { NotificationGroupInvitation } from '../../../../../../../../types/project/dashboard/notification'
|
||||||
import getMeta from '@/utils/meta'
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
|
||||||
type GroupInvitationNotificationProps = {
|
type GroupInvitationNotificationProps = {
|
||||||
acceptGroupInvite: () => void
|
acceptGroupInvite: () => void
|
||||||
|
@ -18,16 +17,15 @@ export default function GroupInvitationNotificationJoin({
|
||||||
dismissGroupInviteNotification,
|
dismissGroupInviteNotification,
|
||||||
}: GroupInvitationNotificationProps) {
|
}: GroupInvitationNotificationProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const newNotificationStyle = getMeta('ol-newNotificationStyle')
|
|
||||||
const {
|
const {
|
||||||
messageOpts: { inviterName },
|
messageOpts: { inviterName },
|
||||||
} = notification
|
} = notification
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="info"
|
type="info"
|
||||||
onDismiss={dismissGroupInviteNotification}
|
onDismiss={dismissGroupInviteNotification}
|
||||||
body={
|
content={
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="invited_to_group"
|
i18nKey="invited_to_group"
|
||||||
values={{ inviterName }}
|
values={{ inviterName }}
|
||||||
|
@ -40,15 +38,13 @@ export default function GroupInvitationNotificationJoin({
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
action={
|
action={
|
||||||
<Button
|
<OLButton
|
||||||
bsStyle={newNotificationStyle ? null : 'info'}
|
variant="secondary"
|
||||||
bsSize="sm"
|
|
||||||
className={newNotificationStyle ? 'btn-secondary' : 'pull-right'}
|
|
||||||
onClick={acceptGroupInvite}
|
onClick={acceptGroupInvite}
|
||||||
disabled={isAcceptingInvitation}
|
disabled={isAcceptingInvitation}
|
||||||
>
|
>
|
||||||
{t('join_now')}
|
{t('join_now')}
|
||||||
</Button>
|
</OLButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import Notification from '../../notification'
|
import Notification from '../../notification'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Icon from '../../../../../../shared/components/icon'
|
|
||||||
import getMeta from '@/utils/meta'
|
|
||||||
|
|
||||||
type GroupInvitationSuccessfulNotificationProps = {
|
type GroupInvitationSuccessfulNotificationProps = {
|
||||||
hideNotification: () => void
|
hideNotification: () => void
|
||||||
|
@ -11,20 +9,12 @@ export default function GroupInvitationSuccessfulNotification({
|
||||||
hideNotification,
|
hideNotification,
|
||||||
}: GroupInvitationSuccessfulNotificationProps) {
|
}: GroupInvitationSuccessfulNotificationProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const newNotificationStyle = getMeta('ol-newNotificationStyle')
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="success"
|
type="success"
|
||||||
onDismiss={hideNotification}
|
onDismiss={hideNotification}
|
||||||
body={
|
content={t('congratulations_youve_successfully_join_group')}
|
||||||
<>
|
|
||||||
{!newNotificationStyle && (
|
|
||||||
<Icon type="check-circle" fw aria-hidden="true" className="me-1" />
|
|
||||||
)}
|
|
||||||
{t('congratulations_youve_successfully_join_group')}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
import { Fragment } from 'react'
|
import { Fragment } from 'react'
|
||||||
import { useTranslation, Trans } from 'react-i18next'
|
import { useTranslation, Trans } from 'react-i18next'
|
||||||
import { Button } from 'react-bootstrap'
|
|
||||||
import Notification from '../notification'
|
import Notification from '../notification'
|
||||||
import Icon from '../../../../../shared/components/icon'
|
|
||||||
import getMeta from '../../../../../utils/meta'
|
import getMeta from '../../../../../utils/meta'
|
||||||
import useAsyncDismiss from '../hooks/useAsyncDismiss'
|
import useAsyncDismiss from '../hooks/useAsyncDismiss'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
|
||||||
function Institution() {
|
function Institution() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { samlInitPath, appName } = getMeta('ol-ExposedSettings')
|
const { samlInitPath, appName } = getMeta('ol-ExposedSettings')
|
||||||
const notificationsInstitution = getMeta('ol-notificationsInstitution') || []
|
const notificationsInstitution = getMeta('ol-notificationsInstitution') || []
|
||||||
const newNotificationStyle = getMeta('ol-newNotificationStyle')
|
|
||||||
const { handleDismiss } = useAsyncDismiss()
|
const { handleDismiss } = useAsyncDismiss()
|
||||||
|
|
||||||
if (!notificationsInstitution.length) {
|
if (!notificationsInstitution.length) {
|
||||||
|
@ -36,10 +34,9 @@ function Institution() {
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
{templateKey === 'notification_institution_sso_available' && (
|
{templateKey === 'notification_institution_sso_available' && (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="info"
|
type="info"
|
||||||
body={
|
content={
|
||||||
<>
|
<>
|
||||||
{' '}
|
|
||||||
<p>
|
<p>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="can_link_institution_email_acct_to_institution_acct"
|
i18nKey="can_link_institution_email_acct_to_institution_acct"
|
||||||
|
@ -67,22 +64,20 @@ function Institution() {
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
action={
|
action={
|
||||||
<Button
|
<OLButton
|
||||||
bsStyle={newNotificationStyle ? null : 'info'}
|
variant="secondary"
|
||||||
className={newNotificationStyle ? 'btn-secondary' : ''}
|
|
||||||
bsSize="sm"
|
|
||||||
href={`${samlInitPath}?university_id=${institutionId}&auto=/project&email=${email}`}
|
href={`${samlInitPath}?university_id=${institutionId}&auto=/project&email=${email}`}
|
||||||
>
|
>
|
||||||
{t('link_account')}
|
{t('link_account')}
|
||||||
</Button>
|
</OLButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{templateKey === 'notification_institution_sso_linked' && (
|
{templateKey === 'notification_institution_sso_linked' && (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="info"
|
type="info"
|
||||||
onDismiss={() => id && handleDismiss(id)}
|
onDismiss={() => id && handleDismiss(id)}
|
||||||
body={
|
content={
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="account_has_been_link_to_institution_account"
|
i18nKey="account_has_been_link_to_institution_account"
|
||||||
components={{ b: <b /> }}
|
components={{ b: <b /> }}
|
||||||
|
@ -95,15 +90,10 @@ function Institution() {
|
||||||
)}
|
)}
|
||||||
{templateKey === 'notification_institution_sso_non_canonical' && (
|
{templateKey === 'notification_institution_sso_non_canonical' && (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="warning"
|
type="warning"
|
||||||
onDismiss={() => id && handleDismiss(id)}
|
onDismiss={() => id && handleDismiss(id)}
|
||||||
body={
|
content={
|
||||||
<>
|
<>
|
||||||
{!newNotificationStyle && (
|
|
||||||
<>
|
|
||||||
<Icon type="exclamation-triangle" fw />{' '}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="tried_to_log_in_with_email"
|
i18nKey="tried_to_log_in_with_email"
|
||||||
components={{ b: <b /> }}
|
components={{ b: <b /> }}
|
||||||
|
@ -125,9 +115,9 @@ function Institution() {
|
||||||
{templateKey ===
|
{templateKey ===
|
||||||
'notification_institution_sso_already_registered' && (
|
'notification_institution_sso_already_registered' && (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="info"
|
type="info"
|
||||||
onDismiss={() => id && handleDismiss(id)}
|
onDismiss={() => id && handleDismiss(id)}
|
||||||
body={
|
content={
|
||||||
<>
|
<>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="tried_to_register_with_email"
|
i18nKey="tried_to_register_with_email"
|
||||||
|
@ -140,29 +130,22 @@ function Institution() {
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
action={
|
action={
|
||||||
<Button
|
<OLButton
|
||||||
bsStyle={newNotificationStyle ? null : 'info'}
|
variant="secondary"
|
||||||
className={newNotificationStyle ? 'btn-secondary' : ''}
|
|
||||||
bsSize="sm"
|
|
||||||
href="/learn/how-to/Institutional_Login"
|
href="/learn/how-to/Institutional_Login"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
{t('find_out_more')}
|
{t('find_out_more')}
|
||||||
</Button>
|
</OLButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{templateKey === 'notification_institution_sso_error' && (
|
{templateKey === 'notification_institution_sso_error' && (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="danger"
|
type="error"
|
||||||
onDismiss={() => id && handleDismiss(id)}
|
onDismiss={() => id && handleDismiss(id)}
|
||||||
body={
|
content={
|
||||||
<>
|
<>
|
||||||
{!newNotificationStyle && (
|
|
||||||
<>
|
|
||||||
<Icon type="exclamation-triangle" fw />{' '}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{t('generic_something_went_wrong')}.
|
{t('generic_something_went_wrong')}.
|
||||||
<div>
|
<div>
|
||||||
{error?.translatedMessage
|
{error?.translatedMessage
|
||||||
|
|
|
@ -49,10 +49,9 @@ export default function IEEERetirementBanner({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="warning"
|
type="warning"
|
||||||
onDismiss={handleClose}
|
onDismiss={handleClose}
|
||||||
newNotificationStyle
|
content={
|
||||||
body={
|
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="notification_ieee_collabratec_retirement_message"
|
i18nKey="notification_ieee_collabratec_retirement_message"
|
||||||
components={[
|
components={[
|
||||||
|
|
|
@ -1,109 +1,21 @@
|
||||||
import { useState } from 'react'
|
|
||||||
import { Alert, AlertProps } from 'react-bootstrap'
|
|
||||||
import Body from './body'
|
|
||||||
import Action from './action'
|
|
||||||
import Close from '../../../../shared/components/close'
|
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import NewNotification, {
|
import NewNotification from '@/shared/components/notification'
|
||||||
NotificationType,
|
|
||||||
} from '@/shared/components/notification'
|
|
||||||
import getMeta from '@/utils/meta'
|
|
||||||
|
|
||||||
type NotificationProps = {
|
type NotificationProps = Pick<
|
||||||
bsStyle: AlertProps['bsStyle']
|
React.ComponentProps<typeof NewNotification>,
|
||||||
children?: React.ReactNode
|
'type' | 'action' | 'content' | 'onDismiss' | 'className'
|
||||||
body?: React.ReactNode
|
>
|
||||||
action?: React.ReactElement
|
|
||||||
onDismiss?: AlertProps['onDismiss']
|
function Notification({ className, ...props }: NotificationProps) {
|
||||||
className?: string
|
const notificationComponent = (
|
||||||
newNotificationStyle?: boolean
|
<NewNotification isDismissible={props.onDismiss != null} {...props} />
|
||||||
|
)
|
||||||
|
|
||||||
|
return notificationComponent ? (
|
||||||
|
<li className={classnames('notification-entry', className)}>
|
||||||
|
{notificationComponent}
|
||||||
|
</li>
|
||||||
|
) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders either a legacy-styled notification using Boostrap `Alert`, or a new-styled notification using
|
|
||||||
* the shared `Notification` component.
|
|
||||||
*
|
|
||||||
* The content of the notification is provided either with `children` (keeping backwards compatibility),
|
|
||||||
* or a `body` prop (along with an optional `action`).
|
|
||||||
*
|
|
||||||
* When the content is provided via `body` prop the notification is rendered with the new Notification component
|
|
||||||
* if `ol-newNotificationStyle` meta is set to true.
|
|
||||||
*/
|
|
||||||
function Notification({
|
|
||||||
bsStyle,
|
|
||||||
children,
|
|
||||||
onDismiss,
|
|
||||||
className,
|
|
||||||
body,
|
|
||||||
action,
|
|
||||||
newNotificationStyle,
|
|
||||||
...props
|
|
||||||
}: NotificationProps) {
|
|
||||||
newNotificationStyle =
|
|
||||||
newNotificationStyle ?? getMeta('ol-newNotificationStyle')
|
|
||||||
|
|
||||||
const [show, setShow] = useState(true)
|
|
||||||
|
|
||||||
const handleDismiss = () => {
|
|
||||||
if (onDismiss) {
|
|
||||||
onDismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
setShow(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!show) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newNotificationStyle && body) {
|
|
||||||
const newNotificationType = (
|
|
||||||
bsStyle === 'danger' ? 'error' : bsStyle
|
|
||||||
) as NotificationType
|
|
||||||
return (
|
|
||||||
<li className={classnames('notification-entry', className)}>
|
|
||||||
<NewNotification
|
|
||||||
type={newNotificationType}
|
|
||||||
isDismissible={onDismiss != null}
|
|
||||||
onDismiss={handleDismiss}
|
|
||||||
content={body as React.ReactElement}
|
|
||||||
action={action}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body) {
|
|
||||||
return (
|
|
||||||
<li className={classnames('notification-entry', className)} {...props}>
|
|
||||||
<Alert bsStyle={bsStyle}>
|
|
||||||
<Body>{body}</Body>
|
|
||||||
{action && <Action>{action}</Action>}
|
|
||||||
{onDismiss ? (
|
|
||||||
<div className="notification-close">
|
|
||||||
<Close onDismiss={handleDismiss} />
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</Alert>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<li className={classnames('notification-entry', className)} {...props}>
|
|
||||||
<Alert bsStyle={bsStyle}>
|
|
||||||
{children}
|
|
||||||
{onDismiss ? (
|
|
||||||
<div className="notification-close">
|
|
||||||
<Close onDismiss={handleDismiss} />
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</Alert>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Notification.Body = Body
|
|
||||||
Notification.Action = Action
|
|
||||||
|
|
||||||
export default Notification
|
export default Notification
|
||||||
|
|
|
@ -10,7 +10,6 @@ import getMeta from '../../../../utils/meta'
|
||||||
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
|
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
|
||||||
import customLocalStorage from '../../../../infrastructure/local-storage'
|
import customLocalStorage from '../../../../infrastructure/local-storage'
|
||||||
import { sendMB } from '../../../../infrastructure/event-tracking'
|
import { sendMB } from '../../../../infrastructure/event-tracking'
|
||||||
import classNames from 'classnames'
|
|
||||||
import GeoBanners from './geo-banners'
|
import GeoBanners from './geo-banners'
|
||||||
import AccessibilitySurveyBanner from './accessibility-survey-banner'
|
import AccessibilitySurveyBanner from './accessibility-survey-banner'
|
||||||
|
|
||||||
|
@ -23,7 +22,6 @@ const EnrollmentNotification: JSXElementConstructor<{
|
||||||
}> = enrollmentNotificationModule?.import.default
|
}> = enrollmentNotificationModule?.import.default
|
||||||
|
|
||||||
function UserNotifications() {
|
function UserNotifications() {
|
||||||
const newNotificationStyle = getMeta('ol-newNotificationStyle')
|
|
||||||
const groupSubscriptionsPendingEnrollment =
|
const groupSubscriptionsPendingEnrollment =
|
||||||
getMeta('ol-groupSubscriptionsPendingEnrollment') || []
|
getMeta('ol-groupSubscriptionsPendingEnrollment') || []
|
||||||
const user = getMeta('ol-user')
|
const user = getMeta('ol-user')
|
||||||
|
@ -54,11 +52,7 @@ function UserNotifications() {
|
||||||
const [dismissedWritefull, setDismissedWritefull] = useState(false)
|
const [dismissedWritefull, setDismissedWritefull] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="user-notifications notification-list">
|
||||||
className={classNames('user-notifications', {
|
|
||||||
'notification-list': newNotificationStyle,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<ul className="list-unstyled">
|
<ul className="list-unstyled">
|
||||||
{EnrollmentNotification &&
|
{EnrollmentNotification &&
|
||||||
groupSubscriptionsPendingEnrollment.map(subscription => (
|
groupSubscriptionsPendingEnrollment.map(subscription => (
|
||||||
|
|
|
@ -3,6 +3,7 @@ import Notification from './notification'
|
||||||
import { sendMB } from '@/infrastructure/event-tracking'
|
import { sendMB } from '@/infrastructure/event-tracking'
|
||||||
import customLocalStorage from '@/infrastructure/local-storage'
|
import customLocalStorage from '@/infrastructure/local-storage'
|
||||||
import WritefullLogo from '@/shared/svgs/writefull-logo'
|
import WritefullLogo from '@/shared/svgs/writefull-logo'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
|
||||||
const eventSegmentation = {
|
const eventSegmentation = {
|
||||||
location: 'dashboard-banner',
|
location: 'dashboard-banner',
|
||||||
|
@ -33,10 +34,9 @@ function WritefullPremiumPromoBanner({
|
||||||
return (
|
return (
|
||||||
<div data-testid="writefull-premium-promo-banner">
|
<div data-testid="writefull-premium-promo-banner">
|
||||||
<Notification
|
<Notification
|
||||||
bsStyle="info"
|
type="info"
|
||||||
newNotificationStyle
|
|
||||||
onDismiss={handleClose}
|
onDismiss={handleClose}
|
||||||
body={
|
content={
|
||||||
<>
|
<>
|
||||||
Enjoying Writefull? Get <strong>10% off Writefull Premium</strong>,
|
Enjoying Writefull? Get <strong>10% off Writefull Premium</strong>,
|
||||||
giving you access to TeXGPT—AI assistance to generate LaTeX code.
|
giving you access to TeXGPT—AI assistance to generate LaTeX code.
|
||||||
|
@ -44,8 +44,8 @@ function WritefullPremiumPromoBanner({
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
action={
|
action={
|
||||||
<a
|
<OLButton
|
||||||
className="btn btn-secondary btn-sm"
|
variant="secondary"
|
||||||
href="https://my.writefull.com/overleaf-invite?code=OVERLEAF10&redirect=plans"
|
href="https://my.writefull.com/overleaf-invite?code=OVERLEAF10&redirect=plans"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
|
@ -55,7 +55,7 @@ function WritefullPremiumPromoBanner({
|
||||||
>
|
>
|
||||||
<WritefullLogo width="16" height="16" />{' '}
|
<WritefullLogo width="16" height="16" />{' '}
|
||||||
<span>Get Writefull Premium</span>
|
<span>Get Writefull Premium</span>
|
||||||
</a>
|
</OLButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { SplitTestProvider } from '@/shared/context/split-test-context'
|
||||||
import OLCol from '@/features/ui/components/ol/ol-col'
|
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
import Notification from '@/shared/components/notification'
|
||||||
|
|
||||||
function ProjectListRoot() {
|
function ProjectListRoot() {
|
||||||
const { isReady } = useWaitForI18n()
|
const { isReady } = useWaitForI18n()
|
||||||
|
@ -127,7 +128,12 @@ function ProjectListPageContent() {
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<div className="project-list-sidebar-survey-wrapper visible-xs">
|
<div
|
||||||
|
className={classnames(
|
||||||
|
'project-list-sidebar-survey-wrapper',
|
||||||
|
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||||
|
)}
|
||||||
|
>
|
||||||
<SurveyWidget />
|
<SurveyWidget />
|
||||||
</div>
|
</div>
|
||||||
<div className="visible-xs mt-1">
|
<div className="visible-xs mt-1">
|
||||||
|
@ -199,10 +205,12 @@ function DashApiError() {
|
||||||
xs={{ span: 8, offset: 2 }}
|
xs={{ span: 8, offset: 2 }}
|
||||||
bs3Props={{ xs: 8, xsOffset: 2 }}
|
bs3Props={{ xs: 8, xsOffset: 2 }}
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
className="text-center"
|
|
||||||
>
|
>
|
||||||
<div className="alert alert-danger">
|
<div className="notification-list">
|
||||||
{t('generic_something_went_wrong')}
|
<Notification
|
||||||
|
content={t('generic_something_went_wrong')}
|
||||||
|
type="error"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</OLCol>
|
</OLCol>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import usePersistedState from '../../../shared/hooks/use-persisted-state'
|
||||||
import getMeta from '../../../utils/meta'
|
import getMeta from '../../../utils/meta'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import Close from '@/shared/components/close'
|
import Close from '@/shared/components/close'
|
||||||
|
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||||
|
|
||||||
export default function SurveyWidget() {
|
export default function SurveyWidget() {
|
||||||
const survey = getMeta('ol-survey')
|
const survey = getMeta('ol-survey')
|
||||||
|
@ -21,7 +22,13 @@ export default function SurveyWidget() {
|
||||||
return (
|
return (
|
||||||
<div className="user-notifications">
|
<div className="user-notifications">
|
||||||
<div className="notification-entry">
|
<div className="notification-entry">
|
||||||
<div role="alert" className="alert alert-info-alt">
|
<div
|
||||||
|
role="alert"
|
||||||
|
className={bsVersion({
|
||||||
|
bs3: 'alert alert-info-alt',
|
||||||
|
bs5: 'survey-notification',
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className="notification-body">
|
<div className="notification-body">
|
||||||
{survey.preText}
|
{survey.preText}
|
||||||
<a
|
<a
|
||||||
|
|
|
@ -28,6 +28,8 @@ export function bs3ButtonProps(props: ButtonProps) {
|
||||||
disabled: props.isLoading || props.disabled,
|
disabled: props.isLoading || props.disabled,
|
||||||
form: props.form,
|
form: props.form,
|
||||||
href: props.href,
|
href: props.href,
|
||||||
|
target: props.target,
|
||||||
|
rel: props.rel,
|
||||||
onClick: props.onClick,
|
onClick: props.onClick,
|
||||||
type: props.type,
|
type: props.type,
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ export type ButtonProps = {
|
||||||
form?: string
|
form?: string
|
||||||
leadingIcon?: string
|
leadingIcon?: string
|
||||||
href?: string
|
href?: string
|
||||||
|
target?: string
|
||||||
|
rel?: string
|
||||||
isLoading?: boolean
|
isLoading?: boolean
|
||||||
onClick?: MouseEventHandler<HTMLButtonElement>
|
onClick?: MouseEventHandler<HTMLButtonElement>
|
||||||
size?: 'small' | 'default' | 'large'
|
size?: 'small' | 'default' | 'large'
|
||||||
|
|
|
@ -15,7 +15,7 @@ export type NotificationProps = {
|
||||||
ariaLive?: 'polite' | 'off' | 'assertive'
|
ariaLive?: 'polite' | 'off' | 'assertive'
|
||||||
className?: string
|
className?: string
|
||||||
content: React.ReactNode
|
content: React.ReactNode
|
||||||
customIcon?: React.ReactElement
|
customIcon?: React.ReactElement | null
|
||||||
disclaimer?: React.ReactElement | string
|
disclaimer?: React.ReactElement | string
|
||||||
isDismissible?: boolean
|
isDismissible?: boolean
|
||||||
isActionBelowContent?: boolean
|
isActionBelowContent?: boolean
|
||||||
|
@ -78,6 +78,8 @@ function Notification({
|
||||||
if (onDismiss) onDismiss()
|
if (onDismiss) onDismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return null
|
||||||
|
|
||||||
if (!show) {
|
if (!show) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -89,7 +91,9 @@ function Notification({
|
||||||
role="alert"
|
role="alert"
|
||||||
id={id}
|
id={id}
|
||||||
>
|
>
|
||||||
<NotificationIcon notificationType={type} customIcon={customIcon} />
|
{customIcon !== null && (
|
||||||
|
<NotificationIcon notificationType={type} customIcon={customIcon} />
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="notification-content-and-cta">
|
<div className="notification-content-and-cta">
|
||||||
<div className="notification-content">
|
<div className="notification-content">
|
||||||
|
|
|
@ -133,7 +133,6 @@ export interface Meta {
|
||||||
'ol-memberGroupSubscriptions': MemberGroupSubscription[]
|
'ol-memberGroupSubscriptions': MemberGroupSubscription[]
|
||||||
'ol-memberOfSSOEnabledGroups': GroupSSOLinkingStatus[]
|
'ol-memberOfSSOEnabledGroups': GroupSSOLinkingStatus[]
|
||||||
'ol-members': MinimalUser[]
|
'ol-members': MinimalUser[]
|
||||||
'ol-newNotificationStyle': boolean
|
|
||||||
'ol-no-single-dollar': boolean
|
'ol-no-single-dollar': boolean
|
||||||
'ol-notifications': NotificationType[]
|
'ol-notifications': NotificationType[]
|
||||||
'ol-notificationsInstitution': InstitutionType[]
|
'ol-notificationsInstitution': InstitutionType[]
|
||||||
|
|
|
@ -25,7 +25,6 @@ export const ProjectInvite = (args: any) => {
|
||||||
token: 'abcdef',
|
token: 'abcdef',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -44,7 +43,6 @@ export const ProjectInviteNetworkError = (args: any) => {
|
||||||
token: 'abcdef',
|
token: 'abcdef',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -58,7 +56,6 @@ export const Wfh2020UpgradeOffer = (args: any) => {
|
||||||
_id: 1,
|
_id: 1,
|
||||||
templateKey: 'wfh_2020_upgrade_offer',
|
templateKey: 'wfh_2020_upgrade_offer',
|
||||||
})
|
})
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -77,7 +74,6 @@ export const IPMatchedAffiliationSsoEnabled = (args: any) => {
|
||||||
ssoEnabled: true,
|
ssoEnabled: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -96,7 +92,6 @@ export const IPMatchedAffiliationSsoDisabled = (args: any) => {
|
||||||
ssoEnabled: false,
|
ssoEnabled: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -113,7 +108,6 @@ export const TpdsFileLimit = (args: any) => {
|
||||||
projectName: 'Abc Project',
|
projectName: 'Abc Project',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -130,7 +124,6 @@ export const DropBoxDuplicateProjectNames = (args: any) => {
|
||||||
projectName: 'Abc Project',
|
projectName: 'Abc Project',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -144,7 +137,6 @@ export const DropBoxUnlinkedDueToLapsedReconfirmation = (args: any) => {
|
||||||
_id: 1,
|
_id: 1,
|
||||||
templateKey: 'notification_dropbox_unlinked_due_to_lapsed_reconfirmation',
|
templateKey: 'notification_dropbox_unlinked_due_to_lapsed_reconfirmation',
|
||||||
})
|
})
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -161,7 +153,6 @@ export const NotificationGroupInvitation = (args: any) => {
|
||||||
inviterName: 'John Doe',
|
inviterName: 'John Doe',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -178,7 +169,6 @@ export const NotificationGroupInvitationCancelSubscription = (args: any) => {
|
||||||
inviterName: 'John Doe',
|
inviterName: 'John Doe',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
window.metaAttributesCache.set('ol-hasIndividualRecurlySubscription', true)
|
window.metaAttributesCache.set('ol-hasIndividualRecurlySubscription', true)
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
|
@ -190,7 +180,6 @@ export const NotificationGroupInvitationCancelSubscription = (args: any) => {
|
||||||
export const NonSpecificMessage = (args: any) => {
|
export const NonSpecificMessage = (args: any) => {
|
||||||
useFetchMock(commonSetupMocks)
|
useFetchMock(commonSetupMocks)
|
||||||
setCommonMeta({ _id: 1, html: 'Non specific message' })
|
setCommonMeta({ _id: 1, html: 'Non specific message' })
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -204,7 +193,6 @@ export const InstitutionSsoAvailable = (args: any) => {
|
||||||
_id: 1,
|
_id: 1,
|
||||||
templateKey: 'notification_institution_sso_available',
|
templateKey: 'notification_institution_sso_available',
|
||||||
})
|
})
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -218,7 +206,6 @@ export const InstitutionSsoLinked = (args: any) => {
|
||||||
_id: 1,
|
_id: 1,
|
||||||
templateKey: 'notification_institution_sso_linked',
|
templateKey: 'notification_institution_sso_linked',
|
||||||
})
|
})
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -232,7 +219,6 @@ export const InstitutionSsoNonCanonical = (args: any) => {
|
||||||
_id: 1,
|
_id: 1,
|
||||||
templateKey: 'notification_institution_sso_non_canonical',
|
templateKey: 'notification_institution_sso_non_canonical',
|
||||||
})
|
})
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -246,7 +232,6 @@ export const InstitutionSsoAlreadyRegistered = (args: any) => {
|
||||||
_id: 1,
|
_id: 1,
|
||||||
templateKey: 'notification_institution_sso_already_registered',
|
templateKey: 'notification_institution_sso_already_registered',
|
||||||
})
|
})
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -264,7 +249,6 @@ export const InstitutionSsoError = (args: any) => {
|
||||||
tryAgain: true,
|
tryAgain: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -275,7 +259,6 @@ export const InstitutionSsoError = (args: any) => {
|
||||||
export const ResendConfirmationEmail = (args: any) => {
|
export const ResendConfirmationEmail = (args: any) => {
|
||||||
useFetchMock(reconfirmationSetupMocks)
|
useFetchMock(reconfirmationSetupMocks)
|
||||||
setReconfirmationMeta()
|
setReconfirmationMeta()
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -286,7 +269,6 @@ export const ResendConfirmationEmail = (args: any) => {
|
||||||
export const ResendConfirmationEmailNetworkError = (args: any) => {
|
export const ResendConfirmationEmailNetworkError = (args: any) => {
|
||||||
useFetchMock(errorsMocks)
|
useFetchMock(errorsMocks)
|
||||||
setReconfirmationMeta()
|
setReconfirmationMeta()
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -300,7 +282,6 @@ export const ReconfirmAffiliation = (args: any) => {
|
||||||
window.metaAttributesCache.set('ol-allInReconfirmNotificationPeriods', [
|
window.metaAttributesCache.set('ol-allInReconfirmNotificationPeriods', [
|
||||||
fakeReconfirmationUsersData,
|
fakeReconfirmationUsersData,
|
||||||
])
|
])
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -314,7 +295,6 @@ export const ReconfirmAffiliationNetworkError = (args: any) => {
|
||||||
window.metaAttributesCache.set('ol-allInReconfirmNotificationPeriods', [
|
window.metaAttributesCache.set('ol-allInReconfirmNotificationPeriods', [
|
||||||
fakeReconfirmationUsersData,
|
fakeReconfirmationUsersData,
|
||||||
])
|
])
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
@ -326,7 +306,6 @@ export const ReconfirmedAffiliationSuccess = (args: any) => {
|
||||||
useFetchMock(reconfirmAffiliationSetupMocks)
|
useFetchMock(reconfirmAffiliationSetupMocks)
|
||||||
setReconfirmAffiliationMeta()
|
setReconfirmAffiliationMeta()
|
||||||
window.metaAttributesCache.set('ol-userEmails', [fakeReconfirmationUsersData])
|
window.metaAttributesCache.set('ol-userEmails', [fakeReconfirmationUsersData])
|
||||||
window.metaAttributesCache.set('ol-newNotificationStyle', true)
|
|
||||||
return (
|
return (
|
||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<UserNotifications {...args} />
|
<UserNotifications {...args} />
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
@import 'account-settings';
|
@import 'account-settings';
|
||||||
@import 'project-list';
|
@import 'project-list';
|
||||||
|
@import 'sidebar-v2-dash-pane';
|
||||||
|
|
|
@ -168,3 +168,54 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.survey-notification {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: var(--spacing-06);
|
||||||
|
background-color: var(--bg-dark-tertiary);
|
||||||
|
border-color: transparent;
|
||||||
|
color: var(--neutral-20);
|
||||||
|
box-shadow: 2px 4px 6px rgba(0, 0, 0, 0.25);
|
||||||
|
border-radius: var(--border-radius-base);
|
||||||
|
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.close {
|
||||||
|
@extend .text-white;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list-sidebar-survey-wrapper {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
.survey-notification {
|
||||||
|
font-size: var(--font-size-02);
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-down(md) {
|
||||||
|
position: static;
|
||||||
|
margin-top: var(--spacing-05);
|
||||||
|
|
||||||
|
.survey-notification {
|
||||||
|
font-size: unset;
|
||||||
|
|
||||||
|
.project-list-sidebar-survey-link {
|
||||||
|
display: block;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 48px;
|
||||||
|
min-height: 48px;
|
||||||
|
padding-top: var(--spacing-07);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
.project-list-sidebar-survey-link {
|
||||||
|
@extend .text-white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-close-button-style button {
|
||||||
|
&:hover {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
}
|
|
@ -67,10 +67,10 @@ describe('<ProjectsActionModal />', function () {
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const alerts = screen.getAllByRole('alert')
|
const alerts = screen.getAllByRole('alert')
|
||||||
expect(alerts.length).to.equal(2)
|
expect(alerts.length).to.equal(2)
|
||||||
expect(alerts[0].textContent).to.equal(
|
expect(alerts[0].textContent).to.contain(
|
||||||
`${projectsData[2].name}Something went wrong. Please try again.`
|
`${projectsData[2].name}Something went wrong. Please try again.`
|
||||||
)
|
)
|
||||||
expect(alerts[1].textContent).to.equal(
|
expect(alerts[1].textContent).to.contain(
|
||||||
`${projectsData[3].name}Something went wrong. Please try again.`
|
`${projectsData[3].name}Something went wrong. Please try again.`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue