'use client';
import * as React from 'react';
import { loadRemote, registerRemotes } from './federation';
interface RemoteComponentModule
{
default: React.ComponentType
;
}
interface RemoteSelection {
name: string;
entry: string;
}
const STORAGE_KEY = 'remoteComponentSelections';
const HIGHLIGHT_EVENT = 'apna:customise-highlight';
export function setCustomiseHighlight(enabled: boolean): void {
if (typeof window === 'undefined') return;
(window as unknown as { __APNA_CUSTOMISE_HIGHLIGHT__?: boolean }).__APNA_CUSTOMISE_HIGHLIGHT__ =
enabled;
window.dispatchEvent(
new CustomEvent(HIGHLIGHT_EVENT, {
detail: { enabled },
})
);
}
export function withDynamicComponent
(
remoteModuleName: string,
DefaultComponent: React.ComponentType
): any {
function DynamicComponent(props: P) {
const [isHighlighted, setIsHighlighted] = React.useState(false);
const [isPickerOpen, setIsPickerOpen] = React.useState(false);
const [remoteName, setRemoteName] = React.useState('');
const [remoteEntry, setRemoteEntry] = React.useState('');
const [RemoteComponent, setRemoteComponent] =
React.useState | null>(null);
const [error, setError] = React.useState(null);
React.useEffect(() => {
if (typeof window === 'undefined') return;
const w = window as unknown as { __APNA_CUSTOMISE_HIGHLIGHT__?: boolean };
setIsHighlighted(Boolean(w.__APNA_CUSTOMISE_HIGHLIGHT__));
const onHighlight = (event: Event) => {
setIsHighlighted(Boolean((event as CustomEvent).detail?.enabled));
};
window.addEventListener(HIGHLIGHT_EVENT, onHighlight);
return () => window.removeEventListener(HIGHLIGHT_EVENT, onHighlight);
}, []);
React.useEffect(() => {
const selection = readSelection(remoteModuleName);
if (selection) {
void loadComponent(selection, false);
}
}, []);
async function loadComponent(
selection: RemoteSelection,
updateStorage = true
) {
try {
setError(null);
registerRemotes(
[
{
name: selection.name,
entry: selection.entry,
},
],
{ force: true }
);
const remoteModule = await loadRemote>(
`${selection.name}/${remoteModuleName}`
);
if (!remoteModule?.default) {
throw new Error('Remote module does not contain a default export');
}
setRemoteComponent(() => remoteModule.default);
setIsPickerOpen(false);
if (updateStorage) writeSelection(remoteModuleName, selection);
} catch (err) {
setError(
err instanceof Error ? err.message : 'Failed to load remote component'
);
}
}
const SelectedComponent = RemoteComponent || DefaultComponent;
return (
<>
{
event.stopPropagation();
setIsPickerOpen(true);
}
: undefined
}
style={
isHighlighted
? { outline: '4px solid #368564', borderRadius: 6 }
: undefined
}
>
{isPickerOpen && (
)}
>
);
}
DynamicComponent.displayName = `withDynamicComponent(${remoteModuleName})`;
return DynamicComponent;
}
function readSelection(remoteModuleName: string): RemoteSelection | null {
if (typeof window === 'undefined') return null;
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) return null;
const selections = JSON.parse(raw) as Record;
return selections[remoteModuleName] ?? null;
}
function writeSelection(
remoteModuleName: string,
selection: RemoteSelection
): void {
if (typeof window === 'undefined') return;
const raw = window.localStorage.getItem(STORAGE_KEY);
const selections = raw ? JSON.parse(raw) : {};
selections[remoteModuleName] = selection;
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(selections));
}
function clearSelection(remoteModuleName: string): void {
if (typeof window === 'undefined') return;
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) return;
const selections = JSON.parse(raw);
delete selections[remoteModuleName];
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(selections));
}