Merge pull request 'public-packages' (#1) from public-packages into master
Reviewed-on: #1
This commit is contained in:
120
README.DE.md
120
README.DE.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
> [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.
|
||||||
|
|
||||||
Dieselbe Codebasis unterstützt zwei Arbeitsweisen:
|
Dieselbe Codebasis unterstützt zwei Arbeitsweisen:
|
||||||
|
|
||||||
@@ -57,7 +57,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,26 +67,23 @@ 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.
|
1. **Abhängigkeiten installieren:**
|
||||||
|
|
||||||
2. **Abhängigkeiten installieren:**
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn install
|
yarn install
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Lokale Development-Umgebung anlegen** (optional, für den Standalone-Modus):
|
2. **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
|
||||||
@@ -94,13 +91,13 @@ Für die lokale eigenständige Entwicklung benötigen Sie außerdem eines der fo
|
|||||||
|
|
||||||
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.
|
||||||
|
|
||||||
4. **GraphQL-Typen generieren** (erfordert PHX Admin API unter der in `codegen.ts` konfigurierten Schema-URL):
|
3. **GraphQL-Typen generieren** (erfordert PHX Admin API unter der in `codegen.ts` konfigurierten Schema-URL):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn codegen
|
yarn codegen
|
||||||
```
|
```
|
||||||
|
|
||||||
5. **In einem der Entwicklungsmodi** unten starten.
|
4. **In einem der Entwicklungsmodi** unten starten.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -120,7 +117,7 @@ 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
|
||||||
@@ -178,7 +175,7 @@ phx-frontend-plugin-demo/
|
|||||||
├── 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 (Standalone-Entwicklung)
|
||||||
│ │ ├── services/
|
│ │ ├── services/
|
||||||
│ │ │ ├── apollo.service.ts
|
│ │ │ ├── apollo.service.ts
|
||||||
│ │ │ └── phoenix-host-bridge.service.ts
|
│ │ │ └── phoenix-host-bridge.service.ts
|
||||||
@@ -345,8 +342,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 +364,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 +381,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 +409,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 +444,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 +477,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) => {
|
||||||
@@ -622,7 +607,7 @@ Eigener Port: `PORT=8080 yarn serve`.
|
|||||||
|
|
||||||
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 +648,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 fügen Sie eine `/login`-Route hinzu, die die PHX-`login`-Mutation aufruft:
|
Für die Entwicklung ohne voreingestellten API-Key fügen Sie eine `/login`-Route hinzu, die als **Auth-Callback** dient — nicht als Login-Formular. Ist der Nutzer nicht authentifiziert, leitet der Auth Guard 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.
|
||||||
|
|
||||||
```gql
|
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>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
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);
|
||||||
|
window.history.replaceState({}, '', redirectTo);
|
||||||
|
window.location.href = redirectTo;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
... on InvalidCredentialsError {
|
|
||||||
errorCode
|
|
||||||
message
|
|
||||||
}
|
|
||||||
... on NativeAuthStrategyError {
|
|
||||||
errorCode
|
|
||||||
message
|
|
||||||
}
|
|
||||||
... on EmailCodeAuthStrategyError {
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -763,8 +747,8 @@ Dieses Repository führt zusätzlich `copy-latest.mjs` parallel zu `build` aus,
|
|||||||
| 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 |
|
||||||
@@ -774,4 +758,4 @@ Dieses Repository führt zusätzlich `copy-latest.mjs` parallel zu `build` aus,
|
|||||||
|
|
||||||
## 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.
|
||||||
|
|||||||
120
README.md
120
README.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
> [Deutsche Version](README.DE.md)
|
> [Deutsche Version](README.DE.md)
|
||||||
|
|
||||||
Example project showing how to build a **PHX ERP frontend plugin** as an Angular web component, using `@phx/shared` and `@phx/shared-ui`.
|
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`.
|
||||||
|
|
||||||
The same codebase supports two workflows:
|
The same codebase supports two workflows:
|
||||||
|
|
||||||
@@ -57,7 +57,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,26 +67,23 @@ 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`.
|
1. **Install dependencies:**
|
||||||
|
|
||||||
2. **Install dependencies:**
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn install
|
yarn install
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Create a local development environment** (optional, for standalone mode):
|
2. **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
|
||||||
@@ -94,13 +91,13 @@ For local standalone development you also need either:
|
|||||||
|
|
||||||
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.
|
||||||
|
|
||||||
4. **Generate GraphQL types** (requires PHX admin API at the schema URL configured in `codegen.ts`):
|
3. **Generate GraphQL types** (requires PHX admin API at the schema URL configured in `codegen.ts`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn codegen
|
yarn codegen
|
||||||
```
|
```
|
||||||
|
|
||||||
5. **Run in one of the development modes** below.
|
4. **Run in one of the development modes** below.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -120,7 +117,7 @@ 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
|
||||||
@@ -178,7 +175,7 @@ phx-frontend-plugin-demo/
|
|||||||
├── 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 (standalone development)
|
||||||
│ │ ├── services/
|
│ │ ├── services/
|
||||||
│ │ │ ├── apollo.service.ts
|
│ │ │ ├── apollo.service.ts
|
||||||
│ │ │ └── phoenix-host-bridge.service.ts
|
│ │ │ └── phoenix-host-bridge.service.ts
|
||||||
@@ -345,8 +342,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 +364,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 +381,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 +409,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 +444,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 +477,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) => {
|
||||||
@@ -622,7 +607,7 @@ Use a custom port: `PORT=8080 yarn serve`.
|
|||||||
|
|
||||||
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 +648,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, add a `/login` route that acts as an **auth callback** — not a login form. 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.
|
||||||
|
|
||||||
```gql
|
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>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
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);
|
||||||
|
window.history.replaceState({}, '', redirectTo);
|
||||||
|
window.location.href = redirectTo;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
... on InvalidCredentialsError {
|
|
||||||
errorCode
|
|
||||||
message
|
|
||||||
}
|
|
||||||
... on NativeAuthStrategyError {
|
|
||||||
errorCode
|
|
||||||
message
|
|
||||||
}
|
|
||||||
... on EmailCodeAuthStrategyError {
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -763,8 +747,8 @@ This repository additionally runs `copy-latest.mjs` alongside `build` to publish
|
|||||||
| 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 |
|
||||||
@@ -774,4 +758,4 @@ This repository additionally runs `copy-latest.mjs` alongside `build` to publish
|
|||||||
|
|
||||||
## 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.
|
||||||
|
|||||||
214
latest/main.js
214
latest/main.js
File diff suppressed because one or more lines are too long
@@ -36,8 +36,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",
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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
33
src/app/login.ts
Normal 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
14
yarn.lock
14
yarn.lock
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user