@ofidj/generator-fidj 3.13.0 → 3.14.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.
@@ -15,8 +15,8 @@
15
15
  "privacy:rehearse": "npm run build && node --test test/server.test.mjs"
16
16
  },
17
17
  "dependencies": {
18
- "@ofidj/entry": "^3.13.0",
19
- "@ofidj/node": "^3.13.0"
18
+ "@ofidj/entry": "^3.14.0",
19
+ "@ofidj/node": "^3.14.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^22.0.0",
@@ -32,6 +32,6 @@ function mountedHead(config) {
32
32
  export function renderContent(config) {
33
33
  // HTML is supplied by the developer at generation time, never by an app visitor.
34
34
  return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="referrer" content="no-referrer"><title>${escape(config.title)}</title><link rel="icon" href="${escape(config.favicon)}"><link rel="stylesheet" href="./main.css">${mountedHead(config)}</head><body>
35
- <header class="topbar"><a class="brand" href="#/content"><img src="${escape(config.logo)}" alt="">${escape(config.title)}</a><nav class="content-nav" id="app-nav" aria-label="App navigation" hidden></nav></header>
35
+ <header class="topbar"><a class="brand" href="#/content"><img src="${escape(config.logo)}" alt=""><span class="brand-name">${escape(config.title)}</span></a><nav class="content-nav" id="app-nav" aria-label="App navigation" hidden></nav></header>
36
36
  <main><template id="public-content"><section class="card public-content"><h1>${escape(config.welcome)}</h1><div>${config.content}</div></section></template><div id="app" aria-live="polite"></div><footer>Built with Fidj · Your choices belong to this app.</footer></main><script type="module" src="./main.js"></script></body></html>`;
37
37
  }
@@ -45,7 +45,7 @@ let accountEmail = "";
45
45
  // with the agreement unchecked folded the form away and left the complaint
46
46
  // floating above a door the person could no longer see.
47
47
  let emailEntryOpen = false;
48
- const accountRoutes = ["forgot", "reset", "verify", "account"];
48
+ const accountRoutes = ["forgot", "reset", "verify", "profile"];
49
49
  let linkToken = "";
50
50
  let verificationConfirmed = false;
51
51
  function currentRoute() {
@@ -88,17 +88,23 @@ function banner() {
88
88
  // account, and it lives on that account's screen.
89
89
  function appNav(current: "content" | "account") {
90
90
  const tab = (id: string, label: string, selected: boolean) =>
91
- `<button id="${id}"${selected ? ' class="selected" aria-current="page"' : ""}>${label}</button>`;
91
+ `<button id="${id}"${selected ? ' class="selected" aria-current="page"' : ""}><span class="tab-label">${label}</span></button>`;
92
92
  const account = signedIn
93
93
  ? tab(
94
94
  "account-tab",
95
- accountEmail ? `Account (${escape(accountEmail)})` : "Account",
95
+ accountEmail ? `Profile (${escape(accountEmail)})` : "Profile",
96
96
  current === "account",
97
97
  )
98
98
  : tab("account-tab", "Sign in", false);
99
99
  return tab("content-tab", "Content", current === "content") + account;
100
100
  }
101
101
 
102
+ function profileSummary() {
103
+ const publicUrl = `${config.dashboardUrl}/#/pub/${encodeURIComponent(config.appId)}`;
104
+ const badgeUrl = `${config.apiEndpoint}/apps/${encodeURIComponent(config.appId)}/badge`;
105
+ return `<header class="profile-summary"><div class="profile-summary-copy"><span class="eyebrow">Profile</span><strong>${escape(accountEmail)}</strong><span>${escape(config.title)}</span></div><a class="profile-public" href="${escape(publicUrl)}" target="_blank" rel="noopener"><img src="${escape(badgeUrl)}" alt="${escape(config.title)} public badge" width="133" height="20"></a><button id="exit" class="danger">Sign out</button></header>`;
106
+ }
107
+
102
108
  // The bar belongs to the document, not to the screen being drawn: it survives
103
109
  // every route change, so it is filled rather than rebuilt with the page.
104
110
  function renderNav(current: "content" | "account") {
@@ -112,7 +118,7 @@ function renderNav(current: "content" | "account") {
112
118
  function wireNav() {
113
119
  element("content-tab")?.addEventListener("click", () => navigate("content"));
114
120
  element("account-tab")?.addEventListener("click", () =>
115
- navigate(signedIn ? "account" : "signin"),
121
+ navigate(signedIn ? "profile" : "signin"),
116
122
  );
117
123
  }
118
124
 
@@ -557,15 +563,15 @@ function render() {
557
563
  route = "signin";
558
564
  else if (!["signin", "content", "privacy", ...accountRoutes].includes(route))
559
565
  route = "content";
560
- // The privacy screen and the account screen became one. The address that
566
+ // The privacy screen and the profile screen became one. The address that
561
567
  // named the first still means something to anyone who bookmarked it, so it
562
568
  // arrives where that screen went rather than nowhere.
563
- if (route === "privacy") route = signedIn ? "account" : "signin";
569
+ if (route === "privacy") route = signedIn ? "profile" : "signin";
564
570
  window.history.replaceState(null, "", "#/" + route);
565
- // My account is a signed-in screen and keeps the app's chrome. Recovery and
571
+ // Profile is a signed-in screen and keeps the app's chrome. Recovery and
566
572
  // verification are reached without a session, so they stand alone.
567
573
  const standaloneAccount =
568
- accountRoutes.includes(route) && !(route === "account" && signedIn);
574
+ accountRoutes.includes(route) && !(route === "profile" && signedIn);
569
575
  document.body.classList.toggle(
570
576
  "signin-view",
571
577
  route === "signin" || standaloneAccount,
@@ -584,11 +590,11 @@ function render() {
584
590
  renderAccount(route);
585
591
  return;
586
592
  }
587
- if (route === "account") {
593
+ if (route === "profile") {
588
594
  // The two ways out, last and together: back to the app, or out of this
589
595
  // session. Leaving the app itself is a different decision and stays where
590
596
  // the things it erases are listed.
591
- root.innerHTML = `<section class="card content-account">${banner()}${accountForm("account", {linkToken, verificationConfirmed, emailVerified, accountEmail})}${privacyBlock()}<div class="account-actions"><button id="continue-app" class="primary">Continue to ${escape(config.title)}</button><button id="exit">Sign out</button></div></section>`;
597
+ root.innerHTML = `<section class="content-account">${profileSummary()}<div class="card profile-body">${banner()}${accountForm("account", {linkToken, verificationConfirmed, emailVerified, accountEmail}, {compact: true})}${privacyBlock()}</div></section>`;
592
598
  renderNav("account");
593
599
  wireAccount("account");
594
600
  wireSignOut();
@@ -213,7 +213,7 @@ function render() {
213
213
  const canWrite = session?.roles.some((role) =>
214
214
  ["Owner", "Editor"].includes(role),
215
215
  );
216
- root.innerHTML = `<header class="topbar"><a class="brand" href="/"><span class="app-symbol">✳</span>${escape(settings.title)}</a><a class="fidj-brand" href="${escape(settings.dashboardUrl)}" target="_blank" rel="noopener"><img src="/fidj-logo.png" alt="Fidj">Privacy with Fidj ↗</a></header>
216
+ root.innerHTML = `<header class="topbar"><a class="brand" href="/"><span class="app-symbol">✳</span><span class="brand-name">${escape(settings.title)}</span></a><a class="fidj-brand" href="${escape(settings.dashboardUrl)}" target="_blank" rel="noopener"><img src="/fidj-logo.png" alt="Fidj">Privacy with Fidj ↗</a></header>
217
217
  <main>${notice ? `<p class="notice" role="status">${escape(notice)}</p>` : ""}${error ? `<p class="error" role="alert">${escape(error)}</p>` : ""}
218
218
  ${
219
219
  !session
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ofidj/generator-fidj",
3
- "version": "3.13.0",
3
+ "version": "3.14.0",
4
4
  "description": "Generate a TypeScript app with Fidj sign-in, live server-side roles and per-app privacy.",
5
5
  "homepage": "https://fidj.ovh",
6
6
  "bugs": {