Index: jobvista-backend/.gitignore
===================================================================
--- jobvista-backend/.gitignore	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-backend/.gitignore	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -32,2 +32,4 @@
 ### VS Code ###
 .vscode/
+
+.env
Index: jobvista-backend/pom.xml
===================================================================
--- jobvista-backend/pom.xml	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-backend/pom.xml	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -63,6 +63,44 @@
 			<scope>test</scope>
 		</dependency>
+		<!-- other -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-oauth2-client</artifactId>
+		</dependency>
 
-		<!-- other -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-mail</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.security</groupId>
+			<artifactId>spring-security-oauth2-jose</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>com.google.api-client</groupId>
+			<artifactId>google-api-client</artifactId>
+			<version>2.5.1</version>
+		</dependency>
+
+		<!-- https://mvnrepository.com/artifact/com.google.oauth-client/google-oauth-client -->
+		<dependency>
+			<groupId>com.google.oauth-client</groupId>
+			<artifactId>google-oauth-client-jetty</artifactId>
+			<version>1.34.1</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.google.http-client</groupId>
+			<artifactId>google-http-client-jackson2</artifactId>
+			<version>1.32.1</version>
+		</dependency>
+
+
+
+
+
+
 		<!-- https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api -->
 		<dependency>
@@ -71,6 +109,4 @@
 			<version>3.0.2</version>
 		</dependency>
-
-
 
 		<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
Index: jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/JobvistaBackendApplication.java
===================================================================
--- jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/JobvistaBackendApplication.java	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/JobvistaBackendApplication.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -27,6 +27,4 @@
 			admin.setEmail("admin@admin.com");
 			admin.setHasAccess(true);
-//			admin.setName("admin");
-//			admin.setSurname("admin");
 			admin.setPassword(new BCryptPasswordEncoder().encode("admin"));
 			userRepository.save(admin);
Index: jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/config/GoogleOAuth2Properties.java
===================================================================
--- jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/config/GoogleOAuth2Properties.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
+++ jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/config/GoogleOAuth2Properties.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -0,0 +1,28 @@
+package mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "spring.security.oauth2.client.registration.google")
+public class GoogleOAuth2Properties {
+
+    private String clientId;
+    private String clientSecret;
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public void setClientId(String clientId) {
+        this.clientId = clientId;
+    }
+
+    public String getClientSecret() {
+        return clientSecret;
+    }
+
+    public void setClientSecret(String clientSecret) {
+        this.clientSecret = clientSecret;
+    }
+}
Index: jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/config/SecurityConfiguration.java
===================================================================
--- jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/config/SecurityConfiguration.java	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/config/SecurityConfiguration.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -33,8 +33,10 @@
                         .requestMatchers(
                                 "/api/auth/**",
+                                "/oauth2/**",
                                 "/api/job-advertisements/**",
                                 "/api/applications/**",
                                 "/api/recruiter/**",
-                                "/api/job-seeker/**"
+                                "/api/job-seeker/**",
+                                "/uploads/**"
                         ).permitAll()
                         .requestMatchers("/api/admin/**").hasAnyAuthority(Role.ROLE_ADMIN.name())
@@ -46,13 +48,21 @@
                         .requestMatchers("/api/job-advertisements/edit/{id}").hasAnyAuthority(Role.ROLE_RECRUITER.name())
                         .requestMatchers("/api/job-advertisements/delete/{id}").hasAnyAuthority(Role.ROLE_RECRUITER.name())
-                        .requestMatchers("/api/applications/{id}/update").hasAnyAuthority(Role.ROLE_RECRUITER.name())
+                        .requestMatchers("/api/applications/{id}/update").hasAnyAuthority(Role.ROLE_JOBSEEKER.name())
+                        .requestMatchers("/api/applications/update").hasAnyAuthority(Role.ROLE_RECRUITER.name())
+                        .requestMatchers("/uploads/applications/**").hasAnyAuthority(Role.ROLE_RECRUITER.name())
                         .requestMatchers("/api/job-advertisements/{advertisement_id}/applications").hasAnyAuthority(Role.ROLE_RECRUITER.name())
+                        .requestMatchers("/api/job-advertisements/{advertisement_id}/applications/filtered").hasAnyAuthority(Role.ROLE_RECRUITER.name())
                         .requestMatchers("/api/applications/submit").hasAnyAuthority(Role.ROLE_JOBSEEKER.name())
                         .requestMatchers("/api/my-applications/{id}").hasAnyAuthority(Role.ROLE_JOBSEEKER.name())
-                        .anyRequest().authenticated())
+                        .requestMatchers("/api/my-applications/{id}/filtered").hasAnyAuthority(Role.ROLE_JOBSEEKER.name())
+                .anyRequest().authenticated())
                 .sessionManagement(manager -> manager.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                 .authenticationProvider(authenticationProvider()).addFilterBefore(
-                        jwtAuthFilter, UsernamePasswordAuthenticationFilter.class
-                );
+                        jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
+                .oauth2Login(oauth2 -> oauth2
+                        .defaultSuccessUrl("/api/auth/google", true)
+                )
+        ;
+
         return http.build();
     }
Index: jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/config/WebConfig.java
===================================================================
--- jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/config/WebConfig.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
+++ jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/config/WebConfig.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -0,0 +1,14 @@
+package mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        registry.addResourceHandler("/uploads/**")
+                .addResourceLocations("file:D:/Finki-Projects/Internet-tehnologii/jobvista/jobvista-backend/uploads/");
+    }
+}
Index: jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/ApplicationController.java
===================================================================
--- jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/ApplicationController.java	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/ApplicationController.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -30,4 +30,10 @@
     }
 
+    @PostMapping("/my-applications/{id}/filtered")
+    public ResponseEntity<?> filterApplicationsByJobSeekerId(@PathVariable Long id, @RequestBody String status) {
+        List<ApplicationDetailsDTO> applicationList = applicationService.filterByJobSeekerId(id, status);
+        return new ResponseEntity<>(applicationList, HttpStatus.OK);
+    }
+
     @GetMapping("/job-advertisements/{advertisement_id}/applications")
     public ResponseEntity<?> findAllApplicationsByJobAdvertisementId(@PathVariable("advertisement_id") Long advertisementId) {
@@ -36,8 +42,20 @@
     }
 
-    @PostMapping("/applications/{id}/update")
+    @PostMapping("/job-advertisements/{advertisement_id}/applications/filtered")
+    public ResponseEntity<?> filterApplicationsByJobAdvertisementId(@PathVariable("advertisement_id") Long advertisementId, @RequestBody String status) {
+        List<ApplicationDetailsDTO> applicationList = applicationService.filterByJobAdvertisementId(advertisementId, status);
+         return new ResponseEntity<>(applicationList, HttpStatus.OK);
+    }
+
+    @PostMapping("/applications/{id}/update/NOT-IN-USE")
     public ResponseEntity<?> updateApplicationStatus(@PathVariable("id") Long applicaitonId, @RequestBody ApplicationStatusDTO appStatusDTO) {
         ApplicationStatusDTO applicationStatusDTO = applicationService.updateApplicationStatus(applicaitonId,appStatusDTO.getStatus());
         return new ResponseEntity<>(applicationStatusDTO, HttpStatus.OK);
+    }
+
+    @PostMapping("/applications/update")
+    public ResponseEntity<?> updateApplications(@RequestBody List<ApplicationStatusDTO> changes) {
+       List<ApplicationStatusDTO> updatedApplications = applicationService.updateApplications(changes);
+       return new ResponseEntity<>(updatedApplications, HttpStatus.OK);
     }
 
@@ -66,3 +84,18 @@
         return new ResponseEntity<>(applicationDetailsDTO, HttpStatus.OK);
     }
+
+    @PostMapping("/applications/{id}/update")
+    public ResponseEntity<ApplicationDetailsDTO> updateApplication(
+            @PathVariable("id") Long applicationId,
+            @RequestParam("additionalFiles") MultipartFile[] additionalFiles) {
+        ApplicationDetailsDTO applicationDetailsDTO = applicationService.updateApplication(applicationId, additionalFiles);
+        return new ResponseEntity<>(applicationDetailsDTO, HttpStatus.OK);
+    }
+
+    @GetMapping("/applications/{id}/download-additional-files")
+    public ResponseEntity<List<String>> getAdditionalFilesUrls(@PathVariable("id") Long applicationId) {
+        List<String> fileUrls = applicationService.loadAdditionalFilesAsUrls(applicationId);
+        return ResponseEntity.ok(fileUrls);
+    }
+
 }
Index: jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/AuthController.java
===================================================================
--- jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/AuthController.java	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/AuthController.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -7,8 +7,14 @@
 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.mappers.JobSeekerMapper;
 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.mappers.RecruiterMapper;
+
 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.AuthService;
+
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+
 
 @RestController
@@ -42,3 +48,8 @@
         return ResponseEntity.ok(authenticationService.refreshToken(refreshTokenRequest));
     }
+
+    @PostMapping("/google")
+    public ResponseEntity<?> googleSignIn(@RequestBody Map<String, String> token) {
+        return ResponseEntity.ok(authenticationService.googleSignIn(token));
+    }
 }
Index: jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/applications/Application.java
===================================================================
--- jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/applications/Application.java	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/applications/Application.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -9,8 +9,7 @@
 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.job_advertisements.JobAdvertisement;
 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.JobSeeker;
-import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.User;
 
 import java.time.LocalDateTime;
-import java.util.HashMap;
+import java.util.ArrayList;
 import java.util.List;
 
@@ -45,4 +44,9 @@
     private ApplicationStatus status;
 
+    private String response;
+
+    @ElementCollection
+    private List<String> additionalFilePaths;
+
     public Application(JobSeeker jobSeeker, JobAdvertisement jobAdvertisement, List<String> answers, String message) {
         this.jobSeeker = jobSeeker;
@@ -53,4 +57,6 @@
         submittedOn = LocalDateTime.now();
         this.status = ApplicationStatus.PROPOSED;
+        this.response = "";
+        this.additionalFilePaths = new ArrayList<>();
     }
 
@@ -72,5 +78,7 @@
                 application.getMessage(),
                 application.getSubmittedOn(),
-                application.getStatus().name()
+                application.getStatus().name(),
+                application.getResponse(),
+                application.getAdditionalFilePaths()
         );
     }
Index: jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/applications/DTO/ApplicationDetailsDTO.java
===================================================================
--- jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/applications/DTO/ApplicationDetailsDTO.java	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/applications/DTO/ApplicationDetailsDTO.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -29,3 +29,5 @@
     private LocalDateTime submittedOn;
     private String status;
+    private String response;
+    private List<String> additionalFileNames;
 }
Index: jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/applications/DTO/ApplicationStatusDTO.java
===================================================================
--- jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/applications/DTO/ApplicationStatusDTO.java	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/applications/DTO/ApplicationStatusDTO.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -9,3 +9,4 @@
     Long id;
     String status;
+    String response;
 }
Index: jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/repositories/JobSeekerRepository.java
===================================================================
--- jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/repositories/JobSeekerRepository.java	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/repositories/JobSeekerRepository.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -4,4 +4,7 @@
 import org.springframework.data.jpa.repository.JpaRepository;
 
+import java.util.Optional;
+
 public interface JobSeekerRepository extends JpaRepository<JobSeeker, Long> {
+    Optional<JobSeeker> findByEmail(String email);
 }
Index: jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/ApplicationServiceImpl.java
===================================================================
--- jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/ApplicationServiceImpl.java	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/ApplicationServiceImpl.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -6,4 +6,5 @@
 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.JobSeeker;
 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.repositories.JobSeekerRepository;
+import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.EmailSenderService;
 import org.springframework.core.io.Resource;
 import org.springframework.core.io.UrlResource;
@@ -19,6 +20,9 @@
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
 
 import java.io.IOException;
+import java.net.MalformedURLException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -39,4 +43,7 @@
 
     @Autowired
+    private EmailSenderService emailSenderService;
+
+    @Autowired
     public ApplicationServiceImpl(@Value("${file.upload-dir}") String uploadDir, UserRepository userRepository, ApplicationRepository applicationRepository, JobAdvertisementRepository jobAdvertisementRepository,
                                   JobSeekerRepository jobSeekerRepository) {
@@ -95,4 +102,38 @@
 
     @Override
+    public ApplicationDetailsDTO updateApplication(Long applicationId, MultipartFile[] additionalFiles) {
+        Application application = applicationRepository.findById(applicationId).orElse(null);
+        if(application== null) {
+            throw new RuntimeException("Application not found.");
+        }
+
+        for (MultipartFile additionalFile : additionalFiles) {
+            if (additionalFile.isEmpty()) {
+                throw new RuntimeException("Failed to store empty file.");
+            }
+        }
+
+        Path filesPath = this.fileStorageLocation.resolve(String.valueOf(application.getId())).resolve("additional_files");
+        for(MultipartFile additionalFile: additionalFiles) {
+            Path targetLocation = filesPath.resolve(additionalFile.getOriginalFilename());
+
+            try {
+                Files.createDirectories(filesPath);
+                Files.copy(additionalFile.getInputStream(), targetLocation);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+
+            String relativePath = Paths.get("uploads","applications",String.valueOf(application.getId()),
+                    "additional_files", additionalFile.getOriginalFilename()).toString();
+            List<String> currentAdditionalFilePaths = application.getAdditionalFilePaths();
+            currentAdditionalFilePaths.add(relativePath);
+            application.setAdditionalFilePaths(currentAdditionalFilePaths);
+            application = applicationRepository.save(application);
+        }
+        return Application.mapToApplicationDetailsDTO(application);
+    }
+
+    @Override
     public List<ApplicationDetailsDTO> findAllByJobAdvertisementId(Long jobId) {
         List<Application> applications =  applicationRepository.findAllByJobAdvertisementId(jobId);
@@ -101,7 +142,32 @@
 
     @Override
+    public List<ApplicationDetailsDTO> filterByJobAdvertisementId(Long jobId, String status) {
+        List<Application> applications =  applicationRepository.findAllByJobAdvertisementId(jobId);
+        String statusTrimmed = status.subSequence(0, status.length()-1).toString();
+
+        if(statusTrimmed.equals("ALL")) {
+            applications =  applicationRepository.findAllByJobAdvertisementId(jobId);
+        } else {
+            applications = applications.stream().filter(application -> application.getStatus().name().equals(statusTrimmed)).toList();
+        }
+        return applications.stream().map(Application::mapToApplicationDetailsDTO).toList();
+    }
+
+    @Override
     public List<ApplicationDetailsDTO> findAllByJobSeekerId(Long jobSeekerId) {
        List<Application> applications = applicationRepository.findAllByJobSeekerId(jobSeekerId);
        return applications.stream().map(Application::mapToApplicationDetailsDTO).toList();
+    }
+
+    @Override
+    public List<ApplicationDetailsDTO> filterByJobSeekerId(Long jobSeekerId, String status) {
+        List<Application> applications = applicationRepository.findAllByJobSeekerId(jobSeekerId);
+        String statusTrimmed = status.subSequence(0, status.length()-1).toString();
+        if(statusTrimmed.equals("ALL")) {
+            applications =  applicationRepository.findAllByJobSeekerId(jobSeekerId);
+        } else {
+            applications = applications.stream().filter(application -> application.getStatus().name().equals(statusTrimmed)).toList();
+        }
+        return applications.stream().map(Application::mapToApplicationDetailsDTO).toList();
     }
 
@@ -126,4 +192,92 @@
     }
 
+    public List<String> loadAdditionalFilesAsUrls(Long applicationId) {
+        Application application = applicationRepository.findById(applicationId)
+                .orElseThrow(() -> new IllegalArgumentException("Application not found"));
+
+        List<String> fileUrls = new ArrayList<>();
+        List<String> relativeFilePaths = application.getAdditionalFilePaths();
+
+        for (String relativeFilePath : relativeFilePaths) {
+            //TO DO: refactor
+            Path filePath = Paths.get(fileStorageLocation.getParent().getParent().toString(), relativeFilePath).normalize();
+            String relativePath = filePath.toString().replace("\\", "/").replaceFirst("^.+uploads", "uploads");
+
+            String fileUrl = ServletUriComponentsBuilder.fromCurrentContextPath()
+                    .path("/")
+                    .path(relativePath)
+                    .toUriString();
+            fileUrls.add(fileUrl);
+        }
+
+        return fileUrls;
+    }
+
+   /* @Override
+    public List<Resource> loadAdditionalFilesAsZippedResource(Long applicationId) {
+        Application application = applicationRepository.findById(applicationId).
+                orElseThrow(() -> new IllegalArgumentException("Application not found"));
+
+        List<Resource> resources = new ArrayList<>();
+
+        List<String> relativeFilePaths = application.getAdditionalFilePaths();
+        for(String relativeFilePath: relativeFilePaths) {
+            Path filePath = fileStorageLocation.getParent().getParent().resolve(relativeFilePath).normalize();
+
+            try {
+                Resource resource = new UrlResource(filePath.toUri());
+                if (resource.exists()) {
+                    resources.add(resource);
+                }
+            } catch (MalformedURLException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return resources;
+    }*/
+
+    @Override
+    public List<ApplicationStatusDTO> updateApplications(List<ApplicationStatusDTO> updates) {
+        List<ApplicationStatusDTO> updatedApplications = new ArrayList<>();
+
+        for(ApplicationStatusDTO applicationStatusDTO : updates) {
+            Application application = applicationRepository.findById(applicationStatusDTO.getId()).orElse(null);
+            if(application != null) {
+                application.setStatus(ApplicationStatus.valueOf(applicationStatusDTO.getStatus()));
+                application.setResponse(applicationStatusDTO.getResponse());
+                applicationRepository.save(application);
+                updatedApplications.add(applicationStatusDTO);
+
+                //email notification
+                String email = application.getJobSeeker().getEmail();
+                String subject = application.getJobAdvertisement().getRecruiter().getName() + ": " + application.getJobAdvertisement().getTitle() + " - STATUS UPDATE";
+                String text = "Dear " + application.getJobSeeker().getName() + ",\n\n";
+
+                switch (applicationStatusDTO.getStatus()) {
+                    case "ACCEPTED":
+                        text += "Great news! Your application has been accepted.\n\n";
+                        break;
+                    case "DENIED":
+                        text += "We regret to inform you that your application has been denied. We appreciate your interest and effort.\n\n";
+                        break;
+                    case "PROPOSED":
+                        text += "Your application status has been updated to 'Proposed'. We're considering your application for the next phase.\n\n";
+                        break;
+                    case "UNDER_REVIEW":
+                        text += "Your application is currently under review.\n\n";
+                        break;
+                }
+
+
+                if(!applicationStatusDTO.getResponse().isEmpty()) {
+                    text += "Response: " + applicationStatusDTO.getResponse() + "\n\n";
+                }
+               text += "Thank you.";
+           emailSenderService.sendEmail(email, subject, text);
+            }
+        }
+        return updatedApplications;
+    }
+
     @Override
     public ApplicationStatusDTO updateApplicationStatus(Long id, String status) {
@@ -132,5 +286,5 @@
        application.setStatus(ApplicationStatus.valueOf(status));
        applicationRepository.save(application);
-       return new ApplicationStatusDTO(id, status);
+       return new ApplicationStatusDTO(id, status, "");
     }
 }
Index: jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/AuthServiceImpl.java
===================================================================
--- jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/AuthServiceImpl.java	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/AuthServiceImpl.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -1,5 +1,12 @@
 package mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.impl;
 
+import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
+import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
+import com.google.api.client.http.javanet.NetHttpTransport;
+import com.google.api.client.json.jackson2.JacksonFactory;
 import lombok.RequiredArgsConstructor;
+import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.config.GoogleOAuth2Properties;
+import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.controllers.AuthController;
+import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.enumerations.Role;
 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.DTO.SignInDTO;
 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.DTO.JwtAuthResponse;
@@ -12,12 +19,30 @@
 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.repositories.UserRepository;
 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.AuthService;
+import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.JobSeekerService;
 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.JwtService;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
+import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
+import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
+import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
+import org.springframework.security.oauth2.core.user.OAuth2User;
 import org.springframework.stereotype.Service;
-
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
 import java.time.LocalDateTime;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
 
 @Service
@@ -30,5 +55,7 @@
     private final AuthenticationManager authenticationManager;
     private final UserRepository userRepository;
+    private final JobSeekerService jobSeekerService;
     private final JwtService jwtService;
+    private final GoogleOAuth2Properties googleOAuth2Properties;
 
     @Override
@@ -56,9 +83,9 @@
         return new JwtAuthResponse(user.getId(), user.getEmail(), user.getName(), user.getRole().name(), user.isHasAccess(), jwt, refreshJwt);
     }
-    
+
     public JwtAuthResponse refreshToken(RefreshTokenRequest refreshTokenRequest) {
         String userEmail = jwtService.extractUsername(refreshTokenRequest.getToken());
         User user = userRepository.findByEmail(userEmail).orElseThrow();
-        if(jwtService.isTokenValid(refreshTokenRequest.getToken(), user)) {
+        if (jwtService.isTokenValid(refreshTokenRequest.getToken(), user)) {
             String jwt = jwtService.generateToken(user);
 
@@ -67,3 +94,157 @@
         return null;
     }
+
+    @Override
+    public JwtAuthResponse googleSignIn(Map<String, String> token) {
+        OAuth2AuthenticationToken authentication = getAuthentication(token.get("tokenId"));
+
+        OAuth2User oAuth2User = authentication.getPrincipal();
+        String email = oAuth2User.getAttribute("email");
+
+        JobSeeker jobSeeker = jobSeekerRepository.findByEmail(email)
+                .orElseGet(() -> {
+                    JobSeeker newJobSeeker = new JobSeeker();
+                    newJobSeeker.setEmail(email);
+                    newJobSeeker.setFirstName(oAuth2User.getAttribute("given_name"));
+                    newJobSeeker.setLastName(oAuth2User.getAttribute("family_name"));
+                    newJobSeeker.setPassword("");
+                    newJobSeeker.setRole(Role.ROLE_JOBSEEKER);
+                    newJobSeeker.setHasAccess(true);
+                    jobSeekerRepository.save(newJobSeeker);
+
+                    String googleProfilePicUrl = oAuth2User.getAttribute("picture");
+                    submitGoogleProfilePic(newJobSeeker.getId(), googleProfilePicUrl);
+
+                    return newJobSeeker;
+                });
+
+        String jwt = jwtService.generateToken(jobSeeker);
+
+        return new JwtAuthResponse(
+                jobSeeker.getId(),
+                jobSeeker.getEmail(),
+                jobSeeker.getFirstName() + " " + jobSeeker.getLastName(),
+                jobSeeker.getRole().name(),
+                jobSeeker.isHasAccess(),
+                jwt,
+                null
+        );
+    }
+
+    public OAuth2AuthenticationToken getAuthentication(String tokenId) {
+        try {
+            GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), new JacksonFactory())
+                    .setAudience(Collections.singletonList(googleOAuth2Properties.getClientId()))
+                    .build();
+
+            GoogleIdToken idToken = verifier.verify(tokenId);
+            if (idToken != null) {
+                GoogleIdToken.Payload payload = idToken.getPayload();
+
+                String userId = payload.getSubject();
+                String email = payload.getEmail();
+                boolean emailVerified = Boolean.TRUE.equals(payload.getEmailVerified());
+                String name = (String) payload.get("name");
+                String pictureUrl = (String) payload.get("picture");
+                String familyName = Optional.ofNullable((String) payload.get("family_name")).orElse("");
+                String givenName = (String) payload.get("given_name");
+
+                Map<String, Object> attributes = Map.of(
+                        "sub", userId,
+                        "email", email,
+                        "email_verified", emailVerified,
+                        "name", name,
+                        "picture", pictureUrl,
+                        "family_name", familyName,
+                        "given_name", givenName
+                );
+
+                OAuth2User oAuth2User = new DefaultOAuth2User(
+                        Collections.singleton(new SimpleGrantedAuthority("ROLE_JOBSEEKER")),
+                        attributes,
+                        "sub"
+                );
+
+                return new OAuth2AuthenticationToken(oAuth2User, oAuth2User.getAuthorities(), "google");
+            } else {
+                throw new IllegalArgumentException("Invalid ID token");
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to verify token", e);
+        }
+    }
+
+    public void submitGoogleProfilePic(Long jobSeekerId, String googleProfilePicUrl) {
+        try {
+            URL url = new URL(googleProfilePicUrl);
+            BufferedImage image = ImageIO.read(url);
+
+            // Convert BufferedImage to byte array
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ImageIO.write(image, "jpg", baos);
+            byte[] imageBytes = baos.toByteArray();
+
+            // Convert byte array to MultipartFile
+            MultipartFile multipartFile = new InMemoryMultipartFile("profilePicFile", "google-profile-pic.jpg", "image/jpeg", imageBytes);
+
+            jobSeekerService.submitProfilePic(jobSeekerId, multipartFile);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    class InMemoryMultipartFile implements MultipartFile {
+
+        private final String name;
+        private final String originalFilename;
+        private final String contentType;
+        private final byte[] content;
+
+        public InMemoryMultipartFile(String name, String originalFilename, String contentType, byte[] content) {
+            this.name = name;
+            this.originalFilename = originalFilename;
+            this.contentType = contentType;
+            this.content = content;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String getOriginalFilename() {
+            return originalFilename;
+        }
+
+        @Override
+        public String getContentType() {
+            return contentType;
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return content.length == 0;
+        }
+
+        @Override
+        public long getSize() {
+            return content.length;
+        }
+
+        @Override
+        public byte[] getBytes() throws IOException {
+            return content;
+        }
+
+        @Override
+        public InputStream getInputStream() throws IOException {
+            return new ByteArrayInputStream(content);
+        }
+
+        @Override
+        public void transferTo(java.io.File dest) throws IOException {
+            throw new UnsupportedOperationException("This method is not implemented");
+        }
+    }
 }
Index: jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/EmailSenderServiceImpl.java
===================================================================
--- jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/EmailSenderServiceImpl.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
+++ jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/EmailSenderServiceImpl.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -0,0 +1,33 @@
+package mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.impl;
+
+import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.EmailSenderService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.mail.SimpleMailMessage;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.stereotype.Service;
+
+@Service
+public class EmailSenderServiceImpl implements EmailSenderService {
+
+    @Autowired
+    private JavaMailSender mailSender;
+
+    @Value("${custom.mail.sender.email}")
+    private String senderEmail;
+
+    @Value("${custom.mail.sender.name}")
+    private String senderName;
+
+    @Override
+    public void sendEmail(String to, String subject, String text) {
+        SimpleMailMessage message = new SimpleMailMessage();
+        message.setTo(to);
+        message.setSubject(subject);
+        message.setText(text);
+
+        message.setFrom(String.format("%s <%s>", senderName, senderEmail));
+
+        mailSender.send(message);
+    }
+}
Index: jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/ApplicationService.java
===================================================================
--- jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/ApplicationService.java	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/ApplicationService.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -7,4 +7,5 @@
 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.applications.DTO.ApplicationStatusDTO;
 import org.springframework.core.io.Resource;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.util.List;
@@ -12,7 +13,12 @@
 public interface ApplicationService {
     ApplicationDetailsDTO submitApplication(ApplicationDTO applicationDTO);
+    ApplicationDetailsDTO updateApplication(Long applicationId, MultipartFile[] additionalFiles);
     List<ApplicationDetailsDTO> findAllByJobAdvertisementId(Long jobId);
+    List<ApplicationDetailsDTO> filterByJobAdvertisementId(Long jobId, String status);
     List<ApplicationDetailsDTO> findAllByJobSeekerId(Long jobSeekerId);
+    List<ApplicationDetailsDTO> filterByJobSeekerId(Long jobSeekerId, String status);
     Resource loadResumeAsResource(Long applicationId);
+    List<String> loadAdditionalFilesAsUrls(Long applicationId);
     ApplicationStatusDTO updateApplicationStatus(Long id, String status);
+    List<ApplicationStatusDTO> updateApplications(List<ApplicationStatusDTO> updates);
 }
Index: jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/AuthService.java
===================================================================
--- jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/AuthService.java	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/AuthService.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -7,4 +7,7 @@
 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.Recruiter;
 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.User;
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
+
+import java.util.Map;
 
 public interface AuthService {
@@ -13,3 +16,7 @@
     JwtAuthResponse signIn(SignInDTO signInDTO);
     JwtAuthResponse refreshToken(RefreshTokenRequest refreshTokenRequest);
+
+    JwtAuthResponse googleSignIn(Map<String, String> token);
+    OAuth2AuthenticationToken getAuthentication(String tokenId);
+    void submitGoogleProfilePic(Long jobSeekerId, String googleProfilePicUrl);
 }
Index: jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/EmailSenderService.java
===================================================================
--- jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/EmailSenderService.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
+++ jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/EmailSenderService.java	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -0,0 +1,5 @@
+package mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef;
+
+public interface EmailSenderService {
+    void sendEmail(String to, String subject, String text);
+}
Index: jobvista-backend/src/main/resources/application.properties
===================================================================
--- jobvista-backend/src/main/resources/application.properties	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-backend/src/main/resources/application.properties	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -2,7 +2,7 @@
 
 spring.datasource.driver-class-name=org.postgresql.Driver
-spring.datasource.url=jdbc:postgresql://localhost:5432/jobvistaDB
-spring.datasource.username=postgres
-spring.datasource.password=postgres
+spring.datasource.url=${db_url}
+spring.datasource.username=${db_username}
+spring.datasource.password=${db_password}
 
 #spring.jpa.hibernate.ddl-auto=create-drop
@@ -12,7 +12,27 @@
 spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
 
-file.upload-dir=./uploads
+file.upload-dir=${file_upload_dir}
 
 spring.servlet.multipart.enabled=true
 spring.servlet.multipart.max-file-size=2MB
 spring.servlet.multipart.max-request-size=2MB
+
+spring.security.oauth2.client.registration.google.client-id=${google_id}
+spring.security.oauth2.client.registration.google.client-secret=${google_secret}
+spring.security.oauth2.client.registration.google.scope=profile, email
+spring.security.oauth2.client.registration.google.redirect-uri=http://localhost:3000/login/oauth2/code/google
+spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/auth
+spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token
+spring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo
+spring.security.oauth2.client.provider.google.user-name-attribute=sub
+
+spring.mail.host=smtp.gmail.com
+spring.mail.port=587
+spring.mail.username=${mail_username}
+spring.mail.password=${mail_password}
+custom.mail.sender.email=${mail_sender_email}
+custom.mail.sender.name=${mail_sender_name}
+spring.mail.properties.mail.smtp.auth=true
+spring.mail.properties.mail.smtp.starttls.enable=true
+
+
Index: jobvista-frontend/.gitignore
===================================================================
--- jobvista-frontend/.gitignore	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/.gitignore	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -22,2 +22,4 @@
 yarn-debug.log*
 yarn-error.log*
+
+.env
Index: jobvista-frontend/package-lock.json
===================================================================
--- jobvista-frontend/package-lock.json	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/package-lock.json	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -13,4 +13,5 @@
         "@hookform/resolvers": "^3.3.4",
         "@mui/material": "^5.15.17",
+        "@react-oauth/google": "^0.12.1",
         "@reduxjs/toolkit": "^2.2.3",
         "@testing-library/jest-dom": "^5.17.0",
@@ -3789,4 +3790,13 @@
       }
     },
+    "node_modules/@react-oauth/google": {
+      "version": "0.12.1",
+      "resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.12.1.tgz",
+      "integrity": "sha512-qagsy22t+7UdkYAiT5ZhfM4StXi9PPNvw0zuwNmabrWyMKddczMtBIOARflbaIj+wHiQjnMAsZmzsUYuXeyoSg==",
+      "peerDependencies": {
+        "react": ">=16.8.0",
+        "react-dom": ">=16.8.0"
+      }
+    },
     "node_modules/@reduxjs/toolkit": {
       "version": "2.2.3",
Index: jobvista-frontend/package.json
===================================================================
--- jobvista-frontend/package.json	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/package.json	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -8,4 +8,5 @@
     "@hookform/resolvers": "^3.3.4",
     "@mui/material": "^5.15.17",
+    "@react-oauth/google": "^0.12.1",
     "@reduxjs/toolkit": "^2.2.3",
     "@testing-library/jest-dom": "^5.17.0",
Index: jobvista-frontend/public/index.html
===================================================================
--- jobvista-frontend/public/index.html	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/public/index.html	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -32,4 +32,7 @@
     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" />
     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" />
+
+<!--    GOOGLE-->
+    <script src="https://accounts.google.com/gsi/client" async defer></script>
     <title>Job Vista</title>
   </head>
Index: jobvista-frontend/src/App.css
===================================================================
--- jobvista-frontend/src/App.css	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/App.css	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -38,8 +38,15 @@
 
 .form-container {
+  font-family: "Segoe UI";
   background-color: white;
   border-radius: 10px;
   padding: 15px 30px;
-  margin-top: 80px;
+  margin-top: 40px;
+}
+
+
+.form-container h5, .form-container h3 {
+  text-align:center;
+  font-family: Poppins
 }
 
@@ -217,6 +224,10 @@
 }
 
-
-
-
-
+.card-company-logo {
+  border-radius: 15%
+}
+
+
+
+
+
Index: jobvista-frontend/src/redux/actionTypes.js
===================================================================
--- jobvista-frontend/src/redux/actionTypes.js	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/redux/actionTypes.js	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -13,7 +13,11 @@
 export const FILTER_JOB_ADVERTISEMENTS_BY_RECRUITER = "FILTER_JOB_ADVERTISEMENTS_BY_RECRUITER"
 export const SUBMIT_APPLICATION = "SUBMIT_APPLICATION"
+export const UPDATE_APPLICATION = "UPDATE_APPLICATION"
 export const UPDATE_APPLICATION_STATUS = "UPDATE_APPLICATION_STATUS"
+export const UPDATE_APPLICATIONS = "UPDATE_APPLICATIONS"
 export const FETCH_APPLICATIONS_BY_JOB_ID = "FETCH_APPLICATIONS_BY_JOB_ID"
+export const FILTER_APPLICATIONS_BY_JOB_ID = "FILTER_APPLICATIONS_BY_JOB_ID"
 export const FETCH_APPLICATIONS_BY_JOB_SEEKER_ID = "FETCH_APPLICATIONS_BY_JOB_SEEKER_ID"
+export const FILTER_APPLICATIONS_BY_JOB_SEEKER_ID = "FILTER_APPLICATIONS_BY_JOB_SEEKER_ID"
 export const DOWNLOAD_RESUME = "DOWNLOAD_RESUME"
 
Index: jobvista-frontend/src/redux/actions/applicationActions.js
===================================================================
--- jobvista-frontend/src/redux/actions/applicationActions.js	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/redux/actions/applicationActions.js	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -3,6 +3,6 @@
     CURRENT_USER,
     FETCH_APPLICATIONS_BY_JOB_ID,
-    FETCH_APPLICATIONS_BY_JOB_SEEKER_ID,
-    SUBMIT_APPLICATION, UPDATE_APPLICATION_STATUS
+    FETCH_APPLICATIONS_BY_JOB_SEEKER_ID, FILTER_APPLICATIONS_BY_JOB_ID, FILTER_APPLICATIONS_BY_JOB_SEEKER_ID,
+    SUBMIT_APPLICATION, UPDATE_APPLICATION, UPDATE_APPLICATION_STATUS, UPDATE_APPLICATIONS
 } from "../actionTypes";
 
@@ -18,4 +18,25 @@
                     dispatch({
                         type: SUBMIT_APPLICATION,
+                        application: response.data
+                    })
+                    callback(true, response)
+                }).catch(error => {
+                callback(false, error)
+                console.log(error)
+            })
+        }
+    },
+
+    updateApplication: (applicationId, additionalFiles, callback) => {
+        console.log(additionalFiles)
+        return dispatch => {
+            axios.post("/applications/"+ applicationId + "/update", additionalFiles, {
+                headers: {
+                    'Content-Type': 'multipart/form-data'
+                }
+            })
+                .then(response => {
+                    dispatch({
+                        type: UPDATE_APPLICATION,
                         application: response.data
                     })
@@ -45,4 +66,20 @@
         }
     },
+
+    updateApplications: (changes, callback) => {
+        return dispatch => {
+            axios.post("/applications/update", changes)
+                .then(response => {
+                    dispatch({
+                        type: UPDATE_APPLICATIONS,
+                        applications: response.data
+                    })
+                    callback(true, response)
+                }).catch(error => {
+                callback(false, error)
+            })
+        }
+    },
+
     fetchApplicationsByJobSeeker: (jobSeekerId, callback) => {
         return dispatch => {
@@ -51,4 +88,19 @@
                     dispatch({
                         type: FETCH_APPLICATIONS_BY_JOB_SEEKER_ID,
+                        applicationsByJobSeeker: response.data
+                    })
+                    callback(true, response)
+                }).catch(error => {
+                callback(false, error)
+            })
+        }
+    },
+
+    filterApplicationsByJobSeeker: (jobSeekerId, status, callback) => {
+        return dispatch => {
+            axios.post("/my-applications/" + jobSeekerId + "/filtered", status)
+                .then(response => {
+                    dispatch({
+                        type: FILTER_APPLICATIONS_BY_JOB_SEEKER_ID,
                         applicationsByJobSeeker: response.data
                     })
@@ -75,4 +127,20 @@
         }
     },
+
+    filterApplicationsByJobAdId: (jobAdId, status, callback) => {
+        return dispatch => {
+            axios.post("/job-advertisements/" + jobAdId + "/applications/filtered", status)
+                .then(response => {
+                        dispatch({
+                            type: FILTER_APPLICATIONS_BY_JOB_ID,
+                            applicationsByJobAdId: response.data
+                        })
+                        callback(true, response)
+                    }
+                ).catch(error => {
+                callback(false, error)
+            })
+        }
+    },
     downloadResume: (id, callback) => {
         return axios.get("/applications/" + id + "/download-resume", {responseType: "blob"})
@@ -86,4 +154,15 @@
             })
 
+    },
+    downloadAdditionalFiles: (id, callback) => {
+        return axios.get("/applications/" + id + "/download-additional-files")
+            .then(response => {
+                const urls = response.data; // This will be a list of URLs
+                callback(true, urls);
+            })
+            .catch(error => {
+                callback(false, error);
+            });
+
     }
 }
Index: jobvista-frontend/src/redux/actions/authActions.js
===================================================================
--- jobvista-frontend/src/redux/actions/authActions.js	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/redux/actions/authActions.js	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -32,4 +32,30 @@
             }).catch((error) => {
                 callback(false, error);
+            });
+        };
+    },
+
+    signInGoogle: (tokenId, callback) => {
+        return dispatch => {
+            axios.post("/auth/google", { tokenId })
+                .then(jwtResponse => {
+                    const response = jwtResponse.data;
+                    const token = response.token;
+                    const user = {
+                        name: response.name,
+                        role: response.role,
+                        access: response.hasAccess,
+                        id: response.id,
+                    };
+                    dispatch({
+                        type: SIGN_IN,
+                        payload: {
+                            token,
+                            user
+                        }
+                    });
+                    callback && callback(true);
+                }).catch(error => {
+                callback && callback(false, error);
             });
         };
Index: jobvista-frontend/src/redux/reducers/applicationReducer.js
===================================================================
--- jobvista-frontend/src/redux/reducers/applicationReducer.js	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/redux/reducers/applicationReducer.js	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -2,6 +2,6 @@
     CURRENT_USER,
     FETCH_APPLICATIONS_BY_JOB_ID,
-    FETCH_APPLICATIONS_BY_JOB_SEEKER_ID,
-    SUBMIT_APPLICATION, UPDATE_APPLICATION_STATUS
+    FETCH_APPLICATIONS_BY_JOB_SEEKER_ID, FILTER_APPLICATIONS_BY_JOB_ID, FILTER_APPLICATIONS_BY_JOB_SEEKER_ID,
+    SUBMIT_APPLICATION, UPDATE_APPLICATION, UPDATE_APPLICATION_STATUS
 } from "../actionTypes";
 
@@ -22,4 +22,13 @@
                 applicationsByJobSeeker: [...state.applicationsByJobSeeker, action.application]
             }
+        case UPDATE_APPLICATION:
+            return {
+                ...state,
+                applicationsByJobSeeker: state.applicationsByJobSeeker.map(application =>
+                    application.id === action.application.id ?
+                        action.application : // Replace with the updated application
+                        application // Keep the old one
+                )
+            }
         case UPDATE_APPLICATION_STATUS:
             return {
@@ -36,5 +45,16 @@
                 applicationsByJobAdId: action.applicationsByJobAdId
             }
+        case FILTER_APPLICATIONS_BY_JOB_ID:
+            return {
+                ...state,
+                applicationsByJobAdId: action.applicationsByJobAdId
+            }
+
         case FETCH_APPLICATIONS_BY_JOB_SEEKER_ID:
+            return {
+                ...state,
+                applicationsByJobSeeker: action.applicationsByJobSeeker
+            }
+        case FILTER_APPLICATIONS_BY_JOB_SEEKER_ID:
             return {
                 ...state,
Index: jobvista-frontend/src/utils/toastUtils.js
===================================================================
--- jobvista-frontend/src/utils/toastUtils.js	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/utils/toastUtils.js	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -108,5 +108,5 @@
     toast.success(
         <span>
-            Status updated successfully!
+            Application/s updated successfully!
         </span>, {
             position: "bottom-right",
@@ -126,4 +126,21 @@
         <span>
             Your application was successfully sent!
+        </span>, {
+            position: "bottom-right",
+            autoClose: 5000,
+            hideProgressBar: false,
+            closeOnClick: true,
+            pauseOnHover: false,
+            draggable: true,
+            progress: undefined,
+            theme: "dark",
+            pauseOnFocusLoss: false
+        });
+}
+
+export const notifyJobAdUpdate= () => {
+    toast.success(
+        <span>
+            Your application was successfully updated!
         </span>, {
             position: "bottom-right",
Index: jobvista-frontend/src/views/applications/ApplicationDetailsModal.js
===================================================================
--- jobvista-frontend/src/views/applications/ApplicationDetailsModal.js	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/views/applications/ApplicationDetailsModal.js	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -17,4 +17,5 @@
 import Roles from "../../enumerations/Roles";
 import {ApplicationActions} from "../../redux/actions/applicationActions";
+import {notifyJobAdApply, notifyJobAdUpdate} from "../../utils/toastUtils";
 
 
@@ -25,9 +26,12 @@
     const auth = useSelector(state => state.auth.currentUser)
     const [resumeUrl, setResumeUrl] = useState("");
+    const [additionalFileUrls, setAdditionalFileUrls] = useState([]);
 
-    //const [resumeFile, setResumeFile] = useState(null);
+    const [additionalFiles, setAdditionalFiles] = useState(null);
     const toggleModal = () => {
         setModal(!modal);
     };
+
+    const {register, handleSubmit, control, formState: {errors}} = useForm();
 
     useEffect(() => {
@@ -36,8 +40,36 @@
                 if (success) {
                     setResumeUrl(response);
+
+                    if (application.additionalFileNames.length > 0) {
+                        ApplicationActions.downloadAdditionalFiles(application.id, (success2, response) => {
+                            if (success2) {
+                                setAdditionalFileUrls(response);
+                            }
+                        })
+                    }
                 }
             })
         }
     }, [])
+
+    const updateApplication = async () => {
+        try {
+            const formData = new FormData();
+            if (additionalFiles && additionalFiles.length > 0) {
+                for (let i = 0; i < additionalFiles.length; i++) {
+                    formData.append('additionalFiles', additionalFiles[i]);
+                }
+            }
+
+            dispatch(ApplicationActions.updateApplication(application.id, formData, (success) => {
+                if (success) {
+                    toggleModal()
+                    window.location.reload()
+                }
+            }))
+        } catch (err) {
+            console.error(err)
+        }
+    }
 
     function getFileName(path) {
@@ -50,5 +82,9 @@
 
     return (<div className="modal-wrap">
-        <button onClick={toggleModal} className="application-button">View application</button>
+        {auth.role === Roles.RECRUITER ? <button onClick={toggleModal} className="application-button">View
+            application</button> : (application.status === "UNDER_REVIEW" && application.response.length > 0 && additionalFileUrls.length === 0) ?
+            <button onClick={toggleModal} className="application-button">Update application</button> :
+            <button onClick={toggleModal} className="application-button">View application</button>}
+
         <Modal open={modal} onClose={toggleModal} center>
             <div className="head-modal">
@@ -58,37 +94,82 @@
 
             <div className="modal-content">
-                <form>
+                <form onSubmit={handleSubmit(updateApplication)}>
                     <div className="row">
-                        <div className="col-md-6">
-                            <label className="label">Why are you interested in joining our company?</label>
-                            <textarea disabled type="text" defaultValue={application.questionAnswers[0]} disabled
-                                      placeholder="Write your answer here..." className="application-textarea"/>
-                            <br/><br/>
-                            <label className="label">What makes you a good fit for this position?</label>
-                            <textarea disabled type="text" defaultValue={application.questionAnswers[1]}
-                                      placeholder="Write your answer here..." className="application-textarea"/>
-                            <br/><br/>
-                            <label className="label">What do you hope to achieve in your first 6 months in this
-                                role?</label>
-                            <textarea disabled type="text" defaultValue={application.questionAnswers[2]}
-                                      placeholder="Write your answer here..." className="application-textarea"/>
+                        <div className="col-md-6 d-flex flex-column gap-4">
+                            <div>
+                                <label className="label">Why are you interested in joining our company?</label>
+                                <textarea disabled type="text" defaultValue={application.questionAnswers[0]} disabled
+                                          placeholder="Write your answer here..." className="application-textarea"/>
+                            </div>
+
+                            <div>
+                                <label className="label">What makes you a good fit for this position?</label>
+                                <textarea disabled type="text" defaultValue={application.questionAnswers[1]}
+                                          placeholder="Write your answer here..." className="application-textarea"/>
+                            </div>
+
+                            <div>
+                                <label className="label">What do you hope to achieve in your first 6 months in this
+                                    role?</label>
+                                <textarea disabled type="text" defaultValue={application.questionAnswers[2]}
+                                          placeholder="Write your answer here..." className="application-textarea"/>
+                            </div>
+
 
                         </div>
-                        <div className="col-md-6">
-                            <label htmlFor="start">Curriculum vitae (CV)</label>
-                            <br/>
-                            <a className="resume-link" href={resumeUrl} target="_blank"
-                               rel="noopener noreferrer">{getFileName(application.fileName)}</a>
-                            <br/>
+                        <div className="col-md-6 d-flex flex-column gap-4">
+                            <div>
+                                <label className="label" htmlFor="start">Curriculum vitae (CV)</label>
 
-                            <br/>
-                            <label className="label">Message to the recruiter</label>
-                            <textarea disabled type="text" defaultValue={application.message} placeholder="Optional..."
-                                      className="application-textarea"/>
+                                <a className="resume-link" href={resumeUrl} target="_blank"
+                                   rel="noopener noreferrer">{getFileName(application.fileName)}</a>
+                            </div>
+
+                            <div>
+                                <label className="label">Message to the recruiter</label>
+                                <textarea disabled type="text" defaultValue={application.message}
+                                          placeholder="Optional..."
+                                          className="application-textarea"/>
+                            </div>
+
+
+                            {additionalFileUrls.length > 0 ? (<div>
+                                    <label className="label" htmlFor="start">Additional documents</label>
+                                    <ul style={{listStyleType: "none", padding: 0, margin: 0}}>
+                                        {additionalFileUrls.map((url, index) => (
+                                            <li style={{marginBottom: 10}} key={index}>
+                                                <a href={url} className="resume-link" download target="_blank">
+                                                    {getFileName(url)}
+                                                </a>
+                                            </li>))}
+                                    </ul>
+                                </div>) : (<div>
+                                    {(application.status === "UNDER_REVIEW" && application.response.length > 0 && auth.role == Roles.JOBSEEKER) &&
+                                        <div>
+                                            <label className="label" htmlFor="start">Additional documents</label>
+                                            <input
+                                                className="resume-link"
+                                                onChange={(e) => setAdditionalFiles(e.target.files)}
+                                                required type="file"
+                                                id="fileUpload"
+                                                accept=".pdf"
+                                                multiple
+                                            />
+                                        </div>}
+                                </div>
+                            )}
+
 
                         </div>
                     </div>
+                    {(additionalFileUrls.length  === 0 && application.status === "UNDER_REVIEW" && application.response.length > 0 && auth.role == Roles.JOBSEEKER) &&
+                        <div className="modal-buttons">
+                            <div className="cancel-btn" onClick={toggleModal}> Cancel</div>
+                            <button className="submit-btn"> Submit</button>
+                        </div>}
 
                 </form>
+
+
             </div>
         </Modal>
Index: jobvista-frontend/src/views/applications/Applications.css
===================================================================
--- jobvista-frontend/src/views/applications/Applications.css	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/views/applications/Applications.css	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -17,8 +17,51 @@
 }
 
-/*.application-title span {*/
-/*    font-weight: bold;*/
-/*    */
-/*}*/
+.response-message {
+    background-color: floralwhite;
+    border-radius: 8px;
+    padding: 15px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+
+
+
+.application-card-wrapper {
+    margin: 15px 0;
+    display: flex;
+    flex-direction: column;
+    transition: all 0.4s ease-in-out;
+    gap: 3px;
+}
+
+.application-card.changed {
+    background-color: aliceblue;
+}
+
+.application-card-wrapper .expand-section {
+    max-height: 0;
+    opacity: 0;
+    transition: 0.5s ease;
+    display: flex;
+    flex-direction: column;
+    align-items: flex-end;
+    margin: 0 !important;
+}
+
+.application-card-wrapper.expanded .expand-section {
+    max-height: 200px;
+    opacity: 1;
+    transform: translateY(0);
+    margin-top: 10px;
+  /*  transition: max-height 0.3s ease, opacity 0.3s ease, transform 0.3s ease;*/
+}
+
+.expand-section textarea {
+    width: 100%;
+    padding: 10px;
+    border-radius: 8px;
+    border: 1px solid #ccc;
+}
+
 
 .application-card {
@@ -29,11 +72,7 @@
     transition: all 0.3s ease;
     box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-    height: auto;
+    /*height: auto;*/
     padding: 20px 20px;
     display: flex;
-    /*justify-content: center;*/
-    /*gap: 20px;*/
-    margin: 15px 0;
-    /*z-index: -1000;*/
 }
 .application-card .app-job-seeker-pic {
@@ -51,5 +90,5 @@
 
 .application-card .app-info {
-    width: 65%;
+    width: 60%;
     display: inline-flex;
     flex-direction: column;
@@ -86,5 +125,5 @@
 
 .application-card .app-status {
-    width: 28%;
+    width: 38%;
     display: inline-flex;
     justify-content: end;
@@ -98,5 +137,5 @@
 }
 
-.status {
+.app-status .status {
     color: white;
     padding: 5px 10px;
@@ -105,4 +144,10 @@
     text-align: center;
 }
+
+
+
+
+
+
 
 
@@ -168,2 +213,43 @@
     transform: rotate(-10deg);
 }
+
+.application-filters {
+    gap: 15px;
+    border-radius: 8px;
+    background-color: white;
+}
+
+/* Default Span Styles */
+.application-filters span {
+    padding: 8px 12px;
+    border-radius: 5px;
+    color: rgba(1,38,90,0.9);
+    cursor: pointer;
+    display: inline-flex;
+    align-items: center;
+}
+
+/* Icon Styles */
+.application-filters span i {
+    margin-right: 5px;
+}
+
+/* Hover Effect */
+.application-filters span:hover {
+    background-color: rgba(1,38,90,0.9);
+    color: white;
+}
+
+.application-filters span:hover i {
+    color: inherit; /* Makes the icon inherit the color from the parent span */
+}
+
+/* Selected State */
+.application-filters span.selected {
+    background-color: rgba(1,38,90,0.9);
+    color: white;
+}
+
+.application-filters span.selected i {
+    color: inherit; /* Ensures icon color matches the selected state */
+}
Index: jobvista-frontend/src/views/applications/ApplicationsByJobAd.js
===================================================================
--- jobvista-frontend/src/views/applications/ApplicationsByJobAd.js	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/views/applications/ApplicationsByJobAd.js	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -25,6 +25,8 @@
     const [jobAdTitle, setJobAdTitle] = useState("");
 
+    const [changedApplications, setChangedApplications] = useState({});
+
     useEffect(() => {
-        if(!dispatched && (applicationsByJobAdState.length === 0 || applicationsByJobAdState.length === 1)) {
+        if(!dispatched) {
             dispatch(ApplicationActions.fetchApplicationsByJobAdId(advertisement_id, (success, reponse) => {
                 if (success && reponse.data.length > 0) {
@@ -60,4 +62,5 @@
     }, [dispatched])
 
+
     const fetchProfilePic = (jobSeekerId) => {
         dispatch(JobSeekerActions.downloadProfilePic(jobSeekerId, (success, response) => {
@@ -67,5 +70,4 @@
         }))
     }
-
 
     const options = [
@@ -76,16 +78,100 @@
     ];
 
+    const [selectedFilter, setSelectedFilter] = useState('All');
+
+    const filters = [
+        { value: 'ALL', label: 'All', icon: 'fa-folder-open' },
+        { value: 'PROPOSED', label: 'Proposed', icon: 'fa-paper-plane' },
+        { value: 'UNDER_REVIEW', label: 'Under Review', icon: 'fa-file-pen' },
+        { value: 'ACCEPTED', label: 'Accepted', icon: 'fa-user-check' },
+        { value: 'DENIED', label: 'Denied', icon: 'fa-user-slash' },
+    ];
+
+    const filterApplicationsByJobAdvertisement = (filter) => {
+        dispatch(ApplicationActions.filterApplicationsByJobAdId(advertisement_id, filter, (success, response) => {
+            if(success) {
+                //notify
+            }
+        }))
+    }
+
     let handleDefaultStatus = (status) => {
         return options.find(option => option.value === status);
     }
 
-    let handleChangeStatus = (selectedOption, id) => {
-        dispatch(ApplicationActions.updateApplicationStatus(id, selectedOption.value, (success, response) => {
+    let handleStatusChange = (selectedOption, id) => {
+
+        const currentApplication = applicationsByJobAd.find(app => app.id === id);
+
+        setChangedApplications(prevState => ({
+            ...prevState,
+            [id]: {
+                ...prevState[id],
+                response: (selectedOption.value === "ACCEPTED" || selectedOption.value === "DENIED") ? (prevState[id]?.response || currentApplication.response || "") : "",
+                status: selectedOption.value
+            }
+        }))
+
+       setApplicationsByJobAd(prevState => (
+           prevState.map(application =>
+           application.id === id ? {...application, status: selectedOption.value} : application)
+       ))
+
+        const responseTextarea = document.getElementById(`response-${id}`);
+        if (responseTextarea) {
+            responseTextarea.value = "";
+        }
+
+       /* dispatch(ApplicationActions.updateApplicationStatus(id, selectedOption.value, (success, response) => {
             if(success) {
-                // console.log("Status updated.")
                 notifyAppStatusUpdate()
             }
-        }))
-    }
+        }))*/
+    }
+
+    let handleResponseChange = (e, id) => {
+
+        const currentApplication = applicationsByJobAd.find(app => app.id === id);
+
+        setChangedApplications(prevState => ({
+                ...prevState,
+                [id]: {
+                    ...prevState[id],
+                    response: e.target.value,
+                    status: prevState[id]?.status || currentApplication.status,
+                }
+            }
+        ))
+    }
+
+    const handleSaveChanges = () => {
+        console.log(changedApplications)
+       const changes = Object.entries(changedApplications).map(
+           ([applicationId, change]) => ({
+               id: applicationId,
+               status: change.status,
+               response: change.response,
+           })
+       );
+        console.log(changes)
+
+       if(changes.length === 0) {
+           return;
+       }
+
+       dispatch(ApplicationActions.updateApplications(changes, (success, response) => {
+           if(success) {
+               setChangedApplications({});
+               notifyAppStatusUpdate()
+               //notify change success
+           }
+       }))
+
+
+    }
+
+    const isChangedApplication = (id) => {
+        return changedApplications && Object.keys(changedApplications).includes(id.toString());
+    };
 
 
@@ -98,40 +184,85 @@
         </div>
 
+        <div className="row">
+            <div className="col-md-6 application-filters-wrap">
+                <div className="application-filters d-inline-flex flex-row justify-content-start">
+                    {
+                        filters.map(filter => (
+                            <span
+                                key={filter.label}
+                                className={selectedFilter === filter.label ? "selected" : ""}
+                                onClick={() => {
+                                    setSelectedFilter(filter.label)
+                                    filterApplicationsByJobAdvertisement(filter.value)
+                                    setChangedApplications({});
+                                }}
+                            ><i className={`fa-solid ${filter.icon}`}></i> {filter.label}</span>
+                        ))
+                    }
+                </div>
+
+            </div>
+            <div className="col-md-6 d-inline-flex flex-row justify-content-end">
+                <button onClick={handleSaveChanges}
+                        className={`blue-submit-button ${Object.keys(changedApplications).length === 0 ? 'disabled' : ''}`}
+                        disabled={Object.keys(changedApplications).length === 0}
+                >Submit Changes</button>
+            </div>
+        </div>
+
+
+
         {applicationsByJobAd && applicationsByJobAd.map((application, index) => (
-            <div className="application-card">
-                <div className="app-job-seeker-pic">
-                    <img
-                        // loading gif
-                        src={profilePicsState[application.jobSeekerId]}
-                        alt=""
-                        width={75} height={75}
-                    />
-                </div>
-                <div className="app-info">
-                    <span>Submitted on <b>{new Date(application.submittedOn).toLocaleString('default', {
-                        day: 'numeric',
-                        month: 'long',
-                        year: 'numeric'
-                    })}</b></span>
-                    <div className="contact-info">
-                        <div className="contact-item">
-                            <i className="fa-solid fa-user"></i> <span>{application.jobSeekerName}</span>
-                        </div> <div className="contact-item">
-                            <i className="fa-solid fa-envelope"></i> <span>{application.jobSeekerEmail}</span>
-                        </div> <div className="contact-item">
-                            <i className="fa-solid fa-phone"></i> <span>{application.jobSeekerPhoneNumber}</span>
+            <div
+                key={application.id}
+                className={`application-card-wrapper ${(application.status !== "PROPOSED" ) ? 'expanded' : ''}`}
+            >
+
+                <div className={`application-card ${changedApplications[application.id] ? 'changed' : ''}`}>
+                    <div className="app-job-seeker-pic">
+                        <img
+                            src={profilePicsState[application.jobSeekerId]}
+                            alt=""
+                            width={75} height={75}
+                        />
+                    </div>
+                    <div className="app-info">
+                <span>Submitted on <b>{new Date(application.submittedOn).toLocaleString('default', {
+                    day: 'numeric',
+                    month: 'long',
+                    year: 'numeric'
+                })}</b></span>
+                        <div className="contact-info">
+                            <div className="contact-item">
+                                <i className="fa-solid fa-user"></i> <span>{application.jobSeekerName}</span>
+                            </div>
+                            <div className="contact-item">
+                                <i className="fa-solid fa-envelope"></i> <span>{application.jobSeekerEmail}</span>
+                            </div>
+                            <div className="contact-item">
+                                <i className="fa-solid fa-phone"></i> <span>{application.jobSeekerPhoneNumber}</span>
+                            </div>
+                        </div>
+                    </div>
+
+                    <div className="app-status">
+                        <ApplicationDetailsModal application={application} />
+                        <div className="select">
+                            <Select options={options} onChange={(selectedOption) => handleStatusChange(selectedOption, application.id)} defaultValue={handleDefaultStatus(application.status)} />
                         </div>
                     </div>
                 </div>
 
-                <div className="app-status">
-                    <ApplicationDetailsModal application={application}/>
-                    <div className="select">
-                        <Select options={options}  onChange={(selectedOption) => handleChangeStatus(selectedOption, application.id)} defaultValue={handleDefaultStatus(application.status)}/>
-                    </div>
-
+                <div className="expand-section">
+                <textarea
+                    id={`response-${application.id}`}
+                    placeholder={application.status === "UNDER_REVIEW" ? "Request additional documents..." :"Write your response..."}
+                    defaultValue={application.response}
+                    onChange={(e) => handleResponseChange(e, application.id)}
+                />
                 </div>
             </div>
-       ))}
+        ))}
+
 
     </div>)
Index: jobvista-frontend/src/views/applications/ApplicationsByJobSeeker.js
===================================================================
--- jobvista-frontend/src/views/applications/ApplicationsByJobSeeker.js	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/views/applications/ApplicationsByJobSeeker.js	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -22,9 +22,8 @@
 
 
-
     useEffect(() => {
-        if(!dispatched && (applicationsByJobSeekerState.length === 0 || applicationsByJobSeekerState.length === 1) ) {
+        if (!dispatched && (applicationsByJobSeekerState.length === 0 || applicationsByJobSeekerState.length === 1)) {
             dispatch(ApplicationActions.fetchApplicationsByJobSeeker(auth.id, (success, response) => {
-                if(success && response.data.length > 0) {
+                if (success && response.data.length > 0) {
                     setApplicationsByJobSeeker(sortElementsBy(response.data, "submittedOn"));
                 }
@@ -41,7 +40,7 @@
     useEffect(() => {
 
-        if(dispatched && !logoDispatched) {
+        if (dispatched && !logoDispatched) {
             applicationsByJobSeeker.forEach(jobAd => {
-                if(jobAd.recruiterId && !logos[jobAd.recruiterId]) {
+                if (jobAd.recruiterId && !logos[jobAd.recruiterId]) {
                     fetchLogo(jobAd.recruiterId);
                 }
@@ -49,5 +48,5 @@
             setLogoDispatched(true)
             console.log("Fetch all logos GET")
-        } else if (logoDispatched){
+        } else if (logoDispatched) {
             setLogos(logosState)
             console.log("Fetch all logos STATE")
@@ -58,8 +57,7 @@
 
 
-
     const fetchLogo = (recruiterId) => {
         dispatch(RecruiterActions.downloadLogo(recruiterId, (success, response) => {
-            if(success) {
+            if (success) {
                 setLogos(prevLogos => ({...prevLogos, [recruiterId]: response}))
             }
@@ -67,10 +65,35 @@
     };
 
-    const options = [
-        {value: 'PROPOSED', label: <span className="status" style={{backgroundColor: '#4A90E2'}}><i className="fa-solid fa-paper-plane"></i> Proposed</span>},
-        {value: 'UNDER_REVIEW', label: <span className="status" style={{backgroundColor: '#F5A623'}}><i className="fa-solid fa-file-pen"></i> Under Review</span>},
-        {value: 'ACCEPTED', label: <span className="status" style={{backgroundColor: '#7ED321'}}><i className="fa-solid fa-user-check"></i> Accepted</span>},
-        {value: 'DENIED', label: <span className="status" style={{backgroundColor: '#D0021B'}}><i className="fa-solid fa-user-slash"></i> Denied</span>}
+    const options = [{
+        value: 'PROPOSED', label: <span className="status" style={{backgroundColor: '#4A90E2'}}><i
+            className="fa-solid fa-paper-plane"></i> Proposed</span>
+    }, {
+        value: 'UNDER_REVIEW', label: <span className="status" style={{backgroundColor: '#F5A623'}}><i
+            className="fa-solid fa-file-pen"></i> Under Review</span>
+    }, {
+        value: 'ACCEPTED', label: <span className="status" style={{backgroundColor: '#7ED321'}}><i
+            className="fa-solid fa-user-check"></i> Accepted</span>
+    }, {
+        value: 'DENIED', label: <span className="status" style={{backgroundColor: '#D0021B'}}><i
+            className="fa-solid fa-user-slash"></i> Denied</span>
+    }];
+
+    const [selectedFilter, setSelectedFilter] = useState('All');
+
+    const filters = [
+        { value: 'ALL', label: 'All', icon: 'fa-folder-open' },
+        { value: 'PROPOSED', label: 'Proposed', icon: 'fa-paper-plane' },
+        { value: 'UNDER_REVIEW', label: 'Under Review', icon: 'fa-file-pen' },
+        { value: 'ACCEPTED', label: 'Accepted', icon: 'fa-user-check' },
+        { value: 'DENIED', label: 'Denied', icon: 'fa-user-slash' },
     ];
+
+    const filterApplicationsByJobSeeker= (filter) => {
+        dispatch(ApplicationActions.filterApplicationsByJobSeeker(auth.id, filter, (success, response) => {
+            if(success) {
+                //notify
+            }
+        }))
+    }
 
     let handleDefaultValue = (status) => {
@@ -79,55 +102,77 @@
 
 
-
-    return (
-        <div className="custom-container">
+    return (<div className="custom-container">
 
             <div className="application-title">
                 <h3>Application history</h3>
             </div>
+
+        <div className="application-filters d-inline-flex flex-row justify-content-start">
+            {
+                filters.map(filter => (
+                    <span
+                        key={filter.label}
+                        className={selectedFilter === filter.label ? "selected" : ""}
+                        onClick={() => {
+                            setSelectedFilter(filter.label)
+                            filterApplicationsByJobSeeker(filter.value)
+                        }}
+                    ><i className={`fa-solid ${filter.icon}`}></i> {filter.label}</span>
+                ))
+            }
+        </div>
+
             {applicationsByJobSeeker && applicationsByJobSeeker.map((application, index) => (
-                <div key={index} className="application-card">
-                    <div className="app-company-logo">
-                        <img
-                            // loading gif
-                            src={logosState[application.recruiterId]}
-                            alt=""
-                            width={75} height={75}
-                        />
-                    </div>
+                <div className="application-card-wrapper">
+                    <div key={index} className="application-card">
+                        <div className="app-company-logo">
+                            <img
+                                // loading gif
+                                src={logosState[application.recruiterId]}
+                                alt=""
+                                width={75} height={75}
+                            />
+                        </div>
 
-                    <div className="app-info">
-                        <Link to={`/job-advertisements/${application.jobAdId}`} className="jobAd-title">{application.jobAdTitle}</Link>
-                        {/*<h5 className="jobAd-title"></h5>*/}
-                        <div className="contact-info">
-                            <div className="contact-item">
-                                <i className="fa-solid fa-building"></i> <span>{application.recruiterName}</span>
+                        <div className="app-info">
+                            <Link to={`/job-advertisements/${application.jobAdId}`}
+                                  className="jobAd-title">{application.jobAdTitle}</Link>
+                            {/*<h5 className="jobAd-title"></h5>*/}
+                            <div className="contact-info">
+                                <div className="contact-item">
+                                    <i className="fa-solid fa-building"></i> <span>{application.recruiterName}</span>
+                                </div>
+                                <div className="contact-item">
+                                    <i className="fa-solid fa-envelope"></i> <span>{application.recruiterEmail}</span>
+                                </div>
+                                <div className="contact-item">
+                                    <i className="fa-solid fa-phone"></i>
+                                    <span>{application.recruiterPhoneNumber}</span>
+                                </div>
+                                <span> • Submitted on <b>{new Date(application.submittedOn).toLocaleString('default', {
+                                    day: 'numeric', month: 'long', year: 'numeric'
+                                })}</b></span>
                             </div>
-                            <div className="contact-item">
-                                <i className="fa-solid fa-envelope"></i> <span>{application.recruiterEmail}</span>
-                            </div>
-                            <div className="contact-item">
-                                <i className="fa-solid fa-phone"></i> <span>{application.recruiterPhoneNumber}</span>
-                            </div>
-                            <span> • Submitted on <b>{new Date(application.submittedOn).toLocaleString('default', {
-                                day: 'numeric',
-                                month: 'long',
-                                year: 'numeric'
-                            })}</b></span>
+                        </div>
+
+                        <div className="app-status">
+                            <ApplicationDetailsModal application={application}/>
+                            <> {handleDefaultValue(application.status).label}</>
+                            {/*<div className="select">*/}
+                            {/*    <Select isDisabled={true} options={options} />*/}
+                            {/*</div>*/}
+
                         </div>
                     </div>
+                    {application.response &&
+                        <div className="response-message">
+                            {application.response}
+                        </div>
+                    }
 
-                    <div className="app-status">
-                        <ApplicationDetailsModal application={application}/>
-                        <> {handleDefaultValue(application.status).label}</>
-                        {/*<div className="select">*/}
-                        {/*    <Select isDisabled={true} options={options} />*/}
-                        {/*</div>*/}
+                </div>
 
-                    </div>
-                </div>
             ))}
 
-        </div>
-    )
+        </div>)
 }
Index: jobvista-frontend/src/views/applications/ApplyToJobAdModal.js
===================================================================
--- jobvista-frontend/src/views/applications/ApplyToJobAdModal.js	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/views/applications/ApplyToJobAdModal.js	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -105,5 +105,4 @@
                                    onChange={(e) => setResumeFile(e.target.files[0])} required type="file"
                                    id="fileUpload" accept=".pdf"/>
-
                             <br/>
                             <label className="label">Message to the recruiter</label>
Index: jobvista-frontend/src/views/auth/SignInForm.js
===================================================================
--- jobvista-frontend/src/views/auth/SignInForm.js	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/views/auth/SignInForm.js	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -1,3 +1,2 @@
-import {Button, TextField} from "@mui/material";
 import {Link} from "react-router-dom";
 import "./auth.css"
@@ -9,4 +8,6 @@
 import {AuthActions} from "../../redux/actions/authActions";
 import {notifyIncorrectEmailOrPassword} from "../../utils/toastUtils";
+
+import {GoogleOAuthProvider, GoogleLogin} from "@react-oauth/google";
 
 export const SignInForm = () => {
@@ -40,10 +41,29 @@
     }
 
+    const handleGoogleSuccess = (response) => {
+        const tokenId = response.credential;
+
+        dispatch(AuthActions.signInGoogle(tokenId, (success, error) => {
+            if (success) {
+                console.log("User signed in successfully");
+                if(success) {
+                    navigate("/")
+                }
+            } else {
+                console.error("Google sign-in failed", error);
+            }
+        }));
+    };
+
+    const handleGoogleFailure = (error) => {
+        console.error(error);
+    };
+
     return (
 
-        <div className="d-flex align-items-center">
+        <div className="">
             <div className="container">
-                <div className="row">
-                    <div className="col-md-8 mx-auto form-container">
+                <div className="row d-flex flex-column justify-content-center align-items-center">
+                    <div className="col-md-8 form-container">
                         <h3 className="login-heading mb-4">Sign in</h3>
                         <form onSubmit={handleSubmit(signIn)}>
@@ -83,4 +103,24 @@
 
                         <div className="row">
+                            <GoogleOAuthProvider clientId={process.env.REACT_APP_GOOGLE_CLIENT_ID}>
+                                <GoogleLogin
+                                    onSuccess={handleGoogleSuccess}
+                                    onError={handleGoogleFailure}
+                                    type={"standard"}
+                                    text={"signin_with"}
+                                    locale={"en"}
+                                    redirectUri="http://localhost:3000/login/oauth2/code/google"
+                                />
+                            </GoogleOAuthProvider>
+                        </div>
+                        <br/>
+                    </div>
+
+                    <div className="col-md-8 mt-5 form-container">
+                        <div>
+                            <h5 className="mb-3">Don't have an account?</h5>
+                        </div>
+
+                        <div className="row">
                             <div className="col-md-6">
                                 <Link to="/signup/recruiter" className="btn auth-secondary-btn text-uppercase fw-bold mb-2 w-100">SIGN UP AS RECRUITER</Link>
@@ -91,4 +131,5 @@
                         </div>
                     </div>
+
                 </div>
             </div>
Index: jobvista-frontend/src/views/auth/auth.css
===================================================================
--- jobvista-frontend/src/views/auth/auth.css	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/views/auth/auth.css	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -4,7 +4,4 @@
 }
 
-.form-container {
-    margin-bottom: 80px;
-}
 
 .auth-primary-btn{
@@ -27,2 +24,6 @@
     color: white;
 }
+
+iframe{
+    margin: auto !important;
+}
Index: jobvista-frontend/src/views/dashboard/Dashboard.js
===================================================================
--- jobvista-frontend/src/views/dashboard/Dashboard.js	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/views/dashboard/Dashboard.js	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -141,5 +141,5 @@
                                 </div>
                                 <div className="card-body">
-                                    <img
+                                    <img className="card-company-logo"
                                         // loading gif
                                         src={logos[jobAd.recruiterId]}
Index: jobvista-frontend/src/views/job_advertisements/JobAdDetails.js
===================================================================
--- jobvista-frontend/src/views/job_advertisements/JobAdDetails.js	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/views/job_advertisements/JobAdDetails.js	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -85,4 +85,5 @@
                                 <>
                                     <img
+                                        className="card-company-logo"
                                         // loading gif
                                         src={logosState[jobAd.recruiterId]}
Index: jobvista-frontend/src/views/shared_css/Modal.css
===================================================================
--- jobvista-frontend/src/views/shared_css/Modal.css	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/views/shared_css/Modal.css	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -65,4 +65,6 @@
 .react-responsive-modal-modal .modal-content form .label {
     display: block;
+    margin-bottom: 10px;
+    font-weight: 500;
 }
 
Index: jobvista-frontend/src/views/static/Header.js
===================================================================
--- jobvista-frontend/src/views/static/Header.js	(revision 0f0add0e646bd880b8213e381f57f621bc0e6861)
+++ jobvista-frontend/src/views/static/Header.js	(revision 4d97b633e131eca76d6c290321725684fc1d5970)
@@ -117,4 +117,5 @@
                                     {user.role === Roles.RECRUITER && <img src={logoState[auth.id]} /> }
                                     {user.role === Roles.ADMIN && <img src="/images/admin.jpg"/> }
+                                    {/*<img src="https://lh3.googleusercontent.com/a/ACg8ocJOmmRzyRWcuhJj_sCzIoxMeP1M1DOgQ1UeYsFoeJuFB4XgOAnS=s96-c"/>*/}
                                 </div>
                                 <div className="auth-box">
