Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"dependencies": {
"@profullstack/affiliates": "0.3.0",
"@profullstack/footer": "latest",
"@profullstack/profile": "0.1.6",
"@profullstack/profile": "0.1.7",
"@profullstack/redeem-credits": "0.2.0",
"@profullstack/sealed-secrets": "0.1.0",
"@profullstack/x402-gateway": "^0.13.1",
Expand Down
14 changes: 11 additions & 3 deletions apps/web/public/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,8 @@ function bindShell() {
* The shared profile fields (@profullstack/profile), from the <template> the
* server renders into the shell, filled with `values`.
*/
function profileFields(values = {}) {
const t = document.getElementById('profile-fields');
function profileFields(values = {}, importOpenProfile = false) {
const t = document.getElementById(importOpenProfile ? 'contact-profile-fields' : 'profile-fields');
if (!t) return '';
const box = document.createElement('div');
box.append(t.content.cloneNode(true));
Expand All @@ -296,6 +296,13 @@ function showFieldErrors(form, errors = {}) {
}
}

function enhanceProfile() {
const script = document.createElement('script');
script.src = '/assets/profile.js';
document.body.append(script);
script.onload = () => script.remove();
}

function renderSignin(q, signup = false) {
const email = q.get('email') ?? '';
root.innerHTML = `<div class="center"><div class="auth">
Expand Down Expand Up @@ -1327,7 +1334,7 @@ async function viewSettings() {
<form id="me-form" class="row"><label class="f" style="flex:1">Name<input name="name" value="${esc(state.me.user.name ?? '')}"></label><button type="submit" style="align-self:flex-end">Save</button></form>
<p class="dim small">${esc(state.me.user.email)}</p></div></section>
<section class="card" id="contact"><div class="card-h"><h2>Contact details</h2></div><div class="card-b">
<form id="contact-form" class="stack">${profileFields(profile)}<div><button type="submit">Save contact details</button></div></form>
<form id="contact-form" class="stack">${profileFields(profile, true)}<div><button type="submit">Save contact details</button></div></form>
<p class="dim small">Private to your account. Sent with ${esc(state.me.user.email)}.</p></div></section>
<section class="card"><div class="card-h"><h2>Passkeys</h2><button class="sm" id="add-passkey">Add a passkey</button></div>
<div class="card-b muted">${state.me.passkeys ? `${state.me.passkeys} passkey${state.me.passkeys === 1 ? '' : 's'} saved. Sign in with Face ID, Touch ID or your security key.` : 'Add one to sign in without waiting for an email.'}</div></section>
Expand All @@ -1354,6 +1361,7 @@ async function viewSettings() {
return {
html,
bind() {
enhanceProfile();
document.getElementById('signout').onclick = signOut;
document.getElementById('me-form').onsubmit = (e) => {
e.preventDefault();
Expand Down
1 change: 1 addition & 0 deletions apps/web/public/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
<div id="toast" role="status" aria-live="polite"></div>
<dialog id="modal"></dialog>
<!--profile-fields-->
<!--contact-profile-fields-->
</body>
</html>
7 changes: 6 additions & 1 deletion apps/web/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,18 @@ app.get('/health/manifest.webmanifest', (c) =>
const PROFILE_TEMPLATE = `<template id="profile-fields">${renderProfileFields({
fields: PROFILE_COLUMNS.filter((f) => f !== 'email'),
})}</template>`;
const CONTACT_PROFILE_TEMPLATE = `<template id="contact-profile-fields">${renderProfileFields({
fields: PROFILE_COLUMNS.filter((f) => f !== 'email'),
importOpenProfile: true,
})}</template>`;
const shell = async (c) =>
c.html(
await withUniversalFooter(
pub('app/index.html')
.toString()
.replace('<!--year-->', String(new Date().getFullYear()))
.replace('<!--profile-fields-->', PROFILE_TEMPLATE),
.replace('<!--profile-fields-->', PROFILE_TEMPLATE)
.replace('<!--contact-profile-fields-->', CONTACT_PROFILE_TEMPLATE),
),
);
for (const p of ['/app', '/app/*', '/signin', '/signup', '/portal', '/portal/*']) app.get(p, shell);
Expand Down
6 changes: 4 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion test/signup-profile.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ const link = (body) =>

test('the signup page shell carries the profile fields, once each', async () => {
const html = await (await app.request('/signup')).text();
const signupFields = html.match(/<template id="profile-fields">([\s\S]*?)<\/template>/)?.[1] ?? '';
for (const name of ['first_name', 'last_name', 'company_domain', 'job_title', 'linkedin_url'])
expect(html.match(new RegExp(`name="${name}"`, 'g'))?.length).toBe(1);
expect(signupFields.match(new RegExp(`name="${name}"`, 'g'))?.length).toBe(1);
// The shared footer has its own email field; the profile template does not.
const fields = html.match(/<template id="profile-fields">[\s\S]*?<\/template>/)?.[0] ?? '';
expect(fields).not.toContain('name="email"');
Expand Down
Loading