Detect locale param from URL in AppBridge (#58)

* Detect locale param from URL in AppBridge

* Add docs
This commit is contained in:
Lukasz Ostrowski 2022-09-14 14:21:53 +02:00 committed by GitHub
parent 910b50a18a
commit 126e949366
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 94 additions and 15 deletions

View file

@ -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>
<YourApp />
</AppBridgeProvider>;
```
`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 <div>Current locale is: {appBridgeState?.locale}</div>;
};
```
`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

View file

@ -24,10 +24,6 @@ export const AppContext = React.createContext<AppBridgeContext>({
mounted: false,
});
/**
* Experimental - try to use provider in app-sdk itself
* Consider monorepo with dedicated react package
*/
export function AppBridgeProvider({ appBridgeInstance, ...props }: React.PropsWithChildren<Props>) {
debug("Provider mounted");
const [appBridge, setAppBridge] = useState<AppBridge | undefined>(appBridgeInstance);

View file

@ -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;
});
});

View file

@ -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) {

View file

@ -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",
});
});
});

View file

@ -104,4 +104,12 @@ export const DashboardEventFactory = {
},
};
},
createLocaleChangedEvent(newLocale: LocaleCode): LocaleChangedEvent {
return {
type: "localeChanged",
payload: {
locale: newLocale,
},
};
},
};