From 126e94936665b7b487d90219c647d5b5cbdf8e4a Mon Sep 17 00:00:00 2001 From: Lukasz Ostrowski Date: Wed, 14 Sep 2022 14:21:53 +0200 Subject: [PATCH] Detect locale param from URL in AppBridge (#58) * Detect locale param from URL in AppBridge * Add docs --- docs/app-bridge.md | 51 +++++++++++++++++++++++--- src/app-bridge/app-bridge-provider.tsx | 4 -- src/app-bridge/app-bridge.test.ts | 12 ++++++ src/app-bridge/app-bridge.ts | 22 ++++++++--- src/app-bridge/events.test.ts | 12 ++++++ src/app-bridge/events.ts | 8 ++++ 6 files changed, 94 insertions(+), 15 deletions(-) diff --git a/docs/app-bridge.md b/docs/app-bridge.md index 2024c15..4d26778 100644 --- a/docs/app-bridge.md +++ b/docs/app-bridge.md @@ -39,6 +39,42 @@ type AppBridgeState = { }; ``` +## AppBridgeProvider + +`AppBridgeProvider` and `useAppBridge` hook are exposed from app-sdk + +```tsx +// app.tsx +import { AppBridgeProvider } from "@saleor/app-sdk/app-bridge"; + + + +; +``` + +`AppBridgeProvider` can optionally receive AppBridge instance in props, otherwise will create one automatically + +### useAppBridge hook + +In components wrapped with `AppBridgeProvider`, `useAppBridge` hook is available + +```tsx +import { useAppBridge } from "@saleor/app-sdk/app-bridge"; +import { useEffect } from "react"; + +const MyComponent = () => { + const { appBridge, appBridgeState } = useAppBridge(); + + useEffect(() => { + appBridge?.dispatch(/* Something */); + }, [appBridge]); + + return
Current locale is: {appBridgeState?.locale}
; +}; +``` + +`appBridgeState?` and `appBridge` can be nullish, because in server side context it's not available + ## Events Events are messages that originate in Saleor Dashboard. AppBridge can subscribe on events and app can react on them @@ -76,12 +112,15 @@ appBridge.unsubscribeAll(); ### Available event types -| Event type | Description | -| :---------- | :--------------------------------------------------------------------------- | -| `handshake` | Fired when iFrame containing the App is initialized or new token is assigned | -| `response` | Fired when Dashboard responds to an Action | -| `redirect` | Fired when Dashboard changes a subpath within the app path | -| `theme` | Fired when Dashboard changes the theme | +| Event type | Description | +| :-------------- | :--------------------------------------------------------------------------- | +| `handshake` | Fired when iFrame containing the App is initialized or new token is assigned | +| `response` | Fired when Dashboard responds to an Action | +| `redirect` | Fired when Dashboard changes a subpath within the app path | +| `theme` | Fired when Dashboard changes the theme | +| `localeChanged` | Fired when Dashboard changes locale (and passes locale code in payload) | + +See [source code for detailed payload](./src/app-bridge/events.ts) ## Actions diff --git a/src/app-bridge/app-bridge-provider.tsx b/src/app-bridge/app-bridge-provider.tsx index e7fb2bb..a138845 100644 --- a/src/app-bridge/app-bridge-provider.tsx +++ b/src/app-bridge/app-bridge-provider.tsx @@ -24,10 +24,6 @@ export const AppContext = React.createContext({ mounted: false, }); -/** - * Experimental - try to use provider in app-sdk itself - * Consider monorepo with dedicated react package - */ export function AppBridgeProvider({ appBridgeInstance, ...props }: React.PropsWithChildren) { debug("Provider mounted"); const [appBridge, setAppBridge] = useState(appBridgeInstance); diff --git a/src/app-bridge/app-bridge.test.ts b/src/app-bridge/app-bridge.test.ts index 1553386..63b1eea 100644 --- a/src/app-bridge/app-bridge.test.ts +++ b/src/app-bridge/app-bridge.test.ts @@ -220,4 +220,16 @@ describe("AppBridge", () => { expect(instance.getState().locale).toBe(locale); }); + + it("Detects locale from URL param and set it to be initial", () => { + const localeToOverwrite = "pl"; + + const currentLocationHref = window.location.href; + + window.location.href = `${origin}?domain=${domain}&id=appid&locale=${localeToOverwrite}`; + + expect(new AppBridge().getState().locale).toBe(localeToOverwrite); + + window.location.href = currentLocationHref; + }); }); diff --git a/src/app-bridge/app-bridge.ts b/src/app-bridge/app-bridge.ts index 1a6d797..88c18d7 100644 --- a/src/app-bridge/app-bridge.ts +++ b/src/app-bridge/app-bridge.ts @@ -71,8 +71,20 @@ export type AppBridgeOptions = { initialLocale?: LocaleCode; }; +/** + * TODO: Consider validating locale if wrong code provided + */ +const getLocaleFromUrl = () => + (new URL(window.location.href).searchParams.get("locale") as LocaleCode) || undefined; + +/** + * TODO: Probably remove empty string fallback + */ +const getDomainFromUrl = () => new URL(window.location.href).searchParams.get("domain") || ""; + const getDefaultOptions = (): AppBridgeOptions => ({ - targetDomain: new URL(window.location.href).searchParams.get("domain") || "", + targetDomain: getDomainFromUrl(), + initialLocale: getLocaleFromUrl(), }); export class AppBridge { @@ -93,15 +105,15 @@ export class AppBridge { ); } - this.state = new AppBridgeStateContainer({ - initialLocale: options.initialLocale, - }); - this.combinedOptions = { ...this.combinedOptions, ...options, }; + this.state = new AppBridgeStateContainer({ + initialLocale: this.combinedOptions.initialLocale, + }); + debug("Resolved combined AppBridge options: %j", this.combinedOptions); if (!this.refererOrigin) { diff --git a/src/app-bridge/events.test.ts b/src/app-bridge/events.test.ts index 317f05f..2cf2b5e 100644 --- a/src/app-bridge/events.test.ts +++ b/src/app-bridge/events.test.ts @@ -12,6 +12,7 @@ describe("DashboardEventFactory", () => { type: "handshake", }); }); + it("Creates redirect event", () => { expect(DashboardEventFactory.createRedirectEvent("/new-path")).toEqual({ payload: { @@ -20,6 +21,7 @@ describe("DashboardEventFactory", () => { type: "redirect", }); }); + it("Creates dispatch response event", () => { expect(DashboardEventFactory.createDispatchResponseEvent("123", true)).toEqual({ payload: { @@ -29,6 +31,7 @@ describe("DashboardEventFactory", () => { type: "response", }); }); + it("Creates theme change event", () => { expect(DashboardEventFactory.createThemeChangeEvent("light")).toEqual({ payload: { @@ -37,4 +40,13 @@ describe("DashboardEventFactory", () => { type: "theme", }); }); + + it("Creates locale change event", () => { + expect(DashboardEventFactory.createLocaleChangedEvent("it")).toEqual({ + payload: { + locale: "it", + }, + type: "localeChanged", + }); + }); }); diff --git a/src/app-bridge/events.ts b/src/app-bridge/events.ts index da566b6..44f3199 100644 --- a/src/app-bridge/events.ts +++ b/src/app-bridge/events.ts @@ -104,4 +104,12 @@ export const DashboardEventFactory = { }, }; }, + createLocaleChangedEvent(newLocale: LocaleCode): LocaleChangedEvent { + return { + type: "localeChanged", + payload: { + locale: newLocale, + }, + }; + }, };