feat(profile): add dynamic authenticated profile integration#208
feat(profile): add dynamic authenticated profile integration#208SagitQuark wants to merge 10 commits into
Conversation
|
Implemented the requested backend + frontend profile integration changes for #198. Would love to get your feedback on the implementation |
There was a problem hiding this comment.
Pull request overview
This PR aims to replace hardcoded admin profile UI data with authenticated, backend-driven user profile data (fetch + update), while aligning public auth routes and improving loading UX via skeleton states.
Changes:
- Added backend endpoints/service methods to fetch/update the currently authenticated user’s profile (
/api/v1/users/me). - Added a new public registration flow via
/public/api/v1/auth/registerand updated the frontend Register page accordingly. - Integrated profile fetching/updating into the admin Profile page and navbar, plus added a reusable profile skeleton loader and removed unused profile UI actions.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| RestroHub/src/main/java/com/restroly/qrmenu/user/service/UserServiceImpl.java | Implements current-user profile fetch/update using SecurityContextHolder. |
| RestroHub/src/main/java/com/restroly/qrmenu/user/service/UserService.java | Adds getCurrentUserProfile / updateUserProfile to the service contract. |
| RestroHub/src/main/java/com/restroly/qrmenu/user/dto/UserProfileResponseDTO.java | Introduces response DTO for the profile payload. |
| RestroHub/src/main/java/com/restroly/qrmenu/user/dto/UserProfileRequestDTO.java | Introduces request DTO for profile updates (name/phone). |
| RestroHub/src/main/java/com/restroly/qrmenu/user/controller/UserController.java | Replaces prior user-management endpoints with /me profile endpoints only. |
| RestroHub/src/main/java/com/restroly/qrmenu/auth/service/AuthServiceImpl.java | Adds register implementation for the new public registration endpoint. |
| RestroHub/src/main/java/com/restroly/qrmenu/auth/service/AuthService.java | Adds register(RegisterRequest) to the auth service interface. |
| RestroHub/src/main/java/com/restroly/qrmenu/auth/dto/RegisterRequest.java | Adds registration request DTO with validation annotations. |
| RestroHub/src/main/java/com/restroly/qrmenu/auth/controller/AuthController.java | Adds /register endpoint under PUBLIC_API_VERSION + "/auth". |
| RestroHub-FrontEnd/src/services/user/profileService.js | Adds frontend API wrapper for /api/v1/users/me fetch/update. |
| RestroHub-FrontEnd/src/services/common/api.js | Changes axios interceptor to always attach the access token. |
| RestroHub-FrontEnd/src/pages/public/Register.jsx | Updates register API route to /public/api/v1/auth/register. |
| RestroHub-FrontEnd/src/components/admin/profile/profileComponents/ProfileSkeleton.jsx | Adds skeleton loader for the profile page. |
| RestroHub-FrontEnd/src/components/admin/profile/profileComponents/ProfileHeader.jsx | Removes unused “Edit Cover” and “Edit Profile” actions. |
| RestroHub-FrontEnd/src/components/admin/profile/profileComponents/PersonalInfoCard.jsx | Wires edit/save flow to backend-driven name/phone fields and adds toasts. |
| RestroHub-FrontEnd/src/components/admin/profile/Profile.jsx | Fetches authenticated profile, shows skeleton while loading, persists updates. |
| RestroHub-FrontEnd/src/components/admin/Header.jsx | Fetches authenticated user profile for navbar rendering. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| .password(passwordEncoder.encode(registerRequest.getPassword())) | ||
| .isActive(true) | ||
| .isLocked(false) | ||
| .authProvider("LOCAL") |
| if (userRepository.findByEmail(registerRequest.getEmail()).isPresent()) { | ||
| throw new RuntimeException("User already exists with this email"); | ||
| } |
| @RestController | ||
| @RequestMapping("/api/v1/users") | ||
| @RequiredArgsConstructor | ||
| @Slf4j | ||
| @Tag(name = "User Management", description = "APIs for user registration and management") | ||
| public class UserController { | ||
|
|
||
| private final UserService userService; | ||
|
|
||
| @PostMapping("/register") | ||
| @Operation(summary = "Register a new user", description = "Creates a new user account") | ||
| public ResponseEntity<Map<String, Object>> registerUser( | ||
| @Valid @RequestBody UserRequest request) { | ||
| log.info("REST request to register user with email: {}", request.getEmail()); | ||
|
|
||
| UserResponse response = userService.registerUser(request); | ||
|
|
||
| return ResponseEntity.status(HttpStatus.CREATED) | ||
| .body(Map.of( | ||
| "success", true, | ||
| "message", "User registered successfully", | ||
| "data", response | ||
| )); | ||
| } | ||
|
|
||
| @GetMapping("/{id}") | ||
| @Operation(summary = "Get user by ID", description = "Retrieves a user by their ID") | ||
| public ResponseEntity<Map<String, Object>> getUserById( | ||
| @Parameter(description = "User ID") @PathVariable Long id) { | ||
| log.info("REST request to get user by ID: {}", id); | ||
|
|
||
| UserResponse response = userService.getUserById(id); | ||
|
|
||
| return ResponseEntity.ok(Map.of( | ||
| "success", true, | ||
| "message", "User retrieved successfully", | ||
| "data", response | ||
| )); | ||
| } | ||
|
|
||
| @GetMapping("/email/{email}") | ||
| @Operation(summary = "Get user by email", description = "Retrieves a user by their email") | ||
| public ResponseEntity<Map<String, Object>> getUserByEmail( | ||
| @Parameter(description = "User email") @PathVariable String email) { | ||
| log.info("REST request to get user by email: {}", email); | ||
|
|
||
| UserResponse response = userService.getUserByEmail(email); | ||
|
|
||
| return ResponseEntity.ok(Map.of( | ||
| "success", true, | ||
| "message", "User retrieved successfully", | ||
| "data", response | ||
| )); | ||
| @GetMapping("/me") | ||
| public ResponseEntity<UserProfileResponseDTO> getCurrentUserProfile() { | ||
| log.info("Fetching current authenticated user profile"); | ||
| UserProfileResponseDTO profile = userService.getCurrentUserProfile(); | ||
| return ResponseEntity.ok(profile); | ||
| } | ||
|
|
||
| @GetMapping | ||
| @Operation(summary = "Get all users", description = "Retrieves all users with pagination") | ||
| public ResponseEntity<Map<String, Object>> getAllUsers( | ||
| @PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) { | ||
| log.info("REST request to get all users with pagination"); | ||
|
|
||
| Page<UserResponse> response = userService.getAllUsers(pageable); | ||
|
|
||
| return ResponseEntity.ok(Map.of( | ||
| "success", true, | ||
| "message", "Users retrieved successfully", | ||
| "data", response.getContent(), | ||
| "pagination", Map.of( | ||
| "currentPage", response.getNumber(), | ||
| "totalPages", response.getTotalPages(), | ||
| "totalElements", response.getTotalElements(), | ||
| "size", response.getSize() | ||
| ) | ||
| )); | ||
| } | ||
|
|
||
| @GetMapping("/all") | ||
| @Operation(summary = "Get all users without pagination", description = "Retrieves all users") | ||
| public ResponseEntity<Map<String, Object>> getAllUsersWithoutPagination() { | ||
| log.info("REST request to get all users"); | ||
|
|
||
| List<UserResponse> response = userService.getAllUsers(); | ||
|
|
||
| return ResponseEntity.ok(Map.of( | ||
| "success", true, | ||
| "message", "Users retrieved successfully", | ||
| "data", response, | ||
| "count", response.size() | ||
| )); | ||
| } | ||
|
|
||
| @PutMapping("/update/{id}") | ||
| @Operation(summary = "Update user", description = "Updates an existing user") | ||
| public ResponseEntity<Map<String, Object>> updateUser( | ||
| @Parameter(description = "User ID") @PathVariable Long id, | ||
| @Valid @RequestBody UserRequest request) { | ||
| log.info("REST request to update user ID: {}", id); | ||
|
|
||
| UserResponse response = userService.updateUser(id, request); | ||
|
|
||
| return ResponseEntity.ok(Map.of( | ||
| "success", true, | ||
| "message", "User updated successfully", | ||
| "data", response | ||
| )); | ||
| } | ||
|
|
||
| @DeleteMapping("/delete/{id}") | ||
| @Operation(summary = "Delete user", description = "Deletes a user by their ID") | ||
| public ResponseEntity<Map<String, Object>> deleteUser( | ||
| @Parameter(description = "User ID") @PathVariable Long id) { | ||
| log.info("REST request to delete user ID: {}", id); | ||
|
|
||
| userService.deleteUser(id); | ||
|
|
||
| return ResponseEntity.ok(Map.of( | ||
| "success", true, | ||
| "message", "User deleted successfully" | ||
| )); | ||
| } | ||
|
|
||
| @PostMapping("/{userId}/roles") | ||
| @Operation(summary = "Assign roles to user", description = "Assigns one or more roles to a user") | ||
| public ResponseEntity<Map<String, Object>> assignRolesToUser( | ||
| @Parameter(description = "User ID") @PathVariable Long userId, | ||
| @RequestBody Set<Long> roleIds) { | ||
| log.info("REST request to assign roles {} to user ID: {}", roleIds, userId); | ||
|
|
||
| UserResponse response = userService.assignRolesToUser(userId, roleIds); | ||
|
|
||
| return ResponseEntity.ok(Map.of( | ||
| "success", true, | ||
| "message", "Roles assigned successfully", | ||
| "data", response | ||
| )); | ||
| } | ||
|
|
||
| @DeleteMapping("/{userId}/roles") | ||
| @Operation(summary = "Remove roles from user", description = "Removes one or more roles from a user") | ||
| public ResponseEntity<Map<String, Object>> removeRolesFromUser( | ||
| @Parameter(description = "User ID") @PathVariable Long userId, | ||
| @RequestBody Set<Long> roleIds) { | ||
| log.info("REST request to remove roles {} from user ID: {}", roleIds, userId); | ||
|
|
||
| UserResponse response = userService.removeRolesFromUser(userId, roleIds); | ||
|
|
||
| return ResponseEntity.ok(Map.of( | ||
| "success", true, | ||
| "message", "Roles removed successfully", | ||
| "data", response | ||
| )); | ||
| } | ||
|
|
||
| @PatchMapping("/{id}/toggle-status") | ||
| @Operation(summary = "Toggle user status", description = "Toggles the active status of a user") | ||
| public ResponseEntity<Map<String, Object>> toggleUserStatus( | ||
| @Parameter(description = "User ID") @PathVariable Long id) { | ||
| log.info("REST request to toggle status for user ID: {}", id); | ||
|
|
||
| UserResponse response = userService.toggleUserStatus(id); | ||
|
|
||
| return ResponseEntity.ok(Map.of( | ||
| "success", true, | ||
| "message", "User status toggled successfully", | ||
| "data", response | ||
| )); | ||
| } | ||
|
|
||
| @GetMapping("/exists/email/{email}") | ||
| @Operation(summary = "Check if email exists", description = "Checks if a user with the given email exists") | ||
| public ResponseEntity<Map<String, Object>> checkEmailExists( | ||
| @Parameter(description = "Email to check") @PathVariable String email) { | ||
| log.info("REST request to check if email exists: {}", email); | ||
|
|
||
| boolean exists = userService.existsByEmail(email); | ||
|
|
||
| return ResponseEntity.ok(Map.of( | ||
| "success", true, | ||
| "exists", exists | ||
| )); | ||
| @PutMapping("/me") | ||
| public ResponseEntity<UserProfileResponseDTO> updateUserProfile( | ||
| @Valid @RequestBody UserProfileRequestDTO request) { | ||
| log.info("Updating current authenticated user profile"); | ||
| UserProfileResponseDTO updatedProfile = userService.updateUserProfile(request); | ||
| return ResponseEntity.ok(updatedProfile); | ||
| } |
| api.interceptors.request.use( | ||
| (config) => { | ||
| const accessToken = localStorage.getItem("accessToken"); | ||
| // Add token only for secure APIs | ||
| if (accessToken && config.url.includes("/secure/")) { | ||
| config.headers.Authorization = `Bearer ${accessToken}`; | ||
| if (accessToken) { | ||
| config.headers.Authorization = `Bearer ${accessToken}`; | ||
| } | ||
| if (config.data instanceof FormData) { |
| private String email; | ||
| private String phoneNumber; | ||
| private String pictureUrl; | ||
| // We can add resuruant specific fields later if user entry holds item |
|
|
||
| AuthResponse response = authService.register(registerRequest); | ||
|
|
||
| return ResponseEntity.ok( |
|
Hi @SagitQuark, Profile data is being fetched and updated properly, but the profile image is not updating correctly yet. Please update the implementation as follows:
The fields like For now, please focus on the above review points and update the profile image handling implementation accordingly. restrohub_208.mp4 |
84af967 to
666d705
Compare
Summary
Implements dynamic authenticated user profile integration for the admin panel as requested in Issue #198.
This PR replaces hardcoded profile data with backend-driven authenticated user data, adds profile update support, integrates reusable skeleton loaders, updates navbar user rendering, and removes unused profile UI actions.
Closes #198
Backend Changes
Added authenticated profile APIs
Added DTOs
UserProfileRequestDTOUserProfileResponseDTORegisterRequestUpdated services
UserServiceUserServiceImplAuth fixes
PUBLIC_API_VERSIONFrontend Changes
Dynamic profile integration
Navbar updates
UI cleanup
Removed unused profile UI actions per issue requirements:
Added services
profileService.jsAdditional Notes
Testing
Tested successfully:
Screenshots