diff --git a/.yarn/cache/@standardnotes-settings-npm-1.19.1-5a4afbfb46-d99d49d440.zip b/.yarn/cache/@standardnotes-settings-npm-1.20.0-5fa566e627-6e8c03107e.zip similarity index 69% rename from .yarn/cache/@standardnotes-settings-npm-1.19.1-5a4afbfb46-d99d49d440.zip rename to .yarn/cache/@standardnotes-settings-npm-1.20.0-5fa566e627-6e8c03107e.zip index 81d2516e5..f9f50f13a 100644 Binary files a/.yarn/cache/@standardnotes-settings-npm-1.19.1-5a4afbfb46-d99d49d440.zip and b/.yarn/cache/@standardnotes-settings-npm-1.20.0-5fa566e627-6e8c03107e.zip differ diff --git a/packages/snjs/lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes.ts b/packages/snjs/lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes.ts index be382d828..a541bad41 100644 --- a/packages/snjs/lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes.ts +++ b/packages/snjs/lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes.ts @@ -8,7 +8,9 @@ export class GetRecoveryCodes implements UseCaseInterface { constructor(private authClient: AuthClientInterface, private settingsClient: SettingsClientInterface) {} async execute(): Promise> { - const existingRecoveryCodes = await this.settingsClient.getSetting(SettingName.RecoveryCodes) + const existingRecoveryCodes = await this.settingsClient.getSetting( + SettingName.create(SettingName.NAMES.RecoveryCodes).getValue(), + ) if (existingRecoveryCodes !== undefined) { return Result.ok(existingRecoveryCodes) } diff --git a/packages/snjs/lib/Services/Api/ApiService.ts b/packages/snjs/lib/Services/Api/ApiService.ts index 1a28bf66c..53a184c75 100644 --- a/packages/snjs/lib/Services/Api/ApiService.ts +++ b/packages/snjs/lib/Services/Api/ApiService.ts @@ -1,6 +1,5 @@ import { FeatureDescription } from '@standardnotes/features' import { joinPaths } from '@standardnotes/utils' -import { SettingName, SubscriptionSettingName } from '@standardnotes/settings' import { AbstractService, ApiServiceInterface, @@ -568,31 +567,25 @@ export class SNApiService }) } - async getSetting(userUuid: UuidString, settingName: SettingName): Promise> { + async getSetting(userUuid: UuidString, settingName: string): Promise> { return await this.tokenRefreshableRequest({ verb: HttpVerb.Get, - url: joinPaths(this.host, Paths.v1.setting(userUuid, settingName.toLowerCase() as SettingName)), + url: joinPaths(this.host, Paths.v1.setting(userUuid, settingName.toLowerCase())), authentication: this.getSessionAccessToken(), fallbackErrorMessage: API_MESSAGE_FAILED_GET_SETTINGS, }) } - async getSubscriptionSetting( - userUuid: UuidString, - settingName: SubscriptionSettingName, - ): Promise> { + async getSubscriptionSetting(userUuid: UuidString, settingName: string): Promise> { return await this.tokenRefreshableRequest({ verb: HttpVerb.Get, - url: joinPaths( - this.host, - Paths.v1.subscriptionSetting(userUuid, settingName.toLowerCase() as SubscriptionSettingName), - ), + url: joinPaths(this.host, Paths.v1.subscriptionSetting(userUuid, settingName.toLowerCase())), authentication: this.getSessionAccessToken(), fallbackErrorMessage: API_MESSAGE_FAILED_GET_SETTINGS, }) } - async deleteSetting(userUuid: UuidString, settingName: SettingName): Promise> { + async deleteSetting(userUuid: UuidString, settingName: string): Promise> { return this.tokenRefreshableRequest({ verb: HttpVerb.Delete, url: joinPaths(this.host, Paths.v1.setting(userUuid, settingName)), diff --git a/packages/snjs/lib/Services/Api/Paths.ts b/packages/snjs/lib/Services/Api/Paths.ts index 96a8fe3e8..331987108 100644 --- a/packages/snjs/lib/Services/Api/Paths.ts +++ b/packages/snjs/lib/Services/Api/Paths.ts @@ -1,5 +1,3 @@ -import { SettingName, SubscriptionSettingName } from '@standardnotes/settings' - const FilesPaths = { closeUploadSession: '/v1/files/upload/close-session', createFileValetToken: '/v1/files/valet-tokens', @@ -31,8 +29,8 @@ const ItemsPaths = { const SettingsPaths = { settings: (userUuid: string) => `/v1/users/${userUuid}/settings`, - setting: (userUuid: string, settingName: SettingName) => `/v1/users/${userUuid}/settings/${settingName}`, - subscriptionSetting: (userUuid: string, settingName: SubscriptionSettingName) => + setting: (userUuid: string, settingName: string) => `/v1/users/${userUuid}/settings/${settingName}`, + subscriptionSetting: (userUuid: string, settingName: string) => `/v1/users/${userUuid}/subscription-settings/${settingName}`, } diff --git a/packages/snjs/lib/Services/Features/FeaturesService.spec.ts b/packages/snjs/lib/Services/Features/FeaturesService.spec.ts index 4a6b27f67..12c32a2a8 100644 --- a/packages/snjs/lib/Services/Features/FeaturesService.spec.ts +++ b/packages/snjs/lib/Services/Features/FeaturesService.spec.ts @@ -725,7 +725,7 @@ describe('featuresService', () => { const featuresService = createService() await featuresService.migrateFeatureRepoToUserSetting([extensionRepoItem]) - expect(settingsService.updateSetting).toHaveBeenCalledWith(SettingName.ExtensionKey, extensionKey, true) + expect(settingsService.updateSetting).toHaveBeenCalledWith(SettingName.create(SettingName.NAMES.ExtensionKey).getValue(), extensionKey, true) }) }) diff --git a/packages/snjs/lib/Services/Features/FeaturesService.ts b/packages/snjs/lib/Services/Features/FeaturesService.ts index ac9a7e473..d581531c1 100644 --- a/packages/snjs/lib/Services/Features/FeaturesService.ts +++ b/packages/snjs/lib/Services/Features/FeaturesService.ts @@ -341,7 +341,11 @@ export class SNFeaturesService const userKeyMatch = repoUrl.match(/\w{32,64}/) if (userKeyMatch && userKeyMatch.length > 0) { const userKey = userKeyMatch[0] - await this.settingsService.updateSetting(SettingName.ExtensionKey, userKey, true) + await this.settingsService.updateSetting( + SettingName.create(SettingName.NAMES.ExtensionKey).getValue(), + userKey, + true, + ) await this.itemManager.changeFeatureRepo(item, (m) => { m.migratedToUserSetting = true }) diff --git a/packages/snjs/lib/Services/Listed/ListedService.ts b/packages/snjs/lib/Services/Listed/ListedService.ts index 3f3723ee9..1348c2710 100644 --- a/packages/snjs/lib/Services/Listed/ListedService.ts +++ b/packages/snjs/lib/Services/Listed/ListedService.ts @@ -108,7 +108,9 @@ export class ListedService extends AbstractService implements ListedClientInterf } private async getSettingsBasedListedAccounts(): Promise { - const response = await this.settingsService.getSetting(SettingName.ListedAuthorSecrets) + const response = await this.settingsService.getSetting( + SettingName.create(SettingName.NAMES.ListedAuthorSecrets).getValue(), + ) if (!response) { return [] } diff --git a/packages/snjs/lib/Services/Mfa/MfaService.ts b/packages/snjs/lib/Services/Mfa/MfaService.ts index a7225725e..b5f87c65e 100644 --- a/packages/snjs/lib/Services/Mfa/MfaService.ts +++ b/packages/snjs/lib/Services/Mfa/MfaService.ts @@ -16,11 +16,17 @@ export class SNMfaService extends AbstractService { } private async saveMfaSetting(secret: string): Promise { - return await this.settingsService.updateSetting(SettingName.MfaSecret, secret, true) + return await this.settingsService.updateSetting( + SettingName.create(SettingName.NAMES.MfaSecret).getValue(), + secret, + true, + ) } async isMfaActivated(): Promise { - const mfaSetting = await this.settingsService.getDoesSensitiveSettingExist(SettingName.MfaSecret) + const mfaSetting = await this.settingsService.getDoesSensitiveSettingExist( + SettingName.create(SettingName.NAMES.MfaSecret).getValue(), + ) return mfaSetting != false } @@ -43,7 +49,7 @@ export class SNMfaService extends AbstractService { } async disableMfa(): Promise { - return await this.settingsService.deleteSetting(SettingName.MfaSecret) + return await this.settingsService.deleteSetting(SettingName.create(SettingName.NAMES.MfaSecret).getValue()) } override deinit(): void { diff --git a/packages/snjs/lib/Services/Settings/SNSettingsService.ts b/packages/snjs/lib/Services/Settings/SNSettingsService.ts index 89f7632e0..c38799305 100644 --- a/packages/snjs/lib/Services/Settings/SNSettingsService.ts +++ b/packages/snjs/lib/Services/Settings/SNSettingsService.ts @@ -1,13 +1,7 @@ import { SNApiService } from '../Api/ApiService' import { SettingsGateway } from './SettingsGateway' import { SNSessionManager } from '../Session/SessionManager' -import { - CloudProvider, - EmailBackupFrequency, - SettingName, - SensitiveSettingName, - SubscriptionSettingName, -} from '@standardnotes/settings' +import { CloudProvider, EmailBackupFrequency, SettingName } from '@standardnotes/settings' import { ExtensionsServerURL } from '@Lib/Hosts' import { AbstractService, InternalEventBusInterface } from '@standardnotes/services' import { SettingsClientInterface } from './SettingsClientInterface' @@ -46,7 +40,7 @@ export class SNSettingsService extends AbstractService implements SettingsClient return this.provider.getSetting(name) } - async getSubscriptionSetting(name: SubscriptionSettingName) { + async getSubscriptionSetting(name: SettingName) { return this.provider.getSubscriptionSetting(name) } @@ -54,7 +48,7 @@ export class SNSettingsService extends AbstractService implements SettingsClient return this.provider.updateSetting(name, payload, sensitive) } - async getDoesSensitiveSettingExist(name: SensitiveSettingName) { + async getDoesSensitiveSettingExist(name: SettingName) { return this.provider.getDoesSensitiveSettingExist(name) } diff --git a/packages/snjs/lib/Services/Settings/SettingsClientInterface.ts b/packages/snjs/lib/Services/Settings/SettingsClientInterface.ts index 58e710a0a..0733ddc12 100644 --- a/packages/snjs/lib/Services/Settings/SettingsClientInterface.ts +++ b/packages/snjs/lib/Services/Settings/SettingsClientInterface.ts @@ -1,4 +1,4 @@ -import { SettingName, SensitiveSettingName, EmailBackupFrequency } from '@standardnotes/settings' +import { SettingName, EmailBackupFrequency } from '@standardnotes/settings' import { SettingsList } from './SettingsList' export interface SettingsClientInterface { @@ -6,7 +6,7 @@ export interface SettingsClientInterface { getSetting(name: SettingName): Promise - getDoesSensitiveSettingExist(name: SensitiveSettingName): Promise + getDoesSensitiveSettingExist(name: SettingName): Promise updateSetting(name: SettingName, payload: string, sensitive?: boolean): Promise diff --git a/packages/snjs/lib/Services/Settings/SettingsGateway.ts b/packages/snjs/lib/Services/Settings/SettingsGateway.ts index 227a12841..2fd15c175 100644 --- a/packages/snjs/lib/Services/Settings/SettingsGateway.ts +++ b/packages/snjs/lib/Services/Settings/SettingsGateway.ts @@ -1,5 +1,5 @@ import { SettingsList } from './SettingsList' -import { SettingName, SensitiveSettingName, SubscriptionSettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/settings' import { API_MESSAGE_INVALID_SESSION } from '@standardnotes/services' import { HttpStatusCode, isErrorResponse, User } from '@standardnotes/responses' import { SettingsServerInterface } from './SettingsServerInterface' @@ -46,7 +46,7 @@ export class SettingsGateway { } async getSetting(name: SettingName): Promise { - const response = await this.settingsApi.getSetting(this.userUuid, name) + const response = await this.settingsApi.getSetting(this.userUuid, name.value) if (response.status === HttpStatusCode.BadRequest) { return undefined @@ -59,8 +59,12 @@ export class SettingsGateway { return response?.data?.setting?.value ?? undefined } - async getSubscriptionSetting(name: SubscriptionSettingName): Promise { - const response = await this.settingsApi.getSubscriptionSetting(this.userUuid, name) + async getSubscriptionSetting(name: SettingName): Promise { + if (!name.isASubscriptionSetting()) { + throw new Error(`Setting ${name.value} is not a subscription setting`) + } + + const response = await this.settingsApi.getSubscriptionSetting(this.userUuid, name.value) if (response.status === HttpStatusCode.BadRequest) { return undefined @@ -73,8 +77,12 @@ export class SettingsGateway { return response?.data?.setting?.value ?? undefined } - async getDoesSensitiveSettingExist(name: SensitiveSettingName): Promise { - const response = await this.settingsApi.getSetting(this.userUuid, name) + async getDoesSensitiveSettingExist(name: SettingName): Promise { + if (!name.isSensitive()) { + throw new Error(`Setting ${name.value} is not sensitive`) + } + + const response = await this.settingsApi.getSetting(this.userUuid, name.value) if (response.status === HttpStatusCode.BadRequest) { return false @@ -88,14 +96,14 @@ export class SettingsGateway { } async updateSetting(name: SettingName, payload: string, sensitive: boolean): Promise { - const response = await this.settingsApi.updateSetting(this.userUuid, name, payload, sensitive) + const response = await this.settingsApi.updateSetting(this.userUuid, name.value, payload, sensitive) if (isErrorResponse(response)) { throw new Error(response.data?.error.message) } } async deleteSetting(name: SettingName): Promise { - const response = await this.settingsApi.deleteSetting(this.userUuid, name) + const response = await this.settingsApi.deleteSetting(this.userUuid, name.value) if (isErrorResponse(response)) { throw new Error(response.data?.error.message) } diff --git a/packages/snjs/lib/Services/Settings/SettingsList.ts b/packages/snjs/lib/Services/Settings/SettingsList.ts index 0b6bd32ec..3a0f08e83 100644 --- a/packages/snjs/lib/Services/Settings/SettingsList.ts +++ b/packages/snjs/lib/Services/Settings/SettingsList.ts @@ -28,16 +28,16 @@ type SettingType = | OneDriveBackupFrequency export class SettingsList { - private map: Partial> = {} + private map: Partial> = {} constructor(settings: SettingData[]) { for (const setting of settings) { - this.map[setting.name as SettingName] = setting + this.map[setting.name] = setting } } - getSettingValue(setting: SettingName, defaultValue: D): T { - const settingData = this.map[setting] + getSettingValue(settingName: SettingName, defaultValue: D): T { + const settingData = this.map[settingName.value] return (settingData?.value as unknown as T) || (defaultValue as unknown as T) } } diff --git a/packages/snjs/mocha/features.test.js b/packages/snjs/mocha/features.test.js index 31419d60d..7d438a5b3 100644 --- a/packages/snjs/mocha/features.test.js +++ b/packages/snjs/mocha/features.test.js @@ -194,7 +194,7 @@ describe('features', () => { return false }) - expect(await application.settings.getDoesSensitiveSettingExist(SettingName.ExtensionKey)).to.equal(false) + expect(await application.settings.getDoesSensitiveSettingExist(SettingName.create(SettingName.NAMES.ExtensionKey).getValue())).to.equal(false) const extensionKey = UuidGenerator.GenerateUuid().split('-').join('') diff --git a/packages/snjs/mocha/recovery.test.js b/packages/snjs/mocha/recovery.test.js index 8f6701932..a8f91dfea 100644 --- a/packages/snjs/mocha/recovery.test.js +++ b/packages/snjs/mocha/recovery.test.js @@ -26,13 +26,13 @@ describe('account recovery', function () { }) it('should get the same recovery code at each consecutive call', async () => { - let recoveryCodesSetting = await application.settings.getSetting(SettingName.RecoveryCodes) + let recoveryCodesSetting = await application.settings.getSetting(SettingName.create(SettingName.NAMES.RecoveryCodes).getValue()) expect(recoveryCodesSetting).to.equal(undefined) const generatedRecoveryCodesAfterFirstCall = await application.getRecoveryCodes.execute() expect(generatedRecoveryCodesAfterFirstCall.getValue().length).to.equal(49) - recoveryCodesSetting = await application.settings.getSetting(SettingName.RecoveryCodes) + recoveryCodesSetting = await application.settings.getSetting(SettingName.create(SettingName.NAMES.RecoveryCodes).getValue()) expect(recoveryCodesSetting).to.equal(generatedRecoveryCodesAfterFirstCall.getValue()) const fetchedRecoveryCodesOnTheSecondCall = await application.getRecoveryCodes.execute() diff --git a/packages/snjs/mocha/settings.test.js b/packages/snjs/mocha/settings.test.js index bbac19784..785942026 100644 --- a/packages/snjs/mocha/settings.test.js +++ b/packages/snjs/mocha/settings.test.js @@ -7,7 +7,7 @@ const expect = chai.expect describe('settings service', function () { this.timeout(Factory.ThirtySecondTimeout) - const validSetting = SettingName.GoogleDriveBackupFrequency + const validSetting = SettingName.create(SettingName.NAMES.GoogleDriveBackupFrequency).getValue() const fakePayload = 'Im so meta even this acronym' const updatedFakePayload = 'is meta' @@ -98,22 +98,22 @@ describe('settings service', function () { }) it('reads a nonexistent sensitive setting', async () => { - const setting = await application.settings.getDoesSensitiveSettingExist(SettingName.MfaSecret) + const setting = await application.settings.getDoesSensitiveSettingExist(SettingName.create(SettingName.NAMES.MfaSecret).getValue()) expect(setting).to.equal(false) }) it('creates and reads a sensitive setting', async () => { - await application.settings.updateSetting(SettingName.MfaSecret, 'fake_secret', true) - const setting = await application.settings.getDoesSensitiveSettingExist(SettingName.MfaSecret) + await application.settings.updateSetting(SettingName.create(SettingName.NAMES.MfaSecret).getValue(), 'fake_secret', true) + const setting = await application.settings.getDoesSensitiveSettingExist(SettingName.create(SettingName.NAMES.MfaSecret).getValue()) expect(setting).to.equal(true) }) it('creates and lists a sensitive setting', async () => { - await application.settings.updateSetting(SettingName.MfaSecret, 'fake_secret', true) - await application.settings.updateSetting(SettingName.MuteFailedBackupsEmails, MuteFailedBackupsEmailsOption.Muted) + await application.settings.updateSetting(SettingName.create(SettingName.NAMES.MfaSecret).getValue(), 'fake_secret', true) + await application.settings.updateSetting(SettingName.create(SettingName.NAMES.MuteFailedBackupsEmails).getValue(), MuteFailedBackupsEmailsOption.Muted) const settings = await application.settings.listSettings() - expect(settings.getSettingValue(SettingName.MuteFailedBackupsEmails)).to.eql(MuteFailedBackupsEmailsOption.Muted) - expect(settings.getSettingValue(SettingName.MfaSecret)).to.not.be.ok + expect(settings.getSettingValue(SettingName.create(SettingName.NAMES.MuteFailedBackupsEmails).getValue())).to.eql(MuteFailedBackupsEmailsOption.Muted) + expect(settings.getSettingValue(SettingName.create(SettingName.NAMES.MfaSecret).getValue())).to.not.be.ok }) it('reads a subscription setting', async () => { @@ -135,7 +135,7 @@ describe('settings service', function () { await Factory.sleep(2) - const setting = await application.settings.getSubscriptionSetting('FILE_UPLOAD_BYTES_LIMIT') + const setting = await application.settings.getSubscriptionSetting(SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue()) expect(setting).to.be.a('string') }) @@ -166,10 +166,10 @@ describe('settings service', function () { await Factory.sleep(1) - const limitSettingBefore = await application.settings.getSubscriptionSetting('FILE_UPLOAD_BYTES_LIMIT') + const limitSettingBefore = await application.settings.getSubscriptionSetting(SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue()) expect(limitSettingBefore).to.equal('107374182400') - const usedSettingBefore = await application.settings.getSubscriptionSetting('FILE_UPLOAD_BYTES_USED') + const usedSettingBefore = await application.settings.getSubscriptionSetting(SettingName.create(SettingName.NAMES.FileUploadBytesUsed).getValue()) expect(usedSettingBefore).to.equal('196') await Factory.publishMockedEvent('SUBSCRIPTION_EXPIRED', { @@ -202,10 +202,10 @@ describe('settings service', function () { }) await Factory.sleep(1) - const limitSettingAfter = await application.settings.getSubscriptionSetting('FILE_UPLOAD_BYTES_LIMIT') + const limitSettingAfter = await application.settings.getSubscriptionSetting(SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue()) expect(limitSettingAfter).to.equal(limitSettingBefore) - const usedSettingAfter = await application.settings.getSubscriptionSetting('FILE_UPLOAD_BYTES_USED') + const usedSettingAfter = await application.settings.getSubscriptionSetting(SettingName.create(SettingName.NAMES.FileUploadBytesUsed).getValue()) expect(usedSettingAfter).to.equal(usedSettingBefore) }) }) diff --git a/packages/snjs/package.json b/packages/snjs/package.json index aa5166399..670243308 100644 --- a/packages/snjs/package.json +++ b/packages/snjs/package.json @@ -46,7 +46,7 @@ "@standardnotes/responses": "workspace:*", "@standardnotes/security": "^1.7.6", "@standardnotes/services": "workspace:*", - "@standardnotes/settings": "^1.19.1", + "@standardnotes/settings": "^1.20.0", "@standardnotes/sncrypto-common": "workspace:*", "@standardnotes/sncrypto-web": "workspace:*", "@standardnotes/utils": "workspace:*", diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Account/AccountPreferences.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Account/AccountPreferences.tsx index 41af64f33..8ffbce119 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Account/AccountPreferences.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Account/AccountPreferences.tsx @@ -9,7 +9,7 @@ import SignOutWrapper from './SignOutView' import FilesSection from './Files' import PreferencesPane from '../../PreferencesComponents/PreferencesPane' import SubscriptionSharing from './SubscriptionSharing/SubscriptionSharing' -import Email from './Email' +import Email from './Email/Email' import DeleteAccount from '@/Components/Preferences/Panes/Account/DeleteAccount' type Props = { diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Account/Email.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Account/Email/Email.tsx similarity index 67% rename from packages/web/src/javascripts/Components/Preferences/Panes/Account/Email.tsx rename to packages/web/src/javascripts/Components/Preferences/Panes/Account/Email/Email.tsx index fa6306631..2bf94b62e 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Account/Email.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Account/Email/Email.tsx @@ -1,4 +1,10 @@ -import { MuteMarketingEmailsOption, MuteSignInEmailsOption, SettingName } from '@standardnotes/snjs' +import { + FeatureIdentifier, + FeatureStatus, + MuteMarketingEmailsOption, + MuteSignInEmailsOption, + SettingName, +} from '@standardnotes/snjs' import { observer } from 'mobx-react-lite' import { FunctionComponent, useCallback, useEffect, useState } from 'react' @@ -10,6 +16,7 @@ import { STRING_FAILED_TO_UPDATE_USER_SETTING } from '@/Constants/Strings' import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup' import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment' import Spinner from '@/Components/Spinner/Spinner' +import NoProSubscription from '../NoProSubscription' type Props = { application: WebApplication @@ -20,6 +27,9 @@ const Email: FunctionComponent = ({ application }: Props) => { const [marketingEmailsMutedValue, setMarketingEmailsMutedValue] = useState(MuteMarketingEmailsOption.NotMuted) const [isLoading, setIsLoading] = useState(true) + const isMuteSignInEmailsFeatureAvailable = + application.features.getFeatureStatus(FeatureIdentifier.SignInAlerts) === FeatureStatus.Entitled + const updateSetting = async (settingName: SettingName, payload: string): Promise => { try { await application.settings.updateSetting(settingName, payload, false) @@ -40,13 +50,13 @@ const Email: FunctionComponent = ({ application }: Props) => { const userSettings = await application.settings.listSettings() setSignInEmailsMutedValue( userSettings.getSettingValue( - SettingName.MuteSignInEmails, + SettingName.create(SettingName.NAMES.MuteSignInEmails).getValue(), MuteSignInEmailsOption.NotMuted, ), ), setMarketingEmailsMutedValue( userSettings.getSettingValue( - SettingName.MuteMarketingEmails, + SettingName.create(SettingName.NAMES.MuteMarketingEmails).getValue(), MuteMarketingEmailsOption.NotMuted, ), ) @@ -67,7 +77,10 @@ const Email: FunctionComponent = ({ application }: Props) => { previousValue === MuteSignInEmailsOption.Muted ? MuteSignInEmailsOption.NotMuted : MuteSignInEmailsOption.Muted setSignInEmailsMutedValue(newValue) - const updateResult = await updateSetting(SettingName.MuteSignInEmails, newValue) + const updateResult = await updateSetting( + SettingName.create(SettingName.NAMES.MuteSignInEmails).getValue(), + newValue, + ) if (!updateResult) { setSignInEmailsMutedValue(previousValue) @@ -82,7 +95,10 @@ const Email: FunctionComponent = ({ application }: Props) => { : MuteMarketingEmailsOption.Muted setMarketingEmailsMutedValue(newValue) - const updateResult = await updateSetting(SettingName.MuteMarketingEmails, newValue) + const updateResult = await updateSetting( + SettingName.create(SettingName.NAMES.MuteMarketingEmails).getValue(), + newValue, + ) if (!updateResult) { setMarketingEmailsMutedValue(previousValue) @@ -96,25 +112,40 @@ const Email: FunctionComponent = ({ application }: Props) => {
- Disable sign-in notification emails - - Disables email notifications when a new sign-in occurs on your account. (Email notifications are - available only to paid subscribers). - + Sign-in notification emails + {isMuteSignInEmailsFeatureAvailable ? ( + + Disables email notifications when a new sign-in occurs on your account. (Email notifications are + available only to paid subscribers). + + ) : ( + + Sign-in notification emails are available only on a{' '} + subscription plan. Please upgrade in order to enable sign-in + notifications. + + } + /> + )}
{isLoading ? ( ) : ( - + isMuteSignInEmailsFeatureAvailable && ( + + ) )}
- Disable marketing notification emails + Marketing notification emails Disables email notifications with special deals and promotions.
{isLoading ? ( diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Account/Files.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Account/Files.tsx index 73d3bae55..19a415867 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Account/Files.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Account/Files.tsx @@ -1,7 +1,7 @@ import { WebApplication } from '@/Application/Application' import Spinner from '@/Components/Spinner/Spinner' import { formatSizeToReadableString } from '@standardnotes/filepicker' -import { SubscriptionSettingName } from '@standardnotes/snjs' +import { SettingName } from '@standardnotes/snjs' import { FunctionComponent, useEffect, useState } from 'react' import { Subtitle, Title } from '../../PreferencesComponents/Content' import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup' @@ -19,10 +19,10 @@ const FilesSection: FunctionComponent = ({ application }) => { useEffect(() => { const getFilesQuota = async () => { const filesQuotaUsed = await application.settings.getSubscriptionSetting( - SubscriptionSettingName.FileUploadBytesUsed, + SettingName.create(SettingName.NAMES.FileUploadBytesUsed).getValue(), ) const filesQuotaTotal = await application.settings.getSubscriptionSetting( - SubscriptionSettingName.FileUploadBytesLimit, + SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue(), ) if (filesQuotaUsed) { diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Account/SubscriptionSharing/NoProSubscription.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Account/NoProSubscription.tsx similarity index 85% rename from packages/web/src/javascripts/Components/Preferences/Panes/Account/SubscriptionSharing/NoProSubscription.tsx rename to packages/web/src/javascripts/Components/Preferences/Panes/Account/NoProSubscription.tsx index 16ccbbe2b..c14b6dd31 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Account/SubscriptionSharing/NoProSubscription.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Account/NoProSubscription.tsx @@ -1,13 +1,14 @@ -import { FunctionComponent, useState } from 'react' +import { FunctionComponent, ReactNode, useState } from 'react' import { LinkButton, Text } from '@/Components/Preferences/PreferencesComponents/Content' import Button from '@/Components/Button/Button' import { WebApplication } from '@/Application/Application' type Props = { application: WebApplication + text: ReactNode } -const NoProSubscription: FunctionComponent = ({ application }) => { +const NoProSubscription: FunctionComponent = ({ application, text }) => { const [isLoadingPurchaseFlow, setIsLoadingPurchaseFlow] = useState(false) const [purchaseFlowError, setPurchaseFlowError] = useState(undefined) @@ -29,10 +30,7 @@ const NoProSubscription: FunctionComponent = ({ application }) => { return ( <> - - Subscription sharing is available only on the Professional plan. Please - upgrade in order to share your subscription. - + {text} {isLoadingPurchaseFlow && Redirecting you to the subscription page...} {purchaseFlowError && {purchaseFlowError}} diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Account/SubscriptionSharing/SubscriptionSharing.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Account/SubscriptionSharing/SubscriptionSharing.tsx index e2417e2e6..24731225d 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Account/SubscriptionSharing/SubscriptionSharing.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Account/SubscriptionSharing/SubscriptionSharing.tsx @@ -9,7 +9,7 @@ import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/Pre import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment' import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator' -import NoProSubscription from './NoProSubscription' +import NoProSubscription from '../NoProSubscription' import InvitationsList from './InvitationsList' import Invite from './Invite/Invite' import Button from '@/Components/Button/Button' @@ -57,7 +57,15 @@ const SubscriptionSharing: FunctionComponent = ({ application, viewContro
) : ( - + + Subscription sharing is available only on the Professional plan. + Please upgrade in order to share your subscription. + + } + /> )}
diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Backups/CloudBackups/CloudBackupProvider.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Backups/CloudBackups/CloudBackupProvider.tsx index cbb651d68..79507e609 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Backups/CloudBackups/CloudBackupProvider.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Backups/CloudBackups/CloudBackupProvider.tsx @@ -82,18 +82,18 @@ const CloudBackupProvider: FunctionComponent = ({ application, providerNa const backupSettingsData = { [CloudProvider.Dropbox]: { - backupTokenSettingName: SettingName.DropboxBackupToken, - backupFrequencySettingName: SettingName.DropboxBackupFrequency, + backupTokenSettingName: SettingName.create(SettingName.NAMES.DropboxBackupToken).getValue(), + backupFrequencySettingName: SettingName.create(SettingName.NAMES.DropboxBackupFrequency).getValue(), defaultBackupFrequency: DropboxBackupFrequency.Daily, }, [CloudProvider.Google]: { - backupTokenSettingName: SettingName.GoogleDriveBackupToken, - backupFrequencySettingName: SettingName.GoogleDriveBackupFrequency, + backupTokenSettingName: SettingName.create(SettingName.NAMES.GoogleDriveBackupToken).getValue(), + backupFrequencySettingName: SettingName.create(SettingName.NAMES.GoogleDriveBackupFrequency).getValue(), defaultBackupFrequency: GoogleDriveBackupFrequency.Daily, }, [CloudProvider.OneDrive]: { - backupTokenSettingName: SettingName.OneDriveBackupToken, - backupFrequencySettingName: SettingName.OneDriveBackupFrequency, + backupTokenSettingName: SettingName.create(SettingName.NAMES.OneDriveBackupToken).getValue(), + backupFrequencySettingName: SettingName.create(SettingName.NAMES.OneDriveBackupFrequency).getValue(), defaultBackupFrequency: OneDriveBackupFrequency.Daily, }, } diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Backups/CloudBackups/CloudBackups.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Backups/CloudBackups/CloudBackups.tsx index 2c2ee2863..1dff27e7a 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Backups/CloudBackups/CloudBackups.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Backups/CloudBackups/CloudBackups.tsx @@ -41,7 +41,7 @@ const CloudLink: FunctionComponent = ({ application }) => { setIsFailedCloudBackupEmailMuted( convertStringifiedBooleanToBoolean( userSettings.getSettingValue( - SettingName.MuteFailedCloudBackupsEmails, + SettingName.create(SettingName.NAMES.MuteFailedCloudBackupsEmails).getValue(), MuteFailedCloudBackupsEmailsOption.NotMuted, ), ), @@ -83,7 +83,7 @@ const CloudLink: FunctionComponent = ({ application }) => { setIsFailedCloudBackupEmailMuted(!isFailedCloudBackupEmailMuted) const updateResult = await updateSetting( - SettingName.MuteFailedCloudBackupsEmails, + SettingName.create(SettingName.NAMES.MuteFailedCloudBackupsEmails).getValue(), `${!isFailedCloudBackupEmailMuted}`, ) if (!updateResult) { diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Backups/EmailBackups.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Backups/EmailBackups.tsx index ed81e20b4..a65e11e72 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Backups/EmailBackups.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Backups/EmailBackups.tsx @@ -34,14 +34,14 @@ const EmailBackups = ({ application }: Props) => { const userSettings = await application.settings.listSettings() setEmailFrequency( userSettings.getSettingValue( - SettingName.EmailBackupFrequency, + SettingName.create(SettingName.NAMES.EmailBackupFrequency).getValue(), EmailBackupFrequency.Disabled, ), ) setIsFailedBackupEmailMuted( convertStringifiedBooleanToBoolean( userSettings.getSettingValue( - SettingName.MuteFailedBackupsEmails, + SettingName.create(SettingName.NAMES.MuteFailedBackupsEmails).getValue(), MuteFailedBackupsEmailsOption.NotMuted, ), ), @@ -81,7 +81,10 @@ const EmailBackups = ({ application }: Props) => { const previousFrequency = emailFrequency setEmailFrequency(frequency) - const updateResult = await updateSetting(SettingName.EmailBackupFrequency, frequency) + const updateResult = await updateSetting( + SettingName.create(SettingName.NAMES.EmailBackupFrequency).getValue(), + frequency, + ) if (!updateResult) { setEmailFrequency(previousFrequency) } @@ -91,7 +94,10 @@ const EmailBackups = ({ application }: Props) => { const previousValue = isFailedBackupEmailMuted setIsFailedBackupEmailMuted(!isFailedBackupEmailMuted) - const updateResult = await updateSetting(SettingName.MuteFailedBackupsEmails, `${!isFailedBackupEmailMuted}`) + const updateResult = await updateSetting( + SettingName.create(SettingName.NAMES.MuteFailedBackupsEmails).getValue(), + `${!isFailedBackupEmailMuted}`, + ) if (!updateResult) { setIsFailedBackupEmailMuted(previousValue) } diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Security/Privacy.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Security/Privacy.tsx index 68d9615bd..b14c9ebe1 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Security/Privacy.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Security/Privacy.tsx @@ -39,7 +39,7 @@ const Privacy: FunctionComponent = ({ application }: Props) => { const userSettings = await application.settings.listSettings() setSessionUaLoggingValue( userSettings.getSettingValue( - SettingName.LogSessionUserAgent, + SettingName.create(SettingName.NAMES.LogSessionUserAgent).getValue(), LogSessionUserAgentOption.Enabled, ), ) @@ -62,7 +62,10 @@ const Privacy: FunctionComponent = ({ application }: Props) => { : LogSessionUserAgentOption.Enabled setSessionUaLoggingValue(newValue) - const updateResult = await updateSetting(SettingName.LogSessionUserAgent, newValue) + const updateResult = await updateSetting( + SettingName.create(SettingName.NAMES.LogSessionUserAgent).getValue(), + newValue, + ) if (!updateResult) { setSessionUaLoggingValue(previousValue) diff --git a/yarn.lock b/yarn.lock index b427f9943..5666fb233 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5317,12 +5317,13 @@ __metadata: languageName: unknown linkType: soft -"@standardnotes/settings@npm:^1.19.1": - version: 1.19.1 - resolution: "@standardnotes/settings@npm:1.19.1" +"@standardnotes/settings@npm:^1.20.0": + version: 1.20.0 + resolution: "@standardnotes/settings@npm:1.20.0" dependencies: + "@standardnotes/domain-core": ^1.11.3 reflect-metadata: ^0.1.13 - checksum: d99d49d4401ac8c973284d8637195c0441e6f73b7e01e9eb4ab14feb27c6d2e928a493d944ed67ea1d6f229bf5ea839b5dd667c373b917aed321bb32972daacb + checksum: 6e8c03107eec03e3d800bb9ced690dbade6713f7ef5e19364aaf06a86a92d2d58ac629d2e948775223ae9798be3ec60edfc5c24a8f7a4d57de959e5937727f44 languageName: node linkType: hard @@ -5403,7 +5404,7 @@ __metadata: "@standardnotes/responses": "workspace:*" "@standardnotes/security": ^1.7.6 "@standardnotes/services": "workspace:*" - "@standardnotes/settings": ^1.19.1 + "@standardnotes/settings": ^1.20.0 "@standardnotes/sncrypto-common": "workspace:*" "@standardnotes/sncrypto-web": "workspace:*" "@standardnotes/utils": "workspace:*"