diff options
Diffstat (limited to 'src/jet/models')
| -rw-r--r-- | src/jet/models/error-page.ts | 15 | ||||
| -rw-r--r-- | src/jet/models/external-action.ts | 7 | ||||
| -rw-r--r-- | src/jet/models/flow-action.ts | 28 | ||||
| -rw-r--r-- | src/jet/models/page.ts | 177 | ||||
| -rw-r--r-- | src/jet/models/static-message-page.ts | 33 |
5 files changed, 260 insertions, 0 deletions
diff --git a/src/jet/models/error-page.ts b/src/jet/models/error-page.ts new file mode 100644 index 0000000..80bcdf5 --- /dev/null +++ b/src/jet/models/error-page.ts @@ -0,0 +1,15 @@ +import { GenericPage } from '@jet-app/app-store/api/models'; +import type { Opt } from '@jet/environment'; + +export class ErrorPage extends GenericPage { + constructor({ error }: { error: Opt<Error> }) { + super([]); + this.error = error; + } + + // Used in our type guards to narrow a `Page` down to a `ErrorPage` + pageType: string = 'errorPage'; + + // The browser `Error`, used to determine which message to display to the user + error: Opt<Error>; +} diff --git a/src/jet/models/external-action.ts b/src/jet/models/external-action.ts new file mode 100644 index 0000000..25dbd14 --- /dev/null +++ b/src/jet/models/external-action.ts @@ -0,0 +1,7 @@ +import type { Action, ExternalUrlAction } from '@jet-app/app-store/api/models'; + +export function isExternalUrlAction( + action: Action, +): action is ExternalUrlAction { + return action.$kind === 'ExternalUrlAction'; +} diff --git a/src/jet/models/flow-action.ts b/src/jet/models/flow-action.ts new file mode 100644 index 0000000..d5edb40 --- /dev/null +++ b/src/jet/models/flow-action.ts @@ -0,0 +1,28 @@ +import type { Intent } from '@jet/environment/dispatching'; +import { FlowAction } from '@jet-app/app-store/api/models'; + +export const FLOW_ACTION_KIND: FlowAction['$kind'] = 'flowAction'; + +/** + * Creates a FlowAction For a given destination. + * + * Note: this is only here temporarily as a convenience for the "web" client, to be used + * while the upstream `FlowAction` is represented as a class that needs to be constructed, + * so those details are abstracted away from our codebase. Once `FlowAction` has been + * migrated to a POJO, there should be a factory-function provided that we should leverage + * instead + * + * @param destination Destination of the `FlowAction` + */ +export function makeFlowAction(destination: Intent<unknown>): FlowAction { + const action = new FlowAction( + // This data is only used by the Jet app's `PageRouter` architecture, which is not + // relevant for us. We should safely be able to pass an arbitrary value here. + 'page', + ); + + // The important part for the "web" client router: setting the `destination` + action.destination = destination; + + return action; +} diff --git a/src/jet/models/page.ts b/src/jet/models/page.ts new file mode 100644 index 0000000..a05e59f --- /dev/null +++ b/src/jet/models/page.ts @@ -0,0 +1,177 @@ +import type { + ArticlePage, + ChartsHubPage, + GenericPage, + SearchLandingPage, + SearchResultsPage, + ShelfBasedProductPage, + TopChartsPage, + TodayPage, + SeeAllPage, +} from '@jet-app/app-store/api/models'; +import { StaticMessagePage } from '~/jet/models/static-message-page'; +import { isObject } from '~/utils/types'; +import { ErrorPage } from './error-page'; +import type { WebRenderablePage } from 'node_modules/@jet-app/app-store/src/api/models/web-renderable-page'; + +/** + * The union of every type of page that the App Store Onyx app can render + */ +export type Page = ( + | ArticlePage + | ChartsHubPage + | GenericPage + | SearchLandingPage + | SearchResultsPage + | ShelfBasedProductPage + | StaticMessagePage + | TopChartsPage + | TodayPage + | ErrorPage +) & + // TS needs to be told this explicitly, even though all the above implement this + WebRenderablePage; + +/** + * Detects if {@linkcode page} is actually an {@linkcode AppEventDetailPage} + */ +export function isAppEventDetailPage(page: Page): page is GenericPage { + return ( + 'shelves' in page && + page.shelves.some(({ contentType }) => contentType === 'appEventDetail') + ); +} + +/** + * Detects if {@linkcode page} is actually an {@linkcode ArticlePage} + */ +export function isArticlePage(page: Page): page is ArticlePage { + return 'card' in page && 'shelves' in page; +} + +/** + * Detects if {@linkcode page} is actually a {@linkcode ChartsHubPage} + */ +export function isChartsHubPage(page: Page): page is ChartsHubPage { + return 'charts' in page; +} + +/** + * Detects if {@linkcode page} is actually a {@linkcode GenericPage} + */ +export function isGenericPage(page: Page): page is GenericPage { + return 'shelves' in page; +} + +/** + * Detects if {@linkcode page} is actually a {@linkcode ShelfBasedProductPage} + */ +export function isShelfBasedProductPage( + page: Page, +): page is ShelfBasedProductPage { + return 'shelfMapping' in page && !('seeAllType' in page); +} + +/** + * Detects if {@linkcode page} is actually a {@linkcode SeeAllPage} + */ +export function isSeeAllPage(page: Page): page is SeeAllPage { + return 'seeAllType' in page; +} + +/** + * Detects if {@linkcode page} is actually a {@linkcode SearchLandingPage} + */ +export function isSearchLandingPage(page: Page): page is SearchLandingPage { + return 'adIncidents' in page; +} + +/** + * Detects if {@linkcode page} is actually a {@linkcode SearchResultsPage} + */ +export function isSearchResultsPage(page: Page): page is SearchResultsPage { + return 'searchClearAction' in page || 'searchCancelAction' in page; +} + +/** + * Detects if {@linkcode page} is actually a {@linkcode TopChartsPage} + */ +export function isTopChartsPage(page: Page): page is TopChartsPage { + return 'segments' in page && 'categories' in page; +} + +/** + * Detects if {@linkcode page} is actually a {@linkcode TodayPage} + */ +export function isTodayPage(page: Page): page is TodayPage { + return 'titleDetail' in page; +} + +/** + * Detects if {@linkcode page} is actually a {@linkcode StaticMessagePage} + */ +export function isStaticMessagePage( + page: GenericPage, +): page is StaticMessagePage { + return 'pageType' in page && page.pageType === 'staticMessagePage'; +} + +export function isErrorPage(page: GenericPage) { + return 'pageType' in page && page.pageType === 'errorPage'; +} + +/** + * Type-guard that determines if the provided {@linkcode page} matches a renderable {@linkcode Page} definition + */ +export function isPage(page: unknown): page is Page { + if (!isObject(page)) { + return false; + } + + return [ + isAppEventDetailPage, + isArticlePage, + isChartsHubPage, + isGenericPage, + isShelfBasedProductPage, + isSearchLandingPage, + isSearchResultsPage, + isTopChartsPage, + isTodayPage, + isErrorPage, + isSeeAllPage, + ].some((specificPageTypePredicate) => + specificPageTypePredicate( + // This type-cast reflects the fact that we don't really know if `page` is really a `Page`, + // but that we're going to use the type-guards of our `Page` members to see if `page` looks + // like one of them + page as Page, + ), + ); +} + +/** + * Type-assertion that determines if the provided {@linkcode page} matches a renderable {@linkcode Page} definition + */ +export function assertIsPage(page: unknown): asserts page is Page { + if (!isPage(page)) { + throw new Error( + 'The view-model for the dispatched `Intent` does not match a known renderable shape', + ); + } +} + +/** + * Detects if {@linkcode page} has the Vision Pro pathname in it's URL. + */ +export function hasVisionProUrl(page: GenericPage) { + if (!page.canonicalURL) { + return false; + } + + const url = new URL(page.canonicalURL); + return ( + url.pathname.includes('/vision/apps-and-games') || + url.pathname.includes('/vision/arcade') + ); +} diff --git a/src/jet/models/static-message-page.ts b/src/jet/models/static-message-page.ts new file mode 100644 index 0000000..91dafb0 --- /dev/null +++ b/src/jet/models/static-message-page.ts @@ -0,0 +1,33 @@ +import { GenericPage } from '@jet-app/app-store/api/models'; + +const contentTypes = [ + 'win-back', + 'carrier', + 'invoice', + 'contingent-price', +] as const; + +export type ContentType = (typeof contentTypes)[number]; + +export class StaticMessagePage extends GenericPage { + constructor({ + titleLocKey, + contentType, + }: { + titleLocKey: string; + contentType: ContentType; + }) { + super([]); + this.titleLocKey = titleLocKey; + this.contentType = contentType; + } + + titleLocKey?: string; + + // Used to indicate which type of content the page needs to show, used to pull in the proper + // LOC keys when rendering + contentType: ContentType; + + // Used in our type guards to narrow a `Page` down to a `StaticMessagePage` + pageType: string = 'staticMessagePage'; +} |
