Compare commits

..

3 Commits

19 changed files with 341 additions and 4293 deletions

View File

@@ -2,15 +2,19 @@
> [English version](README.md) > [English version](README.md)
Beispielprojekt, das zeigt, wie ein **PHX-ERP-Frontend-Plugin** als Angular-Web-Component mit `@phx/shared` und `@phx/shared-ui` erstellt wird. Beispielprojekt, das zeigt, wie ein **PHX-ERP-Frontend-Plugin** als Angular-Web-Component mit `@phx-erp/shared` und `@phx-erp/shared-ui` erstellt wird.
**Neues Plugin starten?** Nutzen Sie [@phx-erp/create-phx-frontend-plugin](https://www.npmjs.com/package/@phx-erp/create-phx-frontend-plugin), um ein Projekt aus dem offiziellen Template zu erzeugen. Dieses Repository ist eine vollständige Demo zum Erkunden, Klonen oder als Referenz.
Dieselbe Codebasis unterstützt zwei Arbeitsweisen: Dieselbe Codebasis unterstützt zwei Arbeitsweisen:
| Modus | Zweck | Typischer Befehl | | Modus | Zweck | Typischer Befehl |
|-------|-------|------------------| | --------------------- | ----------------------------------------------- | ----------------- |
| **Plugin host** | Ausführung in PHX als Custom Element | `yarn run plugin` | | **Plugin host** | Ausführung in PHX als Custom Element | `yarn run plugin` |
| **Standalone client** | Lokale Entwicklung wie eine normale Angular-App | `yarn client` | | **Standalone client** | Lokale Entwicklung wie eine normale Angular-App | `yarn client` |
--- ---
## Inhaltsverzeichnis ## Inhaltsverzeichnis
@@ -22,6 +26,7 @@ Dieselbe Codebasis unterstützt zwei Arbeitsweisen:
- [Entwicklungsmodi](#entwicklungsmodi) - [Entwicklungsmodi](#entwicklungsmodi)
- [Projektstruktur](#projektstruktur) - [Projektstruktur](#projektstruktur)
- [Eigenes Plugin erstellen](#eigenes-plugin-erstellen) - [Eigenes Plugin erstellen](#eigenes-plugin-erstellen)
- [Scaffold-CLI (empfohlen)](#scaffold-cli-empfohlen)
- [1. Grundsetup](#1-grundsetup) - [1. Grundsetup](#1-grundsetup)
- [2. Tailwind CSS](#2-tailwind-css) - [2. Tailwind CSS](#2-tailwind-css)
- [3. GraphQL Codegen](#3-graphql-codegen) - [3. GraphQL Codegen](#3-graphql-codegen)
@@ -42,10 +47,10 @@ Dieselbe Codebasis unterstützt zwei Arbeitsweisen:
Ein PHX-Frontend-Plugin ist eine Angular-Anwendung, die als **Custom Element** (Web Component) verpackt wird. PHX lädt es über ein **Manifest**, das auf die kompilierte `main.js` verweist und den Element-Tag-Namen deklariert. Ein PHX-Frontend-Plugin ist eine Angular-Anwendung, die als **Custom Element** (Web Component) verpackt wird. PHX lädt es über ein **Manifest**, das auf die kompilierte `main.js` verweist und den Element-Tag-Namen deklariert.
``` ```
┌─────────────────┐ manifest.json ┌──────────────────┐ ┌─────────────────┐ manifest.json ┌──────────────────
│ PHX host │ ─────────────────────► │ main.js (+ deps) │ │ PHX host │ ─────────────────────► │ main.js (+ deps) │
│ (ERP shell) │ loads & registers │ custom element │ │ (ERP shell) │ loads & registers │ custom element │
└────────┬────────┘ └────────┬─────────┘ └────────┬────────┘ └────────┬─────────
│ │ │ │
│ pluginServices (Apollo, notifications…) │ │ pluginServices (Apollo, notifications…) │
│ hostInjector │ │ hostInjector │
@@ -57,7 +62,7 @@ Ein PHX-Frontend-Plugin ist eine Angular-Anwendung, die als **Custom Element** (
- Custom-Element-Tag: `frontend-plugin-demo` - Custom-Element-Tag: `frontend-plugin-demo`
- Beispiel-Routen: Hello World, Product View, Address List - Beispiel-Routen: Hello World, Product View, Address List
- GraphQL-Abfragen über den PHX-Host-Apollo (Production) oder einen lokalen Apollo-Client (Development) - GraphQL-Abfragen über den PHX-Host-Apollo (Production) oder einen lokalen Apollo-Client (Development)
- Login-Flow für die eigenständige Entwicklung ohne vorkonfigurierten API-Key - PHX-Login-Weiterleitung und Callback-Route für die eigenständige Entwicklung ohne vorkonfigurierten API-Key
- PrimeNG- und Tailwind-Styling im PHX-Stil - PrimeNG- und Tailwind-Styling im PHX-Stil
--- ---
@@ -67,40 +72,32 @@ Ein PHX-Frontend-Plugin ist eine Angular-Anwendung, die als **Custom Element** (
- [Node.js](https://nodejs.org/) (LTS empfohlen) - [Node.js](https://nodejs.org/) (LTS empfohlen)
- [Yarn v4](https://yarnpkg.com/getting-started/install) - [Yarn v4](https://yarnpkg.com/getting-started/install)
- Eine laufende PHX-Instanz (für Plugin-Host-Tests und GraphQL-Schema/Codegen) - Eine laufende PHX-Instanz (für Plugin-Host-Tests und GraphQL-Schema/Codegen)
- Ein **npm-Zugangstoken** mit Lesezugriff auf PHXGMBH-Pakete — anfordern über Ihren PHX-Partner
Für die lokale eigenständige Entwicklung benötigen Sie außerdem eines der folgenden: Für die lokale eigenständige Entwicklung benötigen Sie außerdem eines der folgenden:
- Einen PHX-API-Benutzer-Token in Ihrer Development-Umgebung, oder - Einen PHX-API-Benutzer-Token in Ihrer Development-Umgebung, oder
- Gültige Anmeldedaten für den integrierten Login-Bildschirm (siehe [Apollo, Auth Guard und Login](#apollo-auth-guard-und-login)) - Zugriff auf die PHX-Login-Seite (siehe [Apollo, Auth Guard und Login](#apollo-auth-guard-und-login))
--- ---
## Schnellstart ## Schnellstart
1. **Yarn konfigurieren**, um auf die PHX-npm-Registry zuzugreifen (siehe [PHX-Bibliotheken](#5-phx-bibliotheken)). `.yarnrc.yml` nicht committen. Diese Schritte gelten für **dieses Demo-Repository**. Um ein eigenes Plugin von Grund auf zu erstellen, nutzen Sie die Scaffold-CLI — siehe [Eigenes Plugin erstellen](#eigenes-plugin-erstellen).
2. **Abhängigkeiten installieren:**
1. **Abhängigkeiten installieren:**
```bash ```bash
yarn install yarn install
``` ```
2. **Lokale Development-Umgebung anlegen** (optional, für den Standalone-Modus):
3. **Lokale Development-Umgebung anlegen** (optional, für den Standalone-Modus):
```bash ```bash
cp src/environments/environment.example.ts src/environments/environment.development.ts cp src/environments/environment.example.ts src/environments/environment.development.ts
``` ```
Passen Sie `apiUrl`, `wsUrl` und optional `apiKey` in dieser Datei an. Die Datei ist gitignored. Passen Sie `apiUrl`, `wsUrl` und optional `apiKey` in dieser Datei an. Die Datei ist gitignored.
3. **GraphQL-Typen generieren** (erfordert PHX Admin API unter der in `codegen.ts` konfigurierten Schema-URL):
4. **GraphQL-Typen generieren** (erfordert PHX Admin API unter der in `codegen.ts` konfigurierten Schema-URL):
```bash ```bash
yarn codegen yarn codegen
``` ```
4. **In einem der Entwicklungsmodi** unten starten.
5. **In einem der Entwicklungsmodi** unten starten.
--- ---
@@ -109,10 +106,10 @@ Für die lokale eigenständige Entwicklung benötigen Sie außerdem eines der fo
Um diese Demo in Ihrer eigenen Instanz zu nutzen, fügen Sie unter **Admin → Custom Elements** (`https://your.phx.instance/admin/customElements`) ein Custom Element mit folgender Manifest-URL hinzu: Um diese Demo in Ihrer eigenen Instanz zu nutzen, fügen Sie unter **Admin → Custom Elements** (`https://your.phx.instance/admin/customElements`) ein Custom Element mit folgender Manifest-URL hinzu:
``` ```
https://gitea.phx-erp.de/api/v1/repos/PHXGMBH/phx-frontend-plugin-webcomponent-demo/raw/master/latest/manifest.json https://unpkg.com/@phx-erp/phx-frontend-plugin-demo/manifest.json
``` ```
Das Manifest verweist auf die kompilierte `main.js` im Verzeichnis `latest/` dieses Repositorys. Das `path`-Feld des Manifests verweist auf die Paketwurzel; unpkg liefert `main.js` über den `"."`-Export. Für die lokale Entwicklung mit `yarn run plugin` registrieren Sie stattdessen `http://localhost:3223/local.manifest.json` unter Admin → Custom Elements.
--- ---
@@ -120,14 +117,14 @@ Das Manifest verweist auf die kompilierte `main.js` im Verzeichnis `latest/` die
### Standalone client (üblicher Angular-Workflow) ### Standalone client (üblicher Angular-Workflow)
Startet die App mit der **Development**-Build-Konfiguration auf Port **4201** — Routing, Login und ein lokaler Apollo-Client funktionieren ohne PHX. Startet die App mit der **Development**-Build-Konfiguration auf Port **4201** — Routing, PHX-Login-Weiterleitung und ein lokaler Apollo-Client funktionieren ohne Einbettung des Plugins in PHX.
```bash ```bash
yarn client yarn client
# equivalent: ng serve --port 4201 --watch --configuration development # equivalent: ng serve --port 4201 --watch --configuration development
``` ```
Öffnen Sie **http://localhost:4201/**. Öffnen Sie **[http://localhost:4201/](http://localhost:4201/)**.
### Plugin-Host-Modus (innerhalb von PHX) ### Plugin-Host-Modus (innerhalb von PHX)
@@ -139,18 +136,20 @@ yarn run plugin
Dies führt `yarn build` und `yarn serve` parallel aus: Dies führt `yarn build` und `yarn serve` parallel aus:
| Skript | Funktion | | Skript | Funktion |
|--------|----------| | ------------ | -------------------------------------------------------------------------------------------- |
| `yarn build` | Production-Watch-Build (in diesem Repo zusätzlich Sync nach `latest/` für die gehostete Demo) | | `yarn build` | Production-Watch-Build |
| `yarn serve` | Stellt `dist/.../browser/` unter **http://localhost:3223/** bereit | | `yarn serve` | Stellt `dist/.../browser/` unter **[http://localhost:3223/](http://localhost:3223/)** bereit |
Verweisen Sie Ihre PHX-Instanz auf das lokale Manifest: Verweisen Sie Ihre PHX-Instanz auf das lokale Manifest:
``` ```
http://localhost:3223/manifest.json http://localhost:3223/local.manifest.json
``` ```
(`public/manifest.json` wird in die bereitgestellte Ausgabe kopiert.) (`public/local.manifest.json` wird in die bereitgestellte Ausgabe kopiert.)
> **Hinweis:** Verwenden Sie `yarn run plugin`, nicht `yarn plugin` — Yarn behandelt `plugin` als eingebauten Befehl. > **Hinweis:** Verwenden Sie `yarn run plugin`, nicht `yarn plugin` — Yarn behandelt `plugin` als eingebauten Befehl.
@@ -169,16 +168,15 @@ yarn codegen # Regenerate GraphQL types
``` ```
phx-frontend-plugin-demo/ phx-frontend-plugin-demo/
├── latest/ # Published build output (main.js, manifest.json)
├── public/ ├── public/
│ └── manifest.json # Local manifest (path → localhost:3223) │ └── local.manifest.json # Lokales Dev-Manifest (in dist kopiert)
├── manifest.json # Veröffentlichtes Manifest (npm/unpkg)
├── scripts/ ├── scripts/
│ ├── copy-latest.mjs # (this repo only) Sync dist/*.js → latest/ for hosted demo
│ └── serve-dist.mjs # Static server for plugin-host dev │ └── serve-dist.mjs # Static server for plugin-host dev
├── src/ ├── src/
│ ├── app/ │ ├── app/
│ │ ├── components/ # Demo pages (hello-world, product-view, …) │ │ ├── components/ # Demo pages (hello-world, product-view, …)
│ │ ├── login/ # Login form (standalone development) │ │ ├── login.ts # Auth-Callback-Route — Weiterleitungsflow (Standalone-Entwicklung)
│ │ ├── services/ │ │ ├── services/
│ │ │ ├── apollo.service.ts │ │ │ ├── apollo.service.ts
│ │ │ └── phoenix-host-bridge.service.ts │ │ │ └── phoenix-host-bridge.service.ts
@@ -201,7 +199,23 @@ phx-frontend-plugin-demo/
## Eigenes Plugin erstellen ## Eigenes Plugin erstellen
Die folgenden Schritte führen durch die Erstellung eines Projekts, das dieser Demo ähnelt, mit [Yarn v4](https://yarnpkg.com/getting-started/install), [Angular 20](https://angular.dev/), [PrimeNG 20](https://primeng.org/) und [Tailwind CSS](https://tailwindcss.com/). ### Scaffold-CLI (empfohlen)
Der schnellste Weg ist die offizielle Scaffold-CLI, veröffentlicht als [@phx-erp/create-phx-frontend-plugin](https://www.npmjs.com/package/@phx-erp/create-phx-frontend-plugin):
```bash
# Yarn Berry — einmalig, ohne globale Installation
yarn dlx @phx-erp/create-phx-frontend-plugin
# npm
npx @phx-erp/create-phx-frontend-plugin@latest
```
Dabei wird das offizielle Plugin-Template kopiert, Platzhalter ersetzt (Projektname, Custom-Element-Tag, API-URLs), `environment.development.ts` angelegt und optional `yarn install` ausgeführt. Nicht-interaktive Flags (`--yes`, `--install`, `--api-url`, `--ui-url`, `--api-key` usw.) sind auf der [Paketseite](https://www.npmjs.com/package/@phx-erp/create-phx-frontend-plugin) dokumentiert.
### Manuelles Setup (Schritt für Schritt)
Wenn Sie jeden Schritt nachvollziehen oder von Grund auf anpassen möchten, führen die folgenden Abschnitte durch die Erstellung eines Projekts, das dieser Demo ähnelt, mit [Yarn v4](https://yarnpkg.com/getting-started/install), [Angular 20](https://angular.dev/), [PrimeNG 20](https://primeng.org/) und [Tailwind CSS](https://tailwindcss.com/).
Ersetzen Sie Platzhalter wie `*PROJECT-NAME*`, `*YOUR-TAG*` und `*YOUR-TOKEN*` durch Ihre Werte. Ersetzen Sie Platzhalter wie `*PROJECT-NAME*`, `*YOUR-TAG*` und `*YOUR-TOKEN*` durch Ihre Werte.
@@ -249,7 +263,7 @@ module.exports = {
}; };
``` ```
Aufgrund eines bekannten Problems müssen diese Direktiven in den **`styles` jeder Komponente** (und in `src/styles.scss` für die lokale Entwicklung) ergänzt werden: Aufgrund eines bekannten Problems müssen diese Direktiven in den `**styles` jeder Komponente** (und in `src/styles.scss` für die lokale Entwicklung) ergänzt werden:
```css ```css
@tailwind base; @tailwind base;
@@ -310,7 +324,7 @@ Dies erzeugt `src/app/schema-types.ts` und `src/app/generated.ts`.
Verwenden Sie getrennte Environments, damit derselbe Build als PHX-Plugin (Production) oder als eigenständige Dev-App (Development) läuft. Verwenden Sie getrennte Environments, damit derselbe Build als PHX-Plugin (Production) oder als eigenständige Dev-App (Development) läuft.
**`src/environments/environment.interface.ts`** `**src/environments/environment.interface.ts**`
```ts ```ts
export abstract class Environment { export abstract class Environment {
@@ -322,7 +336,7 @@ export abstract class Environment {
} }
``` ```
**`src/environments/environment.ts`** (Production — verwendet bei Einbettung in PHX) `**src/environments/environment.ts**` (Production — verwendet bei Einbettung in PHX)
```ts ```ts
import { Environment } from './environment.interface'; import { Environment } from './environment.interface';
@@ -336,7 +350,7 @@ export const environment: Environment = {
}; };
``` ```
**`src/environments/environment.development.ts`** (lokale Entwicklung — in `.gitignore` aufnehmen) `**src/environments/environment.development.ts**` (lokale Entwicklung — in `.gitignore` aufnehmen)
```ts ```ts
import { Environment } from './environment.interface'; import { Environment } from './environment.interface';
@@ -345,8 +359,8 @@ export const environment: Environment = {
production: false, production: false,
apiUrl: 'http://localhost:3000/admin-api', apiUrl: 'http://localhost:3000/admin-api',
wsUrl: 'ws://localhost:3000/admin-api', wsUrl: 'ws://localhost:3000/admin-api',
apiKey: undefined, // or a PHX API user token; otherwise use the login route apiKey: undefined, // or a PHX API user token; otherwise redirects to PHX login
serverUrl: 'https://localhost:4200', serverUrl: 'https://localhost:4200', // PHX instance used for standalone login redirect
}; };
``` ```
@@ -367,24 +381,12 @@ Fügen Sie `fileReplacements` zur **Development**-Build-Konfiguration in `angula
### 5. PHX-Bibliotheken ### 5. PHX-Bibliotheken
`@phx/shared` und `@phx/shared-ui` werden in der PHXGMBH-npm-Registry veröffentlicht. `@phx-erp/shared` und `@phx-erp/shared-ui` werden in der öffentlichen npm-Registry veröffentlicht.
Erstellen Sie `.yarnrc.yml` im Projektroot (oder bearbeiten Sie `~/.yarnrc.yml` für eine globale Konfiguration): Installieren Sie die Bibliotheken:
```yml
nodeLinker: node-modules
npmScopes:
phx:
npmRegistryServer: "https://gitea.phx-erp.de/api/packages/PHXGMBH/npm/"
npmAuthToken: "*YOUR-TOKEN*"
```
> **Wichtig:** Nehmen Sie `.yarnrc.yml` in `.gitignore` auf, wenn sie Ihren Token enthält.
Installieren Sie anschließend die Bibliotheken:
```bash ```bash
yarn add @phx/shared @phx/shared-ui yarn add @phx-erp/shared @phx-erp/shared-ui
``` ```
### 6. Plugin-Setup ### 6. Plugin-Setup
@@ -396,7 +398,7 @@ Fügen Sie `providePhoenixPluginWithPrimeNG` in `src/app/app.config.ts` hinzu:
```ts ```ts
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { providePhoenixPluginWithPrimeNG } from '@phx/shared-ui'; import { providePhoenixPluginWithPrimeNG } from '@phx-erp/shared-ui';
import { routes } from './app.routes'; import { routes } from './app.routes';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
@@ -424,7 +426,7 @@ Erstellen Sie `src/app/services/phoenix-host-bridge.service.ts`:
```ts ```ts
import { Injectable, Injector, signal } from '@angular/core'; import { Injectable, Injector, signal } from '@angular/core';
import type { IPluginServices } from '@phx/shared-ui'; import type { IPluginServices } from '@phx-erp/shared-ui';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class PhoenixHostBridgeService { export class PhoenixHostBridgeService {
@@ -459,7 +461,7 @@ Ihre Root-Komponente empfängt `pluginServices` und `hostInjector` von PHX und l
import { Component, effect, inject, Injector, input } from '@angular/core'; import { Component, effect, inject, Injector, input } from '@angular/core';
import { RouterOutlet } from '@angular/router'; import { RouterOutlet } from '@angular/router';
import { PhoenixHostBridgeService } from './services/phoenix-host-bridge.service'; import { PhoenixHostBridgeService } from './services/phoenix-host-bridge.service';
import { IPluginServices, syncPhoenixHostInjector } from '@phx/shared-ui'; import { IPluginServices, syncPhoenixHostInjector } from '@phx-erp/shared-ui';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@@ -492,7 +494,7 @@ Registrieren Sie in `src/main.ts` Ihr Plugin als Custom Element (ersetzen Sie `*
```ts ```ts
import { appConfig } from './app/app.config'; import { appConfig } from './app/app.config';
import { App } from './app/app'; import { App } from './app/app';
import { bootstrapPhoenixPluginCustomElement } from '@phx/shared-ui'; import { bootstrapPhoenixPluginCustomElement } from '@phx-erp/shared-ui';
import { environment } from './environments/environment'; import { environment } from './environments/environment';
bootstrapPhoenixPluginCustomElement(App, '*YOUR-TAG*', appConfig).then((app) => { bootstrapPhoenixPluginCustomElement(App, '*YOUR-TAG*', appConfig).then((app) => {
@@ -503,8 +505,8 @@ bootstrapPhoenixPluginCustomElement(App, '*YOUR-TAG*', appConfig).then((app) =>
}); });
``` ```
- **`bootstrapPhoenixPluginCustomElement`** — erstellt die Angular-Anwendung und registriert das Custom Element für PHX. - `bootstrapPhoenixPluginCustomElement` — erstellt die Angular-Anwendung und registriert das Custom Element für PHX.
- **`app.bootstrap(App)` in Development** — mountet zusätzlich die Root-Komponente, damit `ng serve` / der Standalone-Modus mit Routing funktioniert. - `app.bootstrap(App)` in Development — mountet zusätzlich die Root-Komponente, damit `ng serve` / der Standalone-Modus mit Routing funktioniert.
Der Custom-Element-Tag muss **kleingeschrieben mit Bindestrichen** sein (z. B. `my-company-orders`). Verwenden Sie denselben Tag in Ihrem Manifest. Der Custom-Element-Tag muss **kleingeschrieben mit Bindestrichen** sein (z. B. `my-company-orders`). Verwenden Sie denselben Tag in Ihrem Manifest.
@@ -525,12 +527,14 @@ PHX lädt ein JSON-Manifest, das auf Ihr Entry-Skript verweist und den Custom-El
} }
``` ```
| Feld | Beschreibung | | Feld | Beschreibung |
|------|--------------| | ----------------- | --------------------------------------------------------- |
| `path` | Absolute URL zu `main.js` (und Basis für Chunk-Auflösung) | | `path` | Absolute URL zu `main.js` (und Basis für Chunk-Auflösung) |
| `items[].tagName` | In `main.ts` registrierter Custom-Element-Tag | | `items[].tagName` | In `main.ts` registrierter Custom-Element-Tag |
**Beispiel lokale Entwicklung** (`public/manifest.json` in diesem Repo):
**Beispiel lokale Entwicklung** (`public/local.manifest.json` in diesem Repo):
```json ```json
{ {
@@ -539,11 +543,11 @@ PHX lädt ein JSON-Manifest, das auf Ihr Entry-Skript verweist und den Custom-El
} }
``` ```
**Gehostetes Beispiel** (`latest/manifest.json` in diesem Repo): **Veröffentlichtes Beispiel** (Root-`manifest.json`, über npm/unpkg bereitgestellt):
```json ```json
{ {
"path": "https://gitea.phx-erp.de/api/v1/repos/PHXGMBH/phx-frontend-plugin-webcomponent-demo/raw/master/latest/main.js", "path": "https://unpkg.com/@phx-erp/phx-frontend-plugin-demo",
"items": [{ "tagName": "frontend-plugin-demo" }] "items": [{ "tagName": "frontend-plugin-demo" }]
} }
``` ```
@@ -553,7 +557,7 @@ PHX lädt ein JSON-Manifest, das auf Ihr Entry-Skript verweist und den Custom-El
1. Hosten Sie `main.js` (und ggf. weitere Chunks) unter einer URL, die aus den Browsern Ihrer Nutzer erreichbar ist — dieselben Netzwerkregeln wie für Ihre PHX-Instanz. 1. Hosten Sie `main.js` (und ggf. weitere Chunks) unter einer URL, die aus den Browsern Ihrer Nutzer erreichbar ist — dieselben Netzwerkregeln wie für Ihre PHX-Instanz.
2. Veröffentlichen Sie ein Manifest-JSON unter einer stabilen URL. 2. Veröffentlichen Sie ein Manifest-JSON unter einer stabilen URL.
3. In PHX: **Admin → Custom Elements** → Manifest-URL hinzufügen und Mount-Pfad oder Tag wählen. 3. In PHX: **Admin → Custom Elements** → Manifest-URL hinzufügen und Mount-Pfad oder Tag wählen.
4. Ab- und wieder anmelden. Das Plugin ist verfügbar unter `https://your.phx.instance/customElements/*PATH*`. 4. Ab- und wieder anmelden. Das Plugin ist verfügbar unter `https://your.phx.instance/customElements/*PATH`*.
Für die lokale Plugin-Host-Entwicklung siehe [Plugin lokal bereitstellen](#plugin-lokal-bereitstellen). Für die lokale Plugin-Host-Entwicklung siehe [Plugin lokal bereitstellen](#plugin-lokal-bereitstellen).
@@ -569,7 +573,7 @@ Statischen Server installieren:
yarn add -D serve yarn add -D serve
``` ```
**`scripts/serve-dist.mjs`** `**scripts/serve-dist.mjs**`
```js ```js
import { spawn } from 'node:child_process'; import { spawn } from 'node:child_process';
@@ -590,7 +594,7 @@ const child = spawn(serveBin, ['-l', port, '--cors', '--no-etag'], {
child.on('exit', (code) => process.exit(code ?? 0)); child.on('exit', (code) => process.exit(code ?? 0));
``` ```
**`serve.json`** (ersetzen Sie `*PROJECT-NAME*` durch Ihren Angular-Projektnamen aus `angular.json`) `**serve.json**` (ersetzen Sie `*PROJECT-NAME*` durch Ihren Angular-Projektnamen aus `angular.json`)
```json ```json
{ {
@@ -616,13 +620,11 @@ In `package.json` ergänzen:
Eigener Port: `PORT=8080 yarn serve`. Eigener Port: `PORT=8080 yarn serve`.
> **Nur in diesem Repository:** `scripts/copy-latest.mjs` kopiert gebaute JS-Dateien nach `latest/`, damit das [Live-Demo](#live-demo)-Manifest auf einen stabilen Pfad in Git verweisen kann. Für die lokale Plugin-Host-Entwicklung ist das nicht erforderlich.
### Apollo, Auth Guard und Login ### Apollo, Auth Guard und Login
Im **Production**-Modus (eingebettet in PHX) übernimmt der Host die Authentifizierung. Nutzen Sie den Apollo Client aus `IPluginServices` über `PhoenixHostBridgeService` — ein separater Login-Flow ist nicht nötig. Im **Production**-Modus (eingebettet in PHX) übernimmt der Host die Authentifizierung. Nutzen Sie den Apollo Client aus `IPluginServices` über `PhoenixHostBridgeService` — ein separater Login-Flow ist nicht nötig.
Im **Development**-Modus (Standalone) stellen Sie einen eigenen Apollo Client und optional einen Login bereit. Im **Development**-Modus (Standalone) stellen Sie einen eigenen Apollo Client bereit und leiten nicht authentifizierte Nutzer zur PHX-Login-Seite weiter. PHX leitet danach zurück auf eine lokale `/login`-Callback-Route, die den Token speichert und den Nutzer zur ursprünglichen Seite zurückbringt.
#### Apollo provider #### Apollo provider
@@ -663,54 +665,53 @@ export class ApolloService {
Wenden Sie dasselbe Muster auf andere Host-Services an (z. B. Notifications), wenn Sie Standalone-Fallbacks benötigen. Wenden Sie dasselbe Muster auf andere Host-Services an (z. B. Notifications), wenn Sie Standalone-Fallbacks benötigen.
#### Login component #### Login-Callback-Route
Für die Entwicklung ohne voreingestellten API-Key gen Sie eine `/login`-Route hinzu, die die PHX-`login`-Mutation aufruft: Für die Entwicklung ohne voreingestellten API-Key benötigen Sie einen Weg, einen API-Token zu erhalten und zu speichern. Dieses Demo nutzt einen **weiterleitungsbasierten Auth-Callback** auf `/login` statt eines eingebetteten Login-Formulars — beide Ansätze sind jedoch möglich: Sie können auch ein eigenes Login-Formular bauen, die GraphQL-`login`-Mutation aufrufen und den Token selbst verwalten.
```gql Bei der Weiterleitungsvariante leitet der Auth Guard nicht authentifizierte Nutzer zur PHX-Login-Seite (`environment.serverUrl`) weiter. Nach erfolgreichem Login leitet PHX zurück auf die `/login`-Route Ihres Plugins mit einem `authToken`-Query-Parameter und einem base64-kodierten `redirectTo`-Ziel. Die Callback-Komponente speichert den Token in `localStorage` und navigiert zurück zur ursprünglichen Seite:
mutation Login($username: String!, $password: String!) {
login(username: $username, password: $password) { ```ts
... on CurrentUser { @Component({
id selector: 'app-login',
identifier template: `
channels { <div class="flex flex-col items-center justify-center h-screen w-screen opacity-20">
id <div class="animate-fadein duration-1000 animate-infinite animate-alternate text-4xl">Signing in...</div>
token </div>
} `,
} })
... on InvalidCredentialsError { export class Login {
errorCode constructor(private readonly route: ActivatedRoute) {
message this.route.queryParams.subscribe((params) => {
} const token = params['authToken'];
... on NativeAuthStrategyError { if (token) {
errorCode const redirectTo = decodeURIComponent(atob(params['redirectTo']));
message localStorage.setItem('api-key', token);
} window.history.replaceState({}, '', redirectTo);
... on EmailCodeAuthStrategyError { window.location.href = redirectTo;
errorCode
message
} }
});
} }
} }
``` ```
Eine vollständige Formular-Implementierung finden Sie unter `src/app/login/` in diesem Repo. Die vollständige Implementierung finden Sie unter `src/app/login.ts` in diesem Repo.
#### Auth guard #### Auth guard
Nicht authentifizierte Nutzer nur im Development-Modus zum Login weiterleiten: Nicht authentifizierte Nutzer nur im Development-Modus zur PHX-Login-Seite weiterleiten. Der `redirectTo`-Query-Parameter verknüpft die Weiterleitung über die `/login`-Callback-Route Ihres Plugins:
```ts ```ts
@Injectable() @Injectable()
export class AuthGuard implements CanActivate { export class AuthGuard implements CanActivate {
private readonly router = inject(Router);
canActivate(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): boolean { canActivate(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): boolean {
const token = environment.apiKey ?? localStorage.getItem('api-key'); const token = environment.apiKey ?? localStorage.getItem('api-key');
if (!environment.production && !token) { if (!environment.production && !token) {
this.router.navigate(['login'], { const redirectTo = encodeURIComponent(
queryParams: { redirectTo: btoa(window.location.pathname + window.location.search) }, btoa(`${window.location.protocol}//${window.location.host}/login?redirectTo=${encodeURIComponent(btoa(window.location.href))}`)
}); );
window.location.href = `${environment.serverUrl}/login?redirectTo=${redirectTo}`;
return false;
} }
return true; return true;
} }
@@ -746,32 +747,34 @@ Empfohlene Skripte nach dieser Anleitung:
} }
``` ```
| Skript | Beschreibung | | Skript | Beschreibung |
|--------|--------------| | --------- | ---------------------------------------------------------------------- |
| `build` | Production-Watch-Build für PHX | | `build` | Production-Watch-Build für PHX |
| `serve` | Stellt kompilierte Assets für PHX bereit (Standard-Port 3223) | | `serve` | Stellt kompilierte Assets für PHX bereit (Standard-Port 3223) |
| `plugin` | Führt `build` + `serve` zusammen aus — verwenden Sie `yarn run plugin` | | `plugin` | Führt `build` + `serve` zusammen aus — verwenden Sie `yarn run plugin` |
| `client` | Eigenständiger Angular-Dev-Server auf Port 4201 | | `client` | Eigenständiger Angular-Dev-Server auf Port 4201 |
| `codegen` | Generiert GraphQL-TypeScript-Typen neu | | `codegen` | Generiert GraphQL-TypeScript-Typen neu |
Dieses Repository führt zusätzlich `copy-latest.mjs` parallel zu `build` aus, um Artefakte nach `latest/` für die gehostete Demo zu veröffentlichen — das ist für Ihr eigenes Plugin nicht erforderlich.
--- ---
## Fehlerbehebung ## Fehlerbehebung
| Symptom | Zu prüfen | | Symptom | Zu prüfen |
|---------|-----------| | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| PHX zeigt eine alte Plugin-Version | Hard-Refresh; prüfen, ob `serve.json` `Cache-Control: no-store` für JS setzt; `yarn run plugin` neu starten | | PHX zeigt eine alte Plugin-Version | Hard-Refresh; prüfen, ob `serve.json` `Cache-Control: no-store` für JS setzt; `yarn run plugin` neu starten |
| `401` / GraphQL-Auth-Fehler im Standalone-Modus | `apiKey` in `environment.development.ts` setzen oder über `/login` anmelden | | `401` / GraphQL-Auth-Fehler im Standalone-Modus | `apiKey` in `environment.development.ts` setzen, oder sicherstellen, dass `serverUrl` auf Ihre PHX-Instanz zeigt, damit die Login-Weiterleitung funktioniert |
| `yarn add @phx/shared` schlägt fehl | Token in `.yarnrc.yml` prüfen; Ihren PHX-Partner kontaktieren | | `yarn add @phx-erp/shared` schlägt fehl | Netzwerkzugriff auf die öffentliche npm-Registry prüfen; `yarn install` erneut ausführen |
| Routing in PHX funktioniert nicht | Routen-Segmente zu `stripTrailingSegments` in `providePhoenixPluginWithPrimeNG` hinzufügen | | Routing in PHX funktioniert nicht | Routen-Segmente zu `stripTrailingSegments` in `providePhoenixPluginWithPrimeNG` hinzufügen |
| Tailwind-Klassen fehlen in einer Komponente | `@tailwind`-Direktiven in den `styles` der Komponente ergänzen | | Tailwind-Klassen fehlen in einer Komponente | `@tailwind`-Direktiven in den `styles` der Komponente ergänzen |
| `yarn codegen` schlägt fehl | PHX muss laufen und die Schema-URL in `codegen.ts` erreichbar sein | | `yarn codegen` schlägt fehl | PHX muss laufen und die Schema-URL in `codegen.ts` erreichbar sein |
| Custom Element nicht gefunden | Tag im Manifest muss exakt mit `customElements.define` / `bootstrapPhoenixPluginCustomElement` übereinstimmen | | Custom Element nicht gefunden | Tag im Manifest muss exakt mit `customElements.define` / `bootstrapPhoenixPluginCustomElement` übereinstimmen |
--- ---
## Support ## Support
Bei Registry-Zugang, Integrationsfragen oder PHX-spezifischen APIs wenden Sie sich an Ihren PHX-Partner. Bei Integrationsfragen oder PHX-spezifischen APIs wenden Sie sich an Ihren PHX-Partner.

211
README.md
View File

@@ -1,16 +1,18 @@
# PHX Frontend Plugin Demo # PHX Frontend Plugin Demo
> [Deutsche Version](README.DE.md) Example project showing how to build a **PHX ERP frontend plugin** as an Angular web component, using `@phx-erp/shared` and `@phx-erp/shared-ui`.
Example project showing how to build a **PHX ERP frontend plugin** as an Angular web component, using `@phx/shared` and `@phx/shared-ui`. **Starting a new plugin?** Use [@phx-erp/create-phx-frontend-plugin](https://www.npmjs.com/package/@phx-erp/create-phx-frontend-plugin) to scaffold a project from the official template. This repository is a full demo you can explore, clone, or use as a reference.
The same codebase supports two workflows: The same codebase supports two workflows:
| Mode | Purpose | Typical command | | Mode | Purpose | Typical command |
|------|---------|-----------------| | --------------------- | ----------------------------------------- | ----------------- |
| **Plugin host** | Run inside PHX as a custom element | `yarn run plugin` | | **Plugin host** | Run inside PHX as a custom element | `yarn run plugin` |
| **Standalone client** | Develop locally like a normal Angular app | `yarn client` | | **Standalone client** | Develop locally like a normal Angular app | `yarn client` |
--- ---
## Table of contents ## Table of contents
@@ -22,6 +24,7 @@ The same codebase supports two workflows:
- [Development modes](#development-modes) - [Development modes](#development-modes)
- [Project structure](#project-structure) - [Project structure](#project-structure)
- [Create your own plugin](#create-your-own-plugin) - [Create your own plugin](#create-your-own-plugin)
- [Scaffold CLI (recommended)](#scaffold-cli-recommended)
- [1. Basic setup](#1-basic-setup) - [1. Basic setup](#1-basic-setup)
- [2. Tailwind CSS](#2-tailwind-css) - [2. Tailwind CSS](#2-tailwind-css)
- [3. GraphQL Codegen](#3-graphql-codegen) - [3. GraphQL Codegen](#3-graphql-codegen)
@@ -57,7 +60,7 @@ A PHX frontend plugin is an Angular application packaged as a **custom element**
- Custom element tag: `frontend-plugin-demo` - Custom element tag: `frontend-plugin-demo`
- Sample routes: hello world, product view, address list - Sample routes: hello world, product view, address list
- GraphQL queries via PHX host Apollo (production) or a local Apollo client (development) - GraphQL queries via PHX host Apollo (production) or a local Apollo client (development)
- Login flow for standalone development without a pre-configured API key - PHX login redirect and callback route for standalone development without a pre-configured API key
- PrimeNG + Tailwind styling aligned with PHX - PrimeNG + Tailwind styling aligned with PHX
--- ---
@@ -67,40 +70,32 @@ A PHX frontend plugin is an Angular application packaged as a **custom element**
- [Node.js](https://nodejs.org/) (LTS recommended) - [Node.js](https://nodejs.org/) (LTS recommended)
- [Yarn v4](https://yarnpkg.com/getting-started/install) - [Yarn v4](https://yarnpkg.com/getting-started/install)
- A running PHX instance (for plugin-host testing and GraphQL schema/codegen) - A running PHX instance (for plugin-host testing and GraphQL schema/codegen)
- An **npm access token** with read access to PHXGMBH packages — request one at your PHX partner
For local standalone development you also need either: For local standalone development you also need either:
- A PHX API user token in your development environment, or - A PHX API user token in your development environment, or
- Valid credentials for the built-in login screen (see [Apollo, auth guard, and login](#apollo-auth-guard-and-login)) - Access to the PHX login page (see [Apollo, auth guard, and login](#apollo-auth-guard-and-login))
--- ---
## Quick start ## Quick start
1. **Configure Yarn** to access the PHX npm registry (see [PHX libraries](#5-phx-libraries)). Do not commit `.yarnrc.yml`. These steps apply to **this demo repository**. To create your own plugin from scratch, use the scaffold CLI — see [Create your own plugin](#create-your-own-plugin).
2. **Install dependencies:**
1. **Install dependencies:**
```bash ```bash
yarn install yarn install
``` ```
2. **Create a local development environment** (optional, for standalone mode):
3. **Create a local development environment** (optional, for standalone mode):
```bash ```bash
cp src/environments/environment.example.ts src/environments/environment.development.ts cp src/environments/environment.example.ts src/environments/environment.development.ts
``` ```
Adjust `apiUrl`, `wsUrl`, and optionally `apiKey` in that file. The file is gitignored. Adjust `apiUrl`, `wsUrl`, and optionally `apiKey` in that file. The file is gitignored.
3. **Generate GraphQL types** (requires PHX admin API at the schema URL configured in `codegen.ts`):
4. **Generate GraphQL types** (requires PHX admin API at the schema URL configured in `codegen.ts`):
```bash ```bash
yarn codegen yarn codegen
``` ```
4. **Run in one of the development modes** below.
5. **Run in one of the development modes** below.
--- ---
@@ -109,10 +104,10 @@ For local standalone development you also need either:
To use this demo in your own instance, add a custom element in **Admin → Custom Elements** (`https://your.phx.instance/admin/customElements`) with this manifest URL: To use this demo in your own instance, add a custom element in **Admin → Custom Elements** (`https://your.phx.instance/admin/customElements`) with this manifest URL:
``` ```
https://gitea.phx-erp.de/api/v1/repos/PHXGMBH/phx-frontend-plugin-webcomponent-demo/raw/master/latest/manifest.json https://unpkg.com/@phx-erp/phx-frontend-plugin-demo/manifest.json
``` ```
The manifest resolves to the compiled `main.js` in the `latest/` directory of this repository. The manifest's `path` field points at the package root; unpkg serves `main.js` via the `"."` export. For local development with `yarn run plugin`, register `http://localhost:3223/local.manifest.json` in Admin → Custom Elements instead.
--- ---
@@ -120,14 +115,14 @@ The manifest resolves to the compiled `main.js` in the `latest/` directory of th
### Standalone client (usual Angular workflow) ### Standalone client (usual Angular workflow)
Runs the app with the **development** build configuration on port **4201** — routing, login, and a local Apollo client work without PHX. Runs the app with the **development** build configuration on port **4201** — routing, PHX login redirect, and a local Apollo client work without embedding the plugin in PHX.
```bash ```bash
yarn client yarn client
# equivalent: ng serve --port 4201 --watch --configuration development # equivalent: ng serve --port 4201 --watch --configuration development
``` ```
Open **http://localhost:4201/**. Open **[http://localhost:4201/](http://localhost:4201/)**.
### Plugin host mode (inside PHX) ### Plugin host mode (inside PHX)
@@ -139,18 +134,20 @@ yarn run plugin
This runs `yarn build` and `yarn serve` concurrently: This runs `yarn build` and `yarn serve` concurrently:
| Script | What it does | | Script | What it does |
|--------|----------------| | ------------ | ---------------------------------------------------------------------------------- |
| `yarn build` | Watches production build (in this repo, also syncs output to `latest/` for the hosted demo) | | `yarn build` | Watches production build |
| `yarn serve` | Serves `dist/.../browser/` at **http://localhost:3223/** | | `yarn serve` | Serves `dist/.../browser/` at **[http://localhost:3223/](http://localhost:3223/)** |
Point your PHX instance at the local manifest: Point your PHX instance at the local manifest:
``` ```
http://localhost:3223/manifest.json http://localhost:3223/local.manifest.json
``` ```
(`public/manifest.json` is copied into the served output.) (`public/local.manifest.json` is copied into the served output.)
> **Note:** Use `yarn run plugin`, not `yarn plugin` — Yarn treats `plugin` as a built-in command. > **Note:** Use `yarn run plugin`, not `yarn plugin` — Yarn treats `plugin` as a built-in command.
@@ -169,16 +166,15 @@ yarn codegen # Regenerate GraphQL types
``` ```
phx-frontend-plugin-demo/ phx-frontend-plugin-demo/
├── latest/ # Published build output (main.js, manifest.json)
├── public/ ├── public/
│ └── manifest.json # Local manifest (path → localhost:3223) │ └── local.manifest.json # Local dev manifest (copied into dist)
├── manifest.json # Published manifest (npm/unpkg)
├── scripts/ ├── scripts/
│ ├── copy-latest.mjs # (this repo only) Sync dist/*.js → latest/ for hosted demo
│ └── serve-dist.mjs # Static server for plugin-host dev │ └── serve-dist.mjs # Static server for plugin-host dev
├── src/ ├── src/
│ ├── app/ │ ├── app/
│ │ ├── components/ # Demo pages (hello-world, product-view, …) │ │ ├── components/ # Demo pages (hello-world, product-view, …)
│ │ ├── login/ # Login form (standalone development) │ │ ├── login.ts # Auth callback route — redirect flow (standalone development)
│ │ ├── services/ │ │ ├── services/
│ │ │ ├── apollo.service.ts │ │ │ ├── apollo.service.ts
│ │ │ └── phoenix-host-bridge.service.ts │ │ │ └── phoenix-host-bridge.service.ts
@@ -201,7 +197,23 @@ phx-frontend-plugin-demo/
## Create your own plugin ## Create your own plugin
The steps below walk through creating a project similar to this demo, using [Yarn v4](https://yarnpkg.com/getting-started/install), [Angular 20](https://angular.dev/), [PrimeNG 20](https://primeng.org/), and [Tailwind CSS](https://tailwindcss.com/). ### Scaffold CLI (recommended)
The fastest way to start is the official scaffold CLI, published as [@phx-erp/create-phx-frontend-plugin](https://www.npmjs.com/package/@phx-erp/create-phx-frontend-plugin):
```bash
# Yarn Berry — one-shot, no global install
yarn dlx @phx-erp/create-phx-frontend-plugin my-orders-plugin
# npm
npx @phx-erp/create-phx-frontend-plugin@latest my-orders-plugin
```
This copies the official plugin template, replaces placeholders (project name, custom element tag, API URLs), creates `environment.development.ts`, and can run `yarn install`. See the [package page](https://www.npmjs.com/package/@phx-erp/create-phx-frontend-plugin) for non-interactive flags (`--yes`, `--install`, `--api-url`, `--ui-url`, `--api-key`, etc.).
### Manual setup (step by step)
If you prefer to understand each piece or customize from scratch, the steps below walk through creating a project similar to this demo, using [Yarn v4](https://yarnpkg.com/getting-started/install), [Angular 20](https://angular.dev/), [PrimeNG 20](https://primeng.org/), and [Tailwind CSS](https://tailwindcss.com/).
Replace placeholders such as `*PROJECT-NAME*`, `*YOUR-TAG*`, and `*YOUR-TOKEN*` with your values. Replace placeholders such as `*PROJECT-NAME*`, `*YOUR-TAG*`, and `*YOUR-TOKEN*` with your values.
@@ -310,7 +322,7 @@ This generates `src/app/schema-types.ts` and `src/app/generated.ts`.
Use separate environments so the same build runs as a PHX plugin (production) or as a standalone dev app (development). Use separate environments so the same build runs as a PHX plugin (production) or as a standalone dev app (development).
**`src/environments/environment.interface.ts`** `**src/environments/environment.interface.ts**`
```ts ```ts
export abstract class Environment { export abstract class Environment {
@@ -322,7 +334,7 @@ export abstract class Environment {
} }
``` ```
**`src/environments/environment.ts`** (production — used when embedded in PHX) `**src/environments/environment.ts**` (production — used when embedded in PHX)
```ts ```ts
import { Environment } from './environment.interface'; import { Environment } from './environment.interface';
@@ -336,7 +348,7 @@ export const environment: Environment = {
}; };
``` ```
**`src/environments/environment.development.ts`** (local development — add to `.gitignore`) `**src/environments/environment.development.ts**` (local development — add to `.gitignore`)
```ts ```ts
import { Environment } from './environment.interface'; import { Environment } from './environment.interface';
@@ -345,8 +357,8 @@ export const environment: Environment = {
production: false, production: false,
apiUrl: 'http://localhost:3000/admin-api', apiUrl: 'http://localhost:3000/admin-api',
wsUrl: 'ws://localhost:3000/admin-api', wsUrl: 'ws://localhost:3000/admin-api',
apiKey: undefined, // or a PHX API user token; otherwise use the login route apiKey: undefined, // or a PHX API user token; otherwise redirects to PHX login
serverUrl: 'https://localhost:4200', serverUrl: 'https://localhost:4200', // PHX instance used for standalone login redirect
}; };
``` ```
@@ -367,24 +379,12 @@ Add `fileReplacements` to the **development** build configuration in `angular.js
### 5. PHX libraries ### 5. PHX libraries
`@phx/shared` and `@phx/shared-ui` are published on the PHXGMBH npm registry. `@phx-erp/shared` and `@phx-erp/shared-ui` are published on the public npm registry.
Create `.yarnrc.yml` in the project root (or edit `~/.yarnrc.yml` for a global setup): Install the libraries:
```yml
nodeLinker: node-modules
npmScopes:
phx:
npmRegistryServer: "https://gitea.phx-erp.de/api/packages/PHXGMBH/npm/"
npmAuthToken: "*YOUR-TOKEN*"
```
> **Important:** Add `.yarnrc.yml` to `.gitignore` if it contains your token.
Then install the libraries:
```bash ```bash
yarn add @phx/shared @phx/shared-ui yarn add @phx-erp/shared @phx-erp/shared-ui
``` ```
### 6. Plugin setup ### 6. Plugin setup
@@ -396,7 +396,7 @@ Add `providePhoenixPluginWithPrimeNG` in `src/app/app.config.ts`:
```ts ```ts
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { providePhoenixPluginWithPrimeNG } from '@phx/shared-ui'; import { providePhoenixPluginWithPrimeNG } from '@phx-erp/shared-ui';
import { routes } from './app.routes'; import { routes } from './app.routes';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
@@ -424,7 +424,7 @@ Create `src/app/services/phoenix-host-bridge.service.ts`:
```ts ```ts
import { Injectable, Injector, signal } from '@angular/core'; import { Injectable, Injector, signal } from '@angular/core';
import type { IPluginServices } from '@phx/shared-ui'; import type { IPluginServices } from '@phx-erp/shared-ui';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class PhoenixHostBridgeService { export class PhoenixHostBridgeService {
@@ -459,7 +459,7 @@ Your root component receives `pluginServices` and `hostInjector` from PHX and fo
import { Component, effect, inject, Injector, input } from '@angular/core'; import { Component, effect, inject, Injector, input } from '@angular/core';
import { RouterOutlet } from '@angular/router'; import { RouterOutlet } from '@angular/router';
import { PhoenixHostBridgeService } from './services/phoenix-host-bridge.service'; import { PhoenixHostBridgeService } from './services/phoenix-host-bridge.service';
import { IPluginServices, syncPhoenixHostInjector } from '@phx/shared-ui'; import { IPluginServices, syncPhoenixHostInjector } from '@phx-erp/shared-ui';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@@ -492,7 +492,7 @@ In `src/main.ts`, register your plugin as a custom element (replace `*YOUR-TAG*`
```ts ```ts
import { appConfig } from './app/app.config'; import { appConfig } from './app/app.config';
import { App } from './app/app'; import { App } from './app/app';
import { bootstrapPhoenixPluginCustomElement } from '@phx/shared-ui'; import { bootstrapPhoenixPluginCustomElement } from '@phx-erp/shared-ui';
import { environment } from './environments/environment'; import { environment } from './environments/environment';
bootstrapPhoenixPluginCustomElement(App, '*YOUR-TAG*', appConfig).then((app) => { bootstrapPhoenixPluginCustomElement(App, '*YOUR-TAG*', appConfig).then((app) => {
@@ -503,8 +503,8 @@ bootstrapPhoenixPluginCustomElement(App, '*YOUR-TAG*', appConfig).then((app) =>
}); });
``` ```
- **`bootstrapPhoenixPluginCustomElement`** — creates the Angular application and registers the custom element for PHX. - `bootstrapPhoenixPluginCustomElement` — creates the Angular application and registers the custom element for PHX.
- **`app.bootstrap(App)` in development** — additionally mounts the root component so `ng serve` / standalone mode works with routing. - `app.bootstrap(App)` in development — additionally mounts the root component so `ng serve` / standalone mode works with routing.
The custom element tag must be **lowercase with hyphens** (e.g. `my-company-orders`). Use the same tag in your manifest. The custom element tag must be **lowercase with hyphens** (e.g. `my-company-orders`). Use the same tag in your manifest.
@@ -525,12 +525,14 @@ PHX loads a JSON manifest that points to your entry script and declares the cust
} }
``` ```
| Field | Description | | Field | Description |
|-------|-------------| | ----------------- | --------------------------------------------------------- |
| `path` | Absolute URL to `main.js` (and base for chunk resolution) | | `path` | Absolute URL to `main.js` (and base for chunk resolution) |
| `items[].tagName` | Custom element tag registered in `main.ts` | | `items[].tagName` | Custom element tag registered in `main.ts` |
**Local development example** (`public/manifest.json` in this repo):
**Local development example** (`public/local.manifest.json` in this repo):
```json ```json
{ {
@@ -539,11 +541,11 @@ PHX loads a JSON manifest that points to your entry script and declares the cust
} }
``` ```
**Hosted example** (`latest/manifest.json` in this repo): **Published example** (root `manifest.json`, served from npm via unpkg):
```json ```json
{ {
"path": "https://gitea.phx-erp.de/api/v1/repos/PHXGMBH/phx-frontend-plugin-webcomponent-demo/raw/master/latest/main.js", "path": "https://unpkg.com/@phx-erp/phx-frontend-plugin-demo",
"items": [{ "tagName": "frontend-plugin-demo" }] "items": [{ "tagName": "frontend-plugin-demo" }]
} }
``` ```
@@ -553,7 +555,7 @@ PHX loads a JSON manifest that points to your entry script and declares the cust
1. Host `main.js` (and any sibling chunks) at a URL reachable from your users' browsers — same network rules as your PHX instance. 1. Host `main.js` (and any sibling chunks) at a URL reachable from your users' browsers — same network rules as your PHX instance.
2. Publish a manifest JSON at a stable URL. 2. Publish a manifest JSON at a stable URL.
3. In PHX: **Admin → Custom Elements** → add the manifest URL and choose a mount path or tag. 3. In PHX: **Admin → Custom Elements** → add the manifest URL and choose a mount path or tag.
4. Log out and back in. The plugin is available at `https://your.phx.instance/customElements/*PATH*`. 4. Log out and back in. The plugin is available at `https://your.phx.instance/customElements/*PATH`*.
For local plugin-host development, see [Serving the plugin locally](#serving-the-plugin-locally). For local plugin-host development, see [Serving the plugin locally](#serving-the-plugin-locally).
@@ -569,7 +571,7 @@ Install the static server:
yarn add -D serve yarn add -D serve
``` ```
**`scripts/serve-dist.mjs`** `**scripts/serve-dist.mjs**`
```js ```js
import { spawn } from 'node:child_process'; import { spawn } from 'node:child_process';
@@ -590,7 +592,7 @@ const child = spawn(serveBin, ['-l', port, '--cors', '--no-etag'], {
child.on('exit', (code) => process.exit(code ?? 0)); child.on('exit', (code) => process.exit(code ?? 0));
``` ```
**`serve.json`** (replace `*PROJECT-NAME*` with your Angular project name from `angular.json`) `**serve.json**` (replace `*PROJECT-NAME*` with your Angular project name from `angular.json`)
```json ```json
{ {
@@ -616,13 +618,11 @@ Add to `package.json`:
Use a custom port: `PORT=8080 yarn serve`. Use a custom port: `PORT=8080 yarn serve`.
> **This repository only:** `scripts/copy-latest.mjs` copies built JS files into `latest/` so the [live demo](#live-demo) manifest can point at a stable path in git. You do not need this for local plugin-host development.
### Apollo, auth guard, and login ### Apollo, auth guard, and login
In **production** (embedded in PHX), authentication is handled by the host. Use the Apollo client from `IPluginServices` via `PhoenixHostBridgeService` — you do not need a separate login flow. In **production** (embedded in PHX), authentication is handled by the host. Use the Apollo client from `IPluginServices` via `PhoenixHostBridgeService` — you do not need a separate login flow.
In **development** (standalone), provide your own Apollo client and optional login. In **development** (standalone), provide your own Apollo client and redirect unauthenticated users to the PHX login page. PHX redirects back to a local `/login` callback route that stores the token and returns the user to the original page.
#### Apollo provider #### Apollo provider
@@ -663,54 +663,53 @@ export class ApolloService {
Apply the same pattern for other host services (e.g. notifications) when you need standalone fallbacks. Apply the same pattern for other host services (e.g. notifications) when you need standalone fallbacks.
#### Login component #### Login callback route
For development without a preset API key, add a `/login` route that calls the PHX `login` mutation: For development without a preset API key, you need a way to obtain and store an API token. This demo uses a **redirect-based auth callback** on `/login` rather than an embedded login form, but either approach is valid — you can also build a login form in your plugin, call the GraphQL `login` mutation, and handle the token yourself.
```gql With the redirect flow, when the user is unauthenticated, the auth guard sends them to the PHX login page (`environment.serverUrl`). After successful login, PHX redirects back to your plugin's `/login` route with an `authToken` query parameter and a base64-encoded `redirectTo` target. The callback component stores the token in `localStorage` and navigates back to the original page:
mutation Login($username: String!, $password: String!) {
login(username: $username, password: $password) { ```ts
... on CurrentUser { @Component({
id selector: 'app-login',
identifier template: `
channels { <div class="flex flex-col items-center justify-center h-screen w-screen opacity-20">
id <div class="animate-fadein duration-1000 animate-infinite animate-alternate text-4xl">Signing in...</div>
token </div>
} `,
} })
... on InvalidCredentialsError { export class Login {
errorCode constructor(private readonly route: ActivatedRoute) {
message this.route.queryParams.subscribe((params) => {
} const token = params['authToken'];
... on NativeAuthStrategyError { if (token) {
errorCode const redirectTo = decodeURIComponent(atob(params['redirectTo']));
message localStorage.setItem('api-key', token);
} window.history.replaceState({}, '', redirectTo);
... on EmailCodeAuthStrategyError { window.location.href = redirectTo;
errorCode
message
} }
});
} }
} }
``` ```
See `src/app/login/` in this repo for a complete form implementation. See `src/app/login.ts` in this repo for the full implementation.
#### Auth guard #### Auth guard
Redirect unauthenticated users to login in development only: Redirect unauthenticated users to the PHX login page in development only. The `redirectTo` query parameter chains back through your plugin's `/login` callback:
```ts ```ts
@Injectable() @Injectable()
export class AuthGuard implements CanActivate { export class AuthGuard implements CanActivate {
private readonly router = inject(Router);
canActivate(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): boolean { canActivate(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): boolean {
const token = environment.apiKey ?? localStorage.getItem('api-key'); const token = environment.apiKey ?? localStorage.getItem('api-key');
if (!environment.production && !token) { if (!environment.production && !token) {
this.router.navigate(['login'], { const redirectTo = encodeURIComponent(
queryParams: { redirectTo: btoa(window.location.pathname + window.location.search) }, btoa(`${window.location.protocol}//${window.location.host}/login?redirectTo=${encodeURIComponent(btoa(window.location.href))}`)
}); );
window.location.href = `${environment.serverUrl}/login?redirectTo=${redirectTo}`;
return false;
} }
return true; return true;
} }
@@ -746,32 +745,34 @@ Recommended scripts after following this guide:
} }
``` ```
| Script | Description | | Script | Description |
|--------|-------------| | --------- | ------------------------------------------------------- |
| `build` | Production watch build for PHX | | `build` | Production watch build for PHX |
| `serve` | Serves compiled assets for PHX (default port 3223) | | `serve` | Serves compiled assets for PHX (default port 3223) |
| `plugin` | Runs `build` + `serve` together — use `yarn run plugin` | | `plugin` | Runs `build` + `serve` together — use `yarn run plugin` |
| `client` | Standalone Angular dev server on port 4201 | | `client` | Standalone Angular dev server on port 4201 |
| `codegen` | Regenerates GraphQL TypeScript types | | `codegen` | Regenerates GraphQL TypeScript types |
This repository additionally runs `copy-latest.mjs` alongside `build` to publish artifacts into `latest/` for the hosted demo — that is not required for your own plugin.
--- ---
## Troubleshooting ## Troubleshooting
| Symptom | Things to check | | Symptom | Things to check |
|---------|-----------------| | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| PHX shows an old version of the plugin | Hard-refresh; confirm `serve.json` sets `Cache-Control: no-store` for JS; restart `yarn run plugin` | | PHX shows an old version of the plugin | Hard-refresh; confirm `serve.json` sets `Cache-Control: no-store` for JS; restart `yarn run plugin` |
| `401` / GraphQL auth errors in standalone mode | Set `apiKey` in `environment.development.ts` or log in via `/login` | | `401` / GraphQL auth errors in standalone mode | Set `apiKey` in `environment.development.ts`, or ensure `serverUrl` points to your PHX instance so the login redirect works |
| `yarn add @phx/shared` fails | Verify `.yarnrc.yml` token; contact your PHX partner | | `yarn add @phx-erp/shared` fails | Check network access to the public npm registry; retry `yarn install` |
| Routing broken inside PHX | Add route segments to `stripTrailingSegments` in `providePhoenixPluginWithPrimeNG` | | Routing broken inside PHX | Add route segments to `stripTrailingSegments` in `providePhoenixPluginWithPrimeNG` |
| Tailwind classes missing in a component | Add `@tailwind` directives to that component's `styles` | | Tailwind classes missing in a component | Add `@tailwind` directives to that component's `styles` |
| `yarn codegen` fails | Ensure PHX is running and the schema URL in `codegen.ts` is reachable | | `yarn codegen` fails | Ensure PHX is running and the schema URL in `codegen.ts` is reachable |
| Custom element not found | Tag in manifest must exactly match `customElements.define` / `bootstrapPhoenixPluginCustomElement` | | Custom element not found | Tag in manifest must exactly match `customElements.define` / `bootstrapPhoenixPluginCustomElement` |
--- ---
## Support ## Support
For registry access, integration questions, or PHX-specific APIs, contact your PHX partner. For integration questions or PHX-specific APIs, contact your PHX partner.

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +0,0 @@
{
"path": "https://gitea.phx-erp.de/api/v1/repos/PHXGMBH/phx-frontend-plugin-webcomponent-demo/raw/master/latest/main.js",
"items": [
{
"tagName": "frontend-plugin-demo"
}
]
}

File diff suppressed because one or more lines are too long

8
manifest.json Normal file
View File

@@ -0,0 +1,8 @@
{
"path": "https://unpkg.com/@phx-erp/phx-frontend-plugin-demo",
"items": [
{
"tagName": "frontend-plugin-demo"
}
]
}

View File

@@ -1,13 +1,22 @@
{ {
"name": "phx-frontend-plugin-demo", "name": "@phx-erp/phx-frontend-plugin-demo",
"version": "0.0.0", "version": "0.1.0",
"files": [
"dist/phx-frontend-plugin-demo/browser",
"manifest.json"
],
"exports": {
".": "./dist/phx-frontend-plugin-demo/browser/main.js",
"./manifest.json": "./manifest.json"
},
"private": false,
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve --configuration development", "start": "ng serve --configuration development",
"build": "concurrently \"ng build --watch --output-hashing none --configuration production\" \"node scripts/copy-latest.mjs --watch\"", "build": "ng build --watch --output-hashing none --configuration production",
"build:once": "ng build --output-hashing none --configuration production",
"test": "ng test", "test": "ng test",
"build:dev": "ng build --output-hashing none --watch --configuration development", "build:dev": "ng build --output-hashing none --watch --configuration development",
"copy-latest": "node scripts/copy-latest.mjs",
"serve": "node ./scripts/serve-dist.mjs", "serve": "node ./scripts/serve-dist.mjs",
"codegen": "graphql-codegen", "codegen": "graphql-codegen",
"client": "ng serve --port 4201 --watch --configuration development", "client": "ng serve --port 4201 --watch --configuration development",
@@ -25,7 +34,6 @@
} }
] ]
}, },
"private": true,
"dependencies": { "dependencies": {
"@angular/animations": "20", "@angular/animations": "20",
"@angular/common": "^20.3.0", "@angular/common": "^20.3.0",
@@ -36,8 +44,8 @@
"@angular/platform-browser": "^20.3.0", "@angular/platform-browser": "^20.3.0",
"@angular/router": "^20.3.0", "@angular/router": "^20.3.0",
"@apollo/client": "^4.0.1", "@apollo/client": "^4.0.1",
"@phx/shared": "^0.1.4", "@phx-erp/shared": "^0.1.4",
"@phx/shared-ui": "^0.1.4", "@phx-erp/shared-ui": "^0.1.4",
"@primeng/themes": "20", "@primeng/themes": "20",
"apollo-angular": "14.0.0", "apollo-angular": "14.0.0",
"graphql": "^16", "graphql": "^16",

View File

@@ -1,62 +0,0 @@
import { watch } from 'node:fs';
import { access, constants, copyFile, mkdir, readdir, unlink } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = join(__dirname, '..');
const sourceDir = join(root, 'dist/phx-frontend-plugin-demo/browser');
const destDir = join(root, 'latest');
async function copyJsFiles() {
try {
await access(sourceDir, constants.F_OK);
await mkdir(destDir, { recursive: true });
const files = (await readdir(sourceDir)).filter((f) => f.endsWith('.js'));
for (const file of files) {
await copyFile(join(sourceDir, file), join(destDir, file));
}
const destFiles = (await readdir(destDir)).filter((f) => f.endsWith('.js'));
for (const file of destFiles) {
if (!files.includes(file)) {
await unlink(join(destDir, file));
}
}
console.log(`[latest] Copied ${files.length} JS file(s)`);
} catch (err) {
if (err.code !== 'ENOENT') {
console.error('[latest] Copy failed:', err.message);
}
}
}
function watchJsFiles() {
let timeout;
const debouncedCopy = () => {
clearTimeout(timeout);
timeout = setTimeout(copyJsFiles, 100);
};
const tryWatch = () => {
access(sourceDir, constants.F_OK)
.then(() => {
copyJsFiles();
watch(sourceDir, debouncedCopy);
console.log('[latest] Watching for JS changes in dist');
})
.catch(() => setTimeout(tryWatch, 500));
};
tryWatch();
}
if (process.argv.includes('--watch')) {
watchJsFiles();
} else {
copyJsFiles();
}

View File

@@ -2,7 +2,7 @@ import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChang
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { routes } from './app.routes'; import { routes } from './app.routes';
import { provideHttpClient } from '@angular/common/http'; import { provideHttpClient } from '@angular/common/http';
import { providePhoenixPluginWithPrimeNG } from '@phx/shared-ui'; import { providePhoenixPluginWithPrimeNG } from '@phx-erp/shared-ui';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import { apolloProvider } from './apollo.provider'; import { apolloProvider } from './apollo.provider';
import { AuthGuard } from './auth-guard'; import { AuthGuard } from './auth-guard';

View File

@@ -1,7 +1,7 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import { HelloWorld } from './components/hello-world/hello-world'; import { HelloWorld } from './components/hello-world/hello-world';
import { ProductView } from './components/product-view/product-view'; import { ProductView } from './components/product-view/product-view';
import { Login } from './login/login'; import { Login } from './login';
import { AuthGuard } from './auth-guard'; import { AuthGuard } from './auth-guard';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import { AddressList } from './components/address-list/address-list'; import { AddressList } from './components/address-list/address-list';

View File

@@ -1,7 +1,7 @@
import { Component, effect, inject, Injector, input } from '@angular/core'; import { Component, effect, inject, Injector, input } from '@angular/core';
import { RouterOutlet } from '@angular/router'; import { RouterOutlet } from '@angular/router';
import { PhoenixHostBridgeService } from './services/phoenix-host-bridge.service'; import { PhoenixHostBridgeService } from './services/phoenix-host-bridge.service';
import { IPluginServices, syncPhoenixHostInjector } from '@phx/shared-ui'; import { IPluginServices, syncPhoenixHostInjector } from '@phx-erp/shared-ui';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',

View File

@@ -1,15 +1,16 @@
import { inject, Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
@Injectable() @Injectable()
export class AuthGuard implements CanActivate { export class AuthGuard implements CanActivate {
private readonly router = inject(Router);
canActivate(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): boolean { canActivate(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): boolean {
const token = environment.apiKey??localStorage.getItem('api-key'); const token = environment.apiKey??localStorage.getItem('api-key');
if (!environment.production && !token) if (!environment.production && !token){
this.router.navigate(['login'], { queryParams: { redirectTo: btoa(window.location.pathname + window.location.search) } }); const redirectTo = encodeURIComponent(btoa(`${window.location.protocol}//${window.location.host}/login?redirectTo=${encodeURIComponent(btoa(window.location.href))}`));
window.location.href = `${environment.serverUrl}/login?redirectTo=${redirectTo}`;
return false;
}
return true; return true;
} }
} }

33
src/app/login.ts Normal file
View File

@@ -0,0 +1,33 @@
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-login',
imports: [],
template: `
<div class="flex flex-col items-center justify-center h-screen w-screen opacity-20">
<div class="animate-fadein duration-1000 animate-infinite animate-alternate text-4xl">Signing in...</div>
</div>
`,
styles: [`
@tailwind base;
@tailwind components;
@tailwind utilities;
`],
})
export class Login {
constructor(
private readonly route: ActivatedRoute
) {
this.route.queryParams.subscribe((params) => {
const token = params['authToken'];
if(token){
const redirectTo = decodeURIComponent(atob(params['redirectTo']));
localStorage.setItem('api-key', token);
//remove the authToken from the url but keep the redirectTo
window.history.replaceState({}, '', redirectTo);
window.location.href = redirectTo;
}
});
}
}

View File

@@ -1,17 +0,0 @@
<div class="min-h-screen flex items-center justify-center p-8">
<div class="border-2 rounded-2xl bg-gray-100/50 drop-shadow-2xl w-full mx-auto p-4" style="max-width: 300px;">
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<div class="flex flex-col gap-2">
<div class="flex flex-col">
<label for="username">Username</label>
<input type="text" pInputText id="username" formControlName="username" />
</div>
<div class="flex flex-col">
<label for="password">Password</label>
<input type="password" pInputText id="password" formControlName="password" />
</div>
</div>
<p-button type="submit" [loading]="loading()" label="Login" icon="fa fa-sign-in" class="w-full" styleClass="w-full mt-4" />
</form>
</div>
</div>

View File

@@ -1,77 +0,0 @@
import { Component, inject, signal } from '@angular/core';
import { FormBuilder, FormsModule, Validators } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { InputText } from 'primeng/inputtext';
import { Button } from 'primeng/button';
import { ApolloService } from '../services/apollo.service';
import { LOGIN } from '../../graphql/base-definitions';
import { LoginMutation } from '../generated';
import { lastValueFrom } from 'rxjs';
import { AuthenticationResult } from '../schema-types';
import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'app-login',
imports: [
FormsModule,
ReactiveFormsModule,
InputText,
Button,
],
templateUrl: './login.html',
styles: [`
@tailwind base;
@tailwind components;
@tailwind utilities;
input[pInputText] {
padding: 0.5rem !important;
border-width: 2px !important;
}
`],
})
export class Login {
readonly apollo = inject(ApolloService);
readonly fb = inject(FormBuilder);
readonly router = inject(Router);
readonly loginForm = this.fb.group({
username: ['', Validators.required],
password: ['', Validators.required],
});
redirectTo = '/';
readonly loading = signal(false);
constructor(private readonly route: ActivatedRoute) {
this.route.queryParams.subscribe(params => {
if(params['redirectTo']) {
this.redirectTo = atob(params['redirectTo']);
console.log('redirectTo', this.redirectTo);
} else {
this.redirectTo = '/';
}
});
}
async onSubmit() {
this.loading.set(true);
try {
const result = await lastValueFrom(this.apollo.apollo().mutate<LoginMutation>({
mutation: LOGIN,
variables: {
username: this.loginForm.value.username,
password: this.loginForm.value.password,
},
}));
const authResult = result.data?.login as AuthenticationResult;
if(authResult.__typename === 'CurrentUser')
this.router.navigate([this.redirectTo]);
} catch (error) {
console.error(error);
} finally {
this.loading.set(false);
}
}
}

View File

@@ -1,5 +1,5 @@
import { Injectable, Injector, signal } from '@angular/core'; import { Injectable, Injector, signal } from '@angular/core';
import type { IPluginServices } from '@phx/shared-ui'; import type { IPluginServices } from '@phx-erp/shared-ui';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class PhoenixHostBridgeService { export class PhoenixHostBridgeService {

View File

@@ -1,8 +1,6 @@
import { appConfig } from './app/app.config'; import { appConfig } from './app/app.config';
import { App } from './app/app'; import { App } from './app/app';
import { bootstrapPhoenixPluginCustomElement } from '@phx/shared-ui'; import { bootstrapPhoenixPluginCustomElement } from '@phx-erp/shared-ui';
import { createApplication } from '@angular/platform-browser';
import { createCustomElement } from '@angular/elements';
import { environment } from './environments/environment'; import { environment } from './environments/environment';
bootstrapPhoenixPluginCustomElement(App, 'frontend-plugin-demo', appConfig) bootstrapPhoenixPluginCustomElement(App, 'frontend-plugin-demo', appConfig)

View File

@@ -2000,17 +2000,17 @@
"@parcel/watcher-win32-ia32" "2.5.6" "@parcel/watcher-win32-ia32" "2.5.6"
"@parcel/watcher-win32-x64" "2.5.6" "@parcel/watcher-win32-x64" "2.5.6"
"@phx/shared-ui@^0.1.4": "@phx-erp/shared-ui@^0.1.4":
version "0.1.4" version "0.1.4"
resolved "https://gitea.phx-erp.de/api/packages/PHXGMBH/npm/%40phx%2Fshared-ui/-/0.1.4/shared-ui-0.1.4.tgz#e9ed9ede01366aaca92086c2ac50d114a4e2c5b9" resolved "https://registry.yarnpkg.com/@phx-erp/shared-ui/-/shared-ui-0.1.4.tgz#666cb2d820a02ebad0b5df3c1c356166132632f9"
integrity sha512-ZJrb68JosqjN1p3l0K+VEF2SQbOCxe7HK0U1Uj/6ZeMTHtCWuEAo/XxGVLJvjUpGE5pF5gw+jcvIBJsvafNxYg== integrity sha512-WJFpDoy6G7HHbB4/0ZTTWiLXgw3VyKgYhkVK3YN0cZ+xJWATbPGXrdEsuVxrVYxUlzVK0XVuCBRZ/MGDiIDtFA==
dependencies: dependencies:
"@phx/shared" "^0.1.4" "@phx-erp/shared" "^0.1.4"
"@phx/shared@^0.1.4": "@phx-erp/shared@^0.1.4":
version "0.1.4" version "0.1.4"
resolved "https://gitea.phx-erp.de/api/packages/PHXGMBH/npm/%40phx%2Fshared/-/0.1.4/shared-0.1.4.tgz#082ebe561ce1614df6ab664252fa6e525ae96e72" resolved "https://registry.yarnpkg.com/@phx-erp/shared/-/shared-0.1.4.tgz#33f343564f6a111308f185257adfd8ecea63aebf"
integrity sha512-4UKLpDJxBRRgX5sNBtdTAeYYb0mftPcBNyuxbpdI/YIqQcTPmTJRE+QkhmEq/GDRGHYnFjn+DBDr95UwzKZSjQ== integrity sha512-XCNduBKSHC7bJlYqHaVGaRvjPuW0+eoFyeH25hvoDIqDn8/lGP7R8lVM3UAca08aL41BqsTcI6oPyaDzQldxCA==
dependencies: dependencies:
"@apollo/client" "^3.9.6" "@apollo/client" "^3.9.6"
graphql "16.8.2" graphql "16.8.2"