feat: add accepting subscription invite route type

This commit is contained in:
Karol Sójko
2022-10-21 10:05:31 +02:00
parent 30310dbb45
commit 83b484002c
15 changed files with 115 additions and 63 deletions

View File

@@ -1,4 +1,7 @@
{
"extends": ["./node_modules/@standardnotes/config/src/.eslintrc"],
"rules": {
"max-classes-per-file": ["error", 1]
},
"ignorePatterns": [".eslintrc.js", "*.webpack.*.js", "webpack-defaults.js", "jest.config.js", "__mocks__", "**/**/coverage"]
}

View File

@@ -0,0 +1,3 @@
export type DemoParams = {
token: string
}

View File

@@ -0,0 +1,3 @@
export type OnboardingParams = {
fromHomepage: boolean
}

View File

@@ -0,0 +1,4 @@
export type PurchaseParams = {
plan: string
period: string
}

View File

@@ -0,0 +1,5 @@
import { PreferenceId } from '../../Preferences/PreferenceId'
export type SettingsParams = {
panel: PreferenceId
}

View File

@@ -0,0 +1,5 @@
import { Uuid } from '@standardnotes/common'
export type SubscriptionInviteParams = {
inviteUuid: Uuid
}

View File

@@ -0,0 +1,6 @@
export enum RootQueryParam {
Purchase = 'purchase',
Settings = 'settings',
DemoToken = 'demo-token',
AcceptSubscriptionInvite = 'accept-subscription-invite',
}

View File

@@ -0,0 +1,4 @@
export enum RootRoutes {
Onboarding = '/onboard',
None = '/',
}

View File

@@ -1,18 +0,0 @@
import { PreferenceId } from '../Preferences/PreferenceId'
export type OnboardingParams = {
fromHomepage: boolean
}
export type SettingsParams = {
panel: PreferenceId
}
export type DemoParams = {
token: string
}
export type PurchaseParams = {
plan: string
period: string
}

View File

@@ -48,4 +48,12 @@ describe('route parser', () => {
expect(() => parser.onboardingParams).toThrowError('Accessing invalid params')
})
it('routes to subscription sharing', () => {
const url = 'https://app.standardnotes.com/?accept-subscription-invite=1-2-3'
const parser = new RouteParser(url)
expect(parser.type).toEqual(RouteType.AcceptSubscriptionInvite)
expect(parser.subscriptionInviteParams.inviteUuid).toEqual('1-2-3')
})
})

View File

@@ -1,19 +1,17 @@
import { Uuid } from '@standardnotes/common'
import { PreferenceId } from './../Preferences/PreferenceId'
import { DemoParams, OnboardingParams, PurchaseParams, SettingsParams } from './RouteParams'
import { DemoParams } from './Params/DemoParams'
import { OnboardingParams } from './Params/OnboardingParams'
import { PurchaseParams } from './Params/PurchaseParams'
import { SettingsParams } from './Params/SettingsParams'
import { SubscriptionInviteParams } from './Params/SubscriptionInviteParams'
import { RootQueryParam } from './RootQueryParam'
import { RootRoutes } from './RootRoutes'
import { RouteParserInterface } from './RouteParserInterface'
import { RouteType } from './RouteType'
enum RootRoutes {
Onboarding = '/onboard',
None = '/',
}
enum RootQueryParam {
Purchase = 'purchase',
Settings = 'settings',
DemoToken = 'demo-token',
}
export class RouteParser {
export class RouteParser implements RouteParserInterface {
private url: URL
private readonly path: string
public readonly type: RouteType
@@ -23,32 +21,19 @@ export class RouteParser {
this.url = new URL(url)
this.path = this.url.pathname
this.searchParams = this.url.searchParams
this.type = this.parseTypeFromQueryParameters()
}
const pathUsesRootQueryParams = this.path === RootRoutes.None
get subscriptionInviteParams(): SubscriptionInviteParams {
this.checkForProperRouteType(RouteType.AcceptSubscriptionInvite)
if (pathUsesRootQueryParams) {
if (this.searchParams.has(RootQueryParam.Purchase)) {
this.type = RouteType.Purchase
} else if (this.searchParams.has(RootQueryParam.Settings)) {
this.type = RouteType.Settings
} else if (this.searchParams.has(RootQueryParam.DemoToken)) {
this.type = RouteType.Demo
} else {
this.type = RouteType.None
}
} else {
if (this.path === RootRoutes.Onboarding) {
this.type = RouteType.Onboarding
} else {
this.type = RouteType.None
}
return {
inviteUuid: this.searchParams.get(RootQueryParam.AcceptSubscriptionInvite) as Uuid,
}
}
get demoParams(): DemoParams {
if (this.type !== RouteType.Demo) {
throw new Error('Accessing invalid params')
}
this.checkForProperRouteType(RouteType.Demo)
return {
token: this.searchParams.get(RootQueryParam.DemoToken) as string,
@@ -56,9 +41,7 @@ export class RouteParser {
}
get settingsParams(): SettingsParams {
if (this.type !== RouteType.Settings) {
throw new Error('Accessing invalid params')
}
this.checkForProperRouteType(RouteType.Settings)
return {
panel: this.searchParams.get(RootQueryParam.Settings) as PreferenceId,
@@ -66,9 +49,7 @@ export class RouteParser {
}
get purchaseParams(): PurchaseParams {
if (this.type !== RouteType.Purchase) {
throw new Error('Accessing invalid params')
}
this.checkForProperRouteType(RouteType.Purchase)
return {
plan: this.searchParams.get('plan') as string,
@@ -77,12 +58,41 @@ export class RouteParser {
}
get onboardingParams(): OnboardingParams {
if (this.type !== RouteType.Onboarding) {
throw new Error('Accessing invalid params')
}
this.checkForProperRouteType(RouteType.Onboarding)
return {
fromHomepage: !!this.searchParams.get('from_homepage'),
}
}
private checkForProperRouteType(type: RouteType): void {
if (this.type !== type) {
throw new Error('Accessing invalid params')
}
}
private parseTypeFromQueryParameters(): RouteType {
if (this.path === RootRoutes.Onboarding) {
return RouteType.Onboarding
}
if (this.path !== RootRoutes.None) {
return RouteType.None
}
const rootQueryParametersMap: Map<RootQueryParam, RouteType> = new Map([
[RootQueryParam.Purchase, RouteType.Purchase],
[RootQueryParam.Settings, RouteType.Settings],
[RootQueryParam.DemoToken, RouteType.Demo],
[RootQueryParam.AcceptSubscriptionInvite, RouteType.AcceptSubscriptionInvite],
])
for (const rootQueryParam of rootQueryParametersMap.keys()) {
if (this.searchParams.has(rootQueryParam)) {
return rootQueryParametersMap.get(rootQueryParam) as RouteType
}
}
return RouteType.None
}
}

View File

@@ -0,0 +1,13 @@
import { DemoParams } from './Params/DemoParams'
import { OnboardingParams } from './Params/OnboardingParams'
import { PurchaseParams } from './Params/PurchaseParams'
import { SettingsParams } from './Params/SettingsParams'
import { SubscriptionInviteParams } from './Params/SubscriptionInviteParams'
export interface RouteParserInterface {
get demoParams(): DemoParams
get settingsParams(): SettingsParams
get purchaseParams(): PurchaseParams
get onboardingParams(): OnboardingParams
get subscriptionInviteParams(): SubscriptionInviteParams
}

View File

@@ -5,9 +5,10 @@ import {
InternalEventBusInterface,
} from '@standardnotes/services'
import { RouteParser } from './RouteParser'
import { RouteParserInterface } from './RouteParserInterface'
import { RouteServiceEvent } from './RouteServiceEvent'
export class RouteService extends AbstractService<RouteServiceEvent, RouteParser> {
export class RouteService extends AbstractService<RouteServiceEvent, RouteParserInterface> {
private unsubApp!: () => void
constructor(
@@ -24,7 +25,7 @@ export class RouteService extends AbstractService<RouteServiceEvent, RouteParser
this.unsubApp()
}
public getRoute(): RouteParser {
public getRoute(): RouteParserInterface {
return new RouteParser(window.location.href)
}

View File

@@ -2,6 +2,7 @@ export enum RouteType {
Onboarding = 'onboarding',
Settings = 'settings',
Purchase = 'purchase',
AcceptSubscriptionInvite = 'accept-subscription-invite',
Demo = 'demo',
None = 'none',
}

View File

@@ -3,7 +3,11 @@ export * from './Alert/WebAlertService'
export * from './Archive/ArchiveManager'
export * from './IO/IOService'
export * from './Preferences/PreferenceId'
export * from './Route/RouteParams'
export * from './Route/Params/DemoParams'
export * from './Route/Params/OnboardingParams'
export * from './Route/Params/PurchaseParams'
export * from './Route/Params/SettingsParams'
export * from './Route/Params/SubscriptionInviteParams'
export * from './Route/RouteParser'
export * from './Route/RouteType'
export * from './Route/RouteService'