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
6 changes: 4 additions & 2 deletions public/confirm-aktionariat/confirm.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
// confirmation email always reaches this web page; the app registers the scheme
// to re-open itself after confirmation.

var STATES = ['loading', 'confirmed', 'invalid', 'unavailable'];
var STATES = ['loading', 'confirmed', 'invalid', 'no-registration', 'unavailable'];
function show(state) {
STATES.forEach(function (s) {
document.getElementById('state-' + s).hidden = s !== state;
Expand All @@ -58,6 +58,8 @@
show('confirmed');
} else if (status === 'invalid') {
show('invalid');
} else if (status === 'no-registration') {
show('no-registration');
} else {
show('unavailable');
}
Expand All @@ -66,7 +68,7 @@
function confirm() {
show('loading');

// Mock hook for LOCAL preview only (?mock=confirmed|invalid|unavailable).
// Mock hook for LOCAL preview only (?mock=confirmed|invalid|no-registration|unavailable).
// Never honored on the real realunit.app / dev.realunit.app hosts, so a
// shared prod link cannot render a spoofed confirmation screen.
var mock = params.get('mock');
Expand Down
10 changes: 10 additions & 0 deletions public/confirm-aktionariat/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ <h1 data-i18n="invalid.title">Link ungültig oder abgelaufen</h1>
<p data-i18n="invalid.body">Dieser Bestätigungslink ist ungültig oder bereits abgelaufen. Bitte fordern Sie in der App einen neuen an.</p>
</div>

<!-- no-registration: the share register confirmed the email, but no wallet
registration matched it — a permanent outcome, so no retry CTA. -->
<div id="state-no-registration" hidden>
<div class="icon icon--warn">
<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v5"/><path d="M12 16h.01"/></svg>
</div>
<h1 data-i18n="no-registration.title">Link passt zu keiner Registrierung</h1>
<p data-i18n="no-registration.body">Ihre E-Mail-Adresse wurde zwar bestätigt, aber wir haben dazu keine Wallet-Registrierung gefunden. Bitte prüfen Sie, ob Sie in der RealUnit-App eine andere E-Mail-Adresse verwendet haben, oder kontaktieren Sie unseren Support.</p>
</div>

<!-- unavailable -->
<div id="state-unavailable" hidden>
<div class="icon icon--wait">
Expand Down
14 changes: 13 additions & 1 deletion public/js/lib/confirm-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
'unavailable.body':
'Wir konnten die Bestätigung gerade nicht abschliessen. Bitte versuchen Sie es in ein paar Minuten erneut.',
'unavailable.cta': 'Erneut versuchen',
'no-registration.title': 'Link passt zu keiner Registrierung',
'no-registration.body':
'Ihre E-Mail-Adresse wurde zwar bestätigt, aber wir haben dazu keine Wallet-Registrierung gefunden. Bitte prüfen Sie, ob Sie in der RealUnit-App eine andere E-Mail-Adresse verwendet haben, oder kontaktieren Sie unseren Support.',
},
en: {
'doc.title': 'RealUnit — Address confirmation',
Expand All @@ -57,6 +60,9 @@
'unavailable.body':
'We couldn’t complete the confirmation right now. Please try again in a few minutes.',
'unavailable.cta': 'Try again',
'no-registration.title': 'Link doesn’t match a registration',
'no-registration.body':
'Your email address was confirmed, but we couldn’t find a wallet registration for it. Please check whether you used a different email address in the RealUnit app, or contact our support.',
},
};

Expand Down Expand Up @@ -148,13 +154,19 @@

// Map an API response to a UI state. Any non-2xx (validation 400, rate-limit
// 429, 5xx) is a transient/unknown state → 'unavailable' (retryable), never a
// hard rejection. On 2xx the body's own status decides, and an unrecognized
// hard rejection. On 2xx the body's own status decides: 'confirmed_no_registration'
// (the email was confirmed but no wallet registration matched it — a permanent
// outcome, not a transient failure) maps to the dedicated 'no-registration' state,
// the other recognized statuses pass through unchanged, and an unrecognized
// status is treated as unavailable rather than trusted.
function mapResult(response) {
if (!response.ok) {
return 'unavailable';
}
var status = response.body && response.body.status;
if (status === 'confirmed_no_registration') {
return 'no-registration';
}
if (status === 'confirmed' || status === 'invalid' || status === 'unavailable') {
return status;
}
Expand Down
6 changes: 6 additions & 0 deletions test/confirm-core.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@ describe('mapResult', () => {
expect(mapResult({ ok: true, body: { status: 'unavailable' } })).toBe('unavailable');
});

test('a 2xx response with status confirmed_no_registration maps to no-registration', () => {
expect(mapResult({ ok: true, body: { status: 'confirmed_no_registration' } })).toBe(
'no-registration',
);
});

test('a 2xx response with an unrecognized status maps to unavailable', () => {
expect(mapResult({ ok: true, body: { status: 'weird' } })).toBe('unavailable');
});
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 9 additions & 1 deletion tests/behavior.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ test.describe('confirm-aktionariat flow', () => {
expect(confirmCalls).toEqual([]);
});

for (const state of ['confirmed', 'invalid', 'unavailable']) {
for (const state of ['confirmed', 'invalid', 'no-registration', 'unavailable']) {
test(`?mock=${state} renders the ${state} state`, async ({ page }) => {
await page.goto(`/confirm-aktionariat/?mock=${state}`);
await expect(page.locator(`#state-${state}`)).toBeVisible();
Expand Down Expand Up @@ -178,6 +178,14 @@ test.describe('confirm-aktionariat flow', () => {
await expect(page.locator('#state-invalid')).toBeVisible();
});

test('a 200 response with the confirmed_no_registration status shows the no-registration state', async ({
page,
}) => {
await routeConfirm(page, { status: 200, body: { status: 'confirmed_no_registration' } });
await page.goto('/confirm-aktionariat/?email=a%40b.ch&code=C&user=U');
await expect(page.locator('#state-no-registration')).toBeVisible();
});

test('a non-2xx API response shows the unavailable state', async ({ page }) => {
await routeConfirm(page, { status: 500, body: {} });
await page.goto('/confirm-aktionariat/?email=a%40b.ch&code=C&user=U');
Expand Down
18 changes: 16 additions & 2 deletions tests/pages.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export const PROJECTS = ['desktop-chromium', 'tablet-chromium', 'mobile-safari']
// override before the page scripts run, so platform.js sets
// html[data-platform] deterministically regardless of the device
// waitFor — optional confirm-page state ('confirmed' | 'invalid' |
// 'unavailable') to wait for before the shot (the ?mock hook renders
// it after a short delay)
// 'no-registration' | 'unavailable') to wait for before the shot (the
// ?mock hook renders it after a short delay)
// projects — the viewports this view applies to
//
// Coverage: the landing page in both its equal-badge (desktop/tablet) and
Expand Down Expand Up @@ -73,6 +73,20 @@ export const VIEWS = [
waitFor: 'invalid',
projects: ['desktop-chromium'],
},
// Confirm — no-registration state (email confirmed, no wallet registration
// matched it — a permanent outcome, no retry CTA).
{
slug: 'confirm-no-registration',
path: '/confirm-aktionariat/?mock=no-registration&lang=de',
waitFor: 'no-registration',
projects: ['desktop-chromium', 'tablet-chromium', 'mobile-safari'],
},
{
slug: 'confirm-no-registration-en',
path: '/confirm-aktionariat/?mock=no-registration&lang=en',
waitFor: 'no-registration',
projects: ['desktop-chromium'],
},
// Confirm — service unavailable (the retry button is shown).
{
slug: 'confirm-unavailable',
Expand Down
Loading