Skip to content

feat: Refine email linking UX and i18n#3622#3642

Draft
nicolas-machugounot wants to merge 13 commits intoopenfrontio:mainfrom
nicolas-machugounot:feature/issue-3622-ux-email-link
Draft

feat: Refine email linking UX and i18n#3622#3642
nicolas-machugounot wants to merge 13 commits intoopenfrontio:mainfrom
nicolas-machugounot:feature/issue-3622-ux-email-link

Conversation

@nicolas-machugounot
Copy link
Copy Markdown

@nicolas-machugounot nicolas-machugounot commented Apr 11, 2026

Description:

This PR refines the email linking UX and i18n for issue #3622. It keeps the PR focused on the client-side email backup flow, with clearer labels, trimmed email input, a translated callback failure message, and a documentation update that matches the POST confirmation contract.

Scope

  • Distinguish labels between login magic link and email backup linking flow.
  • Trim email input before validation/API call in email backup linking.
  • Replace hardcoded callback failure alert with translation key.
  • Align implementation plan doc with POST confirm endpoint using JSON body token.

Changes

  • Account modal email backup section now uses dedicated labels:
    • account_modal.add_email_backup
    • account_modal.send_backup_link
  • Email value is trimmed before empty-check and request submission.
  • Missing token path in #link-email callback now uses:
    • account_modal.email_link_confirmation_failed
  • Plan doc updated to:
    • POST /auth/link/email/confirm with { token } in body.

Testing performed

  • npm run lint

  • npm test -- tests/TranslationSystem.test.ts

  • I have added screenshots for all UI updates

  • I process any text displayed to the user through translateText() and I've added it to the en.json file

  • I have added relevant tests to the test directory

  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

…rs dans le processus de contribution et les exigences de qualité du code.
…ront afin d'améliorer le processus de contribution et de validation.
…endance à Discord, incluant les objectifs, les endpoints API, et les critères d'acceptation.
…session authentifiée et payload email + redirectDomain.

Ajout d’une section UI dans le compte connecté Discord (sans email lié) pour saisir un email de secours et lancer la liaison.
Gestion des retours utilisateur via alertes succès/erreur, sans casser le flow existant de magic link classique.
…mail de liaison

Gestion du callback #link-email?link-email-token=... qui confirme la liaison
Appel POST /auth/link/email/confirm pour valider le token et lier l'email au compte
UI améliorée dans le modal compte (section de liaison email pour Discord sans email lié)
Messages d'alerte explicites et rafraîchissement auto après confirmation
…session authentifiée et payload email + redirectDomain.

Ajout d’une section UI dans le compte connecté Discord (sans email lié) pour saisir un email de secours et lancer la liaison.
Gestion des retours utilisateur via alertes succès/erreur, sans casser le flow existant de magic link classique.
…th.ts.

Ajout de "ignoreDeprecations": "6.0" dans tsconfig.json pour lever l’erreur liée à baseUrl.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 11, 2026

Walkthrough

Adds email backup/link workflow for Discord-linked users (frontend UI, client auth helpers, URL handling), new translation keys, a staged implementation doc for Issue #3622, TypeScript config tweaks, and new AI agent/chatmode/prompt files for contribution guidance.

Changes

Cohort / File(s) Summary
AI Agent & Contribution Guidance
.github/agents/openfront-contributor.agent.md, .github/chatmodes/openfront-contributor.chatmode.md, .github/prompts/openfront-contribution-readiness.prompt.md
Adds agent, chatmode, and prompt configs to guide automated contributors: tool allowlists, workflow rules, PR checklist, validation steps, and required response formats (French-focused).
Email Backup Feature (docs & i18n)
docs/issue-3622-login-alternative-plan.md, resources/lang/en.json
New implementation plan for email-login fallback; adds translation keys for email backup UI and link result messages.
Frontend: Account modal & Auth client
src/client/AccountModal.ts, src/client/Auth.ts
Account modal: new "add email backup" UI, email state, and start-link handler. Auth client: exports sendLinkEmailStart(email) and confirmEmailLink(token) to call backend endpoints.
Frontend: Routing & confirmation flow
src/client/Main.ts
Adds #link-email hash handling and handleEmailLinkConfirmation(token) to call confirmEmailLink, show translated alerts, and trigger reload on success; also small event-listener signature adjustments.
Build config
tsconfig.json
Removed baseUrl and added ignoreDeprecations: "5.0"; adjusted resources/* path mapping to ./resources/*.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as AccountModal
    participant ClientAuth as Client Auth
    participant Server as Backend API

    User->>UI: Enter email and click "start link"
    UI->>ClientAuth: sendLinkEmailStart(email)
    ClientAuth->>Server: POST /auth/link/email/start (with Auth header)
    Server-->>ClientAuth: 200 OK
    ClientAuth-->>UI: true
    UI-->>User: Show success alert, clear input

    rect rgba(100, 150, 200, 0.5)
    Note over User,Server: User receives email and clicks verification link
    end

    User->>UI: Navigate to URL with `#link-email-token`
    UI->>ClientAuth: confirmEmailLink(token)
    ClientAuth->>Server: POST /auth/link/email/confirm (token)
    Server-->>ClientAuth: 200 OK with { email }
    ClientAuth-->>UI: { email }
    UI-->>User: Show success alert
    Note over UI,User: Page reloads after 1.5s to refresh session
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related issues

  • Add AGENTS.md to codebase #3528: Adds .github/agents, .github/chatmodes, and .github/prompts files that provide project-specific AI assistant context, matching the objectives described in that issue.

Poem

Un courriel en secours s'installe,
Liant compte et lien sans débat,
Des agents guident la mise en bal,
Tests, messages, et plan déjà là. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description is directly related to the changeset, clearly outlining the scope of email linking UX refinements, translation key additions, and documentation updates that match the files modified.
Title check ✅ Passed The title 'feat: Refine email linking UX and i18n#3622' clearly describes the main changes: refining the email linking user experience and internationalization for issue #3622.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/client/AccountModal.ts (1)

357-385: Add in-flight protection to prevent duplicate link-start requests.

A quick double-click can send multiple start-link requests. Consider a small loading guard and button disable state.

Suggested refactor
-  `@state`() private linkEmail: string = "";
+  `@state`() private linkEmail: string = "";
+  `@state`() private isStartingEmailLink: boolean = false;
...
-          <button
+          <button
             `@click`="${this.handleStartEmailLink}"
+            ?disabled=${this.isStartingEmailLink}
             class="w-full rounded-xl border border-white/5 bg-gradient-to-r from-blue-600 to-blue-700 px-6 py-3 text-sm font-bold uppercase tracking-wider text-white shadow-lg transition-all hover:from-blue-500 hover:to-blue-600 hover:shadow-blue-900/40"
           >
...
   private async handleStartEmailLink() {
+    if (this.isStartingEmailLink) return;
     const email = this.linkEmail.trim();
     if (!email) {
       alert(translateText("account_modal.enter_email_address"));
       return;
     }

-    const success = await sendLinkEmailStart(email);
-    if (success) {
-      alert(
-        translateText("account_modal.recovery_email_sent", {
-          email,
-        }),
-      );
-      this.linkEmail = "";
-      return;
-    }
-
-    alert(translateText("account_modal.failed_to_send_recovery_email"));
+    this.isStartingEmailLink = true;
+    try {
+      const success = await sendLinkEmailStart(email);
+      if (success) {
+        alert(
+          translateText("account_modal.recovery_email_sent", {
+            email,
+          }),
+        );
+        this.linkEmail = "";
+        return;
+      }
+      alert(translateText("account_modal.failed_to_send_recovery_email"));
+    } finally {
+      this.isStartingEmailLink = false;
+    }
   }

Also applies to: 416-435

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/AccountModal.ts` around lines 357 - 385, renderEmailLinkSection
currently allows rapid double-clicks to trigger duplicate requests; add an
in-flight guard property (e.g., linkInProgress or isLinkingEmail) and use it to
disable the "send backup link" button and short-circuit subsequent calls. Update
handleStartEmailLink to check the flag at the start, set it true before
beginning the async request, and set it false in both success and error/finally
paths so the button becomes enabled again; also bind the button disabled state
to that flag in renderEmailLinkSection. Apply the same pattern to the similar
email-link UI/handler referenced around lines 416-435.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/AccountModal.ts`:
- Around line 423-434: The success/failure alerts in the sendLinkEmailStart flow
use recovery-email translation keys; update the messages to use backup-link
specific keys instead. In the block that calls sendLinkEmailStart(email) and
shows alerts, replace translateText("account_modal.recovery_email_sent", { email
}) with the backup-link success key (e.g., "account_modal.backup_link_sent" or
similar) and replace
translateText("account_modal.failed_to_send_recovery_email") with the
backup-link failure key (e.g., "account_modal.failed_to_send_backup_link"); keep
clearing this.linkEmail and the control flow the same so only the translation
keys change.

In `@src/client/Auth.ts`:
- Around line 269-271: The code reads an untyped response JSON and returns {
email } without validating it; update the logic around const json = await
response.json() in Auth.ts to verify that json.email exists and is a string
(e.g., typeof json.email === 'string') before returning the success object, and
if the check fails, return an appropriate error path or throw a descriptive
error so the function's typed success shape is upheld.

In `@tsconfig.json`:
- Line 10: The TS compiler option "ignoreDeprecations" in tsconfig is set to an
incompatible value ("6.0"); update the JSON property "ignoreDeprecations" to
"5.0" so it matches the installed TypeScript version (e.g., change the value of
the "ignoreDeprecations" key in tsconfig.json from "6.0" to "5.0").

---

Nitpick comments:
In `@src/client/AccountModal.ts`:
- Around line 357-385: renderEmailLinkSection currently allows rapid
double-clicks to trigger duplicate requests; add an in-flight guard property
(e.g., linkInProgress or isLinkingEmail) and use it to disable the "send backup
link" button and short-circuit subsequent calls. Update handleStartEmailLink to
check the flag at the start, set it true before beginning the async request, and
set it false in both success and error/finally paths so the button becomes
enabled again; also bind the button disabled state to that flag in
renderEmailLinkSection. Apply the same pattern to the similar email-link
UI/handler referenced around lines 416-435.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c4fffa37-9e8a-4ecc-a896-3dec29315ba9

📥 Commits

Reviewing files that changed from the base of the PR and between 696e727 and 8920cb4.

📒 Files selected for processing (9)
  • .github/agents/openfront-contributor.agent.md
  • .github/chatmodes/openfront-contributor.chatmode.md
  • .github/prompts/openfront-contribution-readiness.prompt.md
  • docs/issue-3622-login-alternative-plan.md
  • resources/lang/en.json
  • src/client/AccountModal.ts
  • src/client/Auth.ts
  • src/client/Main.ts
  • tsconfig.json

@github-project-automation github-project-automation bot moved this from Triage to Development in OpenFront Release Management Apr 11, 2026
@nicolas-machugounot
Copy link
Copy Markdown
Author

Resolves #3622

Description:

This PR refines the email linking UX and i18n for issue #3622. It keeps the PR focused on the client-side email backup flow, with clearer labels, trimmed email input, a translated callback failure message, and a documentation update that matches the POST confirmation contract.

Scope

  • Distinguish labels between login magic link and email backup linking flow.
  • Trim email input before validation/API call in email backup linking.
  • Replace hardcoded callback failure alert with translation key.
  • Align implementation plan doc with POST confirm endpoint using JSON body token.

Changes

  • Account modal email backup section now uses dedicated labels:
    • account_modal.add_email_backup
    • account_modal.send_backup_link
  • Email value is trimmed before empty-check and request submission.
  • Missing token path in #link-email callback now uses:
    • account_modal.email_link_confirmation_failed
  • Plan doc updated to:
    • POST /auth/link/email/confirm with { token } in body.

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

Please put your Discord username so you can be contacted if a bug or regression is found:

_nicolitoo

Testing performed

  • npm run lint
  • npm test -- tests/TranslationSystem.test.ts

coderabbitai[bot]
coderabbitai bot previously approved these changes Apr 11, 2026
@github-project-automation github-project-automation bot moved this from Development to Final Review in OpenFront Release Management Apr 11, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/Main.ts`:
- Around line 314-320: Avoid unsafe Event-to-CustomEvent casts by typing the
event parameter in the listeners: change the "leave-lobby" listener to supply a
callback signature that accepts CustomEvent<{ lobby: string | number; cause?:
string }> (or simply omit the parameter since handleLeaveLobby doesn't use it)
and invoke this.handleLeaveLobby with that typed event; change the
"update-game-config" listener to accept CustomEvent<{ config: unknown }> and
pass that typed event into handleUpdateGameConfig so the handler can safely
access event.detail.config; keep the "kick-player" listener as-is (it already
binds this) but ensure any handlers that read event.detail use appropriately
typed CustomEvent parameters (refer to handleLeaveLobby and
handleUpdateGameConfig for the exact handler names).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 93a29ce0-3030-4bd5-8230-ca946991fcc7

📥 Commits

Reviewing files that changed from the base of the PR and between 8920cb4 and a88f248.

📒 Files selected for processing (3)
  • src/client/Auth.ts
  • src/client/Main.ts
  • tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • tsconfig.json

Comment on lines +314 to +320
document.addEventListener("leave-lobby", (event: Event) => {
void this.handleLeaveLobby(event as CustomEvent);
});
document.addEventListener("kick-player", this.handleKickPlayer.bind(this));
document.addEventListener(
"update-game-config",
this.handleUpdateGameConfig.bind(this),
);
document.addEventListener("update-game-config", (event: Event) => {
this.handleUpdateGameConfig(event as CustomEvent);
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify emitters/listeners for leave-lobby and update-game-config event shape usage.
rg -n --type=ts -C3 'addEventListener\("leave-lobby"|addEventListener\("update-game-config"' 
rg -n --type=ts -C3 'dispatchEvent\(\s*new\s+CustomEvent\("leave-lobby"|dispatchEvent\(\s*new\s+CustomEvent\("update-game-config"'
rg -n --type=ts -C3 'dispatchEvent\(\s*new\s+Event\("leave-lobby"|dispatchEvent\(\s*new\s+Event\("update-game-config"'

Repository: openfrontio/OpenFrontIO

Length of output: 825


🏁 Script executed:

#!/bin/bash
# Find all dispatch sites for leave-lobby event
rg -n --type=ts 'dispatchEvent.*leave-lobby|CustomEvent.*leave-lobby' -A 2 -B 2

Repository: openfrontio/OpenFrontIO

Length of output: 1474


🏁 Script executed:

#!/bin/bash
# Find all dispatch sites for update-game-config event
rg -n --type=ts 'dispatchEvent.*update-game-config|CustomEvent.*update-game-config' -A 2 -B 2

Repository: openfrontio/OpenFrontIO

Length of output: 368


🏁 Script executed:

#!/bin/bash
# Check the handler implementations
rg -n --type=ts 'handleLeaveLobby|handleUpdateGameConfig' -A 8

Repository: openfrontio/OpenFrontIO

Length of output: 2221


Avoid unchecked EventCustomEvent casts.

Lines 315 and 319 use type assertions to cast plain Event to CustomEvent, but the listeners should declare the event type directly in the callback signature. While current dispatch sites consistently use CustomEvent with proper detail objects, explicit typing prevents runtime errors if the contract changes.

For "leave-lobby": The detail contains { lobby: string | number, cause?: string }. Line 315's handler doesn't access the event anyway, so the cast is unnecessary.

For "update-game-config": The detail contains { config: unknown }. Line 319's handler directly accesses event.detail.config without type safety.

document.addEventListener("leave-lobby", (event: CustomEvent<{ lobby: string | number; cause?: string }>) => {
  void this.handleLeaveLobby(event);
});

document.addEventListener("update-game-config", (event: CustomEvent<{ config: unknown }>) => {
  this.handleUpdateGameConfig(event);
});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
document.addEventListener("leave-lobby", (event: Event) => {
void this.handleLeaveLobby(event as CustomEvent);
});
document.addEventListener("kick-player", this.handleKickPlayer.bind(this));
document.addEventListener(
"update-game-config",
this.handleUpdateGameConfig.bind(this),
);
document.addEventListener("update-game-config", (event: Event) => {
this.handleUpdateGameConfig(event as CustomEvent);
});
document.addEventListener("leave-lobby", (event: CustomEvent<{ lobby: string | number; cause?: string }>) => {
void this.handleLeaveLobby(event);
});
document.addEventListener("kick-player", this.handleKickPlayer.bind(this));
document.addEventListener("update-game-config", (event: CustomEvent<{ config: unknown }>) => {
this.handleUpdateGameConfig(event);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/Main.ts` around lines 314 - 320, Avoid unsafe Event-to-CustomEvent
casts by typing the event parameter in the listeners: change the "leave-lobby"
listener to supply a callback signature that accepts CustomEvent<{ lobby: string
| number; cause?: string }> (or simply omit the parameter since handleLeaveLobby
doesn't use it) and invoke this.handleLeaveLobby with that typed event; change
the "update-game-config" listener to accept CustomEvent<{ config: unknown }> and
pass that typed event into handleUpdateGameConfig so the handler can safely
access event.detail.config; keep the "kick-player" listener as-is (it already
binds this) but ensure any handlers that read event.detail use appropriately
typed CustomEvent parameters (refer to handleLeaveLobby and
handleUpdateGameConfig for the exact handler names).

@github-project-automation github-project-automation bot moved this from Final Review to Development in OpenFront Release Management Apr 11, 2026
@nicolas-machugounot nicolas-machugounot changed the title feat: Refine email linking UX and i18n for PR C #3622 feat: Refine email linking UX and i18n#3622 Apr 11, 2026
@ryanbarlow97
Copy link
Copy Markdown
Contributor

Looks like build + tests are failing, and you need to make sure to update the description, also you comitted the pr.md's which you used to prompt AI for..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Development

Development

Successfully merging this pull request may close these issues.

2 participants