Skip to content

Commit a4e4ab2

Browse files
feat: ship /account.html — post-sign-in dashboard (#16)
After sign-in, signin.html now redirects to /account.html instead of /. The new dashboard mirrors the Pivot / sc.net pattern: - WebID display (clickable link to the profile resource) - Pod URL - Explore buttons for /profile/, /public/, /private/, /inbox/, /settings/ — direct nav, no probe required since the user is already authenticated - Sign out button — calls session.logout(), redirects to / - Footer credit pointing at jspod + solid-oidc The page is auth-state-aware via solid-oidc: - Active session → full dashboard - No session → "You're not signed in. [Sign in]" with a button pointing at /signin.html - restore() failure → silently treated as "no session" (same "Missing refresh data" handling as signin.html) Public-read ACL (account.html.acl) so the page itself loads before / without any session. Welcome.html also gets an "Account" button alongside Sign in so signed-in returning visitors have a clear route back. Files: - account.html (~5 KB, single file) - account.html.acl - signin.html — post-success redirect → /account.html - welcome.html — Account button added next to Sign in - index.js — always-overwrite copy for both new files - package.json — files whitelist + 0.0.18 bump Refs #1
1 parent 81d69a7 commit a4e4ab2

6 files changed

Lines changed: 161 additions & 3 deletions

File tree

account.html

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width,initial-scale=1">
6+
<title>Your account — Solid pod</title>
7+
<style>
8+
body{font:14px/1.6 system-ui,-apple-system,sans-serif;color:#222;background:#f3eee5;max-width:680px;margin:2em auto;padding:0 1em}
9+
.card{background:#fff;border-radius:12px;box-shadow:0 2px 12px rgba(0,0,0,.08);padding:1.5em 2em}
10+
h1{margin:0 0 .5em;font-weight:500;font-size:1.8em}
11+
h2{margin:1.5em 0 .25em;font-weight:500;font-size:1.05em;color:#444}
12+
.lede{color:#555;margin:0 0 1em}
13+
.kv{background:#f3eee5;padding:.55em .9em;border-radius:8px;margin:.3em 0;font-family:"SFMono-Regular",Consolas,monospace;font-size:.9em;word-break:break-all}
14+
.kv .label{font-family:system-ui,sans-serif;color:#555;display:inline-block;margin-right:.5em;font-size:.85em}
15+
a{color:#0a66c2;text-decoration:none}
16+
a:hover{text-decoration:underline}
17+
.btn{display:inline-block;padding:.55em 1.2em;border-radius:6px;text-decoration:none;font-size:.95em;border:1px solid transparent;cursor:pointer;background:#0a66c2;color:#fff;font-family:inherit}
18+
.btn:hover{background:#084e96;text-decoration:none}
19+
.btn-secondary{background:#fff;color:#222;border-color:#ddd}
20+
.btn-secondary:hover{background:#f0efeb}
21+
.btn-danger{background:#fff;color:#a00;border-color:#fcc}
22+
.btn-danger:hover{background:#fff0f0}
23+
.actions{margin:.5em 0 0;display:flex;gap:.5em;flex-wrap:wrap}
24+
[hidden]{display:none!important}
25+
.error{background:#fff0f0;color:#a00;padding:.8em 1em;border-radius:8px;margin:1em 0;display:none;font-family:"SFMono-Regular",Consolas,monospace;font-size:.9em;word-break:break-word}
26+
.error.show{display:block}
27+
footer{margin-top:1.5em;padding-top:1em;border-top:1px solid #eee;color:#888;font-size:.85em;text-align:center}
28+
</style>
29+
</head>
30+
<body>
31+
<div class="card">
32+
<h1>Your account</h1>
33+
<p class="lede" id="status">Loading session…</p>
34+
35+
<div id="signed-in" hidden>
36+
<h2>WebID</h2>
37+
<div class="kv"><a id="webid-link" href="#"><span id="webid"></span></a></div>
38+
39+
<h2>Pod</h2>
40+
<div class="kv"><span class="label">URL:</span><span id="pod-url"></span></div>
41+
42+
<h2>Explore</h2>
43+
<div class="actions">
44+
<a class="btn btn-secondary" id="profile-link" href="#">Profile</a>
45+
<a class="btn btn-secondary" href="./public/">/public/</a>
46+
<a class="btn btn-secondary" href="./private/">/private/</a>
47+
<a class="btn btn-secondary" href="./inbox/">/inbox/</a>
48+
<a class="btn btn-secondary" href="./settings/">/settings/</a>
49+
</div>
50+
51+
<h2>Session</h2>
52+
<div class="actions">
53+
<button class="btn btn-danger" id="signout-btn" type="button">Sign out</button>
54+
<a class="btn btn-secondary" href="./">Back to pod</a>
55+
</div>
56+
</div>
57+
58+
<div id="signed-out" hidden>
59+
<p>You're not signed in to this pod.</p>
60+
<div class="actions">
61+
<a class="btn" href="./signin.html">Sign in</a>
62+
<a class="btn btn-secondary" href="./">Back to pod</a>
63+
</div>
64+
</div>
65+
66+
<div class="error" id="error"></div>
67+
68+
<footer>Powered by <a href="https://github.com/JavaScriptSolidServer/jspod">jspod</a> · <a href="https://github.com/JavaScriptSolidServer/solid-oidc">solid-oidc</a></footer>
69+
</div>
70+
<script type="module">
71+
import Session from 'https://cdn.jsdelivr.net/npm/solid-oidc@0.0.8/solid-oidc.js';
72+
73+
const statusEl = document.getElementById('status');
74+
const errorEl = document.getElementById('error');
75+
const signedInEl = document.getElementById('signed-in');
76+
const signedOutEl = document.getElementById('signed-out');
77+
78+
function fail(msg) {
79+
statusEl.style.display = 'none';
80+
errorEl.textContent = msg;
81+
errorEl.classList.add('show');
82+
}
83+
84+
try {
85+
const session = new Session();
86+
let active = false;
87+
try {
88+
await session.restore();
89+
active = session.isActive;
90+
} catch {
91+
active = false;
92+
}
93+
94+
statusEl.style.display = 'none';
95+
96+
if (active) {
97+
const podUrl = new URL('./', window.location.href).href;
98+
const webid = session.webId || '(unknown)';
99+
document.getElementById('webid').textContent = webid;
100+
document.getElementById('webid-link').href = webid;
101+
document.getElementById('pod-url').textContent = podUrl;
102+
// Profile link strips the fragment (WebID typically ends with #me)
103+
document.getElementById('profile-link').href = webid.replace(/#.*/, '');
104+
signedInEl.hidden = false;
105+
document.getElementById('signout-btn').addEventListener('click', async () => {
106+
try {
107+
await session.logout();
108+
window.location.href = './';
109+
} catch (e) {
110+
fail('Sign-out failed: ' + (e.message || String(e)));
111+
}
112+
});
113+
} else {
114+
signedOutEl.hidden = false;
115+
}
116+
} catch (e) {
117+
fail(e && e.message ? e.message : String(e));
118+
}
119+
</script>
120+
</body>
121+
</html>

account.html.acl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"@context": {
3+
"acl": "http://www.w3.org/ns/auth/acl#",
4+
"foaf": "http://xmlns.com/foaf/0.1/"
5+
},
6+
"@graph": [
7+
{
8+
"@id": "#public",
9+
"@type": "acl:Authorization",
10+
"acl:agentClass": {
11+
"@id": "foaf:Agent"
12+
},
13+
"acl:accessTo": {
14+
"@id": "./account.html"
15+
},
16+
"acl:mode": [
17+
{
18+
"@id": "acl:Read"
19+
}
20+
]
21+
}
22+
]
23+
}

index.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,17 @@ ready.then((ok) => {
484484
const signinAclDst = join(options.root, 'signin.html.acl');
485485
if (existsSync(signinAclSrc)) copyFileSync(signinAclSrc, signinAclDst);
486486

487+
// account.html — post-sign-in dashboard. Same overwrite + public-
488+
// read pattern as signin.html. Both pages render their useful
489+
// state from a restored solid-oidc session; signed-out visitors
490+
// see a "sign in" prompt rather than a blank page.
491+
const acctSrc = join(__dirname, 'account.html');
492+
const acctDst = join(options.root, 'account.html');
493+
if (existsSync(acctSrc)) copyFileSync(acctSrc, acctDst);
494+
const acctAclSrc = join(__dirname, 'account.html.acl');
495+
const acctAclDst = join(options.root, 'account.html.acl');
496+
if (existsSync(acctAclSrc)) copyFileSync(acctAclSrc, acctAclDst);
497+
487498
const linksSrc = join(__dirname, 'links.jsonld');
488499
const linksDst = join(options.root, 'public', 'links.jsonld');
489500
if (existsSync(linksSrc) && !existsSync(linksDst)) {

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "jspod",
3-
"version": "0.0.17",
3+
"version": "0.0.18",
44
"description": "JavaScript Solid Pod - Just works, batteries included",
55
"type": "module",
66
"main": "index.js",
@@ -14,6 +14,8 @@
1414
"welcome.html",
1515
"signin.html",
1616
"signin.html.acl",
17+
"account.html",
18+
"account.html.acl",
1719
"links.jsonld"
1820
],
1921
"scripts": {

signin.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ <h1 id="title">Signing in…</h1>
7171
await session.handleRedirectFromLogin();
7272
titleEl.textContent = 'Signed in';
7373
ledeEl.textContent = 'WebID: ' + (session.webId || '(none)') + '. Redirecting…';
74-
setTimeout(() => { window.location.href = './'; }, 800);
74+
setTimeout(() => { window.location.href = './account.html'; }, 800);
7575
} else {
7676
// Try to restore a prior session. solid-oidc throws "Missing
7777
// refresh data" on a clean first visit — that's fine, it just
@@ -86,7 +86,7 @@ <h1 id="title">Signing in…</h1>
8686
if (restored) {
8787
titleEl.textContent = 'Already signed in';
8888
ledeEl.textContent = 'WebID: ' + session.webId + '. Redirecting…';
89-
setTimeout(() => { window.location.href = './'; }, 400);
89+
setTimeout(() => { window.location.href = './account.html'; }, 400);
9090
} else {
9191
await session.login(idp, redirectUri);
9292
}

welcome.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ <h1>Your Solid pod</h1>
3636

3737
<div class="actions">
3838
<a href="./signin.html" class="btn btn-primary" data-cond="login" hidden>Sign in</a>
39+
<a href="./account.html" class="btn btn-secondary">Account</a>
3940
<a href="./idp/register" class="btn btn-secondary" data-cond="register" hidden>Sign up</a>
4041
</div>
4142

0 commit comments

Comments
 (0)