Skip to content

feat(VCST-4933): add Page Builder Shared Components - #159

Open
ToxaKZ wants to merge 5 commits into
devfrom
feat/VCST-4933-shared-components
Open

feat(VCST-4933): add Page Builder Shared Components#159
ToxaKZ wants to merge 5 commits into
devfrom
feat/VCST-4933-shared-components

Conversation

@ToxaKZ

@ToxaKZ ToxaKZ commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Description

Implements reusable Shared Components for Page Builder.

Content authors can convert adjacent top-level sections into a store-scoped Shared Component, reuse it as a linked instance or an independent copy, edit the original, detach an instance, inspect where it is used, and manage the library from VC Shell.

Terminology follows VCST-4933:

  • LinkedComponent is the code/API domain name.
  • Shared Component is the user-facing name used by the final designs.
  • componentRef is the compact reference stored in raw Page Builder page JSON.

Authoring and management

  • Save selected adjacent top-level sections as a Shared Component and replace them with one linked placement.
  • Show Shared Blocks Library at the top of the regular Add block panel without removing the normal block library.
  • Insert either a linked shared instance or an independent copy with regenerated IDs.
  • Display each shared placement as one logical editor item and one preview boundary.
  • Open and edit the original component, including its nested blocks and image controls.
  • Detach a linked placement into ordinary independent sections.
  • Show name, usage count, and Where used pages.
  • Open a usage page through the same Designer navigation contract used by the page blade's Open Designer action.
  • Support read-only component documents when the user can read but cannot update.
  • Manage the component library in VC Shell: search, pagination, details, audit metadata, rename, usage navigation, deletion when unused, and deletion blocking while referenced.
  • Include assets used by Shared Components in Assets Library reference counts and usage details.

Domain and persistence

Raw page content stores a reference rather than duplicated component content:

{
  "id": "componentRef-placement-id",
  "type": "componentRef",
  "componentRef": "<shared-component-id>"
}

A Shared Component stores a regular Page Builder template containing a group of top-level sections:

{
  "settings": {},
  "content": [
    { "id": "...", "type": "..." }
  ]
}

Persistence includes:

  • PageBuilderLinkedComponent — store-scoped metadata and audit information.
  • PageBuilderLinkedComponentContent — component template JSON.
  • PageBuilderLinkedComponentReference — page-to-component usage index.
  • PageBuilderLinkedComponentAssetReference — assets referenced by component content.

Migrations are included for SQL Server, PostgreSQL, and MySQL.

API, permissions, and authorization

Authenticated operations are exposed under:

/api/page-builder-linked-components

The API supports search, get, create, metadata update/rename, delete, load content, and save content.

Dedicated permissions:

  • builder:linked-components:read
  • builder:linked-components:create
  • builder:linked-components:update
  • builder:linked-components:delete

Page and component resources continue to use Page Builder resource-based store authorization. Usage-page details are returned only when the caller can read pages.

Consistency and concurrency

Component metadata, content, asset references, and page-reference indexes are maintained as one aggregate.

The implementation provides:

  • deterministic write-lock ordering for grouped pages, individual pages, and components;
  • validation under lock that referenced components still exist, contain valid content, and belong to the same store;
  • atomic page-content and reference-index updates;
  • store immutability for existing pages and components;
  • stale-write protection so a concurrent save cannot recreate a deleted component;
  • delete/save and page-move race protection;
  • deletion blocking while saved page references exist;
  • event publication only after successful persistence;
  • compatibility seams for custom/external repository implementations.

Publish and Storefront architecture

Raw Page Builder content remains reference-based while authoring. PageBuilderContentProvider expands every componentRef before the content reaches Pages indexing.

flowchart LR
    D["Page Builder Designer"] -->|"save page with componentRef"| PB["Page Builder storage"]
    D -->|"save component template"| SC["Shared Component aggregate"]
    PB --> RI["Page/component reference index"]
    SC --> AR["Component asset-reference index"]
    PB --> CP["PageBuilderContentProvider"]
    SC --> R["LinkedComponentResolver"]
    CP --> R
    R -->|"expanded ordinary sections"| P["Pages indexing"]
    P -->|"resolved page document"| SF["Storefront"]
Loading

Expanded IDs are deterministically derived from placement ID and structural path. The same component can therefore be placed more than once without ID collisions, while IDs remain stable across reindex operations.

When component content changes:

  1. a component-content-changed event is published after persistence;
  2. a Hangfire propagation job finds referenced page variants in batches;
  3. affected Page Builder caches are invalidated;
  4. page-changed events are emitted;
  5. Pages reindexes the resolved documents.

The production Storefront does not call the Shared Components API. It receives ordinary, fully expanded Page Builder blocks from Pages.

Assets Library integration

Component asset references are indexed with the Shared Component aggregate. Assets Library can return direct component references, affected usage pages, and separate/total reference counts.

Pages using component assets are derived through the component-placement index instead of duplicating component assets into every page index. This keeps component content and its asset index atomic and avoids fan-out partial-update windows.

Export/import

Export writes Shared Components and their content before Page Builder pages.

Import loads components first, then validates page componentRef values before replacing page groups and variants. References must exist, have content, and belong to the target store. Existing exports without Shared Components remain supported.

Constraints

  • Shared Components are store-scoped.
  • Cross-store linked references are rejected; an independent copy remains possible.
  • Nested linked Shared Components are not supported.
  • Shared Components have no independent draft/published state.
  • A component can be deleted only after all saved page usages are removed.
  • Propagation to published Storefront content is asynchronous and depends on Hangfire and Pages indexing.

Companion Storefront PR

VirtoCommerce/vc-frontend#2410

References

QA-test:

Assume the Page Builder module and companion Storefront build are already installed and configured.

1. Ordinary Page Builder regression

  1. Open an existing Page Builder page.
  2. Verify the regular Add block button is present.
  3. Add, edit, reorder, duplicate, and delete an ordinary section and a child block.
  4. Save and reload the page.
  5. Verify ordinary content, selection, and preview behavior remain unchanged.

2. Create a Shared Component

  1. Prepare at least three adjacent top-level sections, for example three image sections.
  2. Select all three section checkboxes from their leading icons.
  3. Choose Save selected as Shared Component, enter a name, and confirm.
  4. Verify the sections are replaced by one shared placement while all three still render in preview.
  5. Save and reopen the page; verify the placement is preserved.
  6. Try non-adjacent sections and verify creation is rejected without changing the page.
  7. Verify an existing Shared Component placement cannot be nested into a new one.

3. Insert from Add block

  1. Click the ordinary Add block button.
  2. Verify Shared Blocks Library appears at the top, followed by the normal block library.
  3. Insert the component as Insert shared instance; save and reload.
  4. Insert it again as Create independent copy.
  5. Edit the independent copy and verify the original and linked instances do not change.
  6. Verify ordinary blocks can still be added from the same panel.

4. Edit and detach

  1. Select a shared placement in the editor tree or preview.
  2. Verify Shared status, name, usage count, Edit/View original, Detach, and Where used.
  3. Open the original and edit individual nested blocks, including an image.
  4. Save and verify every linked instance in preview reflects the update.
  5. Detach one instance and verify it becomes ordinary independent sections.
  6. Edit the original again and verify the detached copy no longer changes.

5. Shared Components workspace

  1. Open Shared Components in VC Shell for the current store.
  2. Verify search, pagination, loading, empty, error, and retry states.
  3. Open a component and verify store, modified date/user, usage count, and usage pages.
  4. Open an active usage page and verify Designer opens exactly as from the page blade's Open Designer action.
  5. Verify unavailable usage pages cannot be opened and show the reason.
  6. Rename the component and verify the name updates in Shell and Designer without changing content.
  7. Verify deleting a used component is blocked.
  8. Remove/detach all placements, refresh usage, and delete the unused component.

6. Publish and propagation

  1. Publish a page containing a linked Shared Component.
  2. Wait for Pages indexing and open the public Storefront page.
  3. Verify the component renders as ordinary blocks and no componentRef placeholder is exposed.
  4. Edit the Shared Component original without republishing the page.
  5. Wait for background propagation/indexing, reload Storefront, and verify the update appears.
  6. Verify an unpublished page draft does not replace the currently published page.
  7. Place the same component twice and verify both placements render with unique, stable IDs.

7. Assets Library

  1. Use an asset inside a Shared Component and save it.
  2. Open the asset in Assets Library.
  3. Verify reference totals include the Shared Component and its affected usage pages.
  4. Verify referenced-asset deletion protection.
  5. Remove the asset, save, refresh references, and verify counts update.

8. Permissions, validation, and races

  1. Verify cross-store and nested linked insertion are unavailable/rejected.
  2. Test read-only, create, update, and delete permission combinations.
  3. Verify a read-only user can inspect but cannot mutate component content.
  4. Open the same component in two sessions; delete it in one and save it in the other.
  5. Verify the stale save fails and does not recreate the deleted component.
  6. Export and import Page Builder data and verify components, content, and page references are preserved.

Jira-link:

https://virtocommerce.atlassian.net/browse/VCST-4933

Artifact URL:

https://vc3prerelease.blob.core.windows.net/packages/VirtoCommerce.PageBuilderModule_3.1021.0-pr-159-28aa.zip

@sonarqubecloud

Copy link
Copy Markdown

@ToxaKZ
ToxaKZ requested a review from basilkot July 31, 2026 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant