feat: handle basic routes (#1784)
This commit is contained in:
14
packages/ui-services/src/Preferences/PreferenceId.ts
Normal file
14
packages/ui-services/src/Preferences/PreferenceId.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
const PREFERENCE_IDS = [
|
||||
'general',
|
||||
'account',
|
||||
'security',
|
||||
'appearance',
|
||||
'backups',
|
||||
'listed',
|
||||
'shortcuts',
|
||||
'accessibility',
|
||||
'get-free-month',
|
||||
'help-feedback',
|
||||
] as const
|
||||
|
||||
export type PreferenceId = typeof PREFERENCE_IDS[number]
|
||||
18
packages/ui-services/src/Route/RouteParams.ts
Normal file
18
packages/ui-services/src/Route/RouteParams.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
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
|
||||
}
|
||||
51
packages/ui-services/src/Route/RouteParser.spec.ts
Normal file
51
packages/ui-services/src/Route/RouteParser.spec.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { RouteParser } from './RouteParser'
|
||||
import { RouteType } from './RouteType'
|
||||
|
||||
describe('route parser', () => {
|
||||
it('routes to onboarding', () => {
|
||||
const url = 'https://app.standardnotes.com/onboard?from_homepage=true'
|
||||
const parser = new RouteParser(url)
|
||||
|
||||
expect(parser.type).toEqual(RouteType.Onboarding)
|
||||
expect(parser.onboardingParams.fromHomepage).toEqual(true)
|
||||
})
|
||||
|
||||
it('routes to demo', () => {
|
||||
const url = 'https://app-demo.standardnotes.com/?demo-token=eyJhY2Nlc3NUb2tl'
|
||||
const parser = new RouteParser(url)
|
||||
|
||||
expect(parser.type).toEqual(RouteType.Demo)
|
||||
expect(parser.demoParams.token).toEqual('eyJhY2Nlc3NUb2tl')
|
||||
})
|
||||
|
||||
it('routes to settings', () => {
|
||||
const url = 'https://app.standardnotes.com/?settings=account'
|
||||
const parser = new RouteParser(url)
|
||||
|
||||
expect(parser.type).toEqual(RouteType.Settings)
|
||||
expect(parser.settingsParams.panel).toEqual('account')
|
||||
})
|
||||
|
||||
it('routes to purchase', () => {
|
||||
const url = 'https://app.standardnotes.com/?purchase=true&plan=PLUS_PLAN&period=year'
|
||||
const parser = new RouteParser(url)
|
||||
|
||||
expect(parser.type).toEqual(RouteType.Purchase)
|
||||
expect(parser.purchaseParams.period).toEqual('year')
|
||||
expect(parser.purchaseParams.plan).toEqual('PLUS_PLAN')
|
||||
})
|
||||
|
||||
it('routes to none', () => {
|
||||
const url = 'https://app.standardnotes.com/unknown?foo=bar'
|
||||
const parser = new RouteParser(url)
|
||||
|
||||
expect(parser.type).toEqual(RouteType.None)
|
||||
})
|
||||
|
||||
it('accessing wrong params should throw', () => {
|
||||
const url = 'https://app.standardnotes.com/item?uuid=123'
|
||||
const parser = new RouteParser(url)
|
||||
|
||||
expect(() => parser.onboardingParams).toThrowError('Accessing invalid params')
|
||||
})
|
||||
})
|
||||
88
packages/ui-services/src/Route/RouteParser.ts
Normal file
88
packages/ui-services/src/Route/RouteParser.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { PreferenceId } from './../Preferences/PreferenceId'
|
||||
import { DemoParams, OnboardingParams, PurchaseParams, SettingsParams } from './RouteParams'
|
||||
import { RouteType } from './RouteType'
|
||||
|
||||
enum RootRoutes {
|
||||
Onboarding = '/onboard',
|
||||
None = '/',
|
||||
}
|
||||
|
||||
enum RootQueryParam {
|
||||
Purchase = 'purchase',
|
||||
Settings = 'settings',
|
||||
DemoToken = 'demo-token',
|
||||
}
|
||||
|
||||
export class RouteParser {
|
||||
private url: URL
|
||||
private readonly path: string
|
||||
public readonly type: RouteType
|
||||
private readonly searchParams: URLSearchParams
|
||||
|
||||
constructor(url: string) {
|
||||
this.url = new URL(url)
|
||||
this.path = this.url.pathname
|
||||
this.searchParams = this.url.searchParams
|
||||
|
||||
const pathUsesRootQueryParams = this.path === RootRoutes.None
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get demoParams(): DemoParams {
|
||||
if (this.type !== RouteType.Demo) {
|
||||
throw new Error('Accessing invalid params')
|
||||
}
|
||||
|
||||
return {
|
||||
token: this.searchParams.get(RootQueryParam.DemoToken) as string,
|
||||
}
|
||||
}
|
||||
|
||||
get settingsParams(): SettingsParams {
|
||||
if (this.type !== RouteType.Settings) {
|
||||
throw new Error('Accessing invalid params')
|
||||
}
|
||||
|
||||
return {
|
||||
panel: this.searchParams.get(RootQueryParam.Settings) as PreferenceId,
|
||||
}
|
||||
}
|
||||
|
||||
get purchaseParams(): PurchaseParams {
|
||||
if (this.type !== RouteType.Purchase) {
|
||||
throw new Error('Accessing invalid params')
|
||||
}
|
||||
|
||||
return {
|
||||
plan: this.searchParams.get('plan') as string,
|
||||
period: this.searchParams.get('period') as string,
|
||||
}
|
||||
}
|
||||
|
||||
get onboardingParams(): OnboardingParams {
|
||||
if (this.type !== RouteType.Onboarding) {
|
||||
throw new Error('Accessing invalid params')
|
||||
}
|
||||
|
||||
return {
|
||||
fromHomepage: !!this.searchParams.get('from_homepage'),
|
||||
}
|
||||
}
|
||||
}
|
||||
50
packages/ui-services/src/Route/RouteService.ts
Normal file
50
packages/ui-services/src/Route/RouteService.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import {
|
||||
AbstractService,
|
||||
ApplicationEvent,
|
||||
ApplicationInterface,
|
||||
InternalEventBusInterface,
|
||||
} from '@standardnotes/services'
|
||||
import { RouteParser } from './RouteParser'
|
||||
import { RouteServiceEvent } from './RouteServiceEvent'
|
||||
|
||||
export class RouteService extends AbstractService<RouteServiceEvent, RouteParser> {
|
||||
private unsubApp!: () => void
|
||||
|
||||
constructor(
|
||||
private application: ApplicationInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
this.addAppEventObserver()
|
||||
}
|
||||
|
||||
override deinit() {
|
||||
super.deinit()
|
||||
;(this.application as unknown) = undefined
|
||||
this.unsubApp()
|
||||
}
|
||||
|
||||
public getRoute(): RouteParser {
|
||||
return new RouteParser(window.location.href)
|
||||
}
|
||||
|
||||
public removeSettingsFromURLQueryParameters() {
|
||||
const urlSearchParams = new URLSearchParams(window.location.search)
|
||||
urlSearchParams.delete('settings')
|
||||
|
||||
const newUrl = `${window.location.origin}${window.location.pathname}${urlSearchParams.toString()}`
|
||||
window.history.replaceState(null, document.title, newUrl)
|
||||
}
|
||||
|
||||
private addAppEventObserver() {
|
||||
this.unsubApp = this.application.addEventObserver(async (event: ApplicationEvent) => {
|
||||
if (event === ApplicationEvent.LocalDataLoaded) {
|
||||
void this.notifyRouteChange()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private notifyRouteChange() {
|
||||
void this.notifyEvent(RouteServiceEvent.RouteChanged, this.getRoute())
|
||||
}
|
||||
}
|
||||
3
packages/ui-services/src/Route/RouteServiceEvent.ts
Normal file
3
packages/ui-services/src/Route/RouteServiceEvent.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export enum RouteServiceEvent {
|
||||
RouteChanged = 'route-changed',
|
||||
}
|
||||
7
packages/ui-services/src/Route/RouteType.ts
Normal file
7
packages/ui-services/src/Route/RouteType.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export enum RouteType {
|
||||
Onboarding = 'onboarding',
|
||||
Settings = 'settings',
|
||||
Purchase = 'purchase',
|
||||
Demo = 'demo',
|
||||
None = 'none',
|
||||
}
|
||||
@@ -2,6 +2,12 @@ export * from './Alert/Functions'
|
||||
export * from './Alert/WebAlertService'
|
||||
export * from './Archive/ArchiveManager'
|
||||
export * from './IO/IOService'
|
||||
export * from './Preferences/PreferenceId'
|
||||
export * from './Route/RouteParams'
|
||||
export * from './Route/RouteParser'
|
||||
export * from './Route/RouteType'
|
||||
export * from './Route/RouteService'
|
||||
export * from './Route/RouteServiceEvent'
|
||||
export * from './Security/AutolockService'
|
||||
export * from './Storage/LocalStorage'
|
||||
export * from './Theme/ThemeManager'
|
||||
|
||||
Reference in New Issue
Block a user