feat(ui): one-tap confirm deeplink screen + client language
Add the /confirm/<token> SPA route and Confirm screen: a prefetch-safe button POSTs the token via a new confirmEmailLink RPC (client/transport/mock/codec + ConfirmLinkResult model). A login adopts the minted session and enters the app; a link shows confirmed / merge-in-the-app; an invalid or expired token asks for a new code. Exempt /confirm from the no-session /login redirect. Forward the client locale on the email request (authEmailRequest gains language → app.locale) so a fresh web login email is localised. Handle the new 'profile' live-event sub-kind by re-fetching the profile, so a link confirmed in another browser reflects in-app at once. i18n en+ru.
This commit is contained in:
@@ -409,6 +409,11 @@ function openStream(): void {
|
||||
if (e.sub === 'banner') {
|
||||
void refreshProfile();
|
||||
}
|
||||
// The viewer's own profile changed out of band (an email confirmed via the
|
||||
// one-tap deeplink opened in another browser): re-fetch it.
|
||||
if (e.sub === 'profile') {
|
||||
void refreshProfile();
|
||||
}
|
||||
void refreshNotifications();
|
||||
}
|
||||
},
|
||||
@@ -738,7 +743,7 @@ export async function bootstrap(): Promise<void> {
|
||||
if (saved) {
|
||||
await adoptSession(saved);
|
||||
if (router.route.name === 'login') navigate('/');
|
||||
} else if (router.route.name !== 'login') {
|
||||
} else if (router.route.name !== 'login' && router.route.name !== 'confirm') {
|
||||
navigate('/login');
|
||||
}
|
||||
app.ready = true;
|
||||
@@ -915,7 +920,7 @@ export async function loginGuest(): Promise<void> {
|
||||
|
||||
export async function requestEmailCode(email: string): Promise<boolean> {
|
||||
try {
|
||||
await gateway.authEmailRequest(email);
|
||||
await gateway.authEmailRequest(email, app.locale);
|
||||
return true;
|
||||
} catch (err) {
|
||||
handleError(err);
|
||||
@@ -933,6 +938,20 @@ export async function loginEmail(email: string, code: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// confirmDeeplink verifies a one-tap email deeplink token opened from a confirmation
|
||||
// email. A login adopts the minted session and enters the app; a link reports whether
|
||||
// it confirmed or needs the interactive merge. It throws on an invalid/expired token,
|
||||
// which the confirm screen surfaces. This browser need not be signed in.
|
||||
export async function confirmDeeplink(token: string): Promise<'login' | 'linked' | 'merge_required'> {
|
||||
const r = await gateway.confirmEmailLink(token);
|
||||
if (r.purpose === 'login' && r.session) {
|
||||
await adoptSession(r.session);
|
||||
navigate('/');
|
||||
return 'login';
|
||||
}
|
||||
return r.status === 'merge_required' ? 'merge_required' : 'linked';
|
||||
}
|
||||
|
||||
export async function logout(): Promise<void> {
|
||||
closeStream();
|
||||
resetConnection();
|
||||
|
||||
Reference in New Issue
Block a user