Skip to content

Improved OTP input styles in Portal#25040

Merged
kevinansfield merged 10 commits intomainfrom
otp-input-field-styling
Oct 3, 2025
Merged

Improved OTP input styles in Portal#25040
kevinansfield merged 10 commits intomainfrom
otp-input-field-styling

Conversation

@peterzimon
Copy link
Contributor

ref https://linear.app/ghost/issue/PROD-2706/improve-the-design-of-the-otc-input-original-component-not-input-otp

  • The current styling of the OTP field in Portal was not following common visual and UX standards.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 2, 2025

Walkthrough

Replaces the OTC InputField with a native numeric input and adds OTP-specific presentational styles. handleInputChange now strips non-digits for the OTC field; doVerifyOTC updates an OTC error message. InputField no longer handles the input-otc case. MagicLinkPage rendering and tests were updated to reflect the new input and simplified error/placeholder behavior. Many portal locale files rename the translation key from "Enter code below" to "Enter code above". No exported APIs or public signatures were changed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

Possibly related PRs

Suggested labels

affects:i18n

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly describes the primary change to the OTP input styling within the Portal component and clearly indicates which part of the application is affected.
Description Check ✅ Passed The description directly references the relevant issue and explains that the current OTP field styling did not meet visual and UX standards, which aligns with the changes made in the pull request.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch otp-input-field-styling

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.

@github-actions github-actions bot added the community [triage] Community features and bugs label Oct 2, 2025
Copy link
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: 2

🧹 Nitpick comments (1)
apps/portal/src/components/pages/MagicLinkPage.js (1)

194-207: Remove unused label attribute from native input.

Line 203 includes a label attribute, which is not a standard HTML attribute for input elements. This appears to be leftover from the previous InputField component implementation and should be removed for cleaner markup.

Apply this diff:

                         <input
                             id={`input-${OTC_FIELD_NAME}`}
                             className='gh-portal-input'
                             name={OTC_FIELD_NAME}
                             type="text"
                             value={this.state.otc}
                             placeholder="••••••"
                             inputMode="numeric"
                             pattern="[0-9]*"
-                            label={t('Code')}
                             autoFocus={false}
                             maxLength={6}
                             onChange={e => this.handleInputChange(e, {name: OTC_FIELD_NAME})}
                         />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 409352d and 1719a23.

📒 Files selected for processing (2)
  • apps/portal/src/components/Frame.styles.js (1 hunks)
  • apps/portal/src/components/pages/MagicLinkPage.js (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: E2E Tests
🔇 Additional comments (5)
apps/portal/src/components/Frame.styles.js (4)

767-772: LGTM! Clean container layout.

The flex column layout with centered alignment is appropriate for the OTP input block.


774-807: Well-designed visual digit separators.

The gradient-based approach to create visual separators between OTP digits is clever and avoids the need for multiple input fields. The math for positioning (16.666%, 33.333%, etc.) correctly divides the space for 6 digits.


809-824: LGTM! Input styling correctly aligned with container.

The clip-path technique combined with letter-spacing creates the desired visual effect where each digit appears in its designated section. The math checks out: 21ch input width minus 3ch clip equals 18ch visible content, fitting within the 18.25ch container.


826-833: LGTM! Error styling consistent with design system.

The error message styling appropriately uses the design system's error color and provides adequate spacing from the input field.

apps/portal/src/components/pages/MagicLinkPage.js (1)

160-175: LGTM! Robust numeric input enforcement.

The regex-based filtering (/[^0-9]/g) effectively restricts the OTC field to numeric input only, complementing the HTML attributes (inputMode="numeric" and pattern="[0-9]*"). This provides a good defense-in-depth approach.

Copy link
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: 0

🧹 Nitpick comments (1)
apps/portal/src/components/pages/MagicLinkPage.js (1)

193-206: Consider adding a visible label for better accessibility.

While aria-label provides screen reader support, a visible <label> element would improve accessibility and UX by:

  • Making the input purpose clear to all users (not just screen reader users)
  • Providing a larger clickable target to focus the input
  • Following common form best practices

If you want to add a visible label, apply this diff:

                     <div className={`gh-portal-otp-field-container ${errors.otc && 'error'}`}>
+                        <label htmlFor={`input-${OTC_FIELD_NAME}`} className="gh-portal-input-label">
+                            {t('Code')}
+                        </label>
                         <input
                             id={`input-${OTC_FIELD_NAME}`}
                             className='gh-portal-input'
                             name={OTC_FIELD_NAME}
                             type="text"
                             value={this.state.otc}
                             placeholder="••••••"
                             inputMode="numeric"
                             pattern="[0-9]*"
                             aria-label={t('Code')}
                             autoFocus={false}
                             maxLength={6}
                             onChange={e => this.handleInputChange(e, {name: OTC_FIELD_NAME})}
                         />
                     </div>

Alternatively, if the label should be visually hidden but accessible, you can use a .sr-only utility class (ensure it exists in your styles).

Based on learnings (past review comments indicated this was a concern).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1719a23 and 28582bf.

📒 Files selected for processing (2)
  • apps/portal/src/components/common/InputField.js (0 hunks)
  • apps/portal/src/components/pages/MagicLinkPage.js (2 hunks)
💤 Files with no reviewable changes (1)
  • apps/portal/src/components/common/InputField.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Unit tests (Node 22.13.1)
  • GitHub Check: Build & Push
🔇 Additional comments (3)
apps/portal/src/components/pages/MagicLinkPage.js (3)

160-175: LGTM! Numeric validation correctly enforced.

The input handler properly strips non-digit characters for the OTC field while preserving original values for other fields. The implementation is clean and correct.


185-185: Verify if TODO comment is still relevant.

The TODO comment mentions "action implementation TBD" for the OTC verification. Since the OTC flow appears functional (with verifyOTC:running and verifyOTC:failed actions being checked), can this TODO be removed, or does it refer to future enhancements?


208-211: LGTM! Error conditional is correct.

The error message now displays correctly when errors.otc exists. This fixes the inverted conditional issue from the previous review.

Copy link
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: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6cf91e5 and 86195de.

📒 Files selected for processing (1)
  • apps/portal/src/components/pages/MagicLinkPage.js (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Inspect Docker Image
  • GitHub Check: E2E Tests
  • GitHub Check: Unit tests (Node 22.13.1)
🔇 Additional comments (4)
apps/portal/src/components/pages/MagicLinkPage.js (4)

160-175: LGTM! Numeric input constraint implemented correctly.

The numeric-only constraint for the OTC field is properly implemented using regex to strip non-digit characters, while preserving the original behavior for other fields.


185-185: Clarify or remove the TODO comment.

The TODO comment mentions "action implementation TBD", but the code already implements the verify OTC action. Is this TODO still relevant, or can it be removed?

If the action implementation is complete, please remove this comment. Otherwise, clarify what specific action implementation is still pending.


208-211: Error display logic is now correct.

The conditional {errors.otc && correctly ensures the error message only renders when an error exists. This addresses the previous critical issue that was flagged.


193-206: Accessibility: aria-label is acceptable for this use case.

The input now uses aria-label={t('Code')} (line 202), which provides the necessary semantic information for screen readers. While a visible <label> element is generally preferred, using aria-label is acceptable here since the design doesn't include a visible label for the OTP input.

Copy link
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: 0

♻️ Duplicate comments (3)
apps/portal/src/components/pages/MagicLinkPage.js (3)

193-205: Verify accessibility approach meets requirements.

The input uses aria-label instead of a <label> element. A previous review comment recommended adding a proper <label htmlFor={...}> element for better screen reader support. While aria-label provides accessibility, a visible or visually-hidden <label> element is generally preferred for form inputs.

Consider adding a label element as suggested in the previous review, or confirm that aria-label alone meets your accessibility requirements.

Based on previous review comments.


185-185: Clarify if TODO comment is still relevant.

A previous review comment asked whether this TODO should be removed now that the new styling is in place, or if it should remain as a note for future component migration.

Please clarify the intent and remove or update the comment accordingly.

Based on previous review comments.


191-210: Verify InputField cleanup was completed.

A previous review comment requested cleanup of OTC-related code in InputField.js now that this component no longer uses <InputField> for the OTC input. Please confirm that the cleanup mentioned in the earlier comment has been addressed.

Based on previous review comments.

🧹 Nitpick comments (1)
apps/portal/src/components/pages/MagicLinkPage.js (1)

193-205: Consider adding a placeholder for better UX.

The native input lacks a placeholder attribute, which could help users understand the expected format. OTP inputs typically show visual indicators like "••••••" or "123456" to convey the 6-digit format.

Apply this diff to add a placeholder:

                        <input
                            id={`input-${OTC_FIELD_NAME}`}
                            className={`gh-portal-input ${errors.otc ? 'error' : ''}`}
                            name={OTC_FIELD_NAME}
                            type="text"
                            value={this.state.otc}
+                            placeholder="••••••"
                            inputMode="numeric"
                            pattern="[0-9]*"
                            aria-label={t('Code')}
                            autoFocus={false}
                            maxLength={6}
                            onChange={e => this.handleInputChange(e, {name: OTC_FIELD_NAME})}
                        />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d01d0f3 and d500bd0.

📒 Files selected for processing (2)
  • apps/portal/src/components/pages/MagicLinkPage.js (3 hunks)
  • apps/portal/src/components/pages/MagicLinkPage.test.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/portal/src/components/pages/MagicLinkPage.test.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Unit tests (Node 22.13.1)
  • GitHub Check: Build & Push
🔇 Additional comments (3)
apps/portal/src/components/pages/MagicLinkPage.js (3)

145-145: LGTM! Error message correctly reflects new layout.

The change from "Enter code below" to "Enter code above" accurately reflects the updated UI structure where the input field now appears above the error message.


162-174: LGTM! Numeric input enforcement is well-implemented.

The logic correctly strips non-numeric characters for the OTC field while preserving the original value for other fields. This aligns with the input constraints defined in the form (inputMode="numeric", pattern="[0-9]*").


207-210: LGTM! Error conditional is now correct.

The error display logic correctly shows the error message only when errors.otc is truthy. This addresses the critical issue flagged in previous reviews where the conditional was inverted.

Copy link
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: 12

🧹 Nitpick comments (1)
ghost/i18n/locales/pa/portal.json (1)

69-69: Add the Punjabi translation for the renamed string

The key moved to “Enter code above” but the value is still empty, so Punjabi users will see the English fallback. Please supply the translation to keep the locale complete.

-    "Enter code above": "",
+    "Enter code above": "ਉੱਪਰ ਕੋਡ ਦਰਜ ਕਰੋ",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a64ec4 and 729f5a3.

📒 Files selected for processing (63)
  • ghost/i18n/locales/af/portal.json (1 hunks)
  • ghost/i18n/locales/ar/portal.json (1 hunks)
  • ghost/i18n/locales/bg/portal.json (1 hunks)
  • ghost/i18n/locales/bn/portal.json (1 hunks)
  • ghost/i18n/locales/bs/portal.json (1 hunks)
  • ghost/i18n/locales/ca/portal.json (1 hunks)
  • ghost/i18n/locales/context.json (2 hunks)
  • ghost/i18n/locales/cs/portal.json (1 hunks)
  • ghost/i18n/locales/da/portal.json (1 hunks)
  • ghost/i18n/locales/de-CH/portal.json (1 hunks)
  • ghost/i18n/locales/de/portal.json (1 hunks)
  • ghost/i18n/locales/el/portal.json (1 hunks)
  • ghost/i18n/locales/en/portal.json (1 hunks)
  • ghost/i18n/locales/eo/portal.json (1 hunks)
  • ghost/i18n/locales/es/portal.json (1 hunks)
  • ghost/i18n/locales/et/portal.json (1 hunks)
  • ghost/i18n/locales/eu/portal.json (1 hunks)
  • ghost/i18n/locales/fa/portal.json (1 hunks)
  • ghost/i18n/locales/fi/portal.json (1 hunks)
  • ghost/i18n/locales/fr/portal.json (1 hunks)
  • ghost/i18n/locales/gd/portal.json (1 hunks)
  • ghost/i18n/locales/he/portal.json (1 hunks)
  • ghost/i18n/locales/hi/portal.json (1 hunks)
  • ghost/i18n/locales/hr/portal.json (1 hunks)
  • ghost/i18n/locales/hu/portal.json (1 hunks)
  • ghost/i18n/locales/id/portal.json (1 hunks)
  • ghost/i18n/locales/is/portal.json (1 hunks)
  • ghost/i18n/locales/it/portal.json (1 hunks)
  • ghost/i18n/locales/ja/portal.json (1 hunks)
  • ghost/i18n/locales/ko/portal.json (1 hunks)
  • ghost/i18n/locales/kz/portal.json (1 hunks)
  • ghost/i18n/locales/lt/portal.json (1 hunks)
  • ghost/i18n/locales/lv/portal.json (1 hunks)
  • ghost/i18n/locales/mk/portal.json (1 hunks)
  • ghost/i18n/locales/mn/portal.json (1 hunks)
  • ghost/i18n/locales/ms/portal.json (1 hunks)
  • ghost/i18n/locales/nb/portal.json (1 hunks)
  • ghost/i18n/locales/ne/portal.json (1 hunks)
  • ghost/i18n/locales/nl/portal.json (1 hunks)
  • ghost/i18n/locales/nn/portal.json (1 hunks)
  • ghost/i18n/locales/pa/portal.json (1 hunks)
  • ghost/i18n/locales/pl/portal.json (1 hunks)
  • ghost/i18n/locales/pt-BR/portal.json (1 hunks)
  • ghost/i18n/locales/pt/portal.json (1 hunks)
  • ghost/i18n/locales/ro/portal.json (1 hunks)
  • ghost/i18n/locales/ru/portal.json (1 hunks)
  • ghost/i18n/locales/si/portal.json (1 hunks)
  • ghost/i18n/locales/sk/portal.json (1 hunks)
  • ghost/i18n/locales/sl/portal.json (1 hunks)
  • ghost/i18n/locales/sq/portal.json (1 hunks)
  • ghost/i18n/locales/sr-Cyrl/portal.json (1 hunks)
  • ghost/i18n/locales/sr/portal.json (1 hunks)
  • ghost/i18n/locales/sv/portal.json (1 hunks)
  • ghost/i18n/locales/sw/portal.json (1 hunks)
  • ghost/i18n/locales/ta/portal.json (1 hunks)
  • ghost/i18n/locales/th/portal.json (1 hunks)
  • ghost/i18n/locales/tr/portal.json (1 hunks)
  • ghost/i18n/locales/uk/portal.json (1 hunks)
  • ghost/i18n/locales/ur/portal.json (1 hunks)
  • ghost/i18n/locales/uz/portal.json (1 hunks)
  • ghost/i18n/locales/vi/portal.json (1 hunks)
  • ghost/i18n/locales/zh-Hant/portal.json (1 hunks)
  • ghost/i18n/locales/zh/portal.json (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • ghost/i18n/locales/zh/portal.json
  • ghost/i18n/locales/ja/portal.json
  • ghost/i18n/locales/da/portal.json
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-02-15T16:00:03.347Z
Learnt from: cathysarisky
PR: TryGhost/Ghost#21982
File: ghost/i18n/locales/hu/portal.json:207-207
Timestamp: 2025-02-15T16:00:03.347Z
Learning: In Hungarian translations for Ghost, ensure that all variables like {{title}} are preserved in the translated strings and not omitted.

Applied to files:

  • ghost/i18n/locales/hu/portal.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Inspect Docker Image
  • GitHub Check: Setup
  • GitHub Check: Setup
🔇 Additional comments (11)
ghost/i18n/locales/fi/portal.json (1)

69-69: String rename looks good

Key rename matches the updated copy, and keeping the value empty preserves existing fallback behavior.

ghost/i18n/locales/sl/portal.json (1)

69-69: Translation key update looks good

Key rename to “Enter code above” aligns with the new OTP UI copy and keeps fallback behavior intact.

ghost/i18n/locales/nb/portal.json (1)

69-69: Provide a translation for the updated key.

The new "Enter code above" key is still mapped to an empty string, so Norwegian users will see the fallback text (or nothing, depending on runtime behavior). Please supply the localized string to keep parity with the rest of the locale changes.

⛔ Skipped due to learnings
Learnt from: cathysarisky
PR: TryGhost/Ghost#0
File: :0-0
Timestamp: 2025-05-20T21:08:21.026Z
Learning: In the Ghost project, translation files (ghost/i18n/locales/*/*.json) commonly have blank values for translations, and this is normal behavior that should not be flagged in reviews. These empty translations will be filled in separate PRs.
Learnt from: cathysarisky
PR: TryGhost/Ghost#0
File: :0-0
Timestamp: 2025-01-29T15:27:29.391Z
Learning: When reviewing translation files in ghost/i18n/locales, translators should only modify the translated values and must not change the keys or placeholder styles (e.g., %%{status}%%, {site}, etc.) as they serve specific purposes like Mailgun string replacement.
ghost/i18n/locales/vi/portal.json (1)

69-69: Double-check we didn’t lose the Vietnamese string.

While renaming the key, the value stayed empty. Please confirm the old “Enter code below” translation wasn’t populated in production; if it was, carry it over here so we don’t ship a missing label.

ghost/i18n/locales/es/portal.json (1)

69-69: Key rename matches updated OTP prompt

Renaming the key keeps the Spanish bundle aligned with the new OTP copy without introducing translation regressions.

ghost/i18n/locales/en/portal.json (1)

69-69: OTP copy key aligned

The English locale now mirrors the new “Enter code above” prompt used in the UI; no issues spotted.

ghost/i18n/locales/nl/portal.json (1)

69-69: Locale key updated consistently

Dutch locale follows the shared OTP key rename and keeps existing fallback behavior intact.

ghost/i18n/locales/zh-Hant/portal.json (1)

69-69: OTP prompt key synced

Traditional Chinese pack now references the new OTP prompt key; everything else remains stable.

ghost/i18n/locales/de-CH/portal.json (1)

69-69: Key rename applied cleanly

The Swiss German locale correctly switches to the revised OTP key with no side effects.

ghost/i18n/locales/et/portal.json (1)

69-69: Updated OTP key looks good

Estonian strings now reference the unified “Enter code above” key; fallback behavior unchanged.

ghost/i18n/locales/de/portal.json (1)

69-69: German locale aligned with OTP copy

The German bundle adopts the new OTP prompt key in line with the Portal UI changes.

"Emails disabled": "E-posse afgeskakel",
"Ends {offerEndDate}": "Eindig {offerEndDate}",
"Enter code below": "",
"Enter code above": "",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Preserve the OTP helper copy

With an empty translation, Afrikaans users lose the OTP instruction. Please supply the localized wording.

-    "Enter code above": "",
+    "Enter code above": "Voer die kode hier bo in",
📝 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
"Enter code above": "",
"Enter code above": "Voer die kode hier bo in",
🤖 Prompt for AI Agents
ghost/i18n/locales/af/portal.json around line 69: the "Enter code above" key has
an empty value so Afrikaans users lose the OTP instruction; replace the empty
string with a proper Afrikaans translation such as "Voer bogenoemde kode in" (or
"Voer kode hierbo in"), preserving the JSON string format and punctuation.

"Emails disabled": "Имейлите са спрени",
"Ends {offerEndDate}": "До {offerEndDate}",
"Enter code below": "Въведете кода по-долу",
"Enter code above": "",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Restore the OTP prompt translation

Setting this key to an empty string will leave the OTP instruction blank in the BG locale. Please carry forward a proper translation so members still see guidance.

-    "Enter code above": "",
+    "Enter code above": "Въведете кода по-горе",
📝 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
"Enter code above": "",
"Enter code above": "Въведете кода по-горе",
🤖 Prompt for AI Agents
In ghost/i18n/locales/bg/portal.json around line 69 the "Enter code above" key
is set to an empty string; replace it with a proper Bulgarian translation such
as "Въведете кода по-горе" (or another approved BG phrasing) so the OTP
instruction is displayed to members, making sure to preserve the JSON key and
proper string quoting/escaping.

"Emails disabled": "Retpoŝtoj malŝaltitaj",
"Ends {offerEndDate}": "",
"Enter code below": "",
"Enter code above": "",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fill in the OTP prompt translation

Leaving this value empty suppresses the instruction in Esperanto. Please add the localized sentence.

-    "Enter code above": "",
+    "Enter code above": "Enigu la kodon supre",
📝 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
"Enter code above": "",
"Enter code above": "Enigu la kodon supre",
🤖 Prompt for AI Agents
In ghost/i18n/locales/eo/portal.json around line 69 the "Enter code above" key
has an empty value; provide the Esperanto translation for the OTP prompt by
replacing the empty string with an appropriate localized sentence (e.g., "Enmetu
la kodon supre" or a stylistically suitable variant) so the instruction is shown
to Esperanto users.

"Emails disabled": "ePostak desgaituta",
"Ends {offerEndDate}": "{offerEndDate}(e)an amaituko da",
"Enter code below": "",
"Enter code above": "",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Provide the Basque translation for this string.

Leaving the value empty means the Portal prompt renders as a blank string for the Basque locale, so members get no instruction text. Please supply the translated copy (e.g. Sartu kodea goian) or remove the entry to fall back to English.

🤖 Prompt for AI Agents
In ghost/i18n/locales/eu/portal.json at line 69 the value for "Enter code above"
is empty causing the Portal prompt to render blank for Basque users; fix by
replacing the empty string with the Basque translation (for example "Sartu kodea
goian") or remove the key entirely to fall back to the English string, then run
a quick JSON lint to ensure the file remains valid.

"Emails disabled": "ईमेल निष्क्रिय",
"Ends {offerEndDate}": "{offerEndDate} को समाप्त होता है",
"Enter code below": "",
"Enter code above": "",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid empty OTP prompt text

An empty value removes the OTP guidance for Hindi users. Please fill in the translated sentence.

-    "Enter code above": "",
+    "Enter code above": "ऊपर कोड दर्ज करें",
📝 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
"Enter code above": "",
"Enter code above": "ऊपर कोड दर्ज करें",
🤖 Prompt for AI Agents
In ghost/i18n/locales/hi/portal.json around line 69 the key "Enter code above"
has an empty string which removes the OTP prompt for Hindi users; replace the
empty value with an appropriate Hindi translation (e.g., a concise instruction
such as "ऊपर दिया गया कोड दर्ज करें" or another approved translation), ensuring
proper JSON string escaping and preserving surrounding punctuation and
formatting.

"Emails disabled": "Wysyłanie emaili zablokowane",
"Ends {offerEndDate}": "Kończy się {offerEndDate}",
"Enter code below": "",
"Enter code above": "",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Provide the Polish translation for “Enter code above”.

Leaving this string empty means Polish-speaking members won’t see the OTP instruction at all. Please supply the localized text (e.g. Wpisz kod powyżej) so the message renders correctly.

🤖 Prompt for AI Agents
In ghost/i18n/locales/pl/portal.json around line 69, the Polish translation for
the key "Enter code above" is empty; add the localized string so OTP
instructions display for Polish users — replace the empty value with the Polish
translation Wpisz kod powyżej (e.g. "Enter code above": "Wpisz kod powyżej")
ensuring proper JSON quoting and comma placement to keep the file valid.

"Emails disabled": "E-maily vypnuté",
"Ends {offerEndDate}": "Vyprší {offerEndDate}",
"Enter code below": "",
"Enter code above": "",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t blank out the OTP prompt

An empty value hides the OTP guidance for Slovak speakers. Please add the correct translation.

-    "Enter code above": "",
+    "Enter code above": "Zadajte kód vyššie",
📝 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
"Enter code above": "",
"Enter code above": "Zadajte kód vyššie",
🤖 Prompt for AI Agents
In ghost/i18n/locales/sk/portal.json around line 69, the "Enter code above"
value is blank which hides the OTP prompt for Slovak users; update the JSON
value to the Slovak translation "Zadajte kód uvedený vyššie" (replace the empty
string with that exact text) and ensure the file remains valid JSON (proper
quotes and trailing commas).

"Emails disabled": "Мејлови онемогућени",
"Ends {offerEndDate}": "Завршава се {offerEndDate}",
"Enter code below": "",
"Enter code above": "",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fill in the Serbian translation for the renamed key.

Leaving this value empty will render a blank prompt for sr-Cyrl members. Please supply the localized text (e.g. "Унесите код изнад").

-    "Enter code above": "",
+    "Enter code above": "Унесите код изнад",
📝 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
"Enter code above": "",
"Enter code above": "Унесите код изнад",
🤖 Prompt for AI Agents
In ghost/i18n/locales/sr-Cyrl/portal.json around line 69 the value for the key
"Enter code above" is empty, causing a blank prompt for Serbian Cyrillic users;
update the value to the Serbian translation by replacing the empty string with
the localized text (for example: Унесите код изнад) so the portal displays the
correct prompt.

"Emails disabled": "อีเมลถูกปิดใช้งาน",
"Ends {offerEndDate}": "สิ้นสุด {offerEndDate}",
"Enter code below": "",
"Enter code above": "",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Provide the Thai translation for “Enter code above”.

Leaving the value empty makes the Thai locale fall back to English, which is a regression versus the surrounding localized strings. Please translate it, e.g. กรอกรหัสด้านบน.

🤖 Prompt for AI Agents
In ghost/i18n/locales/th/portal.json around line 69, the value for "Enter code
above" is empty causing fallback to English; replace the empty string with the
Thai translation, e.g. กรอกรหัสด้านบน, so the locale has a proper localized
string consistent with surrounding entries.

"Emails disabled": "Електронна пошта вимкнена",
"Ends {offerEndDate}": "Закунчується {offerEndDate}",
"Enter code below": "",
"Enter code above": "",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Provide a translation instead of an empty string

With an empty value the OTP helper text disappears for Ukrainian readers. Please add the localized copy.

-    "Enter code above": "",
+    "Enter code above": "Введіть код вище",
📝 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
"Enter code above": "",
"Enter code above": "Введіть код вище",
🤖 Prompt for AI Agents
In ghost/i18n/locales/uk/portal.json around line 69 the value for the "Enter
code above" key is an empty string which causes the OTP helper text to disappear
for Ukrainian users; replace the empty value with the appropriate Ukrainian
translation (e.g., a concise localized string conveying "Enter code above") so
the helper text is visible to Ukrainian readers.

@kevinansfield kevinansfield merged commit 64b03cb into main Oct 3, 2025
28 checks passed
@kevinansfield kevinansfield deleted the otp-input-field-styling branch October 3, 2025 10:46
kevinansfield added a commit that referenced this pull request Oct 3, 2025
This reverts commit 64b03cb.

- holding off on merge until after scheduled release
kevinansfield added a commit that referenced this pull request Oct 3, 2025
This reverts commit 64b03cb.

- holding off on merge until after scheduled release
kevinansfield added a commit that referenced this pull request Oct 6, 2025
ref https://linear.app/ghost/issue/PROD-2706/

- The current styling of the OTP field in Portal was not following common visual and UX standards.

---------

Co-authored-by: Kevin Ansfield <kevin@ghost.org>
kevinansfield added a commit that referenced this pull request Oct 6, 2025
ref https://linear.app/ghost/issue/PROD-2706/

- The current styling of the OTP field in Portal was not following common visual and UX standards.

---------

Co-authored-by: Kevin Ansfield <kevin@ghost.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community [triage] Community features and bugs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants