mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 09:09:36 +02:00
[web] display global off treatment in settings modal (#32942)
* display global disabled state * show loading indicator while project notification preferences load GitOrigin-RevId: d7e905aaa3fc7b15b54bf99caeabf60c1e5d8050
This commit is contained in:
@@ -266,6 +266,7 @@
|
||||
"change_primary_email_address_instructions": "",
|
||||
"change_project_owner": "",
|
||||
"change_role_and_department": "",
|
||||
"change_settings": "",
|
||||
"change_the_ownership_of_your_personal_projects": "",
|
||||
"change_to_group_plan": "",
|
||||
"change_to_this_plan": "",
|
||||
@@ -1435,6 +1436,7 @@
|
||||
"project_name": "",
|
||||
"project_not_linked_to_github": "",
|
||||
"project_notifications": "",
|
||||
"project_notifications_muted_description": "",
|
||||
"project_ownership_transfer_confirmation_1": "",
|
||||
"project_ownership_transfer_confirmation_2": "",
|
||||
"project_renamed_or_deleted": "",
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import RadioButtonSetting, { RadioOption } from '../radio-button-setting'
|
||||
import {
|
||||
NotificationLevel,
|
||||
SettableNotificationLevel,
|
||||
useProjectNotificationPreferences,
|
||||
} from '../../hooks/use-project-notification-preferences'
|
||||
import BetaBadgeIcon from '@/shared/components/beta-badge-icon'
|
||||
import LoadingSpinner from '@/shared/components/loading-spinner'
|
||||
|
||||
export default function ProjectNotificationsSetting() {
|
||||
const { t } = useTranslation()
|
||||
const { notificationLevel, setNotificationLevel, isLoading } =
|
||||
useProjectNotificationPreferences()
|
||||
|
||||
const options: Array<RadioOption<NotificationLevel>> = [
|
||||
if (isLoading) {
|
||||
return <LoadingSpinner loadingText={t('loading')} />
|
||||
}
|
||||
|
||||
const options: Array<RadioOption<SettableNotificationLevel>> = [
|
||||
{
|
||||
value: 'all',
|
||||
label: t('all_project_activity'),
|
||||
@@ -31,38 +36,54 @@ export default function ProjectNotificationsSetting() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<RadioButtonSetting
|
||||
id="projectNotifications"
|
||||
options={options}
|
||||
value={isLoading ? undefined : notificationLevel}
|
||||
onChange={setNotificationLevel}
|
||||
/>
|
||||
<div className="global-notifications-link">
|
||||
<a
|
||||
href="/user/notification-preferences"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('manage_overleaf_email_preferences')}
|
||||
</a>
|
||||
</div>
|
||||
<div className="project-notifications-beta-note">
|
||||
<BetaBadgeIcon />
|
||||
<div>
|
||||
<span className="beta-note-text">
|
||||
{t('email_notifications_are_currently_in_beta')}{' '}
|
||||
{t('these_settings_might_change_in_the_future')}{' '}
|
||||
</span>
|
||||
{/* TODO: update forms link */}
|
||||
{notificationLevel === 'global-off' ? (
|
||||
<div className="ide-setting-description">
|
||||
{t('project_notifications_muted_description')}{' '}
|
||||
<a
|
||||
href="/user/notification-preferences"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://forms.gle/"
|
||||
>
|
||||
{t('give_feedback')}
|
||||
{t('change_settings')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<RadioButtonSetting
|
||||
id="projectNotifications"
|
||||
options={options}
|
||||
value={notificationLevel}
|
||||
onChange={setNotificationLevel}
|
||||
/>
|
||||
|
||||
<div className="global-notifications-link">
|
||||
<a
|
||||
href="/user/notification-preferences"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('manage_overleaf_email_preferences')}
|
||||
</a>
|
||||
</div>
|
||||
<div className="project-notifications-beta-note">
|
||||
<BetaBadgeIcon />
|
||||
<div>
|
||||
<span className="ide-setting-description">
|
||||
{t('email_notifications_are_currently_in_beta')}{' '}
|
||||
{t('these_settings_might_change_in_the_future')}{' '}
|
||||
</span>
|
||||
{/* TODO: update forms link */}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://forms.gle/"
|
||||
>
|
||||
{t('give_feedback')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,17 +2,21 @@ import { useCallback, useEffect, useState } from 'react'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { getJSON, postJSON } from '@/infrastructure/fetch-json'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import type { NotificationPreferencesSchema } from '../../../../../modules/notifications/app/src/types.js'
|
||||
import type {
|
||||
GlobalNotificationPreferencesSchema,
|
||||
NotificationPreferencesSchema,
|
||||
} from '../../../../../modules/notifications/app/src/types.js'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
|
||||
|
||||
export type NotificationLevel = 'all' | 'replies' | 'off'
|
||||
export type SettableNotificationLevel = 'all' | 'replies' | 'off'
|
||||
export type NotificationLevel = SettableNotificationLevel | 'global-off'
|
||||
|
||||
/**
|
||||
* Map UI notification level to backend preferences
|
||||
*/
|
||||
function levelToPreferences(
|
||||
level: NotificationLevel
|
||||
level: SettableNotificationLevel
|
||||
): NotificationPreferencesSchema {
|
||||
switch (level) {
|
||||
case 'all':
|
||||
@@ -55,8 +59,12 @@ function levelToPreferences(
|
||||
* Map backend preferences to UI notification level
|
||||
*/
|
||||
function preferencesToLevel(
|
||||
preferences: NotificationPreferencesSchema
|
||||
preferences: GlobalNotificationPreferencesSchema
|
||||
): NotificationLevel {
|
||||
if (preferences.muteAllNotifications) {
|
||||
return 'global-off'
|
||||
}
|
||||
|
||||
// If all notifications are off
|
||||
if (
|
||||
!preferences.commentOnOwnProject &&
|
||||
@@ -92,7 +100,7 @@ export function useProjectNotificationPreferences() {
|
||||
|
||||
// Load preferences on mount
|
||||
useEffect(() => {
|
||||
getJSON<NotificationPreferencesSchema>(
|
||||
getJSON<GlobalNotificationPreferencesSchema>(
|
||||
`/notifications/preferences/project/${projectId}`
|
||||
)
|
||||
.then(prefs => {
|
||||
@@ -103,7 +111,7 @@ export function useProjectNotificationPreferences() {
|
||||
}, [projectId])
|
||||
|
||||
const setLevel = useCallback(
|
||||
(level: NotificationLevel) => {
|
||||
(level: SettableNotificationLevel) => {
|
||||
setNotificationLevel(level)
|
||||
const preferences = levelToPreferences(level)
|
||||
sendMB('setting-changed', {
|
||||
|
||||
@@ -116,7 +116,3 @@
|
||||
align-items: center;
|
||||
gap: var(--spacing-05);
|
||||
}
|
||||
|
||||
.beta-note-text {
|
||||
color: var(--content-secondary);
|
||||
}
|
||||
|
||||
@@ -351,6 +351,7 @@
|
||||
"change_primary_email_address_instructions": "To change your primary email, please add your new primary email address first (by clicking <0>Add another email</0>) and confirm it. Then click the <0>Make primary</0> button. <1>Learn more about managing your __appName__ emails</1>.",
|
||||
"change_project_owner": "Change project owner",
|
||||
"change_role_and_department": "Change role and department",
|
||||
"change_settings": "Change settings",
|
||||
"change_the_ownership_of_your_personal_projects": "Change the ownership of your personal projects to the new account. <0>Find out how to change project owner.</0>",
|
||||
"change_to_group_plan": "Change to a group plan",
|
||||
"change_to_this_plan": "Change to this plan",
|
||||
@@ -1924,6 +1925,7 @@
|
||||
"project_name": "Project name",
|
||||
"project_not_linked_to_github": "This project is not linked to a GitHub repository. You can create a repository for it in GitHub:",
|
||||
"project_notifications": "Project notifications",
|
||||
"project_notifications_muted_description": "You are not receiving any notifications, as you have disabled all project notifications.",
|
||||
"project_ownership_transfer_confirmation_1": "Are you sure you want to make <0>__user__</0> the owner of <1>__project__</1>?",
|
||||
"project_ownership_transfer_confirmation_2": "This action cannot be undone. The new owner will be notified and will be able to change project access settings (including removing your own access).",
|
||||
"project_renamed_or_deleted": "Project Renamed or Deleted",
|
||||
|
||||
@@ -30,6 +30,18 @@ const repliesOnlyPreferences = {
|
||||
repliesOnParticipatingThread: true,
|
||||
}
|
||||
|
||||
const globallyMutedPreferences = {
|
||||
trackedChangesOnOwnProject: false,
|
||||
trackedChangesOnInvitedProject: false,
|
||||
commentOnOwnProject: false,
|
||||
commentOnInvitedProject: false,
|
||||
repliesOnOwnProject: false,
|
||||
repliesOnInvitedProject: false,
|
||||
repliesOnAuthoredThread: false,
|
||||
repliesOnParticipatingThread: false,
|
||||
muteAllNotifications: true,
|
||||
}
|
||||
|
||||
const allNotificationsOff = {
|
||||
trackedChangesOnOwnProject: false,
|
||||
trackedChangesOnInvitedProject: false,
|
||||
@@ -56,6 +68,16 @@ describe('<ProjectNotificationsSetting />', function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows loading indicator while preferences are loading', async function () {
|
||||
fetchMock.get(preferencesUrl, new Promise(() => {}))
|
||||
|
||||
renderComponent()
|
||||
|
||||
expect(await screen.findByRole('status')).to.exist
|
||||
expect(screen.queryByLabelText('All project activity', { exact: false })).to
|
||||
.not.exist
|
||||
})
|
||||
|
||||
it('selects "All project activity" when all notifications are on', async function () {
|
||||
fetchMock.get(preferencesUrl, allNotificationsOn)
|
||||
|
||||
@@ -140,6 +162,31 @@ describe('<ProjectNotificationsSetting />', function () {
|
||||
).to.be.false
|
||||
})
|
||||
|
||||
it('shows muted message and hides radio buttons when muteAllNotifications is true', async function () {
|
||||
fetchMock.get(preferencesUrl, globallyMutedPreferences)
|
||||
|
||||
renderComponent()
|
||||
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(
|
||||
screen.getByText(
|
||||
'You are not receiving any notifications, as you have disabled all project notifications.',
|
||||
{ exact: false }
|
||||
)
|
||||
).to.exist
|
||||
)
|
||||
expect(
|
||||
screen.getByRole('link', { name: 'Change settings' }).getAttribute('href')
|
||||
).to.equal('/user/notification-preferences')
|
||||
expect(screen.queryByLabelText('All project activity', { exact: false })).to
|
||||
.not.exist
|
||||
expect(
|
||||
screen.queryByLabelText('Replies to your activity only', { exact: false })
|
||||
).to.not.exist
|
||||
expect(screen.queryByLabelText('Off', { exact: false })).to.not.exist
|
||||
})
|
||||
|
||||
it('POSTs "replies" preferences when "Replies to your activity only" is selected', async function () {
|
||||
fetchMock.get(preferencesUrl, allNotificationsOn)
|
||||
const saveMock = fetchMock.post(preferencesUrl, { status: 200 })
|
||||
|
||||
Reference in New Issue
Block a user