diff --git a/backend/src/main/java/com/petshop/backend/controller/AuthController.java b/backend/src/main/java/com/petshop/backend/controller/AuthController.java index 33281560..522c5ad8 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AuthController.java @@ -70,7 +70,8 @@ public class AuthController { public ResponseEntity register(@Valid @RequestBody RegisterRequest request) { String username = trimToNull(request.getUsername()); String email = trimToNull(request.getEmail()); - NameParts nameParts = splitFullName(request.getFullName()); + String firstName = trimToNull(request.getFirstName()); + String lastName = trimToNull(request.getLastName()); String phone = normalizePhone(request.getPhone()); if (userRepository.findByUsername(username).isPresent()) { @@ -98,9 +99,9 @@ public class AuthController { user.setUsername(username); user.setPassword(passwordEncoder.encode(request.getPassword())); user.setEmail(email); - user.setFirstName(nameParts.firstName()); - user.setLastName(nameParts.lastName()); - user.setFullName(nameParts.fullName()); + user.setFirstName(firstName); + user.setLastName(lastName); + user.setFullName(joinFullName(firstName, lastName)); user.setPhone(phone); user.setRole(User.Role.CUSTOMER); user.setActive(true); @@ -203,11 +204,16 @@ public class AuthController { user.setEmail(email); } - if (request.getFullName() != null) { - NameParts nameParts = splitFullName(request.getFullName()); - user.setFirstName(nameParts.firstName()); - user.setLastName(nameParts.lastName()); - user.setFullName(nameParts.fullName()); + String firstName = trimToNull(request.getFirstName()); + if (firstName != null) { + user.setFirstName(firstName); + } + String lastName = trimToNull(request.getLastName()); + if (lastName != null) { + user.setLastName(lastName); + } + if (firstName != null || lastName != null) { + user.setFullName(joinFullName(user.getFirstName(), user.getLastName())); } if (request.getPhone() != null) { @@ -247,6 +253,8 @@ public class AuthController { return new UserInfoResponse( user.getId(), user.getUsername(), + user.getFirstName(), + user.getLastName(), user.getEmail(), fullName, user.getPhone(), diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java b/backend/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java index 58959678..dc1c98c0 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java @@ -11,8 +11,11 @@ public class ProfileUpdateRequest { @Email(message = "Email must be valid") private String email; - @Size(max = 100, message = "Full name must not exceed 100 characters") - private String fullName; + @Size(max = 50, message = "First name must not exceed 50 characters") + private String firstName; + + @Size(max = 50, message = "Last name must not exceed 50 characters") + private String lastName; @Size(max = 20, message = "Phone must not exceed 20 characters") private String phone; @@ -36,12 +39,20 @@ public class ProfileUpdateRequest { this.email = email; } - public String getFullName() { - return fullName; + public String getFirstName() { + return firstName; } - public void setFullName(String fullName) { - this.fullName = fullName; + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; } public String getPhone() { @@ -67,14 +78,15 @@ public class ProfileUpdateRequest { ProfileUpdateRequest that = (ProfileUpdateRequest) o; return Objects.equals(username, that.username) && Objects.equals(email, that.email) && - Objects.equals(fullName, that.fullName) && + Objects.equals(firstName, that.firstName) && + Objects.equals(lastName, that.lastName) && Objects.equals(phone, that.phone) && Objects.equals(password, that.password); } @Override public int hashCode() { - return Objects.hash(username, email, fullName, phone, password); + return Objects.hash(username, email, firstName, lastName, phone, password); } @Override @@ -82,7 +94,8 @@ public class ProfileUpdateRequest { return "ProfileUpdateRequest{" + "username='" + username + '\'' + ", email='" + email + '\'' + - ", fullName='" + fullName + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + ", phone='" + phone + '\'' + ", password='" + password + '\'' + '}'; diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java b/backend/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java index 2791746c..4758d923 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java @@ -18,9 +18,13 @@ public class RegisterRequest { @Email(message = "Email must be valid") private String email; - @NotBlank(message = "Full name is required") - @Size(max = 100, message = "Full name must not exceed 100 characters") - private String fullName; + @NotBlank(message = "First name is required") + @Size(max = 50, message = "First name must not exceed 50 characters") + private String firstName; + + @NotBlank(message = "Last name is required") + @Size(max = 50, message = "Last name must not exceed 50 characters") + private String lastName; @NotBlank(message = "Phone is required") @Size(max = 20, message = "Phone must not exceed 20 characters") @@ -50,12 +54,20 @@ public class RegisterRequest { this.email = email; } - public String getFullName() { - return fullName; + public String getFirstName() { + return firstName; } - public void setFullName(String fullName) { - this.fullName = fullName; + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; } public String getPhone() { @@ -74,13 +86,14 @@ public class RegisterRequest { return Objects.equals(username, that.username) && Objects.equals(password, that.password) && Objects.equals(email, that.email) && - Objects.equals(fullName, that.fullName) && + Objects.equals(firstName, that.firstName) && + Objects.equals(lastName, that.lastName) && Objects.equals(phone, that.phone); } @Override public int hashCode() { - return Objects.hash(username, password, email, fullName, phone); + return Objects.hash(username, password, email, firstName, lastName, phone); } @Override @@ -89,7 +102,8 @@ public class RegisterRequest { "username='" + username + '\'' + ", password='" + password + '\'' + ", email='" + email + '\'' + - ", fullName='" + fullName + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + ", phone='" + phone + '\'' + '}'; } diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java b/backend/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java index 84372638..e88c272d 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java @@ -5,6 +5,8 @@ import java.util.Objects; public class UserInfoResponse { private Long id; private String username; + private String firstName; + private String lastName; private String email; private String fullName; private String phone; @@ -17,9 +19,11 @@ public class UserInfoResponse { public UserInfoResponse() { } - public UserInfoResponse(Long id, String username, String email, String fullName, String phone, String avatarUrl, String role, Long customerId, Long storeId, String storeName) { + public UserInfoResponse(Long id, String username, String firstName, String lastName, String email, String fullName, String phone, String avatarUrl, String role, Long customerId, Long storeId, String storeName) { this.id = id; this.username = username; + this.firstName = firstName; + this.lastName = lastName; this.email = email; this.fullName = fullName; this.phone = phone; @@ -46,6 +50,22 @@ public class UserInfoResponse { this.username = username; } + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + public String getEmail() { return email; } diff --git a/web/app/profile/page.js b/web/app/profile/page.js index 1b251593..ddb23d62 100644 --- a/web/app/profile/page.js +++ b/web/app/profile/page.js @@ -33,7 +33,8 @@ export default function ProfilePage() { const [petAge, setPetAge] = useState("1"); const [submitting, setSubmitting] = useState(false); const [petError, setPetError] = useState(null); - const [profileForm, setProfileForm] = useState({ fullName: "", email: "", phone: "" }); + const [avatarObjectUrl, setAvatarObjectUrl] = useState(null); + const [profileForm, setProfileForm] = useState({ firstName: "", lastName: "", email: "", phone: "", password: "", confirmPassword: "" }); const [profileSubmitting, setProfileSubmitting] = useState(false); const [profileError, setProfileError] = useState(null); const [profileSuccess, setProfileSuccess] = useState(null); @@ -55,9 +56,12 @@ export default function ProfilePage() { useEffect(() => { setProfileForm({ - fullName: user?.fullName || "", + firstName: user?.firstName || "", + lastName: user?.lastName || "", email: user?.email || "", phone: user?.phone || "", + password: "", + confirmPassword: "", }); }, [user]); @@ -117,11 +121,37 @@ export default function ProfilePage() { }, [clearPetImageObjectUrls]); useEffect(() => { -if (user?.role === "CUSTOMER" || user?.role === "ADMIN") { + if (user?.role === "CUSTOMER" || user?.role === "ADMIN") { loadPets(); } }, [user, loadPets]); + useEffect(() => { + let objectUrl = null; + + if (user?.avatarUrl && token) { + fetch(`${API_BASE}${user.avatarUrl}`, { + headers: { Authorization: `Bearer ${token}` }, + }) + .then((res) => (res.ok ? res.blob() : null)) + .then((blob) => { + if (blob) { + objectUrl = URL.createObjectURL(blob); + setAvatarObjectUrl(objectUrl); + } else { + setAvatarObjectUrl(null); + } + }) + .catch(() => setAvatarObjectUrl(null)); + } else { + setAvatarObjectUrl(null); + } + + return () => { + if (objectUrl) URL.revokeObjectURL(objectUrl); + }; + }, [user?.avatarUrl, token]); + function handleLogout() { logout(); router.push("/"); @@ -129,10 +159,26 @@ if (user?.role === "CUSTOMER" || user?.role === "ADMIN") { async function handleProfileSubmit(e) { e.preventDefault(); - setProfileSubmitting(true); setProfileError(null); + + if (profileForm.password && profileForm.password !== profileForm.confirmPassword) { + setProfileError("Passwords do not match."); + return; + } + + setProfileSubmitting(true); setProfileSuccess(null); + const payload = { + firstName: profileForm.firstName, + lastName: profileForm.lastName, + email: profileForm.email, + phone: profileForm.phone, + }; + if (profileForm.password) { + payload.password = profileForm.password; + } + try { const res = await fetch(`${API_BASE}/api/v1/auth/me`, { method: "PUT", @@ -140,7 +186,7 @@ if (user?.role === "CUSTOMER" || user?.role === "ADMIN") { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, - body: JSON.stringify(profileForm), + body: JSON.stringify(payload), }); const data = await res.json().catch(() => null); @@ -149,6 +195,7 @@ if (user?.role === "CUSTOMER" || user?.role === "ADMIN") { } await refreshUser(); + setProfileForm((prev) => ({ ...prev, password: "", confirmPassword: "" })); setProfileSuccess("Profile updated successfully."); } @@ -340,12 +387,14 @@ if (user?.role === "CUSTOMER" || user?.role === "ADMIN") { return

Loading…

; } + const displayName = [user.firstName, user.lastName].filter(Boolean).join(" ") || user.username; + const fields = [ - {label: "Full Name", value: user.fullName}, + {label: "First Name", value: user.firstName || "N/A"}, + {label: "Last Name", value: user.lastName || "N/A"}, {label: "Username", value: user.username}, {label: "Email", value: user.email}, - {label: "Phone", value: user.phone || "—"}, - {label: "Role", value: user.role}, + {label: "Phone", value: user.phone || "N/A"}, ...(user.storeName ? [{ label: "Store", value: user.storeName }] : []), ]; @@ -353,14 +402,14 @@ if (user?.role === "CUSTOMER" || user?.role === "ADMIN") {
- {user.avatarUrl ? ( - {user.fullName + {avatarObjectUrl ? ( + {displayName} ) : ( - (user.fullName || user.username).charAt(0).toUpperCase() + displayName.charAt(0).toUpperCase() )}
-

{user.fullName || user.username}

+

{displayName}

{user.role}
@@ -377,13 +426,23 @@ if (user?.role === "CUSTOMER" || user?.role === "ADMIN") { {profileError &&
{profileError}
} {profileSuccess &&
{profileSuccess}
} + + +