feat(web): add accepting subscription invites from UI
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { InternalEventBus } from '@standardnotes/snjs'
|
||||
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 { WebApplication } from '@/Application/Application'
|
||||
|
||||
@@ -34,7 +34,7 @@ export class PreferencesController extends AbstractViewController {
|
||||
closePreferences = (): void => {
|
||||
this._open = false
|
||||
this.currentPane = DEFAULT_PANE
|
||||
this.application.routeService.removeSettingsFromURLQueryParameters()
|
||||
this.application.routeService.removeQueryParameterFromURL(RootQueryParam.Settings)
|
||||
}
|
||||
|
||||
get isOpen(): boolean {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
||||
import { destroyAllObjectProperties } from '@/Utils'
|
||||
@@ -71,6 +71,7 @@ export class ViewControllerManager implements InternalEventHandlerInterface {
|
||||
private subscriptionManager: SubscriptionClientInterface
|
||||
private persistenceService: PersistenceService
|
||||
private applicationEventObserver: EventObserverInterface
|
||||
private toastService: ToastServiceInterface
|
||||
|
||||
constructor(public application: WebApplication, private device: WebOrDesktopDeviceInterface) {
|
||||
this.eventBus = new InternalEventBus()
|
||||
@@ -146,6 +147,8 @@ export class ViewControllerManager implements InternalEventHandlerInterface {
|
||||
|
||||
this.historyModalController = new HistoryModalController(this.application, this.eventBus)
|
||||
|
||||
this.toastService = new ToastService()
|
||||
|
||||
this.applicationEventObserver = new ApplicationEventObserver(
|
||||
application.routeService,
|
||||
this.purchaseFlowController,
|
||||
@@ -154,6 +157,8 @@ export class ViewControllerManager implements InternalEventHandlerInterface {
|
||||
this.syncStatusController,
|
||||
application.sync,
|
||||
application.sessions,
|
||||
application.subscriptions,
|
||||
this.toastService,
|
||||
)
|
||||
|
||||
this.addAppEventObserver()
|
||||
|
||||
@@ -2,17 +2,30 @@
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { RouteServiceInterface, RouteType } from '@standardnotes/ui-services'
|
||||
import { ApplicationEvent, SessionsClientInterface, SyncClientInterface, SyncOpStatus, User } from '@standardnotes/snjs'
|
||||
import {
|
||||
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 { PreferencesController } from '@/Controllers/PreferencesController'
|
||||
import { PurchaseFlowController } from '@/Controllers/PurchaseFlow/PurchaseFlowController'
|
||||
import { SyncStatusController } from '@/Controllers/SyncStatusController'
|
||||
import { AccountMenuPane } from '@/Components/AccountMenu/AccountMenuPane'
|
||||
|
||||
import { ApplicationEventObserver } from './ApplicationEventObserver'
|
||||
import { RouteParserInterface } from '@standardnotes/ui-services/dist/Route/RouteParserInterface'
|
||||
import { AccountMenuPane } from '@/Components/AccountMenu/AccountMenuPane'
|
||||
|
||||
describe('ApplicationEventObserver', () => {
|
||||
let routeService: RouteServiceInterface
|
||||
@@ -22,6 +35,8 @@ describe('ApplicationEventObserver', () => {
|
||||
let syncStatusController: SyncStatusController
|
||||
let syncClient: SyncClientInterface
|
||||
let sessionManager: SessionsClientInterface
|
||||
let subscriptionManager: SubscriptionClientInterface
|
||||
let toastService: ToastServiceInterface
|
||||
|
||||
const createObserver = () =>
|
||||
new ApplicationEventObserver(
|
||||
@@ -32,6 +47,8 @@ describe('ApplicationEventObserver', () => {
|
||||
syncStatusController,
|
||||
syncClient,
|
||||
sessionManager,
|
||||
subscriptionManager,
|
||||
toastService,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -39,6 +56,7 @@ describe('ApplicationEventObserver', () => {
|
||||
routeService.getRoute = jest.fn().mockReturnValue({
|
||||
type: RouteType.None,
|
||||
} as jest.Mocked<RouteParserInterface>)
|
||||
routeService.removeQueryParameterFromURL = jest.fn()
|
||||
|
||||
purchaseFlowController = {} as jest.Mocked<PurchaseFlowController>
|
||||
purchaseFlowController.openPurchaseFlow = jest.fn()
|
||||
@@ -59,6 +77,12 @@ describe('ApplicationEventObserver', () => {
|
||||
|
||||
sessionManager = {} as jest.Mocked<SessionsClientInterface>
|
||||
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', () => {
|
||||
@@ -102,6 +126,59 @@ describe('ApplicationEventObserver', () => {
|
||||
expect(preferencesController.openPreferences).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', () => {
|
||||
@@ -118,6 +195,25 @@ describe('ApplicationEventObserver', () => {
|
||||
expect(preferencesController.openPreferences).toHaveBeenCalled()
|
||||
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', () => {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { RouteServiceInterface, RouteType } from '@standardnotes/ui-services'
|
||||
import { ApplicationEvent, SessionsClientInterface, SyncClientInterface } from '@standardnotes/snjs'
|
||||
import { RootQueryParam, RouteParserInterface, RouteServiceInterface, RouteType, ToastServiceInterface } from '@standardnotes/ui-services'
|
||||
import {
|
||||
ApplicationEvent,
|
||||
SessionsClientInterface,
|
||||
SubscriptionClientInterface,
|
||||
SyncClientInterface,
|
||||
} from '@standardnotes/snjs'
|
||||
import { ToastType } from '@standardnotes/toast'
|
||||
|
||||
import { PurchaseFlowController } from '@/Controllers/PurchaseFlow/PurchaseFlowController'
|
||||
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
||||
@@ -18,6 +24,8 @@ export class ApplicationEventObserver implements EventObserverInterface {
|
||||
private syncStatusController: SyncStatusController,
|
||||
private syncClient: SyncClientInterface,
|
||||
private sessionManager: SessionsClientInterface,
|
||||
private subscriptionManager: SubscriptionClientInterface,
|
||||
private toastService: ToastServiceInterface,
|
||||
) {}
|
||||
|
||||
async handle(event: ApplicationEvent): Promise<void> {
|
||||
@@ -33,8 +41,7 @@ export class ApplicationEventObserver implements EventObserverInterface {
|
||||
case RouteType.Settings: {
|
||||
const user = this.sessionManager.getUser()
|
||||
if (user === undefined) {
|
||||
this.accountMenuController.setShow(true)
|
||||
this.accountMenuController.setCurrentPane(AccountMenuPane.SignIn)
|
||||
this.promptUserSignIn()
|
||||
|
||||
break
|
||||
}
|
||||
@@ -43,6 +50,17 @@ export class ApplicationEventObserver implements EventObserverInterface {
|
||||
this.preferencesController.setCurrentPane(route.settingsParams.panel)
|
||||
break
|
||||
}
|
||||
case RouteType.AcceptSubscriptionInvite: {
|
||||
const user = this.sessionManager.getUser()
|
||||
if (user === undefined) {
|
||||
this.promptUserSignIn()
|
||||
|
||||
break
|
||||
}
|
||||
await this.acceptSubscriptionInvitation(route)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
@@ -54,6 +72,10 @@ export class ApplicationEventObserver implements EventObserverInterface {
|
||||
this.preferencesController.openPreferences()
|
||||
this.preferencesController.setCurrentPane(route.settingsParams.panel)
|
||||
|
||||
break
|
||||
case RouteType.AcceptSubscriptionInvite:
|
||||
await this.acceptSubscriptionInvitation(route)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -63,4 +85,20 @@ export class ApplicationEventObserver implements EventObserverInterface {
|
||||
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