From e6398042b7a09f9a7c00a819fe17d431040a84d8 Mon Sep 17 00:00:00 2001 From: Dat Truong Date: Sat, 26 Jul 2025 03:45:05 -0500 Subject: [PATCH 01/15] Bug Fix, Leaderboard Opt In, Daily Motivational Message --- .../config/AchievementInitializer.java | 14 +- .../controller/FriendController.java | 39 +++- .../controller/UserController.java | 28 +++ .../com/careconnect/dto/LeaderboardEntry.java | 23 ++ .../main/java/com/careconnect/model/User.java | 45 ++-- .../repository/FriendshipRepository.java | 13 ++ .../repository/PostRepository.java | 2 + .../repository/UserRepository.java | 59 +++-- .../com/careconnect/service/AuthService.java | 48 ++++ .../service/FamilyMemberService.java | 20 +- .../com/careconnect/service/FeedService.java | 65 +++++- .../service/GamificationService.java | 3 +- .../lib/config/router/app_router.dart | 132 +++++++---- .../pages/caregiver_dashboard.dart | 151 ++++++------ .../caregiver_gamification_landingpage.dart | 86 ------- .../pages/caregiver_gamification_screen.dart | 110 --------- .../pages/gamification_screen.dart | 91 ++++++-- .../pages/leaderboard_screen.dart | 113 +++++++++ .../presentation/pages/chat_inbox_screen.dart | 118 ++++++++++ .../presentation/pages/chat_room_screen.dart | 214 ++++++++++++++++++ .../presentation/pages/comment_screen.dart | 103 +++++---- .../pages/friend_requests_screen.dart | 104 ++++++--- .../presentation/pages/main_feed_screen.dart | 180 ++++++++------- .../presentation/pages/my_friend_screen.dart | 69 ++++-- .../presentation/pages/new_post_screen.dart | 96 ++++---- .../pages/search_user_screen.dart | 104 ++++++--- .../lib/services/gamification_service.dart | 18 ++ 27 files changed, 1428 insertions(+), 620 deletions(-) create mode 100644 careconnect2025/backend/core/src/main/java/com/careconnect/dto/LeaderboardEntry.java create mode 100644 careconnect2025/backend/core/src/main/java/com/careconnect/repository/FriendshipRepository.java create mode 100644 careconnect2025/frontend/lib/features/gamification/presentation/pages/leaderboard_screen.dart create mode 100644 careconnect2025/frontend/lib/features/social/presentation/pages/chat_inbox_screen.dart create mode 100644 careconnect2025/frontend/lib/features/social/presentation/pages/chat_room_screen.dart diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/config/AchievementInitializer.java b/careconnect2025/backend/core/src/main/java/com/careconnect/config/AchievementInitializer.java index 9b9d4705..e4dedcaa 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/config/AchievementInitializer.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/config/AchievementInitializer.java @@ -15,8 +15,20 @@ public class AchievementInitializer { @PostConstruct public void initAchievements() { try { - createAchievementIfNotExists("Verified Email", "Awarded for verifying your email.", "verified-icon.png"); createAchievementIfNotExists("First Login", "Awarded for logging in for the first time.", "login-icon.png"); + createAchievementIfNotExists( + "Made a Friend", + "Awarded for adding your first friend.", + "friend-icon.png" + ); + createAchievementIfNotExists( + "Added Family Member", + "Awarded for adding your first family member.", + "family-icon.png" + ); + createAchievementIfNotExists("First Post Created", "Awarded for creating your first post.", "post-icon.png"); + createAchievementIfNotExists("5-Day Streak", "Awarded for logging in 5 days in a row.", "streak-icon.png"); + } catch (Exception e) { // Log the error but don't fail application startup System.err.println("Failed to initialize achievements: " + e.getMessage()); diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/controller/FriendController.java b/careconnect2025/backend/core/src/main/java/com/careconnect/controller/FriendController.java index e1797ace..c4447730 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/controller/FriendController.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/controller/FriendController.java @@ -1,9 +1,12 @@ package com.careconnect.controller; import com.careconnect.model.FriendRequest; +import com.careconnect.model.Friendship; import com.careconnect.model.User; import com.careconnect.repository.FriendRequestRepository; +import com.careconnect.repository.FriendshipRepository; import com.careconnect.repository.UserRepository; +import com.careconnect.service.GamificationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -12,15 +15,21 @@ import java.util.*; @RestController -@RequestMapping("/api/friends") +@RequestMapping("/v1/api/friends") public class FriendController { + @Autowired + private GamificationService gamificationService; + @Autowired private FriendRequestRepository friendRequestRepo; @Autowired private UserRepository userRepo; + @Autowired + private FriendshipRepository friendshipRepository; + // ✅ 1. Send friend request @PostMapping("/request") public ResponseEntity sendFriendRequest(@RequestBody Map payload) { @@ -83,7 +92,33 @@ public ResponseEntity acceptFriendRequest(@RequestBody Map body req.setStatus("accepted"); friendRequestRepo.save(req); - return ResponseEntity.ok("Friend request accepted"); + Optional fromUserOpt = userRepo.findById(req.getFromUserId()); + Optional toUserOpt = userRepo.findById(req.getToUserId()); + + if (fromUserOpt.isEmpty() || toUserOpt.isEmpty()) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("User not found"); + } + + User fromUser = fromUserOpt.get(); + User toUser = toUserOpt.get(); + + Friendship friendship = Friendship.builder() + .user1(fromUser) + .user2(toUser) + .status("CONFIRMED") + .build(); + + friendshipRepository.save(friendship); + + long friendCount = friendshipRepository.countByUserId(fromUser.getId()); + + if (friendCount == 1) { // this is the first confirmed friend added + gamificationService.unlockAchievement( + fromUser.getId(), "Added First Friend", 50 + ); + } + + return ResponseEntity.ok("Friend request accepted and friendship created"); } // ✅ 4. Reject a friend request diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/controller/UserController.java b/careconnect2025/backend/core/src/main/java/com/careconnect/controller/UserController.java index d4439fec..0eb6b320 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/controller/UserController.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/controller/UserController.java @@ -18,6 +18,7 @@ import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; @RestController @@ -179,6 +180,33 @@ public ResponseEntity> searchUsers( return ResponseEntity.ok(response); } + @PutMapping("/{userId}/leaderboard-opt-in") + public ResponseEntity toggleLeaderboardOptIn( + @PathVariable Long userId, + @RequestBody Map body) { + + Boolean optIn = body.get("optIn"); + if (optIn == null) { + return ResponseEntity.badRequest().body(Collections.singletonMap("error", "Missing 'optIn' in request body.")); + } + + Optional userOpt = userRepo.findById(userId); + if (userOpt.isEmpty()) { + return ResponseEntity.status(404).body(Collections.singletonMap("error", "User not found.")); + } + + User user = userOpt.get(); + user.setLeaderboardOptIn(optIn); + userRepo.save(user); + + return ResponseEntity.ok(Collections.singletonMap("message", "Leaderboard opt-in status updated.")); + } + + @GetMapping("/leaderboard") + public ResponseEntity> getLeaderboard() { + List leaderboard = userRepo.findLeaderboard(); + return ResponseEntity.ok(leaderboard); + } @GetMapping("/check-email") @Operation( diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/dto/LeaderboardEntry.java b/careconnect2025/backend/core/src/main/java/com/careconnect/dto/LeaderboardEntry.java new file mode 100644 index 00000000..adbd69c1 --- /dev/null +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/dto/LeaderboardEntry.java @@ -0,0 +1,23 @@ +package com.careconnect.dto; + +public class LeaderboardEntry { + private Long userId; + private String name; + private int xp; + private int level; + private String profileImageUrl; + + public LeaderboardEntry(Long userId, String name, int xp, int level, String profileImageUrl) { + this.userId = userId; + this.name = name; + this.xp = xp; + this.level = level; + this.profileImageUrl = profileImageUrl; + } + + public Long getUserId() { return userId; } + public String getName() { return name; } + public int getXp() { return xp; } + public int getLevel() { return level; } + public String getProfileImageUrl() { return profileImageUrl; } +} \ No newline at end of file diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java b/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java index 189e5839..7d2dd209 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java @@ -6,6 +6,7 @@ import lombok.NoArgsConstructor; import lombok.Builder; import java.sql.Timestamp; +import java.time.LocalDate; @Entity @Table(name = "users") @@ -20,10 +21,6 @@ public class User { private Long id; private String name; - - // Split name into first and last name for better usability - private String firstName; - private String lastName; @Column(unique = true, nullable = false) private String email; @@ -34,6 +31,15 @@ public class User { @Column(name = "password_hash") private String passwordHash; + @Column(name = "last_login_date") + private LocalDate lastLoginDate; + + @Column(name = "login_streak") + private int loginStreak; + + @Column(name = "leaderboard_opt_in", nullable = false) + private Boolean leaderboardOptIn = true; + @Enumerated(EnumType.STRING) @Column(nullable = false) private com.careconnect.security.Role role; @@ -43,9 +49,6 @@ public class User { private Boolean isVerified = false; private String verificationToken; - - @Column(name = "stripe_customer_id") - private String stripeCustomerId; private Timestamp createdAt; @@ -70,26 +73,36 @@ public boolean isActive() { // Additional getters for compatibility public Long getId() { return id; } public String getName() { return name; } - public String getFirstName() { return firstName; } - public String getLastName() { return lastName; } public String getEmail() { return email; } public com.careconnect.security.Role getRole() { return role; } public Boolean getIsVerified() { return isVerified; } - public String getVerificationToken() { return verificationToken; } public String getStatus() { return status; } public String getProfileImageUrl() { return profileImageUrl; } - public String getStripeCustomerId() { return stripeCustomerId; } - + public LocalDate getLastLoginDate() { + return lastLoginDate; + } + public int getLoginStreak() { + return loginStreak; + } + public Boolean getLeaderboardOptIn() { + return leaderboardOptIn; + } + // Additional setters for compatibility public void setId(Long id) { this.id = id; } public void setName(String name) { this.name = name; } - public void setFirstName(String firstName) { this.firstName = firstName; } - public void setLastName(String lastName) { this.lastName = lastName; } public void setEmail(String email) { this.email = email; } public void setRole(com.careconnect.security.Role role) { this.role = role; } public void setIsVerified(Boolean isVerified) { this.isVerified = isVerified; } - public void setVerificationToken(String verificationToken) { this.verificationToken = verificationToken; } public void setStatus(String status) { this.status = status; } public void setProfileImageUrl(String profileImageUrl) { this.profileImageUrl = profileImageUrl; } - public void setStripeCustomerId(String stripeCustomerId) { this.stripeCustomerId = stripeCustomerId; } + public void setLastLoginDate(LocalDate lastLoginDate) { + this.lastLoginDate = lastLoginDate; + } + public void setLoginStreak(int loginStreak) { + this.loginStreak = loginStreak; + } + public void setLeaderboardOptIn(Boolean leaderboardOptIn) { + this.leaderboardOptIn = leaderboardOptIn; + } } diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/repository/FriendshipRepository.java b/careconnect2025/backend/core/src/main/java/com/careconnect/repository/FriendshipRepository.java new file mode 100644 index 00000000..7b6b6a1d --- /dev/null +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/repository/FriendshipRepository.java @@ -0,0 +1,13 @@ +package com.careconnect.repository; + +import com.careconnect.model.Friendship; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; +import org.springframework.data.repository.query.Param; + +@Repository +public interface FriendshipRepository extends JpaRepository { + @Query("SELECT COUNT(f) FROM Friendship f WHERE f.user1.id = :userId OR f.user2.id = :userId") + long countByUserId(@Param("userId") Long userId); +} diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/repository/PostRepository.java b/careconnect2025/backend/core/src/main/java/com/careconnect/repository/PostRepository.java index 419a9de0..f244f7c5 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/repository/PostRepository.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/repository/PostRepository.java @@ -7,4 +7,6 @@ public interface PostRepository extends JpaRepository { List findAllByUserIdOrderByCreatedAtDesc(Long userId); List findAllByOrderByCreatedAtDesc(); // For global feed + List findAllByUserIdInOrderByCreatedAtDesc(List userIds); + long countByUserId(Long userId); } diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/repository/UserRepository.java b/careconnect2025/backend/core/src/main/java/com/careconnect/repository/UserRepository.java index d4dc7e4b..311dfeb8 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/repository/UserRepository.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/repository/UserRepository.java @@ -1,19 +1,44 @@ -package com.careconnect.repository; + package com.careconnect.repository; -import com.careconnect.model.User; -import com.careconnect.security.Role; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; + import com.careconnect.dto.LeaderboardEntry; + import com.careconnect.model.User; + import org.springframework.data.jpa.repository.JpaRepository; + import org.springframework.data.jpa.repository.Query; + import org.springframework.data.repository.query.Param; + import org.springframework.stereotype.Repository; -import java.util.Optional; -import java.util.List; -@Repository -public interface UserRepository extends JpaRepository { - Optional findByEmail(String email); - boolean existsByEmail(String email); - Optional findByEmailAndRole(String email, Role role); - boolean existsByEmailAndRole(String email, Role role); - Optional findByVerificationToken(String token); - List findByNameContainingIgnoreCaseOrEmailContainingIgnoreCase(String name, String email); - Optional findByStripeCustomerId(String stripeCustomerId); -} + import java.util.Optional; + import java.util.List; + @Repository + public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); + boolean existsByEmail(String email); + Optional findByEmailAndRole(String email, String role); + boolean existsByEmailAndRole(String email, String role); + Optional findByVerificationToken(String token); + List findByNameContainingIgnoreCaseOrEmailContainingIgnoreCase(String name, String email); + @Query(""" + SELECT u.id FROM User u + JOIN Friendship f ON + (f.user1.id = :userId AND f.user2.id = u.id OR + f.user2.id = :userId AND f.user1.id = u.id) + WHERE f.status = 'CONFIRMED' + """) + List findConfirmedFriendIds(@Param("userId") Long userId); + + @Query(""" + SELECT new com.careconnect.dto.LeaderboardEntry( + u.id, + u.name, + xp.xp, + xp.level, + u.profileImageUrl + ) + FROM User u + JOIN XPProgress xp ON xp.userId = u.id + WHERE u.leaderboardOptIn = true + ORDER BY xp.xp DESC + """) + List findLeaderboard(); + + } diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/service/AuthService.java b/careconnect2025/backend/core/src/main/java/com/careconnect/service/AuthService.java index 27fe16a0..79196e83 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/service/AuthService.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/service/AuthService.java @@ -37,6 +37,7 @@ import java.nio.charset.StandardCharsets; import java.sql.Timestamp; import java.time.Duration; +import java.time.LocalDate; import java.util.Collections; import java.util.Map; import java.util.Optional; @@ -51,6 +52,9 @@ @Service public class AuthService { + @Autowired + private GamificationService gamificationService; + @Autowired private UserRepository userRepository; @@ -279,6 +283,28 @@ public LoginResponse loginV2(LoginRequest req, if (!user.isActive()) throw new AuthenticationException("Account suspended"); + /* ---------------- Gamification: First Login & 5-Day Streak ---------------- */ + gamificationService.unlockAchievement(user.getId(), "First Login", 50); + + LocalDate today = LocalDate.now(); + LocalDate lastLogin = user.getLastLoginDate(); + int streak = user.getLoginStreak(); + + if (lastLogin != null && lastLogin.plusDays(1).equals(today)) { + streak++; + } else if (lastLogin == null || !lastLogin.equals(today)) { + streak = 1; + } + + user.setLastLoginDate(today); + user.setLoginStreak(streak); + userRepository.save(user); + + if (streak == 5) { + gamificationService.unlockAchievement(user.getId(), "5-Day Streak", 100); + } + + /* ---------------- Resolve profile info ------------------------------ */ Long patientId = null; Long caregiverId = null; @@ -342,6 +368,28 @@ public LoginResponse loginOAuth(String email, HttpServletResponse res) { if (!user.isActive()) throw new AuthenticationException("Account suspended"); + /* ---------------- Gamification: First Login & 5-Day Streak ---------------- */ + + gamificationService.unlockAchievement(user.getId(), "First Login", 50); + + LocalDate today = LocalDate.now(); + LocalDate lastLogin = user.getLastLoginDate(); + int streak = user.getLoginStreak(); + + if (lastLogin != null && lastLogin.plusDays(1).equals(today)) { + streak++; + } else if (lastLogin == null || !lastLogin.equals(today)) { + streak = 1; + } + + user.setLastLoginDate(today); + user.setLoginStreak(streak); + userRepository.save(user); + + if (streak == 5) { + gamificationService.unlockAchievement(user.getId(), "5-Day Streak", 100); + } + /* ---------------- Resolve profile info ------------------------------ */ Long patientId = null; Long caregiverId = null; diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/service/FamilyMemberService.java b/careconnect2025/backend/core/src/main/java/com/careconnect/service/FamilyMemberService.java index 93a89cf5..6f38ec2c 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/service/FamilyMemberService.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/service/FamilyMemberService.java @@ -28,7 +28,7 @@ public class FamilyMemberService { private static final Logger log = LoggerFactory.getLogger(FamilyMemberService.class); - + private final GamificationService gamificationService; private final FamilyMemberRepository familyMemberRepository; private final FamilyMemberLinkRepository familyMemberLinkRepository; private final UserRepository userRepository; @@ -36,14 +36,15 @@ public class FamilyMemberService { private final PasswordEncoder passwordEncoder; private final EmailService emailService; private final AnalyticsService analyticsService; - + public FamilyMemberService(FamilyMemberRepository familyMemberRepository, FamilyMemberLinkRepository familyMemberLinkRepository, UserRepository userRepository, PatientRepository patientRepository, PasswordEncoder passwordEncoder, EmailService emailService, - AnalyticsService analyticsService) { + AnalyticsService analyticsService, + GamificationService gamificationService) { this.familyMemberRepository = familyMemberRepository; this.familyMemberLinkRepository = familyMemberLinkRepository; this.userRepository = userRepository; @@ -51,6 +52,7 @@ public FamilyMemberService(FamilyMemberRepository familyMemberRepository, this.passwordEncoder = passwordEncoder; this.emailService = emailService; this.analyticsService = analyticsService; + this.gamificationService = gamificationService; log.debug("FamilyMemberService initialized - passwordEncoder is null: {}", passwordEncoder == null); } @@ -126,6 +128,18 @@ public FamilyMemberLinkResponse registerFamilyMember(FamilyMemberRegistration re ); log.debug("Sent access granted email to existing family member: {}", familyUser.getEmail()); + + boolean isFirstLink = familyMemberLinkRepository + .findActiveFamilyMembersByPatient(patientUser.getId(), LocalDateTime.now()) + .size() == 1; + + if (isFirstLink) { + gamificationService.unlockAchievement( + patientUser.getId(), + "Added Family Member", + 20 + ); + } return toFamilyMemberLinkResponse(link); } diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/service/FeedService.java b/careconnect2025/backend/core/src/main/java/com/careconnect/service/FeedService.java index 476394bb..cb661bab 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/service/FeedService.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/service/FeedService.java @@ -2,9 +2,11 @@ import com.careconnect.model.Post; import com.careconnect.repository.PostRepository; + import com.careconnect.dto.PostWithCommentCountDto; + import com.careconnect.repository.CommentRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; - + import com.careconnect.repository.UserRepository; import java.time.LocalDateTime; import java.util.List; @@ -12,10 +14,17 @@ public class FeedService { private final PostRepository postRepository; + private final CommentRepository commentRepository; + private final UserRepository userRepository; + + @Autowired + private GamificationService gamificationService; @Autowired - public FeedService(PostRepository postRepository) { + public FeedService(PostRepository postRepository, CommentRepository commentRepository, UserRepository userRepository) { this.postRepository = postRepository; + this.commentRepository = commentRepository; + this.userRepository = userRepository; } // Create a new post (with optional image URL) @@ -25,7 +34,14 @@ public Post createPost(Long userId, String content, String imageUrl) { post.setContent(content); post.setCreatedAt(LocalDateTime.now()); post.setImageUrl(imageUrl); // ✅ Assign uploaded image path - return postRepository.save(post); + Post savedPost = postRepository.save(post); + + long totalPosts = postRepository.countByUserId(userId); + if (totalPosts == 1) { + gamificationService.unlockAchievement(userId, "First Post Created", 50); + } + + return savedPost; } // Fetch all posts globally @@ -37,4 +53,47 @@ public List getAllPosts() { public List getPostsByUser(Long userId) { return postRepository.findAllByUserIdOrderByCreatedAtDesc(userId); } + + public List getAllPostsWithCommentCount() { + List posts = postRepository.findAllByOrderByCreatedAtDesc(); + return posts.stream().map(post -> { + String username = userRepository.findById(post.getUserId()) + .map(user -> { + String name = user.getName(); + return (name != null && !name.isEmpty()) ? name : user.getEmail(); + }).orElse("Unknown"); + + return new PostWithCommentCountDto( + post.getId(), + post.getUserId(), + post.getContent(), + post.getImageUrl(), + post.getCreatedAt(), + commentRepository.countByPostId(post.getId()), + username + ); + }).toList(); + } + + public List getPostsByUserAndFriends(Long userId) { + List friendIds = userRepository.findConfirmedFriendIds(userId); + friendIds.add(userId); + + List posts = postRepository.findAllByUserIdInOrderByCreatedAtDesc(friendIds); + return posts.stream().map(post -> { + String username = userRepository.findById(post.getUserId()) + .map(u -> (u.getName() != null && !u.getName().isEmpty()) ? u.getName() : u.getEmail()) + .orElse("Unknown"); + return new PostWithCommentCountDto( + post.getId(), + post.getUserId(), + post.getContent(), + post.getImageUrl(), + post.getCreatedAt(), + commentRepository.countByPostId(post.getId()), + username + ); + }).toList(); + } + } diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/service/GamificationService.java b/careconnect2025/backend/core/src/main/java/com/careconnect/service/GamificationService.java index f9656f75..0c8d33f7 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/service/GamificationService.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/service/GamificationService.java @@ -26,9 +26,8 @@ public GamificationService( this.userAchievementRepository = userAchievementRepository; } - // XP level threshold (simple static rule: every 100 XP = 1 level) private int calculateLevel(int xp) { - return xp / 100 + 1; + return xp / 50 + 1; } public XPProgress awardXp(Long userId, int amount) { diff --git a/careconnect2025/frontend/lib/config/router/app_router.dart b/careconnect2025/frontend/lib/config/router/app_router.dart index b6e09bc1..faf8991e 100644 --- a/careconnect2025/frontend/lib/config/router/app_router.dart +++ b/careconnect2025/frontend/lib/config/router/app_router.dart @@ -1,40 +1,45 @@ +//import 'package:care_connect_app/features/dashboard/presentation/pages/archive_patient.dart'; +//import 'package:care_connect_app/features/dashboard/presentation/pages/invite_family_member.dart'; +//import 'package:care_connect_app/features/dashboard/presentation/pages/media_upload.dart'; import 'package:care_connect_app/features/integrations/presentation/pages/home_monitoring_screen.dart'; import 'package:care_connect_app/features/integrations/presentation/pages/medication_management.dart'; import 'package:care_connect_app/features/integrations/presentation/pages/smart_devices.dart'; import 'package:care_connect_app/features/integrations/presentation/pages/wearables_screen.dart'; import 'package:care_connect_app/features/calls/presentation/pages/jitsi_meeting_screen.dart'; +import 'package:care_connect_app/features/notes/healthcare_notes.dart'; import 'package:care_connect_app/features/profile/presentation/pages/profile_settings_page.dart'; -import 'package:care_connect_app/pages/profile_page.dart'; -import 'package:care_connect_app/pages/settings_page.dart'; -import 'package:care_connect_app/pages/ai_configuration_page.dart'; -import 'package:care_connect_app/pages/file_management_page.dart'; -import 'package:care_connect_app/widgets/hybrid_video_call_widget.dart'; +import 'package:care_connect_app/features/ai/presentation/pages/speech_to_text.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:care_connect_app/config/theme/app_theme.dart'; +import 'package:care_connect_app/widgets/app_bar_helper.dart'; +import 'package:provider/provider.dart'; -import '../../features/welcome/presentation/pages/welcome_page.dart'; +import '../../features/ai/presentation/pages/voice_command_ai.dart'; +import '../../features/analytics/analytics_page.dart'; import '../../features/auth/presentation/pages/login_page.dart'; import '../../features/auth/presentation/pages/oauth_callback_page.dart'; +import '../../features/auth/presentation/pages/password_reset_page.dart'; +import '../../features/auth/presentation/pages/reset_password_screen.dart'; // ADD THIS IMPORT +import '../../features/auth/presentation/pages/sign_up_screen.dart'; +import '../../features/calls/presentation/pages/mobile_web_call.dart'; import '../../features/dashboard/presentation/pages/caregiver_dashboard.dart'; +//import '../../features/dashboard/presentation/pages/edit_patient.dart'; import '../../features/dashboard/presentation/pages/patient_dashboard.dart'; +import '../../features/gamification/presentation/pages/gamification_screen.dart'; import '../../features/onboarding/presentation/pages/patient_registration.dart'; -import '../../features/auth/presentation/pages/sign_up_screen.dart'; +import '../../features/payments/models/package_model.dart'; +import '../../features/payments/presentation/pages/payment_cancel_page.dart'; +import '../../features/payments/presentation/pages/payment_success_page.dart'; import '../../features/payments/presentation/pages/select_package_page.dart'; import '../../features/payments/presentation/pages/subscription_management_page.dart'; import '../../features/dashboard/presentation/pages/add_patient_screen.dart'; -import '../../features/auth/presentation/pages/password_reset_page.dart'; -import '../../features/auth/presentation/pages/reset_password_screen.dart'; // ADD THIS IMPORT -import '../../features/payments/models/package_model.dart'; -import '../../features/social/presentation/pages/main_feed_screen.dart'; -import '../../features/gamification/presentation/pages/caregiver_gamification_landingpage.dart'; -import '../../features/gamification/presentation/pages/gamification_screen.dart'; import '../../features/payments/presentation/pages/stripe_checkout_page.dart'; -import '../../features/analytics/analytics_page.dart'; -import '../../features/payments/presentation/pages/payment_success_page.dart'; -import '../../features/payments/presentation/pages/payment_cancel_page.dart'; +import '../../features/payments/presentation/pages/subscription_management_page.dart'; +import '../../features/social/presentation/pages/main_feed_screen.dart'; +import '../../features/welcome/presentation/pages/welcome_page.dart'; import '../../features/dashboard/presentation/pages/patient_status_page.dart'; import '../../providers/user_provider.dart'; -import 'package:provider/provider.dart'; /// Helper function to navigate to the appropriate dashboard based on user role void navigateToDashboard(BuildContext context, {String? role}) { @@ -43,7 +48,8 @@ void navigateToDashboard(BuildContext context, {String? role}) { if (userRole == null) { // If no role is found, redirect to login with the last known userType if available - final lastUserType = userProvider.user != null + final lastUserType = + userProvider.user != null && userProvider.user!.role != null ? userProvider.user!.role.toLowerCase() : 'patient'; context.go('/login', extra: {'userType': lastUserType}); @@ -57,6 +63,8 @@ final GoRouter appRouter = GoRouter( initialLocation: '/', routes: [ GoRoute(path: '/', builder: (_, __) => const WelcomePage()), + // GoRoute(path: '/login', builder: (_, __) => const LoginPage()), + // GoRoute(path: '/signup', builder: (_, __) => const SignUpScreen()), GoRoute( path: '/login', builder: (context, state) { @@ -175,16 +183,21 @@ final GoRouter appRouter = GoRouter( builder: (_, __) => const PatientRegistrationPage(), ), GoRoute(path: '/add-patient', builder: (_, __) => const AddPatientScreen()), + GoRoute(path: '/speech-to-text', builder: (_, __) => SpeechToTextFile()), + GoRoute(path: '/healthcare-notes', builder: (_, __) => HealthcareNotes()), + GoRoute(path: '/voice-commands', builder: (_, __) => VoiceCommandAI()), GoRoute( path: '/social-feed', builder: (context, state) { final userIdStr = state.uri.queryParameters['userId']; - final userId = userIdStr != null ? int.tryParse(userIdStr) : 1; - return MainFeedScreen(userId: userId ?? 1); + final userId = userIdStr != null ? int.tryParse(userIdStr) ?? -1 : -1; + + return MainFeedScreen(userId: userId); }, ), GoRoute( path: '/select-package', + // builder: (_, __) => const SelectPackagePage(), builder: (context, state) { final userProvider = Provider.of(context, listen: false); final user = userProvider.user; @@ -240,14 +253,11 @@ final GoRouter appRouter = GoRouter( path: '/gamification', builder: (_, __) => const GamificationScreen(), ), - GoRoute( - path: '/caregiver-gamification', - builder: (_, __) => CaregiverGamificationLandingScreen(), - ), GoRoute( path: '/stripe-checkout', builder: (context, state) { final pkg = state.extra as PackageModel; + // return StripeCheckoutPage(package: pkg); // Get userId and stripeCustomerId from query parameters if available final userId = state.uri.queryParameters['userId']; final stripeCustomerId = state.uri.queryParameters['stripeCustomerId']; @@ -324,6 +334,11 @@ final GoRouter appRouter = GoRouter( path: '/analytics', builder: (context, state) { final patientIdStr = state.uri.queryParameters['patientId']; + // if (patientIdStr == null) { + // return const Scaffold( + // body: Center(child: Text('No patientId provided in the URL.')), + // ); + // } if (patientIdStr == null || int.tryParse(patientIdStr) == null) { // Instead of showing an error screen, redirect back to dashboard final userProvider = Provider.of( @@ -354,12 +369,12 @@ final GoRouter appRouter = GoRouter( body: const Center(child: CircularProgressIndicator()), ); } - final patientId = int.tryParse(patientIdStr); if (patientId == null) { - return const Scaffold( - body: Center(child: Text('Invalid patientId.')), - ); + // return const Scaffold( + // body: Center(child: Text('Invalid patientId.')), + // ); + return Scaffold(body: Center(child: Text('Invalid patientId.'))); } return AnalyticsPage(patientId: patientId); }, @@ -452,7 +467,10 @@ final GoRouter appRouter = GoRouter( return JitsiMeetingScreen(roomName: roomName); }, ), - GoRoute(path: '/wearables', builder: (_, __) => const WearablesScreen()), + GoRoute( + path: '/wearables', + builder: (_, __) => const WearablesScreen(), + ), GoRoute( path: '/home-monitoring', builder: (_, __) => const HomeMonitoringScreen(), @@ -469,22 +487,6 @@ final GoRouter appRouter = GoRouter( path: '/profile-settings', builder: (_, __) => const ProfileSettingsPage(), ), - GoRoute(path: '/profile', builder: (_, __) => const ProfilePage()), - GoRoute(path: '/settings', builder: (_, __) => const SettingsPage()), - GoRoute( - path: '/file-management', - builder: (_, __) => const FileManagementPage(), - ), - GoRoute( - path: '/ai-configuration', - builder: (_, __) => const AIConfigurationPage(), - ), - - // Video Call Test Route - GoRoute( - path: '/video-call-test', - builder: (_, __) => const VideoCallTestPage(), - ), // Handle routes from legacy menus GoRoute( @@ -519,5 +521,45 @@ final GoRouter appRouter = GoRouter( return '/dashboard?tab=emergency'; }, ), + + /*GoRoute( + path: '/edit', + builder: (context, state) { + final patientId = state.uri.queryParameters['patientId'] ?? ''; + return EditScreen(linkId: patientId); + }, + ),*/ + /*GoRoute( + path: '/archive', + builder: (_, __) => const ArchiveScreen(linkId: ''), + ),*/ + + /*GoRoute( + path: '/invite_Family_Member', + builder: (_, __) => const InviteFamilyMemberScreen(), + ),*/ + + //GoRoute(path: '/MediaScreen', builder: (_, __) => const MediaScreen()), + GoRoute( + path: '/mobile-web-call', + builder: (context, state) { + // Retrieve the query parameters + final patientName = + state.uri.queryParameters['patientName'] ?? 'Unknown'; + final roomId = state.uri.queryParameters['roomId'] ?? 'Unknown'; + + // Return the CallScreen widget and pass the parameters + return CallScreen( + patientName: patientName, + roomId: roomId, + isCaller: true, // Adjust this flag as needed + ); + }, + ), + + GoRoute( + path: '/subscription-management', + builder: (context, state) => const SubscriptionManagementPage(), + ), ], ); diff --git a/careconnect2025/frontend/lib/features/dashboard/presentation/pages/caregiver_dashboard.dart b/careconnect2025/frontend/lib/features/dashboard/presentation/pages/caregiver_dashboard.dart index 62a5fb21..7c06bdd6 100644 --- a/careconnect2025/frontend/lib/features/dashboard/presentation/pages/caregiver_dashboard.dart +++ b/careconnect2025/frontend/lib/features/dashboard/presentation/pages/caregiver_dashboard.dart @@ -1,6 +1,14 @@ +import 'dart:convert'; + +import 'package:care_connect_app/providers/user_provider.dart'; +import 'package:care_connect_app/services/api_service.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'dart:convert'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../../../widgets/ai_chat.dart'; +import '../../../social/presentation/pages/main_feed_screen.dart'; import '../../models/patient_model.dart'; import 'package:provider/provider.dart'; import 'package:care_connect_app/providers/user_provider.dart'; @@ -491,75 +499,78 @@ class _CaregiverDashboardState extends State { ); } - Widget _buildContentBasedOnState() { - if (loading) { - return const Center(child: CircularProgressIndicator()); - } else if (error != null) { - return _buildErrorState(); - } else if (patients.isEmpty) { - return _buildEmptyStateContent(); - } else { - return _buildPatientListContent(); - } - } - - Widget _buildErrorState() { - return Center( - child: Padding( - padding: const EdgeInsets.all(24.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.error_outline, - size: 64, - color: Theme.of(context).colorScheme.error, - ), - const SizedBox(height: 16), - const Text( - 'Error Loading Patients', - style: AppTheme.headingSmall, - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - error!, - style: AppTheme.bodyMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 24), - ElevatedButton( - onPressed: fetchPatients, - style: AppTheme.primaryButtonStyle, - child: const Text('Try Again'), - ), - ], - ), - ), - ); - } - - Widget _buildEmptyStateContent() { - // Use responsive utils for width calculation - final isMobile = context.isMobile; - - // Create a container with responsive width - return Center( - child: Container( - width: context.responsiveValue( - mobile: MediaQuery.of(context).size.width * 0.85, - tablet: 400.0, - ), - padding: const EdgeInsets.all(24), - decoration: !isMobile - ? BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 10, - spreadRadius: 1, + return Drawer( + child: ListView( + padding: EdgeInsets.zero, + children: [ + DrawerHeader( + decoration: BoxDecoration(color: Colors.blue.shade700), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const CircleAvatar( + radius: 28, + backgroundColor: Colors.white, + child: Icon(Icons.person, size: 30), + ), + const SizedBox(height: 8), + Text( + user?.name ?? 'User Name', + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + Text( + isFamilyMember ? 'Family Member' : 'Caregiver', + style: const TextStyle(color: Colors.white70), + ), + ], + ), + ), + ListTile( + leading: const Icon(Icons.dashboard), + title: const Text('Dashboard'), + onTap: () => Navigator.pop(context), + ), + ListTile( + leading: const Icon(Icons.emoji_events), + title: const Text('Gamification'), + onTap: () { + Navigator.pop(context); + context.go('/gamification'); + }, + ), + ListTile( + leading: const Icon(Icons.emoji_events), + title: const Text('Subscription Management'), + onTap: () { + Navigator.pop(context); + context.go('/subscription-management'); + }, + ), + ListTile( + leading: const Icon(Icons.people), + title: Text(isFamilyMember ? 'My Patients' : 'Patients'), + onTap: () { + Navigator.pop(context); + if (isFamilyMember) { + context.go('/family-patients'); + } else { + context.go('/patients'); + } + }, + ), + // Only show these options for caregivers + if (!isFamilyMember) ...[ + ListTile( + leading: const Icon(Icons.person_add), + title: const Text('Register Patient'), + onTap: () { + Navigator.pop(context); + context.go('/register/patient'); + }, ), ], ) diff --git a/careconnect2025/frontend/lib/features/gamification/presentation/pages/caregiver_gamification_landingpage.dart b/careconnect2025/frontend/lib/features/gamification/presentation/pages/caregiver_gamification_landingpage.dart index a902f6fd..e69de29b 100644 --- a/careconnect2025/frontend/lib/features/gamification/presentation/pages/caregiver_gamification_landingpage.dart +++ b/careconnect2025/frontend/lib/features/gamification/presentation/pages/caregiver_gamification_landingpage.dart @@ -1,86 +0,0 @@ -import 'package:flutter/material.dart'; -import 'caregiver_gamification_screen.dart'; // Your detail screen -import 'package:care_connect_app/widgets/common_drawer.dart'; - -class CaregiverGamificationLandingScreen extends StatelessWidget { - final List> patients = [ - { - 'name': 'Homer Simpson', - 'level': 4, - 'xp': 450, - 'streak': '3 days', - 'badges': 3, - 'lastActive': 'Today', - }, - { - 'name': 'Maggie Simpson', - 'level': 2, - 'xp': 120, - 'streak': '1 day', - 'badges': 1, - 'lastActive': 'Yesterday', - }, - { - 'name': 'Bart Simpson', - 'level': 3, - 'xp': 310, - 'streak': '2 days', - 'badges': 2, - 'lastActive': 'Today', - }, - ]; - - CaregiverGamificationLandingScreen({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Patient Engagement'), - backgroundColor: Colors.blue.shade900, - ), - drawer: const CommonDrawer(currentRoute: '/caregiver-gamification'), - body: ListView.builder( - itemCount: patients.length, - itemBuilder: (context, index) { - final patient = patients[index]; - return GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => - CaregiverGamificationScreen(patientName: patient['name']), - ), - ); - }, - child: Card( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - patient['name'], - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), - const SizedBox(height: 4), - Text('Level: ${patient['level']} | XP: ${patient['xp']}'), - Text( - 'Streak: ${patient['streak']} | Badges: ${patient['badges']}', - ), - Text('Last Active: ${patient['lastActive']}'), - ], - ), - ), - ), - ); - }, - ), - ); - } -} diff --git a/careconnect2025/frontend/lib/features/gamification/presentation/pages/caregiver_gamification_screen.dart b/careconnect2025/frontend/lib/features/gamification/presentation/pages/caregiver_gamification_screen.dart index 757c31c2..e69de29b 100644 --- a/careconnect2025/frontend/lib/features/gamification/presentation/pages/caregiver_gamification_screen.dart +++ b/careconnect2025/frontend/lib/features/gamification/presentation/pages/caregiver_gamification_screen.dart @@ -1,110 +0,0 @@ -import 'package:flutter/material.dart'; - -class CaregiverGamificationScreen extends StatelessWidget { - final String patientName; - - const CaregiverGamificationScreen({super.key, required this.patientName}); - - @override - Widget build(BuildContext context) { - final patientGamificationData = { - 'name': patientName, - 'badges': ['3-Day Streak', 'Hydration Hero', 'Early Riser'], - 'xp': 450, - 'level': 4, - 'streak': 3, - }; - - return Scaffold( - appBar: AppBar( - title: const Text('Patient Engagement Summary'), - backgroundColor: Theme.of(context).appBarTheme.backgroundColor, - iconTheme: Theme.of(context).appBarTheme.iconTheme, - titleTextStyle: Theme.of(context).appBarTheme.titleTextStyle, - ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: ListView( - children: [ - Text( - 'Patient: ${patientGamificationData['name']}', - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 12), - Card( - elevation: 2, - child: ListTile( - leading: Icon( - Icons.star, - color: Theme.of(context).colorScheme.secondary, - ), - title: Text('XP: ${patientGamificationData['xp']}'), - subtitle: Text('Level: ${patientGamificationData['level']}'), - ), - ), - const SizedBox(height: 12), - Card( - elevation: 2, - child: ListTile( - leading: Icon( - Icons.local_fire_department, - color: Theme.of(context).colorScheme.error, - ), - title: const Text('Current Streak'), - subtitle: Text('${patientGamificationData['streak']} days'), - ), - ), - const SizedBox(height: 20), - const Text( - 'Earned Badges', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), - ), - const SizedBox(height: 8), - Wrap( - spacing: 10, - children: (patientGamificationData['badges'] as List).map( - (badge) { - return Chip( - label: Text(badge), - avatar: const Icon(Icons.emoji_events, color: Colors.amber), - backgroundColor: Colors.blue.shade50, - ); - }, - ).toList(), - ), - const SizedBox(height: 20), - const Text( - 'Engagement Overview (Chart Placeholder)', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), - ), - const SizedBox(height: 8), - Container( - height: 150, - decoration: BoxDecoration( - color: Colors.blue.shade100, - borderRadius: BorderRadius.circular(10), - ), - child: const Center(child: Text('Chart Goes Here')), - ), - const SizedBox(height: 20), - ElevatedButton.icon( - onPressed: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Encouragement sent!")), - ); - }, - icon: const Icon(Icons.thumb_up), - label: const Text("Send Encouragement"), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.green.shade600, - foregroundColor: Colors.white, - ), - ), - ], - ), - ), - ); - } -} diff --git a/careconnect2025/frontend/lib/features/gamification/presentation/pages/gamification_screen.dart b/careconnect2025/frontend/lib/features/gamification/presentation/pages/gamification_screen.dart index 476d4c87..749e0c77 100644 --- a/careconnect2025/frontend/lib/features/gamification/presentation/pages/gamification_screen.dart +++ b/careconnect2025/frontend/lib/features/gamification/presentation/pages/gamification_screen.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:care_connect_app/services/gamification_service.dart'; +import 'leaderboard_screen.dart'; import 'package:confetti/confetti.dart'; import 'dart:math'; import 'package:shared_preferences/shared_preferences.dart'; import 'achievement_detail_screen.dart'; import 'package:care_connect_app/widgets/common_drawer.dart'; -import 'package:care_connect_app/widgets/app_bar_helper.dart'; class GamificationScreen extends StatefulWidget { const GamificationScreen({super.key}); @@ -14,6 +14,14 @@ class GamificationScreen extends StatefulWidget { State createState() => _GamificationScreenState(); } +final List motivationalMessages = [ + "You're doing great — keep going! 💪", + "Small steps every day lead to big results.", + "Believe in yourself and all that you are.", + "Progress, not perfection.", + "One day at a time — you got this!", +]; + class _GamificationScreenState extends State { int level = 1; int xp = 0; @@ -36,13 +44,26 @@ class _GamificationScreenState extends State { initializePrefsAndLoad(); } + late String dailyMessage; + + void pickDailyMessage() { + final dayIndex = DateTime.now().day % motivationalMessages.length; + dailyMessage = motivationalMessages[dayIndex]; + } + Future initializePrefsAndLoad() async { _prefs = await SharedPreferences.getInstance(); userId = - int.tryParse(_prefs.getString('userId') ?? '') ?? - 1; // <-- Get dynamic userId here + int.tryParse(_prefs.getString('userId') ?? '') ?? 1; + + // Show confetti for first login reward + final hasReceivedFirstLoginReward = + _prefs.getBool('first_login_reward_given') ?? false; + + previousAchievementCount = _prefs.getInt('achievement_count') ?? 0; await loadGamificationData(); + pickDailyMessage(); } @override @@ -63,14 +84,20 @@ class _GamificationScreenState extends State { previousAchievementCount = earned.length; await _prefs.setInt('achievement_count', earned.length); } + print("Earned Achievements: $earned"); + print("All Achievements: $all"); List> merged = (all).map>((a) { final match = earned.firstWhere( - (e) => e['title']?.toString().trim() == a['title']?.toString().trim(), - orElse: () => {}, + (e) => + (e['achievement']?['title']?.toString().trim().toLowerCase() ?? '') == + (a['title']?.toString().trim().toLowerCase() ?? ''), + orElse: () => null, ); return {...a, 'unlocked': match != null}; }).toList(); + print("Earned Achievements: $earned"); + print("All Achievements: $all"); setState(() { level = progress['level']; @@ -88,12 +115,15 @@ class _GamificationScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBarHelper.createAppBar( - context, - title: 'Achievements', - additionalActions: [ + appBar: AppBar( + backgroundColor: Colors.blue.shade900, + title: const Text( + 'Care Connect', + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + ), + actions: [ IconButton( - icon: const Icon(Icons.emoji_events), + icon: const Icon(Icons.emoji_events, color: Colors.white), onPressed: () { Navigator.push( context, @@ -106,7 +136,7 @@ class _GamificationScreenState extends State { ), ], ), - drawer: const CommonDrawer(currentRoute: '/gamification'), + drawer: CommonDrawer(currentRoute: '/gamification'), backgroundColor: Colors.white, body: Stack( children: [ @@ -140,9 +170,21 @@ class _GamificationScreenState extends State { color: Colors.indigo, ), ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Text( + dailyMessage, + style: const TextStyle( + fontSize: 16, + fontStyle: FontStyle.italic, + color: Colors.black87, + ), + textAlign: TextAlign.center, + ), + ), const SizedBox(height: 10), LinearProgressIndicator( - value: xp / xpTarget, + value: (xp % xpTarget) / xpTarget, backgroundColor: Colors.grey.shade300, color: Colors.blue.shade900, minHeight: 10, @@ -174,13 +216,30 @@ class _GamificationScreenState extends State { const SizedBox(height: 20), Expanded( child: ListView.builder( - itemCount: allAchievements.length, + itemCount: allAchievements.where((a) => a['unlocked'] == true).length, itemBuilder: (context, index) { - final achievement = allAchievements[index]; + final unlockedAchievements = allAchievements.where((a) => a['unlocked'] == true).toList(); + final achievement = unlockedAchievements[index]; return buildAchievement(achievement); }, ), ), + const SizedBox(height: 20), + ElevatedButton.icon( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const LeaderboardScreen()), + ); + }, + icon: const Icon(Icons.leaderboard), + label: const Text("View Leaderboard"), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.deepPurple, + foregroundColor: Colors.white, + ), + ), + const SizedBox(height: 20), ], ), ), @@ -221,9 +280,9 @@ class _GamificationScreenState extends State { Expanded( child: Text( achievement['title'] ?? 'Unnamed Achievement', - style: TextStyle( + style: const TextStyle( fontSize: 16, - color: Theme.of(context).colorScheme.onSurface, + color: Colors.black87, fontWeight: FontWeight.w500, ), ), diff --git a/careconnect2025/frontend/lib/features/gamification/presentation/pages/leaderboard_screen.dart b/careconnect2025/frontend/lib/features/gamification/presentation/pages/leaderboard_screen.dart new file mode 100644 index 00000000..82b0e82d --- /dev/null +++ b/careconnect2025/frontend/lib/features/gamification/presentation/pages/leaderboard_screen.dart @@ -0,0 +1,113 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:care_connect_app/config/env_constant.dart'; +import 'package:care_connect_app/services/api_service.dart'; + +class LeaderboardScreen extends StatefulWidget { + const LeaderboardScreen({super.key}); + + @override + State createState() => _LeaderboardScreenState(); +} + +class _LeaderboardScreenState extends State { + List leaderboard = []; + bool isLoading = true; + + @override + void initState() { + super.initState(); + fetchLeaderboard(); + } + + Future fetchLeaderboard() async { + final uri = Uri.parse('${getBackendBaseUrl()}/v1/api/users/leaderboard'); + + try { + final headers = await ApiService.getAuthHeaders(); + final response = await http.get(uri, headers: headers); + + if (response.statusCode == 200) { + setState(() { + leaderboard = jsonDecode(response.body); + isLoading = false; + }); + } else { + print("Failed to fetch leaderboard: ${response.statusCode}"); + } + } catch (e) { + print("Error fetching leaderboard: $e"); + } + } + + String getMedalEmoji(int index) { + switch (index) { + case 0: + return '🥇'; + case 1: + return '🥈'; + case 2: + return '🥉'; + default: + return '#${index + 1}'; + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Leaderboard'), + backgroundColor: Colors.blue.shade900, + ), + body: isLoading + ? const Center(child: CircularProgressIndicator()) + : leaderboard.isEmpty + ? const Center(child: Text('No leaderboard data available.')) + : ListView.builder( + itemCount: leaderboard.length, + itemBuilder: (context, index) { + final user = leaderboard[index]; + final medal = getMedalEmoji(index); + + return Card( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + elevation: 2, + child: ListTile( + leading: CircleAvatar( + backgroundImage: user['profileImageUrl'] != null && + user['profileImageUrl'].toString().isNotEmpty + ? NetworkImage(user['profileImageUrl']) + : null, + child: user['profileImageUrl'] == null || user['profileImageUrl'].toString().isEmpty + ? const Icon(Icons.person) + : null, + ), + title: Text( + user['name'] ?? 'Anonymous', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text('XP: ${user['xp']} | Level: ${user['level']}'), + trailing: Text( + medal, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: index == 0 + ? Colors.amber + : index == 1 + ? Colors.grey + : index == 2 + ? Colors.brown + : Colors.black54, + ), + ), + ), + ); + }, + ), + ); + } +} diff --git a/careconnect2025/frontend/lib/features/social/presentation/pages/chat_inbox_screen.dart b/careconnect2025/frontend/lib/features/social/presentation/pages/chat_inbox_screen.dart new file mode 100644 index 00000000..dd5734b9 --- /dev/null +++ b/careconnect2025/frontend/lib/features/social/presentation/pages/chat_inbox_screen.dart @@ -0,0 +1,118 @@ +import 'package:care_connect_app/services/api_service.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../../../../providers/user_provider.dart'; +import '../model/conversation_preview_dto.dart'; +import 'chat_room_screen.dart'; +import 'my_friend_screen.dart'; + + +class ChatInboxScreen extends StatefulWidget { + const ChatInboxScreen({super.key}); + + @override + State createState() => _ChatInboxScreenState(); +} + +class _ChatInboxScreenState extends State { + int? _userId; + List inbox = []; + bool isLoading = true; + bool _initialized = false; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + if (!_initialized) { + final user = Provider.of(context, listen: false).user; + if (user == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('User not logged in')), + ); + return; + } + + _userId = user.id; + fetchInbox(); + _initialized = true; + } + } + + Future fetchInbox() async { + setState(() => isLoading = true); + try { + final data = await ApiService.getInbox(_userId!); + setState(() { + inbox = (data as List) + .map((json) => ConversationPreviewDto.fromJson(json)) + .toList();; + isLoading = false; + }); + } catch (e) { + setState(() => isLoading = false); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Failed to load inbox: $e'))); + } + } + + @override + Widget build(BuildContext context) { + if (_userId == null) { + return Scaffold( + appBar: AppBar(title: const Text('Messages')), + body: const Center(child: CircularProgressIndicator()), + ); + } + + return Scaffold( + appBar: AppBar( + title: const Text('Messages'), + actions: [ + IconButton( + icon: const Icon(Icons.group), + tooltip: 'My Friends', + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => MyFriendsScreen()), + ); + }, + ), + ], + ), + body: isLoading + ? const Center(child: CircularProgressIndicator()) + : inbox.isEmpty + ? const Center(child: Text('No messages yet')) + : ListView.builder( + itemCount: inbox.length, + itemBuilder: (context, index) { + final convo = inbox[index]; + return ListTile( + title: Text(convo.peerName), + subtitle: Text(convo.content), + trailing: Text( + convo.timestamp.toIso8601String().split('T').join(' • '), + style: const TextStyle(fontSize: 12, color: Colors.grey), + ), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => + ChatRoomScreen( + peerUserId: convo.peerId, + peerName: convo.peerName, + ), + ), + ); + }, + ); + }, + ), + ); + } +} diff --git a/careconnect2025/frontend/lib/features/social/presentation/pages/chat_room_screen.dart b/careconnect2025/frontend/lib/features/social/presentation/pages/chat_room_screen.dart new file mode 100644 index 00000000..d28c3935 --- /dev/null +++ b/careconnect2025/frontend/lib/features/social/presentation/pages/chat_room_screen.dart @@ -0,0 +1,214 @@ + import 'dart:async'; + import 'package:care_connect_app/services/api_service.dart'; +import 'package:flutter/foundation.dart'; + import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + + import '../../../../providers/user_provider.dart'; +import '../model/message_dto.dart'; + + class ChatRoomScreen extends StatefulWidget { + final int peerUserId; + final String peerName; + + const ChatRoomScreen({ + super.key, + required this.peerUserId, + required this.peerName, + }); + + @override + State createState() => _ChatRoomScreenState(); + } + + class _ChatRoomScreenState extends State { + final TextEditingController _controller = TextEditingController(); + + int? _currentUserId; + List messages = []; + bool isLoading = true; + bool _initialLoading = true; + Timer? _pollingTimer; + bool _initialized = false; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + if (!_initialized) { + final user = Provider.of(context, listen: false).user; + if (user == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('User not logged in')), + ); + return; + } + + _currentUserId = user.id; + fetchConversation(); + _startPolling(); + + _initialized = true; + } + } + + void _startPolling() { + _pollingTimer = Timer.periodic(const Duration(seconds: 2), (_) { + if (_currentUserId != null && mounted) { + fetchConversation(silent: true); + } + }); + } + + @override + void dispose() { + _pollingTimer?.cancel(); + _controller.dispose(); + super.dispose(); + } + + Future fetchConversation({bool silent = false}) async { + if (_currentUserId == null) return; + + if (!silent && _initialLoading) { + setState(() => isLoading = true); + } + + + try { + final data = await ApiService.getConversation( + user1: _currentUserId!, + user2: widget.peerUserId, + ); + + final updatedMessages = (data as List) + .map((json) => MessageDto.fromJson(json)) + .toList(); + + //Only update UI if messages actually changed + if (!listEquals(messages, updatedMessages)) { + if (mounted) { + setState(() { + messages = updatedMessages; + isLoading = false; + _initialLoading = false; + }); + } + } else if (_initialLoading) { + setState(() { + isLoading = false; + _initialLoading = false; + }); + } + } catch (e) { + if (!silent) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Failed to load conversation: $e')), + ); + } + setState(() { + isLoading = false; + _initialLoading = false; + }); + } + } + + Future sendMessage() async { + final content = _controller.text.trim(); + if (content.isEmpty || _currentUserId == null) return; + + try { + await ApiService.sendMessage( + senderId: _currentUserId!, + receiverId: widget.peerUserId, + content: content, + ); + + _controller.clear(); + await fetchConversation(); + } catch (e) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Failed to send message'))); + } + } + + Widget buildMessageBubble(MessageDto msg) { + final isMe = msg.senderId == _currentUserId; + + return Align( + alignment: isMe ? Alignment.centerRight : Alignment.centerLeft, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 10), + decoration: BoxDecoration( + color: isMe ? Colors.blue.shade100 : Colors.grey.shade300, + borderRadius: BorderRadius.circular(12), + ), + child: Text(msg.content), + ), + ); + } + + @override + Widget build(BuildContext context) { + if (_currentUserId == null) { + return Scaffold( + appBar: AppBar(title: const Text('Chat')), + body: const Center(child: CircularProgressIndicator()), + ); + } + + return Scaffold( + appBar: AppBar( + title: Text(widget.peerName), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.pop(context), + ), + ), + body: Column( + children: [ + Expanded( + child: isLoading + ? const Center(child: CircularProgressIndicator()) + : ListView.builder( + reverse: true, + itemCount: messages.length, + itemBuilder: (context, index) { + final reversedIndex = messages.length - 1 - index; + return buildMessageBubble(messages[reversedIndex]); + }, + ), + ), + const Divider(height: 1), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _controller, + decoration: const InputDecoration( + hintText: 'Type a message...', + border: OutlineInputBorder(), + contentPadding: EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + ), + ), + ), + const SizedBox(width: 8), + IconButton( + icon: const Icon(Icons.send), + onPressed: sendMessage, + ), + ], + ), + ), + ], + ), + ); + } + } diff --git a/careconnect2025/frontend/lib/features/social/presentation/pages/comment_screen.dart b/careconnect2025/frontend/lib/features/social/presentation/pages/comment_screen.dart index 246adb05..c5c1ddae 100644 --- a/careconnect2025/frontend/lib/features/social/presentation/pages/comment_screen.dart +++ b/careconnect2025/frontend/lib/features/social/presentation/pages/comment_screen.dart @@ -1,10 +1,13 @@ +import 'dart:convert'; + import 'package:care_connect_app/config/env_constant.dart'; -import 'package:care_connect_app/services/session_manager.dart'; +import 'package:care_connect_app/services/api_service.dart'; import 'package:flutter/material.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'dart:convert'; -import 'package:care_connect_app/widgets/app_bar_helper.dart'; -import 'package:care_connect_app/widgets/common_drawer.dart'; +import 'package:http/http.dart' as http; +import 'package:provider/provider.dart'; + +import '../../../../providers/user_provider.dart'; +import '../model/comment_dto.dart'; class CommentScreen extends StatefulWidget { final int postId; @@ -16,34 +19,57 @@ class CommentScreen extends StatefulWidget { } class _CommentScreenState extends State { - List comments = []; + List comments = []; + final TextEditingController _commentController = TextEditingController(); bool isLoading = true; bool isSubmitting = false; + int? _userId; + String? _username; + @override void initState() { super.initState(); - fetchComments(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + if (_userId == null) { + final user = Provider.of(context, listen: false).user; + if (user == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('User not logged in')), + ); + return; + } + + setState(() { + _userId = user.id; + _username = user.name; + }); + + fetchComments(); + } } Future fetchComments() async { setState(() => isLoading = true); - final session = SessionManager(); - await session.restoreSession(); - - final url = '${getBackendBaseUrl()}/api/comments/post/${widget.postId}'; + final url = '${getBackendBaseUrl()}/v1/api/comments/post/${widget.postId}'; try { - final response = await session.get(url); - print('Comments GET status: ${response.statusCode}'); - print('Comments GET body: ${response.body}'); + final headers = await ApiService.getAuthHeaders(); + final response = await http.get(Uri.parse(url), headers: headers); if (response.statusCode == 200) { final data = jsonDecode(response.body); setState(() { - comments = data is List ? data : (data['comments'] ?? []); + comments = (data as List) + .map((json) => CommentDto.fromJson(json)) + .toList(); isLoading = false; }); } else { @@ -51,43 +77,40 @@ class _CommentScreenState extends State { } } catch (e) { setState(() => isLoading = false); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('Error: ${e.toString()}'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: ${e.toString()}')), + ); } } Future submitComment() async { - final prefs = await SharedPreferences.getInstance(); - final userId = prefs.getString('userId'); - final username = prefs.getString('username'); // If you have it stored - - if (userId == null || _commentController.text.trim().isEmpty) { + if (_userId == null || _username == null || _commentController.text.trim().isEmpty) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('User ID missing or comment empty')), + const SnackBar(content: Text('User not loaded or comment empty')), ); return; } setState(() => isSubmitting = true); - final session = SessionManager(); - await session.restoreSession(); - - final url = '${getBackendBaseUrl()}/api/comments/post/${widget.postId}'; + final url = '${getBackendBaseUrl()}/v1/api/comments/post/${widget.postId}'; + final headers = await ApiService.getAuthHeaders(); + headers['Content-Type'] = 'application/json'; final body = jsonEncode({ - 'userId': int.parse(userId), - 'username': username, // Include username if your backend expects it! + 'userId': _userId, + 'username': _username, 'content': _commentController.text.trim(), }); - final response = await session.post(url, body: body); + final response = await http.post( + Uri.parse(url), + headers: headers, + body: body, + ); setState(() => isSubmitting = false); - print('Submit comment status: ${response.statusCode}'); - print('Submit comment body: ${response.body}'); if (response.statusCode == 201) { _commentController.clear(); @@ -99,17 +122,17 @@ class _CommentScreenState extends State { } } - Widget buildCommentCard(Map comment) { + Widget buildCommentCard(CommentDto comment) { return ListTile( leading: const Icon(Icons.comment), - title: Text(comment['username'] ?? 'User'), + title: Text(comment.username ?? 'Unknown User'), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(comment['content'] ?? ''), + Text(comment.content), const SizedBox(height: 4), Text( - comment['timestamp'] ?? '', + comment.timestamp.toIso8601String().split('T').join(' at '), style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], @@ -120,8 +143,10 @@ class _CommentScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBarHelper.createAppBar(context, title: 'Comments'), - drawer: const CommonDrawer(currentRoute: '/comments'), + appBar: AppBar( + title: const Text('Comments'), + backgroundColor: Colors.blue.shade900, + ), body: Column( children: [ Expanded( diff --git a/careconnect2025/frontend/lib/features/social/presentation/pages/friend_requests_screen.dart b/careconnect2025/frontend/lib/features/social/presentation/pages/friend_requests_screen.dart index 1460a0b7..4a0e96a0 100644 --- a/careconnect2025/frontend/lib/features/social/presentation/pages/friend_requests_screen.dart +++ b/careconnect2025/frontend/lib/features/social/presentation/pages/friend_requests_screen.dart @@ -1,44 +1,66 @@ -import 'package:care_connect_app/config/env_constant.dart'; -import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; import 'dart:convert'; + +import 'package:care_connect_app/config/env_constant.dart'; import 'package:care_connect_app/features/social/presentation/pages/my_friend_screen.dart'; +import 'package:care_connect_app/services/api_service.dart'; import 'package:care_connect_app/widgets/app_bar_helper.dart'; import 'package:care_connect_app/widgets/common_drawer.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:provider/provider.dart'; + +import '../../../../providers/user_provider.dart'; +import '../model/friend_request_dto.dart'; class FriendRequestsScreen extends StatefulWidget { - final int userId; - const FriendRequestsScreen({super.key, required this.userId}); + const FriendRequestsScreen({super.key}); @override State createState() => _FriendRequestsScreenState(); } class _FriendRequestsScreenState extends State { - List requests = []; + int? _userId; + + List requests = []; bool isLoading = true; @override - void initState() { - super.initState(); - fetchRequests(); + void didChangeDependencies() { + super.didChangeDependencies(); + + if (_userId == null) { + final user = Provider.of(context, listen: false).user; + if (user == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('User not logged in')), + ); + setState(() => isLoading = false); + return; + } + + setState(() { + _userId = user.id; + }); + + fetchRequests(); + } } Future fetchRequests() async { + if (_userId == null) return; + setState(() => isLoading = true); final url = Uri.parse( - '${getBackendBaseUrl()}/api/friends/requests/${widget.userId}', + '${getBackendBaseUrl()}/v1/api/friends/requests/$_userId', ); - final response = await http - .get(url) - .timeout( - const Duration(seconds: 180), - onTimeout: () => http.Response('{"error": "Request timeout"}', 408), - ); + final headers = await ApiService.getAuthHeaders(); + final response = await http.get(url, headers: headers); if (response.statusCode == 200) { + final List data = jsonDecode(response.body); setState(() { - requests = jsonDecode(response.body); + requests = data.map((json) => FriendRequestDto.fromJson(json)).toList(); isLoading = false; }); } else { @@ -50,65 +72,73 @@ class _FriendRequestsScreenState extends State { } Future acceptRequest(int requestId) async { + final url = Uri.parse('${getBackendBaseUrl()}/v1/api/friends/accept'); + final headers = await ApiService.getAuthHeaders(); + headers['Content-Type'] = 'application/json'; + final response = await http.post( - Uri.parse('${getBackendBaseUrl()}/api/friends/accept'), - headers: {'Content-Type': 'application/json'}, + url, + headers: headers, body: jsonEncode({'requestId': requestId}), ); if (response.statusCode == 200) { ScaffoldMessenger.of( context, - ).showSnackBar(const SnackBar(content: Text('Request accepted'))); + ).showSnackBar(SnackBar(content: Text('Request accepted'))); fetchRequests(); } else { ScaffoldMessenger.of( context, - ).showSnackBar(const SnackBar(content: Text('Failed to accept request'))); + ).showSnackBar(SnackBar(content: Text('Failed to accept request'))); } } Future rejectRequest(int requestId) async { + final url = Uri.parse('${getBackendBaseUrl()}/v1/api/friends/reject'); + final headers = await ApiService.getAuthHeaders(); + headers['Content-Type'] = 'application/json'; + final response = await http.post( - Uri.parse('${getBackendBaseUrl()}/api/friends/reject'), - headers: {'Content-Type': 'application/json'}, + url, + headers: headers, body: jsonEncode({'requestId': requestId}), ); if (response.statusCode == 200) { ScaffoldMessenger.of( context, - ).showSnackBar(const SnackBar(content: Text('Request rejected'))); + ).showSnackBar(SnackBar(content: Text('Request rejected'))); fetchRequests(); } else { ScaffoldMessenger.of( context, - ).showSnackBar(const SnackBar(content: Text('Failed to reject request'))); + ).showSnackBar(SnackBar(content: Text('Failed to reject request'))); } } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBarHelper.createAppBar( - context, - title: 'Friend Requests', - additionalActions: [ + appBar: AppBar( + title: const Text('Friend Requests'), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.pop(context), + ), + actions: [ IconButton( icon: const Icon(Icons.group), tooltip: 'My Friends', onPressed: () { Navigator.push( context, - MaterialPageRoute( - builder: (_) => MyFriendsScreen(userId: widget.userId), - ), + MaterialPageRoute(builder: (_) => MyFriendsScreen()), ); }, ), ], ), - drawer: const CommonDrawer(currentRoute: '/friend_requests'), body: isLoading ? const Center(child: CircularProgressIndicator()) : requests.isEmpty @@ -118,13 +148,13 @@ class _FriendRequestsScreenState extends State { itemBuilder: (context, index) { final req = requests[index]; return ListTile( - title: Text(req['from_username'] ?? 'Unknown'), - subtitle: Text('Request ID: ${req['id']}'), + title: Text(req.fromUsername), + subtitle: Text('Request ID: ${req.id}'), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ ElevatedButton( - onPressed: () => acceptRequest(req['id']), + onPressed: () => acceptRequest(req.id), style: ElevatedButton.styleFrom( backgroundColor: Colors.green, ), @@ -132,7 +162,7 @@ class _FriendRequestsScreenState extends State { ), const SizedBox(width: 8), ElevatedButton( - onPressed: () => rejectRequest(req['id']), + onPressed: () => rejectRequest(req.id), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, ), diff --git a/careconnect2025/frontend/lib/features/social/presentation/pages/main_feed_screen.dart b/careconnect2025/frontend/lib/features/social/presentation/pages/main_feed_screen.dart index fb756612..30c5b251 100644 --- a/careconnect2025/frontend/lib/features/social/presentation/pages/main_feed_screen.dart +++ b/careconnect2025/frontend/lib/features/social/presentation/pages/main_feed_screen.dart @@ -1,17 +1,23 @@ -import 'package:flutter/material.dart'; +import 'dart:async'; import 'dart:convert'; + +import 'package:care_connect_app/config/env_constant.dart'; import 'package:care_connect_app/services/api_service.dart'; -import 'package:http/http.dart' as http; import 'package:care_connect_app/shared/widgets/user_avatar.dart'; -import 'package:care_connect_app/services/session_manager.dart'; import 'package:care_connect_app/widgets/app_bar_helper.dart'; import 'package:care_connect_app/widgets/common_drawer.dart'; import 'package:care_connect_app/config/theme/app_theme.dart'; -import 'search_user_screen.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; + +import '../model/post_with_comment_count_dto.dart'; + +import 'chat_inbox_screen.dart'; import 'comment_screen.dart'; import 'friend_requests_screen.dart'; import 'new_post_screen.dart'; -import 'package:care_connect_app/config/env_constant.dart'; +import 'search_user_screen.dart'; class MainFeedScreen extends StatefulWidget { final int userId; @@ -22,68 +28,89 @@ class MainFeedScreen extends StatefulWidget { } class _MainFeedScreenState extends State { - List posts = []; + List posts = []; bool isLoading = true; + Timer? _pollingTimer; @override void initState() { super.initState(); - fetchFeed(); + if (widget.userId == 0 || widget.userId == null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Invalid user ID. Please re-login.')), + ); + }); + setState(() => isLoading = false); + } else { + fetchFeed(); + _pollingTimer = Timer.periodic( + const Duration(seconds: 10), + (_) => fetchFeed(silent: true), + ); + } + } + + @override + void dispose() { + _pollingTimer?.cancel(); + super.dispose(); } - Future fetchFeed() async { - setState(() => isLoading = true); - final session = SessionManager(); - await session.restoreSession(); + Future fetchFeed({bool silent = false}) async { + if (!silent) setState(() => isLoading = true); try { - print('Headers before request: ${session.headers}'); - final http.Response response = await session.get( - '${ApiConstants.feed}/all', + final headers = await ApiService.getAuthHeaders(); + final response = await http.get( + Uri.parse('${ApiConstants.feed}/friends-feed'), + headers: headers, ); - print('Feed status: ${response.statusCode}'); - print('Feed response: ${response.body}'); - if (response.statusCode == 200) { final List data = jsonDecode(response.body); - setState(() { - posts = data; - isLoading = false; - }); + final newPosts = data + .map((json) => PostWithCommentCountDto.fromJson(json)) + .toList(); + + // Update only if different to prevent unnecessary rebuilds + if (!listEquals(posts, newPosts)) { + setState(() { + posts = newPosts; + isLoading = false; + }); + } else if (!silent) { + setState(() => isLoading = false); + } } else { - setState(() => isLoading = false); - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('Failed to load feed'))); + throw Exception('Failed to load feed'); } } catch (e) { - setState(() => isLoading = false); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('Error: ${e.toString()}'))); + if (!silent) { + setState(() => isLoading = false); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: ${e.toString()}')), + ); + } } } - Widget buildPostCard(Map post) { - final imageUrl = post['imageUrl']; - final String backendBaseUrl = - getBackendBaseUrl(); // Change for emulator if needed! + Widget buildPostCard(PostWithCommentCountDto post) { + final String? imageUrl = post.imageUrl; + final String backendBaseUrl = getBackendBaseUrl(); final resolvedUrl = imageUrl != null && imageUrl.isNotEmpty ? '$backendBaseUrl$imageUrl' : null; return InkWell( onTap: () { - if (post['id'] != null) { Navigator.push( context, MaterialPageRoute( - builder: (_) => CommentScreen(postId: post['id']), + builder: (_) => CommentScreen(postId: post.id), ), ); - } - }, + }, child: Card( margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), elevation: 2, @@ -95,24 +122,24 @@ class _MainFeedScreenState extends State { children: [ Row( children: [ - UserAvatar(imageUrl: post['profileImageUrl'], radius: 20), + UserAvatar(imageUrl: null, radius: 20), const SizedBox(width: 10), Text( - post['username'] ?? 'Unknown', - style: Theme.of(context).textTheme.titleMedium?.copyWith( + post.username, + style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), const Spacer(), Text( - post['timestamp'] ?? '', + post.createdAt.toIso8601String().split('T').first, style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], ), const SizedBox(height: 10), - Text(post['content'] ?? ''), + Text(post.content), if (resolvedUrl != null) ...[ const SizedBox(height: 10), ClipRRect( @@ -129,17 +156,15 @@ class _MainFeedScreenState extends State { const Divider(height: 1), TextButton.icon( onPressed: () { - if (post['id'] != null) { Navigator.push( context, MaterialPageRoute( - builder: (_) => CommentScreen(postId: post['id']), + builder: (_) => CommentScreen(postId: post.id), ), ); - } }, icon: const Icon(Icons.comment, size: 18), - label: Text('${post['commentCount'] ?? 0} comments'), + label: Text('${post.commentCount} comments'), ), ], ), @@ -151,7 +176,7 @@ class _MainFeedScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - drawer: const CommonDrawer(currentRoute: '/social-feed'), + drawer: CommonDrawer(currentRoute: '/social-feed'), appBar: AppBarHelper.createAppBar( context, title: 'My Feed', @@ -160,17 +185,17 @@ class _MainFeedScreenState extends State { body: isLoading ? const Center(child: CircularProgressIndicator()) : RefreshIndicator( - onRefresh: fetchFeed, - child: posts.isEmpty - ? const Center(child: Text('No posts yet. Pull to refresh.')) - : ListView.builder( - physics: const AlwaysScrollableScrollPhysics(), - itemCount: posts.length, - itemBuilder: (context, index) { - return buildPostCard(posts[index]); - }, - ), + onRefresh: fetchFeed, + child: posts.isEmpty + ? const Center(child: Text('No posts yet. Pull to refresh.')) + : ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + itemCount: posts.length, + itemBuilder: (context, index) { + return buildPostCard(posts[index]); + }, ), + ), bottomNavigationBar: BottomAppBar( shape: const CircularNotchedRectangle(), notchMargin: 8, @@ -187,16 +212,16 @@ class _MainFeedScreenState extends State { children: [ IconButton( icon: const Icon( - Icons.person_search, - color: Colors.white, + Icons.person_search, + color: Colors.white ), tooltip: 'Add Friend', onPressed: () { Navigator.push( context, MaterialPageRoute( - builder: (_) => - SearchUserScreen(userId: widget.userId), + builder: (_) => + SearchUserScreen() ), ); }, @@ -208,27 +233,20 @@ class _MainFeedScreenState extends State { Navigator.push( context, MaterialPageRoute( - builder: (_) => - FriendRequestsScreen(userId: widget.userId), + builder: (_) => + FriendRequestsScreen() ), ); }, ), - IconButton( - icon: const Icon( - Icons.calendar_today, - color: Colors.white, - ), - tooltip: 'Calendar', - onPressed: () { - // TODO - }, - ), IconButton( icon: const Icon(Icons.chat, color: Colors.white), tooltip: 'Messages', onPressed: () { - // TODO + Navigator.push( + context, + MaterialPageRoute(builder: (_) => ChatInboxScreen()), + ); }, ), IconButton( @@ -239,14 +257,18 @@ class _MainFeedScreenState extends State { ), tooltip: 'Create Post', onPressed: () async { - final success = await Navigator.push( + final result = await Navigator.push( context, MaterialPageRoute( - builder: (_) => - NewPostScreen(userId: widget.userId), + builder: (_) => + NewPostScreen() ), ); - if (success == true) fetchFeed(); + if (result is PostWithCommentCountDto) { + setState(() { + posts.insert(0, result); + }); + } }, ), ], @@ -258,4 +280,4 @@ class _MainFeedScreenState extends State { ), ); } -} +} \ No newline at end of file diff --git a/careconnect2025/frontend/lib/features/social/presentation/pages/my_friend_screen.dart b/careconnect2025/frontend/lib/features/social/presentation/pages/my_friend_screen.dart index aff2dc82..5bc7a382 100644 --- a/careconnect2025/frontend/lib/features/social/presentation/pages/my_friend_screen.dart +++ b/careconnect2025/frontend/lib/features/social/presentation/pages/my_friend_screen.dart @@ -1,66 +1,97 @@ +import 'dart:convert'; + import 'package:care_connect_app/config/env_constant.dart'; +import 'package:care_connect_app/services/api_service.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:http/http.dart' as http; -import 'dart:convert'; -import 'package:care_connect_app/widgets/app_bar_helper.dart'; -import 'package:care_connect_app/widgets/common_drawer.dart'; + +import '../../../../providers/user_provider.dart'; +import '../model/friend_dto.dart'; +import 'chat_room_screen.dart'; class MyFriendsScreen extends StatefulWidget { - final int userId; - const MyFriendsScreen({super.key, required this.userId}); + const MyFriendsScreen({super.key}); @override State createState() => _MyFriendsScreenState(); } class _MyFriendsScreenState extends State { - List friends = []; + List friends = []; bool isLoading = true; @override void initState() { super.initState(); - fetchFriends(); + WidgetsBinding.instance.addPostFrameCallback((_) { + final user = Provider.of(context, listen: false).user; + if (user == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('User not logged in')), + ); + setState(() => isLoading = false); + return; + } + fetchFriends(user.id); + }); } - Future fetchFriends() async { - final response = await http.get( - Uri.parse('${getBackendBaseUrl()}/api/friends/list/${widget.userId}'), + + Future fetchFriends(int userId) async { + setState(() => isLoading = true); + + final url = Uri.parse( + '${getBackendBaseUrl()}/v1/api/friends/list/$userId', ); + final headers = await ApiService.getAuthHeaders(); + final response = await http.get(url, headers: headers); if (response.statusCode == 200) { + final List data = jsonDecode(response.body); setState(() { - friends = jsonDecode(response.body); + friends = data.map((json) => FriendDto.fromJson(json)).toList(); isLoading = false; }); } else { setState(() => isLoading = false); ScaffoldMessenger.of( context, - ).showSnackBar(const SnackBar(content: Text('Failed to load friends'))); + ).showSnackBar(SnackBar(content: Text('Failed to load friends'))); } } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBarHelper.createAppBar(context, title: 'My Friends'), - drawer: const CommonDrawer(currentRoute: '/my_friends'), + appBar: AppBar(title: Text("My Friends")), body: isLoading - ? const Center(child: CircularProgressIndicator()) + ? Center(child: CircularProgressIndicator()) : friends.isEmpty - ? const Center(child: Text("You have no friends yet.")) + ? Center(child: Text("You have no friends yet.")) : ListView.builder( itemCount: friends.length, itemBuilder: (context, index) { final friend = friends[index]; return ListTile( leading: const Icon(Icons.person), - title: Text(friend['name']), - subtitle: Text(friend['email']), + title: Text(friend.name), + subtitle: Text(friend.email), + trailing: const Icon(Icons.chat), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ChatRoomScreen( + peerUserId: friend.id, + peerName: friend.name + ), + ), + ); + }, ); }, - ), + ) ); } } diff --git a/careconnect2025/frontend/lib/features/social/presentation/pages/new_post_screen.dart b/careconnect2025/frontend/lib/features/social/presentation/pages/new_post_screen.dart index 0bbd8768..2264805b 100644 --- a/careconnect2025/frontend/lib/features/social/presentation/pages/new_post_screen.dart +++ b/careconnect2025/frontend/lib/features/social/presentation/pages/new_post_screen.dart @@ -1,20 +1,25 @@ -import 'package:flutter/material.dart'; -import 'package:file_picker/file_picker.dart'; import 'dart:io'; +import 'dart:convert'; + +import 'package:care_connect_app/config/env_constant.dart'; import 'package:care_connect_app/services/api_service.dart'; -import 'package:care_connect_app/services/session_manager.dart'; -import 'package:care_connect_app/widgets/app_bar_helper.dart'; -import 'package:care_connect_app/widgets/common_drawer.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:http/http.dart' as http; + +import '../../../../providers/user_provider.dart'; +import '../model/post_with_comment_count_dto.dart'; class NewPostScreen extends StatefulWidget { - final int userId; - const NewPostScreen({super.key, required this.userId}); + const NewPostScreen({super.key}); @override State createState() => _NewPostScreenState(); } class _NewPostScreenState extends State { + final FlutterSecureStorage _secureStorage = FlutterSecureStorage(); final TextEditingController _contentController = TextEditingController(); File? _selectedImage; bool isPosting = false; @@ -27,8 +32,9 @@ class _NewPostScreenState extends State { } } - Future submitPost() async { + Future submitPost(int userId) async { final content = _contentController.text.trim(); + if (content.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Post content cannot be empty')), @@ -36,44 +42,72 @@ class _NewPostScreenState extends State { return; } - // Call restoreSession() here to ensure the session cookie is restored - final session = SessionManager(); - await session.restoreSession(); // This will restore the session cookie - setState(() => isPosting = true); try { - final response = await ApiService.createPost( - widget.userId, - content, - _selectedImage, - ); + final uri = Uri.parse('${getBackendBaseUrl()}/v1/api/feed/create'); + final headers = await ApiService.getAuthHeaders(); + + final request = http.MultipartRequest('POST', uri) + ..headers.addAll(headers) + ..fields['userId'] = userId.toString() + ..fields['content'] = content; + + if (_selectedImage != null) { + request.files.add(await http.MultipartFile.fromPath( + 'image', + _selectedImage!.path, + )); + } + + final streamedResponse = await request.send(); + final response = await http.Response.fromStream(streamedResponse); - // Debugging lines print('Create post status: ${response.statusCode}'); print('Create post body: ${response.body}'); setState(() => isPosting = false); if (response.statusCode == 200 || response.statusCode == 201) { - Navigator.pop(context, true); + final json = jsonDecode(response.body); + final newPost = PostWithCommentCountDto.fromJson(json); + Navigator.pop(context, newPost); } else { ScaffoldMessenger.of( - context, + context ).showSnackBar(SnackBar(content: Text('Error: ${response.body}'))); } } catch (e) { setState(() => isPosting = false); ScaffoldMessenger.of( - context, + context ).showSnackBar(SnackBar(content: Text('Exception: ${e.toString()}'))); } } @override Widget build(BuildContext context) { + final user = Provider.of(context).user; + + if (user == null) { + return Scaffold( + appBar: AppBar( + title: const Text('Create New Post'), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.pop(context), + ), + ), + body: const Center(child: Text('User not logged in')), + ); + } return Scaffold( - appBar: AppBarHelper.createAppBar(context, title: 'Create New Post'), - drawer: const CommonDrawer(currentRoute: '/new_post'), + appBar: AppBar( + title: const Text('Create New Post'), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.pop(context), + ), + ), body: Padding( padding: const EdgeInsets.all(16), child: Column( @@ -86,23 +120,9 @@ class _NewPostScreenState extends State { border: OutlineInputBorder(), ), ), - const SizedBox(height: 10), - if (_selectedImage != null) ...[ - Image.file(_selectedImage!, height: 150), - const SizedBox(height: 10), - TextButton( - onPressed: () => setState(() => _selectedImage = null), - child: const Text('Remove Photo'), - ), - ], - ElevatedButton.icon( - onPressed: pickImage, - icon: const Icon(Icons.photo), - label: const Text('Upload Photo'), - ), const SizedBox(height: 16), ElevatedButton( - onPressed: isPosting ? null : submitPost, + onPressed: isPosting ? null : () => submitPost(user.id), style: ElevatedButton.styleFrom( backgroundColor: Colors.blue.shade900, ), diff --git a/careconnect2025/frontend/lib/features/social/presentation/pages/search_user_screen.dart b/careconnect2025/frontend/lib/features/social/presentation/pages/search_user_screen.dart index 6e056ba0..83318f86 100644 --- a/careconnect2025/frontend/lib/features/social/presentation/pages/search_user_screen.dart +++ b/careconnect2025/frontend/lib/features/social/presentation/pages/search_user_screen.dart @@ -1,13 +1,15 @@ -import 'package:flutter/material.dart'; -// import 'package:http/http.dart' as http; import 'dart:convert'; + import 'package:care_connect_app/services/api_service.dart'; -import 'package:care_connect_app/widgets/app_bar_helper.dart'; -import 'package:care_connect_app/widgets/common_drawer.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../../../../providers/user_provider.dart'; +import '../model/search_user_dto.dart'; + class SearchUserScreen extends StatefulWidget { - final int userId; - const SearchUserScreen({super.key, required this.userId}); + const SearchUserScreen({super.key}); @override State createState() => _SearchUserScreenState(); @@ -15,47 +17,75 @@ class SearchUserScreen extends StatefulWidget { class _SearchUserScreenState extends State { final TextEditingController _controller = TextEditingController(); - List results = []; + List results = []; bool isLoading = false; - Future searchUsers() async { + Future searchUsers(int currentUserId) async { + setState(() => isLoading = true); - final response = await ApiService.searchUsers( - _controller.text.trim(), - widget.userId, - ); + try { + final response = await ApiService.searchUsers( + _controller.text.trim(), + currentUserId, + ); - if (response.statusCode == 200) { - setState(() => results = jsonDecode(response.body)); - } else { + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + setState(() { + results = (data as List) + .map((json) => SearchUserDto.fromJson(json)) + .toList(); + }); + } else { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Search failed'))); + } + } catch (e) { ScaffoldMessenger.of( context, - ).showSnackBar(const SnackBar(content: Text('Search failed'))); + ).showSnackBar(SnackBar(content: Text('Error: ${e.toString()}'))); } setState(() => isLoading = false); } - Future sendRequest(int toUserId) async { - final response = await ApiService.sendFriendRequest( - widget.userId, - toUserId, - ); - if (response.statusCode == 201) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('Friend request sent'))); - } else { + Future sendRequest(int fromUserId, int toUserId) async { + try { + final response = await ApiService.sendFriendRequest(fromUserId, toUserId); + if (response.statusCode == 201) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Friend request sent'))); + } else { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Request failed'))); + } + } catch (e) { ScaffoldMessenger.of( context, - ).showSnackBar(const SnackBar(content: Text('Request failed'))); + ).showSnackBar(SnackBar(content: Text('Error: ${e.toString()}'))); } } @override Widget build(BuildContext context) { + final user = Provider.of(context).user; + if (user == null) { + return Scaffold( + appBar: AppBar(title: const Text('Search Users')), + body: const Center(child: Text('User not logged in')), + ); + } + return Scaffold( - appBar: AppBarHelper.createAppBar(context, title: 'Search Users'), - drawer: const CommonDrawer(currentRoute: '/search-users'), + appBar: AppBar( + title: const Text('Search Users'), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.pop(context), + ), + ), body: Column( children: [ Padding( @@ -65,25 +95,25 @@ class _SearchUserScreenState extends State { decoration: InputDecoration( labelText: 'Search by name or email', suffixIcon: IconButton( - icon: const Icon(Icons.search), - onPressed: searchUsers, + icon: Icon(Icons.search), + onPressed: () => searchUsers(user.id), ), ), ), ), Expanded( child: isLoading - ? const Center(child: CircularProgressIndicator()) + ? Center(child: CircularProgressIndicator()) : ListView.builder( itemCount: results.length, itemBuilder: (context, index) { - final user = results[index]; + final searchUser = results[index]; return ListTile( - title: Text(user['name']), - subtitle: Text(user['email']), + title: Text(searchUser.name), + subtitle: Text(searchUser.email), trailing: ElevatedButton( - onPressed: () => sendRequest(user['id']), - child: const Text('Add'), + onPressed: () => sendRequest(user.id, searchUser.id), + child: Text('Add'), ), ); }, diff --git a/careconnect2025/frontend/lib/services/gamification_service.dart b/careconnect2025/frontend/lib/services/gamification_service.dart index 6fafb653..a191d13b 100644 --- a/careconnect2025/frontend/lib/services/gamification_service.dart +++ b/careconnect2025/frontend/lib/services/gamification_service.dart @@ -46,4 +46,22 @@ class GamificationService { final error = jsonDecode(res.body); throw Exception(error['error'] ?? 'Failed to load all achievements'); } + + static Future addXP(int userId, int amount) async { + final headers = await AuthTokenManager.getAuthHeaders(); + headers['Content-Type'] = 'application/json'; + + final response = await http.post( + Uri.parse('${ApiConstants.gamification}/award-xp'), + headers: headers, + body: jsonEncode({ + 'userId': userId, + 'amount': amount, + }), + ); + + if (response.statusCode != 200 && response.statusCode != 201) { + throw Exception('Failed to award XP: ${response.body}'); + } + } } From 67419a849ed4620892dde93c8e94c54fab6e2f39 Mon Sep 17 00:00:00 2001 From: dtruong5 <87693856+dtruong5@users.noreply.github.com> Date: Sat, 26 Jul 2025 09:25:46 -0500 Subject: [PATCH 02/15] Update gamification_screen.dart Replaced hardcoded colors and text styles --- .../pages/gamification_screen.dart | 73 +++++++++---------- 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/careconnect2025/frontend/lib/features/gamification/presentation/pages/gamification_screen.dart b/careconnect2025/frontend/lib/features/gamification/presentation/pages/gamification_screen.dart index 749e0c77..e7c991db 100644 --- a/careconnect2025/frontend/lib/features/gamification/presentation/pages/gamification_screen.dart +++ b/careconnect2025/frontend/lib/features/gamification/presentation/pages/gamification_screen.dart @@ -6,6 +6,7 @@ import 'dart:math'; import 'package:shared_preferences/shared_preferences.dart'; import 'achievement_detail_screen.dart'; import 'package:care_connect_app/widgets/common_drawer.dart'; +import 'package:care_connect_app/widgets/app_bar_helper.dart'; class GamificationScreen extends StatefulWidget { const GamificationScreen({super.key}); @@ -56,11 +57,6 @@ class _GamificationScreenState extends State { userId = int.tryParse(_prefs.getString('userId') ?? '') ?? 1; - // Show confetti for first login reward - final hasReceivedFirstLoginReward = - _prefs.getBool('first_login_reward_given') ?? false; - - previousAchievementCount = _prefs.getInt('achievement_count') ?? 0; await loadGamificationData(); pickDailyMessage(); @@ -114,16 +110,17 @@ class _GamificationScreenState extends State { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + final textTheme = theme.textTheme; + return Scaffold( - appBar: AppBar( - backgroundColor: Colors.blue.shade900, - title: const Text( - 'Care Connect', - style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), - ), - actions: [ + appBar: AppBarHelper.createAppBar( + context, + title: 'Achievements', + additionalActions: [ IconButton( - icon: const Icon(Icons.emoji_events, color: Colors.white), + icon: const Icon(Icons.emoji_events), onPressed: () { Navigator.push( context, @@ -136,8 +133,8 @@ class _GamificationScreenState extends State { ), ], ), - drawer: CommonDrawer(currentRoute: '/gamification'), - backgroundColor: Colors.white, + drawer: const CommonDrawer(currentRoute: '/gamification'), + backgroundColor: colorScheme.background, body: Stack( children: [ isLoading @@ -151,33 +148,31 @@ class _GamificationScreenState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ const SizedBox(height: 10), - const Text( + Text( 'Gamification', - style: TextStyle( - fontSize: 28, + style: textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.bold, - color: Colors.indigo, + color: colorScheme.primary, ), ), const SizedBox(height: 30), - const Icon(Icons.shield, size: 80, color: Colors.indigo), + Icon(Icons.shield, size: 80, color: colorScheme.primary), const SizedBox(height: 12), Text( 'Level $level', - style: const TextStyle( + style: textTheme.titleMedium?.copyWith( fontSize: 20, fontWeight: FontWeight.bold, - color: Colors.indigo, + color: colorScheme.primary, ), ), Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Text( dailyMessage, - style: const TextStyle( - fontSize: 16, + style: textTheme.bodyMedium?.copyWith( fontStyle: FontStyle.italic, - color: Colors.black87, + color: colorScheme.onBackground, ), textAlign: TextAlign.center, ), @@ -185,8 +180,8 @@ class _GamificationScreenState extends State { const SizedBox(height: 10), LinearProgressIndicator( value: (xp % xpTarget) / xpTarget, - backgroundColor: Colors.grey.shade300, - color: Colors.blue.shade900, + backgroundColor: colorScheme.surfaceVariant, + color: colorScheme.primary, minHeight: 10, borderRadius: BorderRadius.circular(10), ), @@ -195,21 +190,20 @@ class _GamificationScreenState extends State { alignment: Alignment.centerRight, child: Text( '$xp / $xpTarget XP', - style: const TextStyle( + style: textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w500, - color: Colors.indigo, + color: colorScheme.primary, ), ), ), const SizedBox(height: 30), - const Align( + Align( alignment: Alignment.centerLeft, child: Text( 'Achievements', - style: TextStyle( - fontSize: 22, + style: textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, - color: Colors.indigo, + color: colorScheme.primary, ), ), ), @@ -235,8 +229,8 @@ class _GamificationScreenState extends State { icon: const Icon(Icons.leaderboard), label: const Text("View Leaderboard"), style: ElevatedButton.styleFrom( - backgroundColor: Colors.deepPurple, - foregroundColor: Colors.white, + backgroundColor: colorScheme.secondary, + foregroundColor: colorScheme.onSecondary, ), ), const SizedBox(height: 20), @@ -271,7 +265,7 @@ class _GamificationScreenState extends State { MaterialPageRoute( builder: (_) => AchievementDetailScreen( achievements: allAchievements, - ), // ✅ This must include the `unlocked` key + ), ), ); }, @@ -280,10 +274,9 @@ class _GamificationScreenState extends State { Expanded( child: Text( achievement['title'] ?? 'Unnamed Achievement', - style: const TextStyle( - fontSize: 16, - color: Colors.black87, - fontWeight: FontWeight.w500, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface, ), ), ), From 07852316538151ffa0be0458b654b5a518dc4637 Mon Sep 17 00:00:00 2001 From: dtruong5 <87693856+dtruong5@users.noreply.github.com> Date: Sat, 26 Jul 2025 09:31:38 -0500 Subject: [PATCH 03/15] Update friend_requests_screen.dart reverted the back button to createAppBar --- .../presentation/pages/friend_requests_screen.dart | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/careconnect2025/frontend/lib/features/social/presentation/pages/friend_requests_screen.dart b/careconnect2025/frontend/lib/features/social/presentation/pages/friend_requests_screen.dart index 4a0e96a0..39d253dd 100644 --- a/careconnect2025/frontend/lib/features/social/presentation/pages/friend_requests_screen.dart +++ b/careconnect2025/frontend/lib/features/social/presentation/pages/friend_requests_screen.dart @@ -120,13 +120,10 @@ class _FriendRequestsScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Friend Requests'), - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () => Navigator.pop(context), - ), - actions: [ + appBar: AppBarHelper.createAppBar( + context, + title: 'Friend Requests', + additionalActions: [ IconButton( icon: const Icon(Icons.group), tooltip: 'My Friends', From aa2afc367f8149effb4a183ecb11bdafe09eb5f7 Mon Sep 17 00:00:00 2001 From: dtruong5 <87693856+dtruong5@users.noreply.github.com> Date: Sat, 26 Jul 2025 09:36:36 -0500 Subject: [PATCH 04/15] Update search_user_screen.dart implement createAppBar --- .../social/presentation/pages/search_user_screen.dart | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/careconnect2025/frontend/lib/features/social/presentation/pages/search_user_screen.dart b/careconnect2025/frontend/lib/features/social/presentation/pages/search_user_screen.dart index 83318f86..ad784b3b 100644 --- a/careconnect2025/frontend/lib/features/social/presentation/pages/search_user_screen.dart +++ b/careconnect2025/frontend/lib/features/social/presentation/pages/search_user_screen.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:care_connect_app/services/api_service.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; - +import 'package:care_connect_app/widgets/app_bar_helper.dart'; import '../../../../providers/user_provider.dart'; import '../model/search_user_dto.dart'; @@ -79,13 +79,10 @@ class _SearchUserScreenState extends State { } return Scaffold( - appBar: AppBar( - title: const Text('Search Users'), - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () => Navigator.pop(context), + appBar: AppBarHelper.createAppBar( + context, + title: 'Search Users', ), - ), body: Column( children: [ Padding( From 80e0b4a386c790f08eee37449c877f4e1bab9a45 Mon Sep 17 00:00:00 2001 From: Dat Truong Date: Sun, 27 Jul 2025 22:19:19 -0500 Subject: [PATCH 05/15] update user to fix my branch after merge --- .../dto/PostWithCommentCountDto.java | 18 ++++++++ .../com/careconnect/model/Friendship.java | 28 +++++++++++++ .../main/java/com/careconnect/model/User.java | 4 ++ .../repository/CommentRepository.java | 2 + .../src/main/resources/application.properties | 42 +++++++++---------- 5 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 careconnect2025/backend/core/src/main/java/com/careconnect/dto/PostWithCommentCountDto.java create mode 100644 careconnect2025/backend/core/src/main/java/com/careconnect/model/Friendship.java diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/dto/PostWithCommentCountDto.java b/careconnect2025/backend/core/src/main/java/com/careconnect/dto/PostWithCommentCountDto.java new file mode 100644 index 00000000..20865e97 --- /dev/null +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/dto/PostWithCommentCountDto.java @@ -0,0 +1,18 @@ +package com.careconnect.dto; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor + +public class PostWithCommentCountDto { + private Long id; + private Long userId; + private String content; + private String imageUrl; + private LocalDateTime createdAt; + private int commentCount; + private String username; +} \ No newline at end of file diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/model/Friendship.java b/careconnect2025/backend/core/src/main/java/com/careconnect/model/Friendship.java new file mode 100644 index 00000000..035e563d --- /dev/null +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/model/Friendship.java @@ -0,0 +1,28 @@ +package com.careconnect.model; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "friendships") +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Friendship { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "user1_id", nullable = false) + private User user1; + + @ManyToOne + @JoinColumn(name = "user2_id", nullable = false) + private User user2; + + @Column(nullable = false) + private String status; // e.g., "PENDING", "CONFIRMED", etc. +} \ No newline at end of file diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java b/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java index 7d2dd209..74cb84b5 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java @@ -22,6 +22,10 @@ public class User { private String name; + // Split name into first and last name for better usability + private String firstName; + private String lastName; + @Column(unique = true, nullable = false) private String email; diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/repository/CommentRepository.java b/careconnect2025/backend/core/src/main/java/com/careconnect/repository/CommentRepository.java index e6f8f51b..0a302289 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/repository/CommentRepository.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/repository/CommentRepository.java @@ -7,4 +7,6 @@ public interface CommentRepository extends JpaRepository { List findByPostIdOrderByCreatedAtAsc(Long postId); + + int countByPostId(Long postId); } diff --git a/careconnect2025/backend/core/src/main/resources/application.properties b/careconnect2025/backend/core/src/main/resources/application.properties index 11885e14..fd90f34b 100644 --- a/careconnect2025/backend/core/src/main/resources/application.properties +++ b/careconnect2025/backend/core/src/main/resources/application.properties @@ -1,6 +1,6 @@ spring.application.name=careconnect # JPA / Hibernate Configuration - Let JPA handle schema for now -spring.jpa.hibernate.ddl-auto=${HIBERNATE_DDL_AUTO:update} +spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=false # Critical: Defer JPA initialization until after database is ready @@ -9,9 +9,9 @@ spring.sql.init.mode=never # Database Configuration -spring.datasource.url=${JDBC_URI} -spring.datasource.username=${DB_USER} -spring.datasource.password=${DB_PASSWORD} +spring.datasource.url=jdbc:mysql://localhost:3306/careconnect?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true +spring.datasource.username=root +spring.datasource.password=@828798boM spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.main.banner-mode=off @@ -48,11 +48,6 @@ spring.datasource.hikari.maximum-pool-size=10 spring.datasource.hikari.minimum-idle=2 spring.datasource.hikari.connection-timeout=20000 spring.datasource.hikari.idle-timeout=300000 - -# Subscription price ID mappings -# Comma-separated list of Stripe price IDs for each plan type -subscription.premium-price-ids=price_1RmqWxELoozGI1YxQql5rsvN -subscription.standard-price-ids=price_standard spring.datasource.hikari.max-lifetime=1200000 spring.datasource.hikari.leak-detection-threshold=60000 spring.datasource.hikari.auto-commit=false @@ -60,7 +55,7 @@ spring.datasource.hikari.auto-commit=false # JVM and Spring Boot Performance spring.jpa.open-in-view=false spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false -spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect +spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect # Optional: Server port (defaults to 8080) # server.port=8080 @@ -86,12 +81,12 @@ spring.mail.properties.mail.smtp.starttls.enable=${MAIL_SMTP_STARTTLS:true} # - mailgun: Mailgun API (production) # - sendgrid: SendGrid (production) # - gmail: Gmail SMTP (production) -careconnect.email.provider=${EMAIL_PROVIDER:sendgrid} +careconnect.email.provider=${EMAIL_PROVIDER:mailtrap} # From email address - Must be a valid domain for Mailtrap/SMTP # For Mailtrap: Use any valid email format (doesn't need to be real) # For production: Use your actual domain -careconnect.email.from=${FROM_EMAIL:smpestest@gmail.com} +careconnect.email.from=${FROM_EMAIL:noreply@careconnect.com} # API-based email provider configurations # These are used when EMAIL_PROVIDER is set to the respective service @@ -112,14 +107,16 @@ frontend.base-url=${APP_FRONTEND_BASE_URL:http://localhost:3000} # Upload directory configuration - Use environment variable for security careconnect.upload.dir=${UPLOAD_DIR:${user.home}/Documents/uploads} careconnect.baseurl=${BASE_URL:http://localhost:8080} -careconnect.cors_allowed=${CORS_ALLOWED_LIST:https://care-connect-develop.d26kqsucj1bwc1.amplifyapp.com} +# careconnect.cors_allowed=${CORS_ALLOWED_LIST:https://care-connect-develop.d26kqsucj1bwc1.amplifyapp.com} +# for local dev +careconnect.cors_allowed=http://localhost:* -security.jwt.secret=${SECURITY_JWT_SECRET} +security.jwt.secret=YXpDa0EyYUdWc0pFR2hxdEZKTk1wcGlXT0dkU3NFZFI= jwt.expiration.ms=${JWT_EXPIRATION:10800000} -stripe.secret-key=${STRIPE_SECRET_KEY} +stripe.secret-key=${STRIPE_SECRET_KEY:sk_test_dummyvalue} openai.api-key=${OPENAI_API_KEY} -stripe.webhook-secret=${STRIPE_WEBHOOK_SIGNING_SECRET} +stripe.webhook-secret=${STRIPE_WEBHOOK_SIGNING_SECRET:dummy-stripe-webhook-secret} spring.security.oauth2.client.registration.fitbit.client-id=${FITBIT_CLIENT_ID} @@ -135,9 +132,9 @@ spring.security.oauth2.client.provider.fitbit.user-name-attribute=user_id -spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID} +spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID:dummy-google-client-id} -spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET} +spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET:dummy-google-client-secret} spring.security.oauth2.client.registration.google.scope=${GOOGLE_SCOPE:openid,email,profile} spring.security.oauth2.client.registration.google.redirect-uri=${GOOGLE_REDIRECT_URI:{baseUrl}/login/oauth2/code/google} spring.security.oauth2.client.registration.google.client-name=${GOOGLE_CLIENT_NAME:Google} @@ -160,10 +157,6 @@ springdoc.swagger-ui.filter=true # Disable default OpenAPI security for documentation endpoints springdoc.swagger-ui.disable-swagger-default-url=true -# Firebase Configuration for Push Notifications -firebase.project-id=${FIREBASE_PROJECT_ID:careconnectcapstone} -firebase.service-account-key=${FIREBASE_SERVICE_ACCOUNT_KEY:firebase-service-account.json} -firebase.sender-id=${FIREBASE_SENDER_ID:663999888931} # Flyway Configuration - TEMPORARILY DISABLED to resolve circular dependency spring.flyway.enabled=false @@ -185,6 +178,9 @@ spring.flyway.enabled=false # spring.flyway.init-sql=SET FOREIGN_KEY_CHECKS=0; # spring.jpa.properties.hibernate.hbm2ddl.auto=none +spring.jpa.properties.hibernate.format_sql=true +logging.level.org.hibernate.SQL=DEBUG +logging.level.org.hibernate.tool.hbm2ddl=DEBUG aws.s3.access-key=${AWS_ACCESS_KEY_ID} aws.s3.secret-key=${AWS_SECRET_ACCESS_KEY} @@ -194,4 +190,4 @@ aws.s3.base-url=${AWS_S3_BASE_URL:https://cc-internal-file-storage-us-east-1-641 # File upload settings spring.servlet.multipart.max-file-size=10MB -spring.servlet.multipart.max-request-size=10MB +spring.servlet.multipart.max-request-size=10MB \ No newline at end of file From 1fa8161542941b7cd410f9947785749fbd04695b Mon Sep 17 00:00:00 2001 From: Dat Truong Date: Sun, 27 Jul 2025 22:29:35 -0500 Subject: [PATCH 06/15] Updated User.java to match care-connect-develop-ag2 branch --- .../main/java/com/careconnect/model/User.java | 43 ++++++------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java b/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java index 74cb84b5..189e5839 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java @@ -6,7 +6,6 @@ import lombok.NoArgsConstructor; import lombok.Builder; import java.sql.Timestamp; -import java.time.LocalDate; @Entity @Table(name = "users") @@ -21,7 +20,7 @@ public class User { private Long id; private String name; - + // Split name into first and last name for better usability private String firstName; private String lastName; @@ -35,15 +34,6 @@ public class User { @Column(name = "password_hash") private String passwordHash; - @Column(name = "last_login_date") - private LocalDate lastLoginDate; - - @Column(name = "login_streak") - private int loginStreak; - - @Column(name = "leaderboard_opt_in", nullable = false) - private Boolean leaderboardOptIn = true; - @Enumerated(EnumType.STRING) @Column(nullable = false) private com.careconnect.security.Role role; @@ -53,6 +43,9 @@ public class User { private Boolean isVerified = false; private String verificationToken; + + @Column(name = "stripe_customer_id") + private String stripeCustomerId; private Timestamp createdAt; @@ -77,36 +70,26 @@ public boolean isActive() { // Additional getters for compatibility public Long getId() { return id; } public String getName() { return name; } + public String getFirstName() { return firstName; } + public String getLastName() { return lastName; } public String getEmail() { return email; } public com.careconnect.security.Role getRole() { return role; } public Boolean getIsVerified() { return isVerified; } + public String getVerificationToken() { return verificationToken; } public String getStatus() { return status; } public String getProfileImageUrl() { return profileImageUrl; } - public LocalDate getLastLoginDate() { - return lastLoginDate; - } - public int getLoginStreak() { - return loginStreak; - } - public Boolean getLeaderboardOptIn() { - return leaderboardOptIn; - } - + public String getStripeCustomerId() { return stripeCustomerId; } + // Additional setters for compatibility public void setId(Long id) { this.id = id; } public void setName(String name) { this.name = name; } + public void setFirstName(String firstName) { this.firstName = firstName; } + public void setLastName(String lastName) { this.lastName = lastName; } public void setEmail(String email) { this.email = email; } public void setRole(com.careconnect.security.Role role) { this.role = role; } public void setIsVerified(Boolean isVerified) { this.isVerified = isVerified; } + public void setVerificationToken(String verificationToken) { this.verificationToken = verificationToken; } public void setStatus(String status) { this.status = status; } public void setProfileImageUrl(String profileImageUrl) { this.profileImageUrl = profileImageUrl; } - public void setLastLoginDate(LocalDate lastLoginDate) { - this.lastLoginDate = lastLoginDate; - } - public void setLoginStreak(int loginStreak) { - this.loginStreak = loginStreak; - } - public void setLeaderboardOptIn(Boolean leaderboardOptIn) { - this.leaderboardOptIn = leaderboardOptIn; - } + public void setStripeCustomerId(String stripeCustomerId) { this.stripeCustomerId = stripeCustomerId; } } From ce809d709666c9e788ff757779d8adc8e11c0609 Mon Sep 17 00:00:00 2001 From: Dat Truong Date: Mon, 28 Jul 2025 00:19:30 -0500 Subject: [PATCH 07/15] brought the changes from care-connect-develop to ag2 (gamification + social-networking), bug fixes --- .../main/java/com/careconnect/model/User.java | 30 +- .../repository/UserRepository.java | 6 +- .../lib/config/router/app_router.dart | 132 +++------ .../pages/caregiver_dashboard.dart | 263 +++++++++--------- .../model/PostWithCommentCountDto.dart | 31 +++ .../presentation/model/comment_dto.dart | 30 ++ .../model/conversation_preview_dto.dart | 22 ++ .../social/presentation/model/friend_dto.dart | 19 ++ .../model/friend_request_dto.dart | 22 ++ .../presentation/model/message_dto.dart | 25 ++ .../presentation/model/search_user_dto.dart | 19 ++ .../presentation/pages/main_feed_screen.dart | 2 +- .../presentation/pages/new_post_screen.dart | 4 +- .../frontend/lib/services/api_service.dart | 54 ++++ .../lib/services/gamification_service.dart | 40 ++- .../Flutter/GeneratedPluginRegistrant.swift | 4 +- careconnect2025/frontend/pubspec.yaml | 2 +- 17 files changed, 456 insertions(+), 249 deletions(-) create mode 100644 careconnect2025/frontend/lib/features/social/presentation/model/PostWithCommentCountDto.dart create mode 100644 careconnect2025/frontend/lib/features/social/presentation/model/comment_dto.dart create mode 100644 careconnect2025/frontend/lib/features/social/presentation/model/conversation_preview_dto.dart create mode 100644 careconnect2025/frontend/lib/features/social/presentation/model/friend_dto.dart create mode 100644 careconnect2025/frontend/lib/features/social/presentation/model/friend_request_dto.dart create mode 100644 careconnect2025/frontend/lib/features/social/presentation/model/message_dto.dart create mode 100644 careconnect2025/frontend/lib/features/social/presentation/model/search_user_dto.dart diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java b/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java index 189e5839..472014b3 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java @@ -6,6 +6,7 @@ import lombok.NoArgsConstructor; import lombok.Builder; import java.sql.Timestamp; +import java.time.LocalDate; @Entity @Table(name = "users") @@ -34,6 +35,15 @@ public class User { @Column(name = "password_hash") private String passwordHash; + @Column(name = "last_login_date") + private LocalDate lastLoginDate; + + @Column(name = "login_streak") + private int loginStreak; + + @Column(name = "leaderboard_opt_in", nullable = false) + private Boolean leaderboardOptIn = true; + @Enumerated(EnumType.STRING) @Column(nullable = false) private com.careconnect.security.Role role; @@ -79,7 +89,16 @@ public boolean isActive() { public String getStatus() { return status; } public String getProfileImageUrl() { return profileImageUrl; } public String getStripeCustomerId() { return stripeCustomerId; } - + public LocalDate getLastLoginDate() { + return lastLoginDate; + } + public int getLoginStreak() { + return loginStreak; + } + public Boolean getLeaderboardOptIn() { + return leaderboardOptIn; + } + // Additional setters for compatibility public void setId(Long id) { this.id = id; } public void setName(String name) { this.name = name; } @@ -92,4 +111,13 @@ public boolean isActive() { public void setStatus(String status) { this.status = status; } public void setProfileImageUrl(String profileImageUrl) { this.profileImageUrl = profileImageUrl; } public void setStripeCustomerId(String stripeCustomerId) { this.stripeCustomerId = stripeCustomerId; } + public void setLastLoginDate(LocalDate lastLoginDate) { + this.lastLoginDate = lastLoginDate; + } + public void setLoginStreak(int loginStreak) { + this.loginStreak = loginStreak; + } + public void setLeaderboardOptIn(Boolean leaderboardOptIn) { + this.leaderboardOptIn = leaderboardOptIn; + } } diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/repository/UserRepository.java b/careconnect2025/backend/core/src/main/java/com/careconnect/repository/UserRepository.java index 311dfeb8..752ebb5c 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/repository/UserRepository.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/repository/UserRepository.java @@ -2,6 +2,7 @@ import com.careconnect.dto.LeaderboardEntry; import com.careconnect.model.User; + import com.careconnect.security.Role; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -13,14 +14,15 @@ public interface UserRepository extends JpaRepository { Optional findByEmail(String email); boolean existsByEmail(String email); - Optional findByEmailAndRole(String email, String role); + Optional findByEmailAndRole(String email, Role role); boolean existsByEmailAndRole(String email, String role); Optional findByVerificationToken(String token); List findByNameContainingIgnoreCaseOrEmailContainingIgnoreCase(String name, String email); + Optional findByStripeCustomerId(String stripeCustomerId); @Query(""" SELECT u.id FROM User u JOIN Friendship f ON - (f.user1.id = :userId AND f.user2.id = u.id OR + (f.user1.id = :userId AND f.user2.id = u.id OR f.user2.id = :userId AND f.user1.id = u.id) WHERE f.status = 'CONFIRMED' """) diff --git a/careconnect2025/frontend/lib/config/router/app_router.dart b/careconnect2025/frontend/lib/config/router/app_router.dart index faf8991e..f07766ca 100644 --- a/careconnect2025/frontend/lib/config/router/app_router.dart +++ b/careconnect2025/frontend/lib/config/router/app_router.dart @@ -1,45 +1,40 @@ -//import 'package:care_connect_app/features/dashboard/presentation/pages/archive_patient.dart'; -//import 'package:care_connect_app/features/dashboard/presentation/pages/invite_family_member.dart'; -//import 'package:care_connect_app/features/dashboard/presentation/pages/media_upload.dart'; import 'package:care_connect_app/features/integrations/presentation/pages/home_monitoring_screen.dart'; import 'package:care_connect_app/features/integrations/presentation/pages/medication_management.dart'; import 'package:care_connect_app/features/integrations/presentation/pages/smart_devices.dart'; import 'package:care_connect_app/features/integrations/presentation/pages/wearables_screen.dart'; import 'package:care_connect_app/features/calls/presentation/pages/jitsi_meeting_screen.dart'; -import 'package:care_connect_app/features/notes/healthcare_notes.dart'; import 'package:care_connect_app/features/profile/presentation/pages/profile_settings_page.dart'; -import 'package:care_connect_app/features/ai/presentation/pages/speech_to_text.dart'; +import 'package:care_connect_app/pages/profile_page.dart'; +import 'package:care_connect_app/pages/settings_page.dart'; +import 'package:care_connect_app/pages/ai_configuration_page.dart'; +import 'package:care_connect_app/pages/file_management_page.dart'; +import 'package:care_connect_app/widgets/hybrid_video_call_widget.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:care_connect_app/config/theme/app_theme.dart'; -import 'package:care_connect_app/widgets/app_bar_helper.dart'; -import 'package:provider/provider.dart'; -import '../../features/ai/presentation/pages/voice_command_ai.dart'; -import '../../features/analytics/analytics_page.dart'; +import '../../features/welcome/presentation/pages/welcome_page.dart'; import '../../features/auth/presentation/pages/login_page.dart'; import '../../features/auth/presentation/pages/oauth_callback_page.dart'; -import '../../features/auth/presentation/pages/password_reset_page.dart'; -import '../../features/auth/presentation/pages/reset_password_screen.dart'; // ADD THIS IMPORT -import '../../features/auth/presentation/pages/sign_up_screen.dart'; -import '../../features/calls/presentation/pages/mobile_web_call.dart'; import '../../features/dashboard/presentation/pages/caregiver_dashboard.dart'; -//import '../../features/dashboard/presentation/pages/edit_patient.dart'; import '../../features/dashboard/presentation/pages/patient_dashboard.dart'; -import '../../features/gamification/presentation/pages/gamification_screen.dart'; import '../../features/onboarding/presentation/pages/patient_registration.dart'; -import '../../features/payments/models/package_model.dart'; -import '../../features/payments/presentation/pages/payment_cancel_page.dart'; -import '../../features/payments/presentation/pages/payment_success_page.dart'; +import '../../features/auth/presentation/pages/sign_up_screen.dart'; import '../../features/payments/presentation/pages/select_package_page.dart'; import '../../features/payments/presentation/pages/subscription_management_page.dart'; import '../../features/dashboard/presentation/pages/add_patient_screen.dart'; -import '../../features/payments/presentation/pages/stripe_checkout_page.dart'; -import '../../features/payments/presentation/pages/subscription_management_page.dart'; +import '../../features/auth/presentation/pages/password_reset_page.dart'; +import '../../features/auth/presentation/pages/reset_password_screen.dart'; // ADD THIS IMPORT +import '../../features/payments/models/package_model.dart'; import '../../features/social/presentation/pages/main_feed_screen.dart'; -import '../../features/welcome/presentation/pages/welcome_page.dart'; +import '../../features/gamification/presentation/pages/caregiver_gamification_landingpage.dart'; +import '../../features/gamification/presentation/pages/gamification_screen.dart'; +import '../../features/payments/presentation/pages/stripe_checkout_page.dart'; +import '../../features/analytics/analytics_page.dart'; +import '../../features/payments/presentation/pages/payment_success_page.dart'; +import '../../features/payments/presentation/pages/payment_cancel_page.dart'; import '../../features/dashboard/presentation/pages/patient_status_page.dart'; import '../../providers/user_provider.dart'; +import 'package:provider/provider.dart'; /// Helper function to navigate to the appropriate dashboard based on user role void navigateToDashboard(BuildContext context, {String? role}) { @@ -48,8 +43,7 @@ void navigateToDashboard(BuildContext context, {String? role}) { if (userRole == null) { // If no role is found, redirect to login with the last known userType if available - final lastUserType = - userProvider.user != null && userProvider.user!.role != null + final lastUserType = userProvider.user != null ? userProvider.user!.role.toLowerCase() : 'patient'; context.go('/login', extra: {'userType': lastUserType}); @@ -63,8 +57,6 @@ final GoRouter appRouter = GoRouter( initialLocation: '/', routes: [ GoRoute(path: '/', builder: (_, __) => const WelcomePage()), - // GoRoute(path: '/login', builder: (_, __) => const LoginPage()), - // GoRoute(path: '/signup', builder: (_, __) => const SignUpScreen()), GoRoute( path: '/login', builder: (context, state) { @@ -114,7 +106,7 @@ final GoRouter appRouter = GoRouter( case 'ADMIN': return const CaregiverDashboard(); default: - // Unknown role, redirect to login + // Unknown role, redirect to login WidgetsBinding.instance.addPostFrameCallback((_) { context.go('/login'); }); @@ -183,21 +175,16 @@ final GoRouter appRouter = GoRouter( builder: (_, __) => const PatientRegistrationPage(), ), GoRoute(path: '/add-patient', builder: (_, __) => const AddPatientScreen()), - GoRoute(path: '/speech-to-text', builder: (_, __) => SpeechToTextFile()), - GoRoute(path: '/healthcare-notes', builder: (_, __) => HealthcareNotes()), - GoRoute(path: '/voice-commands', builder: (_, __) => VoiceCommandAI()), GoRoute( path: '/social-feed', builder: (context, state) { final userIdStr = state.uri.queryParameters['userId']; - final userId = userIdStr != null ? int.tryParse(userIdStr) ?? -1 : -1; - - return MainFeedScreen(userId: userId); + final userId = userIdStr != null ? int.tryParse(userIdStr) : 1; + return MainFeedScreen(userId: userId ?? 1); }, ), GoRoute( path: '/select-package', - // builder: (_, __) => const SelectPackagePage(), builder: (context, state) { final userProvider = Provider.of(context, listen: false); final user = userProvider.user; @@ -257,7 +244,6 @@ final GoRouter appRouter = GoRouter( path: '/stripe-checkout', builder: (context, state) { final pkg = state.extra as PackageModel; - // return StripeCheckoutPage(package: pkg); // Get userId and stripeCustomerId from query parameters if available final userId = state.uri.queryParameters['userId']; final stripeCustomerId = state.uri.queryParameters['stripeCustomerId']; @@ -334,11 +320,6 @@ final GoRouter appRouter = GoRouter( path: '/analytics', builder: (context, state) { final patientIdStr = state.uri.queryParameters['patientId']; - // if (patientIdStr == null) { - // return const Scaffold( - // body: Center(child: Text('No patientId provided in the URL.')), - // ); - // } if (patientIdStr == null || int.tryParse(patientIdStr) == null) { // Instead of showing an error screen, redirect back to dashboard final userProvider = Provider.of( @@ -369,12 +350,12 @@ final GoRouter appRouter = GoRouter( body: const Center(child: CircularProgressIndicator()), ); } + final patientId = int.tryParse(patientIdStr); if (patientId == null) { - // return const Scaffold( - // body: Center(child: Text('Invalid patientId.')), - // ); - return Scaffold(body: Center(child: Text('Invalid patientId.'))); + return const Scaffold( + body: Center(child: Text('Invalid patientId.')), + ); } return AnalyticsPage(patientId: patientId); }, @@ -467,10 +448,7 @@ final GoRouter appRouter = GoRouter( return JitsiMeetingScreen(roomName: roomName); }, ), - GoRoute( - path: '/wearables', - builder: (_, __) => const WearablesScreen(), - ), + GoRoute(path: '/wearables', builder: (_, __) => const WearablesScreen()), GoRoute( path: '/home-monitoring', builder: (_, __) => const HomeMonitoringScreen(), @@ -487,6 +465,22 @@ final GoRouter appRouter = GoRouter( path: '/profile-settings', builder: (_, __) => const ProfileSettingsPage(), ), + GoRoute(path: '/profile', builder: (_, __) => const ProfilePage()), + GoRoute(path: '/settings', builder: (_, __) => const SettingsPage()), + GoRoute( + path: '/file-management', + builder: (_, __) => const FileManagementPage(), + ), + GoRoute( + path: '/ai-configuration', + builder: (_, __) => const AIConfigurationPage(), + ), + + // Video Call Test Route + GoRoute( + path: '/video-call-test', + builder: (_, __) => const VideoCallTestPage(), + ), // Handle routes from legacy menus GoRoute( @@ -521,45 +515,5 @@ final GoRouter appRouter = GoRouter( return '/dashboard?tab=emergency'; }, ), - - /*GoRoute( - path: '/edit', - builder: (context, state) { - final patientId = state.uri.queryParameters['patientId'] ?? ''; - return EditScreen(linkId: patientId); - }, - ),*/ - /*GoRoute( - path: '/archive', - builder: (_, __) => const ArchiveScreen(linkId: ''), - ),*/ - - /*GoRoute( - path: '/invite_Family_Member', - builder: (_, __) => const InviteFamilyMemberScreen(), - ),*/ - - //GoRoute(path: '/MediaScreen', builder: (_, __) => const MediaScreen()), - GoRoute( - path: '/mobile-web-call', - builder: (context, state) { - // Retrieve the query parameters - final patientName = - state.uri.queryParameters['patientName'] ?? 'Unknown'; - final roomId = state.uri.queryParameters['roomId'] ?? 'Unknown'; - - // Return the CallScreen widget and pass the parameters - return CallScreen( - patientName: patientName, - roomId: roomId, - isCaller: true, // Adjust this flag as needed - ); - }, - ), - - GoRoute( - path: '/subscription-management', - builder: (context, state) => const SubscriptionManagementPage(), - ), ], -); +); \ No newline at end of file diff --git a/careconnect2025/frontend/lib/features/dashboard/presentation/pages/caregiver_dashboard.dart b/careconnect2025/frontend/lib/features/dashboard/presentation/pages/caregiver_dashboard.dart index 7c06bdd6..87703c9c 100644 --- a/careconnect2025/frontend/lib/features/dashboard/presentation/pages/caregiver_dashboard.dart +++ b/careconnect2025/frontend/lib/features/dashboard/presentation/pages/caregiver_dashboard.dart @@ -1,14 +1,6 @@ -import 'dart:convert'; - -import 'package:care_connect_app/providers/user_provider.dart'; -import 'package:care_connect_app/services/api_service.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:provider/provider.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -import '../../../../widgets/ai_chat.dart'; -import '../../../social/presentation/pages/main_feed_screen.dart'; +import 'dart:convert'; import '../../models/patient_model.dart'; import 'package:provider/provider.dart'; import 'package:care_connect_app/providers/user_provider.dart'; @@ -180,7 +172,7 @@ class _CaregiverDashboardState extends State { // Always set relationship from link if present patientJson['relationship'] = patientJson['relationship'] ?? - (link['relationship'] ?? 'Patient'); + (link['relationship'] ?? 'Patient'); } } else { patientJson = Map.from(json); @@ -333,8 +325,8 @@ class _CaregiverDashboardState extends State { return summaryItems.isNotEmpty ? summaryItems - .take(2) - .join(', ') // Show max 2 vitals to avoid overcrowding + .take(2) + .join(', ') // Show max 2 vitals to avoid overcrowding : 'Vitals monitoring active'; } @@ -361,8 +353,8 @@ class _CaregiverDashboardState extends State { if (numValue < 95) return '⚠️'; break; case 'bloodPressure': - // For blood pressure, we'll just show checkmark for now - // As it's typically in format "120/80" + // For blood pressure, we'll just show checkmark for now + // As it's typically in format "120/80" return '✓'; } } catch (e) { @@ -378,10 +370,10 @@ class _CaregiverDashboardState extends State { try { // Check subscription access for caregivers before initiating call final canUseVideoCalls = - await SubscriptionService.checkPremiumAccessWithDialog( - context, - isVideoCall ? 'Video Calls' : 'Voice Calls', - ); + await SubscriptionService.checkPremiumAccessWithDialog( + context, + isVideoCall ? 'Video Calls' : 'Voice Calls', + ); if (!canUseVideoCalls) { return; // User doesn't have premium access, dialog was shown @@ -472,108 +464,105 @@ class _CaregiverDashboardState extends State { : 'Caregiver Dashboard', appBarActions: isLargeScreen ? [ - CallNotificationStatusIndicator( - isInitialized: _callNotificationInitialized, - ), - const SizedBox(width: 12), - IconButton( - icon: const Icon(Icons.help_outline), - onPressed: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Help documentation coming soon'), - ), - ); - }, + CallNotificationStatusIndicator( + isInitialized: _callNotificationInitialized, + ), + const SizedBox(width: 12), + IconButton( + icon: const Icon(Icons.help_outline), + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Help documentation coming soon'), ), - const SizedBox(width: 8), - ] + ); + }, + ), + const SizedBox(width: 8), + ] : [ - CallNotificationStatusIndicator( - isInitialized: _callNotificationInitialized, - ), - const SizedBox(width: 8), - ], + CallNotificationStatusIndicator( + isInitialized: _callNotificationInitialized, + ), + const SizedBox(width: 8), + ], currentRoute: '/dashboard', body: _buildMainContent(), ); } - return Drawer( - child: ListView( - padding: EdgeInsets.zero, - children: [ - DrawerHeader( - decoration: BoxDecoration(color: Colors.blue.shade700), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const CircleAvatar( - radius: 28, - backgroundColor: Colors.white, - child: Icon(Icons.person, size: 30), - ), - const SizedBox(height: 8), - Text( - user?.name ?? 'User Name', - style: const TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - Text( - isFamilyMember ? 'Family Member' : 'Caregiver', - style: const TextStyle(color: Colors.white70), - ), - ], - ), - ), - ListTile( - leading: const Icon(Icons.dashboard), - title: const Text('Dashboard'), - onTap: () => Navigator.pop(context), - ), - ListTile( - leading: const Icon(Icons.emoji_events), - title: const Text('Gamification'), - onTap: () { - Navigator.pop(context); - context.go('/gamification'); - }, - ), - ListTile( - leading: const Icon(Icons.emoji_events), - title: const Text('Subscription Management'), - onTap: () { - Navigator.pop(context); - context.go('/subscription-management'); - }, - ), - ListTile( - leading: const Icon(Icons.people), - title: Text(isFamilyMember ? 'My Patients' : 'Patients'), - onTap: () { - Navigator.pop(context); - if (isFamilyMember) { - context.go('/family-patients'); - } else { - context.go('/patients'); - } - }, - ), - // Only show these options for caregivers - if (!isFamilyMember) ...[ - ListTile( - leading: const Icon(Icons.person_add), - title: const Text('Register Patient'), - onTap: () { - Navigator.pop(context); - context.go('/register/patient'); - }, - ), - ], - ) + Widget _buildContentBasedOnState() { + if (loading) { + return const Center(child: CircularProgressIndicator()); + } else if (error != null) { + return _buildErrorState(); + } else if (patients.isEmpty) { + return _buildEmptyStateContent(); + } else { + return _buildPatientListContent(); + } + } + + Widget _buildErrorState() { + return Center( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.error_outline, + size: 64, + color: Theme.of(context).colorScheme.error, + ), + const SizedBox(height: 16), + const Text( + 'Error Loading Patients', + style: AppTheme.headingSmall, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + error!, + style: AppTheme.bodyMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: fetchPatients, + style: AppTheme.primaryButtonStyle, + child: const Text('Try Again'), + ), + ], + ), + ), + ); + } + + Widget _buildEmptyStateContent() { + // Use responsive utils for width calculation + final isMobile = context.isMobile; + + // Create a container with responsive width + return Center( + child: Container( + width: context.responsiveValue( + mobile: MediaQuery.of(context).size.width * 0.85, + tablet: 400.0, + ), + padding: const EdgeInsets.all(24), + decoration: !isMobile + ? BoxDecoration( + color: Theme.of(context).cardColor, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + spreadRadius: 1, + ), + ], + ) : null, child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -672,19 +661,19 @@ class _CaregiverDashboardState extends State { context.isDesktopOrLarger ? _buildResponsivePatientGrid(horizontalMargin) : SliverPadding( - padding: EdgeInsets.fromLTRB( - horizontalMargin, - 0, - horizontalMargin, - 16, - ), - sliver: SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - final patient = patients[index]; - return _buildPatientCard(patient); - }, childCount: patients.length), - ), - ), + padding: EdgeInsets.fromLTRB( + horizontalMargin, + 0, + horizontalMargin, + 16, + ), + sliver: SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + final patient = patients[index]; + return _buildPatientCard(patient); + }, childCount: patients.length), + ), + ), ], ), ); @@ -698,7 +687,7 @@ class _CaregiverDashboardState extends State { // Ensure we have at least 1 column and limit to 2 columns max for wider patient cards if (crossAxisCount > 2) { crossAxisCount = - 2; // Limit to 2 columns max for patient cards to make them wider + 2; // Limit to 2 columns max for patient cards to make them wider } return SliverPadding( @@ -839,10 +828,10 @@ class _CaregiverDashboardState extends State { currentUserId: widget.caregiverId .toString(), currentUserName: - caregiverName ?? 'Caregiver', + caregiverName ?? 'Caregiver', recipientId: patient.id.toString(), recipientName: - '${patient.firstName} ${patient.lastName}', + '${patient.firstName} ${patient.lastName}', ), ), ); @@ -897,7 +886,7 @@ class _CaregiverDashboardState extends State { builder: (context) => PatientMedicalNotesPage( patientId: patient.id, patientName: - '${patient.firstName} ${patient.lastName}', + '${patient.firstName} ${patient.lastName}', ), ), ); @@ -1081,9 +1070,9 @@ class _CaregiverDashboardState extends State { return; } final response = - await ApiService.suspendCaregiverPatientLink( - linkId, - ); + await ApiService.suspendCaregiverPatientLink( + linkId, + ); if (response.statusCode == 200) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -1140,9 +1129,9 @@ class _CaregiverDashboardState extends State { return; } final response = - await ApiService.reactivateCaregiverPatientLink( - linkId, - ); + await ApiService.reactivateCaregiverPatientLink( + linkId, + ); if (response.statusCode == 200) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -1265,4 +1254,4 @@ class _CaregiverDashboardState extends State { ), ); } -} +} \ No newline at end of file diff --git a/careconnect2025/frontend/lib/features/social/presentation/model/PostWithCommentCountDto.dart b/careconnect2025/frontend/lib/features/social/presentation/model/PostWithCommentCountDto.dart new file mode 100644 index 00000000..01e185b6 --- /dev/null +++ b/careconnect2025/frontend/lib/features/social/presentation/model/PostWithCommentCountDto.dart @@ -0,0 +1,31 @@ +class PostWithCommentCountDto { + final int id; + final int userId; + final String content; + final String? imageUrl; + final DateTime createdAt; + final int commentCount; + final String username; + + PostWithCommentCountDto({ + required this.id, + required this.userId, + required this.content, + this.imageUrl, + required this.createdAt, + required this.commentCount, + required this.username, + }); + + factory PostWithCommentCountDto.fromJson(Map json) { + return PostWithCommentCountDto( + id: json['id'], + userId: json['userId'], + content: json['content'], + imageUrl: json['imageUrl'], + createdAt: DateTime.parse(json['createdAt']), + commentCount: json['commentCount'], + username: json['username'] ?? 'Unknown User', + ); + } +} \ No newline at end of file diff --git a/careconnect2025/frontend/lib/features/social/presentation/model/comment_dto.dart b/careconnect2025/frontend/lib/features/social/presentation/model/comment_dto.dart new file mode 100644 index 00000000..af6a2a7d --- /dev/null +++ b/careconnect2025/frontend/lib/features/social/presentation/model/comment_dto.dart @@ -0,0 +1,30 @@ +class CommentDto { + final int id; + final int userId; + final int postId; + final String content; + final String username; + final DateTime timestamp; + + CommentDto({ + required this.id, + required this.userId, + required this.postId, + required this.content, + required this.username, + required this.timestamp, + }); + + factory CommentDto.fromJson(Map json) { + print('DEBUG — Raw comment JSON: $json'); + + return CommentDto( + id: json['id'], + userId: json['userId'], + postId: json['postId'], + content: json['content'], + username: json['username'] ?? 'Unknown User', + timestamp: DateTime.parse(json['createdAt']), + ); + } +} \ No newline at end of file diff --git a/careconnect2025/frontend/lib/features/social/presentation/model/conversation_preview_dto.dart b/careconnect2025/frontend/lib/features/social/presentation/model/conversation_preview_dto.dart new file mode 100644 index 00000000..9a9be46d --- /dev/null +++ b/careconnect2025/frontend/lib/features/social/presentation/model/conversation_preview_dto.dart @@ -0,0 +1,22 @@ +class ConversationPreviewDto { + final int peerId; + final String peerName; + final String content; // last message + final DateTime timestamp; + + ConversationPreviewDto({ + required this.peerId, + required this.peerName, + required this.content, + required this.timestamp, + }); + + factory ConversationPreviewDto.fromJson(Map json) { + return ConversationPreviewDto( + peerId: json['peerId'], + peerName: json['peerName'], + content: json['content'], + timestamp: DateTime.parse(json['timestamp']), + ); + } +} \ No newline at end of file diff --git a/careconnect2025/frontend/lib/features/social/presentation/model/friend_dto.dart b/careconnect2025/frontend/lib/features/social/presentation/model/friend_dto.dart new file mode 100644 index 00000000..b1457bc4 --- /dev/null +++ b/careconnect2025/frontend/lib/features/social/presentation/model/friend_dto.dart @@ -0,0 +1,19 @@ +class FriendDto { + final int id; + final String name; + final String email; + + FriendDto({ + required this.id, + required this.name, + required this.email, + }); + + factory FriendDto.fromJson(Map json) { + return FriendDto( + id: json['id'], + name: json['name'], + email: json['email'], + ); + } +} \ No newline at end of file diff --git a/careconnect2025/frontend/lib/features/social/presentation/model/friend_request_dto.dart b/careconnect2025/frontend/lib/features/social/presentation/model/friend_request_dto.dart new file mode 100644 index 00000000..26aa2390 --- /dev/null +++ b/careconnect2025/frontend/lib/features/social/presentation/model/friend_request_dto.dart @@ -0,0 +1,22 @@ +class FriendRequestDto { + final int id; + final int fromUserId; + final int toUserId; + final String fromUsername; + + FriendRequestDto({ + required this.id, + required this.fromUserId, + required this.toUserId, + required this.fromUsername, + }); + + factory FriendRequestDto.fromJson(Map json) { + return FriendRequestDto( + id: json['id'], + fromUserId: json['fromUserId'], + toUserId: json['toUserId'], + fromUsername: json['from_username'], // or json['fromUsername'] based on backend + ); + } +} \ No newline at end of file diff --git a/careconnect2025/frontend/lib/features/social/presentation/model/message_dto.dart b/careconnect2025/frontend/lib/features/social/presentation/model/message_dto.dart new file mode 100644 index 00000000..937babea --- /dev/null +++ b/careconnect2025/frontend/lib/features/social/presentation/model/message_dto.dart @@ -0,0 +1,25 @@ +class MessageDto { + final int id; + final int senderId; + final int receiverId; + final String content; + final DateTime timestamp; + + MessageDto({ + required this.id, + required this.senderId, + required this.receiverId, + required this.content, + required this.timestamp, + }); + + factory MessageDto.fromJson(Map json) { + return MessageDto( + id: json['id'], + senderId: json['senderId'], + receiverId: json['receiverId'], + content: json['content'], + timestamp: DateTime.parse(json['timestamp']), + ); + } +} \ No newline at end of file diff --git a/careconnect2025/frontend/lib/features/social/presentation/model/search_user_dto.dart b/careconnect2025/frontend/lib/features/social/presentation/model/search_user_dto.dart new file mode 100644 index 00000000..2b33dd43 --- /dev/null +++ b/careconnect2025/frontend/lib/features/social/presentation/model/search_user_dto.dart @@ -0,0 +1,19 @@ +class SearchUserDto { + final int id; + final String name; + final String email; + + SearchUserDto({ + required this.id, + required this.name, + required this.email, + }); + + factory SearchUserDto.fromJson(Map json) { + return SearchUserDto( + id: json['id'], + name: json['name'], + email: json['email'], + ); + } +} \ No newline at end of file diff --git a/careconnect2025/frontend/lib/features/social/presentation/pages/main_feed_screen.dart b/careconnect2025/frontend/lib/features/social/presentation/pages/main_feed_screen.dart index 30c5b251..f9696aec 100644 --- a/careconnect2025/frontend/lib/features/social/presentation/pages/main_feed_screen.dart +++ b/careconnect2025/frontend/lib/features/social/presentation/pages/main_feed_screen.dart @@ -11,7 +11,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; -import '../model/post_with_comment_count_dto.dart'; +import '../model/PostWithCommentCountDto.dart'; import 'chat_inbox_screen.dart'; import 'comment_screen.dart'; diff --git a/careconnect2025/frontend/lib/features/social/presentation/pages/new_post_screen.dart b/careconnect2025/frontend/lib/features/social/presentation/pages/new_post_screen.dart index 2264805b..a95790ca 100644 --- a/careconnect2025/frontend/lib/features/social/presentation/pages/new_post_screen.dart +++ b/careconnect2025/frontend/lib/features/social/presentation/pages/new_post_screen.dart @@ -9,7 +9,8 @@ import 'package:provider/provider.dart'; import 'package:http/http.dart' as http; import '../../../../providers/user_provider.dart'; -import '../model/post_with_comment_count_dto.dart'; +import '../model/PostWithCommentCountDto.dart'; + class NewPostScreen extends StatefulWidget { const NewPostScreen({super.key}); @@ -19,7 +20,6 @@ class NewPostScreen extends StatefulWidget { } class _NewPostScreenState extends State { - final FlutterSecureStorage _secureStorage = FlutterSecureStorage(); final TextEditingController _contentController = TextEditingController(); File? _selectedImage; bool isPosting = false; diff --git a/careconnect2025/frontend/lib/services/api_service.dart b/careconnect2025/frontend/lib/services/api_service.dart index 32c447fd..ad6a746e 100644 --- a/careconnect2025/frontend/lib/services/api_service.dart +++ b/careconnect2025/frontend/lib/services/api_service.dart @@ -919,4 +919,58 @@ class ApiService { return null; } } + + // ======================== + // MESSAGING METHODS + // ======================== + + static Future sendMessage({ + required int senderId, + required int receiverId, + required String content, + }) async { + final headers = await AuthTokenManager.getAuthHeaders(); + final body = jsonEncode({ + 'senderId': senderId, + 'receiverId': receiverId, + 'content': content, + }); + + return await _httpClient + .post( + Uri.parse('${ApiConstants.baseUrl}messages/send'), + headers: headers, + body: body, + ) + .timeout(const Duration(seconds: 15)); + } + + static Future> getConversation({ + required int user1, + required int user2, + }) async { + final headers = await AuthTokenManager.getAuthHeaders(); + final url = Uri.parse( + '${ApiConstants.baseUrl}messages/conversation?user1=$user1&user2=$user2', + ); + + final response = await _httpClient.get(url, headers: headers); + if (response.statusCode == 200) { + return jsonDecode(response.body); + } else { + throw Exception('Failed to load conversation'); + } + } + + static Future> getInbox(int userId) async { + final headers = await AuthTokenManager.getAuthHeaders(); + final url = Uri.parse('${ApiConstants.baseUrl}messages/inbox/$userId'); + + final response = await _httpClient.get(url, headers: headers); + if (response.statusCode == 200) { + return jsonDecode(response.body); + } else { + throw Exception('Failed to load inbox'); + } + } } diff --git a/careconnect2025/frontend/lib/services/gamification_service.dart b/careconnect2025/frontend/lib/services/gamification_service.dart index a191d13b..347f44d3 100644 --- a/careconnect2025/frontend/lib/services/gamification_service.dart +++ b/careconnect2025/frontend/lib/services/gamification_service.dart @@ -1,8 +1,9 @@ import 'dart:convert'; -// import 'dart:io'; -import '../config/env_constant.dart'; -import '../services/session_manager.dart'; +import 'package:http/http.dart' as http; + +import '../config/env_constant.dart'; +import '../services/auth_token_manager.dart'; class ApiConstants { static final String _host = getBackendBaseUrl(); @@ -10,17 +11,18 @@ class ApiConstants { } class GamificationService { - static final session = SessionManager(); - static Future> fetchXPProgress(int userId) async { - await session.restoreSession(); // Ensure cookie is restored + final headers = await AuthTokenManager.getAuthHeaders(); - final response = await session.get( - '${ApiConstants.gamification}/progress/$userId', + final response = await http.get( + Uri.parse('${ApiConstants.gamification}/progress/$userId'), + headers: headers, ); if (response.statusCode == 200 && response.body.isNotEmpty) { return jsonDecode(response.body); + } else if (response.statusCode == 401) { + throw Exception("Not authorized. Please log in again."); } else { print("Status: ${response.statusCode}, Body: '${response.body}'"); throw Exception("Failed to load XP Progress"); @@ -28,21 +30,31 @@ class GamificationService { } static Future> fetchAchievements(int userId) async { - final res = await session.get( - '${ApiConstants.gamification}/achievements/$userId', + final headers = await AuthTokenManager.getAuthHeaders(); + final res = await http.get( + Uri.parse('${ApiConstants.gamification}/achievements/$userId'), + headers: headers, ); if (res.statusCode == 200) return jsonDecode(res.body); - + if (res.statusCode == 401) { + throw Exception("Not authorized. Please log in again."); + } final error = jsonDecode(res.body); throw Exception(error['error'] ?? 'Failed to load achievements'); } static Future> fetchAllAchievements(int userId) async { - final res = await session.get( - '${ApiConstants.gamification}/all-achievements', + final headers = await AuthTokenManager.getAuthHeaders(); + final res = await http.get( + Uri.parse('${ApiConstants.gamification}/all-achievements'), + headers: headers, ); if (res.statusCode == 200) return jsonDecode(res.body); + if (res.statusCode == 401) { + throw Exception("Not authorized. Please log in again."); + } + final error = jsonDecode(res.body); throw Exception(error['error'] ?? 'Failed to load all achievements'); } @@ -64,4 +76,4 @@ class GamificationService { throw Exception('Failed to award XP: ${response.body}'); } } -} +} \ No newline at end of file diff --git a/careconnect2025/frontend/macos/Flutter/GeneratedPluginRegistrant.swift b/careconnect2025/frontend/macos/Flutter/GeneratedPluginRegistrant.swift index b2c7dcc4..1f0feecc 100644 --- a/careconnect2025/frontend/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/careconnect2025/frontend/macos/Flutter/GeneratedPluginRegistrant.swift @@ -22,7 +22,7 @@ import geolocator_apple import iris_method_channel import path_provider_foundation import shared_preferences_foundation -import speech_to_text +import speech_to_text_macos import sqflite_darwin import url_launcher_macos import window_to_front @@ -45,7 +45,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { IrisMethodChannelPlugin.register(with: registry.registrar(forPlugin: "IrisMethodChannelPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) - SpeechToTextPlugin.register(with: registry.registrar(forPlugin: "SpeechToTextPlugin")) + SpeechToTextMacosPlugin.register(with: registry.registrar(forPlugin: "SpeechToTextMacosPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin")) diff --git a/careconnect2025/frontend/pubspec.yaml b/careconnect2025/frontend/pubspec.yaml index e1d01e5e..69620f9c 100644 --- a/careconnect2025/frontend/pubspec.yaml +++ b/careconnect2025/frontend/pubspec.yaml @@ -93,7 +93,7 @@ dependencies: # AI Voice Commands # ag added as a fix porcupine_flutter: ^3.0.2 - speech_to_text: ^7.0.0 + speech_to_text: ^6.6.0 # device integration fitbitter: ^2.0.0 From 9edb9d9390d2cadb48e5fd287bd8f329690743be Mon Sep 17 00:00:00 2001 From: dtruong5 <87693856+dtruong5@users.noreply.github.com> Date: Mon, 28 Jul 2025 00:33:45 -0500 Subject: [PATCH 08/15] Update application.properties from local setup back to original --- .../src/main/resources/application.properties | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/careconnect2025/backend/core/src/main/resources/application.properties b/careconnect2025/backend/core/src/main/resources/application.properties index fd90f34b..11885e14 100644 --- a/careconnect2025/backend/core/src/main/resources/application.properties +++ b/careconnect2025/backend/core/src/main/resources/application.properties @@ -1,6 +1,6 @@ spring.application.name=careconnect # JPA / Hibernate Configuration - Let JPA handle schema for now -spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.hibernate.ddl-auto=${HIBERNATE_DDL_AUTO:update} spring.jpa.show-sql=false # Critical: Defer JPA initialization until after database is ready @@ -9,9 +9,9 @@ spring.sql.init.mode=never # Database Configuration -spring.datasource.url=jdbc:mysql://localhost:3306/careconnect?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true -spring.datasource.username=root -spring.datasource.password=@828798boM +spring.datasource.url=${JDBC_URI} +spring.datasource.username=${DB_USER} +spring.datasource.password=${DB_PASSWORD} spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.main.banner-mode=off @@ -48,6 +48,11 @@ spring.datasource.hikari.maximum-pool-size=10 spring.datasource.hikari.minimum-idle=2 spring.datasource.hikari.connection-timeout=20000 spring.datasource.hikari.idle-timeout=300000 + +# Subscription price ID mappings +# Comma-separated list of Stripe price IDs for each plan type +subscription.premium-price-ids=price_1RmqWxELoozGI1YxQql5rsvN +subscription.standard-price-ids=price_standard spring.datasource.hikari.max-lifetime=1200000 spring.datasource.hikari.leak-detection-threshold=60000 spring.datasource.hikari.auto-commit=false @@ -55,7 +60,7 @@ spring.datasource.hikari.auto-commit=false # JVM and Spring Boot Performance spring.jpa.open-in-view=false spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false -spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect +spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect # Optional: Server port (defaults to 8080) # server.port=8080 @@ -81,12 +86,12 @@ spring.mail.properties.mail.smtp.starttls.enable=${MAIL_SMTP_STARTTLS:true} # - mailgun: Mailgun API (production) # - sendgrid: SendGrid (production) # - gmail: Gmail SMTP (production) -careconnect.email.provider=${EMAIL_PROVIDER:mailtrap} +careconnect.email.provider=${EMAIL_PROVIDER:sendgrid} # From email address - Must be a valid domain for Mailtrap/SMTP # For Mailtrap: Use any valid email format (doesn't need to be real) # For production: Use your actual domain -careconnect.email.from=${FROM_EMAIL:noreply@careconnect.com} +careconnect.email.from=${FROM_EMAIL:smpestest@gmail.com} # API-based email provider configurations # These are used when EMAIL_PROVIDER is set to the respective service @@ -107,16 +112,14 @@ frontend.base-url=${APP_FRONTEND_BASE_URL:http://localhost:3000} # Upload directory configuration - Use environment variable for security careconnect.upload.dir=${UPLOAD_DIR:${user.home}/Documents/uploads} careconnect.baseurl=${BASE_URL:http://localhost:8080} -# careconnect.cors_allowed=${CORS_ALLOWED_LIST:https://care-connect-develop.d26kqsucj1bwc1.amplifyapp.com} -# for local dev -careconnect.cors_allowed=http://localhost:* +careconnect.cors_allowed=${CORS_ALLOWED_LIST:https://care-connect-develop.d26kqsucj1bwc1.amplifyapp.com} -security.jwt.secret=YXpDa0EyYUdWc0pFR2hxdEZKTk1wcGlXT0dkU3NFZFI= +security.jwt.secret=${SECURITY_JWT_SECRET} jwt.expiration.ms=${JWT_EXPIRATION:10800000} -stripe.secret-key=${STRIPE_SECRET_KEY:sk_test_dummyvalue} +stripe.secret-key=${STRIPE_SECRET_KEY} openai.api-key=${OPENAI_API_KEY} -stripe.webhook-secret=${STRIPE_WEBHOOK_SIGNING_SECRET:dummy-stripe-webhook-secret} +stripe.webhook-secret=${STRIPE_WEBHOOK_SIGNING_SECRET} spring.security.oauth2.client.registration.fitbit.client-id=${FITBIT_CLIENT_ID} @@ -132,9 +135,9 @@ spring.security.oauth2.client.provider.fitbit.user-name-attribute=user_id -spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID:dummy-google-client-id} +spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID} -spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET:dummy-google-client-secret} +spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET} spring.security.oauth2.client.registration.google.scope=${GOOGLE_SCOPE:openid,email,profile} spring.security.oauth2.client.registration.google.redirect-uri=${GOOGLE_REDIRECT_URI:{baseUrl}/login/oauth2/code/google} spring.security.oauth2.client.registration.google.client-name=${GOOGLE_CLIENT_NAME:Google} @@ -157,6 +160,10 @@ springdoc.swagger-ui.filter=true # Disable default OpenAPI security for documentation endpoints springdoc.swagger-ui.disable-swagger-default-url=true +# Firebase Configuration for Push Notifications +firebase.project-id=${FIREBASE_PROJECT_ID:careconnectcapstone} +firebase.service-account-key=${FIREBASE_SERVICE_ACCOUNT_KEY:firebase-service-account.json} +firebase.sender-id=${FIREBASE_SENDER_ID:663999888931} # Flyway Configuration - TEMPORARILY DISABLED to resolve circular dependency spring.flyway.enabled=false @@ -178,9 +185,6 @@ spring.flyway.enabled=false # spring.flyway.init-sql=SET FOREIGN_KEY_CHECKS=0; # spring.jpa.properties.hibernate.hbm2ddl.auto=none -spring.jpa.properties.hibernate.format_sql=true -logging.level.org.hibernate.SQL=DEBUG -logging.level.org.hibernate.tool.hbm2ddl=DEBUG aws.s3.access-key=${AWS_ACCESS_KEY_ID} aws.s3.secret-key=${AWS_SECRET_ACCESS_KEY} @@ -190,4 +194,4 @@ aws.s3.base-url=${AWS_S3_BASE_URL:https://cc-internal-file-storage-us-east-1-641 # File upload settings spring.servlet.multipart.max-file-size=10MB -spring.servlet.multipart.max-request-size=10MB \ No newline at end of file +spring.servlet.multipart.max-request-size=10MB From b6c04b3d8425cac153b37faae7425cf46729c466 Mon Sep 17 00:00:00 2001 From: Dat Truong Date: Mon, 28 Jul 2025 11:35:09 -0500 Subject: [PATCH 09/15] Safely update login streak and last login date with null checks --- .../main/java/com/careconnect/model/User.java | 2 +- .../com/careconnect/service/AuthService.java | 29 +++++++----- .../src/main/resources/application.properties | 44 ++++++++++--------- 3 files changed, 44 insertions(+), 31 deletions(-) diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java b/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java index 472014b3..3e4b37ca 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java @@ -39,7 +39,7 @@ public class User { private LocalDate lastLoginDate; @Column(name = "login_streak") - private int loginStreak; + private Integer loginStreak; @Column(name = "leaderboard_opt_in", nullable = false) private Boolean leaderboardOptIn = true; diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/service/AuthService.java b/careconnect2025/backend/core/src/main/java/com/careconnect/service/AuthService.java index 79196e83..8a262c33 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/service/AuthService.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/service/AuthService.java @@ -288,12 +288,16 @@ public LoginResponse loginV2(LoginRequest req, LocalDate today = LocalDate.now(); LocalDate lastLogin = user.getLastLoginDate(); - int streak = user.getLoginStreak(); + Integer streak = user.getLoginStreak(); - if (lastLogin != null && lastLogin.plusDays(1).equals(today)) { - streak++; - } else if (lastLogin == null || !lastLogin.equals(today)) { - streak = 1; + if (streak == null || lastLogin == null || !lastLogin.plusDays(1).equals(today)) { + user.setLoginStreak(1); + } else { + user.setLoginStreak(streak + 1); + } + + if (lastLogin == null || !lastLogin.equals(today)) { + user.setLastLoginDate(today); } user.setLastLoginDate(today); @@ -374,12 +378,17 @@ public LoginResponse loginOAuth(String email, HttpServletResponse res) { LocalDate today = LocalDate.now(); LocalDate lastLogin = user.getLastLoginDate(); - int streak = user.getLoginStreak(); + Integer streak = user.getLoginStreak(); + + + if (streak == null || lastLogin == null || !lastLogin.plusDays(1).equals(today)) { + user.setLoginStreak(1); + } else { + user.setLoginStreak(streak + 1); + } - if (lastLogin != null && lastLogin.plusDays(1).equals(today)) { - streak++; - } else if (lastLogin == null || !lastLogin.equals(today)) { - streak = 1; + if (lastLogin == null || !lastLogin.equals(today)) { + user.setLastLoginDate(today); } user.setLastLoginDate(today); diff --git a/careconnect2025/backend/core/src/main/resources/application.properties b/careconnect2025/backend/core/src/main/resources/application.properties index fd90f34b..313748bf 100644 --- a/careconnect2025/backend/core/src/main/resources/application.properties +++ b/careconnect2025/backend/core/src/main/resources/application.properties @@ -1,6 +1,6 @@ spring.application.name=careconnect # JPA / Hibernate Configuration - Let JPA handle schema for now -spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.hibernate.ddl-auto=${HIBERNATE_DDL_AUTO} spring.jpa.show-sql=false # Critical: Defer JPA initialization until after database is ready @@ -8,14 +8,14 @@ spring.jpa.defer-datasource-initialization=true spring.sql.init.mode=never -# Database Configuration -spring.datasource.url=jdbc:mysql://localhost:3306/careconnect?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true -spring.datasource.username=root -spring.datasource.password=@828798boM +# Database Configuration - We replaced the spring.datasource.* one's to avoid conflicts +careconnect.db.url=${JDBC_URI} +careconnect.db.username=${DB_USER} +careconnect.db.password=${DB_PASSWORD} spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.main.banner-mode=off # Disable startup info logging +spring.main.banner-mode=off spring.output.ansi.enabled=never # Comprehensive Logging Configuration @@ -48,6 +48,11 @@ spring.datasource.hikari.maximum-pool-size=10 spring.datasource.hikari.minimum-idle=2 spring.datasource.hikari.connection-timeout=20000 spring.datasource.hikari.idle-timeout=300000 + +# Subscription price ID mappings +# Comma-separated list of Stripe price IDs for each plan type +subscription.premium-price-ids=price_1RmqWxELoozGI1YxQql5rsvN +subscription.standard-price-ids=price_standard spring.datasource.hikari.max-lifetime=1200000 spring.datasource.hikari.leak-detection-threshold=60000 spring.datasource.hikari.auto-commit=false @@ -55,7 +60,7 @@ spring.datasource.hikari.auto-commit=false # JVM and Spring Boot Performance spring.jpa.open-in-view=false spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false -spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect +spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect # Optional: Server port (defaults to 8080) # server.port=8080 @@ -81,12 +86,12 @@ spring.mail.properties.mail.smtp.starttls.enable=${MAIL_SMTP_STARTTLS:true} # - mailgun: Mailgun API (production) # - sendgrid: SendGrid (production) # - gmail: Gmail SMTP (production) -careconnect.email.provider=${EMAIL_PROVIDER:mailtrap} +careconnect.email.provider=${EMAIL_PROVIDER:sendgrid} # From email address - Must be a valid domain for Mailtrap/SMTP # For Mailtrap: Use any valid email format (doesn't need to be real) # For production: Use your actual domain -careconnect.email.from=${FROM_EMAIL:noreply@careconnect.com} +careconnect.email.from=${FROM_EMAIL:smpestest@gmail.com} # API-based email provider configurations # These are used when EMAIL_PROVIDER is set to the respective service @@ -107,16 +112,14 @@ frontend.base-url=${APP_FRONTEND_BASE_URL:http://localhost:3000} # Upload directory configuration - Use environment variable for security careconnect.upload.dir=${UPLOAD_DIR:${user.home}/Documents/uploads} careconnect.baseurl=${BASE_URL:http://localhost:8080} -# careconnect.cors_allowed=${CORS_ALLOWED_LIST:https://care-connect-develop.d26kqsucj1bwc1.amplifyapp.com} -# for local dev -careconnect.cors_allowed=http://localhost:* +careconnect.cors_allowed=${CORS_ALLOWED_LIST:https://care-connect-develop.d26kqsucj1bwc1.amplifyapp.com} -security.jwt.secret=YXpDa0EyYUdWc0pFR2hxdEZKTk1wcGlXT0dkU3NFZFI= +security.jwt.secret=${SECURITY_JWT_SECRET} jwt.expiration.ms=${JWT_EXPIRATION:10800000} -stripe.secret-key=${STRIPE_SECRET_KEY:sk_test_dummyvalue} +stripe.secret-key=${STRIPE_SECRET_KEY} openai.api-key=${OPENAI_API_KEY} -stripe.webhook-secret=${STRIPE_WEBHOOK_SIGNING_SECRET:dummy-stripe-webhook-secret} +stripe.webhook-secret=${STRIPE_WEBHOOK_SIGNING_SECRET} spring.security.oauth2.client.registration.fitbit.client-id=${FITBIT_CLIENT_ID} @@ -132,9 +135,9 @@ spring.security.oauth2.client.provider.fitbit.user-name-attribute=user_id -spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID:dummy-google-client-id} +spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID} -spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET:dummy-google-client-secret} +spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET} spring.security.oauth2.client.registration.google.scope=${GOOGLE_SCOPE:openid,email,profile} spring.security.oauth2.client.registration.google.redirect-uri=${GOOGLE_REDIRECT_URI:{baseUrl}/login/oauth2/code/google} spring.security.oauth2.client.registration.google.client-name=${GOOGLE_CLIENT_NAME:Google} @@ -157,6 +160,10 @@ springdoc.swagger-ui.filter=true # Disable default OpenAPI security for documentation endpoints springdoc.swagger-ui.disable-swagger-default-url=true +# Firebase Configuration for Push Notifications +firebase.project-id=${FIREBASE_PROJECT_ID:careconnectcapstone} +firebase.service-account-key=${FIREBASE_SERVICE_ACCOUNT_KEY:firebase-service-account.json} +firebase.sender-id=${FIREBASE_SENDER_ID:663999888931} # Flyway Configuration - TEMPORARILY DISABLED to resolve circular dependency spring.flyway.enabled=false @@ -178,9 +185,6 @@ spring.flyway.enabled=false # spring.flyway.init-sql=SET FOREIGN_KEY_CHECKS=0; # spring.jpa.properties.hibernate.hbm2ddl.auto=none -spring.jpa.properties.hibernate.format_sql=true -logging.level.org.hibernate.SQL=DEBUG -logging.level.org.hibernate.tool.hbm2ddl=DEBUG aws.s3.access-key=${AWS_ACCESS_KEY_ID} aws.s3.secret-key=${AWS_SECRET_ACCESS_KEY} From c5805a0e5199829a0045f22145aa79158a77bf5e Mon Sep 17 00:00:00 2001 From: Dat Truong Date: Mon, 28 Jul 2025 14:11:50 -0500 Subject: [PATCH 10/15] Add login streak tracking and leaderboard opt-in logic to User model and AuthService --- .../main/java/com/careconnect/model/User.java | 2 +- .../com/careconnect/service/AuthService.java | 59 +++++++------------ .../V25__add_login_fields_to_users.sql | 6 ++ 3 files changed, 28 insertions(+), 39 deletions(-) create mode 100644 careconnect2025/backend/core/src/main/resources/db/migration/V25__add_login_fields_to_users.sql diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java b/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java index 3e4b37ca..a08df5e5 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java @@ -93,7 +93,7 @@ public LocalDate getLastLoginDate() { return lastLoginDate; } public int getLoginStreak() { - return loginStreak; + return loginStreak != null ? loginStreak : 0; } public Boolean getLeaderboardOptIn() { return leaderboardOptIn; diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/service/AuthService.java b/careconnect2025/backend/core/src/main/java/com/careconnect/service/AuthService.java index 8a262c33..537d1b39 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/service/AuthService.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/service/AuthService.java @@ -286,27 +286,9 @@ public LoginResponse loginV2(LoginRequest req, /* ---------------- Gamification: First Login & 5-Day Streak ---------------- */ gamificationService.unlockAchievement(user.getId(), "First Login", 50); - LocalDate today = LocalDate.now(); - LocalDate lastLogin = user.getLastLoginDate(); - Integer streak = user.getLoginStreak(); - - if (streak == null || lastLogin == null || !lastLogin.plusDays(1).equals(today)) { - user.setLoginStreak(1); - } else { - user.setLoginStreak(streak + 1); - } - - if (lastLogin == null || !lastLogin.equals(today)) { - user.setLastLoginDate(today); - } - - user.setLastLoginDate(today); - user.setLoginStreak(streak); + handleLoginStreak(user); userRepository.save(user); - if (streak == 5) { - gamificationService.unlockAchievement(user.getId(), "5-Day Streak", 100); - } /* ---------------- Resolve profile info ------------------------------ */ @@ -376,28 +358,10 @@ public LoginResponse loginOAuth(String email, HttpServletResponse res) { gamificationService.unlockAchievement(user.getId(), "First Login", 50); - LocalDate today = LocalDate.now(); - LocalDate lastLogin = user.getLastLoginDate(); - Integer streak = user.getLoginStreak(); - - - if (streak == null || lastLogin == null || !lastLogin.plusDays(1).equals(today)) { - user.setLoginStreak(1); - } else { - user.setLoginStreak(streak + 1); - } - - if (lastLogin == null || !lastLogin.equals(today)) { - user.setLastLoginDate(today); - } + handleLoginStreak(user); - user.setLastLoginDate(today); - user.setLoginStreak(streak); userRepository.save(user); - if (streak == 5) { - gamificationService.unlockAchievement(user.getId(), "5-Day Streak", 100); - } /* ---------------- Resolve profile info ------------------------------ */ Long patientId = null; @@ -685,4 +649,23 @@ private Map getUserInfoFromGoogle(String accessToken) { private String generateSecureState() { return UUID.randomUUID().toString(); } + + private void handleLoginStreak(User user) { + LocalDate today = LocalDate.now(); + LocalDate lastLogin = user.getLastLoginDate(); + Integer streak = user.getLoginStreak(); + + if (streak == null || lastLogin == null || !lastLogin.plusDays(1).equals(today)) { + user.setLoginStreak(1); + } else { + user.setLoginStreak(streak + 1); + } + + user.setLastLoginDate(today); + + if (user.getLoginStreak() == 5) { + gamificationService.unlockAchievement(user.getId(), "5-Day Streak", 100); + } + } + } diff --git a/careconnect2025/backend/core/src/main/resources/db/migration/V25__add_login_fields_to_users.sql b/careconnect2025/backend/core/src/main/resources/db/migration/V25__add_login_fields_to_users.sql new file mode 100644 index 00000000..7d388d93 --- /dev/null +++ b/careconnect2025/backend/core/src/main/resources/db/migration/V25__add_login_fields_to_users.sql @@ -0,0 +1,6 @@ +-- Add login tracking fields to users table + +ALTER TABLE users +ADD COLUMN last_login_date DATE, +ADD COLUMN login_streak INT, +ADD COLUMN leaderboard_opt_in BOOLEAN NOT NULL DEFAULT TRUE; \ No newline at end of file From c8040a62d6bf8acd46d7eb2f58e574f49d045455 Mon Sep 17 00:00:00 2001 From: Dat Truong Date: Mon, 28 Jul 2025 16:02:36 -0500 Subject: [PATCH 11/15] clean up User entity with required fields --- .../core/src/main/java/com/careconnect/model/User.java | 6 +++--- .../db/migration/V25__add_login_fields_to_users.sql | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java b/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java index a08df5e5..fd1d4a25 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/model/User.java @@ -92,8 +92,8 @@ public boolean isActive() { public LocalDate getLastLoginDate() { return lastLoginDate; } - public int getLoginStreak() { - return loginStreak != null ? loginStreak : 0; + public Integer getLoginStreak() { + return loginStreak; } public Boolean getLeaderboardOptIn() { return leaderboardOptIn; @@ -114,7 +114,7 @@ public Boolean getLeaderboardOptIn() { public void setLastLoginDate(LocalDate lastLoginDate) { this.lastLoginDate = lastLoginDate; } - public void setLoginStreak(int loginStreak) { + public void setLoginStreak(Integer loginStreak) { this.loginStreak = loginStreak; } public void setLeaderboardOptIn(Boolean leaderboardOptIn) { diff --git a/careconnect2025/backend/core/src/main/resources/db/migration/V25__add_login_fields_to_users.sql b/careconnect2025/backend/core/src/main/resources/db/migration/V25__add_login_fields_to_users.sql index 7d388d93..17312077 100644 --- a/careconnect2025/backend/core/src/main/resources/db/migration/V25__add_login_fields_to_users.sql +++ b/careconnect2025/backend/core/src/main/resources/db/migration/V25__add_login_fields_to_users.sql @@ -2,5 +2,5 @@ ALTER TABLE users ADD COLUMN last_login_date DATE, -ADD COLUMN login_streak INT, +ADD COLUMN login_streak INTEGER NOT NULL DEFAULT 0, ADD COLUMN leaderboard_opt_in BOOLEAN NOT NULL DEFAULT TRUE; \ No newline at end of file From f0e1af1d737e5c41d7d28bd17a47b8bb0d088d69 Mon Sep 17 00:00:00 2001 From: Dat Truong Date: Mon, 28 Jul 2025 17:52:50 -0500 Subject: [PATCH 12/15] removed Billing module --- .../target/classes/application.properties | 20 ------------------ .../Careconnect2025Application.class | Bin 800 -> 0 bytes .../careconnect2025/ServletInitializer.class | Bin 977 -> 0 bytes .../careconnect2025/config/CorsConfig.class | Bin 1279 -> 0 bytes .../config/ModelMapperConfig.class | Bin 358 -> 0 bytes .../config/OpenApiConfig.class | Bin 1027 -> 0 bytes .../config/SecurityConfig.class | Bin 7752 -> 0 bytes .../careconnect2025/config/StripeConfig.class | Bin 1014 -> 0 bytes .../controller/AuthController.class | Bin 2440 -> 0 bytes .../controller/CaregiverController.class | Bin 372 -> 0 bytes .../controller/GamificationController.class | Bin 773 -> 0 bytes .../controller/GlobalExceptionHandler.class | Bin 381 -> 0 bytes .../controller/HealthCheck.class | Bin 996 -> 0 bytes .../controller/PatientController.class | Bin 366 -> 0 bytes .../controller/PaymentController.class | Bin 2847 -> 0 bytes .../dto/CaregiverRegistration.class | Bin 364 -> 0 bytes .../careconnect2025/dto/LoginRequest.class | Bin 1658 -> 0 bytes .../dto/PatientRegistration.class | Bin 358 -> 0 bytes .../careconnect2025/dto/ResetPassword.class | Bin 340 -> 0 bytes .../careconnect2025/dto/TokenDto.class | Bin 325 -> 0 bytes .../dto/auth/CaregiverRegistration.class | Bin 2849 -> 0 bytes .../dto/auth/Credentials.class | Bin 1677 -> 0 bytes .../dto/auth/LoginRequest.class | Bin 1683 -> 0 bytes .../dto/auth/PatientRegistration.class | Bin 2492 -> 0 bytes .../careconnect2025/dto/auth/TokenDto.class | Bin 1503 -> 0 bytes .../dto/gamification/GamificationDto.class | Bin 372 -> 0 bytes .../dto/payment/CardSubscriptionRequest.class | Bin 2578 -> 0 bytes .../dto/payment/SubscriptionDto.class | Bin 2350 -> 0 bytes .../dto/payment/SubscriptionResponse.class | Bin 1772 -> 0 bytes .../dto/shared/AddressDto.class | Bin 2240 -> 0 bytes .../dto/shared/ProfessionalInfoDto.class | Bin 1908 -> 0 bytes .../exception/AppException.class | Bin 349 -> 0 bytes .../exception/EmailSendException.class | Bin 367 -> 0 bytes .../exception/ResourceNotFoundException.class | Bin 388 -> 0 bytes .../model/auth/Permission.class | Bin 345 -> 0 bytes .../model/gamification/Achievement.class | Bin 364 -> 0 bytes .../gamification/GamificationProgress.class | Bin 391 -> 0 bytes .../model/gamification/XPEvent.class | Bin 352 -> 0 bytes .../model/payment/Subscription.class | Bin 1077 -> 0 bytes .../model/token/PasswordResetToken.class | Bin 371 -> 0 bytes .../careconnect2025/model/user/Address.class | Bin 1738 -> 0 bytes .../model/user/Caregiver.class | Bin 2958 -> 0 bytes .../careconnect2025/model/user/Patient.class | Bin 2447 -> 0 bytes .../model/user/ProfessionalInfo.class | Bin 1205 -> 0 bytes .../careconnect2025/model/user/User.class | Bin 2207 -> 0 bytes .../repository/AchievementRepository.class | Bin 378 -> 0 bytes .../repository/CaregiverRepository.class | Bin 395 -> 0 bytes .../repository/PatientRepository.class | Bin 389 -> 0 bytes .../repository/ResetTokenRepository.class | Bin 375 -> 0 bytes .../repository/SubscriptionRepository.class | Bin 643 -> 0 bytes .../repository/UserRepository.class | Bin 646 -> 0 bytes .../security/JwtAuthenticationFilter.class | Bin 3325 -> 0 bytes .../security/JwtTokenProvider.class | Bin 3494 -> 0 bytes .../careconnect2025/security/Role.class | Bin 1277 -> 0 bytes .../security/UserDetailsServiceImpl.class | Bin 3014 -> 0 bytes .../security/UserPrincipal.class | Bin 350 -> 0 bytes .../careconnect2025/service/AuthService.class | Bin 7572 -> 0 bytes .../service/CaregiverService.class | Bin 357 -> 0 bytes .../service/EmailService.class | Bin 345 -> 0 bytes .../service/GamificationService.class | Bin 366 -> 0 bytes .../service/PatientService.class | Bin 351 -> 0 bytes .../service/ProfessionalInfoDto.class | Bin 1893 -> 0 bytes .../service/StripeService.class | Bin 2490 -> 0 bytes .../careconnect2025/util/DateUtils.class | Bin 330 -> 0 bytes .../careconnect2025/util/EmailTemplates.class | Bin 345 -> 0 bytes .../Careconnect2025ApplicationTests.class | Bin 598 -> 0 bytes .../frontend/lib/services/auth_service.dart | 2 +- 67 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 careconnect2025/backend/billing/target/classes/application.properties delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/Careconnect2025Application.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/ServletInitializer.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/config/CorsConfig.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/config/ModelMapperConfig.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/config/OpenApiConfig.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/config/SecurityConfig.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/config/StripeConfig.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/AuthController.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/CaregiverController.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/GamificationController.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/GlobalExceptionHandler.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/HealthCheck.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/PatientController.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/PaymentController.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/CaregiverRegistration.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/LoginRequest.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/PatientRegistration.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/ResetPassword.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/TokenDto.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/auth/CaregiverRegistration.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/auth/Credentials.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/auth/LoginRequest.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/auth/PatientRegistration.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/auth/TokenDto.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/gamification/GamificationDto.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/payment/CardSubscriptionRequest.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/payment/SubscriptionDto.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/payment/SubscriptionResponse.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/shared/AddressDto.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/shared/ProfessionalInfoDto.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/exception/AppException.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/exception/EmailSendException.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/exception/ResourceNotFoundException.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/auth/Permission.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/gamification/Achievement.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/gamification/GamificationProgress.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/gamification/XPEvent.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/payment/Subscription.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/token/PasswordResetToken.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/user/Address.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/user/Caregiver.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/user/Patient.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/user/ProfessionalInfo.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/user/User.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/repository/AchievementRepository.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/repository/CaregiverRepository.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/repository/PatientRepository.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/repository/ResetTokenRepository.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/repository/SubscriptionRepository.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/repository/UserRepository.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/security/JwtAuthenticationFilter.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/security/JwtTokenProvider.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/security/Role.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/security/UserDetailsServiceImpl.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/security/UserPrincipal.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/AuthService.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/CaregiverService.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/EmailService.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/GamificationService.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/PatientService.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/ProfessionalInfoDto.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/StripeService.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/util/DateUtils.class delete mode 100644 careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/util/EmailTemplates.class delete mode 100644 careconnect2025/backend/billing/target/test-classes/com/careconnectpt/careconnect2025/Careconnect2025ApplicationTests.class diff --git a/careconnect2025/backend/billing/target/classes/application.properties b/careconnect2025/backend/billing/target/classes/application.properties deleted file mode 100644 index 9ebdf779..00000000 --- a/careconnect2025/backend/billing/target/classes/application.properties +++ /dev/null @@ -1,20 +0,0 @@ -spring.application.name=careconnect2025 -spring.jpa.hibernate.ddl-auto=update -spring.jpa.show-sql=true - -# Configure the H2 in-memory database -spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE -spring.datasource.driverClassName=org.h2.Driver -spring.datasource.username=sa -spring.datasource.password=password -spring.datasource.platform=h2 - -# Optional: Enable the H2 console for easy access -spring.h2.console.enabled=true -spring.h2.console.path=/h2-console -security.jwt.secret=yourVerySecretKeyHereThatIsLongAndRandom -server.port=8080 -jwt.expiration.ms=3600000 # 1 hour in milliseconds - -stripe.secret-key=jahgsf_jaskahsgfk -stripe.webhook-secret= whsec_kjhdfkjasf \ No newline at end of file diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/Careconnect2025Application.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/Careconnect2025Application.class deleted file mode 100644 index 8010d61f9284a49aca0bd3a3baf227c43745a9da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 800 zcmbtS%TC)s6g?9X970-XDDPGkyO2evLMoe3B#6YyJVa2s=xS^RGhoj|<8jMxbydWo zAJC6N+%ZV;B3)t0_v*gyoOAy@KKupn4If*mVJ1MmjRs~J7H`B)!Be3Vej44#*fGrR zDXrW-!%TO36rjnl7n_X7!phiaO*vy%wRXPjeC2~l)Y}K+ajIhBl+i7;Fdv}Z#v8n4 z=oy>vd~B6YuB^!9FJo_cWQ^m((pjmb4Q!zqqHZ|Za*&EV@BgTHhR!$ZZ->)5)5cET zIX*D@N+pFAQ7S7V$nC>@hWfxE9m)1{0y%GM-wYp XCI1@z4Z@jJ4eO=cz$R%w_5<()W^2?^ diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/ServletInitializer.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/ServletInitializer.class deleted file mode 100644 index f92f32310e42c5dc93b0f43c24b6c0b87daabdf6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 977 zcmbtTTWb?R6#h=K>Dp~Jv3QR10RAjJzpvkzzZ&gGkPzO(b==hyE59^+<&2AUy)Hd>4{3WnpzbKCp74 zj*WdM5@Vb=R*5K;J<605WmOc$Ix(yh`y3ulBfGhFT4wr#vV^4_&F~4K+1=U;afYz= zRvt-_$$TsZnJmi?5n(4a6Ol@*Qj_N@b%i^Lw(o5}5QBNt1?FztmP97{b_&@mZq|)DhV&U`|)p`_O{|vQ};r22>1U#=HEmN24-`Oe4@nt%;#G74}Bmzl&D_nF9(uHFg0A$-&9EDt|pWw#=|3(0eFq&IQkHYqCbHF3^I%!^3U9Ix!17X z*A9iF83tFSC-oXbB$KU15o0L$s$m5!CB4SJ;!V-<)hDYXYL?@xz}h6(^u2xAXe+Lz z@5K>C#K3R@3EW|r;;okZbw{YC)LUYow_RQ3Zd(Kl#Z1=jjlC2@l5*SGtC$dD38XMi zb?|z9-F5wrsBa5>;MW-@Gxm9LyV_)5%2s2Tq!PEvcDYiHAx%tg%atgm8CGx1V^6so zJP`FO)$aOj<%l?@5Q*Ud@maZ?v1Su^h&hI7B^ol&LS6A>C|{FeukA1zJ!V*T{HEn_ zB^=-LNXM4GXcd==E2NeY>4IJi{hNMWFeGj1iFfU0O{fa5xwMnAeTTbMuB5R~H~aO0 zB+XXrTf{0cq|LZonv`zu7+&=}1zk@f9qd-cu+(eg$TtYJV_3V{_aE9*7SkWvo|a8f zm4T#wt$UuY!?!@~TD+E=($?aOw)ojqYeR64xZ>T~-bkZE@_cFvW||pZ{x997PM7eA zRv-NXK*-GFCwxMdMPEkWf!s0JkMQ^@p*aSIF;AH2UcfUvCyOD2ERh)JZ-~$W3MYtu zJjT!w;&whYa)i-*Y9hqDgeRDy!+YBcsr&gO%q)B-k3N_vv-E_45sH(<7)FsMoS_#z rk8vzuB8)!Qjc^)$nBwM;r!3|%M0Xa6sX!biUn}HY3h~9+ZV~tk8k=LG diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/config/ModelMapperConfig.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/config/ModelMapperConfig.class deleted file mode 100644 index 027b1df4a3e52678768a1410808d6d73bd0f29dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 358 zcmbV|zfQw25XQgDpQeP;Rt(+QfB~aQEYPx4>XgC&>h2g*T%~qoQr?RRiGc^;p%CY& z0}@-$(r16aJL!D){qy++V2WXm2yup_hZG&ca4p}YsH9zq`*N*SL+DJ5HO(y{K0RAz z=n}4#+XyASR?b?kshhuUd^x@noOw1Yv2ahiTFAQAe%2B~GGj&<6vpbi?WWZJQI-{x zmDBY-Xp$pQSG) zk;EV1k22obqUeL#xSQPB`+e@YJ72$j{sQ1Bcz_Y)@)!*W*9ykib!JLS8H)SG)d0rI}+1jEuFLzQkC zzC$8FfLs9=8K$;`m0Lz@>KkQc0!)Xvgd)S3Q?4x;9+&Iq5ci!`x>Z}P|GP`AfLR*h zes$gVyBy+*ubS^kn<}FVm}gkr6HZC(qLs)4sk9V%qogfd)7XwryB6ZQPn$TDX>3*E zDAmt;Bg9Qa45NN1hVAk&fc}E|jK*3XOAL=<)8R1ziU-Uj?pJMNed8e~PgBvxJo||v z21z3{TeK8nwXU>$+3hr>eJvWE!(!dUqJ1E&^7@%^)V)(F@mv4Db~PeXB+4(8VYxiS za18UWy4tCZJW#13Ae&knM=LOza8&BXws@NO1vjne$YW#Q`$3)j;JEl1%AZT2sd=z( zx;B9K~P@+5HA1~>afZ-N>aXOi|tet>;%G}E&y&IUs zZIa<204umd8c@b674gY$k;{CgQ{>;A;KC^aI>O>)A5+DdKIV#7`&j5>@goI~z!zSi o(!eymMa*G_^jR#@<%q~~-mG-iY>i?)l)G8}y-Zf{!&(Kt13BLYEC2ui diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/config/SecurityConfig.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/config/SecurityConfig.class deleted file mode 100644 index f53d2fcb2b9a68a414f9e4cae06d71d60e746755..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7752 zcmcgx`F|VL5&u44Yb%??QI3+7rU62+ohX|)2{e_V*ow%3ow$}`;)Fuit7mx=X;bYNqAjKOLGSi2>8JB{SJG-Dv~o@OLE7E7Z|2Q+X5P%a z*=PTI;rjq?#(!dHL%W0y8J*~2=sls%t4dxqa?0VE6I^o`x`uQ^cWz;5@9UqE(9LkG zY2_5VXz507)=~@nlxdw*Y_63o-C0mHYoX|v%8YweMpEK&T(xbQl}#I(ndMdtl2~*Z z!-1x()RHsD4M*2hM>h>6tD4GQ)f}!H9k+D*EP3H>870Jk zmWY#u*2#Dou43rXO15Jb^m_@wU|)X}0%KOsDX9vHm5gBnUM}Hk8Lz-bhW!x=aLHA} zFda|vr}&IAQ_}OKW?R|sIK_-_Y=#cawq_abi$ZILMQRO0D?%jWU|1(0UL)gLfp3+~ zZJRu1Of}RTFOUnx$<5s<*UNa7n6g&*mT8ef1)d3hPl?-(9YY`bB@D>ef~^cUX=Xvu zREuk-VbEqMI>D#G?Ss1&dLwjQQAo;tWLR0TtMBo&X(Fu_;SfT$Z88*WC$#lDqp}AW zo{6+Wv~x!|SA>`mcC8m&X}p(Re;pK9cQQO20jV{P5n}mfY{}gu>}D8=c8zL8Jb22f z$9bEccSIE2g_}g&eKo`VeWQzGIBA~bM#b*Q2$QuG(ImWvVX|pt5OE-+ZV?;}$^n(| z)fngo49S#jD2c38vsn-R2%jan7)pJkC)AUw<*1ZHtob~56mLS3#w*Dvm8UqFd=ZT( z!o{sJhB3m>>BcsOHMB)*OM3Y)DPb?eu1K|10)QuaYEIRS7;eKp3HxOnz(IywQy!N) zU`Wy4gp%b&J_9-=&<5r@iyJM3K%UsequI8P&7^ZNCgrhR1 zag5=CMe0sCYDHlpX9*aW1^bHDxV5?R-c_!T($5KFs-w+uYl7!=+p!i1kL4u`|AozP zL*Ma$-+0O(Uv1z@cQWK(A}Bn2#LCBT9CyjM8*iWzqR6d+?hNPi3=c)Qfu}qr6c4YA zN|Qa9fhHjl$fjj;0WtOVZeQENU>l;SC(Bxz{=4f}j?v+yu`fR13 zEO6mxCxnxoWEgFnXu`}|vvm&&FeI2Vinxb?yfLbhsHTevGf!br8XulKkUm5hcPu6~ z2x3Z~1=unil&C-x(K#I3d><7;o|17|NZ6Aao=ERIFqNJVPIIq}H{w2q*qmw)8w;LD zk2Uu5M&_!5c}X2fSo6&?-Xel}OeL|sohksmO~(CV7c371IG*jnJMc~k?~?IuyoceI zR-m~4knsx4l*QeUF>dB{jbcMjW^#BkJ(|vBsNiepnqkGfFgLQ|d-0%z_sMt&XBdvP zB3zRV>x*2giEHuCJuJBua~RO0jrYVNP(v1hd_cknWqb%9W*A$dic)lz z32H^k>b5!~cH_bSFJSHuoRvnr15}c>WQq>Klt@N(gC8mtX1F!!tr8zKH8nq_TDo}l z2RocOos!I+7NUT!%#43J^Ptdz>nBQdiZ1Xe-PVcmaP?$Id=m}blsGy3v=dAKBV09z zS9>W-R3iz40sjmH>kZHI(5X2a8TvIP@YRZNG97yS&-TyvtRs<>95%oPqTq8M$G=JdXI zwxkJBUWNcN+`J_HP)bOj)_760l57cIXINdU(5i3z&Ooi}R4tt!U$mh^uIAxyp@NS( z=Y;qU*Y(3HH9Dobx&;~Z#hNFyP93|@$vx%vJ$!8B5o_{lVJ53?4s4apgA8XDt-~u< z$y0}oq8SzQ3=b|=GgnZm#=6_VaQBjR*DR9?WwhA^hC7$4i)O1d21gC8i6w)p&)u$Z z+cb*ShnI>c#F)ZJ#w=MHrw~X!=97MM< z5JD*_(Q&AH*!8*tX+zWxUgoOECvw?{X*zU>Rg2y$du11X6vL146A3?+@eF>(aBVFn z>c%`ZnUqm?CliG_t7?uZj=;-k)!Nf@qJdLs)c8c+0{c}Xo9FiCQPVtGDhB&G{sH8L z2qzbcJUG3MYtxtBlFey@M{}QZO3mjpy2F#b_ywhaAR@mH)IUYq`lV>XJ}2Y5c&Zmq z(ZFxSz;9)I2T%6mNgDWr82F=%CvdhGXQ>}@fI7#P+rHwIb^gL|*Ak0V5$>DxG8hIb zGg|3pEU!#<`vnsIL1*fgtSRAN3>%wIrH@CR>2VEdrH#I40NvEAr%z4v{}Fmp=*sBY zIdC5AEZwx>qx3IcgT8!xjQ(YB0v^G~>4}NXJk1isgLD=1x-UYSK98O!=srllAwb{l z(|>~I2=1SxD;>+R9qlf!Iil>Ku{9SFBVO{M#FjFaC$^TcDpBJbcjs&*5U<2?Y(kuX z2^j}GT7M1Vf=}Vou54@YC_Y2Hy9vi*_$;mVIhx&Gk1r4_0qzmfk`QQU z%LS|j%2W6SV%PN6#ChBp-&02N4wC(~myoInzJWl4 zY_J0TSV!Vsjco*7p$_!+0Dz$?fFXPlUvdEq;mi060U$|SQ0@3C-FMKtuenf`x6@S6 zB)E|3;d}$%^x^Cw0>Pw{L`&D?@(jJ(m0_JXp(;Zs9>=$ctbV;tM#FV#>y3%sMiYhF z&iKA_bU=C9$@xz(v&iLyx(_QgQri>$(aJ-DypM%=r&B@WAPJ^B9<2mHZ zu!!s&=EY4J3uU~?rFrWeH6QhSA%XSek=w{8$B+p4+fkguw*iukLW3{cP687<7^0-+vi(Ux2y`7jThgcHn#T--f4MKNBB& z#dqHy(C-rcHjz92kbXbMv-p*N_iOylzxzG@gg?`)4K!m4&*QIHMU9l_@i&s;@AxPF HjrIQn?|um| diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/config/StripeConfig.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/config/StripeConfig.class deleted file mode 100644 index 212c34494c77ff7c775ef78515166799a7d5c61d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1014 zcmbVK%Wl&^6g`uMIuDmN>ASqly9rR2LW@uU2`aH6Kt)j&?56hAohJ5J_5_+8|GseY z(FL&Oz^N(!dZr)K_@rnbj84l zlqRU1-|s&-F&~5?(y8OilyCKfU%`1?u;AFZh)WEUSq;aD$z^P0dJ!ov{$FLIQ^pls zwQ$YGb<@q7>1LWWYG{YPgU6EER6>I~WvY)wX0E5WFZ?Bj;=1w$!+2Xp;zd8)63J_0 z(DYS#yw~B0G$F)e#Ob5f*rsiJvIgf|D^RN1mmZ9j#RFc}`h?>W> zR1t|eS?PN&KfH7|RH|tfsMfxxiM6t+`iUo=N@Kj1mzmGXl4&f%T3aQ7o0@(G+X)ZF zJC$_JY-zElk22?3{Uj4_(dU?n2Nl7&F@e#y`UTgz5U31DxA8SD$g&;6;Ne ze*{Klfmnb#YBcQgm?AtwwrA16+!590P<5GNizw7exJ@Th#tQ8P+{xCvhqtT1U-DY_ AcK`qY diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/AuthController.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/AuthController.class deleted file mode 100644 index 50ec8cd858b0c375d5234b87e72f662bdd676478..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2440 zcmcgu+j1L45Iv(<77|Mw+YoTLB;*2?4VmCza*=K9$fok31efg63r{O)kSCURHnStA z@-6%XRB>0(IWDOp`GE)RPS19qKGQwjt$+Ug>u&(-*eoH33k6IC$YWaI z(zEb5l-)4u$nTraRLcoWujxp;4S@@bMs@YPrxI5wPhz=m4pp@64Eeu{_X@Zi;C*}`@VT)anZ#B{ zogZy@q+S?%C=Z;AWnCq)i4wIFIqhBw%0uRREKiro*%}+>s5T?X(-&o{j)4Mct)`J_$-U7WN~dI fiwAFF@o*%I94h3T!&=IHj;nRb8=i9-x^Mpjni8$N diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/CaregiverController.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/CaregiverController.class deleted file mode 100644 index be370377732517e8005ef13701f4cecf709e677b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 372 zcmbu5PfG(a5XIl@pRTUe7EfNisRttkPihetdRp|L^q!5Orlg6)wBO5<;K2{zhY}}6 z#6xc;@Fu@E0~6lo*ZT*6D-3I7$SV|ml;{zLbGeejN;eaC-CV0k=v^9T;+T*h9ZxH4 z5w4YA2qlA7-Z`Fmj(=%%HaZvFiNRZ|gJ^hgW>z{hzaF7z{G%oeTI2NXa?$DVAUn&> ze(ROAQyEO+AG3&0=7n(6{&(>Op}zOap!CF~Z3i1ZcbX!=Au3KL9pcN`ux>MnH3Q|z c8|amd3^i+N137kB`|Eefb(e{-$C_dA4NwnXZ~y=R diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/GamificationController.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/GamificationController.class deleted file mode 100644 index 537a5744377df4f42fa96c209149f97c6f1e9035..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 773 zcmb_a-7f<{5TEU}O4V1@_X~Y0!P1b3uS6QW91^52p7zeF+JrEUcU+lvuJPta%Po`F0y%Xzf63`prioBsqr zKMY`m+Xq8ingke0zGaxaHAZN7690OY|U_6d~ IqJ^0J0Pq3H&;S4c diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/GlobalExceptionHandler.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/GlobalExceptionHandler.class deleted file mode 100644 index 377cf8773ea37f7f46600aeb000c430535317a00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 381 zcmb`Dy-ou$5QJy%CkIC$5iJ!RP+$~^3P=+rp*TSSO7C+laOBvLaS*RXg+##v@KA_# zL_tA`Esgh^wY01G{CfWYaD{%240(m3ixM3|e<7cxu+lw<+jgNL8}px6+PZlwL*S-L-Ucwh6_^&o!af7^iR6 z%T|Yb*;;n?8?U6D$zT%ym_>XvPlR#v53?@_^_^b_r6(p0JotOX)071cQE^1+5MK_7 jb(=|S7${HPK(B0Ms994R$g#uP-P9$&yG(>V)(pLGo{VCD diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/HealthCheck.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/controller/HealthCheck.class deleted file mode 100644 index 82883290f40ddc53391110f7df225ac0b5a56444..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 996 zcmbVL%We}f6g|#s+BBpgJj-)J7o;HJssag-wg^Q$7J*6)%7&FQaWifw_TcfP;;&!@ zV!;RSQHVR!Dv1z_z(`}y@x90QTp!QRU*CTK*u!QO1r$A$YAB<^&^Q)T!6Tu^{9t%2 z1IJL=Ra&`yhGKhbu!0qac2`_BMp_obbxKqfw|`DyG6J@Rd;wbJ8O6;-TxsMT-* zb%y)Kj(HMW3LRN7k*CI<@X)!KAIc;)I*~86Q|=Q(!JIJcwR?Z6>1_3u+@s^6!SFON z6CTXf#%>YqZ0|hb)Q7VsilpUlq=;PD4P`*pOIMLoXI+Bo49ImGbzuMV8bAs;F|;$oJ|UrYB^#BsJMLwPEql*D=btV@P!-=sE> zuT*xZjfJy+l(|mcEnSFikL%8Cq+Sy{YV9u^Duq)^6>v_%w gZqQsO?oE1ceE>^vo49xK=-oWpB@(M*4B|3!uN zyR@CQVmspp=!fd`*-MCrf*g+1KlXOtz4!O~?z8*PzkfXka0}n&kU`c!kA+_J2@F1x z+tO}Iw_z_=AE{a-&^P0_PBbf!EsU)jI3X}y^S11o3{=f?UGn%*lA63eIc0|`*mi2l zu0(<3t4f>7!9dnTKP&?S7Ea=n!0gewXeA1~W>W?Birm>!ZdB^-2n>Wt69PBOM{7LL zyP}DAFl6Agg)?|p;F1?K?9dM!xA9{jx6~6a*t9pI$hQ~ysYEhrg#t!tbzyF8;jX~t zateWB4)5WtfpZql<9&fEhsvy~(D&R>Ex3^r?XV1C$Knr#G=@rET@}X;=iIM;bl$7) z%(t9oT?KT}zy%8*;v<24El@I2B^lH!t!h|fH6zD!1%581$a6jNY`Tyej z5q&(mUP(PkH}--k_G1JW4P3G?ipv6@rOawFY=NFClOS-bP=5Ikq)%jA-zydh7!$~N zn>;LqH>-jUX~M!MxGKHdo|fymw}_{ z&Sp=v;jo|Yq$tPh4kYcGVY^eRG!?wPHc44iI*<+2V}biAB1tyZVGSyfTllG_;=T_J z+!Pqq9ovQ-TkZYoKwX!WSh}R54X?f;HF`uUV0O)l({KqK0aLU4zo%yAm>D_nsLe9L z=T=)pr7gAYgbq7s&UHP46LEndP92CjPgK>eI&R&TU0!>|3!^3J`vh3(?A?~l7Socm z6YXAh@?$vryl!Yuj?f>UfeI-t-~9-+W{<%lvHjoq76?U@Z}_4 z==+Z0BoL&tlNto5I~zI;eM(=zP)7;P5>A_*Bo@fGYj!4KZQW!3be&Jw-6*G1m$j3p zMJ4rR!egMF_tn)z>*p$7E2yb^jz**WE0_8;9k{@aW0(vRZvxspj%a)hz39UMTl&u4 zt;K$}9`KD5l$tUO70Ld@O-Y3A=kp0*vGi+(t1Q=iNb!4()ND)xgX2#j#{Y)#-BXx< zAonZ(WnlBGpI<;8JvfW&Tv?PPKR(4IXWnG^oVT&owL)E*_snk?+(mx;cjPAi#0SqX zoWU>LZ|gfrE?}5`jo>tAtux=2>*%BgOyLXap60(E&NuKSSG_#CoQfP7%v zUEfR!F2?^&Jm4x5`(|di4Q}lD_tgRZeH}YAl%aH)<93K8PTH?59`c*Pax68()e7lv UG*cYeDk`LNq(}HVTkA9b1CcJWkPY4$hPa8;taPO7|OP1D@!h3l#@!$jaP{t|o z;K7@n!ow~+@a8fn*JR&TTlgw}(xCX5N`)%CJK zn=n*vE0pwFIcvG+CH$tr-QZrVf)f+&Set|PPn>qa%V3-(WD~d2gl=W5p6<7`_6u1z z>|9h%$!00NiTq<`;o0m6qv{_shlFzO_Fm~n69@JGeA#XMIxbQ0M&cn3-U;golQ=Sv d-@Jid*+@{b#%mzODQo9g=bX=&2W@|H%hQ zBqlug1N>3OJKe1;DH7yiGjs3Wd(ORQ?#!>>r#}I#V?Bcy`ZUCKB#>knI^aj#w7Iiy zZi||9>kP>?%dz~I41M{+UK%N+HDq-3LuV+~WYes1mqHy!)clqowTg?y6|?S3vm*B` zXIp$~3(sdrh$gpehFs;0Y{z#kXTPN22X$l>cB;ia?=zKv(6+SS` z<|F=4xL!|Tk0Bm(lC4;dc-wAPg}ckEHuaXP$QrlzxN9jpbjJOLMWrrZM^>7gE#Wt0 z{XKViQ~1Iqq>=muYIo+S%rF}5BGMg`=O-il#gN&NZMP=gSgL{H=twQ7IU=#U6Fc-u zO5gKc-U@2+vPnG7U;(CvMIB2hGEAM#p4)bOt0~NYDNJEVSGy<-$I)Uf=SUt3GYH(| zPTdyXbVbU;cB|BT4iQh7xcj9gUJI}0S}k9?(Uc9FMn38YPxoUlNR<8DP=|V@u*@*n zxoM^DManX0^bIiRWxB3zhfa7Jo-@px>2XPa8cGb4mxt0p1Y(fTDSBN%Urs$2w9Bch zO=}YL4B!s^xl7iTdV^@q8b82{xf5u{{0Rn*$rZz77!JfSMf-a}D6RL&qNENq>+BSG z3=e3Zj*(maq^E;e9}=HpTB22jYQ`ysfN!+zWzd6=kYSh}hUp8^M}V(aN}s?Cktp^x zT9qCtW9}5U{*^x##Dw$|jYpWJ0#sWGMh3{}|J#yDmTDfLx~WPkt$t1qR9hS sNsc2=Trm`2&^k!F;9!Geo$L4*=IN|bc!Cu?#VYwSRN*tcAOXhz0K@8bRsaA1 diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/PatientRegistration.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/PatientRegistration.class deleted file mode 100644 index 8e51a9cdaaf327a0565c35047c6e7f724bbd0129..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 358 zcmbV|&rSj{5XQgRKdi1G#FJNV>cJ8c4g~dL;%TEs4fk$ouw-dTDZCda6AwOs4`rMZ z4;(x>O}_T`%_P&AukVjf05|BBNRSrD+Q`u&^yYFSMI-G@Jl1oq0-<$ntO>V-^!#E{ z;D9hvZXuNPS~+XEW*PQr&>sxNG&u1jgV8oT>zP>v&)_T}yK_@b=vKz+hxMY?{#Diu zJ13P>vYAM4BLA9Mcrz=)xcYa;F`;~MYp?XZiEU2*_|;V$8fPeY7cqzrZ-e!aN$eQN dFW*61HWHMqF%6_RVr}o2} diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/ResetPassword.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/ResetPassword.class deleted file mode 100644 index dfe474edf39272fd18ab6859a720606b8001b4b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 340 zcmbV{Jx&8L5QX3DPc|%pMBE@9P+%043P^*LY84Qm^j@z8jy5*(I&dy3Bnl3|p`y&L zD4@lbp67dGY36-zKfeH6W0WI8oFN$?MUOCA$yX_AX`jSxxzZ{SdRNApFeAjrC(8_d z!i92cp`_Q!S<8Q3!f%?KPo|;@PTcE8hdbFcZ_ZbQ79|lJd;v2977nG2=QrKWzO>`k)-zg0ght`b6_i{zzf(PKCjJHG+ zwzkRX{myOf%{hL59RN(w%@HBakhGAZLFjJehZL2xEAd=zwDN?;y|Knm32}e0$j~GV zmD>s>YptBM{O8S|(|9z#6-)2L+`Vf1;2j~Exuqs_3S;%tZd>YlF3XCY>%u8nEo5y1 z|C)(kn|H#f_}`j4LjK}*wbG9!%x?efABIfmA>*jw5MK_cxfR>7g9iemmFmtI@)%Pvu3`eJz@w(SuQyGPR~^S{2HhZe zTE4M-OK%0PZuWwm2J${rv4{%-6HRKhoWM3alzAtm%*da|NTbMLey-vR zd`T|_Zsa?GoywU64hxV`JsA_J?Xrq1C{tV8^xJE0i_v@XF|^gB@QR8mG^U>QOD}ZH zj~_vF;vFv=ubHey^(TRUD+zv_bE!BVoK`7A6DMXOe^LTZBuRBgAQOdZs%|^hMsL4i zd9TbyCoDa;X?Au@&z9$ynF-o9(=u%hy}!&2c9s`b&~{s!rpHnbERS-}RfZ&2*k@`2 zv&q0D0w&yd`$YUpV4c!FI4AQbrt0w4{i*k2)s@D-F6P# zHxLy*n#$N6x8FB6XoDe%i`#bF@ z4t*X^1#TaW5o0hV3{D0kISq`sc^iqQJH?*PHOW=rn&Fz~THqk5ain>notCfQrNEVA zM-DM<;1~rz2wXV*DkB(q5jQ!yM%eOloMs%U+!f`p3n}MYNIBO+$_W%wP7cy>2+r&! zqObDw8>vEC*8YOfmikb%t9?vr*ZL@ErA!|)S}EJdyjIHfvGA5RM{teb9Hq$bb$*{k z5}@M-Pm~o}B>$H~o1~p(Idw4_?xbCra3?LxhC69nj;{hrIl@7pd=faE$>Lh%n&zZh zrebOMO;V{s(GGDD_=DS%mZ{N|*s^Jkp09_%F9E-&f>-fP?6wtB`K-LQbcnCsho6h? z962VBTlkg+$Y*8+58z?q=t(+H@=00kPbjB7C>u)@^wPi|N$_MkaB(QOkPbYH+cZ{2 zBB^;M9k_%$L%{Rtz>By$1iX+2oWVUBJOTqNq-ADYR6B#pQOS*}CaRgJV(ybWN0kp) bb9p?$cg*-2p5uGe@q+(kMQvacTPXbxIxeMi diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/auth/Credentials.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/auth/Credentials.class deleted file mode 100644 index 05bb5fd0d69d5ae7ba78630fe6e51765aca1b0ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1677 zcmbVMT~8BH5IwiF-L@>WR6xNG`~YbeWB~=y1s}v1Ns1aFJm}MHdqWqtd+Y8NGQs_p-R33#mX7rPT<+@zNH|Bb>vn4OpE*e7b>Gj)sE;W;u;wP8M-o5 zN|pX9u*$u_3S&&iIIa^$gZqt5Sr-ge{}@{;<%Et&+$0{w=XR&}?Yjoj>yXLwn-cwE$nD6sR}-&o)kGn-VGC-li1Y3e z7hXyk_<_e;VM%^IjYl~wz|^p)V+qR)Q)jE_wP{hC!VH0;5Qc2En}y*xUaRdM$wOg= zj&HbIcZ5G(k@B$JvU*1mvqXWrUt8jp@N1sk3Zxeg*>vaz#0}AaKlLVwqo3RFh^`Et zFbsDtp4Giqc?ONXAqIVmZm`#Z6TXIL3^QkZT+*Kgi{Z}Yt~7{{3?e#14-V)nsuzT$ zqPppHra`X)Zqc9Hq;0EbiO#(76U>-9fo9B~VCa}k3EYX?K`v7y-wj>qyhj=ZbyBmA zq>?9apX6+U%<3n-B!v2a@D$P#oyt`+PEi29lh})(hb|Gq2)z;07le-j->wusi5UV> z=<9SUJ~GDKDQ^5PemwMv;K>^gF-rxgwo;4~kTUqMC6hGOQp)%N?XreaQAS3rboqz} t4_zfVi4tKYP=-P0FiGKH!?DhFYz*^AQrVBOf>k^vTaGGR!*e2F;x97|eGmWu diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/auth/LoginRequest.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/auth/LoginRequest.class deleted file mode 100644 index 223140473a66556ae6a57ac7201b105c7747c4ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1683 zcmbtU-%k@k5dOBby|x?_Dxjc(KS0_8IY1Gy;DZ<=NkIdI2YuSM8+y>**1Ijn|H%hQ zBqlug2lz)BXM0yUQX~W)F0(T?GvCa&v-9is=}!P_Sj!-W9s_X`2_zW?4)_tb>)hS9 zw?##IRfgoM<2vdkLr=c2mqrR{0~r&&Fd3FBvSC-aN3O0bDypfX+T#4;l3i8O<}FpT zOLE_Fx5cNH@D)QsG`Le|$d%3zca-P2`$dh>Zz8J!QcdprpQ(-A`X3-0I_Ic~G2A99se4}c8MdyUF;exr@&R#P zEPP;?&PVJakKLBS9z#6nDO++}vDIpng}2Mgb?P!#k`-Rx{EcrE;j=QNe{q9N;bdInKLc*P&PlSI+a&3MRH z3QrmO+pn+Kd0bfrgT6inbA#To*M<|mffa_SGd?cq&p?r3{Bl>ih=2?tIz?*-^yTy( zp;JyjcallaYQP=(bC5!X@G(pg zh(=!}srg7*GpD%qulSL`Cxj<&Ji;^;pxa6?Qb0=Y-ljHKV}8diPuH`7UR_J^tt4gTDdX!s802Fs-1VqKJ|} z^@aJ$)CZ=!r$4iMo^J`1HXPRpHwC7ft=$=vF{7ZOViu~vX3yK#d#2B$u50(gVVJho zudH9wtQA6+=4#SxAeO)ZY%Vnpl4Oo@xH)p zj~|(}V-D!^dQP8Fz3n5UQ6t$Os`v=!SV!o^)d}o0PvbCyK%;e>Ga0L);$wWmSba0- zw>^vOw%T zUv6txpb(Qh-*H^~$!Nc8`#WZL5H+>eGY7k-??^k@EQEcB?R4if5wmWe@Aa`A_C4#V z>GKJNwok81&5WjqUhlTRa+=f>*}{AC{Ux6S40?N8LIrN-Mjv}J0+r|9$nV+r9m(U_ zjQTH2yIJrtqFvZ)SY+{ zc%7!pabI~aZ9P8tz;vyF9bD*m-pkR@II%Gk;La~Yd&>@bzB3FxKMiS9+Jf{(((XT| z+-5|7oZc?NC*XiD()WN+S_RBPD9{)J%>!jw;6@$BKZuO*!|YwCq3u8)x;1 zw~S0CJScc1aPH)zys(%wT%~YNQBsQ3a{irgRg+R5%~bbjrm9CX)tK`P1W$iXRFl@P zoJ*V=+Fuab+8ByAzU7w+zdXQ0bWr~f2%bWB diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/auth/TokenDto.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/auth/TokenDto.class deleted file mode 100644 index a8970c18cbea52aa96ab5b8fc8743d53022ec26a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1503 zcmbVLTTc^F5dKc-c9*hPs9eMgUI1;2vRp(-iaZE0*3?Uc@Ssnp+Y`Fb?y0+{B>pEK zB+;1k!5`p{GS2C4Wfdf*54(G2cIKP;zM1pmXZJgRC)mg$g&`AZ8wN5AV~6~hJ5BB% zIB$h3gE~WI)AK#G#V}MT?hnJlu!*dV5!eiCu539j4=B|4g{#^sX{|1=t~+%l9o|t5 zXHOmpe@96ML+K+!zIs}1R|TGbP|j0Vmu%!P#$YKKc^N8&>Hw9>#q$f~ZCu6#6=?9V zu`TO@VPn7o72WWZjcHsVrNoC0Z-xvn21849`?_{Wa+Ztl7$yq|e=JjRpCKJ}o~wGk zc-3juM6kzeP2!TTN|!hHdEjX~cBWOsqt4a`(vkM!rBDr7f5QXb5=sOFHBmT6-rkl} z{%SfwhSB(wV~>9^WOrpJaK$rEGZ^jf%936X61KmKV^5`2p$d3A>L<))P|9Kv_f0&o zu>^vt&_x4~@n}XM>RejMKfpMJXC~g}yr3EOjvoe5T`BOgjpRF~{jv-##aO z9QgEK>C?DFMRfH|`f5^EX|apRf8|G%tlyL@%we7yh$+nxfF{IvV+`Y2ST-`4P$<68 zd)nz}@42&ST7J-0m{@T=H@c%RUa4+RB8SN=@;1)ER#wP4jc#gpoqVbhX=2C~X|s---g)dtMid@7XAb zIm^>sBZ6rgB@twMf!i{Tw#a5}%!#a!So_XIJAS*-E|KW_HajFQ50UK{dT=Qf7dv%;v?;~J0`re;rqM&Uj4*$CkLg*?wfe%_(A%}T1SUr zT96dcvlQ$I>q9M{gf54>OxR$I)n!W6lgU&+HjN4#T70JX6#2|3*bR=(VBm@gvv{Cz z`6yN+tRHgE;*rAQ$*N3J94Or6gdF40usAq5y7_&Py9p?K%=HqV9MlX3yOK=Xl*Z*D z4cV1s{QiRmhw+ITw8uN=3O)LSh#l%?xGvOwhpH{@BU`(=kEzKSNZ-dK7|^Vxa0W&JNdtmu#hkDc6=ZcuPj{U_q0V-P)X>3cSjtCMOgFvWK`yf zLbCG=n=Cvp)UWX=TS?rQD{j;8Xo!9Zp(d%V8AQ8$aZiL<$QW_tCif=+GQ~M3s zlL%!J#1U0C8&?@`>XEQh{{>dUpA;`)<7AXZ!WQ2Zc5Wo>%)7x__>>IBSjM-wZn89` yIn^Pjq#>mp(=y>HC4F2b#eH1%`9N3U3pPv+ckm7FVHIn*PkEl&>v)I;%6|hBZy6l` diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/payment/SubscriptionDto.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/payment/SubscriptionDto.class deleted file mode 100644 index 793749c69c8f92998fe19db9eaa166f8467ec2cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2350 zcmbtUTXWh*6#fgZ*e&9&=Adn0xha8j|W2eo)bKH}fa`S$SwG;xz*lM?>R3gY60!%LN4^h*=g9UWm;4nI{u+|Jm8cXS z>-YqpGUNgo#lX<4G^R?gR!umH6H~6P>o9PGTy?nL*_Um>us5}{-y>PAi6T_=YLLO%}wIqCs>M=t)s>yuAamDjNuO+-A-s)17N)2iA z?lJcqb&rMVpyN=c52t)9YKvzg=*adV_jpeP!Xu+gl?hK64nmz_Ia!26aTxV=vZdo+ zB)niPOqaU5DF>b{9y=<*rT+}bZ8aDS<(C6DaC+j{@f}Kh*L5ZJIn5A-xY2N=<-g@8 zC&IH%?^%-j79T5EJ<)c!)wDaJ$7>9Qwm6?ah8w$wb>q|#Zw>jg!4372ePQ^CDWzT- z@j!7;%D@ji-j539&u6inCl1-s@KDG1*k!mro@6hI*^1JSAq=yvkrjsb$qG8|seB`> zNbxCm+g;(WHKcqq=+`dotr&Rpt}hPaS?3bQ6LZl|sWahHICr z(wjwf!(Af66!AiaD3kb_P9?>u;Z7tP?nIp7PHaj0EEs8jPN*;F_ABjK+70thFmrtf z&DWW7nQRFHRRS4K7S3?Bi%(=i!KA&fic7RjyGkTyuAkwv|HH3DI_IUx;Z7L0N-@Le0*a<^o{~j3l~Tt1 z1KMQ?WnznrDm1D`5^P`SeeI9jt)a&2TeTg77Xf!8#IKU%3#uGe6o&MzM&ktzeIm-V6 D){F;D diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/payment/SubscriptionResponse.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/payment/SubscriptionResponse.class deleted file mode 100644 index 72bbd2313eb1191514f10cd9158e18107463f25b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1772 zcmb_cTTc^F5dKbSyKPx0R6qe0FCguLETDKR_y9z0Qq)jFygY5UCv>6RQ+H2E_)k7a zA~E5?AK;HN&gm|)q)15QVRO#R&dfL8%$fQ1`}ik-6|7_sLyv~Ijs%hn1H0C~Wi~8# z$K2$$@M;XnWyf{oONO3&VLOc!(i$>4dZ9C{+M;RNmPe+p%Wc_`QE730amlPnVYaM; zCU>P-X;*#Qb6V07?k4wJ!u2^rzaLST2r*Ya&7vYb$K5F^+)XtzY2}T?4CjGfZ(uUf4bY_hoV8+Z5G-LJ%eTSrq;dW>a zGMS+JoxqgNyCjiQCp7EaRPY$?(LEg_wfaf>h*0knoz+5SxF&c5E)N8Jgn~S)jfN^_S}E|J@^~ISJICWwl}z>R*eZ};7hroJ!xYOJ!S z?zQf9d!Pb48q#E7-x>@;8AY2a5Gam3U#mCJcV05l!Y6Eo{AJ>fB7yrSzcCZ_gS{v0 zn$!77AZTawX(!&ErnAdVCbg;d_!90<_V;A?$lV*UTJ>JgcSpN!=;?E+EU2N!yl{IceIk|~wo`^~=F^p`;8VK52%@~)?|s2)A{ ztNM+zd54s$-3|g3sn8uK5k=J!ZdULm90ThnX-T%7KjK9=@s+nP?Zi(C5m?weyhY$e zHdN1l7Ce=9Vz}k{gOQ9b_JZK)Wb7Q<(iHgU`B-kss2_S`6@*#H#>kDLtRj6VKWDDZ z@%{=^(rT6Qt-ytN3OM@Pbu_V^n9L3d?8Kz43K(3ATs5v`t_D|Az})7<3FCldWPt6u zbX2jN7!E67;GV#_<4xJBq+D3z7?~sgN*q3%c-+E#&U74b0H49TLTg~DW4Xat2qWOZBbFla=@fKhntNd;y zH{dd^@Wi{~i1hzf9FvZ-rH3T#^-jlCh<7@&V!YF_m2j2&W{GeRs8a%s!QlxGOkjya z(xzh__!^~lVOR%vANYgY*CR6%lQgm=j=yU;@C(52Gr?Q*nv`vwQm0k6Ru6FTP54G) z^Rh5ybnz(z&}kM058&a{%an9#I;EoZCye6~DyBD*%emmi*}(N&aBVj5N-lVLHt?BT zaAP*`*<5gQ7H|QdvA%P-j?XDud~peP4w<9*k+3G=Ov0EO)Hc|eFUYwvzQHZr#s)TV R2Y2xu|7k{T;d}JZ{0{*vz#9Mn diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/shared/ProfessionalInfoDto.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/dto/shared/ProfessionalInfoDto.class deleted file mode 100644 index 5e3a0f6186f355a57a1f01fe228310232915d992..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1908 zcmb_cTTc@~6#fQUw$!aHwJ3N2@3srNfa0y-1uwBly+lk+)R*aYpbPCz-Q5!TPd;cO zG2y`<;Eyt%>26`MwK0*0-7|CMob#P;&YAP`*X|Dh%UDTb2*Vm;I^q~%$ZYT}ZZ^2H zZoU?lbZZPFOSWVC_ZWurg~})r7}b!}aRfTU14}kdi@Rj%IKuK^eb;u@iwg9pjrgqPV+zw$5%ICj8y>^kLkJz=@v^!>H7pk1G34_ReOQKG zO{q{}hy^s$W!njh_=Z;-L_AxT7H?FzYpZ>zjQMq&GGF=s3Q)5?7k*vVUU8QY*E?{By5_;yp60buCDFk0<+%rZ5A;XWXeZgmEBsKGrist9ZTzBfn|{lGH_uPuSw490<1U5q~3 z;vWw+&7jejqAx@5yF`P;?W7|-4K#Kx?cdh`{u&-IObs@rLIszFc^Vu;^jwE&cF-WB zRaT9cj-+WqQUjxjc$Tcr(e4Au7|ERR9n6^7fo9C^AZ47{LFOyzhj5<$BjlrEUZB4b z*yAED(T+SjIO;stK~m^BHMz+CGObg?WB@u+sQ_a%w60J%1wKzwIcml(x!?;ed-3$Z zB*Yt|DU|QyJ^_6GC-*57Lh4H-Rk8_VW*4Xb=6^h}*`u4F3z(q<6ty_D0|`b1yOd1Y zDN1qU8?-@$;$cLZ?|q$9|K^E7ey?QHc8rL+{Z&q{szMmvO@p> diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/exception/AppException.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/exception/AppException.class deleted file mode 100644 index 866c5800a3f8e2ba227790affa0caf38391eb3c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 349 zcmbV|&q@O^5XQgRKV4m`t-e8Twg)2xLDUvXv8P23O7GbiG-aDCYbw5$r-BC`z=ski zMWN?T;7fkr3`}Oee?GqejL^*yA|zFcVK39TDrjUN-@)BZfe z9^q2CrBJfb%2~@bwcn=U`S3#Mccp7@oE4M0zWoP6GIdXy&?$`7cdKQon@3qz?3@%% z$!abe6Zp?e{EK-dTot=Eye8xiZq+C~Goj1z_HWL@tmq-*6@(x@yad*LCb3~49jrkg ZY(&UeLmG&2z}nutQ@#(G2uG|DI=`Y?RjU91 diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/exception/EmailSendException.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/exception/EmailSendException.class deleted file mode 100644 index c07d513e76cf593e37009fbcf2af4e071f5bd4a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 367 zcmbu5&q@O^5XQgRKV4m`EnYl(Qx8T8p41{pJuP~u^qx%zP1#M#nu@RGN$}tU_)y}c zC%z?b~K8JNs`e7?N{xWKSPg0w)^M~)t0xX@2pHQL>)+j_w!5PIj<*)S%g2Zyr) z8-%g(tune}@TajcFjomS}(d&gI?~BJ7AJ7<>Um|6H&D diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/exception/ResourceNotFoundException.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/exception/ResourceNotFoundException.class deleted file mode 100644 index 92e3edae85a9ca17ebee27a68d05119528f287b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 388 zcmb`D%}N6?6ot>7pN@{z79XJCPF)x&xbkNst(&2OO81#GXv#Dx(~S69T?t+I06vs> zGbp%m*91=ToqOS4&ga+W1Hd)Xvk}gto2>rQym7cL1sM6_Ono)#69-m@{-l-)$KS>MZ(;K2v*p~OiM z>e&f=$?uzi$;{XH$0vXpCM6Q21+p=6j0lsJe3hb=_F3FDE3E=ybY-jw*M#)ΞE` zaIRb@l=NCTYq{nn{H5vX^h|W_Nw-1{VJYsl?@ZqtX9?L2`-G}CR^JX?qy3|7T6Xqp zr)0a3-bDU2v#>Nh;iCR$ze_^-;0CYs+{7l;<|`+0P8^`%@naAlUI6PhlUOs5AHRd% Z*ho;a#x#&(hjqMu=X~!n5%yRUR6nIEQ}_S? diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/gamification/Achievement.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/gamification/Achievement.class deleted file mode 100644 index 7380820fd3f7c773e2219ab5ea9f6f37eb844a88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 364 zcmbu5Jx&8L5QX3DPZpLyR@{INC@_jd#g76=R4XVz>Al7z6Ybh)adIyzBnl3|p%CMU zB0aYBJm2$Z#-8t=&o2O%s7fSA3uHs&7!az3UTM{8H&YMI!kR!BT(IMCMMw{irUkYL z6XQE&bZ?D!PI6wuCXG+WXR7nhwpBCT@e3Or*gJJ?=4@BCvrdX;w~{~@)$Hv3vTJPr zq?=Zp{n{JdPIb?b|I94R`HgT>|F`iup?vhq-q<^i-Kvd8oy4!>0EJ8>E|M)X5#1J2 gYX7pN@{z7I!Y)sS6_oS86wcMI2O6y3fQorpzRgroNXe!G#atLrHIn zi!NN6z)8MyZ^-3*|9pM{xWKSPg0w)^M~)t0IFoNuw9+=>uAXTX2)%P-O&Al>gTrZo z4Z^i@olw$i<*enN*RW2blhLW@+>>raBRlhKlnlmMForFPUtiD}z zwf2v)ZrRzboRaNSdK3BA%)*P|M>o~KO>jmiAKbz#eP!aedw-3298~ff jZZe4#1NqSh=$(xOC2K4LDYjVqt9#DpHWOioHNjvB?Ym}u diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/gamification/XPEvent.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/gamification/XPEvent.class deleted file mode 100644 index 37dd9b7b9e017e74ad9b8980b492e03db353de31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 352 zcmbu5Jx&8L5QX3DPZpMiMBIQ5C@_jdMM#67S^*L$qV*aRCfc>p;^babNE94^Lm|dd z3VLkmdA>K+j6L5!pI-njFe;HCEs#~nF(8ZP`e*rM z;)8#Hf0Xgtv(Y}Bk(ZhMc4l_wo0RG?yooLMSSSy264ewMB;^~gOP zoU>;HmG_iZW?xWoYr9vW89{qq7`UE{*$cIXPh_Ui?vveTZV(RHcOy9tSR1#K4iYa` zkx`)*R8T`CYX8mD4)y4{pyfBIHY(trO4PvT1Fb_NZAc<$3GN?dY@{1;oT$XG_SkI= z5w@9Ydc~S!X?WO^e#&s0NsPs#@qZ$auRD&ih}O%)lWwc~PS65^Rm9UgS|Bk$MWO?b zjK*qiJhb&jDXys!<&m z4Vs^r>(Ezmv(Tq&wAiQXI4{9U(908jgH|Tqi@06I`6kmXTBCIwHxjxHg)veY@(v>1 s45N^puOz-?Pi;b4GohBdnY8B;-J|=^1=`Bwc2;o>cnA0a{PFt3U#?XmJ^%m! diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/token/PasswordResetToken.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/token/PasswordResetToken.class deleted file mode 100644 index c82413da5b9a678f5b963c981f751a811f177cb7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 371 zcmbu5%}N6?6ot>7pN@{z7B{ZlsS6_oSN*l%#iJ(-&v|dVm z69t!E>@}s9F^K=}Tj_5AH;-x;XdOK4+Vxm@#og;F6E}zKJJrJ2Av6x_F1tNsq{N-L ziaN0`Unp*&lT3D>$0=y#O()c)L%J^Xp6QJUBAdz%Cj^xAf2}q5%iFD zpyDEDu;!%Y-EWldBl4RhTEbbD%?^uQRf5S!;JyfY1XWfbV8Wu6X)?{UYGo?fnI_Xr z>tNa#!o)M`F-;q$vXyBj&9rG{+O;#yq?xwBv^|80?@N!VV3_u-Omk_b9V^pgJJVd6 zsRX9-5GKC6J*EZ2v~OivOf&6SnOr;5Vw%YX)4>oXen&l~Wy5q}Wm-uyRjf>hcBYjy d(=nJ%hA{D)?lG-_DMJDT}O diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/user/Caregiver.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/user/Caregiver.class deleted file mode 100644 index ed84385d65add9e9c5e4894691e4065be714a3ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2958 zcmbuAYi|=r6o%h%9B&c~Ar}HE0g@J|6E2$ul7=P(jN^c*b15;Fs(z@}-icXi??$_8 zr2MN^YE`Kp`UCo-s=l*cY-bs(vhar)pP6&sdFFD?{`2o&e*<`m$^=pvF_6w;6d8fb z@2wA(*|gk-d02Zd>!Cnq+i{(6M_?qkTph<4#tlqlaRFI@jk?z|>y|I;p6k;4H0-z5 zR@c_emS@YR*$$*{7U^W$roglEQ0PZ}!6o;^ zi%=J`xPhAjhGpA6rFkC1spdOzk=SAuOZZYCt&U3I(Qry{8MGxZ|EBGRPD@suz^OH5 z-gUju3b|#nUE?ACV)>zEo=QJ(f>643X_jn;-PTUMl&s8K9qlnUO@t{(w{3vi!yGF59leR4tgx=xMl@*T-9gMma)GeFuVM(i1=uF2;kL1f` zfkmz7YtL~DUbEeDDVuhqH)g1F|5-iDbb(J+V6yDE@}NyXzhdz))id23~pU)@wt`>M5_rmZf0Hn&XP^9e|Y(S{651e-oL9c2tBgU;kkt}is2Tdp$2 z_GS3avyUvFn+$pHOa!X!^>XX{nsIi~i?V%QSNhdsC#8RZV&ypdXfdc~WjMF%)mT9| z9Q~Q(NPi{?zczru zmY)v@d_`-D)@}B8Xy29i8aWQ|6TqRAs_#+Hk{*u znv9t8TBfNa)2f!~g`R0D$@G{^>jRk7bL%qAL`(%O(`=GyQ_HlgXPQkiZIkKg04DYH zyG(NtQxSWbyStiXdah-9iC4P2yP9O$B~x(#Qzs4MyPJ=gN_eedx}IcurDggaWgXM? zBvYA8`vaIdWgup{5i#xKK*Mw^$#kS;I>eEV=~j}dLZ-I^m^x)BW?CXs3O_{co2c}u I+Hws12Z4K4KL7v# diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/user/Patient.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/user/Patient.class deleted file mode 100644 index 37e2d2158306075cd50f14098b0d6a279869ec73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2447 zcmbW2ZBN@)6vzKJgcwpY%0nrv&_PGHKp)H~FghMu2BA%r(Q3^iRee!8_APan*vNJ! z@vSy#)1x z`zPCK+y1Hby!Bo>u|R3p^SyXqV5qXv9K{Gm4U|n>fhn-z1Rcw5q08+t!&3BQJ`j??|iW(rsCL z^hEkHv}5Tu?RHnvY$S@6etP~t9?aJ`KTAAnN7a)?t?^3WrZz{l(~_<$6@=d4xi1?5 z7aACj9XYnkOIy;$I!uqV)I$gLy1=5=@>$^dhe5mB@fo)0Cxu|B@BT*BPVtacB`{w1 ze0kC(VA!zvF;%Suj@@qBp{Mk0vlzeg2EX~NTA6YG*!d&%nB<>;A_ihqT{>RJ6$qf<{S z@$4vQv4gptJe0YU6|}%M6Ms}I2$UJ{BF@9g$AwY&27y#x2y6~YleLpUlr+NDfE-Bx z1yOz(bVElTd#ZrU?Aq_Eix!yI7H5?m9G~xCi4&&4Ia=b%c?f~es7+8?q+FtYOX71Z zbAwZjTLm?;63Ho*NKU8Btweb{+2fGO_U7pE1)sls4Ixp%3RbDziEtNVeBj9DdXG*P zrJx*M{TKKHyxGFpvU=3eU3asM+*AJb4h4C{F3+Oq|YMnQ*Nh{^NPnmp! z)jPkz=%rRa7GRzU7m^4wsa2LhA7(o@tj%dwrPFR?6;fE@3*vk%s9; np6RicsfuGA(~UgS5t*uen9^3xm=?%Xz>{Qsnv4q7pEckg)9~M$ diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/user/ProfessionalInfo.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/model/user/ProfessionalInfo.class deleted file mode 100644 index 59f05b4d8075e41ecf23048397b559e7c41b8ac2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1205 zcmbtTT~8B16g|@qD!8R(W+fM37d->|VZovPb7>!IYJ=Jc9g9T-)p|IIes%DlQHB%WgNKKB=n84M^SrU#~;T06BCMtytYOcJqAzrp+%|ZN zk21D0pe2b;taByUqs#5K)4Ym1v@gRqm}TT!;;)P|InDHxxd7oFCF4A+fLSo-w|+u= zPhks;ZVXIgkh{3ygTd)L-Aw$fPg?#|B4Gqdw{|NQ&c-vGYGw=>9Ls)T$61r!zLkKAL| z@m46(X zzXV}dbT3qJ5$`JG!@$>cta-X;k?tg+7oRx$R9RQJyq}oZ>+7}`d56BYZjNQ`YNJCp*4?)2CxSj5#X|Q^PSK#f*2%zXGn7=^9ulB%EM6*Hn<$$3 zR6JuP4A$-y+anHLv^1u{29WR@bYUsEEL`g*T=W>*ESEQ6)z9ZURX4uT}? z=*OPCy)1)dMapkdXVlrq@d*=lg;D>1-`YYUH*c6KK3t|#gc}?qId)8*E2kSf=Mt#} zQkS{ENBWA!RlLs&j;V|-`!(Kt@Eq51<2gR$nbRrb&RVW|$>4;_80WZu#Q#l=kMRj+ zcr4&k>Q-s$M@s>PE6D z20;OLX+4iso)_{I1%*I1$N^A)+fNG6_~-! zS=dM8VHZYWpAfb&0X8k&40g%FZsD78-ku+YZ4&nB1lVm`_!;arVRP8AYu)zP9O=iT LcNv@~5gPvi#mvE> diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/repository/AchievementRepository.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/repository/AchievementRepository.class deleted file mode 100644 index ce18eb2cf6816be44faeb606e234b86fa6310779..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 378 zcmbu5PfG(a5XIl@pRTUe7LVS%sRttkPijFco)!g_-m@{-l-;DHX~B=>N$}tY@Iy%_ zMZ|+Q6L^{5o4^e3^XvTszy4T{i$3^(MbC&?&_&lfzUZO)`TG;JvbZ} z*dSaf*9s-QR?b@fGYh|Ia56X*UeDaz1m|DGrJ5MM)UCGR{>LR`Bln~Uy~nH~f NFoI&!Phh;6lW*bwfvo@l diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/repository/PatientRepository.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/repository/PatientRepository.class deleted file mode 100644 index 4e8505195765ca04f3ce5f73f624be80d2a3c988..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 389 zcmbV|O-{o=423=AC$wx>aSV(qb(PX(7bp^lp1_@&(WJ?EITAlst!Y_jM`Y z(7;(uJHm#7!<%)TAq5;8n29>n3gcA{yq7EP~N(=S9)yXxP!meJB=ye5Cw-55Ao%AShtzP ghJpO#74*VJf|511ffPHe-A$eIyURq_V@=Te0!aj6LjV8( diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/repository/SubscriptionRepository.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/repository/SubscriptionRepository.class deleted file mode 100644 index 9e4b72b3a124e92a4ba48aff7b60f128c7aa0455..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 643 zcmbu7O-{ow5QWEul9u1D;R0oYQKhZ|EI>j61r?DKOcOVCNNme-KsgFmVZi}76k?J{ zB&b%&aE)IkWSl*hvl+} ztTaw)_3$&zg@vQA5kI^Ad4fd694R(SI z4p&memsRMzUx%7J+3f_ZtaG*t%OV4R*R{(4h)PXI!*npgGa2vX_ GOFLg?LdLxS diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/security/JwtAuthenticationFilter.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/security/JwtAuthenticationFilter.class deleted file mode 100644 index 0a7bdce508a431378599692e5d94804c892f8794..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3325 zcmb_eYgZFT7=9)MHYAH;6l(+(Z7X-zLTM}U3TVM{6~tn-?PN2Mg=IJH&H`RO^&*{g0=x^%j={uVxB?MB`9uAY4ci-DQ?`7UU|NiZF05|Y5i5TiM#C6o8fnmV) zmJPq?S#Z6aB4;dFwiKQ*?U-Ulcn`$0K=|@rNMvZZZaJ2`$xzqXwa|zJ z8Z{(!q@Xhlo9>!na!;79;|Nn0<=#{O)&4=l7iQpD^0{$;L*5BwK{(PfxwKqo*Ff{i zhMad-g)`&1>sDTP3^yj;O>u>IRylc0$8j_>qyt}gqe61a_UDARZkdAIo;V1JvZto& z2}8-{OLE4;DjnGb(hN=NLat}M2=9}^DYR%ft>Yt{Vdz-ltK5^E5b)M*A&r8RMPrN> zU}EU}!nW#ABu8l8%q@2|45kIX|?mxW^B$pTuQ!Xz0|@rA%f&AY26F zI=8Jnmm&zynBCLyHO5pC z3R!liq6j0nx)zeSk8dyWrgmIhcGymm6y1oVg7zOb ztEDlU!VG3LJkT+R`6K1rA-*x3AS5b7$uQVi8}-n)N}+7mLJA9bq~Ti~i+Ie?x3Er%hIcX!{M(duld?8+*eE3>8i-7mV1FUUB+CtQ=;3*58RIU>eo!J;Z3eMiSDkuSlRrI{eeld|SXLGNIG zhO4Q9CJ9B_#d@6bllr&K;8$aQ$dgzNel=qhg{m;^iFg>IghEiYQ-#O=myvFeQs2vE znSK9|kghnVPR=k}TZv-&cM3X6yC>E4QcXzo=6j{-j6rqj$_%XrDIDZTI^l^DUJTtw z5R0%SaF)U$Fsh$(Rib;rrb)374KYn~lAEiOycp$N^6sW7is}*ZHLNqV@Ao{atumX2 zO@@FOS3L%Q`=*ts2 zEvhk@evfx=gLU`3f!5vo22DSQj8ADzBTnlnT3gVF(>O*G-uFalFNxs?{75HK5Sn4( zj+3DnaQ`bbuLi-Ehw)Pw0R&}) z6qKM;!p3QBPE(VbnNUwnOzUx1Lg^vHG~7oe1e;sN!YD(zjIe?VC@Z$dT-z`c63P;~ zJFw{Lj)YKi%Y}h39>4|}8x=f=Dhc(5rA|ARIj>K+){Jhdjvi0jhP$Yq&=)CFIW?=r zV^bQRj#{>ay5m}AqF^hwNr=R)Sv9WNdfYNi zVo$k+Pu-o}`}5X@=iO0Z|Fmt*8Iw9K1!wd{37b4y`F+b~Y87lporDMGw4^afyip^W zGzeH4ma&8HJ>t@?kyMXpt{z4`g5qnJg57vXf=pUQjal8JaagQ03d)-VOT&WKX0*s? zRj?0j5;m`9iEwF4o=6)>5A2TSmXRWR2D0ms3Uok~(W#&d-4bf=y;VZk(cM_u4|ZpB z)_*SpvLTJEX{ijobiuZgbh!@)WIU|k5ge4Dt|J&{C{8V=CivM**Ya8DV-OARk8)ft{=`L+~E*kT}^hzv%su88iTg}9y) zX(eNt$&{~?VJO71xqunr(<@ z|7FOX5@*5Tk#!(geF70pDT|dyn=q2+!S8@PY@@n6Wlb`J*(Y7yror83(x%H(Wz294 zqRB1wo2KP@?T4FsM=U#`Iw^5JUlOwOmOZ0R=$h%Mmsr1+y{KwA0d-7pOfV%L*`#RC zmE|7NF+J~gWHB-t4XYNGPsW2ieVyWuTPo-dPebpdl`vj}?Da4vj)@g1lB1?9&8Cw* zKDySekr`@Q^W-;6dcNZ`AiC(|%;8GUxx6dYMOs+*N++^jjLCuUx%>G zhPE=JRwPnu(L{2Xin`@4rgU|X2ZCj?{Y0>fSF-@Gm;qFY%Nb`?;sV971mplOB8Zpx zbdh6-V|D8tNUi&BLtaAVEo^FSxP_WuxDdcKeph*$#P=p_plaUx@G{>wP@KYe1=l%> z*{gVs2E%sSMHFz4HNovuMG|922w69KEd8TRpM06TecXut^9grmJ#FgZt~xZyh*<=Xqy zTE^SQ2l$YL)S?F;aTZ#3{)VRj^zUPQ;`?`)Ezc>nQmEa-u_db@XT8z4F47@q@mpN=Ejn~d zu+eoNi&Z^UWf{x`X}v7kwuI;N*2OO=DnJ{$b4-Zm<<#u*5wtOqy<{Srnm(2QzN2se N-{S|4!jT_={{WvHtg-+A diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/security/Role.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/security/Role.class deleted file mode 100644 index 7402bdd762b53ff7ab2b4914afd08e7230912546..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1277 zcmb7DZBG+H5Pr6I*K5zK)QTVozF?IWgrl|ik|Lq0)zp+iC^jK}T8^_Ex!#%UmBerU zBrS;$jfT(uDC6w4#uD{IFWH-&*_mf%o}K;i^UHSt&+t@5guyVqwqbJLGCkL|%-~%x ztmRkot47B%yS^Q~H+DS7ii1Jcp&-hjZkKkemHIA2t~Me}QQ*XM#05^gT-vF;tTrn< z!g@)EF04xF`DV2)ESGhRAxZht%~I`krNNMYGjc)=DO^>N)G>~0gl;;XYcY&xvb7_A z%ngUTEn{o{h&U)}n8bAzQ#z(`gYy4zsJPuW6>`j-uC?`+VKp-pU3PeTkjr zlABg==pAfxpL_z#rzYj#&?cd!Eut{pk4^Ly&p)tT?$9JP?3T-eu1~m9CflSLTQi*j z!R4Vu;u_XQZe+bk`5Ru>H?0@8;3qDg#IhKVjU3&~Bt0y7V@kXZfFwLY0k3-phMXA=2OV-bDAR#L6V`|g^-2xRO(XLM`H{+Q^kTPxtcgCD0uzp^TaFz_NBKUb8&T_Q z?CjVDE0j(gxY4S0HjSb==Lw24YN3S57&Fp@6Ck?UDAjmTBL?GokG}v_4lE8w2qg2}-kGiT#&(g_AQe|LBU`z#zR@l&6t_5~oK2pK*W=0(q z$V8!gy9D7Qa|3GHi=b=s=XOk`()wA4pYc^-QM ziC-A07`Q5MZTr+}9=6g-!*^Rn(<=6>Q;!{))Lbv7xh>sThI|zb)%4FSinwUthQO8A z_tM^bFK!U}lSqj+Fj2di2~a)7F{m zBnA6vA?gFU{`lV~}+k$l2**t6>kReQK|Q^RKkZnDl_e-m30O*<~DIB?{&t8;gF zYs}B+3KJNZjs3{2%ZeMiOznhEa@v~z3^~7bNX(8@(P~3l<@9XS>6=t43ZuYoqdqGC^%+8di z9tZ*R2Jc`&(c>X1Ljx-UCpz}GRiX_%7C8E9QMyT4fn&rp!*lF9pPar0^3t=5-`)6( z>o+-@=9uA_9bJPMJ@pHWpE=8*#P=S`f>*RI>h8u{xW(ClR2sK2!x{Aws{(iU-A(p# z5cnLkRMqBxfRPv(AAJJj)NgnrgJ-mar#Scrj@){V!THlqag5K2=g8~t(Wf}`4CjG0 zeDo_mS;Li|XeSLQ$54QS4B`;)Ap<-y4<$hjr9L)-8b=v(jN<@K;w~!GzDK<-t`ye; zEd%{oO*Ss-K#wOTxS!`J&gZ7qa80`v2@X9Rwc9Ry$uWcb_==-m{hH%9d~#fUKnpc2 TVHqC(1cmzuJKCGOxNYlcTU=2F-FEaXoL9h7FhdCqGKRE deh0m=5g})7(?E<})L5Lh6bSwNByBpPB&VlY^bVRw>D*z7Djn?S5> ztv$50rM1;sZEdx;+C!}@jkId-Vy(UJi?;N>@5>(lH#0jKl4y2+!jGLdZ{GL4@B4qp zd*8>C|9$i^09!?C7$qnTAs`V%nLyRBHl{_BT52$Q>Aqn-o)swDVx)}hHi6RGy1ojO zqauW`L?xua`nWj~jcXY_Zl+RPozD8-)~;T=K5FTiF(a-=n@6)lJD##yjAo5w^wM!G?dvkNNiL#V>~#{5=J97OEjs;N@|$*ol0!M))2Ny zT!`%gTb_>t(v?TFY&FceFU5^yr6m#>-C{0_73`E26P=!jHanC| zBJ|2~yj%8`7LsdxM}oU0Pm`Mk*3E7iAE_I#VcdjL1Z-&gd_V4xc(Ynu znlSe%u6IeiMXf6%*B#E(-4buZ+j$w|ln3PILV)z?l!rqZ`zG8g@eaJx@f&rmWb!DI zpQ4J@^;P2CcuxrLm3SZSD=01cZZTeB%wKT@0+u$$lV!O-I^DeCk4twdj0aRwem`%$ zvm1Cadrn?VCo{4N0%Go#Yq0PJG0At9$WwoaU zc&I+4T!%8MjCt$_ZuBbmR@f5zm%O_qdU~-F7@**iEraOjtYLP1X_xW96z=44MvgL;BNJEZzX=9;^~J4 zna>wWo;s7oRtP^THu1ShB8(FXl9Tvp2tSkfxw2`2RnWAw%_=Bqr1Uk4=PxCGrQARr z&mGR>N&H6Qw`v1>*;=*i_Y!~bFXQA%{7K@^YFQx8l2EPstHj^bDs}X=+H)uI4~c); zye!A_PT=1X&*XWz?syXamH3}pR(hS0CMzMNC{c@4&fTk~PKtmOL7P*0$V}0PPl%8d z<#|qBYEp=;g;?Haq$ve_0SC2@cv`ZI6-PgVhs2&=-Ce-`-X(47-JMQPTeh--V@N4q za8gXKwoern)g5MBOZI6QL%q9`f$WgMTgk?Pww=~>i7{vG*0V!qqDxaP23}RkESOXU zyFj`SOL_;0zsiplaxL*F^Fi`eOZMSz`~dK?1P)BozW2A zzgtVY=+4Vu;9WlxXe|m8&pjuPRNKZ>bCAib02w_sXJSQdHQp|n8zYVV+1k zovxx;xZ==c^Ya*au_Vw|6iOapw_Pqd#h{H-Agtp8M(q*d!7W10)?A;Plg?r4=OkCe-s4PJD%2~Jx>3Y+f=ofW>(emX(y#v z^UB1UuvjY^Lt>p2>%|6vrIUghsWCPfqaC(3q|gSmxa#P#m}DZFe0$1&>vf5o4R&d% zL{hhwb(rS<(ew@l)>tV(Q}+WB;E?F6fd z^`VGKQ77uFL_Kee?QEcDT3Ikzx)l-^3YUwH)c z>mz|9SXdtk9zo47d>NuY;QbLR5+QuJHF$ZN;Fo{t<*$ zKtBc4_=?c8|}fH za=0@RSee6HBLQM?4_VHM1SasV66{e%caYd-+hG+g5v8fu65&Ryi6e zuu&`pUz8&dz=#dt5>evEWlbrST;2v;wGx*Q|uW3z4#J5zgc z7+2yB_H*ySRk#mV<3YAkbGVk}^FG$3abI*E_Y85IhB((Y#66xN4tRzb5=+H-jLyS| ziDlw^+CVw>RFjmbzL;YYJEy3yY&wP$MB~S{%l;&XUntl8bq>E%&p+ny7xnym4*ycm z|K!-fT#*x{IZ@W2LeR-JOBq7IAb*FLHw@;9VP=l~sAX4lH8tOalrJ2cJe``va&ZB5 zD(8Rc)Y7F&^8T|Ns0ynj%`j96aH$=~K-fzQYI9-7ze<&0><7dOK1)P{Sji`6#VRg| bax6Js%^2AzHj6D{D`&!-*(SD&7DWCJ{Exta diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/CaregiverService.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/CaregiverService.class deleted file mode 100644 index 8bf5eadbbad2d58b89e9086f60cbbe3d266c7746..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 357 zcmbV|&q@O^5XQgRKV4m`ErJi=O+6SXc+u8N!PBCLO7Gb?)Rb)^>2}}Cli z^&lS2z?b=bGcd_~e7?N{m|$2UL0TZ|BS()gTqeZz z${XFxb+D0t%&dK~Yr?qt|Hx}XIrE!f_}0cbM}OaR9{-GE6taqVNJ5q&x+|o%4CH68 bpcgR`l%lZ=q}UVfZ}VL4eIdeuXoA5PSW#HQ diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/EmailService.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/EmailService.class deleted file mode 100644 index 0572ce87a7ae6cc22400d2eb54f138561025da42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 345 zcmbV|&rSj{5XQgRKdi0@=o@%b4%U!pOi(TaP8&U7xOYo~CA+OD3-9I0#DfpuLlLLw zfwR-(Yk%KNGM)L}eSQIWL@!5#I78A#iWZ@_l3OV%X_w-;Txr!1S`WsWW<-cDujUz! z2zScWLP@WcvzBY#nnN1g3~t3*`>j!0OloPW*)I^1v0G?Dw=h;eZR%3{7g<*9oEJ{X zYA(GA{AVW3t63B7i~qG767rebc%`Q%^f){G%XOF&7sz=05X6TUz0qNDE|r*)^lqjp?A)XWB23%wS diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/PatientService.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/PatientService.class deleted file mode 100644 index 041ad2ea6ece888e6c28b9fc493aeaaa53b68304..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 351 zcmbV|%}PTt5QWd&pI*IITl5LssS6_oLHyeYt}ZH6y3dV+rraiyrhPA0f(swOhY}~% zg>Id|Nxm}!lbMgtw|4*+7}m&;S19@@(IX6}dZ|^X-J`l~r)(mjcW#}HmxO$8e^OzM zaAN#S86DVo=cML2{-)8<=vd7;EUjU6r=w*T$KOaOuKWWN290%mvzWCU?seOVv)y>3 zyNM1q@sC-=Cp#ycHvesSMySVr5e#43)Me-Ie-6{M*hM8vNI`OB4WjEpYQ;c#@Cten ZBSS5k(m;+4(f;aP%DpK>*b>b!_yUG)R&xLV diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/ProfessionalInfoDto.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/ProfessionalInfoDto.class deleted file mode 100644 index c3a40081c8cf474a11105fff3a8f2411b5347169..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1893 zcmbtUZBG+H5PlX4SL)T4S`bw54YWOw1AMn23TSLnl?dU3`eiv5I4HMu?@IJP`9Tti z2_O6c{wU*Y?+T~IhJ+7yvpYMp&pb1;^XvEFPXM!+PoV>y1`;N^(9MwD=6l?(a&Oaq zCmgBP8MRO=Q_e( zQgTE1zAHUmEqfbsF^~-DDjj*gcvIh96N({MImTZOlJssxwy)Z z2nl8?t{2sAg|AhKa;_pBUR~wN)%!@92)0}bd+v;hr|x_$f-Ske#1-Eaflzd5zHmZ4 z8ogR(=!-LoF=fkpjnphORqug2`RorEv%6&hshzkCp^^!RHU)>+2Er~}A)?Y8V6o06te;#Ls z!JscqUzXl5@ zb-I%bKu0KZMsf_Pm$D)hIL3T_(scCJTo+j@Oo+Z z6i#p-0KT5eeHg`v`V2`OY|!Nlb!H8g!l1)2JsmuBf zqaC5H=ti39CpxE3=bSsrMjScYE*DMY1jZ@Q4&1>6$vl-SY?x-d=w`9$%MzhUYMCS+ T;xV3}gc;JNNIQ!;JjdW4_0Fw% diff --git a/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/StripeService.class b/careconnect2025/backend/billing/target/classes/com/careconnectpt/careconnect2025/service/StripeService.class deleted file mode 100644 index 4c9b8a6570ff63eae9cd556264f4bc7b87e42e46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2490 zcmb_eTUQ%Z7~O|UC!_<^6hT^TX{jY#%Crc@HU&irl^QM*O0D<9)6W5MS)XPx$;V&DJ-pp0yEiHBLu4Ghfd@NdsdE0HgO`y_m->M8O^HAL_01B zG?#cIktbdc`3bdZ5EDodN)@EzCeeX64Ro4Fp^J=PO*kLbNE66of&u2XQLBrfA^1Mir)f~x{;N3vPp+Ym5zrCU;q>jD?kk87dy zappX(;ktod6F2a#Kx?dBiX7LPmqmdFpL);IH3Z7u&iuEX%Jql%nI!s`97Ow6E5`)tm{V|+q9o6m@dH}o2AA=3`bn^@3U(q4)G zc@=H?`MG=&OZe2lvWXm41g;!&)KWQL%JKqhzOMA&ElJ=&+oio_0o+$k= z&6J?UvW}+~N`*BQtjIN&JE^R1OLtWUj$T(bo1#sJ)$QIHs@sW`H^V?Hd-vtaB=CLu z45())ZWZ_Zdta!8mLy=kCb6n(Qf3@c5>ur|N zibs+&-`Oh8*3bZJ>y4V&R)X+jqHfXP7q${$&7vDg7USvy^VU@eZ>K=O7@tnxcf@w|(Gehyn2v z3_Y~|7z?g(Y(pc*E;OMVEw}`mE3>SEJQP;~>)7DAhWnGyP5iE=-Wc4+#nfBL16(>l z&jD`kW9S7&1YY2M0kMzC-_SQd^gB8$q7SJs8;gEC&uR7miwn0>PlopK^Z=jl8@vl>8><5fPYhehKW25}w3=*1{&#W?4a=*KLjRyb7A4Wz5;v2977nDrm%AXC!)kGH}`%Y=3WN6LU@x5G`xZnYJDB~?L z#w~4fdcV{5wrBtSwF7X2VU7rKhNOoS9l~%U-=(OfeG!kFHmKjb6 zGv&5I$wn(@EuVSy|EZW2m!kEi7I)I?XD;3d$=t0pVNe>YAKGoDn}w`ucFs$uWWAJ) z3H)m&er@E8Vl0)hn$6p?7VZ3AcpwDQF`0@k7%T1@Y3xEbYb%3(C1iL0NfU;Zar&`qYTYhmZP_`h zypnb)TNC-$%)*P|qRZ-E-L44b+;^?g4<@!4|9#APoD;_=c>K7CH-7-@9+TKIke|JS aHf$s)Sz{VVvClf##yOt{OoT($1j8SMWK-Gz diff --git a/careconnect2025/backend/billing/target/test-classes/com/careconnectpt/careconnect2025/Careconnect2025ApplicationTests.class b/careconnect2025/backend/billing/target/test-classes/com/careconnectpt/careconnect2025/Careconnect2025ApplicationTests.class deleted file mode 100644 index 94ed05532ce0117f9d6874127f4bea98a6f755fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 598 zcmbtRO-lnY5PfNDt8KNldJ#N$@Zbj+Dd@!ys0B}pf-Svgx3Q+~CQCM}{w+^}2Y-M+ zN}Q|}3m!a}!%W`1c{7vA`^W1WfHSPuQ9`+fN)rpHGPI}SLGW1U3BL}fGI9*nQ>B$V zXDILNj%rwB=tU;sk+3o{T2oHy=27>sd&K)ctjE1Hja4L^GI}U8moZfOW-J+6fzt9S zPeN&jB8(~02}~s7k+90+S+L^n6p8x5zdN5WG-+%npIl(Xn2K)P Date: Thu, 31 Jul 2025 22:40:45 -0500 Subject: [PATCH 13/15] Update: FeedController, LeaderboardEntry --- .../controller/FeedController.java | 64 +++++++++++-------- .../com/careconnect/dto/LeaderboardEntry.java | 10 ++- .../repository/UserRepository.java | 4 +- .../Flutter/GeneratedPluginRegistrant.swift | 2 + 4 files changed, 48 insertions(+), 32 deletions(-) diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/controller/FeedController.java b/careconnect2025/backend/core/src/main/java/com/careconnect/controller/FeedController.java index 46f95f5a..5eab7685 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/controller/FeedController.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/controller/FeedController.java @@ -1,4 +1,5 @@ package com.careconnect.controller; +import com.careconnect.dto.PostWithCommentCountDto; import org.springframework.beans.factory.annotation.Value; import com.careconnect.model.Post; import com.careconnect.service.FeedService; @@ -29,7 +30,7 @@ import java.util.UUID; @RestController -@RequestMapping("/api/feed") +@RequestMapping("/v1/api/feed") @Tag(name = "Feed", description = "Social feed management endpoints for posts and content sharing") @SecurityRequirement(name = "JWT Authentication") public class FeedController { @@ -54,18 +55,7 @@ public class FeedController { description = "Global feed retrieved successfully", content = @Content( mediaType = "application/json", - schema = @Schema(implementation = Post.class, type = "array"), - examples = @ExampleObject(value = """ - [ - { - "id": 1, - "userId": 123, - "content": "Had a great therapy session today!", - "imageUrl": "/uploads/image123.jpg", - "createdAt": "2025-01-15T10:30:00Z" - } - ] - """) + schema = @Schema(implementation = PostWithCommentCountDto.class, type = "array") ) ), @ApiResponse( @@ -82,23 +72,15 @@ public class FeedController { ) }) public ResponseEntity getGlobalFeed() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null || !authentication.isAuthenticated()) { - return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Not authenticated"); - } - - List posts = feedService.getAllPosts(); + List posts = feedService.getAllPostsWithCommentCount(); return ResponseEntity.ok(posts); } @GetMapping("/user/{userId}") public ResponseEntity getUserFeed(@PathVariable Long userId) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null || !authentication.isAuthenticated()) { - return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Not authenticated"); - } - // Get user from JWT token (email is the subject) + // Get user from JWT token (email is the subject). + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String email = authentication.getName(); User user = userRepository.findByEmail(email).orElse(null); if (user == null) { @@ -114,18 +96,30 @@ public ResponseEntity getUserFeed(@PathVariable Long userId) { return ResponseEntity.ok(posts); } + @GetMapping("/friends-feed") + public ResponseEntity getFriendsFeed() { + // (Updated) Removed manual authentication check + + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + String email = auth.getName(); + User user = userRepository.findByEmail(email).orElse(null); + if (user == null) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body("User not found"); + } + + List posts = feedService.getPostsByUserAndFriends(user.getId()); + return ResponseEntity.ok(posts); + } + @PostMapping(value = "/create", consumes = "multipart/form-data") public ResponseEntity createPost( @RequestParam("userId") Long userId, @RequestParam("content") String content, @RequestPart(value = "image", required = false) MultipartFile imageFile ) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null || !authentication.isAuthenticated()) { - return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Not authenticated"); - } // Get user from JWT token (email is the subject) + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String email = authentication.getName(); User user = userRepository.findByEmail(email).orElse(null); if (user == null) { @@ -165,6 +159,20 @@ public ResponseEntity createPost( } Post post = feedService.createPost(userId, content, imageUrl); + + String username = user.getName() != null && !user.getName().isEmpty() ? user.getName() : user.getEmail(); + int commentCount = 0; // New post, so always 0 + + PostWithCommentCountDto dto = new PostWithCommentCountDto( + post.getId(), + post.getUserId(), + post.getContent(), + post.getImageUrl(), + post.getCreatedAt(), + commentCount, + username + ); + return ResponseEntity.status(HttpStatus.CREATED).body(post); } catch (IOException e) { diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/dto/LeaderboardEntry.java b/careconnect2025/backend/core/src/main/java/com/careconnect/dto/LeaderboardEntry.java index adbd69c1..0e77ba92 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/dto/LeaderboardEntry.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/dto/LeaderboardEntry.java @@ -3,20 +3,24 @@ public class LeaderboardEntry { private Long userId; private String name; + private String lastName; + private String firstName; private int xp; private int level; private String profileImageUrl; - public LeaderboardEntry(Long userId, String name, int xp, int level, String profileImageUrl) { + public LeaderboardEntry(Long userId, String lastName, String firstName, int xp, int level, String profileImageUrl) { this.userId = userId; - this.name = name; + this.name = lastName +" "+ firstName; + this.lastName = lastName; + this.firstName = firstName; this.xp = xp; this.level = level; this.profileImageUrl = profileImageUrl; } public Long getUserId() { return userId; } - public String getName() { return name; } + public String getName() { return name ; } public int getXp() { return xp; } public int getLevel() { return level; } public String getProfileImageUrl() { return profileImageUrl; } diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/repository/UserRepository.java b/careconnect2025/backend/core/src/main/java/com/careconnect/repository/UserRepository.java index 752ebb5c..b9201086 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/repository/UserRepository.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/repository/UserRepository.java @@ -31,13 +31,15 @@ public interface UserRepository extends JpaRepository { @Query(""" SELECT new com.careconnect.dto.LeaderboardEntry( u.id, - u.name, + p.lastName, + p.firstName, xp.xp, xp.level, u.profileImageUrl ) FROM User u JOIN XPProgress xp ON xp.userId = u.id + JOIN Patient p ON p.user.id = u.id WHERE u.leaderboardOptIn = true ORDER BY xp.xp DESC """) diff --git a/careconnect2025/frontend/macos/Flutter/GeneratedPluginRegistrant.swift b/careconnect2025/frontend/macos/Flutter/GeneratedPluginRegistrant.swift index 1f0feecc..3efa49dc 100644 --- a/careconnect2025/frontend/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/careconnect2025/frontend/macos/Flutter/GeneratedPluginRegistrant.swift @@ -20,6 +20,7 @@ import flutter_web_auth_2 import flutter_webrtc import geolocator_apple import iris_method_channel +import mobile_scanner import path_provider_foundation import shared_preferences_foundation import speech_to_text_macos @@ -43,6 +44,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) IrisMethodChannelPlugin.register(with: registry.registrar(forPlugin: "IrisMethodChannelPlugin")) + MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SpeechToTextMacosPlugin.register(with: registry.registrar(forPlugin: "SpeechToTextMacosPlugin")) From e737e0e1532666a27c3753995fb85f3c410b75d7 Mon Sep 17 00:00:00 2001 From: Dat Truong Date: Fri, 1 Aug 2025 20:26:07 -0500 Subject: [PATCH 14/15] Messaging & FeedController integration updates --- .../controller/CommentController.java | 2 +- .../controller/FeedController.java | 2 +- .../controller/MessageController.java | 72 ++++++++++++++++ .../com/careconnect/dto/InboxMessageDto.java | 75 +++++++++++++++++ .../java/com/careconnect/model/Message.java | 84 +++++++++++++++++++ .../repository/MessageRepository.java | 27 ++++++ 6 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 careconnect2025/backend/core/src/main/java/com/careconnect/controller/MessageController.java create mode 100644 careconnect2025/backend/core/src/main/java/com/careconnect/dto/InboxMessageDto.java create mode 100644 careconnect2025/backend/core/src/main/java/com/careconnect/model/Message.java create mode 100644 careconnect2025/backend/core/src/main/java/com/careconnect/repository/MessageRepository.java diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/controller/CommentController.java b/careconnect2025/backend/core/src/main/java/com/careconnect/controller/CommentController.java index ebbb72a8..6c1c920f 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/controller/CommentController.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/controller/CommentController.java @@ -24,7 +24,7 @@ import java.util.List; @RestController -@RequestMapping("/api/comments") +@RequestMapping("/v1/api/comments") @Tag(name = "Comments", description = "Comment management endpoints for posts") @SecurityRequirement(name = "JWT Authentication") public class CommentController { diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/controller/FeedController.java b/careconnect2025/backend/core/src/main/java/com/careconnect/controller/FeedController.java index 5eab7685..c281b686 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/controller/FeedController.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/controller/FeedController.java @@ -173,7 +173,7 @@ public ResponseEntity createPost( username ); - return ResponseEntity.status(HttpStatus.CREATED).body(post); + return ResponseEntity.status(HttpStatus.CREATED).body(dto); } catch (IOException e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/controller/MessageController.java b/careconnect2025/backend/core/src/main/java/com/careconnect/controller/MessageController.java new file mode 100644 index 00000000..5f83e146 --- /dev/null +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/controller/MessageController.java @@ -0,0 +1,72 @@ +package com.careconnect.controller; + +import com.careconnect.model.Message; +import com.careconnect.model.User; +import com.careconnect.dto.InboxMessageDto; +import com.careconnect.repository.MessageRepository; +import com.careconnect.repository.UserRepository; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.*; + +@RestController +@RequestMapping("/v1/api/messages") +public class MessageController { + + @Autowired + private MessageRepository messageRepo; + + @Autowired + private UserRepository userRepo; + + // ✅ Send a new message + @PostMapping("/send") + public ResponseEntity sendMessage(@RequestBody Message message) { + message.setTimestamp(LocalDateTime.now()); + message.setRead(false); + Message saved = messageRepo.save(message); + return ResponseEntity.ok(saved); + } + + // ✅ Fetch full conversation between two users + @GetMapping("/conversation") + public ResponseEntity> getConversation( + @RequestParam Long user1, + @RequestParam Long user2 + ) { + List conversation = messageRepo.findConversation(user1, user2); + return ResponseEntity.ok(conversation); + } + + // ✅ Inbox view: list all recent conversations with peer info + @GetMapping("/inbox/{userId}") + public ResponseEntity> getInbox(@PathVariable Long userId) { + List messages = messageRepo.findAllUserMessages(userId); + Map map = new LinkedHashMap<>(); // keep order + + for (Message m : messages) { + Long peerId = m.getSenderId().equals(userId) ? m.getReceiverId() : m.getSenderId(); + if (map.containsKey(peerId)) continue; // already got latest from this peer + + Optional peer = userRepo.findById(peerId); + if (peer.isPresent()) { + User u = peer.get(); + InboxMessageDto dto = new InboxMessageDto( + m.getId(), + peerId, + u.getName(), + u.getEmail(), + m.getContent(), + m.getTimestamp() + ); + map.put(peerId, dto); + } + } + + return ResponseEntity.ok(new ArrayList<>(map.values())); + } +} \ No newline at end of file diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/dto/InboxMessageDto.java b/careconnect2025/backend/core/src/main/java/com/careconnect/dto/InboxMessageDto.java new file mode 100644 index 00000000..90e7c668 --- /dev/null +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/dto/InboxMessageDto.java @@ -0,0 +1,75 @@ +package com.careconnect.dto; + +import java.time.LocalDateTime; + +public class InboxMessageDto { + private Long messageId; + private Long peerId; + private String peerName; + private String peerEmail; + private String content; + private LocalDateTime timestamp; + + // Default constructor + public InboxMessageDto() {} + + public InboxMessageDto(Long messageId, Long peerId, String peerName, String peerEmail, + String content, LocalDateTime timestamp) { + this.messageId = messageId; + this.peerId = peerId; + this.peerName = peerName; + this.peerEmail = peerEmail; + this.content = content; + this.timestamp = timestamp; + } + + // Getters and Setters + + public Long getMessageId() { + return messageId; + } + + public void setMessageId(Long messageId) { + this.messageId = messageId; + } + + public Long getPeerId() { + return peerId; + } + + public void setPeerId(Long peerId) { + this.peerId = peerId; + } + + public String getPeerName() { + return peerName; + } + + public void setPeerName(String peerName) { + this.peerName = peerName; + } + + public String getPeerEmail() { + return peerEmail; + } + + public void setPeerEmail(String peerEmail) { + this.peerEmail = peerEmail; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } + + public void setTimestamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + } +} \ No newline at end of file diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/model/Message.java b/careconnect2025/backend/core/src/main/java/com/careconnect/model/Message.java new file mode 100644 index 00000000..c683842c --- /dev/null +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/model/Message.java @@ -0,0 +1,84 @@ +package com.careconnect.model; + +import jakarta.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "messages") +public class Message { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long senderId; + + @Column(nullable = false) + private Long receiverId; + + @Column(nullable = false, columnDefinition = "TEXT") + private String content; + + @Column(nullable = false) + private LocalDateTime timestamp; + + @Column(name = "is_read", nullable = false) + private boolean isRead = false; + + // Constructors + public Message() {} + + public Message(Long senderId, Long receiverId, String content) { + this.senderId = senderId; + this.receiverId = receiverId; + this.content = content; + this.timestamp = LocalDateTime.now(); + this.isRead = false; + } + + // Getters and Setters + public Long getId() { + return id; + } + + public Long getSenderId() { + return senderId; + } + + public void setSenderId(Long senderId) { + this.senderId = senderId; + } + + public Long getReceiverId() { + return receiverId; + } + + public void setReceiverId(Long receiverId) { + this.receiverId = receiverId; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } + + public void setTimestamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + } + + public boolean isRead() { + return isRead; + } + + public void setRead(boolean isRead) { + this.isRead = isRead; + } +} \ No newline at end of file diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/repository/MessageRepository.java b/careconnect2025/backend/core/src/main/java/com/careconnect/repository/MessageRepository.java new file mode 100644 index 00000000..29fc9dee --- /dev/null +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/repository/MessageRepository.java @@ -0,0 +1,27 @@ +package com.careconnect.repository; + +import com.careconnect.model.Message; +import jakarta.annotation.PostConstruct; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface MessageRepository extends JpaRepository { + + @PostConstruct + public default void test() { + System.out.println("✅ MessageRepository initialized"); + } + // Get messages between two users, ordered by timestamp + @Query("SELECT m FROM Message m " + + "WHERE (m.senderId = :user1 AND m.receiverId = :user2) " + + "OR (m.senderId = :user2 AND m.receiverId = :user1) " + + "ORDER BY m.timestamp ASC") + List findConversation(@Param("user1") Long user1, @Param("user2") Long user2); + + // Optionally for inbox preview: last message per user + @Query("SELECT m FROM Message m WHERE m.senderId = :userId OR m.receiverId = :userId ORDER BY m.timestamp DESC") + List findAllUserMessages(@Param("userId") Long userId); +} \ No newline at end of file From ae62f973114b98b4ee0a61efdbc70ab113bb8972 Mon Sep 17 00:00:00 2001 From: Dat Truong Date: Sat, 2 Aug 2025 00:15:12 -0500 Subject: [PATCH 15/15] Removed image upload logic from FeedController#createPost --- .../controller/FeedController.java | 91 ++++++++----------- .../repository/CaregiverRepository.java | 1 + .../presentation/pages/new_post_screen.dart | 21 ++--- 3 files changed, 46 insertions(+), 67 deletions(-) diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/controller/FeedController.java b/careconnect2025/backend/core/src/main/java/com/careconnect/controller/FeedController.java index 283a8226..ae9b7745 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/controller/FeedController.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/controller/FeedController.java @@ -1,5 +1,8 @@ package com.careconnect.controller; import com.careconnect.dto.PostWithCommentCountDto; +import com.careconnect.repository.CaregiverRepository; +import com.careconnect.repository.PatientRepository; +import com.careconnect.security.Role; import org.springframework.beans.factory.annotation.Value; import com.careconnect.model.Post; import com.careconnect.service.FeedService; @@ -35,8 +38,6 @@ @SecurityRequirement(name = "JWT Authentication") public class FeedController { - @Value("${careconnect.upload.dir}") - private String uploadDir; @Autowired private FeedService feedService; @@ -44,6 +45,12 @@ public class FeedController { @Autowired private UserRepository userRepository; + @Autowired + private PatientRepository patientRepository; + + @Autowired + private CaregiverRepository caregiverRepository; + @GetMapping("/all") @Operation( summary = "Get global feed", @@ -111,13 +118,9 @@ public ResponseEntity getFriendsFeed() { return ResponseEntity.ok(posts); } - @PostMapping(value = "/create", consumes = "multipart/form-data") - public ResponseEntity createPost( - @RequestParam("userId") Long userId, - @RequestParam("content") String content, - @RequestPart(value = "image", required = false) MultipartFile imageFile - ) { - + @PostMapping(value = "/create", consumes = "application/json") + public ResponseEntity createPost(@RequestBody Post postData) { + // Get user from JWT token (email is the subject) Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String email = authentication.getName(); @@ -125,62 +128,42 @@ public ResponseEntity createPost( if (user == null) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("User not found"); } - + // Verify the post belongs to the authenticated user - if (!user.getId().equals(userId)) { + if (!user.getId().equals(postData.getUserId())) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Cannot create post as another user"); } - try { - String imageUrl = null; - - // Ensure the upload directory exists - File uploadFolder = new File(uploadDir); - if (!uploadFolder.exists()) { - boolean created = uploadFolder.mkdirs(); - if (!created) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body("Failed to create upload directory"); - } - } - - // Handle image upload if present - if (imageFile != null && !imageFile.isEmpty()) { - String extension = ""; - String originalName = imageFile.getOriginalFilename(); - int dotIndex = (originalName != null) ? originalName.lastIndexOf('.') : -1; - if (dotIndex > 0) { - extension = originalName.substring(dotIndex); - } - String filename = UUID.randomUUID() + extension; - File destination = new File(uploadFolder, filename); - imageFile.transferTo(destination); - imageUrl = "/uploads/" + filename; // URL for client - } - - Post post = feedService.createPost(userId, content, imageUrl); - - String username = user.getName() != null && !user.getName().isEmpty() ? user.getName() : user.getEmail(); - int commentCount = 0; // New post, so always 0 + Post savedPost = feedService.createPost(user.getId(), postData.getContent(), null); PostWithCommentCountDto dto = new PostWithCommentCountDto( - post.getId(), - post.getUserId(), - post.getContent(), - post.getImageUrl(), - post.getCreatedAt(), - commentCount, - username + savedPost.getId(), + savedPost.getUserId(), + savedPost.getContent(), + null, + savedPost.getCreatedAt(), + 0, + resolveDisplayName(user) ); return ResponseEntity.status(HttpStatus.CREATED).body(dto); - } catch (IOException e) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body("Error saving image: " + e.getMessage()); } catch (Exception e) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body("Error creating post: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error creating post: " + e.getMessage()); + } + } + + private String resolveDisplayName(User user) { + if (user.getRole() == Role.PATIENT) { + return patientRepository.findByUserId(user.getId()) + .map(p -> p.getFirstName() + " " + p.getLastName()) + .orElse(user.getEmail()); + } else if (user.getRole() == Role.CAREGIVER) { + return caregiverRepository.findByUserId(user.getId()) + .map(c -> c.getFirstName() + " " + c.getLastName()) + .orElse(user.getEmail()); + } else { + return user.getEmail(); } } } diff --git a/careconnect2025/backend/core/src/main/java/com/careconnect/repository/CaregiverRepository.java b/careconnect2025/backend/core/src/main/java/com/careconnect/repository/CaregiverRepository.java index c2b80f8d..1bce1706 100644 --- a/careconnect2025/backend/core/src/main/java/com/careconnect/repository/CaregiverRepository.java +++ b/careconnect2025/backend/core/src/main/java/com/careconnect/repository/CaregiverRepository.java @@ -8,6 +8,7 @@ public interface CaregiverRepository extends JpaRepository { Optional findByUser(User user); + Optional findByUserId(Long userId); boolean existsByIdAndUserId(Long caregiverId, Long userId); } \ No newline at end of file diff --git a/careconnect2025/frontend/lib/features/social/presentation/pages/new_post_screen.dart b/careconnect2025/frontend/lib/features/social/presentation/pages/new_post_screen.dart index a95790ca..bad1ee13 100644 --- a/careconnect2025/frontend/lib/features/social/presentation/pages/new_post_screen.dart +++ b/careconnect2025/frontend/lib/features/social/presentation/pages/new_post_screen.dart @@ -47,20 +47,15 @@ class _NewPostScreenState extends State { final uri = Uri.parse('${getBackendBaseUrl()}/v1/api/feed/create'); final headers = await ApiService.getAuthHeaders(); - final request = http.MultipartRequest('POST', uri) - ..headers.addAll(headers) - ..fields['userId'] = userId.toString() - ..fields['content'] = content; - - if (_selectedImage != null) { - request.files.add(await http.MultipartFile.fromPath( - 'image', - _selectedImage!.path, - )); - } + headers['Content-Type'] = 'application/json'; + + final body = jsonEncode({ + 'userId': userId, + 'content': content, + }); + - final streamedResponse = await request.send(); - final response = await http.Response.fromStream(streamedResponse); + final response = await http.post(uri, headers: headers, body: body); print('Create post status: ${response.statusCode}'); print('Create post body: ${response.body}');