-
Notifications
You must be signed in to change notification settings - Fork 2.9k
[WEB-5430] feat: allow users to change email #8120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Linked to Plane Work Item(s) This comment was auto-generated by Plane |
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds a two-step email-update flow: new API routes and view logic to generate and verify cache-backed magic codes, Celery tasks and an HTML confirmation template, a React ChangeEmailModal with frontend service calls, rate-limiting throttle, and i18n strings across many locales. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser
participant Frontend as "ChangeEmailModal"
participant API as "Backend API"
participant Cache as "Redis/Cache"
participant Queue as "Email Queue"
participant Mail as "Mail Server"
rect rgb(240,248,255)
Note over Browser,Frontend: Step 1 — request verification code
Browser->>Frontend: enter new_email
Frontend->>API: POST /users/me/email/generate-code {email}
API->>API: validate email & throttle
API->>Cache: set magic_email_update_<user_id>_<email> = token
API->>Queue: enqueue send_email_update_magic_code(email, token)
API-->>Frontend: 200 OK
end
rect rgb(245,255,240)
Note over Browser,Frontend: Step 2 — submit code
Browser->>Frontend: submit {email, code}
Frontend->>API: PATCH /users/me/email {email, code}
API->>Cache: get magic_email_update_<user_id>_<email>
Cache-->>API: token present / absent
API->>API: final uniqueness check, update user.email, set is_email_verified=false, logout/invalidate session
API->>Queue: enqueue send_email_update_confirmation(email)
API-->>Frontend: 200 OK (updated user)
end
Queue->>Mail: deliver emails
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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 (2)
apps/api/plane/app/views/user/base.py (2)
91-118: Consider extracting duplicate email validation logic.The email validation logic (required check, format validation, same-email check, duplicate check) is duplicated between
generate_email_verification_codeandupdate_email. This violates the DRY principle.Consider extracting a helper method:
def _validate_new_email(self, user, new_email): """Validate new email address for update.""" if not new_email: return {"error": "Email is required"} try: validate_email(new_email) except Exception: return {"error": "Invalid email format"} if new_email == user.email: return {"error": "New email must be different from current email"} if User.objects.filter(email=new_email).exclude(id=user.id).exists(): return {"error": "An account with this email already exists"} return None # No errorThen use it in both methods:
error = self._validate_new_email(user, new_email) if error: return Response(error, status=status.HTTP_400_BAD_REQUEST)Also applies to: 152-185
123-123: Consider extracting Redis key construction to avoid coupling.The Redis key construction is split between two methods with implicit knowledge that
MagicCodeProviderprepends "magic_". This creates tight coupling and fragility.Consider extracting key construction:
def _get_email_update_redis_key(self, email, include_magic_prefix=False): """Get Redis key for email update verification.""" key = f"email_update_{email}" if include_magic_prefix: return f"magic_{key}" return keyThen use:
- Line 123:
key=self._get_email_update_redis_key(new_email)- Line 190:
redis_key = self._get_email_update_redis_key(new_email, include_magic_prefix=True)Also applies to: 190-190
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/api/plane/app/urls/user.py(1 hunks)apps/api/plane/app/views/user/base.py(3 hunks)apps/api/plane/bgtasks/user_email_update_task.py(1 hunks)apps/api/templates/emails/user/email_updated.html(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
apps/api/plane/bgtasks/user_email_update_task.py (2)
apps/api/plane/db/models/user.py (1)
User(38-164)apps/api/plane/utils/exception_logger.py (1)
log_exception(9-20)
apps/api/plane/app/views/user/base.py (4)
apps/api/plane/authentication/adapter/error.py (2)
AuthenticationException(72-87)get_error_dict(82-87)apps/api/plane/bgtasks/user_email_update_task.py (2)
send_email_update_magic_code(19-60)send_email_update_confirmation(64-111)apps/api/plane/db/models/user.py (1)
User(38-164)apps/api/plane/app/serializers/user.py (1)
UserMeSerializer(59-82)
apps/api/plane/app/urls/user.py (1)
apps/api/plane/app/views/user/base.py (1)
UserEndpoint(56-339)
⏰ 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: Agent
- GitHub Check: CodeQL analysis (python)
- GitHub Check: Analyze (javascript)
🔇 Additional comments (8)
apps/api/plane/app/urls/user.py (1)
33-42: LGTM!The new URL patterns for email update functionality are well-structured and follow the existing REST conventions.
apps/api/templates/emails/user/email_updated.html (3)
1-267: LGTM!The email template follows HTML email best practices with proper DOCTYPE, MSO compatibility, and responsive design.
537-551: LGTM!The
{{email}}placeholder is correctly used in both the main content and footer sections. Django's template rendering will properly substitute this value.Also applies to: 1028-1033
375-383: Verify logo visibility on white background.The logo URL references "new-logo-white.png" which appears to be a white-colored image displayed on the white email background (confirmed at line 297:
style="background-color: #ffffff"). No alternative logo variants or design rationale were found in the codebase. Consider using a darker logo variant or adding a subtle background container to ensure readability.apps/api/plane/bgtasks/user_email_update_task.py (2)
18-60: LGTM with observation.The task correctly reuses the existing magic sign-in template for email verification codes. Error handling logs exceptions but returns
Noneregardless of success or failure. This is acceptable for background tasks, though consider whether the calling code needs to know about failures.
63-111: LGTM!The confirmation email task correctly uses the new template and follows the same pattern as the verification code task. Logging includes the recipient email for traceability.
apps/api/plane/app/views/user/base.py (2)
3-4: LGTM!All new imports are correctly organized and necessary for the email update functionality.
Also applies to: 10-13, 45-51, 53-53
220-241: LGTM!The email update logic is well-implemented with several good security practices:
- Final duplicate-check prevents TOCTOU race conditions (line 221)
- Correctly resets
is_email_verifiedtoFalse(line 230)- Logs out the user to force re-authentication with the new email (line 234)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces functionality to allow users to update their email address through a verification flow using magic codes sent via email.
- Implements a two-step email update process: code generation and verification
- Adds email templates for verification codes and confirmation messages
- Automatically logs out users after successful email update for security
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 11 comments.
| File | Description |
|---|---|
| apps/api/templates/emails/user/email_updated.html | New HTML email template to confirm successful email address update |
| apps/api/plane/bgtasks/user_email_update_task.py | Background tasks for sending verification codes and confirmation emails |
| apps/api/plane/app/views/user/base.py | View methods to generate verification codes and process email updates with validation |
| apps/api/plane/app/urls/user.py | New API endpoints for email verification code generation and email update |
Comments suppressed due to low confidence (1)
apps/api/plane/bgtasks/user_email_update_task.py:13
- Import of 'User' is not used.
from plane.db.models import User
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this 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
♻️ Duplicate comments (3)
apps/api/plane/bgtasks/user_email_update_task.py (1)
62-110: Same exception handling concern applies here.Like the previous task, consider letting exceptions propagate for Celery's retry mechanism.
apps/api/plane/app/views/user/base.py (2)
217-229: Critical bug: Old email address is never notified of the email change.On line 218,
user.emailis updated tonew_email, and then saved on line 221. However, line 229 attempts to send a notification touser.email, which at that point contains the new email address, not the old one. This means the old email address is never notified of this security-sensitive change.Apply this diff to fix:
+ # Save the old email before updating + old_email = user.email + # Update the email - this updates the existing user record without creating a new user user.email = new_email # Reset email verification status when email is changed user.is_email_verified = False user.save() # Logout the user logout(request) # Send confirmation email to the new email address send_email_update_confirmation.delay(new_email) # send the email to the old email address - send_email_update_confirmation.delay(user.email) + send_email_update_confirmation.delay(old_email)
102-108: Catch specificValidationErrorinstead of broadException.The
validate_emailfunction raisesValidationError, not a genericException. CatchingExceptioncould hide programming errors.Apply this diff:
+from django.core.exceptions import ValidationError + # Validate email format try: validate_email(new_email) -except Exception: +except ValidationError: return Response( {"error": "Invalid email format"}, status=status.HTTP_400_BAD_REQUEST, )
🧹 Nitpick comments (2)
apps/api/plane/bgtasks/user_email_update_task.py (1)
57-59: Consider letting exceptions propagate for Celery retry behavior.The broad
except Exceptionblock catches all errors and always returnsNone, which prevents Celery's automatic retry mechanism from working. For transient failures (like SMTP connection issues), you may want to let the exception propagate so Celery can retry the task.Consider this pattern:
except Exception as e: log_exception(e) - return + raise # Allow Celery to retry on failureAlternatively, catch specific exceptions that should be retried vs. those that shouldn't:
from smtplib import SMTPException try: # ... email sending code ... except SMTPException as e: log_exception(e) raise # Retry on SMTP issues except Exception as e: log_exception(e) return # Don't retry on other errorsapps/api/plane/app/views/user/base.py (1)
203-208: Catch specific exceptions for better error handling.The broad
except Exceptionblock on line 203 catches all errors including JSON decode errors, Redis connection issues, and unexpected programming errors. This makes debugging difficult and provides users with a generic error message.Consider catching specific exceptions:
import json from redis.exceptions import RedisError try: data = json.loads(ri.get(redis_key)) stored_token = data.get("token") # ... validation ... except json.JSONDecodeError as e: logger.error(f"Failed to parse Redis data: {str(e)}", exc_info=True) return Response( {"error": "Invalid verification data. Please request a new code."}, status=status.HTTP_400_BAD_REQUEST, ) except RedisError as e: logger.error(f"Redis connection error: {str(e)}", exc_info=True) return Response( {"error": "Service temporarily unavailable. Please try again."}, status=status.HTTP_503_SERVICE_UNAVAILABLE, )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/api/plane/app/views/user/base.py(3 hunks)apps/api/plane/bgtasks/user_email_update_task.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/api/plane/bgtasks/user_email_update_task.py (1)
apps/api/plane/utils/exception_logger.py (1)
log_exception(9-20)
apps/api/plane/app/views/user/base.py (4)
apps/api/plane/authentication/adapter/error.py (2)
AuthenticationException(72-87)get_error_dict(82-87)apps/api/plane/bgtasks/user_email_update_task.py (2)
send_email_update_magic_code(18-59)send_email_update_confirmation(63-110)apps/api/plane/db/models/user.py (1)
User(38-164)apps/api/plane/app/serializers/user.py (1)
UserMeSerializer(59-82)
⏰ 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: Analyze (javascript)
🔇 Additional comments (3)
apps/api/plane/bgtasks/user_email_update_task.py (1)
1-14: LGTM!Imports are appropriate and there are no unused dependencies.
apps/api/plane/app/views/user/base.py (2)
3-4: LGTM!New imports are appropriate for the email update functionality.
Also applies to: 10-13, 45-50, 53-53
126-156: LGTM!The email verification code generation flow is well-structured with proper validation and error handling.
There was a problem hiding this 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 (3)
apps/web/core/components/profile/form.tsx (1)
20-20: SMTP‑gated change‑email entrypoint looks good; consider minor UX/i18n polishThe wiring for the change‑email flow (instance config read,
isSMTPConfiguredguard, modal state, and button handler) is consistent and safe — the modal only becomes reachable when SMTP is configured, and the email field itself stays read‑only.You might optionally:
- Localize the
"Change Email"label through the existing translation layer to match nearby labels.- Consider reusing the shared
Buttoncomponent here for consistency with other actions in this form (if design allows).Also applies to: 25-25, 54-55, 84-87, 165-166, 365-373
apps/web/core/services/user.service.ts (1)
14-15: Email-check and verification service methods are consistent; minor DRY opportunityThe new
checkEmail,generateEmailCode, andverifyEmailCodemethods are consistent with the rest of this service (URL shapes, CSRF header usage for/auth/*, and error propagation viaerror?.response?.data). The typedIEmailCheckResponseresult forcheckEmailis a good addition.If you want to tighten things up later, you could:
- Extract the common
then(response => response?.data).catch(...)pattern into a small helper to reduce repetition.- Type the payloads/return types for
generateEmailCodeandverifyEmailCodemore specifically once the backend response contracts are stable.Also applies to: 262-293
apps/web/core/components/core/modals/change-email-modal.tsx (1)
1-240: Change-email flow is solid; a couple of small UX/robustness tweaks to considerThe two-step flow, error handling, and cleanup on close are all wired correctly and align with the new service methods. Nice job keeping the form logic straightforward and resetting state via
handleClose.A few small improvements you could make without changing the core behavior:
- When verifying the code (step 2), trim the submitted value before sending it so accidental whitespace doesn’t cause spurious “invalid code” errors:
- await userService.verifyEmailCode({ email: formData.email, code: formData.code });
- await userService.verifyEmailCode({ email: formData.email, code: formData.code.trim() });
The primary button label uses
"Sending"for both steps. If you want clearer feedback, you could vary the loading text by step (e.g.,"Sending code"vs"Verifying"), though this is purely UX polish.In the step‑2
catch, you’re already showing a fallback message. If the backend also returns structurederror_code/error_messagelike in step 1, you might eventually want to route those through the sameauthErrorHandlerfor consistency, but it’s fine to keep it simpler for now.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/web/core/components/core/modals/change-email-modal.tsx(1 hunks)apps/web/core/components/profile/form.tsx(5 hunks)apps/web/core/services/user.service.ts(2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-14T11:22:43.964Z
Learnt from: gakshita
Repo: makeplane/plane PR: 7393
File: apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx:104-104
Timestamp: 2025-07-14T11:22:43.964Z
Learning: In the Plane project's SMTP configuration implementation, the email configuration form (email-config-form.tsx) hardcodes ENABLE_SMTP to "1" in form submission because the form is only rendered when SMTP is enabled. The enable/disable functionality is managed at the page level (page.tsx) with a toggle, and the form only handles configuration details when SMTP is already enabled.
Applied to files:
apps/web/core/components/profile/form.tsx
🧬 Code graph analysis (2)
apps/web/core/components/core/modals/change-email-modal.tsx (1)
packages/propel/src/toast/toast.tsx (1)
setToast(199-219)
apps/web/core/components/profile/form.tsx (1)
apps/web/core/components/core/modals/change-email-modal.tsx (1)
ChangeEmailModal(28-240)
⏰ 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: Build and lint web apps
- GitHub Check: Analyze (javascript)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the final PR Bugbot will review for you during this billing cycle
Your free Bugbot reviews will reset on December 20
Details
Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
There was a problem hiding this 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
♻️ Duplicate comments (3)
apps/api/plane/app/views/user/base.py (3)
229-240: Handle potential race condition with IntegrityError.There's a race condition between checking email availability (line 230) and saving the user (line 240). Although the database has a unique constraint on email, the code should handle the potential
IntegrityErrorexception that could occur if another user claims the email between the check and save.Apply this diff to handle the race condition:
+ from django.db import IntegrityError + # Final check: ensure email is still available (might have been taken between code generation and update) if User.objects.filter(email=new_email).exclude(id=user.id).exists(): return Response( {"error": "An account with this email already exists"}, status=status.HTTP_400_BAD_REQUEST, ) old_email = user.email - # Update the email - this updates the existing user record without creating a new user - user.email = new_email - # Reset email verification status when email is changed - user.is_email_verified = False - user.save() + + try: + # Update the email - this updates the existing user record without creating a new user + user.email = new_email + # Reset email verification status when email is changed + user.is_email_verified = False + user.save() + except IntegrityError: + return Response( + {"error": "An account with this email already exists"}, + status=status.HTTP_400_BAD_REQUEST, + )
108-115: Catch specific exception type.The broad
except Exceptionclause can hide programming errors or unexpected issues. Thevalidate_emailfunction raisesValidationErrorfromdjango.core.exceptions.Apply this diff to catch the specific exception:
- try: - validate_email(new_email) - except Exception: - return Response( - {"error": "Invalid email format"}, - status=status.HTTP_400_BAD_REQUEST, - ) + from django.core.exceptions import ValidationError + + try: + validate_email(new_email) + except ValidationError: + return Response( + {"error": "Invalid email format"}, + status=status.HTTP_400_BAD_REQUEST, + )
170-175: Catch specific exception types.The broad
except Exceptionclause can hide programming errors or unexpected issues. Consider catching more specific exceptions related to cache operations and email sending, or at minimum log and re-raise unexpected exceptions.Apply this diff to improve exception handling:
- except Exception as e: - logger.error("Failed to generate verification code: %s", str(e), exc_info=True) - return Response( - {"error": "Failed to generate verification code. Please try again."}, - status=status.HTTP_400_BAD_REQUEST, - ) + except (ValueError, TypeError) as e: + logger.error("Failed to generate verification code: %s", str(e), exc_info=True) + return Response( + {"error": "Failed to generate verification code. Please try again."}, + status=status.HTTP_400_BAD_REQUEST, + ) + except Exception as e: + logger.error("Unexpected error in generate_email_verification_code: %s", str(e), exc_info=True) + raise
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/api/plane/app/views/user/base.py(4 hunks)apps/api/plane/authentication/rate_limit.py(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/api/plane/authentication/rate_limit.py (1)
apps/api/plane/authentication/adapter/error.py (2)
AuthenticationException(72-87)get_error_dict(82-87)
apps/api/plane/app/views/user/base.py (5)
apps/api/plane/bgtasks/user_email_update_task.py (2)
send_email_update_magic_code(18-59)send_email_update_confirmation(63-110)apps/api/plane/authentication/rate_limit.py (1)
EmailVerificationThrottle(27-43)apps/api/plane/api/views/base.py (1)
get_throttles(60-73)apps/api/plane/db/models/user.py (1)
User(38-164)apps/api/plane/app/serializers/user.py (1)
UserMeSerializer(59-82)
⏰ 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: Cursor Bugbot
- GitHub Check: Build and lint web apps
- GitHub Check: Analyze (javascript)
🔇 Additional comments (6)
apps/api/plane/authentication/rate_limit.py (2)
2-2: LGTM!The addition of
UserRateThrottleimport is necessary for the newEmailVerificationThrottleclass.
27-43: LGTM!The
EmailVerificationThrottleimplementation follows the existing throttle pattern correctly. The 3 requests per hour limit is reasonable for preventing abuse of the email verification flow while allowing legitimate use cases.apps/api/plane/app/views/user/base.py (4)
3-6: LGTM!All new imports are necessary for the email verification functionality and are properly utilized in the new methods.
Also applies to: 12-16, 48-52
63-69: LGTM!The selective application of
EmailVerificationThrottleto thegenerate_email_verification_codeaction is correct and ensures rate limiting is applied only where needed.
177-196: LGTM!The validation logic appropriately reuses the
_validate_new_emailhelper and includes proper checks for the verification code.
250-252: LGTM!Returning the updated user data via
UserMeSerializeris appropriate for the client to reflect the changes immediately.
| // store hooks | ||
| const { signOut } = useUser(); | ||
| // services | ||
| const authService = new AuthService(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this outside of the component.
| message: "Failed to sign out. Please try again.", | ||
| }) | ||
| ); | ||
| }; | ||
|
|
||
| const onSubmit = async (formData: TUniqueCodeValuesForm) => { | ||
| if (currentStep === "UNIQUE_CODE") { | ||
| // Step 2: Verify the code and update email | ||
| try { | ||
| await userService.verifyEmailCode({ email: formData.email, code: formData.code }); | ||
|
|
||
| setToast({ | ||
| type: TOAST_TYPE.SUCCESS, | ||
| title: "Success!", | ||
| message: "Email updated successfully. Please sign in again.", | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add these text to translations and then use it here.
- Added translation support for the change email modal, including titles, descriptions, and error messages. - Integrated the useTranslation hook for dynamic text rendering. - Updated form validation messages to utilize localized strings. - Enhanced user feedback with localized success and error toast messages. - Updated button labels and placeholders to reflect localization changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🧹 Nitpick comments (8)
packages/i18n/src/locales/zh-CN/translations.ts (1)
1531-1531: Use formal "您的" for consistency.Line 1531 uses "你的" (informal "your"), while the rest of the file consistently uses "您的" (formal "your") throughout account settings and user-facing text (e.g., lines 211, 409, 210).
Apply this diff:
- helper_text: "验证码已发送至你的新邮箱。", + helper_text: "验证码已发送至您的新邮箱。",packages/i18n/src/locales/tr-TR/translations.ts (1)
1532-1570: Change‑email modal Turkish copy is solid; consider minor consistency tweaksThe structure and keys look correct and consistent with the rest of the locale. Two small optional polish items you might consider:
- Align wording with the “code” flow: description currently mentions a “verification link” while the UI below talks about a “verification code” (
Doğrulama kodu yeni e-postanıza gönderildi.). You could make them consistent, e.g. change “bağlantısı” → “kodu”.- Reuse the existing localized unique-code placeholder from
auth.common.unique_code.placeholder("alır-atar-uçar") instead of the English"gets-sets-flys"so the example slug style stays localized and consistent within this file.Example diff:
- description: "Doğrulama bağlantısı almak için yeni bir e-posta adresi girin.", + description: "Doğrulama kodunu almak için yeni bir e-posta adresi girin.", ... - code: { - label: "Benzersiz kod", - placeholder: "gets-sets-flys", + code: { + label: "Benzersiz kod", + placeholder: "alır-atar-uçar",packages/i18n/src/locales/ro/translations.ts (1)
1540-1554: Standardize "email" terminology for consistency.The new translations use "e-mail" (hyphenated), but existing Romanian translations in this file use "Email" without a hyphen (see lines 24, 27-28, 200). For consistency across the Romanian locale, consider using the same form throughout.
Apply this diff to align with existing conventions:
- title: "Schimbă e-mailul", - description: "Introduceți o nouă adresă de e-mail pentru a primi un link de verificare.", + title: "Schimbă email-ul", + description: "Introduceți o nouă adresă de email pentru a primi un link de verificare.", toasts: { success_title: "Succes!", - success_message: "E-mail actualizat cu succes. Conectați-vă din nou.", + success_message: "Email actualizat cu succes. Conectați-vă din nou.", }, form: { email: { - label: "E-mail nou", - placeholder: "Introduceți e-mailul", + label: "Email nou", + placeholder: "Introduceți email-ul", errors: { - required: "E-mailul este obligatoriu", - invalid: "E-mailul este invalid", - exists: "E-mailul există deja. Folosiți altul.", - validation_failed: "Validarea e-mailului a eșuat. Încercați din nou.", + required: "Email-ul este obligatoriu", + invalid: "Email-ul este invalid", + exists: "Email-ul există deja. Folosiți altul.", + validation_failed: "Validarea email-ului a eșuat. Încercați din nou.", }, },Also update line 1560:
- helper_text: "Codul de verificare a fost trimis la noul e-mail.", + helper_text: "Codul de verificare a fost trimis la noul email.",packages/i18n/src/locales/cs/translations.ts (1)
1525-1565: LGTM! Czech translations for change email modal are well-structured.The new translation subtree is properly nested, syntactically correct, and consistent with existing Czech translations throughout the file. The structure aligns with the PR's two-step email change flow (enter email → enter code), and all necessary keys are present including error states, form labels, and action buttons.
Minor observation: Some email validation error messages (lines 1539-1540:
required,invalid) duplicate existing translations at lines 27-28 underauth.common.email.errors. While this keeps translations feature-scoped and may be intentional, you could optionally refactor to reference the shared translations if you want to reduce maintenance overhead. Not a blocker given the clear namespacing benefit.packages/i18n/src/locales/de/translations.ts (1)
1543-1583: Consider aligning with formal address used in security flows.The new change email modal uses informal German address ("Gib", "deine", "versuche"), while the similar
forgot_passwordflow (lines 136-157) uses formal address ("Geben Sie", "Ihre"). For security-sensitive operations like email changes, maintaining a consistent tone with other authentication flows improves user trust and experience.Apply this diff to align with formal address:
profile: { change_email_modal: { title: "E-Mail ändern", - description: "Gib eine neue E-Mail-Adresse ein, um einen Verifizierungslink zu erhalten.", + description: "Geben Sie eine neue E-Mail-Adresse ein, um einen Verifizierungslink zu erhalten.", toasts: { success_title: "Erfolg!", - success_message: "E-Mail erfolgreich aktualisiert. Bitte melde dich erneut an.", + success_message: "E-Mail erfolgreich aktualisiert. Bitte melden Sie sich erneut an.", }, form: { email: { label: "Neue E-Mail", - placeholder: "Gib deine E-Mail ein", + placeholder: "Geben Sie Ihre E-Mail ein", errors: { required: "E-Mail ist erforderlich", invalid: "E-Mail ist ungültig", - exists: "E-Mail existiert bereits. Bitte nutze eine andere.", - validation_failed: "E-Mail-Verifizierung fehlgeschlagen. Bitte versuche es erneut.", + exists: "E-Mail existiert bereits. Bitte nutzen Sie eine andere.", + validation_failed: "E-Mail-Verifizierung fehlgeschlagen. Bitte versuchen Sie es erneut.", }, }, code: { label: "Einmaliger Code", placeholder: "gets-sets-flys", - helper_text: "Verifizierungscode wurde an deine neue E-Mail gesendet.", + helper_text: "Verifizierungscode wurde an Ihre neue E-Mail gesendet.", errors: { required: "Einmaliger Code ist erforderlich", - invalid: "Ungültiger Verifizierungscode. Bitte versuche es erneut.", + invalid: "Ungültiger Verifizierungscode. Bitte versuchen Sie es erneut.", }, }, },packages/i18n/src/locales/it/translations.ts (1)
1537-1577: Italian change_email_modal block is correct; consider normalizing apostrophe styleThe block’s structure and keys line up with the English source and other locales, and the Italian copy reads well. It will integrate cleanly with the modal.
Tiny style nit: the new email error messages use a curly apostrophe (
L’email), while most existing strings in this file use a straight apostrophe (l'elemento, etc.). If you want to keep punctuation style consistent across the locale, you could normalize these:- required: "L’email è obbligatoria", - invalid: "L’email non è valida", + required: "L'email è obbligatoria", + invalid: "L'email non è valida",Purely cosmetic; no functional impact.
packages/i18n/src/locales/ua/translations.ts (1)
1532-1572: Align email terminology and description with existing UA locale and code-based flowThe structure and keys look good, but a few text tweaks would make this consistent with the rest of the UA translations and less confusing for the two-step code flow:
- Use “адреса електронної пошти”/“електронна пошта” instead of Latin “email” to match existing UA strings (e.g.,
auth.common.email).- Description currently says “посилання для підтвердження”, but the UI here is code-based; better to mention a confirmation code.
- Reuse the same terminology in toasts, labels, placeholders, and error messages for consistency.
Suggested diff (values only):
account_settings: { profile: { change_email_modal: { - title: "Змінити email", - description: "Введіть нову адресу електронної пошти, щоб отримати посилання для підтвердження.", + title: "Змінити адресу електронної пошти", + description: "Введіть нову адресу електронної пошти, щоб отримати код підтвердження.", toasts: { success_title: "Успіх!", - success_message: "Email успішно оновлено. Увійдіть знову.", + success_message: "Адресу електронної пошти успішно оновлено. Увійдіть знову.", }, form: { email: { - label: "Новий email", - placeholder: "Введіть свій email", + label: "Нова адреса електронної пошти", + placeholder: "Введіть нову адресу електронної пошти", errors: { - required: "Email є обов’язковим", - invalid: "Email недійсний", - exists: "Email уже існує. Використайте інший.", - validation_failed: "Не вдалося підтвердити email. Спробуйте ще раз.", + required: "Адреса електронної пошти є обов’язковою", + invalid: "Неправильна адреса електронної пошти", + exists: "Така адреса електронної пошти вже існує. Вкажіть іншу.", + validation_failed: "Не вдалося підтвердити адресу електронної пошти. Спробуйте ще раз.", }, }, code: { label: "Унікальний код", placeholder: "gets-sets-flys", - helper_text: "Код підтвердження надіслано на ваш новий email.", + helper_text: "Код підтвердження надіслано на вашу нову адресу електронної пошти.", errors: { required: "Унікальний код є обов’язковим", invalid: "Недійсний код підтвердження. Спробуйте ще раз.", }, }, },This keeps behavior unchanged while tightening copy and aligning with the rest of the locale.
packages/i18n/src/locales/pt-BR/translations.ts (1)
1558-1563: Consider adding SMTP configuration error message for defensive coding.While the PR description indicates the "Change Email" action is only visible when SMTP is configured, consider adding an error message for SMTP not being enabled, similar to the
forgot_passwordflow (lines 140-142).This provides defensive messaging in case the UI logic changes or for edge cases where the modal might be accessed when SMTP is not properly configured.
Apply this diff to add the SMTP error message:
errors: { required: "O e-mail é obrigatório", invalid: "O e-mail é inválido", exists: "O e-mail já existe. Use outro.", validation_failed: "Falha na validação do e-mail. Tente novamente.", + smtp_not_enabled: "SMTP não está habilitado. Entre em contato com o administrador.", },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
apps/web/core/components/core/modals/change-email-modal.tsx(1 hunks)apps/web/core/components/profile/form.tsx(5 hunks)packages/i18n/src/locales/cs/translations.ts(2 hunks)packages/i18n/src/locales/de/translations.ts(2 hunks)packages/i18n/src/locales/en/translations.ts(1 hunks)packages/i18n/src/locales/es/translations.ts(2 hunks)packages/i18n/src/locales/fr/translations.ts(1 hunks)packages/i18n/src/locales/id/translations.ts(2 hunks)packages/i18n/src/locales/it/translations.ts(2 hunks)packages/i18n/src/locales/ja/translations.ts(2 hunks)packages/i18n/src/locales/ko/translations.ts(2 hunks)packages/i18n/src/locales/pl/translations.ts(2 hunks)packages/i18n/src/locales/pt-BR/translations.ts(2 hunks)packages/i18n/src/locales/ro/translations.ts(2 hunks)packages/i18n/src/locales/ru/translations.ts(2 hunks)packages/i18n/src/locales/sk/translations.ts(2 hunks)packages/i18n/src/locales/tr-TR/translations.ts(2 hunks)packages/i18n/src/locales/ua/translations.ts(2 hunks)packages/i18n/src/locales/vi-VN/translations.ts(2 hunks)packages/i18n/src/locales/zh-CN/translations.ts(2 hunks)packages/i18n/src/locales/zh-TW/translations.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/core/components/core/modals/change-email-modal.tsx
- apps/web/core/components/profile/form.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-03-11T19:42:41.769Z
Learnt from: janreges
Repo: makeplane/plane PR: 6743
File: packages/i18n/src/store/index.ts:160-161
Timestamp: 2025-03-11T19:42:41.769Z
Learning: In the Plane project, the file 'packages/i18n/src/store/index.ts' already includes support for Polish language translations with the case "pl".
Applied to files:
packages/i18n/src/locales/sk/translations.tspackages/i18n/src/locales/cs/translations.tspackages/i18n/src/locales/pl/translations.ts
⏰ 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: Cursor Bugbot
- GitHub Check: Build and lint web apps
- GitHub Check: Analyze (javascript)
🔇 Additional comments (8)
packages/i18n/src/locales/fr/translations.ts (1)
1545-1585: LGTM! Well-structured translations for the change email modal.The French translations are comprehensive, covering all necessary strings including form labels, validation errors, toast messages, and action buttons. The structure is consistent with other modal translations in the file, and the error messages are clear and actionable.
packages/i18n/src/locales/pl/translations.ts (1)
1528-1568: LGTM! Complete and consistent Polish translations for change-email modal.The translation block is well-structured and covers all necessary UI elements for the two-step email-change flow:
- Clear title, description, and success messaging
- Comprehensive form field labels, placeholders, and helper text
- Complete error coverage (required, invalid, exists, validation_failed)
- All actions and loading states
The terminology is consistent with existing translations in the file (e.g., "E-mail" at lines 1542-1543 matches lines 27-28, "Unikalny kod" at line 1549 matches line 76).
Based on learnings, Polish language support is already established in the project, and this addition integrates cleanly with the existing translation structure.
packages/i18n/src/locales/zh-CN/translations.ts (1)
1508-1548: LGTM! Well-structured translations for the email change modal.The translation block is properly structured with all necessary keys for the two-step email change flow. Translations are accurate, natural Chinese, and follow established patterns in the file (error messages match lines 27-28, placeholder matches line 77).
packages/i18n/src/locales/zh-TW/translations.ts (1)
1509-1549: LGTM! Well-structured and consistent translations.The Traditional Chinese translations for the email change modal are complete and properly integrated:
- Terminology is consistent with existing strings (e.g., "電子郵件", "驗證碼", "必填")
- Two-step flow clearly represented (email entry → code verification)
- All necessary UI elements covered: form fields, validation errors, success/loading states, and action buttons
- JSON structure is valid and properly nested
packages/i18n/src/locales/tr-TR/translations.ts (1)
2626-2626: Blank line change is harmlessThis trailing blank-line change has no functional impact; fine to keep as-is or drop per formatting preferences.
packages/i18n/src/locales/en/translations.ts (1)
1360-1397: New English change_email_modal translations are structurally and semantically soundThe
account_settings.profile.change_email_modalblock mirrors the expected schema (title/description/toasts/form/email+code/actions/states) and the copy matches the intended flow and error cases; no issues from an i18n or key-structure standpoint.packages/i18n/src/locales/pt-BR/translations.ts (1)
1545-1585: Well-structured translation block for email change flow.The translation structure is comprehensive and well-organized:
- Clear two-step flow (email → code)
- Comprehensive form field error messages
- Proper action labels
- State indicators for async operations
The translations appear natural and consistent with the existing Portuguese (pt-BR) patterns throughout the file.
packages/i18n/src/locales/ja/translations.ts (1)
1524-1564: All keys and structure verified; only translation accuracy requires native speaker review.The
change_email_modaltranslations are correctly implemented:✓ Key completeness: All keys match the English locale (lines 1360-1397). The structure includes email/code fields, error handling, actions, and states as required.
✓ Placeholder consistency: The
"gets-sets-flys"placeholder at line 1546 matches the pattern used throughout the file and is consistent with the English locale (line 1381).✓ Structure: Properly nested under
account_settings.profilewith correct formatting and indentation.Remaining verification: Confirm the Japanese translations are natural and contextually appropriate for the email change workflow. This requires review by a native Japanese speaker familiar with the application's user experience.
| account_settings: { | ||
| profile: { | ||
| change_email_modal: { | ||
| title: "Cambiar correo electrónico", | ||
| description: "Introduce una nueva dirección de correo electrónico para recibir un enlace de verificación.", | ||
| toasts: { | ||
| success_title: "¡Éxito!", | ||
| success_message: "Correo electrónico actualizado correctamente. Inicia sesión de nuevo.", | ||
| }, | ||
| form: { | ||
| email: { | ||
| label: "Nuevo correo electrónico", | ||
| placeholder: "Introduce tu correo electrónico", | ||
| errors: { | ||
| required: "El correo electrónico es obligatorio", | ||
| invalid: "El correo electrónico no es válido", | ||
| exists: "El correo electrónico ya existe. Usa uno diferente.", | ||
| validation_failed: "La validación del correo electrónico falló. Inténtalo de nuevo.", | ||
| }, | ||
| }, | ||
| code: { | ||
| label: "Código único", | ||
| placeholder: "gets-sets-flys", | ||
| helper_text: "Código de verificación enviado a tu nuevo correo electrónico.", | ||
| errors: { | ||
| required: "El código único es obligatorio", | ||
| invalid: "Código de verificación inválido. Inténtalo de nuevo.", | ||
| }, | ||
| }, | ||
| }, | ||
| actions: { | ||
| continue: "Continuar", | ||
| confirm: "Confirmar", | ||
| cancel: "Cancelar", | ||
| }, | ||
| states: { | ||
| sending: "Enviando…", | ||
| }, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spanish change_email_modal block looks correct; align unique-code placeholder with existing locale usage
The structure and keys here match the English source and other locales, so this will wire up cleanly in the UI.
Minor localization nit: in this file, the existing unique code placeholder under auth.common.unique_code.placeholder is localized ("obtiene-establece-vuela"), while change_email_modal.form.code.placeholder keeps the English "gets-sets-flys". For consistency within the es locale, consider reusing the localized variant:
- placeholder: "gets-sets-flys",
+ placeholder: "obtiene-establece-vuela",🤖 Prompt for AI Agents
In packages/i18n/src/locales/es/translations.ts around lines 1547 to 1585, the
change_email_modal.form.code.placeholder uses the English string
"gets-sets-flys" while the rest of the es locale uses a localized unique code
placeholder; update change_email_modal.form.code.placeholder to the same
localized variant used elsewhere in this file (e.g., the value under
auth.common.unique_code.placeholder) so the Spanish locale is consistent across
the app.
| profile: { | ||
| change_email_modal: { | ||
| title: "Ubah email", | ||
| description: "Masukkan alamat email baru untuk menerima tautan verifikasi.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor translation accuracy issue in description.
The description mentions "tautan verifikasi" (verification link), but the actual feature sends a verification code, not a link. This is confirmed by the helper text at line 1556: "Kode verifikasi dikirim ke email baru Anda."
Apply this diff to correct the translation:
- description: "Masukkan alamat email baru untuk menerima tautan verifikasi.",
+ description: "Masukkan alamat email baru untuk menerima kode verifikasi.",📝 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.
| description: "Masukkan alamat email baru untuk menerima tautan verifikasi.", | |
| description: "Masukkan alamat email baru untuk menerima kode verifikasi.", |
🤖 Prompt for AI Agents
In packages/i18n/src/locales/id/translations.ts around line 1537, the
description text incorrectly says "tautan verifikasi" (verification link) while
the feature sends a verification code; update the string to reference "kode
verifikasi" (verification code) instead so it matches the helper text at line
1556 and the actual behavior.
| account_settings: { | ||
| profile: { | ||
| change_email_modal: { | ||
| title: "이메일 변경", | ||
| description: "확인 링크를 받으려면 새 이메일 주소를 입력하세요.", | ||
| toasts: { | ||
| success_title: "성공!", | ||
| success_message: "이메일이 업데이트되었습니다. 다시 로그인하세요.", | ||
| }, | ||
| form: { | ||
| email: { | ||
| label: "새 이메일", | ||
| placeholder: "이메일을 입력하세요", | ||
| errors: { | ||
| required: "이메일은 필수입니다", | ||
| invalid: "유효하지 않은 이메일입니다", | ||
| exists: "이미 존재하는 이메일입니다. 다른 주소를 사용하세요.", | ||
| validation_failed: "이메일 확인에 실패했습니다. 다시 시도하세요.", | ||
| }, | ||
| }, | ||
| code: { | ||
| label: "고유 코드", | ||
| placeholder: "gets-sets-flys", | ||
| helper_text: "인증 코드가 새 이메일로 전송되었습니다.", | ||
| errors: { | ||
| required: "고유 코드는 필수입니다", | ||
| invalid: "잘못된 인증 코드입니다. 다시 시도하세요.", | ||
| }, | ||
| }, | ||
| }, | ||
| actions: { | ||
| continue: "계속", | ||
| confirm: "확인", | ||
| cancel: "취소", | ||
| }, | ||
| states: { | ||
| sending: "전송 중…", | ||
| }, | ||
| }, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check terminology consistency in the modal description.
The description at line 1521 mentions "확인 링크를 받으려면" (to receive a confirmation link), but the actual flow sends a verification code to the user's email, as indicated by the helper text at line 1540: "인증 코드가 새 이메일로 전송되었습니다" (Verification code has been sent to the new email). This terminology mismatch could confuse users about what to expect.
Consider updating line 1521 to match the actual implementation:
- description: "확인 링크를 받으려면 새 이메일 주소를 입력하세요.",
+ description: "인증 코드를 받으려면 새 이메일 주소를 입력하세요.",This would change "confirmation link" to "verification code", making it consistent with the rest of the flow and matching how other locales likely describe this feature.
📝 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.
| account_settings: { | |
| profile: { | |
| change_email_modal: { | |
| title: "이메일 변경", | |
| description: "확인 링크를 받으려면 새 이메일 주소를 입력하세요.", | |
| toasts: { | |
| success_title: "성공!", | |
| success_message: "이메일이 업데이트되었습니다. 다시 로그인하세요.", | |
| }, | |
| form: { | |
| email: { | |
| label: "새 이메일", | |
| placeholder: "이메일을 입력하세요", | |
| errors: { | |
| required: "이메일은 필수입니다", | |
| invalid: "유효하지 않은 이메일입니다", | |
| exists: "이미 존재하는 이메일입니다. 다른 주소를 사용하세요.", | |
| validation_failed: "이메일 확인에 실패했습니다. 다시 시도하세요.", | |
| }, | |
| }, | |
| code: { | |
| label: "고유 코드", | |
| placeholder: "gets-sets-flys", | |
| helper_text: "인증 코드가 새 이메일로 전송되었습니다.", | |
| errors: { | |
| required: "고유 코드는 필수입니다", | |
| invalid: "잘못된 인증 코드입니다. 다시 시도하세요.", | |
| }, | |
| }, | |
| }, | |
| actions: { | |
| continue: "계속", | |
| confirm: "확인", | |
| cancel: "취소", | |
| }, | |
| states: { | |
| sending: "전송 중…", | |
| }, | |
| }, | |
| }, | |
| }, | |
| account_settings: { | |
| profile: { | |
| change_email_modal: { | |
| title: "이메일 변경", | |
| description: "인증 코드를 받으려면 새 이메일 주소를 입력하세요.", | |
| toasts: { | |
| success_title: "성공!", | |
| success_message: "이메일이 업데이트되었습니다. 다시 로그인하세요.", | |
| }, | |
| form: { | |
| email: { | |
| label: "새 이메일", | |
| placeholder: "이메일을 입력하세요", | |
| errors: { | |
| required: "이메일은 필수입니다", | |
| invalid: "유효하지 않은 이메일입니다", | |
| exists: "이미 존재하는 이메일입니다. 다른 주소를 사용하세요.", | |
| validation_failed: "이메일 확인에 실패했습니다. 다시 시도하세요.", | |
| }, | |
| }, | |
| code: { | |
| label: "고유 코드", | |
| placeholder: "gets-sets-flys", | |
| helper_text: "인증 코드가 새 이메일로 전송되었습니다.", | |
| errors: { | |
| required: "고유 코드는 필수입니다", | |
| invalid: "잘못된 인증 코드입니다. 다시 시도하세요.", | |
| }, | |
| }, | |
| }, | |
| actions: { | |
| continue: "계속", | |
| confirm: "확인", | |
| cancel: "취소", | |
| }, | |
| states: { | |
| sending: "전송 중…", | |
| }, | |
| }, | |
| }, | |
| }, |
🤖 Prompt for AI Agents
In packages/i18n/src/locales/ko/translations.ts around lines 1517–1557, the
modal description at line 1521 refers to a "confirmation link" ("확인 링크를 받으려면")
while the flow actually sends a verification code (helper text at line 1540 says
"인증 코드가 새 이메일로 전송되었습니다"); update the description to reference a verification
code (e.g., replace "확인 링크를 받으려면" with "인증 코드를 받으려면") so the wording matches the
rest of the modal and other locales, and verify similar phrasing is consistent
across related keys.
| toasts: { | ||
| success_title: "Sucesso!", | ||
| success_message: "E-mail atualizado com sucesso. Faça login novamente.", | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error toast messages for consistency and better error handling.
The toast section only includes success messages but lacks error messages. This is inconsistent with established patterns throughout this translation file (e.g., auth.forgot_password.toast at lines 143-153, auth.common.password.toast.change_password at lines 62-73).
When the email change request fails due to server-side issues (network errors, rate limiting, service unavailable, etc.), users should receive clear feedback via toast notifications.
Apply this diff to add error toast messages:
toasts: {
success_title: "Sucesso!",
success_message: "E-mail atualizado com sucesso. Faça login novamente.",
+ error_title: "Erro!",
+ error_message: "Não foi possível atualizar o e-mail. Por favor, tente novamente.",
},📝 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.
| toasts: { | |
| success_title: "Sucesso!", | |
| success_message: "E-mail atualizado com sucesso. Faça login novamente.", | |
| }, | |
| toasts: { | |
| success_title: "Sucesso!", | |
| success_message: "E-mail atualizado com sucesso. Faça login novamente.", | |
| error_title: "Erro!", | |
| error_message: "Não foi possível atualizar o e-mail. Por favor, tente novamente.", | |
| }, |
🤖 Prompt for AI Agents
In packages/i18n/src/locales/pt-BR/translations.ts around lines 1550-1553, the
toasts block for email update only contains success messages; add corresponding
error toast keys to match the file's pattern (e.g., add error_title and
error_message alongside success_title/success_message) using Portuguese text
consistent with other entries (for example "Erro!" and a message like "Falha ao
atualizar o e-mail. Tente novamente mais tarde."). Ensure the new keys are
placed within the same toasts object and follow the same key naming and
punctuation conventions as nearby toast blocks.
| account_settings: { | ||
| profile: { | ||
| change_email_modal: { | ||
| title: "Изменить email", | ||
| description: "Введите новый адрес электронной почты, чтобы получить ссылку для подтверждения.", | ||
| toasts: { | ||
| success_title: "Успех!", | ||
| success_message: "Email успешно обновлён. Пожалуйста, войдите снова.", | ||
| }, | ||
| form: { | ||
| email: { | ||
| label: "Новый email", | ||
| placeholder: "Введите свой email", | ||
| errors: { | ||
| required: "Email обязателен", | ||
| invalid: "Email недействителен", | ||
| exists: "Email уже существует. Используйте другой.", | ||
| validation_failed: "Не удалось подтвердить email. Попробуйте ещё раз.", | ||
| }, | ||
| }, | ||
| code: { | ||
| label: "Уникальный код", | ||
| placeholder: "gets-sets-flys", | ||
| helper_text: "Код подтверждения отправлен на ваш новый email.", | ||
| errors: { | ||
| required: "Уникальный код обязателен", | ||
| invalid: "Неверный код подтверждения. Попробуйте ещё раз.", | ||
| }, | ||
| }, | ||
| }, | ||
| actions: { | ||
| continue: "Продолжить", | ||
| confirm: "Подтвердить", | ||
| cancel: "Отмена", | ||
| }, | ||
| states: { | ||
| sending: "Отправка…", | ||
| }, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Align description with code-based verification flow
The modal description says “ссылку для подтверждения”, but the flow (and this copy) is centered around entering a magic/unique code. This can slightly confuse users expecting a link instead of a code.
I’d align the wording with the code-based flow:
- description: "Введите новый адрес электронной почты, чтобы получить ссылку для подтверждения.",
+ description: "Введите новый адрес электронной почты, чтобы получить код подтверждения.",Everything else in this block (keys, structure, and Russian phrasing) looks consistent with the rest of the locale.
🤖 Prompt for AI Agents
In packages/i18n/src/locales/ru/translations.ts around lines 1530 to 1569,
update the modal description which currently says it will send a confirmation
"link" to instead refer to a confirmation "code" to match the code-based
verification flow; change the description string to something like "Введите код
подтверждения, отправленный на ваш новый адрес электронной почты." so it clearly
instructs users to enter the code they receive.
| account_settings: { | ||
| profile: { | ||
| change_email_modal: { | ||
| title: "Zmeniť e-mail", | ||
| description: "Zadajte novú e-mailovú adresu, aby ste dostali overovací odkaz.", | ||
| toasts: { | ||
| success_title: "Úspech!", | ||
| success_message: "E-mail bol úspešne aktualizovaný. Prihláste sa znova.", | ||
| }, | ||
| form: { | ||
| email: { | ||
| label: "Nový e-mail", | ||
| placeholder: "Zadajte svoj e-mail", | ||
| errors: { | ||
| required: "E-mail je povinný", | ||
| invalid: "E-mail je neplatný", | ||
| exists: "E-mail už existuje. Použite iný.", | ||
| validation_failed: "Overenie e-mailu zlyhalo. Skúste znova.", | ||
| }, | ||
| }, | ||
| code: { | ||
| label: "Jedinečný kód", | ||
| placeholder: "gets-sets-flys", | ||
| helper_text: "Overovací kód bol odoslaný na váš nový e-mail.", | ||
| errors: { | ||
| required: "Jedinečný kód je povinný", | ||
| invalid: "Neplatný overovací kód. Skúste znova.", | ||
| }, | ||
| }, | ||
| }, | ||
| actions: { | ||
| continue: "Pokračovať", | ||
| confirm: "Potvrdiť", | ||
| cancel: "Zrušiť", | ||
| }, | ||
| states: { | ||
| sending: "Odosielanie…", | ||
| }, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Align wording with code-based verification flow
The new block looks structurally correct, but the description talks about an “overovací odkaz” (verification link) while the UI flow and helper text are code-based, which can confuse users. You might also make the email placeholder explicitly “new email” for clarity.
Suggested tweak:
change_email_modal: {
title: "Zmeniť e-mail",
- description: "Zadajte novú e-mailovú adresu, aby ste dostali overovací odkaz.",
+ description: "Zadajte novú e-mailovú adresu, aby ste dostali overovací kód.",
toasts: {
success_title: "Úspech!",
success_message: "E-mail bol úspešne aktualizovaný. Prihláste sa znova.",
},
form: {
email: {
label: "Nový e-mail",
- placeholder: "Zadajte svoj e-mail",
+ placeholder: "Zadajte svoj nový e-mail",Optionally, you may also standardize the ellipsis in states.sending to "...“ if you prefer consistency with other strings in this locale.
📝 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.
| account_settings: { | |
| profile: { | |
| change_email_modal: { | |
| title: "Zmeniť e-mail", | |
| description: "Zadajte novú e-mailovú adresu, aby ste dostali overovací odkaz.", | |
| toasts: { | |
| success_title: "Úspech!", | |
| success_message: "E-mail bol úspešne aktualizovaný. Prihláste sa znova.", | |
| }, | |
| form: { | |
| email: { | |
| label: "Nový e-mail", | |
| placeholder: "Zadajte svoj e-mail", | |
| errors: { | |
| required: "E-mail je povinný", | |
| invalid: "E-mail je neplatný", | |
| exists: "E-mail už existuje. Použite iný.", | |
| validation_failed: "Overenie e-mailu zlyhalo. Skúste znova.", | |
| }, | |
| }, | |
| code: { | |
| label: "Jedinečný kód", | |
| placeholder: "gets-sets-flys", | |
| helper_text: "Overovací kód bol odoslaný na váš nový e-mail.", | |
| errors: { | |
| required: "Jedinečný kód je povinný", | |
| invalid: "Neplatný overovací kód. Skúste znova.", | |
| }, | |
| }, | |
| }, | |
| actions: { | |
| continue: "Pokračovať", | |
| confirm: "Potvrdiť", | |
| cancel: "Zrušiť", | |
| }, | |
| states: { | |
| sending: "Odosielanie…", | |
| }, | |
| }, | |
| account_settings: { | |
| profile: { | |
| change_email_modal: { | |
| title: "Zmeniť e-mail", | |
| description: "Zadajte novú e-mailovú adresu, aby ste dostali overovací kód.", | |
| toasts: { | |
| success_title: "Úspech!", | |
| success_message: "E-mail bol úspešne aktualizovaný. Prihláste sa znova.", | |
| }, | |
| form: { | |
| email: { | |
| label: "Nový e-mail", | |
| placeholder: "Zadajte svoj nový e-mail", | |
| errors: { | |
| required: "E-mail je povinný", | |
| invalid: "E-mail je neplatný", | |
| exists: "E-mail už existuje. Použite iný.", | |
| validation_failed: "Overenie e-mailu zlyhalo. Skúste znova.", | |
| }, | |
| }, | |
| code: { | |
| label: "Jedinečný kód", | |
| placeholder: "gets-sets-flys", | |
| helper_text: "Overovací kód bol odoslaný na váš nový e-mail.", | |
| errors: { | |
| required: "Jedinečný kód je povinný", | |
| invalid: "Neplatný overovací kód. Skúste znova.", | |
| }, | |
| }, | |
| }, | |
| actions: { | |
| continue: "Pokračovať", | |
| confirm: "Potvrdiť", | |
| cancel: "Zrušiť", | |
| }, | |
| states: { | |
| sending: "Odosielanie…", | |
| }, | |
| }, |
🤖 Prompt for AI Agents
In packages/i18n/src/locales/sk/translations.ts around lines 1528-1566, the
change_email_modal description mentions an "overovací odkaz" (verification link)
while the UI and helper_text use a code-based flow; update the description to
refer to a verification code (e.g., "Zadajte novú e-mailovú adresu, aby ste
obdržali overovací kód."), change the email placeholder to explicitly say "Nový
e-mail" instead of the generic "Zadajte svoj e-mail", and optionally normalize
states.sending to use three ASCII dots ("...") for consistency with other locale
strings.
| account_settings: { | ||
| profile: { | ||
| change_email_modal: { | ||
| title: "Đổi email", | ||
| description: "Nhập địa chỉ email mới để nhận liên kết xác minh.", | ||
| toasts: { | ||
| success_title: "Thành công!", | ||
| success_message: "Email đã được cập nhật. Vui lòng đăng nhập lại.", | ||
| }, | ||
| form: { | ||
| email: { | ||
| label: "Email mới", | ||
| placeholder: "Nhập email của bạn", | ||
| errors: { | ||
| required: "Email là bắt buộc", | ||
| invalid: "Email không hợp lệ", | ||
| exists: "Email đã tồn tại. Vui lòng dùng email khác.", | ||
| validation_failed: "Xác thực email thất bại. Thử lại.", | ||
| }, | ||
| }, | ||
| code: { | ||
| label: "Mã duy nhất", | ||
| placeholder: "gets-sets-flys", | ||
| helper_text: "Mã xác minh đã được gửi tới email mới của bạn.", | ||
| errors: { | ||
| required: "Mã duy nhất là bắt buộc", | ||
| invalid: "Mã xác minh không hợp lệ. Thử lại.", | ||
| }, | ||
| }, | ||
| }, | ||
| actions: { | ||
| continue: "Tiếp tục", | ||
| confirm: "Xác nhận", | ||
| cancel: "Hủy", | ||
| }, | ||
| states: { | ||
| sending: "Đang gửi…", | ||
| }, | ||
| }, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Well-structured translation branch with minor terminology concern.
The new account_settings.profile.change_email_modal translation branch is well-organized and follows existing patterns. Good reuse of common terms like "Mã duy nhất" (line 76), "Tiếp tục" (line 769), and "Xác nhận" (line 826).
However, there's a potential terminology inconsistency at line 1538: the description mentions "liên kết xác minh" (verification link), but line 1557's helper_text and the PR's backend endpoints indicate this is a code-based verification flow. Consider updating line 1538 to use "mã xác minh" (verification code) instead of "liên kết xác minh" for consistency.
Please verify:
- All translation keys match the frontend usage in
change-email-modal.tsxandform.tsx - This terminology inconsistency is mirrored across all locale files mentioned in the AI summary (cs, de, en, es, fr, etc.) — if the English version also says "link", it should be corrected to "code" across all locales
- The Vietnamese translation accurately reflects the intended two-step code verification flow
🏁 Script executed:
#!/bin/bash
# Verify that other locale files have the same structure and check for "link" vs "code" terminology
echo "=== Checking for account_settings.profile.change_email_modal in other locale files ==="
fd -e ts -e json "translations" packages/i18n/src/locales/ --exec grep -l "change_email_modal" {}
echo -e "\n=== Checking English locale for 'link' vs 'code' terminology ==="
rg -A2 "change_email_modal" packages/i18n/src/locales/en/translations.ts | grep -i "description"Length of output: 1104
🏁 Script executed:
#!/bin/bash
# Check English locale for the complete change_email_modal structure including code field
echo "=== English locale change_email_modal structure ==="
rg -A30 "change_email_modal:" packages/i18n/src/locales/en/translations.ts | head -35
echo -e "\n=== Check if frontend implementation exists ==="
fd -e ts -e tsx "change.*email" packages/src/ --type f 2>/dev/null | head -10
echo -e "\n=== Search for email code verification flow ==="
rg -l "email.*code\|verification.*code" packages/src/ --type ts --type tsx 2>/dev/null | head -5Length of output: 1269
Fix terminology inconsistency across all locale files: "verification link" should be "verification code".
Verification confirms the issue is systematic. The English locale description says "Enter a new email address to receive a verification link," but the code field and helper_text reference a code. The actual flow uses code-based verification (as confirmed by the backend endpoints in the PR description), so "link" is incorrect terminology.
The Vietnamese translation accurately mirrors the English source, so the issue exists across all 19 locale files that were updated. Update the description field in all locales—en, vi-VN, cs, de, es, fr, ja, ko, pl, pt-BR, ro, ru, sk, tr-TR, ua, zh-CN, zh-TW, id, and it—from "verification link" to "verification code" for consistency with the actual code-based flow.
🤖 Prompt for AI Agents
In packages/i18n/src/locales/vi-VN/translations.ts around lines 1534-1574, the
description text says "Nhập địa chỉ email mới để nhận liên kết xác minh"
(verification link) but the flow uses a verification code; update the
description to use "mã xác minh" (verification code) instead of "liên kết xác
minh". Apply the same replacement in the corresponding description fields across
all updated locale files (en, vi-VN, cs, de, es, fr, ja, ko, pl, pt-BR, ro, ru,
sk, tr-TR, ua, zh-CN, zh-TW, id, it) so the UI text consistently refers to a
verification code matching the backend flow. Ensure translation grammar is
correct for each language and run a quick grep across the locale directory to
catch any remaining "verification link" instances.
55cfb78
There was a problem hiding this 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 (1)
packages/i18n/src/locales/ko/translations.ts (1)
1517-1557: Terminology mismatch: description references "confirmation link" but flow uses "verification code".This issue was previously identified but remains unresolved. Line 1521 states "확인 링크를 받으려면" (to receive a confirmation link), while line 1540 correctly indicates "인증 코드가 새 이메일로 전송되었습니다" (Verification code has been sent to the new email). This inconsistency may confuse users about what to expect.
Apply this diff to align the description with the actual implementation:
- description: "확인 링크를 받으려면 새 이메일 주소를 입력하세요.", + description: "인증 코드를 받으려면 새 이메일 주소를 입력하세요.",
🧹 Nitpick comments (1)
packages/i18n/src/locales/cs/translations.ts (1)
1525-1565: Align description wording with code-based flow (minor translation nit)The flow uses a verification code rather than a link, but the
descriptioncurrently mentions “ověřovací odkaz”. For clarity and parity with the UI/other locales, consider updating it (and optionally tightening the email placeholder):change_email_modal: { title: "Změnit e-mail", - description: "Zadejte novou e-mailovou adresu a obdržíte ověřovací odkaz.", + description: "Zadejte novou e-mailovou adresu a obdržíte ověřovací kód.", toasts: { @@ form: { email: { label: "Nový e-mail", - placeholder: "Zadejte svůj e-mail", + placeholder: "Zadejte novou e-mailovou adresu",Structurally the keys and other messages look good and consistent with the existing
cslocale; this is just a wording polish.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
packages/i18n/src/locales/cs/translations.ts(1 hunks)packages/i18n/src/locales/de/translations.ts(1 hunks)packages/i18n/src/locales/es/translations.ts(1 hunks)packages/i18n/src/locales/id/translations.ts(1 hunks)packages/i18n/src/locales/it/translations.ts(1 hunks)packages/i18n/src/locales/ja/translations.ts(1 hunks)packages/i18n/src/locales/ko/translations.ts(1 hunks)packages/i18n/src/locales/pl/translations.ts(1 hunks)packages/i18n/src/locales/pt-BR/translations.ts(1 hunks)packages/i18n/src/locales/ro/translations.ts(1 hunks)packages/i18n/src/locales/ru/translations.ts(1 hunks)packages/i18n/src/locales/sk/translations.ts(1 hunks)packages/i18n/src/locales/tr-TR/translations.ts(1 hunks)packages/i18n/src/locales/ua/translations.ts(1 hunks)packages/i18n/src/locales/vi-VN/translations.ts(1 hunks)packages/i18n/src/locales/zh-CN/translations.ts(1 hunks)packages/i18n/src/locales/zh-TW/translations.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (15)
- packages/i18n/src/locales/ro/translations.ts
- packages/i18n/src/locales/it/translations.ts
- packages/i18n/src/locales/sk/translations.ts
- packages/i18n/src/locales/pt-BR/translations.ts
- packages/i18n/src/locales/id/translations.ts
- packages/i18n/src/locales/es/translations.ts
- packages/i18n/src/locales/zh-TW/translations.ts
- packages/i18n/src/locales/vi-VN/translations.ts
- packages/i18n/src/locales/ru/translations.ts
- packages/i18n/src/locales/tr-TR/translations.ts
- packages/i18n/src/locales/ua/translations.ts
- packages/i18n/src/locales/ja/translations.ts
- packages/i18n/src/locales/zh-CN/translations.ts
- packages/i18n/src/locales/de/translations.ts
- packages/i18n/src/locales/pl/translations.ts
⏰ 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). (4)
- GitHub Check: Cursor Bugbot
- GitHub Check: Analyze (javascript)
- GitHub Check: Analyze (javascript)
- GitHub Check: Build and lint web apps
| user.email = new_email | ||
| # Reset email verification status when email is changed | ||
| user.is_email_verified = False | ||
| user.save() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Race condition in email update causes database error
A race condition exists between checking email availability and saving the user. After the final User.objects.filter(email=new_email).exclude(id=user.id).exists() check at line 228, another user could claim the same email before user.save() executes at line 238. This creates a window where two concurrent requests could pass the validation check but one would fail with a database integrity error when attempting to save a duplicate email.
| return Response( | ||
| {"error": "Failed to verify code. Please try again."}, | ||
| status=status.HTTP_400_BAD_REQUEST, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Exception swallows error details during code verification
The exception handler catches all exceptions during code verification but doesn't log the actual error or provide specific feedback. This makes debugging difficult when legitimate errors occur, such as JSON parsing failures or cache connection issues. The caught exception e is never logged or used.
| ) | ||
|
|
||
| # Check if email is the same as current email | ||
| if new_email == user.email: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Email case sensitivity causes validation inconsistency
The comparison new_email == user.email compares a lowercased email against the user's stored email which may have mixed case. If a user's current email is "User@Example.com" and they enter "user@example.com", the validation incorrectly allows this as a "different" email, but the database save at line 235 will fail due to the unique constraint since emails are typically case-insensitive in practice. The comparison needs to normalize both sides: new_email == user.email.lower().
|
|
||
| # Send magic code to the new email | ||
| send_email_update_magic_code.delay(new_email, token) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Cache entry persists after email sending failure
If send_email_update_magic_code.delay(new_email, token) fails or the exception handler is triggered after cache.set() executes, the cache entry remains for 10 minutes even though no verification email was sent. Users cannot retry immediately because the cache key exists, but they never received the code. The cache should be cleaned up in the exception handler or the cache should only be set after successful email dispatch.
Description
this pull request introduces a feature that allows users to update their email address.
Type of Change
Summary by CodeRabbit
New Features
Improvements
Localization
✏️ Tip: You can customize this high-level summary in your review settings.
Note
Adds a two-step email change flow with rate-limited code generation, backend endpoints and emails, a new UI modal, and translations.
POST /api/users/me/email/generate-code/andPATCH /api/users/me/email/inapps/api/plane/app/urls/user.pyhandled byUserEndpoint._validate_new_email,generate_email_verification_code(cached token, sends magic code, rate-limited), andupdate_email(verifies code, updatesUser.email, resets verification, logs out, clears cache, sends confirmations).EmailVerificationThrottle(3/hour) inapps/api/plane/authentication/rate_limit.pyand apply viaget_throttles.send_email_update_magic_codeandsend_email_update_confirmationinapps/api/plane/bgtasks/user_email_update_task.py.apps/api/templates/emails/user/email_updated.html.ChangeEmailModal(apps/web/core/components/core/modals/change-email-modal.tsx) for email entry and code verification; signs out on success.ProfileForm(SMTP-gated) and expose "Change email" action.UserServicewithcheckEmail,generateEmailCode, andverifyEmailCode.Written by Cursor Bugbot for commit 55cfb78. This will update automatically on new commits. Configure here.