feat(web): add accepting subscription invites from UI
This commit is contained in:
@@ -38,7 +38,7 @@ describe('SubscriptionServer', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should accept an invite to a shared subscription', async () => {
|
it('should accept an invite to a shared subscription', async () => {
|
||||||
httpService.get = jest.fn().mockReturnValue({
|
httpService.post = jest.fn().mockReturnValue({
|
||||||
data: { success: true },
|
data: { success: true },
|
||||||
} as jest.Mocked<SubscriptionInviteAcceptResponse>)
|
} as jest.Mocked<SubscriptionInviteAcceptResponse>)
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export class SubscriptionServer implements SubscriptionServerInterface {
|
|||||||
constructor(private httpService: HttpServiceInterface) {}
|
constructor(private httpService: HttpServiceInterface) {}
|
||||||
|
|
||||||
async acceptInvite(params: SubscriptionInviteAcceptRequestParams): Promise<SubscriptionInviteAcceptResponse> {
|
async acceptInvite(params: SubscriptionInviteAcceptRequestParams): Promise<SubscriptionInviteAcceptResponse> {
|
||||||
const response = await this.httpService.get(Paths.v1.acceptInvite(params.inviteUuid), params)
|
const response = await this.httpService.post(Paths.v1.acceptInvite(params.inviteUuid), params)
|
||||||
|
|
||||||
return response as SubscriptionInviteAcceptResponse
|
return response as SubscriptionInviteAcceptResponse
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ export interface SubscriptionClientInterface {
|
|||||||
listSubscriptionInvitations(): Promise<Invitation[]>
|
listSubscriptionInvitations(): Promise<Invitation[]>
|
||||||
inviteToSubscription(inviteeEmail: string): Promise<boolean>
|
inviteToSubscription(inviteeEmail: string): Promise<boolean>
|
||||||
cancelInvitation(inviteUuid: Uuid): Promise<boolean>
|
cancelInvitation(inviteUuid: Uuid): Promise<boolean>
|
||||||
|
acceptInvitation(inviteUuid: Uuid): Promise<{ success: true } | { success: false; message: string }>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ describe('SubscriptionManager', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
subscriptionApiService = {} as jest.Mocked<SubscriptionApiServiceInterface>
|
subscriptionApiService = {} as jest.Mocked<SubscriptionApiServiceInterface>
|
||||||
subscriptionApiService.cancelInvite = jest.fn()
|
subscriptionApiService.cancelInvite = jest.fn()
|
||||||
|
subscriptionApiService.acceptInvite = jest.fn()
|
||||||
subscriptionApiService.invite = jest.fn()
|
subscriptionApiService.invite = jest.fn()
|
||||||
subscriptionApiService.listInvites = jest.fn()
|
subscriptionApiService.listInvites = jest.fn()
|
||||||
|
|
||||||
@@ -80,4 +81,27 @@ describe('SubscriptionManager', () => {
|
|||||||
|
|
||||||
expect(await createManager().listSubscriptionInvitations()).toEqual([])
|
expect(await createManager().listSubscriptionInvitations()).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should accept invite to a shared subscription', async () => {
|
||||||
|
subscriptionApiService.acceptInvite = jest.fn().mockReturnValue({ data: { success: true } })
|
||||||
|
|
||||||
|
expect(await createManager().acceptInvitation('1-2-3')).toEqual({ success: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not accept invite if the api fails to do so', async () => {
|
||||||
|
subscriptionApiService.acceptInvite = jest.fn().mockReturnValue({ data: { error: { message: 'foobar' } } })
|
||||||
|
|
||||||
|
expect(await createManager().acceptInvitation('1-2-3')).toEqual({ success: false, message: 'foobar' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not accept invite if the api throws an error', async () => {
|
||||||
|
subscriptionApiService.acceptInvite = jest.fn().mockImplementation(() => {
|
||||||
|
throw new Error('Oops')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(await createManager().acceptInvitation('1-2-3')).toEqual({
|
||||||
|
success: false,
|
||||||
|
message: 'Could not accept invitation.',
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,6 +13,20 @@ export class SubscriptionManager extends AbstractService implements Subscription
|
|||||||
super(internalEventBus)
|
super(internalEventBus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async acceptInvitation(inviteUuid: string): Promise<{ success: true } | { success: false; message: string }> {
|
||||||
|
try {
|
||||||
|
const result = await this.subscriptionApiService.acceptInvite(inviteUuid)
|
||||||
|
|
||||||
|
if (result.data.error) {
|
||||||
|
return { success: false, message: result.data.error.message }
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.data
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, message: 'Could not accept invitation.' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async listSubscriptionInvitations(): Promise<Invitation[]> {
|
async listSubscriptionInvitations(): Promise<Invitation[]> {
|
||||||
try {
|
try {
|
||||||
const response = await this.subscriptionApiService.listInvites()
|
const response = await this.subscriptionApiService.listInvites()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
InternalEventBusInterface,
|
InternalEventBusInterface,
|
||||||
} from '@standardnotes/services'
|
} from '@standardnotes/services'
|
||||||
|
|
||||||
|
import { RootQueryParam } from './RootQueryParam'
|
||||||
import { RouteParser } from './RouteParser'
|
import { RouteParser } from './RouteParser'
|
||||||
import { RouteParserInterface } from './RouteParserInterface'
|
import { RouteParserInterface } from './RouteParserInterface'
|
||||||
import { RouteServiceEvent } from './RouteServiceEvent'
|
import { RouteServiceEvent } from './RouteServiceEvent'
|
||||||
@@ -30,13 +31,13 @@ export class RouteService
|
|||||||
this.unsubApp()
|
this.unsubApp()
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRoute(): RouteParserInterface {
|
getRoute(): RouteParserInterface {
|
||||||
return new RouteParser(window.location.href)
|
return new RouteParser(window.location.href)
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeSettingsFromURLQueryParameters() {
|
removeQueryParameterFromURL(param: RootQueryParam): void {
|
||||||
const urlSearchParams = new URLSearchParams(window.location.search)
|
const urlSearchParams = new URLSearchParams(window.location.search)
|
||||||
urlSearchParams.delete('settings')
|
urlSearchParams.delete(param)
|
||||||
|
|
||||||
const newUrl = `${window.location.origin}${window.location.pathname}${urlSearchParams.toString()}`
|
const newUrl = `${window.location.origin}${window.location.pathname}${urlSearchParams.toString()}`
|
||||||
window.history.replaceState(null, document.title, newUrl)
|
window.history.replaceState(null, document.title, newUrl)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import { RootQueryParam } from './RootQueryParam'
|
||||||
import { RouteParserInterface } from './RouteParserInterface'
|
import { RouteParserInterface } from './RouteParserInterface'
|
||||||
|
|
||||||
export interface RouteServiceInterface {
|
export interface RouteServiceInterface {
|
||||||
deinit(): void
|
deinit(): void
|
||||||
getRoute(): RouteParserInterface
|
getRoute(): RouteParserInterface
|
||||||
removeSettingsFromURLQueryParameters(): void
|
removeQueryParameterFromURL(param: RootQueryParam): void
|
||||||
}
|
}
|
||||||
|
|||||||
12
packages/ui-services/src/Toast/ToastService.ts
Normal file
12
packages/ui-services/src/Toast/ToastService.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { addToast, ToastType } from '@standardnotes/toast'
|
||||||
|
|
||||||
|
import { ToastServiceInterface } from './ToastServiceInterface'
|
||||||
|
|
||||||
|
export class ToastService implements ToastServiceInterface {
|
||||||
|
showToast(type: ToastType, message: string): void {
|
||||||
|
addToast({
|
||||||
|
type: type,
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
5
packages/ui-services/src/Toast/ToastServiceInterface.ts
Normal file
5
packages/ui-services/src/Toast/ToastServiceInterface.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { ToastType } from '@standardnotes/toast'
|
||||||
|
|
||||||
|
export interface ToastServiceInterface {
|
||||||
|
showToast(type: ToastType, message: string): void
|
||||||
|
}
|
||||||
@@ -8,7 +8,9 @@ export * from './Route/Params/OnboardingParams'
|
|||||||
export * from './Route/Params/PurchaseParams'
|
export * from './Route/Params/PurchaseParams'
|
||||||
export * from './Route/Params/SettingsParams'
|
export * from './Route/Params/SettingsParams'
|
||||||
export * from './Route/Params/SubscriptionInviteParams'
|
export * from './Route/Params/SubscriptionInviteParams'
|
||||||
|
export * from './Route/RootQueryParam'
|
||||||
export * from './Route/RouteParser'
|
export * from './Route/RouteParser'
|
||||||
|
export * from './Route/RouteParserInterface'
|
||||||
export * from './Route/RouteType'
|
export * from './Route/RouteType'
|
||||||
export * from './Route/RouteService'
|
export * from './Route/RouteService'
|
||||||
export * from './Route/RouteServiceInterface'
|
export * from './Route/RouteServiceInterface'
|
||||||
@@ -16,3 +18,5 @@ export * from './Route/RouteServiceEvent'
|
|||||||
export * from './Security/AutolockService'
|
export * from './Security/AutolockService'
|
||||||
export * from './Storage/LocalStorage'
|
export * from './Storage/LocalStorage'
|
||||||
export * from './Theme/ThemeManager'
|
export * from './Theme/ThemeManager'
|
||||||
|
export * from './Toast/ToastService'
|
||||||
|
export * from './Toast/ToastServiceInterface'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { InternalEventBus } from '@standardnotes/snjs'
|
import { InternalEventBus } from '@standardnotes/snjs'
|
||||||
import { action, computed, makeObservable, observable } from 'mobx'
|
import { action, computed, makeObservable, observable } from 'mobx'
|
||||||
import { PreferenceId } from '@standardnotes/ui-services'
|
import { PreferenceId, RootQueryParam } from '@standardnotes/ui-services'
|
||||||
import { AbstractViewController } from './Abstract/AbstractViewController'
|
import { AbstractViewController } from './Abstract/AbstractViewController'
|
||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ export class PreferencesController extends AbstractViewController {
|
|||||||
closePreferences = (): void => {
|
closePreferences = (): void => {
|
||||||
this._open = false
|
this._open = false
|
||||||
this.currentPane = DEFAULT_PANE
|
this.currentPane = DEFAULT_PANE
|
||||||
this.application.routeService.removeSettingsFromURLQueryParameters()
|
this.application.routeService.removeQueryParameterFromURL(RootQueryParam.Settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
get isOpen(): boolean {
|
get isOpen(): boolean {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { PaneController } from './PaneController'
|
import { PaneController } from './PaneController'
|
||||||
import { storage, StorageKey } from '@standardnotes/ui-services'
|
import { storage, StorageKey, ToastService, ToastServiceInterface } from '@standardnotes/ui-services'
|
||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
||||||
import { destroyAllObjectProperties } from '@/Utils'
|
import { destroyAllObjectProperties } from '@/Utils'
|
||||||
@@ -71,6 +71,7 @@ export class ViewControllerManager implements InternalEventHandlerInterface {
|
|||||||
private subscriptionManager: SubscriptionClientInterface
|
private subscriptionManager: SubscriptionClientInterface
|
||||||
private persistenceService: PersistenceService
|
private persistenceService: PersistenceService
|
||||||
private applicationEventObserver: EventObserverInterface
|
private applicationEventObserver: EventObserverInterface
|
||||||
|
private toastService: ToastServiceInterface
|
||||||
|
|
||||||
constructor(public application: WebApplication, private device: WebOrDesktopDeviceInterface) {
|
constructor(public application: WebApplication, private device: WebOrDesktopDeviceInterface) {
|
||||||
this.eventBus = new InternalEventBus()
|
this.eventBus = new InternalEventBus()
|
||||||
@@ -146,6 +147,8 @@ export class ViewControllerManager implements InternalEventHandlerInterface {
|
|||||||
|
|
||||||
this.historyModalController = new HistoryModalController(this.application, this.eventBus)
|
this.historyModalController = new HistoryModalController(this.application, this.eventBus)
|
||||||
|
|
||||||
|
this.toastService = new ToastService()
|
||||||
|
|
||||||
this.applicationEventObserver = new ApplicationEventObserver(
|
this.applicationEventObserver = new ApplicationEventObserver(
|
||||||
application.routeService,
|
application.routeService,
|
||||||
this.purchaseFlowController,
|
this.purchaseFlowController,
|
||||||
@@ -154,6 +157,8 @@ export class ViewControllerManager implements InternalEventHandlerInterface {
|
|||||||
this.syncStatusController,
|
this.syncStatusController,
|
||||||
application.sync,
|
application.sync,
|
||||||
application.sessions,
|
application.sessions,
|
||||||
|
application.subscriptions,
|
||||||
|
this.toastService,
|
||||||
)
|
)
|
||||||
|
|
||||||
this.addAppEventObserver()
|
this.addAppEventObserver()
|
||||||
|
|||||||
@@ -2,17 +2,30 @@
|
|||||||
* @jest-environment jsdom
|
* @jest-environment jsdom
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { RouteServiceInterface, RouteType } from '@standardnotes/ui-services'
|
import {
|
||||||
import { ApplicationEvent, SessionsClientInterface, SyncClientInterface, SyncOpStatus, User } from '@standardnotes/snjs'
|
RootQueryParam,
|
||||||
|
RouteServiceInterface,
|
||||||
|
RouteParserInterface,
|
||||||
|
RouteType,
|
||||||
|
ToastServiceInterface,
|
||||||
|
} from '@standardnotes/ui-services'
|
||||||
|
import { ToastType } from '@standardnotes/toast'
|
||||||
|
import {
|
||||||
|
ApplicationEvent,
|
||||||
|
SessionsClientInterface,
|
||||||
|
SubscriptionClientInterface,
|
||||||
|
SyncClientInterface,
|
||||||
|
SyncOpStatus,
|
||||||
|
User,
|
||||||
|
} from '@standardnotes/snjs'
|
||||||
|
|
||||||
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
||||||
import { PreferencesController } from '@/Controllers/PreferencesController'
|
import { PreferencesController } from '@/Controllers/PreferencesController'
|
||||||
import { PurchaseFlowController } from '@/Controllers/PurchaseFlow/PurchaseFlowController'
|
import { PurchaseFlowController } from '@/Controllers/PurchaseFlow/PurchaseFlowController'
|
||||||
import { SyncStatusController } from '@/Controllers/SyncStatusController'
|
import { SyncStatusController } from '@/Controllers/SyncStatusController'
|
||||||
|
import { AccountMenuPane } from '@/Components/AccountMenu/AccountMenuPane'
|
||||||
|
|
||||||
import { ApplicationEventObserver } from './ApplicationEventObserver'
|
import { ApplicationEventObserver } from './ApplicationEventObserver'
|
||||||
import { RouteParserInterface } from '@standardnotes/ui-services/dist/Route/RouteParserInterface'
|
|
||||||
import { AccountMenuPane } from '@/Components/AccountMenu/AccountMenuPane'
|
|
||||||
|
|
||||||
describe('ApplicationEventObserver', () => {
|
describe('ApplicationEventObserver', () => {
|
||||||
let routeService: RouteServiceInterface
|
let routeService: RouteServiceInterface
|
||||||
@@ -22,6 +35,8 @@ describe('ApplicationEventObserver', () => {
|
|||||||
let syncStatusController: SyncStatusController
|
let syncStatusController: SyncStatusController
|
||||||
let syncClient: SyncClientInterface
|
let syncClient: SyncClientInterface
|
||||||
let sessionManager: SessionsClientInterface
|
let sessionManager: SessionsClientInterface
|
||||||
|
let subscriptionManager: SubscriptionClientInterface
|
||||||
|
let toastService: ToastServiceInterface
|
||||||
|
|
||||||
const createObserver = () =>
|
const createObserver = () =>
|
||||||
new ApplicationEventObserver(
|
new ApplicationEventObserver(
|
||||||
@@ -32,6 +47,8 @@ describe('ApplicationEventObserver', () => {
|
|||||||
syncStatusController,
|
syncStatusController,
|
||||||
syncClient,
|
syncClient,
|
||||||
sessionManager,
|
sessionManager,
|
||||||
|
subscriptionManager,
|
||||||
|
toastService,
|
||||||
)
|
)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -39,6 +56,7 @@ describe('ApplicationEventObserver', () => {
|
|||||||
routeService.getRoute = jest.fn().mockReturnValue({
|
routeService.getRoute = jest.fn().mockReturnValue({
|
||||||
type: RouteType.None,
|
type: RouteType.None,
|
||||||
} as jest.Mocked<RouteParserInterface>)
|
} as jest.Mocked<RouteParserInterface>)
|
||||||
|
routeService.removeQueryParameterFromURL = jest.fn()
|
||||||
|
|
||||||
purchaseFlowController = {} as jest.Mocked<PurchaseFlowController>
|
purchaseFlowController = {} as jest.Mocked<PurchaseFlowController>
|
||||||
purchaseFlowController.openPurchaseFlow = jest.fn()
|
purchaseFlowController.openPurchaseFlow = jest.fn()
|
||||||
@@ -59,6 +77,12 @@ describe('ApplicationEventObserver', () => {
|
|||||||
|
|
||||||
sessionManager = {} as jest.Mocked<SessionsClientInterface>
|
sessionManager = {} as jest.Mocked<SessionsClientInterface>
|
||||||
sessionManager.getUser = jest.fn().mockReturnValue({} as jest.Mocked<User>)
|
sessionManager.getUser = jest.fn().mockReturnValue({} as jest.Mocked<User>)
|
||||||
|
|
||||||
|
subscriptionManager = {} as jest.Mocked<SubscriptionClientInterface>
|
||||||
|
subscriptionManager.acceptInvitation = jest.fn()
|
||||||
|
|
||||||
|
toastService = {} as jest.Mocked<ToastServiceInterface>
|
||||||
|
toastService.showToast = jest.fn()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Upon Application Launched', () => {
|
describe('Upon Application Launched', () => {
|
||||||
@@ -102,6 +126,59 @@ describe('ApplicationEventObserver', () => {
|
|||||||
expect(preferencesController.openPreferences).not.toHaveBeenCalled()
|
expect(preferencesController.openPreferences).not.toHaveBeenCalled()
|
||||||
expect(preferencesController.setCurrentPane).not.toHaveBeenCalled()
|
expect(preferencesController.setCurrentPane).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should open up sign in if user is not logged in and to accept subscription invitation', async () => {
|
||||||
|
sessionManager.getUser = jest.fn().mockReturnValue(undefined)
|
||||||
|
routeService.getRoute = jest.fn().mockReturnValue({
|
||||||
|
type: RouteType.AcceptSubscriptionInvite,
|
||||||
|
subscriptionInviteParams: {
|
||||||
|
inviteUuid: '1-2-3',
|
||||||
|
},
|
||||||
|
} as jest.Mocked<RouteParserInterface>)
|
||||||
|
|
||||||
|
await createObserver().handle(ApplicationEvent.Launched)
|
||||||
|
|
||||||
|
expect(accountMenuController.setShow).toHaveBeenCalledWith(true)
|
||||||
|
expect(accountMenuController.setCurrentPane).toHaveBeenCalledWith(AccountMenuPane.SignIn)
|
||||||
|
expect(subscriptionManager.acceptInvitation).not.toHaveBeenCalled()
|
||||||
|
expect(toastService.showToast).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should accept subscription invitation if user is logged in', async () => {
|
||||||
|
subscriptionManager.acceptInvitation = jest.fn().mockReturnValue({ success: true })
|
||||||
|
routeService.getRoute = jest.fn().mockReturnValue({
|
||||||
|
type: RouteType.AcceptSubscriptionInvite,
|
||||||
|
subscriptionInviteParams: {
|
||||||
|
inviteUuid: '1-2-3',
|
||||||
|
},
|
||||||
|
} as jest.Mocked<RouteParserInterface>)
|
||||||
|
|
||||||
|
await createObserver().handle(ApplicationEvent.Launched)
|
||||||
|
|
||||||
|
expect(subscriptionManager.acceptInvitation).toHaveBeenCalledWith('1-2-3')
|
||||||
|
expect(toastService.showToast).toHaveBeenCalledWith(
|
||||||
|
ToastType.Success,
|
||||||
|
'Successfully joined a shared subscription',
|
||||||
|
)
|
||||||
|
expect(routeService.removeQueryParameterFromURL).toHaveBeenCalledWith(RootQueryParam.AcceptSubscriptionInvite)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should show accept subscription invitation failure if user is logged in and accepting fails', async () => {
|
||||||
|
subscriptionManager.acceptInvitation = jest.fn().mockReturnValue({ success: false, message: 'Oops!' })
|
||||||
|
|
||||||
|
routeService.getRoute = jest.fn().mockReturnValue({
|
||||||
|
type: RouteType.AcceptSubscriptionInvite,
|
||||||
|
subscriptionInviteParams: {
|
||||||
|
inviteUuid: '1-2-3',
|
||||||
|
},
|
||||||
|
} as jest.Mocked<RouteParserInterface>)
|
||||||
|
|
||||||
|
await createObserver().handle(ApplicationEvent.Launched)
|
||||||
|
|
||||||
|
expect(subscriptionManager.acceptInvitation).toHaveBeenCalledWith('1-2-3')
|
||||||
|
expect(toastService.showToast).toHaveBeenCalledWith(ToastType.Error, 'Oops!')
|
||||||
|
expect(routeService.removeQueryParameterFromURL).toHaveBeenCalledWith(RootQueryParam.AcceptSubscriptionInvite)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Upon Signing In', () => {
|
describe('Upon Signing In', () => {
|
||||||
@@ -118,6 +195,25 @@ describe('ApplicationEventObserver', () => {
|
|||||||
expect(preferencesController.openPreferences).toHaveBeenCalled()
|
expect(preferencesController.openPreferences).toHaveBeenCalled()
|
||||||
expect(preferencesController.setCurrentPane).toHaveBeenCalledWith('general')
|
expect(preferencesController.setCurrentPane).toHaveBeenCalledWith('general')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should accept subscription invitation', async () => {
|
||||||
|
subscriptionManager.acceptInvitation = jest.fn().mockReturnValue({ success: true })
|
||||||
|
routeService.getRoute = jest.fn().mockReturnValue({
|
||||||
|
type: RouteType.AcceptSubscriptionInvite,
|
||||||
|
subscriptionInviteParams: {
|
||||||
|
inviteUuid: '1-2-3',
|
||||||
|
},
|
||||||
|
} as jest.Mocked<RouteParserInterface>)
|
||||||
|
|
||||||
|
await createObserver().handle(ApplicationEvent.SignedIn)
|
||||||
|
|
||||||
|
expect(subscriptionManager.acceptInvitation).toHaveBeenCalledWith('1-2-3')
|
||||||
|
expect(toastService.showToast).toHaveBeenCalledWith(
|
||||||
|
ToastType.Success,
|
||||||
|
'Successfully joined a shared subscription',
|
||||||
|
)
|
||||||
|
expect(routeService.removeQueryParameterFromURL).toHaveBeenCalledWith(RootQueryParam.AcceptSubscriptionInvite)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Upon Sync Status Changing', () => {
|
describe('Upon Sync Status Changing', () => {
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { RouteServiceInterface, RouteType } from '@standardnotes/ui-services'
|
import { RootQueryParam, RouteParserInterface, RouteServiceInterface, RouteType, ToastServiceInterface } from '@standardnotes/ui-services'
|
||||||
import { ApplicationEvent, SessionsClientInterface, SyncClientInterface } from '@standardnotes/snjs'
|
import {
|
||||||
|
ApplicationEvent,
|
||||||
|
SessionsClientInterface,
|
||||||
|
SubscriptionClientInterface,
|
||||||
|
SyncClientInterface,
|
||||||
|
} from '@standardnotes/snjs'
|
||||||
|
import { ToastType } from '@standardnotes/toast'
|
||||||
|
|
||||||
import { PurchaseFlowController } from '@/Controllers/PurchaseFlow/PurchaseFlowController'
|
import { PurchaseFlowController } from '@/Controllers/PurchaseFlow/PurchaseFlowController'
|
||||||
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
||||||
@@ -18,6 +24,8 @@ export class ApplicationEventObserver implements EventObserverInterface {
|
|||||||
private syncStatusController: SyncStatusController,
|
private syncStatusController: SyncStatusController,
|
||||||
private syncClient: SyncClientInterface,
|
private syncClient: SyncClientInterface,
|
||||||
private sessionManager: SessionsClientInterface,
|
private sessionManager: SessionsClientInterface,
|
||||||
|
private subscriptionManager: SubscriptionClientInterface,
|
||||||
|
private toastService: ToastServiceInterface,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: ApplicationEvent): Promise<void> {
|
async handle(event: ApplicationEvent): Promise<void> {
|
||||||
@@ -33,8 +41,7 @@ export class ApplicationEventObserver implements EventObserverInterface {
|
|||||||
case RouteType.Settings: {
|
case RouteType.Settings: {
|
||||||
const user = this.sessionManager.getUser()
|
const user = this.sessionManager.getUser()
|
||||||
if (user === undefined) {
|
if (user === undefined) {
|
||||||
this.accountMenuController.setShow(true)
|
this.promptUserSignIn()
|
||||||
this.accountMenuController.setCurrentPane(AccountMenuPane.SignIn)
|
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -43,6 +50,17 @@ export class ApplicationEventObserver implements EventObserverInterface {
|
|||||||
this.preferencesController.setCurrentPane(route.settingsParams.panel)
|
this.preferencesController.setCurrentPane(route.settingsParams.panel)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case RouteType.AcceptSubscriptionInvite: {
|
||||||
|
const user = this.sessionManager.getUser()
|
||||||
|
if (user === undefined) {
|
||||||
|
this.promptUserSignIn()
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
await this.acceptSubscriptionInvitation(route)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -54,6 +72,10 @@ export class ApplicationEventObserver implements EventObserverInterface {
|
|||||||
this.preferencesController.openPreferences()
|
this.preferencesController.openPreferences()
|
||||||
this.preferencesController.setCurrentPane(route.settingsParams.panel)
|
this.preferencesController.setCurrentPane(route.settingsParams.panel)
|
||||||
|
|
||||||
|
break
|
||||||
|
case RouteType.AcceptSubscriptionInvite:
|
||||||
|
await this.acceptSubscriptionInvitation(route)
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,4 +85,20 @@ export class ApplicationEventObserver implements EventObserverInterface {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private promptUserSignIn(): void {
|
||||||
|
this.accountMenuController.setShow(true)
|
||||||
|
this.accountMenuController.setCurrentPane(AccountMenuPane.SignIn)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async acceptSubscriptionInvitation(route: RouteParserInterface): Promise<void> {
|
||||||
|
const acceptResult = await this.subscriptionManager.acceptInvitation(route.subscriptionInviteParams.inviteUuid)
|
||||||
|
|
||||||
|
const toastType = acceptResult.success ? ToastType.Success : ToastType.Error
|
||||||
|
const toastMessage = acceptResult.success ? 'Successfully joined a shared subscription' : acceptResult.message
|
||||||
|
|
||||||
|
this.toastService.showToast(toastType, toastMessage)
|
||||||
|
|
||||||
|
this.routeService.removeQueryParameterFromURL(RootQueryParam.AcceptSubscriptionInvite)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user