feat(components): pro_only flag + registry merge API + picker gate hook

Three small additions so private overlays can add components gated behind
a paid subscription without forking the picker:

- types/component-metadata.ts: optional pro_only?: boolean field on
  ComponentMetadata. Self-hosters never set it; picker behaves identically.
- services/componentRegistry.ts: new mergeComponents() public method.
  Pro overlay calls this after the default registry has loaded to splice
  in extra components (replacing any with the same id).
- components/ComponentPickerModal.tsx: when a pro_only component is
  clicked, the picker first calls window.__velxio_pro_gate__(component)
  if defined. If the gate returns true, the click is consumed (overlay
  shows an upgrade modal). If absent or returns false, the click passes
  through to onSelectComponent as normal.

Net upstream change: ~25 lines, all backwards-compatible. OSS image
behaves exactly as before since no overlay sets pro_only or installs
the gate.
This commit is contained in:
David Montero Crespo 2026-05-05 10:33:53 -03:00
parent 77b3e86b50
commit 8b14815094
2 changed files with 18 additions and 1 deletions

View File

@ -259,7 +259,18 @@ export const ComponentPickerModal: React.FC<ComponentPickerModalProps> = ({
<ComponentCard
key={component.id}
component={component}
onSelect={() => onSelectComponent(component)}
onSelect={() => {
// Pro overlays can intercept clicks on pro_only
// components by setting window.__velxio_pro_gate__.
// Returning true means "handled — do not pass through".
if (component.pro_only) {
const gate = (window as unknown as {
__velxio_pro_gate__?: (c: typeof component) => boolean;
}).__velxio_pro_gate__;
if (gate && gate(component)) return;
}
onSelectComponent(component);
}}
/>
))
)}

View File

@ -41,6 +41,12 @@ export interface ComponentMetadata {
defaultValues: Record<string, any>;
pinCount: number;
tags: string[]; // For search functionality
// Optional flag set by private overlays (e.g. velxio.dev) to mark a
// component as gated behind a paid subscription. The OSS image never
// sets this — self-hosters have everything unlocked. The picker can
// delegate the click on a pro_only component to a window-level gate
// (see ComponentPickerModal) which the overlay implements.
pro_only?: boolean;
}
export interface ComponentMetadataCollection {