Index: pom.xml
===================================================================
--- pom.xml	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ pom.xml	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -95,4 +95,9 @@
             <artifactId>spring-boot-starter-websocket</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
 
     </dependencies>
Index: src/main/java/com/zinemasterapp/zinemasterapp/ZineMasterAppApplication.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/ZineMasterAppApplication.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/ZineMasterAppApplication.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -3,6 +3,8 @@
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
 
 @SpringBootApplication
+@EnableScheduling
 public class ZineMasterAppApplication {
 
Index: src/main/java/com/zinemasterapp/zinemasterapp/controller/AuthController.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/controller/AuthController.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/controller/AuthController.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -8,4 +8,5 @@
 import com.zinemasterapp.zinemasterapp.repository.TokenRepository;
 import com.zinemasterapp.zinemasterapp.repository.UserRepository;
+import com.zinemasterapp.zinemasterapp.security.jwt.JwtService;
 import com.zinemasterapp.zinemasterapp.service.EmailService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -18,4 +19,5 @@
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.token.TokenService;
 import org.springframework.security.core.userdetails.UserDetails;
@@ -39,4 +41,5 @@
     private final UserRepository userRepository;//isto e kako @Autowired ama povekje imam so konstructor koristeno
     private final TokenRepository tokenRepository;
+    private final JwtService jwtService;
 
     @Autowired
@@ -47,29 +50,44 @@
 
 
-    public AuthController(UserRepository userRepository, TokenRepository tokenRepository) {
+    public AuthController(UserRepository userRepository, TokenRepository tokenRepository,  JwtService jwtService) {
         this.userRepository = userRepository;
         this.tokenRepository = tokenRepository;
+        this.jwtService = jwtService;
     }
 
-    @PostMapping("/login")// koga se pravi metod POST na ovaa specificna ruta
-    public ResponseEntity<?> login(@RequestBody LoginRequest request) {//JSON teloto go zema i go mapira vo LoginRequest
-        User user = userRepository.findByUsername(request.getUsername()).orElse(null);//dali ima vo baza korisnik vakov(so pomos na interfejsot
-        // )
-        System.out.println("Login request received from " + request.getUsername());//proverka
+    @PostMapping("/login")
+    public ResponseEntity<?> login(@RequestBody LoginRequest req) {
 
-
-        if (user == null) {
-            return ResponseEntity.status(401).body("User not found");
+        System.out.println("[/api/auth/login] username=" + req.getUsername());
+        var userOpt = userRepository.findByUsername(req.getUsername());
+        if (userOpt.isEmpty()) {
+            return ResponseEntity.status(401).body(Map.of("error", "User not found"));
         }
-        if(user.getAccess() == 0){
+        var user = userOpt.get();
+        if(user.getAccess() == 0) {
             return ResponseEntity.status(401).body("User is not active");
+        }
+        if (!passwordEncoder.matches(req.getPassword(), user.getPassword())) {
+            return ResponseEntity.status(401).body(Map.of("error", "Incorrect password"));
         }
 
 
-        if (passwordEncoder.matches(request.getPassword(), user.getPassword())) {//dali e ist so od baza pass
-            return ResponseEntity.ok(user);
-        } else {
-            return ResponseEntity.status(401).body("Invalid password");
-        }
+        String role = user.getUserType();
+        String token = jwtService.createToken(user.getId(), user.getUsername(), role, user.getEmail());
+
+        return ResponseEntity.ok(Map.of(
+                "token", token,
+                "user", Map.of(
+                        "id", user.getId(),
+                        "username", user.getUsername(),
+                        "name", user.getName(),
+                        "surname", user.getSurname(),
+                        "userType", user.getUserType(),
+                        "profilePic", user.getProfilePic() ,
+                        "address",user.getAddress(),
+                        "access",user.getAccess(),
+                        "startDate",user.getStartDate()
+                )
+        ));
     }
 
Index: src/main/java/com/zinemasterapp/zinemasterapp/controller/DevNotifyController.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/controller/DevNotifyController.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/controller/DevNotifyController.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -3,8 +3,10 @@
 import com.zinemasterapp.zinemasterapp.dto.Notificationdto;
 import com.zinemasterapp.zinemasterapp.dto.UserDTO;
+import com.zinemasterapp.zinemasterapp.repository.UserRepository;
 import com.zinemasterapp.zinemasterapp.service.RequestNotificationService;
 import org.springframework.context.annotation.Profile;
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.Authentication;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -13,4 +15,5 @@
 
 import java.time.Instant;
+import java.util.Map;
 import java.util.UUID;
 
@@ -27,5 +30,4 @@
 
     @PostMapping("/ping-admins")
-    @PreAuthorize("hasRole('ProductAdministrator')")//samo ako e administrator na produkti
     public ResponseEntity<Void> pingAdmins(@AuthenticationPrincipal org.springframework.security.core.userdetails.User me) {//samo vrakja http status
         var payload = new Notificationdto(
@@ -34,5 +36,6 @@
                 Instant.now(),
                 1,
-                "Test notification from " + me.getUsername()
+                "Test notification from " + me.getUsername(),
+                "REQUEST_CREATED"
         );
         notifier.notifyAdmins(payload);
Index: src/main/java/com/zinemasterapp/zinemasterapp/controller/NotificationController.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/controller/NotificationController.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
+++ src/main/java/com/zinemasterapp/zinemasterapp/controller/NotificationController.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -0,0 +1,48 @@
+package com.zinemasterapp.zinemasterapp.controller;
+
+import com.zinemasterapp.zinemasterapp.model.User;
+import com.zinemasterapp.zinemasterapp.repository.UserRepository;
+import com.zinemasterapp.zinemasterapp.service.NotificationCounterService;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.security.Principal;
+import java.util.Map;
+@RestController
+@RequestMapping("/api/notifications")
+public class NotificationController {
+    private final NotificationCounterService counters;
+    private final UserRepository userRepo;
+
+    public NotificationController(NotificationCounterService counters, UserRepository userRepo) {
+        this.counters = counters; this.userRepo = userRepo;
+    }
+
+    private User requireUser(Principal principal) {//java interfejs i go pretstvauva segashniot avtencticiran korisnik
+        if (principal == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
+        return userRepo.findByUsername(principal.getName())
+                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+    }
+
+    @GetMapping("/unread-count")
+    public Map<String,Integer> unreadCount(Principal principal) {//counter kolku treba da e
+        User user = requireUser(principal);
+        return Map.of("unread", counters.getUnread(user.getId()));
+    }
+
+    @PostMapping("/reset-unread")
+    public ResponseEntity<Void> resetUnread(Principal principal) {//reset na 0
+        User user = requireUser(principal);
+        counters.reset(user.getId());
+        return ResponseEntity.noContent().build();
+    }
+}
+
+
+
Index: src/main/java/com/zinemasterapp/zinemasterapp/controller/ProductRequestController.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/controller/ProductRequestController.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/controller/ProductRequestController.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -11,5 +11,8 @@
 import com.zinemasterapp.zinemasterapp.repository.UserRepository;
 import com.zinemasterapp.zinemasterapp.service.RequestNotificationService;
-import jakarta.transaction.Transactional;
+//import jakarta.transaction.Transactional;
+import com.zinemasterapp.zinemasterapp.service.RequestService;
+import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -36,6 +39,7 @@
     private final ProductRepository productRepo;
     private final RequestNotificationService requestNotificationService;
-
-    public ProductRequestController(ProductRequestRepository requestRepo, ProductRequestItemRepository itemRepo,UserRepository userRepo,ProductRepository productRepo,RequestNotificationService requestNotificationService) {
+    private final RequestService requestService;
+
+    public ProductRequestController(ProductRequestRepository requestRepo, ProductRequestItemRepository itemRepo, UserRepository userRepo, ProductRepository productRepo, RequestNotificationService requestNotificationService, RequestService requestService) {
         this.requestRepo = requestRepo;
         this.itemRepo = itemRepo;
@@ -43,4 +47,5 @@
         this.productRepo = productRepo;
         this.requestNotificationService = requestNotificationService;
+        this.requestService = requestService;
     }
 
@@ -87,5 +92,5 @@
             dto.getItems() != null ? dto.getItems().size() : 0,
             "New request #" + requestId + " (" +
-                    (dto.getItems() != null ? dto.getItems().size() : 0) + " items)"
+                    (dto.getItems() != null ? dto.getItems().size() : 0) + " items)","REQUEST_CREATED"
     );
 
@@ -192,4 +197,5 @@
     @PutMapping("/{id}/status")
     @CrossOrigin(origins = "http://localhost:8082")
+    @Transactional
     public ResponseEntity<Void> updateStatus(@PathVariable String id, @RequestParam String status,
                                              @RequestParam String adminId) {
@@ -221,4 +227,8 @@
         requestRepo.save(request);
 
+        var maker = userRepo.findById(request.getUserId()).orElseThrow();
+        var by = userRepo.findById(adminId).orElseThrow();
+
+        requestService.notifyStatusChanged(request.getId(),maker.getId(),maker.getUsername(),status,by.getUsername());
         return ResponseEntity.ok().build();
     }
Index: src/main/java/com/zinemasterapp/zinemasterapp/controller/UserController.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/controller/UserController.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/controller/UserController.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -134,4 +134,21 @@
 
 
+    @GetMapping("/{id}/processed-count")//isto kako so notificationcontroller samo ova e za drugi promenlivi
+    public ResponseEntity<Integer> getProcessedCount(@PathVariable String id) {
+        return userRepository.findById(id)
+                .map(user -> ResponseEntity.ok(user.getRequestsProcessed()))
+                .orElse(ResponseEntity.notFound().build());
+    }
+
+    @PostMapping("/{id}/processed-count/reset")
+    public ResponseEntity<Object> resetProcessedCount(@PathVariable String id) {
+        return userRepository.findById(id)
+                .map(user -> {
+                    user.setRequestsProcessed(0);
+                    userRepository.save(user);
+                    return ResponseEntity.ok().build();
+                })
+                .orElse(ResponseEntity.notFound().build());
+    }
 
 
Index: src/main/java/com/zinemasterapp/zinemasterapp/controller/UserCounterController.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/controller/UserCounterController.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
+++ src/main/java/com/zinemasterapp/zinemasterapp/controller/UserCounterController.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -0,0 +1,46 @@
+package com.zinemasterapp.zinemasterapp.controller;
+
+import com.zinemasterapp.zinemasterapp.model.User;
+import com.zinemasterapp.zinemasterapp.repository.UserRepository;
+import com.zinemasterapp.zinemasterapp.service.NotificationCounterService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+import java.util.Optional;
+
+@RestController
+@RequestMapping("/api/users")
+@RequiredArgsConstructor
+public class UserCounterController {
+    private final UserRepository users;
+    private final NotificationCounterService counters;
+
+    @GetMapping("/{username}/counters")
+    public Map<String,Object> counters(@PathVariable String username) {
+        var u = users.findByUsername(username).orElseThrow();
+        return Map.of(
+                "unread", Optional.of(u.getUnreadNotificationCount()).orElse(0),
+                "pending", Optional.of(u.getPendingEmailCount()).orElse(0),
+                "processed", Optional.of(u.getRequestsProcessed()).orElse(0)
+        );
+    }
+
+    @PostMapping("/{username}/unread/reset")
+    public void resetUnread(@PathVariable String username) {
+        counters.resetUnread(username);
+    }
+
+    @GetMapping("/{username}/status/unseen-count")
+    public int getUnseen(@PathVariable String username) {
+        var u = users.findByUsername(username).orElseThrow();
+        return counters.unseen(u.getId());
+    }
+
+    @PostMapping("/{username}/status/unseen/reset")
+    public void resetUnseen(@PathVariable String username) {
+        var u = users.findByUsername(username).orElseThrow();
+        counters.resetUnseen(u.getId());
+    }
+}
+
Index: src/main/java/com/zinemasterapp/zinemasterapp/dto/Notificationdto.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/dto/Notificationdto.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/dto/Notificationdto.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -9,6 +9,8 @@
     int itemCount;
     String summary;
+    String type;
 
-    public Notificationdto(String requestId, String createdBy, Instant createdAt, int itemCount, String summary) {
+
+    public Notificationdto(String requestId, String createdBy, Instant createdAt, int itemCount, String summary,String type) {
         this.requestId = requestId;
         this.createdBy = createdBy;
@@ -16,4 +18,14 @@
         this.itemCount = itemCount;
         this.summary = summary;
+        this.type = type;
+
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
     }
 
Index: src/main/java/com/zinemasterapp/zinemasterapp/model/ProductRequest.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/model/ProductRequest.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/model/ProductRequest.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -4,4 +4,5 @@
 import jakarta.persistence.*;
 
+import java.time.Instant;
 import java.time.LocalDate;
 import java.util.ArrayList;
@@ -21,5 +22,18 @@
     @OneToMany(mappedBy = "request", cascade = CascadeType.ALL, orphanRemoval = false)//site operacii na items ke se ferlektiraat i na narakcata
     private List<ProductRequestItem> items = new ArrayList<>();
+    @Column(nullable = false, updatable = false)
+    private Instant createdAt;
+    @PrePersist
+    public void prePersist() {
+        this.createdAt = Instant.now();
+    }
 
+    public Instant getCreatedAt() {
+        return createdAt;
+    }
+
+    public void setCreatedAt(Instant createdAt) {
+        this.createdAt = createdAt;
+    }
 
     public List<ProductRequestItem> getItems() {
Index: src/main/java/com/zinemasterapp/zinemasterapp/model/RequestCreatedEvent.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/model/RequestCreatedEvent.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
+++ src/main/java/com/zinemasterapp/zinemasterapp/model/RequestCreatedEvent.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -0,0 +1,20 @@
+package com.zinemasterapp.zinemasterapp.model;
+
+import java.time.Instant;
+
+public final class RequestCreatedEvent {
+    private final String requestId;
+    private final String actorUsername;
+    private final Instant at;
+
+    public RequestCreatedEvent(String requestId, String actorUsername, Instant at) {
+        this.requestId = requestId;
+        this.actorUsername = actorUsername;
+        this.at = at;
+    }
+
+    public String getRequestId() { return requestId; }
+    public String getActorUsername() { return actorUsername; }
+    public Instant getAt() { return at; }
+}
+
Index: src/main/java/com/zinemasterapp/zinemasterapp/model/RequestStatusChangedEvent.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/model/RequestStatusChangedEvent.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
+++ src/main/java/com/zinemasterapp/zinemasterapp/model/RequestStatusChangedEvent.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -0,0 +1,28 @@
+package com.zinemasterapp.zinemasterapp.model;
+
+import java.time.Instant;
+
+public class RequestStatusChangedEvent {
+    private final String requestId;
+    private final String makerId;
+    private final String makerUsername;
+    private final String newStatus;
+    private final String decidedBy;
+    private final Instant changedAt;
+
+    public RequestStatusChangedEvent(String requestId, String makerId, String makerUsername, String newStatus, String decidedBy, Instant changedAt) {
+        this.requestId = requestId;
+        this.makerId = makerId;
+        this.makerUsername = makerUsername;
+        this.newStatus = newStatus;
+        this.decidedBy = decidedBy;
+        this.changedAt = changedAt;
+    }
+
+    public String getRequestId() { return requestId; }
+    public String getMakerId() { return makerId; }
+    public String getMakerUsername() { return makerUsername; }
+    public String getNewStatus() { return newStatus; }
+    public String getDecidedBy() { return decidedBy; }
+    public Instant getChangedAt() { return changedAt; }
+}
Index: src/main/java/com/zinemasterapp/zinemasterapp/model/User.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/model/User.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/model/User.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -44,4 +44,48 @@
     private String authProvider;
 
+    @Column(name = "unread_notification_count", nullable = false)
+    private int unreadNotificationCount = 0;
+    @Column(name = "pending_email_count", nullable = false)
+    private int pendingEmailCount = 0;
+
+    @Column(name = "requests_processed",nullable = false)
+    private int requestsProcessed = 0;
+
+
+    @Column(name = "unseen_processed_status", nullable = false)
+    private int unseenProcessedStatus;
+
+    public int getUnseenProcessedStatus() {
+        return unseenProcessedStatus;
+    }
+
+    public void setUnseenProcessedStatus(int unseenProcessedStatus) {
+        this.unseenProcessedStatus = unseenProcessedStatus;
+    }
+
+    public int getRequestsProcessed() {
+        return requestsProcessed;
+    }
+
+    public void setRequestsProcessed(int requestsProcessed) {
+        this.requestsProcessed = requestsProcessed;
+    }
+
+    public int getPendingEmailCount() {
+        return pendingEmailCount;
+    }
+
+    public void setPendingEmailCount(int pendingEmailCount) {
+        this.pendingEmailCount = pendingEmailCount;
+    }
+
+    public int getUnreadNotificationCount() {
+        return unreadNotificationCount;
+    }
+
+    public void setUnreadNotificationCount(int unreadNotificationCount) {
+        this.unreadNotificationCount = unreadNotificationCount;
+    }
+
     public String getGoogleSub() {
         return googleSub;
Index: src/main/java/com/zinemasterapp/zinemasterapp/repository/UserRepository.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/repository/UserRepository.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/repository/UserRepository.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -3,4 +3,9 @@
 import com.zinemasterapp.zinemasterapp.model.User;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.transaction.annotation.Transactional;
+
 
 import java.util.List;
@@ -11,4 +16,82 @@
     Optional<User> findByEmail(String email);
     List<User> findByUserTypeAndAccess(String user_type, int access);
+
+
+    @Modifying(clearAutomatically = true, flushAutomatically = true)
+    @Query("update User u set u.unreadNotificationCount = u.unreadNotificationCount + 1 where u.id = :userId")
+    int incrementUnread(@Param("userId") String userId);
+
+    @Modifying(clearAutomatically = true, flushAutomatically = true)
+    @Query("update User u set u.unreadNotificationCount = u.unreadNotificationCount + 1 where u.username = :username")
+    int incrementUnreadByUsername(@Param("username") String username);
+
+    @Modifying(clearAutomatically = true, flushAutomatically = true)
+    @Query("update User u set u.unreadNotificationCount = 0 where u.username = :username")
+    int resetUnread(@Param("username") String username);
+
+    @Query("select u.unreadNotificationCount from User u where u.id = :userId")
+    Integer getUnread(@Param("userId") String userId);
+
+    @Modifying
+    @Query("UPDATE User u SET u.pendingEmailCount = u.pendingEmailCount + 1 WHERE u.id = :id")
+    void incrementPendingEmailCount(@Param("id") String id);
+
+    @Modifying
+    @Transactional
+    @Query("update User u set u.pendingEmailCount = 0 where u.username = :username and u.pendingEmailCount > 0")
+    void resetPendingEmail(@Param("username") String username);
+
+    @Query("SELECT u FROM User u WHERE u.userType = 'ProductAdministrator' AND u.pendingEmailCount > 0")
+    List<User> findAdminsWithPending();
+
+    @Modifying(clearAutomatically = true, flushAutomatically = true)
+    @Transactional
+    @Query("update User u set u.requestsProcessed = 0 where u.id = :userId")
+    int resetProccessedRequests(@Param("userId") String userId);
+
+    @Modifying(clearAutomatically = true, flushAutomatically = true)
+    @Transactional
+    @Query("update User u set u.requestsProcessed = u.requestsProcessed + 1 where u.id = :id")
+    int incrementRequestsProcessed(@Param("id") String id);
+
+
+    @Query("select u.requestsProcessed from User u where u.id = :id")
+    Integer getRequestsProcessed(@Param("id") String id);
+
+    @Modifying
+    @Query("""
+         update User u
+            set u.unreadNotificationCount = coalesce(u.unreadNotificationCount,0)+coalesce(u.pendingEmailCount,0),
+                u.pendingEmailCount = 0
+          where u.username = :username
+         """)
+    int transferPendingToUnread(@Param("username") String username);
+
+    @Modifying
+    @Query("update User u set u.pendingEmailCount = 0 where u.id=:userId")
+    int resetPending(@Param("userId") String userId);
+
+
+    @Modifying
+    @Query("update User u set u.unseenProcessedStatus = u.unseenProcessedStatus + 1 where u.id = :id")
+    void incUnseenProcessedStatus(@Param("id") String id);
+
+    @Modifying(clearAutomatically = true, flushAutomatically = true)
+    @Query("""
+    update User u
+       set u.unseenProcessedStatus = coalesce(u.unseenProcessedStatus, 0) + coalesce(u.requestsProcessed, 0),
+           u.requestsProcessed = 0
+     where u.id = :id
+""")
+    void transferProcessedToUnseen(@Param("id") String id);
+
+
+    @Query("select u.unseenProcessedStatus from User u where u.id = :id")
+    int getUnseenProcessedStatus(@Param("id") String id);
+
+    @Modifying
+    @Query("update User u set u.unseenProcessedStatus = 0 where u.id = :id")
+    void resetUnseenProcessedStatus(@Param("id") String id);
+
 }
 
Index: src/main/java/com/zinemasterapp/zinemasterapp/security/JwtAuthenticationFilter.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/security/JwtAuthenticationFilter.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/security/JwtAuthenticationFilter.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -3,8 +3,11 @@
 
 import com.zinemasterapp.zinemasterapp.security.jwt.JwtService;
+import io.jsonwebtoken.Claims;
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -29,35 +32,42 @@
 
     @Override
-    protected void doFilterInternal(HttpServletRequest request,
-                                    HttpServletResponse response,
-                                    FilterChain filterChain) throws ServletException, IOException {
+    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)//bez ova principle nemase da raboti
+            throws IOException, ServletException {
 
-        final String authHeader = request.getHeader("Authorization");//od zaglavje cita
-        final String jwt;
-        final String username;
-
-        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
-            filterChain.doFilter(request, response);//Authentication: Bearer,ako nema krositikot togas ostanuva anonimen
+        final String uri = req.getRequestURI();
+        final String header = req.getHeader("Authorization");
+        if (header == null || !header.startsWith("Bearer ")) {
+            System.out.println("[JWT] No Bearer " + uri);
+            chain.doFilter(req, res);//go prakjame vo SecurityConfig i ako ne mora da e avtenticiran i moze da dozvoli(toa so .permitAll())
             return;
         }
 
-        jwt = authHeader.substring(7);
-        username = jwtService.extractUsername(jwt);
+        final String token = header.substring(7);
+        try {
 
-        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
-            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);//ako ne  eavtenticiran
+            Claims claims = jwtService.getClaims(token);
+            String username = claims.getSubject();
+            if (username == null || username.isBlank()) {
+                username = claims.get("username", String.class);
+            }
+            System.out.println("[JWT] username" + username);
 
-            if (jwtService.isTokenValid(jwt, userDetails)) {
-                UsernamePasswordAuthenticationToken authToken =
-                        new UsernamePasswordAuthenticationToken(
-                                userDetails,
-                                null,
-                                userDetails.getAuthorities());
-                authToken.setDetails(
-                        new WebAuthenticationDetailsSource().buildDetails(request));
-                SecurityContextHolder.getContext().setAuthentication(authToken);
+            if (username == null || SecurityContextHolder.getContext().getAuthentication() != null) {
+                chain.doFilter(req, res);
+                return;
             }
+
+            UserDetails user = userDetailsService.loadUserByUsername(username);
+            boolean valid = jwtService.isTokenValid(token, user);
+            if (valid) {
+                UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
+                auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));
+                SecurityContextHolder.getContext().setAuthentication(auth);
+            }
+        } catch (Exception e) {
+         System.out.println("[JWT] Invalid Bearer token");
         }
-        filterChain.doFilter(request, response);
+
+        chain.doFilter(req, res);
     }
 }
Index: src/main/java/com/zinemasterapp/zinemasterapp/security/SecurityConfig.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/security/SecurityConfig.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/security/SecurityConfig.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -8,4 +8,6 @@
 import org.springframework.http.HttpMethod;
 import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.ProviderManager;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
 import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -32,5 +34,5 @@
 
     public SecurityConfig(OAuth2AuthenticationSuccessHandler successHandler,
-            OAuth2AuthenticationFailureHandler failureHandler, JwtAuthenticationFilter jwtAuthenticationFilter)
+                          OAuth2AuthenticationFailureHandler failureHandler, JwtAuthenticationFilter jwtAuthenticationFilter)
     {
         this.successHandler = successHandler;
@@ -55,5 +57,5 @@
                 .csrf(csrf -> csrf.disable())
                 .cors(c -> c.configurationSource(corsConfigurationSource()))
-                .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+                .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))//.IF_REQUIRED
                 .exceptionHandling(ex -> ex.authenticationEntryPoint(
                         (req, res, e) -> res.sendError(HttpServletResponse.SC_UNAUTHORIZED)))
@@ -63,9 +65,16 @@
                         .requestMatchers(HttpMethod.PUT, "/api/users/**").permitAll()
                                 .requestMatchers(HttpMethod.POST, "/api/auth/").permitAll()
+                        .requestMatchers(HttpMethod.POST, "/api/auth/login").permitAll()
                         .requestMatchers(HttpMethod.GET, "/api/requests/**").permitAll()
                                 .requestMatchers(HttpMethod.GET, "/api/users/**").permitAll()
                                 .requestMatchers(HttpMethod.GET, "/api/products/*/reservations-by-month").permitAll()
                                 .requestMatchers(HttpMethod.POST, "/api/products").permitAll()
+                        .requestMatchers(HttpMethod.POST, "/dev/**").permitAll()
                                 .requestMatchers(HttpMethod.PUT, "/api/products/**").permitAll()
+                        .requestMatchers("/api/users/*/processed-count").permitAll()
+                        .requestMatchers("/api/users/*/processed-count/reset").permitAll()
+                        .requestMatchers("/api/users/*/status/unseen-count").permitAll()
+                        .requestMatchers("/api/users/*/status/unseen/reset").permitAll()
+                        .requestMatchers("/api/dev/ping-admins").permitAll()
                                 .requestMatchers(HttpMethod.PUT, "/api/requests/**").permitAll()// mora za put eksplicitno da kazam
                         .requestMatchers(
@@ -77,6 +86,10 @@
                                 "/api/uploads/**",
                                 "/stomp/**",
-                                "/uploads/**",
-                                "/api/dev/**"//ova e za da mozat da se zemat slikite
+                                "/uploads/**",//ova e za da mozat da se zemat slikite
+                                "/api/dev/**",
+                                "/ws/**",
+                                "/api/admin/**",
+                                "/dev/**",
+                                "/api/auth/login"
                         ).permitAll()
                         .requestMatchers(HttpMethod.GET, "/api/products/**").permitAll()
Index: src/main/java/com/zinemasterapp/zinemasterapp/security/WebSocketConfig.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/security/WebSocketConfig.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/security/WebSocketConfig.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -1,5 +1,7 @@
 package com.zinemasterapp.zinemasterapp.security;
 
+import com.zinemasterapp.zinemasterapp.security.jwt.JwtChannelInterceptor;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.simp.config.ChannelRegistration;
 import org.springframework.messaging.simp.config.MessageBrokerRegistry;
 import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
@@ -10,4 +12,10 @@
 @EnableWebSocketMessageBroker
 public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
+
+    private final JwtChannelInterceptor jwtChannelInterceptor;
+
+    public WebSocketConfig(JwtChannelInterceptor jwtChannelInterceptor) {
+                this.jwtChannelInterceptor = jwtChannelInterceptor;
+    }
     @Override
     public void registerStompEndpoints(StompEndpointRegistry registry) {
@@ -23,4 +31,9 @@
         config.setUserDestinationPrefix("/user");//za specificni korisnici
     }
+    @Override
+   public void configureClientInboundChannel(ChannelRegistration registration) {
+               registration.interceptors(jwtChannelInterceptor);
+           }
+
 }
 
Index: src/main/java/com/zinemasterapp/zinemasterapp/security/jwt/JwtChannelInterceptor.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/security/jwt/JwtChannelInterceptor.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/security/jwt/JwtChannelInterceptor.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -1,4 +1,5 @@
 package com.zinemasterapp.zinemasterapp.security.jwt;
 
+import io.jsonwebtoken.Claims;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.messaging.Message;
@@ -10,4 +11,6 @@
 import org.springframework.messaging.support.MessageHeaderAccessor;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
@@ -25,14 +28,15 @@
 
     @Override
-    public Message<?> preSend(Message<?> message, MessageChannel channel) {//stom dojde poraka od klientot
-        StompHeaderAccessor acc = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);//da go citame zaglavjeto
-        if (acc != null && StompCommand.CONNECT.equals(acc.getCommand())) {//ako e connect(online e)
-            String auth = acc.getFirstNativeHeader("Authorization"); //tokenot
-            if (auth != null && auth.startsWith("Bearer ")) {
-                String token = auth.substring(7);//trgame bearer
-                String username = jwtService.extractUsername(token);
+    public Message<?> preSend(Message<?> message, MessageChannel channel) {
+        StompHeaderAccessor acc = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);//headerite gi zema, CONNECT,SUBSCRIBE
+        if (acc != null && StompCommand.CONNECT.equals(acc.getCommand())) {//samo na prviot CONNECT
+            String auth = acc.getFirstNativeHeader("Authorization");//dali e avtoriziran
+            if (auth != null && auth.startsWith("Bearer ")) {//ako e
+                String token = auth.substring(7);//tokenot
+                Claims claims = jwtService.getClaims(token);//go citame tokenot
+                String username = claims.getSubject();
                 UserDetails user = userDetailsService.loadUserByUsername(username);
-                UsernamePasswordAuthenticationToken authn = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
-                acc.setUser(authn);//da moze da znaeme koja sesija za koj korisnik e
+                Authentication a = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());//kreirame avtentikaciski korisnik
+                acc.setUser(a);
             }
         }
Index: src/main/java/com/zinemasterapp/zinemasterapp/security/jwt/JwtService.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/security/jwt/JwtService.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/security/jwt/JwtService.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -4,11 +4,18 @@
 import io.jsonwebtoken.security.Keys;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.stereotype.Service;
 
 import java.nio.charset.StandardCharsets;
+import java.security.Key;
 import java.time.Duration;
 import java.time.Instant;
+import java.util.Collection;
 import java.util.Date;
+import java.util.List;
 
 @Service
@@ -25,6 +32,6 @@
         Instant now = Instant.now();//ne e dovolno detailed za so LocalDate
         return Jwts.builder()
-                .setSubject(userId)//unique treba da e
-                .claim("username", username)//claim se kroisti za stavanje vo teloto na tokenot,username:{value}
+                .setSubject(username)//unique treba da e
+                .claim("uid", userId)//claim se kroisti za stavanje vo teloto na tokenot,username:{value}
                 .claim("role", role)
                 .claim("email", email)
@@ -40,10 +47,36 @@
     }
 
+
+    public Authentication parse(String token) {
+        Claims claims = getClaims(token);
+        String username = claims.get("username", String.class);
+        String role = claims.get("role", String.class);
+        var authorities = List.of(new SimpleGrantedAuthority(role));
+        return new UsernamePasswordAuthenticationToken(username, null, authorities);
+    }
+
+
+    private Key getSigningKey() {
+
+        return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
+
+        // return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
+    }
+
+
+
+    public Claims getClaims(String token) {
+        return Jwts.parserBuilder()
+                .setSigningKey(getSigningKey())
+                .build()
+                .parseClaimsJws(token)
+                .getBody();
+    }
+
     public boolean isTokenValid(String token, UserDetails userDetails) {
         try {
-            final String username = extractUsername(token);
-            return username != null
-                    && username.equals(userDetails.getUsername())
-                    && !isExpired(token);
+            Claims claims = getClaims(token);
+            String username = claims.getSubject();
+            return username.equals(userDetails.getUsername()) && !isExpired(claims);
         } catch (JwtException e) {
             return false;
@@ -51,18 +84,6 @@
     }
 
-
-    private boolean isExpired(String token) {
-//        Date exp = getClaims(token).getExpiration();
-//        return exp.before(new Date());
-        return getClaims(token).getExpiration().toInstant()
-                .isBefore(Instant.now());
-    }
-
-    private Claims getClaims(String token) {
-        return Jwts.parserBuilder()
-                .setSigningKey(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)))//klucot
-                .build()//pocnat e parserot
-                .parseClaimsJws(token)//gi zemame site delovi od tokenot
-                .getBody();//go zeame samo teloto
+    private boolean isExpired(Claims claims) {
+        return claims.getExpiration().before(new Date());
     }
 }
Index: src/main/java/com/zinemasterapp/zinemasterapp/security/oauth/OAuth2AuthenticationSuccessHandler.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/security/oauth/OAuth2AuthenticationSuccessHandler.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/security/oauth/OAuth2AuthenticationSuccessHandler.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -78,8 +78,10 @@
         );
 
+        System.out.println("[OAUTH] frontendRedirect=" + frontendRedirect);
 
         String target = UriComponentsBuilder.fromUriString(frontendRedirect)
                 .queryParam("token", jwt)//stavame kveri parametar token=...
                 .build(true).toUriString();
+        System.out.println("[OAUTH] redirecting to " + target);
 
         response.sendRedirect(target);
Index: src/main/java/com/zinemasterapp/zinemasterapp/service/EmailDigestScheduler.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/service/EmailDigestScheduler.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
+++ src/main/java/com/zinemasterapp/zinemasterapp/service/EmailDigestScheduler.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -0,0 +1,65 @@
+package com.zinemasterapp.zinemasterapp.service;
+
+import com.zinemasterapp.zinemasterapp.model.User;
+import com.zinemasterapp.zinemasterapp.repository.UserRepository;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class EmailDigestScheduler {
+
+    private final UserRepository userRepository;
+    private final EmailService emailService;
+    private final NotificationUpdateHelper notificationUpdateHelper;
+    private  final NotificationCounterService notificationCounterService;
+
+    public EmailDigestScheduler(UserRepository userRepository, EmailService emailService, NotificationUpdateHelper notificationUpdateHelper, NotificationCounterService notificationCounterService) {
+        this.userRepository = userRepository;
+        this.emailService = emailService;
+        this.notificationUpdateHelper = notificationUpdateHelper;
+        this.notificationCounterService = notificationCounterService;
+    }
+
+    @Scheduled(cron = "0 0 8-18 * * * ")
+    @Transactional
+    public void sendDigestEmails() {
+        var admins = userRepository.findAdminsWithPending();
+
+        for (User admin : admins) {
+            int count = admin.getPendingEmailCount();
+            if (count == 0) continue;
+
+            String subject = "[ZineMaster] "+ count +" New Requests";
+            String body = "Hello"+admin.getUsername()+",\n\nYou have " + count + " new request"
+                    + (count == 1 ? "" : "s") + " since the last digest. You are receiving this e-mail because you are offline.\n\n"
+                    + "-ZineMaster";
+
+            emailService.sendEmail(admin.getEmail(), subject, body);
+
+            admin.setPendingEmailCount(0);
+            notificationCounterService.resetPending(admin.getId());
+            userRepository.save(admin);
+        }
+    }
+
+
+    @Scheduled(cron = "0 0 8-18 * * * ")
+    public void sendProcessedDigests() {
+        for (var user : userRepository.findAll()) {
+            int n = notificationUpdateHelper.getProcessedCount(user.getId());
+            if (n <= 0) continue;
+
+            String subject = "[ZineMaster] " + n + " of your requests were processed";
+            String body = "Hello " + user.getUsername() + ",\n\n"
+                    + n + " of your requests were processed (approved/rejected).\n"
+                    + "Check your My Requests page for details.\n\n"
+                    + "— ZineMaster";
+
+            emailService.sendEmail(user.getEmail(), subject, body);
+            userRepository.resetProccessedRequests(user.getId());
+        }
+    }
+
+}
+
Index: src/main/java/com/zinemasterapp/zinemasterapp/service/MakerStatusNotifyListener.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/service/MakerStatusNotifyListener.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
+++ src/main/java/com/zinemasterapp/zinemasterapp/service/MakerStatusNotifyListener.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -0,0 +1,99 @@
+package com.zinemasterapp.zinemasterapp.service;
+
+import com.zinemasterapp.zinemasterapp.model.RequestStatusChangedEvent;
+import lombok.RequiredArgsConstructor;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.event.TransactionPhase;
+import org.springframework.transaction.event.TransactionalEventListener;
+
+import java.time.Instant;
+import java.util.Map;
+import java.util.Set;
+
+
+@Component
+@RequiredArgsConstructor
+public class MakerStatusNotifyListener {
+
+    private final SimpMessagingTemplate messaging;
+    private final PresenceService presence;
+    private final NotificationUpdateHelper notification;
+    private final NotificationCounterService notificationCounterService;
+
+
+
+    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
+    public void onStatusChangedone(RequestStatusChangedEvent e) {
+        String maker = e.getMakerUsername();
+        String makerId = e.getMakerId();
+        System.out.println("Listener something");
+
+        var payload = Map.of(
+                "type", "STATUS_CHANGED",
+                "requestId", e.getRequestId(),
+                "newStatus", e.getNewStatus(),
+                "decidedBy", e.getDecidedBy(),
+                "changedAt", e.getChangedAt().toString(),
+                "summary", "Request " + e.getRequestId() + " → " + e.getNewStatus()
+        );
+
+
+
+        if (!presence.isOnline(maker)) {
+            //notification.incrementProcessedRequestsByUserId(makerId);
+            notificationCounterService.bumpOffline(makerId);
+        }else{
+            notificationCounterService.bumpUnseen(makerId);
+        }
+
+    System.out.println("ITS KINDA WORKING");
+
+        messaging.convertAndSendToUser(maker, "/queue/status", payload);
+        System.out.println("Listener");
+        System.out.println("LISTENER IS WORKING");
+
+
+    }
+
+
+    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    public void onStatusChanged(RequestStatusChangedEvent e) {
+        try {
+
+            notificationCounterService.bumpUnseen(e.getMakerUsername());
+
+
+            var payload = java.util.Map.of(
+                    "type","status_changed",
+                    "requestId", e.getRequestId(),
+                    "newStatus", e.getNewStatus(),
+                    "status", e.getNewStatus(),
+                    "to", e.getMakerUsername(),
+                    "decidedBy", e.getDecidedBy(),
+                    "changedAt", e.getChangedAt().toString()
+            );
+
+            if (!presence.isOnline(e.getMakerUsername())) {
+                //notification.incrementProcessedRequestsByUserId(makerId);
+                notificationCounterService.bumpOffline(e.getMakerId());
+            }else{
+                notificationCounterService.bumpUnseen(e.getMakerId());
+            }
+            messaging.convertAndSendToUser(e.getMakerUsername(), "/queue/status", payload);
+
+            System.out.println("After commit, working");
+
+        } catch (Exception ex) {
+
+            System.out.println("It failed, exception " + ex.getMessage());
+        }
+    }
+
+}
+
+
+
Index: src/main/java/com/zinemasterapp/zinemasterapp/service/NotificationCounterService.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/service/NotificationCounterService.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
+++ src/main/java/com/zinemasterapp/zinemasterapp/service/NotificationCounterService.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -0,0 +1,98 @@
+package com.zinemasterapp.zinemasterapp.service;
+
+import com.zinemasterapp.zinemasterapp.repository.UserRepository;
+
+import org.springframework.stereotype.Service;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+
+@Service
+public class NotificationCounterService {
+
+
+    private final UserRepository repo;
+
+
+    public NotificationCounterService(UserRepository repo) {
+        this.repo = repo;
+    }
+
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    public void increment(String userId, String username) {
+        int rows = 0;
+
+        if (userId != null && !userId.isBlank()) {
+            rows = repo.incrementUnread(userId);
+            System.out.println("incrementUnread id=" + userId + " -> rows=" + rows);
+        }
+
+
+        if (rows == 0 && username != null && !username.isBlank()) {
+            rows = repo.incrementUnreadByUsername(username);
+            System.out.println("incrementUnreadByUsername username=" + username + " -> rows=" + rows);
+        }
+
+        if (rows == 0) {
+            System.out.println("Unread NOT incremented username=" + username);
+        }
+    }
+
+    @Transactional(readOnly = true)
+    public int getUnread(String userId) {
+        Integer v = repo.getUnread(userId);
+        return v == null ? 0 : v;
+    }
+
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    public void reset(String userId) {
+        int rows = repo.resetUnread(userId);
+        System.out.println("resetUnread id=" + userId + " -> rows=" + rows);
+    }
+
+    @Transactional
+    public void incUnread(String username) { repo.incrementUnreadByUsername(username);}
+
+    @Transactional
+    public void incPending(String userId) { repo.incrementPendingEmailCount(userId); }
+
+    @Transactional
+    public void transferPendingToUnread(String username) {
+        repo.transferPendingToUnread(username);
+    }
+
+    @Transactional
+    public void resetUnread(String username) {
+        repo.resetUnread(username);
+    }
+
+    @Transactional
+    public void resetPending(String userId) {
+        repo.resetPendingEmail(userId);
+    }
+
+    @Transactional public void bumpOffline(String ownerId) {
+        repo.incrementRequestsProcessed(ownerId);
+    }
+
+    @Transactional public void bumpUnseen(String ownerId) {
+        repo.incUnseenProcessedStatus(ownerId);
+    }
+
+    @Transactional public void transferToUnseen(String ownerId) {
+        repo.transferProcessedToUnseen(ownerId);
+    }
+
+    @Transactional(readOnly = true)
+    public int unseen(String ownerId) { return repo.getUnseenProcessedStatus(ownerId); }
+
+    @Transactional public void resetUnseen(String ownerId) {
+        repo.resetUnseenProcessedStatus(ownerId);
+    }
+
+
+}
+
+
Index: src/main/java/com/zinemasterapp/zinemasterapp/service/NotificationUpdateHelper.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/service/NotificationUpdateHelper.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
+++ src/main/java/com/zinemasterapp/zinemasterapp/service/NotificationUpdateHelper.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -0,0 +1,74 @@
+package com.zinemasterapp.zinemasterapp.service;
+
+import com.zinemasterapp.zinemasterapp.repository.UserRepository;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Service
+public class NotificationUpdateHelper {
+
+
+    private final UserRepository userRepository;
+    private final NotificationCounterService notificationCounterService;
+
+    public NotificationUpdateHelper(UserRepository userRepository,
+                                      NotificationCounterService notificationCounterService) {
+        this.userRepository = userRepository;
+        this.notificationCounterService = notificationCounterService;
+    }
+
+
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    public void incrementUnreadByUsername(String username) {
+
+        notificationCounterService.increment(null, username);
+
+        // userRepository.incrementUnreadByUsername(username);
+    }
+
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    public void incrementUnreadById(String userId) {
+        notificationCounterService.increment(userId, null);
+
+    }
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    public int incrementUnreadByIdOrUsername(String userId, String username) {
+        if (userId != null && !userId.isBlank()) {
+            return userRepository.incrementUnread(userId);
+        } else if (username != null && !username.isBlank()) {
+            return userRepository.incrementUnreadByUsername(username);
+        }
+        return 0;
+    }
+
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    public void incrementPendingEmailById(String userId) {
+        userRepository.incrementPendingEmailCount(userId);
+    }
+
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    public void incrementProcessedRequestsByUserId(String userId) {
+        userRepository.incrementRequestsProcessed(userId);
+    }
+
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    public void resetProcessedCount(String userId) {
+        userRepository.resetProccessedRequests(userId);
+    }
+
+    @Transactional(readOnly = true)
+    public int getProcessedCount(String userId) {
+        Integer n = userRepository.getRequestsProcessed(userId);
+        return n == null ? 0 : n;
+    }
+
+    @Transactional
+    public void resetPending(String username) {
+        userRepository.resetPendingEmail(username);
+    }
+}
Index: src/main/java/com/zinemasterapp/zinemasterapp/service/PresenceService.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/service/PresenceService.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/service/PresenceService.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -1,5 +1,8 @@
 package com.zinemasterapp.zinemasterapp.service;
 
+import com.zinemasterapp.zinemasterapp.model.User;
+import com.zinemasterapp.zinemasterapp.repository.UserRepository;
 import org.springframework.context.event.EventListener;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
 import org.springframework.security.core.Authentication;
 import org.springframework.stereotype.Component;
@@ -7,4 +10,5 @@
 import org.springframework.web.socket.messaging.SessionDisconnectEvent;
 
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -12,22 +16,38 @@
 @Component
 public class PresenceService {
-    private final Set<String> onlineAdmins = ConcurrentHashMap.newKeySet();
+    private final Set<String> onlineUsers = ConcurrentHashMap.newKeySet();
+    private final NotificationCounterService notificationCounterService;
+    private final UserRepository userRepository;
+    private final SimpMessagingTemplate messaging;
+
+    public PresenceService(NotificationCounterService notificationCounterService, UserRepository userRepository, SimpMessagingTemplate messaging) {
+        this.notificationCounterService = notificationCounterService;
+        this.userRepository = userRepository;
+        this.messaging = messaging;
+    }
 
     @EventListener
-    public void onConnect(SessionConnectEvent e) {
-        var user = (Authentication) e.getUser();
-        if (user != null && user.getAuthorities().stream()
-                .anyMatch(a -> a.getAuthority().equals("ROLE_ProductAdministrator"))) {
-            onlineAdmins.add(user.getName());
+    public void onConnect(SessionConnectEvent e) {//sekogas koga nekoj ke se konektira
+        var a = (Authentication) e.getUser();
+        if (a != null)
+        {
+            onlineUsers.add(a.getName());
+            String username = a.getName();
+            notificationCounterService.transferPendingToUnread(username);
+            User u = userRepository.findByUsername(username).orElse(null);
+            notificationCounterService.transferToUnseen(u.getId());
+            System.out.println("DONE "+u.getUsername());
         }
+
     }
 
     @EventListener
     public void onDisconnect(SessionDisconnectEvent e) {
-        var user = (Authentication) e.getUser();
-        if (user != null) onlineAdmins.remove(user.getName());
+        var a = (Authentication) e.getUser();
+        if (a != null) onlineUsers.remove(a.getName());
     }
 
-    public Set<String> getOnlineAdmins() { return Set.copyOf(onlineAdmins); }
+    public boolean isOnline(String username) { return onlineUsers.contains(username); }
+    public Set<String> getOnlineUsers() { return onlineUsers; }
 }
 
Index: src/main/java/com/zinemasterapp/zinemasterapp/service/RequestNotificationService.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/service/RequestNotificationService.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/service/RequestNotificationService.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -9,4 +9,5 @@
 
 import java.util.List;
+import java.util.Map;
 
 @Service
@@ -16,58 +17,52 @@
     private final UserRepository userRepository;
     private final EmailService emailService;
+    private final NotificationCounterService notificationCounterService;
+    private final NotificationUpdateHelper notificationUpdateHelper;
 
     @Value("${app.ui.requests-url-base:http://localhost:8082/requests/}")
     private String requestsUrlBase;
 
-    @Value("${app.mail.from:no-reply@your-domain.com}")
-    private String fromAddress;
-
-    public RequestNotificationService(PresenceService presence, SimpMessagingTemplate messaging, UserRepository userRepository, EmailService emailService) {
+    public RequestNotificationService(PresenceService presence, SimpMessagingTemplate messaging, UserRepository userRepository, EmailService emailService, NotificationCounterService notificationCounterService, NotificationUpdateHelper notificationUpdateHelper) {
         this.presence = presence; this.messaging = messaging;
         this.userRepository = userRepository;
         this.emailService = emailService;
+        this.notificationCounterService = notificationCounterService;
+
+        this.notificationUpdateHelper = notificationUpdateHelper;
     }
 
     public void notifyAdmins(Notificationdto payload) {
-        for (String adminUsername : presence.getOnlineAdmins()) {
+        String actorUsername = payload.getCreatedBy(); // username
+        for (String adminUsername : presence.getOnlineUsers()) {
+            //if (adminUsername.equalsIgnoreCase(actorUsername)) continue;
+            try {
+                notificationCounterService.increment(null, adminUsername);
+            } catch (Exception e) {
+                System.err.println("Failed to update notification counter");
+            }
+
+
             messaging.convertAndSendToUser(adminUsername, "/queue/requests", payload);
+
         }
+
 
         var allAdmins = findAllAdmins();
         for (User admin : allAdmins) {
             String username = admin.getUsername();
-            if (presence.getOnlineAdmins().contains(username)) continue;
-            String to = safeEmail(admin.getEmail());
-            if (to == null) continue;
 
-            String link = (requestsUrlBase.endsWith("/") ? requestsUrlBase : requestsUrlBase + "/");
-            String subject = "New Request " + payload.getRequestId();
-            String body =
-                    "Hello,\n\n" +
-                            "A new request " + payload.getRequestId() + " (" + payload.getItemCount() + " item" +
-                            (payload.getItemCount() == 1 ? "" : "s") + ") was created by " + payload.getCreatedBy() + ".\n" +
-                            "Open: " + link + "\n\n" +
-                            "You are receiving this because you were offline.\n";
+            if (presence.isOnline(username)) continue;
+           // notificationCounterService.increment(admin.getId(),admin.getUsername());//sekako mora brojkata da se zgolemi
+           // notificationUpdateHelper.incrementUnreadById(admin.getId());
 
-            try {
 
-                emailService.sendEmail(to, subject, body);
-            } catch (Exception e) {
+            notificationUpdateHelper.incrementPendingEmailById(admin.getId());
+        }
 
-                System.err.println("Email send failed to " + to + ": " + e.getMessage());
-            }
-        }
     }
     private List<User> findAllAdmins() {
+            return userRepository.findByUserTypeAndAccess("ProductAdministrator", 1);
+    }
 
-            return userRepository.findByUserTypeAndAccess("ProductAdministrator", 1);
-
-    }
-    private static boolean equalsIgnoreCase(String a, String b) {
-        return a != null && b != null && a.equalsIgnoreCase(b);
-    }
-    private static String safeEmail(String e) {
-        return (e != null && !e.isBlank()) ? e : null;
-    }
 }
 
Index: src/main/java/com/zinemasterapp/zinemasterapp/service/RequestService.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/service/RequestService.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
+++ src/main/java/com/zinemasterapp/zinemasterapp/service/RequestService.java	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -0,0 +1,36 @@
+package com.zinemasterapp.zinemasterapp.service;
+
+import com.zinemasterapp.zinemasterapp.model.RequestCreatedEvent;
+import com.zinemasterapp.zinemasterapp.model.RequestStatusChangedEvent;
+import com.zinemasterapp.zinemasterapp.model.User;
+import com.zinemasterapp.zinemasterapp.repository.ProductRepository;
+import com.zinemasterapp.zinemasterapp.repository.ProductRequestRepository;
+import com.zinemasterapp.zinemasterapp.repository.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.Instant;
+
+@Service
+@RequiredArgsConstructor
+public class RequestService {
+
+    private final ApplicationEventPublisher events;
+    private final ProductRequestRepository repo;
+    private final UserRepository userRepo;
+
+    @Transactional(propagation = Propagation.MANDATORY)
+    public void notifyStatusChanged(String requestId, String makerId, String makerUsername, String newStatus, String decidedBy) {
+        events.publishEvent(new RequestStatusChangedEvent(
+                requestId, makerId, makerUsername, newStatus, decidedBy, Instant.now()
+        ));
+    }
+
+
+}
+
+
+
Index: src/main/resources/application.properties
===================================================================
--- src/main/resources/application.properties	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/resources/application.properties	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -28,4 +28,5 @@
 spring.security.oauth2.client.registration.google.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
 spring.security.oauth2.client.provider.google.issuer-uri=https://accounts.google.com
+management.health.mail.enabled=false
 
 app.oauth2.post-login-redirect=http://localhost:8082/oauth2/callback
Index: zinemaster-frontend/api.js
===================================================================
--- zinemaster-frontend/api.js	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ zinemaster-frontend/api.js	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -1,8 +1,16 @@
-import axios from 'axios'
-axios.defaults.baseURL = 'http://localhost:8081'
+import axios from 'axios';
 
-axios.interceptors.request.use(config => {
-    const t = localStorage.getItem('auth_token')
-    if (t) config.headers.Authorization = `Bearer ${t}`
-    return config
-})
+const api = axios.create({
+    baseURL: 'http://localhost:8081',
+    withCredentials: false
+});
+
+
+api.interceptors.request.use((config) => {
+    const token = localStorage.getItem('auth_token');
+    if (token) config.headers.Authorization = `Bearer ${token}`;
+    return config;
+});
+
+export default api;
+
Index: zinemaster-frontend/src/components/AllRequests.vue
===================================================================
--- zinemaster-frontend/src/components/AllRequests.vue	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ zinemaster-frontend/src/components/AllRequests.vue	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -7,8 +7,10 @@
         <router-link to="/main" class="btn btn-outline-light btn-sm me-2">Дома</router-link>
         <router-link to="/requests" class="btn btn-outline-light btn-sm me-2">Сите нарачки</router-link>
+        <RequestBell v-if="user.userType === 'ProductAdministrator'" />
         <button class="btn btn-outline-light btn-sm me-2" @click="logout">Logout</button>
         <router-link to="/profile">
           <img :src="userProfileImage" alt="Profile" class="rounded-circle me-2 " style="width: 35px; height: 35px; object-fit: cover; border: 2px solid white; cursor: pointer;"/>
         </router-link>
+        <StatusBell v-if="user.userType === 'Worker'" />
         <router-link v-if="user.userType === 'UserAdministrator'" to="/manage-users" class="btn btn-outline-warning btn-sm me-2">
           Управувај со корисници
@@ -108,5 +110,5 @@
                         @click="updateStatus(req.id, 'rejected')">Одбиј</button>
               </div>
-              <p v-if="req.status==='pending' && !canProcess(req) && req.username !== user.username" class="text-muted small mt-1">
+              <p v-if="req.status==='pending' && !canProcess(req) && req.username !== user.username && user.userType !== 'Worker'" class="text-muted small mt-1">
                 📌 Прво обработи ги најстарите нарачки.
               </p>
@@ -118,16 +120,27 @@
       </div>
     </div>
-
-
   </div>
 
 
-
 </template>
 
 <script setup>
-import { ref, onMounted, computed } from 'vue';
+import { ref, onMounted, computed, onBeforeUnmount } from 'vue';
 import { useRouter } from 'vue-router';
 import Swal from 'sweetalert2';
+import api from '@/api'
+
+import StatusBell from './StatusBell.vue';
+import { useBell } from './useBell';
+import RequestBell from './RequestBell.vue';
+function getStoredUser() {
+  try {
+    const raw = localStorage.getItem('user')
+    return raw ? JSON.parse(raw) : null
+  } catch { return null }
+}
+const storedUser = getStoredUser()
+const USERNAME_SAFE = (storedUser?.username) || localStorage.getItem('username') || ''
+console.log('[AllRequests] USERNAME_SAFE =', USERNAME_SAFE)
 
 
@@ -148,4 +161,6 @@
 
 
+const { startLiveAcknowledge, stopLiveAcknowledge , markAllRead} = useBell('request', USERNAME_SAFE)
+
 onMounted(async () => {
   const res = await fetch('http://localhost:8081/api/requests');
@@ -168,10 +183,27 @@
   user.value = JSON.parse(stored);
   userProfileImage.value = user.value.profilePic || "https://www.gravatar.com/avatar/?d=mp";
+
+  try {
+    await api.post(`/api/users/${USERNAME_SAFE}/unread/reset`)
+
+  } catch (e) {
+    console.error('Failed to reset unread counter', e)
+
+  }
+
+  startLiveAcknowledge()
+  await markAllRead();
+
 });
 
 const logout = () => {
   localStorage.removeItem('user');
+  localStorage.removeItem('auth_token');
+  localStorage.removeItem('saved');
   router.push('/login');
 };
+onBeforeUnmount(() => {
+  stopLiveAcknowledge()
+})
 
 const oldestPendingDateForOthers = computed(() => {
@@ -184,5 +216,5 @@
 
 const canProcess = (req) => {
-  if (req.status !== 'pending') return true;
+  if (req.status !== 'pending') return false;//vo false
 
   if (req.username === user.value.username) return false;
@@ -251,5 +283,5 @@
   const res = await fetch(`http://localhost:8081/api/requests/${requestId}/status?status=${newStatus}&adminId=${user.value.id}`, {
     method: 'PUT',
-    headers: { 'Content-Type': 'application/json' }
+    headers: { 'Content-Type': 'application/json' }//mora ovie dve da gi pratime
   });
 
Index: zinemaster-frontend/src/components/LoginPage.vue
===================================================================
--- zinemaster-frontend/src/components/LoginPage.vue	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ zinemaster-frontend/src/components/LoginPage.vue	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -11,5 +11,5 @@
             <input
                 type="text"
-                v-model="username"
+                v-model.trim="form.username"
                 class="form-control"
                 id="username" placeholder="Type your username" required />
@@ -23,5 +23,5 @@
             <input
                 :type="showPassword ? 'text' : 'password'"
-                v-model="password"
+                v-model="form.password"
                 class="form-control"
                 id="password" placeholder="Type your password" required />
@@ -58,9 +58,11 @@
 import { useRouter } from 'vue-router';
 
+import { reactive } from 'vue';
+
 
 const loginWithGoogle = async () => {
   try {
 
-    window.location.href = "http://localhost:8081/oauth2/authorization/google"; // goes via proxy(popravi go loso e)
+    window.location.href = "http://localhost:8081/oauth2/authorization/google";
   } catch (err) {
     console.error("Google login failed", err);
@@ -75,16 +77,23 @@
 
 
-const login = async () => {//ova e demek pomoderno,ama i so method block da koristis isto ke e
+const form = reactive({ username: '', password: '' });
+
+async function login() {
+  const payload = {
+    username: form.username?.trim(),
+    password: form.password
+  };
+  console.log('Login payload:', payload);
   try {
-    const res = await axios.post('http://localhost:8081/api/auth/login', {//prakajme POST do kaj so ni e povrzan backendot(JSON format)
-      username: username.value,
-      password: password.value
+    const { data } = await axios.post('http://localhost:8081/api/auth/login', payload, {
+      headers: { 'Content-Type': 'application/json' }
     });
-    localStorage.setItem('user', JSON.stringify(res.data));
-    router.push('/main');
-  } catch (err) {
-    error.value = err.response?.data || 'Login failed.';
+    localStorage.setItem('auth_token', data.token);
+    localStorage.setItem('user', JSON.stringify(data.user));
+    router.replace("/main")
+  } catch (e) {
+    console.log('Login failed body:', e.response?.data);
   }
-};
+}
 </script>
 
Index: zinemaster-frontend/src/components/MainPage.vue
===================================================================
--- zinemaster-frontend/src/components/MainPage.vue	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ zinemaster-frontend/src/components/MainPage.vue	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -7,8 +7,11 @@
           <button class="btn btn-outline-light btn-sm me-2" @click="startRequest">Направи нарачка</button>
           <router-link to="/requests" class="btn btn-outline-light btn-sm me-2">Сите нарачки</router-link>
+          <RequestBell v-if="user.userType === 'ProductAdministrator'" />
           <button class="btn btn-outline-light btn-sm me-2" @click="logout">Logout</button>
           <router-link to="/profile">
-            <img :src="userProfileImage" alt="Profile" class="rounded-circle me-2 " style="width: 35px; height: 35px; object-fit: cover; border: 2px solid white; cursor: pointer;"/>
+            <img :src="userProfileImage" @click="resetProcessedCount" alt="Profile" class="rounded-circle me-2 " style="width: 35px; height: 35px; object-fit: cover; border: 2px solid white; cursor: pointer;"/>
           </router-link>
+
+          <StatusBell v-if="user.userType === 'Worker' " />
           <router-link v-if="user.userType === 'UserAdministrator'" to="/manage-users" class="btn btn-outline-warning btn-sm me-2">
             Управувај со корисници
@@ -180,5 +183,7 @@
 
     </main>
+    <button class="btn btn-sm btn-primary" @click="ping">Ping Admins</button>
   </div>
+
 
 </template>
@@ -188,4 +193,8 @@
 import { useRouter } from 'vue-router';
 import Swal from 'sweetalert2';
+import RequestBell from './RequestBell.vue';
+import StatusBell from './StatusBell.vue';
+import axios from 'axios';
+
 
 const router = useRouter();
@@ -194,6 +203,19 @@
 const userProfileImage = ref("https://www.gravatar.com/avatar/?d=mp");
 console.log(user)
-
-onMounted(() => {
+const processedCount = ref(0)
+const token = localStorage.getItem('auth_token')
+
+
+
+async function ping(){
+  try {
+    await axios.post('http://localhost:8081/api/dev/ping-admins', null,{
+      headers: { Authorization: `Bearer ${token}` }
+    })
+  } catch (e) {
+    console.error('Ping failed:', e?.response?.status, e?.response?.data || e.message)
+  }
+}
+onMounted(async () => {
   const stored = localStorage.getItem('user');
   if (!stored) {
@@ -211,4 +233,6 @@
 const logout = () => {
   localStorage.removeItem('user');
+  localStorage.removeItem('auth_token');
+  localStorage.removeItem('saved');
   router.push('/login');
 };
@@ -222,4 +246,17 @@
 const selectedCategory = ref('');
 
+
+
+async function resetProcessedCount() {
+  if (!user.value?.id) return
+  try {
+    await axios.post(`http://localhost:8081/api/users/${user.value.id}/processed-count/reset`)
+    processedCount.value = 0
+  } catch (e) {
+    console.error("Failed to reset processed count", e)
+  }
+}
+
+
 const startRequest = () => {
   requestItems.value = [];
@@ -233,5 +270,5 @@
   if (!selectedCategory.value) return products.value;
   return products.value.filter(p =>
-      p.categories && p.categories.some(cat => cat.id === selectedCategory.value)
+      p.categories && p.categories.some(cat => cat.id === selectedCategory.value)//barem edna kategorija
   );
 });
Index: zinemaster-frontend/src/components/OAuth2Callback.vue
===================================================================
--- zinemaster-frontend/src/components/OAuth2Callback.vue	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ zinemaster-frontend/src/components/OAuth2Callback.vue	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -9,50 +9,40 @@
 <script setup>
 import { ref, onMounted } from 'vue'
-import { useRouter } from 'vue-router'
-import axios from 'axios'
+import { useRouter, useRoute } from 'vue-router'
+import http from '@/api'
 
 const router = useRouter()
+const route = useRoute()
 const errorMessage = ref(null)
 
 onMounted(async () => {
-  const params = new URLSearchParams(window.location.search)
-  const err = params.get('error')
-  const token = params.get('token')
+  try {
 
-  if (err) {
-    if (err === 'no_account_for_email') {
-      errorMessage.value = 'No account found for this Google email. Please log in with username/password.'
-    } else if (err === 'account_inactive') {
-      errorMessage.value = 'Your account is inactive. Please contact support.'
-    } else if (err === 'domain_not_allowed') {
-      errorMessage.value = 'This Google domain is not allowed.'
-    } else {
-      errorMessage.value = 'Login failed. Please try again.'
+    const tokenFromQuery = route.query.token
+    const tokenFromHash  = new URLSearchParams(window.location.hash.slice(1)).get('token')
+    const token = tokenFromQuery || tokenFromHash
+
+    console.log('[OAuth2Callback] token from URL:', token)
+
+    if (!token) {
+      console.error('No JWT in callback URL; redirecting to login')
+      return router.replace('/login')
     }
-    setTimeout(() => router.replace('/login'), 2500)
-    return
-  }
 
-  if (token) {
+
     localStorage.setItem('auth_token', token)
 
-    try {
-      const res = await axios.get("http://localhost:8081/api/auth/me", {
-        headers: { Authorization: `Bearer ${token}` }
-      })
+    const { data } = await http.get('/api/auth/me')
+    localStorage.setItem('user', JSON.stringify(data))
 
 
-      console.log("my data")
-      console.log(res.data)
-      localStorage.setItem('user', JSON.stringify(res.data))
-      router.replace('/main')
-    } catch (e) {
-      console.error('Fetching user failed:', e)
-      router.replace('/login')
-    }
-  } else {
+    router.replace('/main')
+  } catch (e) {
+    console.error('OAuth2 callback flow failed:', e)
+    localStorage.removeItem('auth_token')
     router.replace('/login')
   }
 })
+
 </script>
 
Index: zinemaster-frontend/src/components/ProfilePage.vue
===================================================================
--- zinemaster-frontend/src/components/ProfilePage.vue	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ zinemaster-frontend/src/components/ProfilePage.vue	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -7,5 +7,7 @@
         <router-link to="/main" class="btn btn-outline-light btn-sm me-2">Дома</router-link>
         <router-link to="/requests" class="btn btn-outline-light btn-sm me-2">Сите нарачки</router-link>
+        <RequestBell v-if="user.userType === 'ProductAdministrator'" />
         <button class="btn btn-outline-light btn-sm me-2" @click="logout">Logout</button>
+        <StatusBell v-if="user.userType === 'Worker' "/>
         <router-link v-if="user.userType === 'UserAdministrator'" to="/manage-users" class="btn btn-outline-warning btn-sm me-2">
           Управувај со корисници
@@ -131,12 +133,38 @@
     </div>
   </div>
+  <button class="btn btn-sm btn-primary" @click="ping">Ping Admins</button>
 
 </template>
 
 <script setup>
-import { ref, onMounted,computed } from 'vue';
+import { ref, onMounted,computed, onBeforeUnmount } from 'vue';
 import { useRouter } from 'vue-router';
 import Swal from 'sweetalert2';
-
+import axios from 'axios';
+import RequestBell from './RequestBell.vue';
+import { useBell } from './useBell';
+import StatusBell from './StatusBell.vue';
+
+const token = localStorage.getItem("auth_token")
+function getStoredUser() {
+  try {
+    const raw = localStorage.getItem('user')
+    return raw ? JSON.parse(raw) : null
+  } catch { return null }
+}
+const storedUser = getStoredUser()
+const USERNAME_SAFE = (storedUser?.username) || localStorage.getItem('username') || ''
+console.log('[ProfilePage] USERNAME_SAFE =', USERNAME_SAFE)
+
+async function ping(){
+  try {
+    await axios.post('http://localhost:8081/api/dev/ping-admins', null,{
+      headers: { Authorization: `Bearer ${token}` }
+    })
+  } catch (e) {
+    console.error('Ping failed:', e?.response?.status, e?.response?.data || e.message)
+  }
+}
+const { startLiveAcknowledge, stopLiveAcknowledge , markAllRead} = useBell('status', USERNAME_SAFE)
 
 const router = useRouter();
@@ -191,5 +219,12 @@
     allUsers.value = await userRes.json();
   }
+
+  startLiveAcknowledge()
+  await markAllRead();
+
 });
+onBeforeUnmount(() => {
+  stopLiveAcknowledge()
+})
 
 
Index: zinemaster-frontend/src/components/RequestBell.vue
===================================================================
--- zinemaster-frontend/src/components/RequestBell.vue	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
+++ zinemaster-frontend/src/components/RequestBell.vue	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -0,0 +1,32 @@
+<template>
+
+     <span v-if="count > 0" class="position-absolute translate-middle badge rounded-pill bg-danger">
+       {{ count }}
+     </span>
+
+
+  <div class="toast-container position-fixed bottom-0 end-0 p-3" >
+    <div v-for="n in items" :key="String(n.id)" class="toast show bg-white text-dark shadow" role="alert" aria-atomic="true">
+      <div class="toast-header">
+        <strong class="me-auto">{{ n.title }}</strong>
+        <small>{{ timeAgo(n.createdAt) }}</small>
+        <button type="button" class="btn-close ms-2 mb-1" @click="dismiss(n.id)"></button>
+      </div>
+      <div class="toast-body">{{ n.summary }}</div>
+    </div>
+  </div>
+
+</template>
+
+<script setup >
+/* eslint-disable no-undef */
+import { useBell } from './useBell.js'
+
+const username = localStorage.getItem('username') || ''
+const { items, count, dismiss, timeAgo } = useBell('request', username)
+</script>
+
+<style scoped>
+.toast, .toast-body { color:#212529; }
+.toast { z-index: 20000; }
+</style>
Index: zinemaster-frontend/src/components/StatusBell.vue
===================================================================
--- zinemaster-frontend/src/components/StatusBell.vue	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
+++ zinemaster-frontend/src/components/StatusBell.vue	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -0,0 +1,35 @@
+<template>
+  <span
+      v-if="count > 0"
+      class="position-absolute translate-middle badge rounded-pill bg-danger">
+    {{ count }}
+  </span>
+
+  <div class="toast-container position-fixed bottom-0 end-0 p-3">
+    <div v-for="n in items" :key="`status-${n.id}`" class="toast show bg-white text-dark shadow" role="alert" aria-atomic="true">
+      <div class="toast-header">
+        <strong class="me-auto">{{ n.title }}</strong>
+        <small>{{ timeAgo(n.createdAt) }}</small>
+        <button type="button" class="btn-close ms-2 mb-1" @click="dismiss(n.id)"></button>
+      </div>
+      <div class="toast-body">{{ n.summary }}</div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { useBell } from './useBell.js'
+
+function getStoredUser() {
+  try { return JSON.parse(localStorage.getItem('user') || 'null') } catch { return null }
+}
+const storedUser = getStoredUser()
+const username = storedUser?.username || localStorage.getItem('username') || ''
+console.log("From StatusBell "+username)
+const { items, count, dismiss, timeAgo } = useBell('status', username)
+</script>
+
+<style scoped>
+.toast, .toast-body { color:#212529; }
+.toast { z-index: 20000; }
+</style>
Index: zinemaster-frontend/src/components/useBell.js
===================================================================
--- zinemaster-frontend/src/components/useBell.js	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
+++ zinemaster-frontend/src/components/useBell.js	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -0,0 +1,161 @@
+import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
+import http from '@/api'
+import { connectWs, addWsListener } from '@/ws'
+
+function getStoredUser() {
+    try { return JSON.parse(localStorage.getItem('user') || 'null') } catch { return null }
+}
+
+const statusToMacedonian = (status) => {
+    switch (status) {
+        case 'approved': return 'Одобрено';
+        case 'pending': return 'Во тек';
+        case 'rejected': return 'Одбиено';
+        default: return status;
+    }
+};
+
+function endpoints(kind, me) {
+    if (kind === 'status') {
+        return {
+            countUrl: `/api/users/${me}/status/unseen-count`,
+            resetUrl: `/api/users/${me}/status/unseen/reset`,
+            parseCount: (data) => Number(data) || 0
+        }
+    }
+    return {
+        countUrl: `/api/users/${me}/counters`,
+        resetUrl: `/api/users/${me}/unread/reset`,
+        parseCount: (data) => Number(data?.unread ?? 0),
+    }
+}
+
+export function useBell(kind /* 'request' | 'status' */, username) {
+    let nextId = 1
+    const list = ref([])
+    const bootUnread = ref(0)
+    const liveUnread = ref(0)
+    const liveAck = ref(false)
+
+    const storedUser = getStoredUser()
+    const me = username || storedUser?.username || localStorage.getItem('username') || ''
+    console.log('[Bell] user =', me)
+
+    const { countUrl, resetUrl } = endpoints(kind, me)
+
+    function pushToast(title, summary, inc = true) {
+        if (!title && !summary) return
+        const n = { id: nextId++, title, summary, createdAt: new Date() }
+        list.value.unshift(n)
+        if (list.value.length > 5) list.value.pop()
+        if (inc) liveUnread.value += 1
+        setTimeout(() => dismiss(n.id), 6000)
+    }
+
+    function dismiss(id) {
+        const i = list.value.findIndex(n => n.id === id)
+        if (i !== -1) list.value.splice(i, 1)
+    }
+
+    function timeAgo(d) {
+        const s = (Date.now() - d.getTime()) / 1000
+        if (s < 60) return 'just now'
+        return `${Math.floor(s / 60)}m ago`
+    }
+
+    let resetTimer = null
+    async function resetUnreadDebounced() {
+        if (!me || resetTimer) return
+        resetTimer = setTimeout(async () => {
+            resetTimer = null
+            await http.post(resetUrl)
+            bootUnread.value = 0
+            liveUnread.value = 0
+        }, 400)
+    }
+
+    async function markAllRead() {
+        if (!me) return
+        await http.post(resetUrl)
+        bootUnread.value = 0
+        liveUnread.value = 0
+    }
+
+    function startLiveAcknowledge() {
+        liveAck.value = true
+        markAllRead()
+    }
+
+    function stopLiveAcknowledge() {
+        liveAck.value = false
+        loadBootCount()
+    }
+
+    const count = computed(() => bootUnread.value + liveUnread.value)
+    const items = computed(() => list.value.filter(n => n && n.id != null))
+
+    async function loadBootCount() {
+        try {
+            if (!me) { bootUnread.value = 0; return }
+            const { data } = await http.get(countUrl)
+            const v = (typeof data === 'number')
+                ? data
+                : (Number(data?.unread ?? data?.unseen ?? 0))
+            bootUnread.value = Number.isFinite(v) ? Number(v) : 0
+        } catch {
+            bootUnread.value = 0
+        }
+    }
+
+    let removeWs = null
+
+    onMounted(async () => {
+        await loadBootCount()
+        connectWs(() => localStorage.getItem('auth_token') || '')
+
+        removeWs = addWsListener({
+            onConnect: async () => { await loadBootCount() },
+            onNewRequest: (payload) => {
+                if (kind !== 'request') return
+                const id = payload?.requestId ?? ''
+                const summary = payload?.summary ?? (id ? `Request #${id}` : 'New request')
+                if (liveAck.value) {
+                    pushToast('New Request', summary, false)
+                    resetUnreadDebounced()
+                } else {
+                    pushToast('New Request', summary)
+                }
+            },
+            onStatusChange: (payload) => {
+                if (kind !== 'status') return
+                if (!payload) return
+                console.log('[WS] status payload:', payload)
+                const recipient =
+                    payload.to ??
+                    payload.makerUsername ??
+                    payload.recipient
+
+                if (recipient && recipient !== me) return
+
+                if (payload.maker && payload.maker !== me) return
+                const id = payload?.requestId ?? ''
+                const st = payload?.newStatus ?? payload?.status ?? ''
+                const summary = id ? `Request #${id} → ${statusToMacedonian(st)}` : st
+                if (liveAck.value) {
+                    pushToast(`Status: ${st}`, summary, false)
+                    resetUnreadDebounced()
+                } else {
+                    pushToast(`Status: ${st}`, summary)
+                }
+            }
+        })
+
+
+        setTimeout(loadBootCount, 500)
+    })
+
+
+    onBeforeUnmount(() => { if (removeWs) removeWs() })
+
+    return { items, count, dismiss, timeAgo, markAllRead, startLiveAcknowledge, stopLiveAcknowledge }
+}
Index: zinemaster-frontend/src/main.js
===================================================================
--- zinemaster-frontend/src/main.js	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ zinemaster-frontend/src/main.js	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -1,2 +1,3 @@
+import http from '@/api'
 import { createApp } from 'vue';
 import App from './App.vue';
@@ -4,5 +5,9 @@
 import 'bootstrap/dist/css/bootstrap.min.css';
 
-createApp(App).use(router).mount('#app');
+
+const app = createApp(App)
+app.config.globalProperties.$http = http
+app.use(router)
+app.mount('#app')
 const params = new URLSearchParams(window.location.search)
 const tok = params.get('token')
Index: zinemaster-frontend/src/ws.js
===================================================================
--- zinemaster-frontend/src/ws.js	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
+++ zinemaster-frontend/src/ws.js	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -0,0 +1,59 @@
+
+import { Client } from '@stomp/stompjs'
+import SockJS from 'sockjs-client'
+
+let client = null
+let listeners = []
+
+export function connectWs(getToken) {
+    if (client?.active) return
+    const tok = getToken();
+    console.log(tok)
+
+    const token = localStorage.getItem('auth_token') ;
+
+    client = new Client({
+        webSocketFactory: () => new SockJS('http://localhost:8081/stomp'),
+        connectHeaders: token ? { Authorization: `Bearer ${token}` } : {},
+        reconnectDelay: 3000,
+        heartbeatIncoming: 20000,
+        heartbeatOutgoing: 20000,
+        debug: () => {}
+    })
+
+    client.onConnect = () => {
+        console.log('[WS] connected')
+
+
+        client.subscribe('/user/queue/requests', (frame) => {
+            let payload
+            try { payload = JSON.parse(frame.body) } catch { return }
+            listeners.forEach(l => l?.onNewRequest?.(payload))
+        })
+
+        client.subscribe('/user/queue/status', (frame) => {
+            let payload
+            try { payload = JSON.parse(frame.body) } catch { return }
+            listeners.forEach(l => l?.onStatusChange?.(payload))
+        })
+
+        listeners.forEach(l => l?.onConnect?.())
+    }
+
+
+    client.onStompError = f => console.error('[WS] STOMP error:', f.headers?.message)
+    client.onWebSocketClose = e => console.warn('[WS] closed:', e?.reason || '')
+
+    client.activate()
+}
+
+export function addWsListener(handlers) {
+    listeners.push(handlers)
+    return () => { listeners = listeners.filter(h => h !== handlers) }
+}
+
+export function disconnectWs() {
+    if (client?.active) client.deactivate()
+    client = null
+    listeners = []
+}
Index: zinemaster-frontend/vue.config.js
===================================================================
--- zinemaster-frontend/vue.config.js	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ zinemaster-frontend/vue.config.js	(revision 157c73048f63a67fef803399cb69ddef6c7f26de)
@@ -4,7 +4,19 @@
   devServer: {
     port: 8082,
+    historyApiFallback: {
+      rewrites: [{ from: /^\/oauth2\/callback$/, to: '/index.html' }]
+    },
     proxy: {
-      '^/api': { target: 'http://localhost:8081', changeOrigin: true, ws: false, timeout: 120000 },
-      '^/oauth2/callback': { bypass: () => true }
+      '/api': {
+        target: 'http://localhost:8081',
+        changeOrigin: true,
+        ws: false,
+        timeout: 120000
+      },
+      '/stomp': {
+        target: 'http://localhost:8081',
+        changeOrigin: true,
+        ws: true
+      }
     }
   }
