diff --git a/.eslintrc.json b/.eslintrc.json index 9a0c1e6f3..71577cd76 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -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"] } diff --git a/packages/ui-services/src/Route/Params/DemoParams.ts b/packages/ui-services/src/Route/Params/DemoParams.ts new file mode 100644 index 000000000..f7d8ba72f --- /dev/null +++ b/packages/ui-services/src/Route/Params/DemoParams.ts @@ -0,0 +1,3 @@ +export type DemoParams = { + token: string +} diff --git a/packages/ui-services/src/Route/Params/OnboardingParams.ts b/packages/ui-services/src/Route/Params/OnboardingParams.ts new file mode 100644 index 000000000..dc3db6a7d --- /dev/null +++ b/packages/ui-services/src/Route/Params/OnboardingParams.ts @@ -0,0 +1,3 @@ +export type OnboardingParams = { + fromHomepage: boolean +} diff --git a/packages/ui-services/src/Route/Params/PurchaseParams.ts b/packages/ui-services/src/Route/Params/PurchaseParams.ts new file mode 100644 index 000000000..64a15a06e --- /dev/null +++ b/packages/ui-services/src/Route/Params/PurchaseParams.ts @@ -0,0 +1,4 @@ +export type PurchaseParams = { + plan: string + period: string +} diff --git a/packages/ui-services/src/Route/Params/SettingsParams.ts b/packages/ui-services/src/Route/Params/SettingsParams.ts new file mode 100644 index 000000000..c77a4de7b --- /dev/null +++ b/packages/ui-services/src/Route/Params/SettingsParams.ts @@ -0,0 +1,5 @@ +import { PreferenceId } from '../../Preferences/PreferenceId' + +export type SettingsParams = { + panel: PreferenceId +} diff --git a/packages/ui-services/src/Route/Params/SubscriptionInviteParams.ts b/packages/ui-services/src/Route/Params/SubscriptionInviteParams.ts new file mode 100644 index 000000000..979f70ea9 --- /dev/null +++ b/packages/ui-services/src/Route/Params/SubscriptionInviteParams.ts @@ -0,0 +1,5 @@ +import { Uuid } from '@standardnotes/common' + +export type SubscriptionInviteParams = { + inviteUuid: Uuid +} diff --git a/packages/ui-services/src/Route/RootQueryParam.ts b/packages/ui-services/src/Route/RootQueryParam.ts new file mode 100644 index 000000000..1b2c054da --- /dev/null +++ b/packages/ui-services/src/Route/RootQueryParam.ts @@ -0,0 +1,6 @@ +export enum RootQueryParam { + Purchase = 'purchase', + Settings = 'settings', + DemoToken = 'demo-token', + AcceptSubscriptionInvite = 'accept-subscription-invite', +} diff --git a/packages/ui-services/src/Route/RootRoutes.ts b/packages/ui-services/src/Route/RootRoutes.ts new file mode 100644 index 000000000..436fd04f0 --- /dev/null +++ b/packages/ui-services/src/Route/RootRoutes.ts @@ -0,0 +1,4 @@ +export enum RootRoutes { + Onboarding = '/onboard', + None = '/', +} diff --git a/packages/ui-services/src/Route/RouteParams.ts b/packages/ui-services/src/Route/RouteParams.ts deleted file mode 100644 index 3cf73c08b..000000000 --- a/packages/ui-services/src/Route/RouteParams.ts +++ /dev/null @@ -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 -} diff --git a/packages/ui-services/src/Route/RouteParser.spec.ts b/packages/ui-services/src/Route/RouteParser.spec.ts index f242f586c..011d63b8d 100644 --- a/packages/ui-services/src/Route/RouteParser.spec.ts +++ b/packages/ui-services/src/Route/RouteParser.spec.ts @@ -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') + }) }) diff --git a/packages/ui-services/src/Route/RouteParser.ts b/packages/ui-services/src/Route/RouteParser.ts index 09ecfe741..d33ea2c53 100644 --- a/packages/ui-services/src/Route/RouteParser.ts +++ b/packages/ui-services/src/Route/RouteParser.ts @@ -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 = 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 + } } diff --git a/packages/ui-services/src/Route/RouteParserInterface.ts b/packages/ui-services/src/Route/RouteParserInterface.ts new file mode 100644 index 000000000..adf2e21fa --- /dev/null +++ b/packages/ui-services/src/Route/RouteParserInterface.ts @@ -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 +} diff --git a/packages/ui-services/src/Route/RouteService.ts b/packages/ui-services/src/Route/RouteService.ts index e71a1ce2e..c91965584 100644 --- a/packages/ui-services/src/Route/RouteService.ts +++ b/packages/ui-services/src/Route/RouteService.ts @@ -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 { +export class RouteService extends AbstractService { private unsubApp!: () => void constructor( @@ -24,7 +25,7 @@ export class RouteService extends AbstractService