diff --git a/messages/de.json b/messages/de.json index 2ac54d12..373c3b77 100644 --- a/messages/de.json +++ b/messages/de.json @@ -424,6 +424,8 @@ "firstName": "Vorname", "lastName": "Nachname", "emailAddress": "E-Mail-Adresse", + "currentPassword": "Aktuelles Passwort", + "currentPasswordHelp": "Bestätigen Sie Ihr aktuelles Passwort, um Ihre E-Mail-Adresse zu ändern.", "saveChanges": "Änderungen speichern", "saving": "Speichern...", "profileUpdated": "Profil erfolgreich aktualisiert!", diff --git a/messages/en.json b/messages/en.json index b7f64c7f..5dd69a47 100644 --- a/messages/en.json +++ b/messages/en.json @@ -424,6 +424,8 @@ "firstName": "First Name", "lastName": "Last Name", "emailAddress": "Email Address", + "currentPassword": "Current Password", + "currentPasswordHelp": "Confirm your current password to change your email address.", "saveChanges": "Save Changes", "saving": "Saving...", "profileUpdated": "Profile updated successfully!", diff --git a/messages/es.json b/messages/es.json index c7785dc1..8e7610ce 100644 --- a/messages/es.json +++ b/messages/es.json @@ -424,6 +424,8 @@ "firstName": "Nombre", "lastName": "Apellido", "emailAddress": "Correo electronico", + "currentPassword": "Contrasena actual", + "currentPasswordHelp": "Confirma tu contrasena actual para cambiar tu correo electronico.", "saveChanges": "Guardar cambios", "saving": "Guardando...", "profileUpdated": "\u00a1Perfil actualizado con exito!", diff --git a/messages/fr.json b/messages/fr.json index 15f80c18..f36c9878 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -424,6 +424,8 @@ "firstName": "Prenom", "lastName": "Nom", "emailAddress": "Adresse e-mail", + "currentPassword": "Mot de passe actuel", + "currentPasswordHelp": "Confirmez votre mot de passe actuel pour modifier votre adresse e-mail.", "saveChanges": "Enregistrer les modifications", "saving": "Enregistrement...", "profileUpdated": "Profil mis a jour avec succes !", diff --git a/messages/pl.json b/messages/pl.json index 92f075c0..86bd1c51 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -424,6 +424,8 @@ "firstName": "Imię", "lastName": "Nazwisko", "emailAddress": "Adres e-mail", + "currentPassword": "Obecne hasło", + "currentPasswordHelp": "Potwierdź swoje obecne hasło, aby zmienić adres e-mail.", "saveChanges": "Zapisz zmiany", "saving": "Zapisywanie...", "profileUpdated": "Profil został zaktualizowany!", diff --git a/src/app/[country]/[locale]/(storefront)/account/profile/page.tsx b/src/app/[country]/[locale]/(storefront)/account/profile/page.tsx index 5ffad034..09558a34 100644 --- a/src/app/[country]/[locale]/(storefront)/account/profile/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/account/profile/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { CircleAlert } from "lucide-react"; +import { CircleAlert, Eye, EyeOff } from "lucide-react"; import { useTranslations } from "next-intl"; import { useState } from "react"; import { toast } from "sonner"; @@ -25,27 +25,44 @@ function ProfileForm({ refreshUser: () => Promise; }) { const t = useTranslations("profile"); + const ta = useTranslations("account"); // Initialize form data from user props - no useEffect needed const [formData, setFormData] = useState({ first_name: user.first_name || "", last_name: user.last_name || "", email: user.email || "", }); + const [currentPassword, setCurrentPassword] = useState(""); + const [showCurrentPassword, setShowCurrentPassword] = useState(false); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); + const [passwordError, setPasswordError] = useState(null); + + const emailChanged = formData.email.trim() !== user.email; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); + setPasswordError(null); setSaving(true); - const result = await updateCustomer(formData); + const result = await updateCustomer({ + ...formData, + ...(emailChanged && { current_password: currentPassword }), + }); if (result.success) { toast.success(t("profileUpdated")); + setCurrentPassword(""); + setShowCurrentPassword(false); await refreshUser(); } else { - setError(result.error || t("failedToUpdate")); + const message = result.error || t("failedToUpdate"); + if (emailChanged && /current password/i.test(message)) { + setPasswordError(message); + } else { + setError(message); + } } setSaving(false); @@ -102,6 +119,60 @@ function ProfileForm({ } /> + + {emailChanged && ( + + + {t("currentPassword")} + +
+ { + setCurrentPassword(e.target.value); + if (passwordError) setPasswordError(null); + }} + placeholder="••••••••" + className="pr-10" + aria-invalid={passwordError ? true : undefined} + aria-describedby="current_password_help" + /> +
+ +
+
+

+ {passwordError || t("currentPasswordHelp")} +

+
+ )}
diff --git a/src/lib/data/__tests__/customer.test.ts b/src/lib/data/__tests__/customer.test.ts index d0166212..2d5b419c 100644 --- a/src/lib/data/__tests__/customer.test.ts +++ b/src/lib/data/__tests__/customer.test.ts @@ -193,6 +193,21 @@ describe("customer server actions", () => { expect(result).toEqual({ success: true, customer: mockUser }); }); + it("forwards current_password when changing email", async () => { + mockClient.customer.update.mockResolvedValue(mockUser); + + const result = await updateCustomer({ + email: "new@example.com", + current_password: "secret", + }); + + expect(mockClient.customer.update).toHaveBeenCalledWith( + { email: "new@example.com", current_password: "secret" }, + { token: "jwt-token" }, + ); + expect(result).toEqual({ success: true, customer: mockUser }); + }); + it("returns error on failure", async () => { mockClient.customer.update.mockRejectedValue(new Error("Email taken")); @@ -204,6 +219,22 @@ describe("customer server actions", () => { }); }); + it("surfaces invalid current password error", async () => { + mockClient.customer.update.mockRejectedValue( + new Error("Current password is invalid or missing"), + ); + + const result = await updateCustomer({ + email: "new@example.com", + current_password: "wrong", + }); + + expect(result).toEqual({ + success: false, + error: "Current password is invalid or missing", + }); + }); + it("returns fallback message for non-Error throws", async () => { mockClient.customer.update.mockRejectedValue("unexpected"); diff --git a/src/lib/data/customer.ts b/src/lib/data/customer.ts index cb8de44a..18115d16 100644 --- a/src/lib/data/customer.ts +++ b/src/lib/data/customer.ts @@ -191,6 +191,7 @@ export async function updateCustomer(data: { first_name?: string; last_name?: string; email?: string; + current_password?: string; }) { return actionResult(async () => { let customer;