feat: sharing subscriptions UI (#1567)

* feat(web): add ui for subscription sharing

* fix(web): add missing triggers

* fix(snjs): setting authorization token on http service

* fix(web): add alert upon invite failure

* fix(web): display invitations list

* fix(web): canceling subscription invitations

* fix(web): fonts

* fix(web): linter issues

* fix: click event handler

* fix: styles

* feat: update styles

* feat: don't show bottom separator if all invites used

* fix(web): references to alert service

* fix(web): remove usebeforeunload

Co-authored-by: Aman Harwara <amanharwara@protonmail.com>
This commit is contained in:
Karol Sójko
2022-09-15 12:00:29 +02:00
committed by GitHub
parent 05068ef63a
commit 2d0ee10226
13 changed files with 462 additions and 21 deletions

View File

@@ -4,6 +4,10 @@ import {
ClientDisplayableError,
convertTimestampToMilliseconds,
InternalEventBus,
Invitation,
InvitationStatus,
SubscriptionClientInterface,
Uuid,
} from '@standardnotes/snjs'
import { action, computed, makeObservable, observable } from 'mobx'
import { WebApplication } from '../../Application/Application'
@@ -12,28 +16,40 @@ import { AvailableSubscriptions } from './AvailableSubscriptionsType'
import { Subscription } from './SubscriptionType'
export class SubscriptionController extends AbstractViewController {
private readonly ALLOWED_SUBSCRIPTION_INVITATIONS = 5
userSubscription: Subscription | undefined = undefined
availableSubscriptions: AvailableSubscriptions | undefined = undefined
subscriptionInvitations: Invitation[] | undefined = undefined
override deinit() {
super.deinit()
;(this.userSubscription as unknown) = undefined
;(this.availableSubscriptions as unknown) = undefined
;(this.subscriptionInvitations as unknown) = undefined
destroyAllObjectProperties(this)
}
constructor(application: WebApplication, eventBus: InternalEventBus) {
constructor(
application: WebApplication,
eventBus: InternalEventBus,
private subscriptionManager: SubscriptionClientInterface,
) {
super(application, eventBus)
makeObservable(this, {
userSubscription: observable,
availableSubscriptions: observable,
subscriptionInvitations: observable,
userSubscriptionName: computed,
userSubscriptionExpirationDate: computed,
isUserSubscriptionExpired: computed,
isUserSubscriptionCanceled: computed,
usedInvitationsCount: computed,
allowedInvitationsCount: computed,
allInvitationsUsed: computed,
setUserSubscription: action,
setAvailableSubscriptions: action,
@@ -43,6 +59,7 @@ export class SubscriptionController extends AbstractViewController {
application.addEventObserver(async () => {
if (application.hasAccount()) {
this.getSubscriptionInfo().catch(console.error)
this.reloadSubscriptionInvitations().catch(console.error)
}
}, ApplicationEvent.Launched),
)
@@ -50,12 +67,14 @@ export class SubscriptionController extends AbstractViewController {
this.disposers.push(
application.addEventObserver(async () => {
this.getSubscriptionInfo().catch(console.error)
this.reloadSubscriptionInvitations().catch(console.error)
}, ApplicationEvent.SignedIn),
)
this.disposers.push(
application.addEventObserver(async () => {
this.getSubscriptionInfo().catch(console.error)
this.reloadSubscriptionInvitations().catch(console.error)
}, ApplicationEvent.UserRolesChanged),
)
}
@@ -91,6 +110,22 @@ export class SubscriptionController extends AbstractViewController {
return Boolean(this.userSubscription?.cancelled)
}
get usedInvitationsCount(): number {
return (
this.subscriptionInvitations?.filter((invitation) =>
[InvitationStatus.Accepted, InvitationStatus.Sent].includes(invitation.status),
).length ?? 0
)
}
get allowedInvitationsCount(): number {
return this.ALLOWED_SUBSCRIPTION_INVITATIONS
}
get allInvitationsUsed(): boolean {
return this.usedInvitationsCount === this.ALLOWED_SUBSCRIPTION_INVITATIONS
}
public setUserSubscription(subscription: Subscription): void {
this.userSubscription = subscription
}
@@ -99,6 +134,26 @@ export class SubscriptionController extends AbstractViewController {
this.availableSubscriptions = subscriptions
}
async sendSubscriptionInvitation(inviteeEmail: string): Promise<boolean> {
const success = await this.subscriptionManager.inviteToSubscription(inviteeEmail)
if (success) {
await this.reloadSubscriptionInvitations()
}
return success
}
async cancelSubscriptionInvitation(invitationUuid: Uuid): Promise<boolean> {
const success = await this.subscriptionManager.cancelInvitation(invitationUuid)
if (success) {
await this.reloadSubscriptionInvitations()
}
return success
}
private async getAvailableSubscriptions() {
try {
const subscriptions = await this.application.getAvailableSubscriptions()
@@ -125,4 +180,8 @@ export class SubscriptionController extends AbstractViewController {
await this.getSubscription()
await this.getAvailableSubscriptions()
}
private async reloadSubscriptionInvitations(): Promise<void> {
this.subscriptionInvitations = await this.subscriptionManager.listSubscriptionInvitations()
}
}

View File

@@ -9,6 +9,7 @@ import {
InternalEventBus,
ItemCounterInterface,
ItemCounter,
SubscriptionClientInterface,
} from '@standardnotes/snjs'
import { action, makeObservable, observable } from 'mobx'
import { ActionsMenuController } from './ActionsMenuController'
@@ -60,12 +61,15 @@ export class ViewControllerManager {
private appEventObserverRemovers: (() => void)[] = []
private eventBus: InternalEventBus
private itemCounter: ItemCounterInterface
private subscriptionManager: SubscriptionClientInterface
constructor(public application: WebApplication, private device: WebOrDesktopDeviceInterface) {
this.eventBus = new InternalEventBus()
this.itemCounter = new ItemCounter()
this.subscriptionManager = application.subscriptions
this.selectionController = new SelectedItemsController(application, this.eventBus)
this.noteTagsController = new NoteTagsController(application, this.eventBus)
@@ -102,7 +106,7 @@ export class ViewControllerManager {
this.accountMenuController = new AccountMenuController(application, this.eventBus, this.itemCounter)
this.subscriptionController = new SubscriptionController(application, this.eventBus)
this.subscriptionController = new SubscriptionController(application, this.eventBus, this.subscriptionManager)
this.purchaseFlowController = new PurchaseFlowController(application, this.eventBus)