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