diff --git a/packages/react-ui/Home.js b/packages/react-ui/Home.js
new file mode 100644
index 0000000..cc830b2
--- /dev/null
+++ b/packages/react-ui/Home.js
@@ -0,0 +1,78 @@
+import React from 'react';
+
+export default class Home extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
+ const reactVersion = require('./package.json').dependencies['react'];
+
+ const styles = {
+ container: {
+ fontFamily: 'Arial, sans-serif',
+ padding: '20px',
+ backgroundColor: '#f0f2f5',
+ borderRadius: '8px',
+ boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
+ maxWidth: '600px',
+ margin: '20px auto',
+ border: '1px solid #ddd'
+ },
+ header: {
+ color: '#1a1a1a',
+ textAlign: 'center',
+ borderBottom: '2px solid #e0e0e0',
+ paddingBottom: '15px',
+ marginBottom: '20px',
+ fontSize: '24px'
+ },
+ content: {
+ color: '#333',
+ },
+ paragraph: {
+ lineHeight: '1.6',
+ fontSize: '16px',
+ marginBottom: '15px'
+ },
+ box: {
+ backgroundColor: '#ffffff',
+ border: '1px solid #e0e0e0',
+ padding: '15px',
+ borderRadius: '6px',
+ margin: '20px 0',
+ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)'
+ },
+ code: {
+ fontFamily: 'monospace',
+ backgroundColor: '#e8e8e8',
+ padding: '2px 4px',
+ borderRadius: '3px',
+ },
+ strong: {
+ color: '#0056b3'
+ }
+ };
+
+ return (
+
+
React Application as a Web Component
+
+
+ This user interface is built using React.
+
+
+
React Version: {reactVersion}
+
+
+ The entire React application is encapsulated and delivered as a Web Component.
+ This is a standards-based way to create custom, reusable HTML elements that can be used with any JavaScript library or framework, or even with no framework at all.
+
+
+ This approach promotes interoperability and allows different parts of a larger application to be built with different technologies without conflicts.
+
+
+
+ );
+ }
+}
diff --git a/packages/react-ui/app.js b/packages/react-ui/app.js
new file mode 100644
index 0000000..d07312d
--- /dev/null
+++ b/packages/react-ui/app.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { BrowserRouter, MemoryRouter, Switch, Route } from 'react-router-dom';
+
+import Home from './Home';
+
+function App({ baseHref }) {
+ // When the host app uses hash-based routing (e.g. VCFA tenant), Angular owns
+ // the hash so React cannot use BrowserRouter (pathname mismatch) or HashRouter
+ // (it would read Angular's hash segment). MemoryRouter is used instead.
+ const isHashBased = baseHref && !window.location.pathname.startsWith(baseHref);
+ const Router = isHashBased ? MemoryRouter : BrowserRouter;
+ const routerProps = isHashBased ? { initialEntries: ['/'] } : { basename: baseHref };
+
+ return (
+
+
+
+ );
+}
+
+class MyReactAppUIElement extends HTMLElement {
+ connectedCallback() {
+ const baseHref = this.getAttribute('baseHref');
+ ReactDOM.render(, this);
+ }
+
+ static get observedAttributes() {
+ return ['baseHref'];
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ if (name === 'baseHref') {
+ console.log(`baseHref changed from ${oldValue} to ${newValue}`);
+ }
+ }
+
+}
+
+customElements.define('react-element', MyReactAppUIElement);
diff --git a/packages/react-ui/index.html b/packages/react-ui/index.html
new file mode 100644
index 0000000..b5858e2
--- /dev/null
+++ b/packages/react-ui/index.html
@@ -0,0 +1,2 @@
+
+
diff --git a/packages/react-ui/index.js b/packages/react-ui/index.js
new file mode 100644
index 0000000..b5fb900
--- /dev/null
+++ b/packages/react-ui/index.js
@@ -0,0 +1 @@
+import('./app');
diff --git a/packages/react-ui/package.json b/packages/react-ui/package.json
new file mode 100644
index 0000000..46d97a5
--- /dev/null
+++ b/packages/react-ui/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "react-ui",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "start": "webpack serve --config webpack.config.js --mode=development --devtool=source-map",
+ "build": "webpack --config webpack.config.js --mode=development",
+ "build:plugin": "webpack --config webpack.config.plugin.js --mode=development"
+ },
+ "author": "",
+ "license": "MIT",
+ "devDependencies": {
+ "@babel/core": "^7.12.10",
+ "@babel/preset-env": "^7.12.10",
+ "@babel/preset-react": "^7.12.10",
+ "babel-runtime": "^6.26.0",
+ "babel-loader": "^8.2.2",
+ "babel-plugin-transform-runtime": "^6.23.0",
+ "webpack": "^5.10.1",
+ "webpack-cli": "^4.2.0",
+ "webpack-dev-server": "^3.11.0"
+ },
+ "dependencies": {
+ "copy-webpack-plugin": "^8.1.1",
+ "react": "^17.0.1",
+ "react-dom": "^17.0.1",
+ "react-router-dom": "5.3.3"
+ }
+}
diff --git a/packages/react-ui/plugin.js b/packages/react-ui/plugin.js
new file mode 100644
index 0000000..b5fb900
--- /dev/null
+++ b/packages/react-ui/plugin.js
@@ -0,0 +1 @@
+import('./app');
diff --git a/packages/react-ui/webpack.config.js b/packages/react-ui/webpack.config.js
new file mode 100644
index 0000000..fa7e1ab
--- /dev/null
+++ b/packages/react-ui/webpack.config.js
@@ -0,0 +1,53 @@
+const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
+const CopyWebpackPlugin = require("copy-webpack-plugin");
+const path = require("path");
+
+module.exports = options => {
+ return {
+ entry: './index.js',
+ output: {
+ filename: 'bundle.js',
+ publicPath: "auto",
+ uniqueName: "react-example-ui",
+ path: path.resolve(__dirname, './dist'),
+ },
+ module: {
+ rules: [
+ {
+ test: /.js$/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: 'babel-loader',
+ options: {
+ cacheDirectory: true,
+ presets: ['@babel/react', '@babel/env']
+ }
+ },
+ ],
+ },
+ ],
+ },
+ plugins: [
+ new ModuleFederationPlugin({
+ name: "react_example_ui",
+ library: { type: "var", name: "react_example_ui" },
+ filename: "remoteEntry.js",
+ exposes: {
+ './web-components': './app.js',
+ },
+ shared: ["react", "react-dom"]
+ }),
+ new CopyWebpackPlugin({
+ patterns: [
+ {
+ from: './*.html'
+ }
+ ]
+ })
+ ],
+ devServer: {
+ port: 4204
+ }
+ }
+}
diff --git a/packages/react-ui/webpack.config.plugin.js b/packages/react-ui/webpack.config.plugin.js
new file mode 100644
index 0000000..014845e
--- /dev/null
+++ b/packages/react-ui/webpack.config.plugin.js
@@ -0,0 +1,53 @@
+const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
+const CopyWebpackPlugin = require("copy-webpack-plugin");
+const path = require("path");
+
+module.exports = options => {
+ return {
+ entry: './plugin.js',
+ output: {
+ filename: 'bundle.js',
+ publicPath: "auto",
+ uniqueName: "react-example-ui",
+ path: path.resolve(__dirname, '../vcfa-plugin/projects/ui-plugin/public/assets/react-plugin/dist'),
+ },
+ module: {
+ rules: [
+ {
+ test: /.js$/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: 'babel-loader',
+ options: {
+ cacheDirectory: true,
+ presets: ['@babel/react', '@babel/env']
+ }
+ },
+ ],
+ },
+ ],
+ },
+ plugins: [
+ new ModuleFederationPlugin({
+ name: "react_example_ui",
+ library: { type: "var", name: "react_example_ui" },
+ filename: "remoteEntry.js",
+ exposes: {
+ './web-components': './app.js',
+ },
+ shared: ["react", "react-dom"]
+ }),
+ new CopyWebpackPlugin({
+ patterns: [
+ {
+ from: './*.html'
+ }
+ ]
+ })
+ ],
+ devServer: {
+ port: 4204
+ }
+ }
+}
diff --git a/packages/vcfa-plugin/README.md b/packages/vcfa-plugin/README.md
new file mode 100644
index 0000000..83e4949
--- /dev/null
+++ b/packages/vcfa-plugin/README.md
@@ -0,0 +1,30 @@
+# Prerequisites
+- NodeJS >= v22.14.x
+
+# Start
+```
+nvm use 22
+npm ci
+npm run start
+```
+
+# Build
+```
+nvm use 22
+npm ci
+npm run build
+```
+
+# Test
+```
+nvm use 22
+npm ci
+npm run test
+```
+
+# Build VCFA Plugin
+```
+nvm use 22
+npm ci
+npm run build:plugin
+```
\ No newline at end of file
diff --git a/packages/vcfa-plugin/package.json b/packages/vcfa-plugin/package.json
index 0a8f389..212f784 100644
--- a/packages/vcfa-plugin/package.json
+++ b/packages/vcfa-plugin/package.json
@@ -6,45 +6,50 @@
},
"scripts": {
"ng": "ng",
- "start": "ng serve",
+ "start": "cd ../react-ui && npm run build:plugin && cd ../vcfa-plugin && ng serve",
"build": "ng build",
- "build:plugin": "ng build --configuration plugin && npm run zip",
+ "build:plugin": "cd ../react-ui && npm run build:plugin && cd ../vcfa-plugin && ng build --configuration plugin && npm run zip",
"test": "ng test",
- "zip": "node ./scripts/zip.js ./dist/quick-start ./dist/plugin.zip"
+ "zip": "node ./scripts/zip.js ./dist/ui-plugin ./dist/plugin.zip"
},
"private": true,
"dependencies": {
- "@angular-architects/module-federation": "19.0.3",
- "@angular/animations": "19.2.0",
- "@angular/cdk": "19.2.0",
- "@angular/common": "19.2.0",
- "@angular/compiler": "19.2.0",
- "@angular/core": "19.2.0",
- "@angular/forms": "19.2.0",
- "@angular/platform-browser": "19.2.0",
- "@angular/platform-browser-dynamic": "19.2.0",
- "@angular/router": "19.2.0",
- "@cds/core": "6.15.1",
- "@clr/angular": "17.10.0",
- "@clr/ui": "17.10.0",
- "ansi-escapes": "7.1.0",
- "rxjs": "7.8.0",
- "tslib": "2.3.0",
- "zone.js": "0.15.0"
+ "@angular-architects/module-federation": "21.2.2",
+ "@angular/animations": "21.2.17",
+ "@angular/cdk": "21.2.14",
+ "@angular/common": "21.2.17",
+ "@angular/compiler": "21.2.17",
+ "@angular/core": "21.2.17",
+ "@angular/forms": "21.2.17",
+ "@angular/platform-browser": "21.2.17",
+ "@angular/platform-browser-dynamic": "21.2.17",
+ "@angular/router": "21.2.17",
+ "@clr/angular": "18.2.1",
+ "@clr/ui": "18.2.1",
+ "@ngx-translate/core": "17.0.0",
+ "@ngx-translate/http-loader": "17.0.0",
+ "@vcd/bindings": "9.1.1",
+ "@vcfa/container-hooks": "19.0.4",
+ "@vcfa/sdk": "19.1.9",
+ "ansi-escapes": "7.3.0",
+ "rxjs": "7.8.2",
+ "tslib": "2.8.1",
+ "zone.js": "0.16.2"
},
"devDependencies": {
- "@angular-devkit/build-angular": "19.2.0",
- "@angular/cli": "19.2.0",
- "@angular/compiler-cli": "19.2.0",
- "@types/jasmine": "5.1.0",
+ "@angular-devkit/build-angular": "21.2.17",
+ "@angular/build": "21.2.17",
+ "@angular/cli": "21.2.17",
+ "@angular/compiler-cli": "21.2.17",
+ "@types/jasmine": "5.1.15",
"archiver": "7.0.1",
- "jasmine-core": "5.6.0",
- "karma": "6.4.0",
+ "jasmine-core": "5.13.0",
+ "karma": "6.4.4",
"karma-chrome-launcher": "3.2.0",
- "karma-coverage": "2.2.0",
+ "karma-coverage": "2.2.1",
"karma-jasmine": "5.1.0",
- "karma-jasmine-html-reporter": "2.1.0",
- "ngx-build-plus": "19.0.0",
- "typescript": "5.7.2"
+ "karma-jasmine-html-reporter": "2.2.0",
+ "ngx-build-plus": "20.0.0",
+ "typescript": "5.9.3"
}
}
diff --git a/packages/vcfa-plugin/projects/ui-plugin/public/assets/i18n/en.json b/packages/vcfa-plugin/projects/ui-plugin/public/assets/i18n/en.json
new file mode 100644
index 0000000..c184e35
--- /dev/null
+++ b/packages/vcfa-plugin/projects/ui-plugin/public/assets/i18n/en.json
@@ -0,0 +1,5 @@
+{
+ "subnav.menu.home": "Home",
+ "home.title": "Welcome to your VCFA Plugin",
+ "home.description": "The Quick Start Plugin showcases all available extension points in VCFA where you can start building your own plugin."
+}
\ No newline at end of file
diff --git a/packages/vcfa-plugin/projects/ui-plugin/public/assets/iframe-content.html b/packages/vcfa-plugin/projects/ui-plugin/public/assets/iframe-content.html
new file mode 100644
index 0000000..374fe80
--- /dev/null
+++ b/packages/vcfa-plugin/projects/ui-plugin/public/assets/iframe-content.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+ Iframe Content
+
+
+
+ Iframe Content Page
+ This page is designed to receive a JWT from a parent window via postMessage.
+ Received JWT:
+ Waiting for token...
+
+
+
+
diff --git a/packages/vcfa-plugin/projects/ui-plugin/public/manifest.json b/packages/vcfa-plugin/projects/ui-plugin/public/manifest.json
index ebc12c8..0a7008f 100644
--- a/packages/vcfa-plugin/projects/ui-plugin/public/manifest.json
+++ b/packages/vcfa-plugin/projects/ui-plugin/public/manifest.json
@@ -2,7 +2,7 @@
"urn": "vcfa:plugin:quick-start",
"name": "Quick Start Plugin",
"containerVersion": "9.1.0",
- "manifestVersion": "6.0.0",
+ "manifestVersion": "7.0.0",
"version": "0.0.0",
"scope": [
"service-provider",
diff --git a/packages/vcfa-plugin/projects/ui-plugin/src/app/app.routes.ts b/packages/vcfa-plugin/projects/ui-plugin/src/app/app.routes.ts
index c12dd1b..92a412e 100644
--- a/packages/vcfa-plugin/projects/ui-plugin/src/app/app.routes.ts
+++ b/packages/vcfa-plugin/projects/ui-plugin/src/app/app.routes.ts
@@ -1,7 +1,11 @@
import { Routes } from "@angular/router";
import { HomeComponent } from "../plugin/home/home.component";
+import { JwtIframeComponent } from "../plugin/jwt-iframe/jwt-iframe.component";
+import { ReactAppComponent } from "../react/react-app.component";
export const routes: Routes = [
{ path: "", redirectTo: "home", pathMatch: "full" },
{ path: "home", component: HomeComponent },
-];
\ No newline at end of file
+ { path: "access-token", component: JwtIframeComponent },
+ { path: "react-example", component: ReactAppComponent },
+];
diff --git a/packages/vcfa-plugin/projects/ui-plugin/src/bootstrap.plugin.ts b/packages/vcfa-plugin/projects/ui-plugin/src/bootstrap.plugin.ts
index 032c4bc..d16b8c1 100644
--- a/packages/vcfa-plugin/projects/ui-plugin/src/bootstrap.plugin.ts
+++ b/packages/vcfa-plugin/projects/ui-plugin/src/bootstrap.plugin.ts
@@ -1,2 +1,20 @@
+import { provideHttpClient } from '@angular/common/http';
+import { provideTranslateService } from '@ngx-translate/core';
+import { TranslateLoader } from '@ngx-translate/core';
+import { HttpClient } from '@angular/common/http';
+import { DynamicTranslateLoader } from './plugin/dynamic-translate-loader';
+import { EXTENSION_ASSET_URL } from '@vcfa/sdk';
+
export { SubnavComponent } from './plugin/subnav.component';
export { routes } from './plugin/plugin.routes';
+export const providers = [
+ provideHttpClient(),
+ provideTranslateService({
+ loader: {
+ provide: TranslateLoader,
+ useFactory: (http: HttpClient, assetUrl: string) => new DynamicTranslateLoader(http, `${assetUrl}/i18n/`),
+ deps: [HttpClient, EXTENSION_ASSET_URL],
+ },
+ fallbackLang: 'en',
+ }),
+];
\ No newline at end of file
diff --git a/packages/vcfa-plugin/projects/ui-plugin/src/bootstrap.ts b/packages/vcfa-plugin/projects/ui-plugin/src/bootstrap.ts
index 981801d..03c6112 100644
--- a/packages/vcfa-plugin/projects/ui-plugin/src/bootstrap.ts
+++ b/packages/vcfa-plugin/projects/ui-plugin/src/bootstrap.ts
@@ -1,11 +1,24 @@
import { bootstrapApplication } from '@angular/platform-browser';
+import { provideHttpClient } from '@angular/common/http';
import { provideRouter } from '@angular/router';
import { routes } from './app/app.routes';
import { AppComponent } from './app/app.component';
+import { provideTranslateService } from "@ngx-translate/core";
+import { provideTranslateHttpLoader } from "@ngx-translate/http-loader";
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes),
+ provideHttpClient(),
+ provideTranslateService({
+ loader: provideTranslateHttpLoader({
+ prefix: '/assets/i18n/',
+ suffix: '.json'
+ }),
+ fallbackLang: 'en',
+ lang: 'en'
+ })
+
]
})
.catch((err) => console.error(err));
diff --git a/packages/vcfa-plugin/projects/ui-plugin/src/plugin/dynamic-translate-loader.ts b/packages/vcfa-plugin/projects/ui-plugin/src/plugin/dynamic-translate-loader.ts
new file mode 100644
index 0000000..e60c7ae
--- /dev/null
+++ b/packages/vcfa-plugin/projects/ui-plugin/src/plugin/dynamic-translate-loader.ts
@@ -0,0 +1,20 @@
+import { HttpClient } from '@angular/common/http';
+import { TranslateLoader } from '@ngx-translate/core';
+
+export class DynamicTranslateLoader implements TranslateLoader {
+ constructor(
+ private http: HttpClient,
+ private prefix: string = './assets/i18n/',
+ private suffix: string = '.json'
+ ) { }
+
+ /**
+ * Gets the translations for a given language.
+ * @param lang The language to get the translations for.
+ * @returns An observable containing the loaded translations.
+ */
+ public getTranslation(lang: string): any {
+ const fullPath = `${this.prefix}${lang}${this.suffix}`;
+ return this.http.get(fullPath);
+ }
+}
\ No newline at end of file
diff --git a/packages/vcfa-plugin/projects/ui-plugin/src/plugin/home/home.component.html b/packages/vcfa-plugin/projects/ui-plugin/src/plugin/home/home.component.html
index ebf108a..e03420d 100644
--- a/packages/vcfa-plugin/projects/ui-plugin/src/plugin/home/home.component.html
+++ b/packages/vcfa-plugin/projects/ui-plugin/src/plugin/home/home.component.html
@@ -1,6 +1,6 @@
👋
-Welcome to your VCFA Plugin
-The Quick Start Plugin showcases all available extension points in VCFA where you can start building your own plugin.
+{{ 'home.title' | translate }}
+{{ 'home.description' | translate }}
![]()
+
+
diff --git a/packages/vcfa-plugin/projects/ui-plugin/src/plugin/jwt-iframe/jwt-iframe.component.scss b/packages/vcfa-plugin/projects/ui-plugin/src/plugin/jwt-iframe/jwt-iframe.component.scss
new file mode 100644
index 0000000..f83d4f0
--- /dev/null
+++ b/packages/vcfa-plugin/projects/ui-plugin/src/plugin/jwt-iframe/jwt-iframe.component.scss
@@ -0,0 +1,7 @@
+.iframe-container {
+ padding: 1rem;
+}
+
+iframe {
+ border: 1px solid #ccc;
+}
diff --git a/packages/vcfa-plugin/projects/ui-plugin/src/plugin/jwt-iframe/jwt-iframe.component.ts b/packages/vcfa-plugin/projects/ui-plugin/src/plugin/jwt-iframe/jwt-iframe.component.ts
new file mode 100644
index 0000000..12ea289
--- /dev/null
+++ b/packages/vcfa-plugin/projects/ui-plugin/src/plugin/jwt-iframe/jwt-iframe.component.ts
@@ -0,0 +1,72 @@
+import { CommonModule } from '@angular/common';
+import { Component, ElementRef, Inject, OnDestroy, OnInit, AfterViewInit, Optional, ViewChild } from '@angular/core';
+import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
+import { AuthTokenHolderService, EXTENSION_ASSET_URL } from '@vcfa/sdk';
+import { Subject } from 'rxjs';
+import { takeUntil } from 'rxjs/operators';
+
+@Component({
+ standalone: true,
+ imports: [CommonModule],
+ selector: 'app-jwt-iframe',
+ templateUrl: './jwt-iframe.component.html',
+ styleUrls: ['./jwt-iframe.component.scss'],
+})
+export class JwtIframeComponent implements OnInit, AfterViewInit, OnDestroy {
+ @ViewChild('iframe', { static: true }) iframe!: ElementRef;
+ private destroy$ = new Subject
();
+ // A placeholder URL for the iframe content.
+ // In a real scenario, this would be the URL of the page that can receive the JWT.
+ iframeSrc: SafeResourceUrl = '/iframe-content.html';
+
+ constructor(
+ @Optional() @Inject(EXTENSION_ASSET_URL) public pluginAssetUrl: string,
+ @Optional() @Inject(AuthTokenHolderService) private authTokenHolderService: AuthTokenHolderService,
+ private sanitizer: DomSanitizer,
+ ) {}
+
+ ngOnInit(): void {
+ this.iframeSrc = this.sanitizer.bypassSecurityTrustResourceUrl(`${this.pluginAssetUrl ? this.pluginAssetUrl : '/assets'}/iframe-content.html`);
+ }
+
+ ngAfterViewInit(): void {
+ this.initTokenListener();
+ window.addEventListener('message', this.handleIframeMessage.bind(this));
+ }
+
+ ngOnDestroy(): void {
+ this.destroy$.next();
+ this.destroy$.complete();
+ window.removeEventListener('message', this.handleIframeMessage.bind(this));
+ }
+
+ private handleIframeMessage(event: MessageEvent): void {
+ if (event.data && event.data.status === 'ready') {
+ this.initTokenListener();
+ }
+ }
+
+ private initTokenListener(): void {
+ if (this.authTokenHolderService) {
+ // Send the initial token
+ this.sendToken(this.authTokenHolderService.jwt);
+
+ // Subscribe to token changes
+ if (this.authTokenHolderService.jwtAsync) {
+ this.authTokenHolderService.jwtAsync
+ .pipe(takeUntil(this.destroy$))
+ .subscribe((token: string | undefined) => {
+ this.sendToken(token);
+ });
+ }
+ } else {
+ this.sendToken(undefined);
+ }
+ }
+
+ private sendToken(token: string | undefined): void {
+ if (this.iframe && this.iframe.nativeElement.contentWindow) {
+ this.iframe.nativeElement.contentWindow.postMessage(token, '*');
+ }
+ }
+}
diff --git a/packages/vcfa-plugin/projects/ui-plugin/src/plugin/plugin.routes.ts b/packages/vcfa-plugin/projects/ui-plugin/src/plugin/plugin.routes.ts
index 5ab4491..e3d63c6 100644
--- a/packages/vcfa-plugin/projects/ui-plugin/src/plugin/plugin.routes.ts
+++ b/packages/vcfa-plugin/projects/ui-plugin/src/plugin/plugin.routes.ts
@@ -1,7 +1,11 @@
import { Routes } from "@angular/router";
import { HomeComponent } from "./home/home.component";
+import { JwtIframeComponent } from "./jwt-iframe/jwt-iframe.component";
+import { ReactAppComponent } from "../react/react-app.component";
export const routes: Routes = [
{ path: "", redirectTo: "home", pathMatch: "full" },
{ path: "home", component: HomeComponent },
-];
\ No newline at end of file
+ { path: "react-example", component: ReactAppComponent },
+ { path: "access-token", component: JwtIframeComponent },
+];
diff --git a/packages/vcfa-plugin/projects/ui-plugin/src/plugin/subnav.component.html b/packages/vcfa-plugin/projects/ui-plugin/src/plugin/subnav.component.html
index 9bcba00..71fdc01 100755
--- a/packages/vcfa-plugin/projects/ui-plugin/src/plugin/subnav.component.html
+++ b/packages/vcfa-plugin/projects/ui-plugin/src/plugin/subnav.component.html
@@ -4,7 +4,7 @@
-
+
{{navItem.labelKey}}
diff --git a/packages/vcfa-plugin/projects/ui-plugin/src/plugin/subnav.component.scss b/packages/vcfa-plugin/projects/ui-plugin/src/plugin/subnav.component.scss
index 780c65a..cc31a6d 100644
--- a/packages/vcfa-plugin/projects/ui-plugin/src/plugin/subnav.component.scss
+++ b/packages/vcfa-plugin/projects/ui-plugin/src/plugin/subnav.component.scss
@@ -1,3 +1,3 @@
.main-content {
- padding: 0 20px;
+ padding: 0 20px !important;
}
\ No newline at end of file
diff --git a/packages/vcfa-plugin/projects/ui-plugin/src/plugin/subnav.component.ts b/packages/vcfa-plugin/projects/ui-plugin/src/plugin/subnav.component.ts
index 42a89e9..aaf1984 100755
--- a/packages/vcfa-plugin/projects/ui-plugin/src/plugin/subnav.component.ts
+++ b/packages/vcfa-plugin/projects/ui-plugin/src/plugin/subnav.component.ts
@@ -4,9 +4,8 @@ import { ClarityModule } from "@clr/angular";
import { StandaloneHelperModule } from "./standalone/standalone.helper.module";
import { RouterLink, RouterLinkActive, RouterOutlet } from "@angular/router";
-import "@cds/core/icon/register";
-import { ClarityIcons, homeIcon } from "@cds/core/icon";
-ClarityIcons.addIcons(homeIcon);
+import { ClarityIcons, homeIcon, cogIcon, atomIcon } from "@clr/angular";
+ClarityIcons.addIcons(homeIcon, cogIcon, atomIcon);
@Component({
imports: [
@@ -20,10 +19,12 @@ ClarityIcons.addIcons(homeIcon);
selector: "plugin-subnav",
templateUrl: "./subnav.component.html",
host: { "class": "content-container" },
- styleUrls: ["subnav.component.scss"],
+ styleUrls: ["./subnav.component.scss"],
})
export class SubnavComponent {
navItems: any[] = [
- { routerLink: "./home", iconShape: "home", labelKey: "Home" }
+ { routerLink: "./home", iconShape: "home", labelKey: "Home" },
+ { routerLink: "./access-token", iconShape: "cog", labelKey: "Access Token" },
+ { routerLink: "./react-example", iconShape: "atom", labelKey: "React Example" },
];
}
diff --git a/packages/vcfa-plugin/projects/ui-plugin/src/react/federation.ts b/packages/vcfa-plugin/projects/ui-plugin/src/react/federation.ts
new file mode 100644
index 0000000..f896f11
--- /dev/null
+++ b/packages/vcfa-plugin/projects/ui-plugin/src/react/federation.ts
@@ -0,0 +1,12 @@
+import { init, ModuleFederation } from '@module-federation/enhanced/runtime';
+
+export function createFederationInstance(): ModuleFederation {
+ // Store React dependencies in separate module federation instance.
+ const mf: ModuleFederation = init({
+ name: 'my-react-plugin-host',
+ remotes: [],
+ shared: {}
+ });
+ mf.initializeSharing();
+ return mf;
+}
diff --git a/packages/vcfa-plugin/projects/ui-plugin/src/react/react-app.component.html b/packages/vcfa-plugin/projects/ui-plugin/src/react/react-app.component.html
new file mode 100644
index 0000000..af2ebd8
--- /dev/null
+++ b/packages/vcfa-plugin/projects/ui-plugin/src/react/react-app.component.html
@@ -0,0 +1 @@
+
diff --git a/packages/vcfa-plugin/projects/ui-plugin/src/react/react-app.component.ts b/packages/vcfa-plugin/projects/ui-plugin/src/react/react-app.component.ts
new file mode 100644
index 0000000..1307543
--- /dev/null
+++ b/packages/vcfa-plugin/projects/ui-plugin/src/react/react-app.component.ts
@@ -0,0 +1,64 @@
+import { CommonModule, NgComponentOutlet } from '@angular/common';
+import { Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, Inject, Optional, ViewChild } from '@angular/core';
+import { RouterOutlet } from '@angular/router';
+import { ClarityModule } from '@clr/angular';
+import { EXTENSION_ASSET_URL, EXTENSION_ROUTE } from '@vcfa/sdk';
+
+import { createFederationInstance } from './federation';
+
+@Component({
+ selector: 'react-app',
+ imports: [
+ RouterOutlet,
+ ClarityModule,
+ NgComponentOutlet,
+ CommonModule,
+ ],
+ templateUrl: './react-app.component.html',
+ schemas: [
+ CUSTOM_ELEMENTS_SCHEMA
+ ],
+})
+export class ReactAppComponent {
+ @ViewChild('reactApp', {static: true}) reactApp: ElementRef | undefined;
+
+ private assetsUrl: string = '/assets';
+ private baseHref: string = '/react-example';
+
+ constructor(
+ @Optional() @Inject(EXTENSION_ASSET_URL) private EXTENSION_ASSET_URL: string,
+ @Optional() @Inject(EXTENSION_ROUTE) private EXTENSION_ROUTE: string,
+ ) {
+ if (this.EXTENSION_ASSET_URL) {
+ this.assetsUrl = this.EXTENSION_ASSET_URL;
+ }
+
+ if (this.EXTENSION_ROUTE) {
+ this.baseHref = `${this.EXTENSION_ROUTE}/react-example`;
+ }
+
+ this.loadReactApplication();
+ }
+
+ private loadReactApplication() {
+ const mf = createFederationInstance();
+ mf.registerRemotes([
+ {
+ name: "react",
+ entry: `${this.assetsUrl}/react-plugin/dist/remoteEntry.js`,
+ type: "script",
+ entryGlobalName: "react_example_ui"
+ }
+ ]);
+
+ mf.loadRemote("react/web-components").then((m) => {
+ const elementName = 'react-element';
+ const reactApp = document.createElement(elementName);
+ reactApp.setAttribute("baseHref", this.baseHref);
+ this.reactApp?.nativeElement.appendChild(reactApp);
+ }).catch((e) => {
+ console.error("React Application failed to load", e);
+ this.reactApp?.nativeElement.appendChild(document.createTextNode("React Application failed to load..."));
+ });
+ }
+}
diff --git a/packages/vcfa-plugin/projects/ui-plugin/src/styles.scss b/packages/vcfa-plugin/projects/ui-plugin/src/styles.scss
index 2609775..6386d9c 100644
--- a/packages/vcfa-plugin/projects/ui-plugin/src/styles.scss
+++ b/packages/vcfa-plugin/projects/ui-plugin/src/styles.scss
@@ -1,4 +1,2 @@
/* You can add global styles to this file, and also import other style files */
-@import '@cds/core/global.min.css';
-@import '@cds/core/styles/theme.dark.min.css';
@import '@clr/ui/clr-ui.min.css';
\ No newline at end of file
diff --git a/packages/vcfa-plugin/projects/ui-plugin/vcf-webpack-util.js b/packages/vcfa-plugin/projects/ui-plugin/vcf-webpack-util.js
index b696241..f30cac6 100644
--- a/packages/vcfa-plugin/projects/ui-plugin/vcf-webpack-util.js
+++ b/packages/vcfa-plugin/projects/ui-plugin/vcf-webpack-util.js
@@ -21,9 +21,8 @@ function prepare() {
const coreLibs = [
"@angular",
"@clr",
- "@cds",
"rxjs",
- "@vcfa"
+ "@vcfa/container-hooks"
];
const libsToShare = Object.keys(allRecords).map((npmPackageName) => {