Merge pull request #322 from RecentRunner/android-desktop-parity
fix six app bugs
This commit was merged in pull request #322.
This commit is contained in:
145
backend/backend-test-results.txt
Normal file
145
backend/backend-test-results.txt
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
|
||||||
|
WARNING: sun.misc.Unsafe::staticFieldBase has been called by com.google.inject.internal.aop.HiddenClassDefiner (file:/nix/store/snv87hz5j78nqiqqamlf1mimbkmcrl6l-maven-3.9.11/maven/lib/guice-5.1.0-classes.jar)
|
||||||
|
WARNING: Please consider reporting this to the maintainers of class com.google.inject.internal.aop.HiddenClassDefiner
|
||||||
|
WARNING: sun.misc.Unsafe::staticFieldBase will be removed in a future release
|
||||||
|
[INFO] Scanning for projects...
|
||||||
|
[INFO]
|
||||||
|
[INFO] ------------------------< com.petshop:backend >-------------------------
|
||||||
|
[INFO] Building PetShop Backend 1.0.0
|
||||||
|
[INFO] from pom.xml
|
||||||
|
[INFO] --------------------------------[ jar ]---------------------------------
|
||||||
|
[INFO]
|
||||||
|
[INFO] --- enforcer:3.5.0:enforce (require-java-25) @ backend ---
|
||||||
|
[INFO] Rule 0: org.apache.maven.enforcer.rules.version.RequireJavaVersion passed
|
||||||
|
[INFO]
|
||||||
|
[INFO] --- resources:3.3.1:resources (default-resources) @ backend ---
|
||||||
|
[INFO] Copying 2 resources from src/main/resources to target/classes
|
||||||
|
[INFO] Copying 14 resources from src/main/resources to target/classes
|
||||||
|
[INFO]
|
||||||
|
[INFO] --- compiler:3.14.1:compile (default-compile) @ backend ---
|
||||||
|
[INFO] Nothing to compile - all classes are up to date.
|
||||||
|
[INFO]
|
||||||
|
[INFO] --- resources:3.3.1:testResources (default-testResources) @ backend ---
|
||||||
|
[INFO] skip non existing resourceDirectory /home/user/threaded-parity/backend/src/test/resources
|
||||||
|
[INFO]
|
||||||
|
[INFO] --- compiler:3.14.1:testCompile (default-testCompile) @ backend ---
|
||||||
|
[INFO] Nothing to compile - all classes are up to date.
|
||||||
|
[INFO]
|
||||||
|
[INFO] --- surefire:3.5.4:test (default-test) @ backend ---
|
||||||
|
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
|
||||||
|
[INFO]
|
||||||
|
[INFO] -------------------------------------------------------
|
||||||
|
[INFO] T E S T S
|
||||||
|
[INFO] -------------------------------------------------------
|
||||||
|
[INFO] Running com.petshop.backend.service.UserServiceTest
|
||||||
|
Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3
|
||||||
|
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
|
||||||
|
WARNING: A Java agent has been loaded dynamically (/home/user/.m2/repository/net/bytebuddy/byte-buddy-agent/1.17.8/byte-buddy-agent-1.17.8.jar)
|
||||||
|
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
|
||||||
|
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
|
||||||
|
WARNING: Dynamic loading of agents will be disallowed by default in a future release
|
||||||
|
[ERROR] Tests run: 10, Failures: 3, Errors: 2, Skipped: 0, Time elapsed: 0.624 s <<< FAILURE! -- in com.petshop.backend.service.UserServiceTest
|
||||||
|
[ERROR] com.petshop.backend.service.UserServiceTest.scopedUpdateDeniesRoleEscalation -- Time elapsed: 0.007 s <<< FAILURE!
|
||||||
|
org.opentest4j.AssertionFailedError: Unexpected exception type thrown, expected: <org.springframework.security.access.AccessDeniedException> but was: <com.petshop.backend.exception.ResourceNotFoundException>
|
||||||
|
at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:158)
|
||||||
|
at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:68)
|
||||||
|
at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:35)
|
||||||
|
at org.junit.jupiter.api.Assertions.assertThrows(Assertions.java:3223)
|
||||||
|
at com.petshop.backend.service.UserServiceTest.scopedUpdateDeniesRoleEscalation(UserServiceTest.java:181)
|
||||||
|
Caused by: com.petshop.backend.exception.ResourceNotFoundException: User not found with id: 2
|
||||||
|
at com.petshop.backend.service.UserService.lambda$updateUser$0(UserService.java:113)
|
||||||
|
at java.base/java.util.Optional.orElseThrow(Optional.java:403)
|
||||||
|
at com.petshop.backend.service.UserService.updateUser(UserService.java:113)
|
||||||
|
at com.petshop.backend.service.UserServiceTest.lambda$scopedUpdateDeniesRoleEscalation$0(UserServiceTest.java:181)
|
||||||
|
at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:54)
|
||||||
|
... 3 more
|
||||||
|
|
||||||
|
[ERROR] com.petshop.backend.service.UserServiceTest.updateUserTreatsWrongScopedRoleAsNotFound -- Time elapsed: 0.005 s <<< ERROR!
|
||||||
|
org.mockito.exceptions.misusing.UnnecessaryStubbingException:
|
||||||
|
|
||||||
|
Unnecessary stubbings detected.
|
||||||
|
Clean & maintainable test code requires zero unnecessary code.
|
||||||
|
Following stubbings are unnecessary (click to navigate to relevant line of code):
|
||||||
|
1. -> at com.petshop.backend.service.UserServiceTest.updateUserTreatsWrongScopedRoleAsNotFound(UserServiceTest.java:75)
|
||||||
|
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.
|
||||||
|
at org.mockito.junit.jupiter.MockitoExtension.lambda$afterEach$2(MockitoExtension.java:200)
|
||||||
|
at java.base/java.util.Optional.ifPresent(Optional.java:178)
|
||||||
|
at org.mockito.junit.jupiter.MockitoExtension.afterEach(MockitoExtension.java:198)
|
||||||
|
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
|
||||||
|
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
|
||||||
|
|
||||||
|
[ERROR] com.petshop.backend.service.UserServiceTest.updateUserDeniesPromotingAnotherUserToAdmin -- Time elapsed: 0.004 s <<< FAILURE!
|
||||||
|
org.opentest4j.AssertionFailedError: Unexpected exception type thrown, expected: <org.springframework.security.access.AccessDeniedException> but was: <com.petshop.backend.exception.ResourceNotFoundException>
|
||||||
|
at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:158)
|
||||||
|
at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:68)
|
||||||
|
at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:35)
|
||||||
|
at org.junit.jupiter.api.Assertions.assertThrows(Assertions.java:3223)
|
||||||
|
at com.petshop.backend.service.UserServiceTest.updateUserDeniesPromotingAnotherUserToAdmin(UserServiceTest.java:167)
|
||||||
|
Caused by: com.petshop.backend.exception.ResourceNotFoundException: User not found with id: 2
|
||||||
|
at com.petshop.backend.service.UserService.lambda$updateUser$0(UserService.java:113)
|
||||||
|
at java.base/java.util.Optional.orElseThrow(Optional.java:403)
|
||||||
|
at com.petshop.backend.service.UserService.updateUser(UserService.java:113)
|
||||||
|
at com.petshop.backend.service.UserService.updateUser(UserService.java:107)
|
||||||
|
at com.petshop.backend.service.UserServiceTest.lambda$updateUserDeniesPromotingAnotherUserToAdmin$0(UserServiceTest.java:167)
|
||||||
|
at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:54)
|
||||||
|
... 3 more
|
||||||
|
|
||||||
|
[ERROR] com.petshop.backend.service.UserServiceTest.updateUserAllowsEditingOwnAdminAccount -- Time elapsed: 0.003 s <<< ERROR!
|
||||||
|
com.petshop.backend.exception.ResourceNotFoundException: User not found with id: 1
|
||||||
|
at com.petshop.backend.service.UserService.lambda$updateUser$0(UserService.java:113)
|
||||||
|
at java.base/java.util.Optional.orElseThrow(Optional.java:403)
|
||||||
|
at com.petshop.backend.service.UserService.updateUser(UserService.java:113)
|
||||||
|
at com.petshop.backend.service.UserService.updateUser(UserService.java:107)
|
||||||
|
at com.petshop.backend.service.UserServiceTest.updateUserAllowsEditingOwnAdminAccount(UserServiceTest.java:152)
|
||||||
|
|
||||||
|
[ERROR] com.petshop.backend.service.UserServiceTest.updateUserDeniesEditingAnotherAdmin -- Time elapsed: 0.005 s <<< FAILURE!
|
||||||
|
org.opentest4j.AssertionFailedError: Unexpected exception type thrown, expected: <org.springframework.security.access.AccessDeniedException> but was: <com.petshop.backend.exception.ResourceNotFoundException>
|
||||||
|
at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:158)
|
||||||
|
at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:68)
|
||||||
|
at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:35)
|
||||||
|
at org.junit.jupiter.api.Assertions.assertThrows(Assertions.java:3223)
|
||||||
|
at com.petshop.backend.service.UserServiceTest.updateUserDeniesEditingAnotherAdmin(UserServiceTest.java:65)
|
||||||
|
Caused by: com.petshop.backend.exception.ResourceNotFoundException: User not found with id: 2
|
||||||
|
at com.petshop.backend.service.UserService.lambda$updateUser$0(UserService.java:113)
|
||||||
|
at java.base/java.util.Optional.orElseThrow(Optional.java:403)
|
||||||
|
at com.petshop.backend.service.UserService.updateUser(UserService.java:113)
|
||||||
|
at com.petshop.backend.service.UserService.updateUser(UserService.java:107)
|
||||||
|
at com.petshop.backend.service.UserServiceTest.lambda$updateUserDeniesEditingAnotherAdmin$0(UserServiceTest.java:65)
|
||||||
|
at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:54)
|
||||||
|
... 3 more
|
||||||
|
|
||||||
|
[INFO]
|
||||||
|
[INFO] Results:
|
||||||
|
[INFO]
|
||||||
|
[ERROR] Failures:
|
||||||
|
[ERROR] UserServiceTest.scopedUpdateDeniesRoleEscalation:181 Unexpected exception type thrown, expected: <org.springframework.security.access.AccessDeniedException> but was: <com.petshop.backend.exception.ResourceNotFoundException>
|
||||||
|
[ERROR] UserServiceTest.updateUserDeniesEditingAnotherAdmin:65 Unexpected exception type thrown, expected: <org.springframework.security.access.AccessDeniedException> but was: <com.petshop.backend.exception.ResourceNotFoundException>
|
||||||
|
[ERROR] UserServiceTest.updateUserDeniesPromotingAnotherUserToAdmin:167 Unexpected exception type thrown, expected: <org.springframework.security.access.AccessDeniedException> but was: <com.petshop.backend.exception.ResourceNotFoundException>
|
||||||
|
[ERROR] Errors:
|
||||||
|
[ERROR] UserServiceTest.updateUserAllowsEditingOwnAdminAccount:152 » ResourceNotFound User not found with id: 1
|
||||||
|
[ERROR] UserServiceTest.updateUserTreatsWrongScopedRoleAsNotFound » UnnecessaryStubbing
|
||||||
|
Unnecessary stubbings detected.
|
||||||
|
Clean & maintainable test code requires zero unnecessary code.
|
||||||
|
Following stubbings are unnecessary (click to navigate to relevant line of code):
|
||||||
|
1. -> at com.petshop.backend.service.UserServiceTest.updateUserTreatsWrongScopedRoleAsNotFound(UserServiceTest.java:75)
|
||||||
|
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.
|
||||||
|
[INFO]
|
||||||
|
[ERROR] Tests run: 10, Failures: 3, Errors: 2, Skipped: 0
|
||||||
|
[INFO]
|
||||||
|
[INFO] ------------------------------------------------------------------------
|
||||||
|
[INFO] BUILD FAILURE
|
||||||
|
[INFO] ------------------------------------------------------------------------
|
||||||
|
[INFO] Total time: 2.040 s
|
||||||
|
[INFO] Finished at: 2026-04-16T08:04:25-06:00
|
||||||
|
[INFO] ------------------------------------------------------------------------
|
||||||
|
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.5.4:test (default-test) on project backend: There are test failures.
|
||||||
|
[ERROR]
|
||||||
|
[ERROR] See /home/user/threaded-parity/backend/target/surefire-reports for the individual test results.
|
||||||
|
[ERROR] See dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
|
||||||
|
[ERROR] -> [Help 1]
|
||||||
|
[ERROR]
|
||||||
|
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
|
||||||
|
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
|
||||||
|
[ERROR]
|
||||||
|
[ERROR] For more information about the errors and possible solutions, please read the following articles:
|
||||||
|
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
|
||||||
@@ -56,7 +56,8 @@ public class AiChatController {
|
|||||||
|
|
||||||
List<Pet> userPets;
|
List<Pet> userPets;
|
||||||
try {
|
try {
|
||||||
userPets = petRepository.findAllByOwner_IdOrderByPetNameAsc(user.getId());
|
userPets = petRepository.findAllByOwner_IdAndPetStatusInOrderByPetNameAsc(
|
||||||
|
user.getId(), List.of("Adopted", "Owned"));
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
|
|||||||
@@ -53,5 +53,8 @@ public interface AppointmentRepository extends JpaRepository<Appointment, Long>
|
|||||||
|
|
||||||
List<Appointment> findByPet_Id(Long petId);
|
List<Appointment> findByPet_Id(Long petId);
|
||||||
|
|
||||||
|
@Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.pet.petId = :petId AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) NOT IN ('cancelled', 'missed')")
|
||||||
|
List<Appointment> findByPetIdAndAppointmentDate(@Param("petId") Long petId, @Param("date") LocalDate date);
|
||||||
|
|
||||||
List<Appointment> findByAppointmentDateAndAppointmentStatusIgnoreCase(LocalDate date, String status);
|
List<Appointment> findByAppointmentDateAndAppointmentStatusIgnoreCase(LocalDate date, String status);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ public interface PetRepository extends JpaRepository<Pet, Long> {
|
|||||||
List<Pet> findAdoptablePetsByStore(@Param("storeId") Long storeId);
|
List<Pet> findAdoptablePetsByStore(@Param("storeId") Long storeId);
|
||||||
|
|
||||||
List<Pet> findAllByOwner_IdOrderByPetNameAsc(Long ownerId);
|
List<Pet> findAllByOwner_IdOrderByPetNameAsc(Long ownerId);
|
||||||
|
List<Pet> findAllByOwner_IdAndPetStatusInOrderByPetNameAsc(Long ownerId, List<String> statuses);
|
||||||
Optional<Pet> findByIdAndOwner_Id(Long id, Long ownerId);
|
Optional<Pet> findByIdAndOwner_Id(Long id, Long ownerId);
|
||||||
|
|
||||||
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ public class AppointmentService {
|
|||||||
validateStoreAccess(store.getStoreId(), authenticatedUser);
|
validateStoreAccess(store.getStoreId(), authenticatedUser);
|
||||||
validatePetServiceCompatibility(pet, service);
|
validatePetServiceCompatibility(pet, service);
|
||||||
validateAvailability(employee, service, request.getAppointmentDate(), request.getAppointmentTime(), null);
|
validateAvailability(employee, service, request.getAppointmentDate(), request.getAppointmentTime(), null);
|
||||||
|
validatePetAvailability(pet, service, request.getAppointmentDate(), request.getAppointmentTime(), null);
|
||||||
|
|
||||||
Appointment appointment = new Appointment();
|
Appointment appointment = new Appointment();
|
||||||
appointment.setCustomer(customer);
|
appointment.setCustomer(customer);
|
||||||
@@ -170,6 +171,7 @@ public class AppointmentService {
|
|||||||
validateStoreAccess(store.getStoreId(), authenticatedUser);
|
validateStoreAccess(store.getStoreId(), authenticatedUser);
|
||||||
validatePetServiceCompatibility(pet, service);
|
validatePetServiceCompatibility(pet, service);
|
||||||
validateAvailability(employee, service, request.getAppointmentDate(), request.getAppointmentTime(), id);
|
validateAvailability(employee, service, request.getAppointmentDate(), request.getAppointmentTime(), id);
|
||||||
|
validatePetAvailability(pet, service, request.getAppointmentDate(), request.getAppointmentTime(), id);
|
||||||
|
|
||||||
appointment.setCustomer(customer);
|
appointment.setCustomer(customer);
|
||||||
appointment.setStore(store);
|
appointment.setStore(store);
|
||||||
@@ -385,6 +387,15 @@ public class AppointmentService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validatePetAvailability(Pet pet, com.petshop.backend.entity.Service service, LocalDate date, LocalTime time, Long appointmentIdToIgnore) {
|
||||||
|
if (pet == null) return;
|
||||||
|
List<Appointment> existingAppointments = appointmentRepository
|
||||||
|
.findByPetIdAndAppointmentDate(pet.getPetId(), date);
|
||||||
|
if (!isSlotAvailable(existingAppointments, service, time, appointmentIdToIgnore)) {
|
||||||
|
throw new IllegalArgumentException("This pet already has an appointment during this time slot");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void validateStoreAccess(Long requestedStoreId, User user) {
|
private void validateStoreAccess(Long requestedStoreId, User user) {
|
||||||
if (user.getRole() != User.Role.STAFF) {
|
if (user.getRole() != User.Role.STAFF) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -111,16 +111,18 @@ function AiChatPage() {
|
|||||||
lastScrolledIdRef.current = lastMsg.id;
|
lastScrolledIdRef.current = lastMsg.id;
|
||||||
const area = messagesAreaRef.current;
|
const area = messagesAreaRef.current;
|
||||||
if (!area) return;
|
if (!area) return;
|
||||||
const nearBottom = area.scrollHeight - area.scrollTop - area.clientHeight < 150;
|
const nearBottom = area.scrollHeight - area.scrollTop - area.clientHeight < 80;
|
||||||
if (nearBottom) messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
if (nearBottom) {
|
||||||
|
area.scrollTop = area.scrollHeight;
|
||||||
|
}
|
||||||
}, [messages]);
|
}, [messages]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!botTyping) return;
|
if (!botTyping) return;
|
||||||
const area = messagesAreaRef.current;
|
const area = messagesAreaRef.current;
|
||||||
if (!area) return;
|
if (!area) return;
|
||||||
const nearBottom = area.scrollHeight - area.scrollTop - area.clientHeight < 150;
|
const nearBottom = area.scrollHeight - area.scrollTop - area.clientHeight < 80;
|
||||||
if (nearBottom) messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
if (nearBottom) area.scrollTop = area.scrollHeight;
|
||||||
}, [botTyping]);
|
}, [botTyping]);
|
||||||
|
|
||||||
const fetchMessages = useCallback(async (convId) => {
|
const fetchMessages = useCallback(async (convId) => {
|
||||||
@@ -214,6 +216,8 @@ function AiChatPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!token || authLoading) return;
|
if (!token || authLoading) return;
|
||||||
|
|
||||||
|
let stale = false;
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
setLoadingConv(true);
|
setLoadingConv(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -225,6 +229,7 @@ function AiChatPage() {
|
|||||||
const res = await fetch(`${API_BASE}/api/v1/chat/conversations`, {
|
const res = await fetch(`${API_BASE}/api/v1/chat/conversations`, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
});
|
});
|
||||||
|
if (stale) return;
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const list = await res.json();
|
const list = await res.json();
|
||||||
const openAi = Array.isArray(list)
|
const openAi = Array.isArray(list)
|
||||||
@@ -233,12 +238,12 @@ function AiChatPage() {
|
|||||||
if (openAi) convId = openAi.id;
|
if (openAi) convId = openAi.id;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
if (stale) return;
|
||||||
setError("Failed to load conversations.");
|
setError("Failed to load conversations.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!convId) {
|
if (!convId) {
|
||||||
// Auto-create a new AI conversation
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_BASE}/api/v1/chat/conversations`, {
|
const res = await fetch(`${API_BASE}/api/v1/chat/conversations`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -248,17 +253,19 @@ function AiChatPage() {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({}),
|
body: JSON.stringify({}),
|
||||||
});
|
});
|
||||||
|
if (stale) return;
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const conv = await res.json();
|
const conv = await res.json();
|
||||||
convId = conv.id;
|
convId = conv.id;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// silent
|
if (stale) return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!convId) {
|
if (!convId) {
|
||||||
await fetchConversations();
|
await fetchConversations();
|
||||||
|
if (stale) return;
|
||||||
setLoadingConv(false);
|
setLoadingConv(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -268,6 +275,7 @@ function AiChatPage() {
|
|||||||
fetchMessages(convId),
|
fetchMessages(convId),
|
||||||
fetchConversations(),
|
fetchConversations(),
|
||||||
]);
|
]);
|
||||||
|
if (stale) return;
|
||||||
setLoadingConv(false);
|
setLoadingConv(false);
|
||||||
connectStomp(convId);
|
connectStomp(convId);
|
||||||
router.replace(`/ai-chat?id=${convId}`, { scroll: false });
|
router.replace(`/ai-chat?id=${convId}`, { scroll: false });
|
||||||
@@ -276,6 +284,7 @@ function AiChatPage() {
|
|||||||
init();
|
init();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
stale = true;
|
||||||
if (stompRef.current) { stompRef.current.deactivate(); stompRef.current = null; }
|
if (stompRef.current) { stompRef.current.deactivate(); stompRef.current = null; }
|
||||||
};
|
};
|
||||||
}, [token, authLoading, conversationIdParam, fetchConversation, fetchMessages, connectStomp, fetchConversations, router]);
|
}, [token, authLoading, conversationIdParam, fetchConversation, fetchMessages, connectStomp, fetchConversations, router]);
|
||||||
@@ -431,11 +440,6 @@ function AiChatPage() {
|
|||||||
setMessages([]);
|
setMessages([]);
|
||||||
setError(null);
|
setError(null);
|
||||||
setBotTyping(false);
|
setBotTyping(false);
|
||||||
setSwitchingConv(true);
|
|
||||||
await fetchConversation(convId);
|
|
||||||
await fetchMessages(convId);
|
|
||||||
setSwitchingConv(false);
|
|
||||||
connectStomp(convId);
|
|
||||||
router.replace(`/ai-chat?id=${convId}`, { scroll: false });
|
router.replace(`/ai-chat?id=${convId}`, { scroll: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -609,7 +609,7 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
|
|||||||
}, [storeId, serviceId, appointmentDate]);
|
}, [storeId, serviceId, appointmentDate]);
|
||||||
|
|
||||||
const eligiblePets = customerPets.filter(
|
const eligiblePets = customerPets.filter(
|
||||||
(p) => p.petStatus === "Owned" || p.petStatus === "Adopted"
|
(p) => p.petStatus?.toLowerCase() === "owned" || p.petStatus?.toLowerCase() === "adopted"
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedService = services.find((s) => s.serviceId === Number(serviceId));
|
const selectedService = services.find((s) => s.serviceId === Number(serviceId));
|
||||||
@@ -669,7 +669,7 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!adoptionMode && selectedPet && selectedPet.petStatus !== "Owned" && selectedPet.petStatus !== "Adopted") {
|
if (!adoptionMode && selectedPet && selectedPet.petStatus?.toLowerCase() !== "owned" && selectedPet.petStatus?.toLowerCase() !== "adopted") {
|
||||||
setError("The selected pet is no longer eligible for appointments. Please refresh the page.");
|
setError("The selected pet is no longer eligible for appointments. Please refresh the page.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,6 +215,8 @@ function ChatPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!token || authLoading) return;
|
if (!token || authLoading) return;
|
||||||
|
|
||||||
|
let stale = false;
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
setLoadingConv(true);
|
setLoadingConv(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -226,6 +228,7 @@ function ChatPage() {
|
|||||||
const res = await fetch(`${API_BASE}/api/v1/chat/conversations`, {
|
const res = await fetch(`${API_BASE}/api/v1/chat/conversations`, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
});
|
});
|
||||||
|
if (stale) return;
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const list = await res.json();
|
const list = await res.json();
|
||||||
const open = Array.isArray(list)
|
const open = Array.isArray(list)
|
||||||
@@ -234,12 +237,14 @@ function ChatPage() {
|
|||||||
if (open) convId = open.id;
|
if (open) convId = open.id;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
if (stale) return;
|
||||||
setError("Failed to load conversations.");
|
setError("Failed to load conversations.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!convId) {
|
if (!convId) {
|
||||||
await fetchConversations();
|
await fetchConversations();
|
||||||
|
if (stale) return;
|
||||||
setLoadingConv(false);
|
setLoadingConv(false);
|
||||||
setConversation(null);
|
setConversation(null);
|
||||||
return;
|
return;
|
||||||
@@ -250,6 +255,7 @@ function ChatPage() {
|
|||||||
fetchMessages(convId),
|
fetchMessages(convId),
|
||||||
fetchConversations(),
|
fetchConversations(),
|
||||||
]);
|
]);
|
||||||
|
if (stale) return;
|
||||||
setLoadingConv(false);
|
setLoadingConv(false);
|
||||||
connectStomp(convId);
|
connectStomp(convId);
|
||||||
}
|
}
|
||||||
@@ -257,6 +263,7 @@ function ChatPage() {
|
|||||||
init();
|
init();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
stale = true;
|
||||||
if (stompRef.current) { stompRef.current.deactivate(); stompRef.current = null; }
|
if (stompRef.current) { stompRef.current.deactivate(); stompRef.current = null; }
|
||||||
};
|
};
|
||||||
}, [token, authLoading, conversationIdParam, fetchConversation, fetchMessages, connectStomp, fetchConversations]);
|
}, [token, authLoading, conversationIdParam, fetchConversation, fetchMessages, connectStomp, fetchConversations]);
|
||||||
@@ -415,11 +422,6 @@ function ChatPage() {
|
|||||||
if (stompRef.current) { stompRef.current.deactivate(); stompRef.current = null; }
|
if (stompRef.current) { stompRef.current.deactivate(); stompRef.current = null; }
|
||||||
setMessages([]);
|
setMessages([]);
|
||||||
setError(null);
|
setError(null);
|
||||||
setSwitchingConv(true);
|
|
||||||
await fetchConversation(convId);
|
|
||||||
await fetchMessages(convId);
|
|
||||||
setSwitchingConv(false);
|
|
||||||
connectStomp(convId);
|
|
||||||
router.replace(`/chat?id=${convId}`, { scroll: false });
|
router.replace(`/chat?id=${convId}`, { scroll: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ body {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1.25rem;
|
gap: 1.25rem;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Indivdual Link Styles */
|
/* Indivdual Link Styles */
|
||||||
|
|||||||
Reference in New Issue
Block a user