Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ Never pass raw internal IDs from the client.
| `clientSecret` | `string` | ✅ | Project bearer token. |
| `environment` | `"production"` \| `"staging"` \| `string` | | Default `"production"`. Pass any custom URL to point at a self-hosted loader. |
| `dataLayer` | `string` | | Default `"stromCom"`. The global JS variable name. Change it if `stromCom` collides with something else on the page. |
| `language` | `string` | | UI language (e.g. `"cs"`, `"en"`, `"sk"`). Omit to auto-detect from the browser. |

### `<StromcomUser>`

Expand Down Expand Up @@ -184,13 +185,14 @@ Sends SDK configuration. Place once inside the provider, before threads.

Notable options:

| Prop | Type | Description |
| ----------------------------- | ----------------------------------------- | --------------------------------------------- |
| `notificationRenderer` | `Function` | Custom notification rendering function. |
| `onNotification` | `Function` | Called when unread count changes. |
| `pageCSSPath` | `string` | CSS file URL injected into the widget iframe. |
| `notificationElementPosition` | `1\|2\|3\|4` | Icon position: 1=TL, 2=TR, 3=BR, 4=BL. |
| `theme` | `"stromcom-light"\|"stromcom-dark"\|null` | Theme. `null` follows browser preference. |
| Prop | Type | Description |
| ----------------------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `notificationRenderer` | `Function` | Custom notification rendering function. |
| `onNotification` | `Function` | Called when unread count changes. |
| `pageCSSPath` | `string` | CSS file URL injected into the widget iframe. |
| `notificationElementPosition` | `1\|2\|3\|4` | Icon position: 1=TL, 2=TR, 3=BR, 4=BL. |
| `theme` | `"stromcom-light"\|"stromcom-dark"\|null` | Theme. `null` follows browser preference. |
| `entityResolve` | `Function` | `async ({type, id}) => detail` — resolves business-entity detail (order, ticket…) for the message editor's chip hover-card. |

See [`src/StromcomConf.jsx`](./src/StromcomConf.jsx) for the full list.

Expand Down
4 changes: 4 additions & 0 deletions src/StromcomConf.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useStromcom } from './StromcomProvider.jsx';
* @param {Function} [props.notificationElementBeforeRender]- Callback before notification icon renders
* @param {Function} [props.notificationElementAfterRender] - Callback after notification icon renders
* @param {('stromcom-light'|'stromcom-dark'|null)} [props.theme] - Theme. null follows browser preference.
* @param {Function} [props.entityResolve] - async ({type, id}) => detail; resolves business-entity detail for the message editor's chip hover-card
*
* @example
* <StromcomConf
Expand All @@ -43,6 +44,7 @@ export function StromcomConf({
notificationElementBeforeRender,
notificationElementAfterRender,
theme,
entityResolve,
}) {
const sc = useStromcom();

Expand Down Expand Up @@ -72,6 +74,7 @@ export function StromcomConf({
if (notificationElementAfterRender !== undefined)
opts.notificationElementAfterRender = notificationElementAfterRender;
if (theme !== undefined) opts.theme = theme;
if (entityResolve !== undefined) opts.entityResolve = entityResolve;

sc.conf(opts);
}, [
Expand All @@ -89,6 +92,7 @@ export function StromcomConf({
notificationElementBeforeRender,
notificationElementAfterRender,
theme,
entityResolve,
]);

return null;
Expand Down
5 changes: 4 additions & 1 deletion src/StromcomProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ function setupLayer(dataLayer) {
* @param {string} props.clientSecret - Bearer token from the Stromcom dashboard
* @param {string} [props.dataLayer] - Global JS variable name (default: "stromCom")
* @param {string} [props.environment] - "production" | "staging" | full custom loader URL
* @param {string} [props.language] - UI language (e.g. "cs", "en", "sk"). Omit to auto-detect from the browser.
* @param {React.ReactNode} props.children
*
* @example
Expand All @@ -70,6 +71,7 @@ export function StromcomProvider({
clientSecret,
dataLayer = 'stromCom',
environment = 'production',
language,
children,
}) {
// Set up stubs synchronously so children can queue calls immediately on render.
Expand All @@ -91,11 +93,12 @@ export function StromcomProvider({
script.dataset.dl = dl;
script.dataset.ck = clientKey;
script.dataset.cs = clientSecret;
if (language) script.dataset.lang = language;
script.src = `${base}?${clientKey}`;
document.head.appendChild(script);

return () => script.remove();
}, [clientKey, clientSecret, dataLayer, environment]);
}, [clientKey, clientSecret, dataLayer, environment, language]);

return <StromcomContext.Provider value={layer}>{children}</StromcomContext.Provider>;
}
Expand Down
8 changes: 8 additions & 0 deletions src/__tests__/StromcomConf.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ describe('StromcomConf', () => {
expect(layer.conf).toHaveBeenCalledWith({ onNotification });
});

it('forwards entityResolve as-is (no string-wrapping)', () => {
const layer = { conf: vi.fn() };
const entityResolve = vi.fn();
renderWithLayer(layer, { entityResolve });

expect(layer.conf).toHaveBeenCalledWith({ entityResolve });
});

it('re-sends conf when an option changes', () => {
const layer = { conf: vi.fn() };
const { rerender } = renderWithLayer(layer, { theme: 'stromcom-light' });
Expand Down
20 changes: 20 additions & 0 deletions src/__tests__/StromcomProvider.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,26 @@ describe('StromcomProvider', () => {
expect(scriptFor('ck_5')).toBeNull();
});

it('sets data-lang when language is provided', () => {
render(
<StromcomProvider clientKey="ck_lang" clientSecret="cs_lang" language="cs">
<div />
</StromcomProvider>,
);

expect(scriptFor('ck_lang').dataset.lang).toBe('cs');
});

it('omits data-lang when language is not provided', () => {
render(
<StromcomProvider clientKey="ck_nolang" clientSecret="cs_nolang">
<div />
</StromcomProvider>,
);

expect(scriptFor('ck_nolang').dataset.lang).toBeUndefined();
});

it('respects a custom dataLayer name', () => {
render(
<StromcomProvider clientKey="ck_6" clientSecret="cs_6" dataLayer="customLayer">
Expand Down
4 changes: 4 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export interface StromcomProviderProps {
dataLayer?: string;
/** `"production"`, `"staging"`, or a full custom loader URL. Default `"production"`. */
environment?: StromcomEnvironment;
/** UI language (e.g. `"cs"`, `"en"`, `"sk"`). Omit to auto-detect from the browser. */
language?: string;
children?: ReactNode;
}

Expand Down Expand Up @@ -80,6 +82,8 @@ export interface StromcomConfOptions {
notificationElementAfterRender?: () => void;
/** Theme: `"stromcom-light"`, `"stromcom-dark"`, or `null` to follow browser preference. */
theme?: 'stromcom-light' | 'stromcom-dark' | null;
/** Resolves business-entity detail (order, ticket…) for the message editor's chip hover-card. */
entityResolve?: (entity: { type: string; id: string }) => unknown | Promise<unknown>;
}

export function StromcomConf(props: StromcomConfOptions): null;