diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.js | 6 | ||||
| -rw-r--r-- | src/features/gifts.js | 2 | ||||
| -rw-r--r-- | src/features/nostrlogin.js | 13 | ||||
| -rw-r--r-- | src/i18n.js | 1 | ||||
| -rw-r--r-- | src/pomegranate.js | 14 | ||||
| -rw-r--r-- | src/style.css | 7 |
6 files changed, 32 insertions, 11 deletions
@@ -1095,7 +1095,7 @@ function createPane() { h('div', { class: 'warn-box' }, t('writeDownWarn')), h( 'div', - { class: 'words' }, + { class: 'words', style: '--rows-3:' + Math.ceil(words.length / 3) + ';--rows-2:' + Math.ceil(words.length / 2) }, words.map((w, i) => h('div', { class: 'w' }, h('span', { class: 'n' }, i + 1), h('span', { class: 't' }, w)) ) @@ -3053,7 +3053,7 @@ function recoveryCard(a) { return h('div', { class: 'card col' }, h('h3', {}, t('recoveryPhrase')), h('div', { class: 'warn-box' }, t('recoveryWarn')), - h('div', { class: 'words' }, words.map((w, i) => + h('div', { class: 'words', style: '--rows-3:' + Math.ceil(words.length / 3) + ';--rows-2:' + Math.ceil(words.length / 2) }, words.map((w, i) => h('div', { class: 'w' + (unmasked ? '' : ' masked') }, h('span', { class: 'n' }, i + 1), h('span', { class: 't' }, unmasked ? w : '••••••')))), @@ -3405,7 +3405,7 @@ function onboardScreen() { title(t('onbBackupTitle')), h('p', { class: 'muted', style: 'margin:0' }, t('onbBackupBody')), h('div', { class: 'warn-box' }, t('writeDownWarn')), - h('div', { class: 'words' }, words.map((w, i) => + h('div', { class: 'words', style: '--rows-3:' + Math.ceil(words.length / 3) + ';--rows-2:' + Math.ceil(words.length / 2) }, words.map((w, i) => h('div', { class: 'w' }, h('span', { class: 'n' }, i + 1), h('span', { class: 't' }, w)))), h('div', { class: 'row gap6' }, copyBtn(acc.mnemonic, t('copyPhrase'))), h('button', { class: 'btn-primary btn-block', style: 'padding:14px', onClick: done }, t('onbBackupDone')), diff --git a/src/features/gifts.js b/src/features/gifts.js index 20b192c..052a107 100644 --- a/src/features/gifts.js +++ b/src/features/gifts.js @@ -209,7 +209,7 @@ export function giftsFeature(ctx) { h('div', { class: 'card col' }, h('h3', {}, t('recoveryPhrase')), h('div', { class: 'warn-box' }, t('writeDownWarn')), - h('div', { class: 'words' }, + h('div', { class: 'words', style: '--rows-3:' + Math.ceil(words.length / 3) + ';--rows-2:' + Math.ceil(words.length / 2) }, words.map((w, i) => h('div', { class: 'w' }, h('span', { class: 'n' }, i + 1), h('span', { class: 't' }, w))) ), h('div', { class: 'row gap6' }, copyBtn(wallet.mnemonic, t('copyPhrase'))), diff --git a/src/features/nostrlogin.js b/src/features/nostrlogin.js index 503384f..1d78b39 100644 --- a/src/features/nostrlogin.js +++ b/src/features/nostrlogin.js @@ -311,7 +311,7 @@ export function nostrLoginFeature(ctx) { // "Sign in with Google" — pomegranate: the code (and its FROST dealer) // rides in a lazy chunk, so the popup must open HERE, inside the click's // user activation, and the module navigates it once loaded. - async function googleLogin(run) { + async function googleLogin(run, central = null) { const popup = window.open('about:blank', 'OAuth', 'width=600,height=600'); ui.nostrLoginError = ''; // no status narration — a quiet spinner on the button itself is the whole @@ -320,7 +320,7 @@ export function nostrLoginFeature(ctx) { busy(true); try { const pome = await import('../pomegranate.js'); - const { uri } = await pome.loginWithGoogle(popup); + const { uri } = await pome.loginWithGoogle(popup, { central }); await run(() => bunkerSigner(uri, { onAuth: (url) => { ui.nostrLoginAuthUrl = url; render(); } })); } catch (e) { try { popup && popup.close(); } catch {} @@ -375,6 +375,12 @@ export function nostrLoginFeature(ctx) { // Google + passkey, with their transient status rows — shared between the // collapsed front-door card and the expanded signer list. + // Google accounts live per pomegranate central. coinos runs its own + // (auth.coinos.io); whoever signed in while auth.njump.me was the default + // still has a door to it. Mirrors pomegranate.js's CENTRALS (that module + // is a lazy chunk, so the two strings live here too). + const GOOGLE_CENTRAL = 'https://auth.njump.me'; // flip to auth.coinos.io with pomegranate.js DEFAULT_CENTRAL + const GOOGLE_LEGACY = 'https://auth.njump.me'; function externalButtons(run, big = false) { const hasPasskey = typeof window !== 'undefined' && !!window.PublicKeyCredential && !!navigator.credentials; const pad = big ? 'padding:14px' : ''; @@ -382,6 +388,9 @@ export function nostrLoginFeature(ctx) { h('button', { class: 'btn-block', style: pad, disabled: ui.nostrLoginBusy, onClick: () => googleLogin(run) }, markedLabel(GOOGLE_MARK, t('nlGoogle'), ui.nostrLoginVia === 'google')), + GOOGLE_CENTRAL !== GOOGLE_LEGACY + ? h('button', { class: 'linklike small', style: 'align-self:center', disabled: ui.nostrLoginBusy, onClick: () => googleLogin(run, GOOGLE_LEGACY) }, t('nlGooglePrev')) + : null, hasPasskey ? h('button', { class: 'btn-block', style: pad, disabled: ui.nostrLoginBusy, onClick: () => passkeyLogin(run) }, markedLabel(PASSKEY_MARK, t('nlPasskey'), ui.nostrLoginVia === 'passkey')) diff --git a/src/i18n.js b/src/i18n.js index c3fa08b..66ed1a8 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -842,6 +842,7 @@ const STR = { onbStartHint: "Takes a minute: write down twelve words and it's yours", onbOrSignIn: 'or', onbHaveSeed: 'Have an existing seed or entropy to import?', + nlGooglePrev: 'Signed in with Google here before? Use the previous sign-in server', onbCreateInstead: 'Create a new wallet instead', onbBackupTitle: 'Write down your recovery phrase', onbBackupBody: "Signing in was the easy part — but the sign-in isn't your wallet. These twelve words are. They're the only way back in if you lose access to that account, and nobody can reissue them for you.", diff --git a/src/pomegranate.js b/src/pomegranate.js index 59a1d3b..e86b650 100644 --- a/src/pomegranate.js +++ b/src/pomegranate.js @@ -19,7 +19,13 @@ import { hex } from '@scure/base'; import { finalizeEvent, generateSecretKey } from 'nostr-tools/pure'; import { hexPubShard, hexShard, trustedKeyDeal } from '@jsr/fiatjaf__promenade-trusted-dealer'; -export const CENTRAL = 'https://auth.njump.me'; +// Which central: coinos runs its own (auth.coinos.io — the Google consent +// screen and the bunker relay carry our name), but accounts live PER +// central, so anyone who signed in while auth.njump.me was the default +// keeps a door to it (see nostrlogin's "previous server" line). +export const CENTRALS = { coinos: 'https://auth.coinos.io', legacy: 'https://auth.njump.me' }; +export const DEFAULT_CENTRAL = CENTRALS.legacy; // flip to CENTRALS.coinos once its Google client is live +let CENTRAL = DEFAULT_CENTRAL; const OPERATORS = [ 'https://po.f7z.io', 'https://po.coracle.social', @@ -147,8 +153,10 @@ async function bunkerUri(token) { } // The whole flow: authenticate, find-or-create the sharded account, hand back -// a bunker URI. `popup` must come from the caller's click handler. -export async function loginWithGoogle(popup, { onStatus } = {}) { +// a bunker URI. `popup` must come from the caller's click handler; `central` +// picks the server (default: DEFAULT_CENTRAL). +export async function loginWithGoogle(popup, { onStatus, central } = {}) { + CENTRAL = central || DEFAULT_CENTRAL; const token = await authenticate(popup); const email = emailOf(token); if (!email) throw new Error('Google login returned no email'); diff --git a/src/style.css b/src/style.css index 8cdd92d..36698ae 100644 --- a/src/style.css +++ b/src/style.css @@ -254,7 +254,10 @@ textarea { resize: vertical; min-height: 84px; font-family: var(--mono); font-si .addr-check .hl { color: var(--ink); font-weight: 700; } /* ---- mnemonic grid ---- */ -.words { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; } +/* Words run DOWN a column, then the next — the way a hand copies a list — + so on a phone (two columns) the first column is 1–6, not 1,3,5,…; the row + count comes from the word count (--rows-3 / --rows-2, set inline). */ +.words { display: grid; grid-auto-flow: column; grid-template-rows: repeat(var(--rows-3, 4), auto); grid-auto-columns: 1fr; gap: 8px; } .words .w { display: flex; align-items: baseline; gap: 7px; background: var(--surface2); border: 1px solid var(--line); border-radius: 10px; padding: 9px 11px; font-family: var(--mono); @@ -366,7 +369,7 @@ hr.divider { margin: 16px 0; } /* ---- how it works ---- */ @media (max-width: 420px) { - .words { grid-template-columns: repeat(2, 1fr); } + .words { grid-template-rows: repeat(var(--rows-2, 6), auto); } .balance .amt { font-size: 28px; } } |
