@mostajs/licensing-ui-html 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,14 @@
1
+ # @mostajs/licensing-ui-html
2
+
3
+ **Auteur** : Dr Hamid MADANI <drmdh@msn.com> · **Licence** : AGPL-3.0-or-later
4
+
5
+ Vues HTML **server-rendered** de licence (statut, activation, gate) pour apps **sans build / sans
6
+ React** (node:http…). Rend des **fragments** ; l'app les enveloppe dans son shell. La logique reste
7
+ dans `@mostajs/licensing`. **Config pilotée par `.env`.**
8
+
9
+ ```js
10
+ import { licenseStatusView, activationForm, licenseBlockedView } from '@mostajs/licensing-ui-html';
11
+ res.end(page(session, 'Licence', licenseStatusView({ status: app.license.status() })));
12
+ ```
13
+
14
+ Tests & exemple (§11.1 / §12) : `npm install && npm test`. Variante React : `@mostajs/licensing-ui`.
package/llms.txt ADDED
@@ -0,0 +1,31 @@
1
+ # @mostajs/licensing-ui-html — fiche LLM
2
+ > Vues HTML server-rendered de licence (statut, activation, gate) pour apps SANS build/React (node:http).
3
+
4
+ - Version: 0.1.0 · Licence: AGPL-3.0-or-later · Auteur: Dr Hamid MADANI <drmdh@msn.com>
5
+ - Stack: mosta-licensing-stack · Logique: @mostajs/licensing · Variante React: @mostajs/licensing-ui
6
+
7
+ ## RÔLE
8
+ Rend des FRAGMENTS HTML (pas de page, pas de React, pas de dépendance lourde) que l'app enveloppe
9
+ dans son shell. AUCUNE logique métier : l'app passe le STATUT (produit par @mostajs/licensing) et
10
+ les URLs d'action. Pour Salsabil & toute app no-build.
11
+
12
+ ## EXPORTS
13
+ - licenseBadge(status) -> html
14
+ - licenseStatusView({ status, activateHref? }) -> html
15
+ - activationForm({ action?, requestAction?, serverUrl?, project?, error?, success? }) -> html
16
+ - licenseBlockedView({ status, activateHref? }) -> html (gate quand licence invalide + enforce)
17
+
18
+ ## STATUT attendu (de @mostajs/licensing / app)
19
+ { valid, status, reason?, expiresAt?, daysLeft?, source?, enforce?, project? }
20
+
21
+ ## COMPOSITION (app no-build, ex. Salsabil)
22
+ - Logique : @mostajs/licensing (createLicenseClient pour activate/requestCode ; checkLicense pour vérif).
23
+ - Rendu : ces fragments → enveloppés dans le shell de l'app (app-shell-ui page()).
24
+ - Routes typiques : GET /license (licenseStatusView), GET /license/activate (activationForm),
25
+ POST /license/activate (client.activate → persister la licence signée), gate (licenseBlockedView).
26
+
27
+ ## CONFIG .env (côté app)
28
+ LICENSE_SERVER_URL, LICENSE_PROJECT, LICENSE_PUBLIC_KEY, LICENSE, LICENSE_ENFORCE.
29
+
30
+ ## SÉCURITÉ
31
+ Échappe tout libellé (anti-injection). La clé privée reste chez l'éditeur.
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@mostajs/licensing-ui-html",
3
+ "version": "0.1.0",
4
+ "description": "Vues HTML server-rendered de licence (statut, activation, gate) pour apps SANS build/React (node:http). Compose @mostajs/licensing.",
5
+ "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
+ "license": "AGPL-3.0-or-later",
7
+ "type": "module",
8
+ "main": "src/index.js",
9
+ "exports": {
10
+ ".": "./src/index.js"
11
+ },
12
+ "files": [
13
+ "src",
14
+ "README.md",
15
+ "llms.txt"
16
+ ],
17
+ "keywords": [
18
+ "license",
19
+ "licence",
20
+ "html",
21
+ "ssr",
22
+ "no-build",
23
+ "mostajs"
24
+ ],
25
+ "devDependencies": {
26
+ "@mostajs/mjs-unit": "^0.3.0"
27
+ },
28
+ "scripts": {
29
+ "test": "node test-scripts/unit/licensing-ui-html.test.mjs && node examples/run.mjs"
30
+ }
31
+ }
package/src/index.js ADDED
@@ -0,0 +1,58 @@
1
+ // @mostajs/licensing-ui-html — vues HTML server-rendered de licence (fragments réutilisables).
2
+ // Pour apps SANS build/React (node:http…). Rend des FRAGMENTS ; l'app les enveloppe dans son shell.
3
+ // La logique reste dans @mostajs/licensing (vérif/activation) ; ici, AUCUNE logique métier.
4
+ // @author Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
5
+
6
+ const esc = (s) => String(s ?? '').replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
7
+
8
+ /** Petit badge d'état. */
9
+ export function licenseBadge(status = {}) {
10
+ const ok = status.valid;
11
+ const style = ok ? 'background:#eef7ee;border:1px solid #cce5cc' : 'background:#fdecec;border:1px solid #f5c2c2';
12
+ return `<span style="padding:4px 10px;border-radius:8px;${style}">${ok ? '✅' : '⛔'} ${esc(status.status || (ok ? 'ACTIVE' : 'INVALID'))}</span>`;
13
+ }
14
+
15
+ /** Vue STATUT complète (fragment). */
16
+ export function licenseStatusView({ status = {}, activateHref = '/license/activate' } = {}) {
17
+ return `<h1>📜 Licence — ${esc(status.project || 'app')}</h1>
18
+ <p>${licenseBadge(status)}${status.reason ? ` — ${esc(status.reason)}` : ''}</p>
19
+ <ul>
20
+ <li>Statut : <strong>${esc(status.status || '')}</strong></li>
21
+ ${status.expiresAt ? `<li>Échéance : <strong>${esc(String(status.expiresAt).slice(0, 10))}</strong>${status.daysLeft != null ? ` — <strong>${status.daysLeft}</strong> j restants` : ''}</li>` : ''}
22
+ <li>Source : ${esc(status.source || '')} · Mode : ${status.enforce ? '<strong>bloquant</strong>' : 'informatif'}</li>
23
+ </ul>
24
+ ${!status.valid ? `<p><a href="${esc(activateHref)}">→ Activer la licence</a></p>` : ''}`;
25
+ }
26
+
27
+ /** Formulaire d'ACTIVATION (demande de code + activation par code/fichier). */
28
+ export function activationForm({ action = '/license/activate', requestAction = '/license/request-code', serverUrl = 'https://licences.amia.fr', project = 'app', error = null, success = null } = {}) {
29
+ return `<h1>🔑 Activer la licence — ${esc(project)}</h1>
30
+ ${error ? `<p role="alert" style="padding:8px 12px;border-radius:8px;background:#fdecec;border:1px solid #f5c2c2">${esc(error)}</p>` : ''}
31
+ ${success ? `<p style="padding:8px 12px;border-radius:8px;background:#eef7ee;border:1px solid #cce5cc">${esc(success)}</p>` : ''}
32
+ <h2>Activation automatique</h2>
33
+ <p class="muted">Saisissez votre email et votre organisation : l'application <strong>télécharge et active</strong> la licence depuis le serveur, sans intervention.</p>
34
+ <form method="post" action="${esc(requestAction)}">
35
+ <p><label>Email <input type="email" name="email" required placeholder="vous@asso.dz"></label>
36
+ <label>Organisation <input name="company" required placeholder="Association…"></label>
37
+ <button>⬇️ Télécharger et activer la licence</button></p>
38
+ </form>
39
+ <details style="margin-top:10px">
40
+ <summary>J'ai déjà un code d'activation</summary>
41
+ <form method="post" action="${esc(action)}" style="margin-top:8px">
42
+ <p><label>Code d'activation <input name="activationCode" required placeholder="XXXX-XXXX-XXXX-XXXX" pattern="[A-Za-z0-9-]+"></label>
43
+ <button>✅ Activer avec le code</button></p>
44
+ </form>
45
+ </details>
46
+ <p class="muted">Serveur de licences : <a href="${esc(serverUrl)}" target="_blank" rel="noopener">${esc(serverUrl)}</a>. Projet : <strong>${esc(project)}</strong>. Config pilotée par <code>.env</code>.</p>
47
+ <p><a href="/license">‹ Statut</a></p>`;
48
+ }
49
+
50
+ /** Vue de BLOCAGE (gate) — affichée quand la licence est invalide et l'enforcement actif. */
51
+ export function licenseBlockedView({ status = {}, activateHref = '/license/activate' } = {}) {
52
+ return `<h1>⛔ Accès suspendu — licence ${esc(status.status || 'invalide')}</h1>
53
+ <p>${esc(status.reason || 'La licence de cette installation n’est pas valide.')}</p>
54
+ <p>Veuillez <a href="${esc(activateHref)}">activer une licence</a> pour continuer.</p>
55
+ <p class="muted">Administrateur : générer/renouveler via <code>scripts/issue-license.mjs</code> ou activer en ligne depuis le serveur de licences.</p>`;
56
+ }
57
+
58
+ export default { licenseBadge, licenseStatusView, activationForm, licenseBlockedView };