Repository: Tech-Flurry/ai-recruiter
Pull request #26: Delete Job Post | SR-149
Author: login: Abdullah66438
Created: 2025-05-28T12:42:58Z | Merged: 2025-05-28T18:07:28Z



Diff:
diff --git a/.gitignore b/.gitignore
index 06b8a000..52bdce25 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,6 +32,7 @@ bld/
 [Ll]og/
 [Ll]ogs/
 /src/AiRecruiterCodeBase/dointo.airecruiter.web/public/theme/
+/src/AiRecruiterCodeBase/dointo.airecruiter.web/obj/Debug/package.g.props
 
 # Visual Studio 2015/2017 cache/options directory
 .vs/
diff --git a/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Application/Resolvers/EditJobDtoResolver.cs b/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Application/Resolvers/EditJobDtoResolver.cs
index 17484c20..127f6ae8 100644
--- a/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Application/Resolvers/EditJobDtoResolver.cs
+++ b/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Application/Resolvers/EditJobDtoResolver.cs
@@ -3,6 +3,7 @@
 using Dointo.AiRecruiter.Domain.Entities;
 using Dointo.AiRecruiter.Domain.ValueObjects;
 using Dointo.AiRecruiter.Dtos;
+using Humanizer;
 
 namespace Dointo.AiRecruiter.Application.Resolvers;
 internal class EditJobDtoResolver : ResolverBase<Job, EditJobDto>
@@ -21,13 +22,15 @@ protected override IDointoMapper Mapper
 				   .ForMember(dest => dest.AdditionalQuestions, opt => opt.MapFrom(src => string.Join(SEPARATOR, ( src.AdditionalQuestions ?? Enumerable.Empty<Question>( ) ).Select(x => x.Text))))
 				   .ForMember(dest => dest.BudgetAmount, opt => opt.MapFrom(src => src.Budget != null ? src.Budget.Amount : (double?)null))
 				   .ForMember(dest => dest.BudgetCurrency, opt => opt.MapFrom(src => src.Budget != null ? src.Budget.CurrencySymbol : null))
+				   .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status.Humanize( )))
 				   .ReverseMap( )
 				   .ForMember(dest => dest.AdditionalQuestions, opt => opt.MapFrom(src => !string.IsNullOrEmpty(src.AdditionalQuestions) ? src.AdditionalQuestions.Split(SEPARATOR, StringSplitOptions.RemoveEmptyEntries).Select(x => new Question { Text = x }).ToList( ) : new List<Question>( )))
 				   .ForMember(dest => dest.Budget, opt => opt.MapFrom(src => src.BudgetAmount != null ? new Money
 				   {
 					   Amount = src.BudgetAmount.Value,
 					   CurrencySymbol = src.BudgetCurrency ?? string.Empty
-				   } : null));
+				   } : null))
+				   .ForMember(dest => dest.Status, opt => opt.Ignore( ));
 			});
 			return new AutoMapperProvider(configuration.CreateMapper( ));
 		}
diff --git a/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Application/Services/JobPostsService.cs b/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Application/Services/JobPostsService.cs
index 2d1e72df..650c33d0 100644
--- a/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Application/Services/JobPostsService.cs
+++ b/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Application/Services/JobPostsService.cs
@@ -6,6 +6,7 @@
 using Dointo.AiRecruiter.Domain.Aggregates;
 using Dointo.AiRecruiter.Domain.Entities;
 using Dointo.AiRecruiter.Domain.Validators;
+using Dointo.AiRecruiter.Domain.ValueObjects;
 using Dointo.AiRecruiter.Dtos;
 using Humanizer;
 
@@ -17,6 +18,7 @@ public interface IJobPostsService
 	Task<IProcessingState> GetByIdAsync(string id);
 	Task<IProcessingState> GetJobsListAsync( );
 	Task<IProcessingState> SaveAsync(EditJobDto jobPostDto, string username);
+	Task<IProcessingState> CloseMultipleJobsAsync(CloseMultipleJobsDto closeJobDto);
 }
 
 internal class JobPostsService(IJobPostRepository repository, IResolver<Job, EditJobDto> editJobResolver, IResolver<Job, JobListDto> jobListResolver, IReadOnlyRepository readOnlyRepository) : IJobPostsService
@@ -50,16 +52,17 @@ public async Task<IProcessingState> GetJobsListAsync( )
 	{
 		var jobs = await _repository.GetByOwnerAsync("system");
 		var interviewsSet = _readOnlyRepository.Query<Interview>( );
+		_messageBuilder.Clear( );
 		var jobPostDtos = jobs.Select(x =>
 		{
 			var dto = _jobListResolver.Resolve(x);
-			dto.IsEditable = x.Status != Domain.ValueObjects.JobStatus.Closed;
+			dto.IsEditable = x.Status != JobStatus.Closed;
 			dto.URL = $"/jobs/conduct/{x.Id}?usp=share";
 			dto.Posted = x.CreatedAt.Humanize( );
 			dto.NumberOfInterviews = x.GetInterviewCount(interviewsSet);
 			return dto;
 		}).ToList( );
-		return new SuccessState<List<JobListDto>>("Job posts retrieved successfully", jobPostDtos);
+		return new SuccessState<List<JobListDto>>(_messageBuilder.AddFormat(Messages.RECORD_RETRIEVED_FORMAT).AddString(JOB_STRING).Build( ), jobPostDtos);
 	}
 
 	public async Task<IProcessingState> GetByIdAsync(string id)
@@ -75,13 +78,38 @@ public async Task<IProcessingState> GetByIdAsync(string id)
 	public async Task<IProcessingState> DeleteAsync(string id)
 	{
 		_messageBuilder.Clear( );
+
 		var jobPost = await _repository.GetByIdAsync(id);
 		if (jobPost is null)
 			return new BusinessErrorState(RecordNotFoundMessage( ));
 
+		var interviewCount = jobPost.GetInterviewCount(_readOnlyRepository.Query<Interview>( ));
+
+		if (interviewCount > 0)
+			return new BusinessErrorState(_messageBuilder.AddFormat(Messages.JOB_NOT_CLOSED_DUE_TO_INTERVIEWS).AddString(JOB_STRING).Build( ));
+
 		jobPost.IsDeleted = true;
 		await _repository.SaveAsync(jobPost, string.Empty);
-		return new SuccessState(_messageBuilder.AddFormat(Messages.RECORD_DELETED_FORMAT).AddString(JOB_STRING).Build( ));
+
+		return new SuccessState(
+		 _messageBuilder.AddFormat(Messages.RECORD_DELETED_FORMAT).AddString(JOB_STRING).Build( )
+		);
+	}
+
+
+	public async Task<IProcessingState> CloseMultipleJobsAsync(CloseMultipleJobsDto closeJobDto)
+	{
+		if (closeJobDto is { JobIds.Count: 0 })
+			return new BusinessErrorState(Messages.JOB_NOT_CLOSED);
+		foreach (var jobId in closeJobDto.JobIds)
+		{
+			var job = await _repository.GetByIdAsync(jobId);
+			if (job is null)
+				continue;
+			job.Status = JobStatus.Closed;
+			job.ClosedReason = !string.IsNullOrEmpty(closeJobDto.Reason) ? closeJobDto.Reason : Messages.JOB_CLOSE_NO_REASON;
+		}
+		return new SuccessState(Messages.JOB_CLOSED);
 	}
 
 	private string RecordNotFoundMessage( )
diff --git a/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Application/Utils/Messages.cs b/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Application/Utils/Messages.cs
index ddf4e341..fb58f1f7 100644
--- a/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Application/Utils/Messages.cs
+++ b/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Application/Utils/Messages.cs
@@ -6,4 +6,8 @@ internal static class Messages
 	public const string RECORD_NOT_FOUND_FORMAT = "{0} not found";
 	public const string RECORD_DELETED_FORMAT = "{0} has been deleted";
 	public const string RECORD_RETRIEVED_FORMAT = "{0} retrieved successfully";
+	public const string JOB_CLOSED = "Jobs closed successfully";
+	public const string JOB_NOT_CLOSED = "No jobs were closed";
+	public const string JOB_CLOSE_NO_REASON = "No reason given to close";
+	public const string JOB_NOT_CLOSED_DUE_TO_INTERVIEWS = "This job post cannot be deleted because interviews have already occurred. You may close it instead.";
 }
diff --git a/src/AiRecruiterCodeBase/Dointo.AiRecruiter.DbInfrastructure/Database/AiRecruiterDbContext.cs b/src/AiRecruiterCodeBase/Dointo.AiRecruiter.DbInfrastructure/Database/AiRecruiterDbContext.cs
index fe996ee4..9daca445 100644
--- a/src/AiRecruiterCodeBase/Dointo.AiRecruiter.DbInfrastructure/Database/AiRecruiterDbContext.cs
+++ b/src/AiRecruiterCodeBase/Dointo.AiRecruiter.DbInfrastructure/Database/AiRecruiterDbContext.cs
@@ -32,6 +32,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
 
 	public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
 	{
+		Database.AutoTransactionBehavior = AutoTransactionBehavior.Never;
 		foreach (var entry in ChangeTracker.Entries( ))
 		{
 			if (entry.State is not EntityState.Modified && entry.State is not EntityState.Added)
diff --git a/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Domain/Entities/Job.cs b/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Domain/Entities/Job.cs
index cfe33e93..683144ff 100644
--- a/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Domain/Entities/Job.cs
+++ b/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Domain/Entities/Job.cs
@@ -10,5 +10,6 @@ public class Job : BaseEntity
 	public List<string> RequiredSkills { get; set; } = [ ];
 	public Money? Budget { get; set; }
 	public JobStatus Status { get; set; } = JobStatus.Open;
-	public List<Question> AdditionalQuestions { get; set; } = null!;
+	public string? ClosedReason { get; set; }
+	public List<Question> AdditionalQuestions { get; set; } = [ ];
 }
diff --git a/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Dtos/CloseMultipleJobsDto.cs b/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Dtos/CloseMultipleJobsDto.cs
new file mode 100644
index 00000000..8932c0fc
--- /dev/null
+++ b/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Dtos/CloseMultipleJobsDto.cs
@@ -0,0 +1,8 @@
+namespace Dointo.AiRecruiter.Dtos;
+
+public class CloseMultipleJobsDto
+{
+	public List<string> JobIds { get; set; } = [ ];
+
+	public string Reason { get; set; } = string.Empty;
+}
diff --git a/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Dtos/EditJobDto.cs b/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Dtos/EditJobDto.cs
index f84a14ca..ab317fd4 100644
--- a/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Dtos/EditJobDto.cs
+++ b/src/AiRecruiterCodeBase/Dointo.AiRecruiter.Dtos/EditJobDto.cs
@@ -10,4 +10,6 @@ public class EditJobDto
 	public string? AdditionalQuestions { get; set; }
 	public double? BudgetAmount { get; set; }
 	public string? BudgetCurrency { get; set; }
+	public string? Status { get; set; } = null!;
+	public bool HasInterviews { get; set; }
 }
diff --git a/src/AiRecruiterCodeBase/Dointo.AiRecruiter.RestApi/Controllers/JobPostsController.cs b/src/AiRecruiterCodeBase/Dointo.AiRecruiter.RestApi/Controllers/JobPostsController.cs
index d5d4ba0e..da3cf9ee 100644
--- a/src/AiRecruiterCodeBase/Dointo.AiRecruiter.RestApi/Controllers/JobPostsController.cs
+++ b/src/AiRecruiterCodeBase/Dointo.AiRecruiter.RestApi/Controllers/JobPostsController.cs
@@ -2,6 +2,7 @@
 using Dointo.AiRecruiter.Dtos;
 using Microsoft.AspNetCore.Mvc;
 
+
 namespace Dointo.AiRecruiter.RestApi.Controllers;
 
 [ApiController]
@@ -24,14 +25,7 @@ public class JobPostsController(IJobPostsService service) : ControllerBase
 	[HttpPost]
 	[ProducesResponseType(StatusCodes.Status200OK)]
 	[ProducesResponseType(StatusCodes.Status400BadRequest)]
-	public async Task<IActionResult> CreateOrUpdateJobPost([FromBody] EditJobDto dto)
-	{
-		if (dto is null)
-			return BadRequest(new { Message = "Invalid job post data." });
-
-		var result = await _service.SaveAsync(dto, User.Identity?.Name ?? "system");
-		return Ok(result);
-	}
+	public async Task<IActionResult> CreateOrUpdateJobPost([FromBody] EditJobDto dto) => Ok(await _service.SaveAsync(dto, User.Identity?.Name ?? "system"));
 
 	// ✅ DELETE: api/JobPosts/{id}
 	[HttpDelete("{id}")]
@@ -39,19 +33,9 @@ public async Task<IActionResult> CreateOrUpdateJobPost([FromBody] EditJobDto dto
 	[ProducesResponseType(StatusCodes.Status404NotFound)]
 	public async Task<IActionResult> DeleteJobPost(string id) => Ok(await _service.DeleteAsync(id));
 
-	//[HttpPost("reset")]
-	//[ProducesResponseType(StatusCodes.Status200OK)]
-	//public async Task<IActionResult> ResetAllJobPosts( )
-	//{
-	//	_logger.LogWarning("Resetting all job posts by marking them deleted.");
-
-	//	var posts = await _repository.GetByOwnerAsync("system", true); // Adjust owner logic as needed
-	//	foreach (var post in posts)
-	//	{
-	//		post.IsDeleted = true;
-	//		await _repository.SaveAsync(post, "system");
-	//	}
-
-	//	return Ok(new { Message = "All job posts have been soft-deleted." });
-	//} //TODO: Need to confirm with the team if this is needed or not.
+	// ✅ POST: api/JobPosts/close-multiple
+	[HttpPost("close-multiple")]
+	[ProducesResponseType(StatusCodes.Status200OK)]
+	[ProducesResponseType(StatusCodes.Status400BadRequest)]
+	public async Task<IActionResult> CloseMultipleJobPosts([FromBody] CloseMultipleJobsDto dto) => Ok(await _service.CloseMultipleJobsAsync(dto));
 }
diff --git a/src/AiRecruiterCodeBase/dointo.airecruiter.web/package-lock.json b/src/AiRecruiterCodeBase/dointo.airecruiter.web/package-lock.json
index 994199c2..54f39080 100644
--- a/src/AiRecruiterCodeBase/dointo.airecruiter.web/package-lock.json
+++ b/src/AiRecruiterCodeBase/dointo.airecruiter.web/package-lock.json
@@ -16,7 +16,7 @@
         "@tanstack/react-query-devtools": "^5.59.20",
         "@tanstack/react-table": "^8.20.5",
         "@vitejs/plugin-react": "^4.3.4",
-        "@yaireo/tagify": "^4.35.0",
+        "@yaireo/tagify": "^4.35.1",
         "animate.css": "4.1.1",
         "apexcharts": "3.51.0",
         "axios": "1.7.2",
@@ -566,21 +566,6 @@
       "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==",
       "license": "MIT"
     },
-    "node_modules/@esbuild/darwin-arm64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
-      "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
-      "cpu": [
-        "arm64"
-      ],
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
     "node_modules/@eslint-community/eslint-utils": {
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -1054,227 +1039,6 @@
         "react": ">=16.14.0"
       }
     },
-    "node_modules/@rollup/rollup-android-arm-eabi": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz",
-      "integrity": "sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==",
-      "cpu": [
-        "arm"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "android"
-      ]
-    },
-    "node_modules/@rollup/rollup-android-arm64": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.4.tgz",
-      "integrity": "sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==",
-      "cpu": [
-        "arm64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "android"
-      ]
-    },
-    "node_modules/@rollup/rollup-darwin-arm64": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.4.tgz",
-      "integrity": "sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==",
-      "cpu": [
-        "arm64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ]
-    },
-    "node_modules/@rollup/rollup-darwin-x64": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.4.tgz",
-      "integrity": "sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==",
-      "cpu": [
-        "x64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ]
-    },
-    "node_modules/@rollup/rollup-freebsd-arm64": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.4.tgz",
-      "integrity": "sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==",
-      "cpu": [
-        "arm64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "freebsd"
-      ]
-    },
-    "node_modules/@rollup/rollup-freebsd-x64": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.4.tgz",
-      "integrity": "sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==",
-      "cpu": [
-        "x64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "freebsd"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.4.tgz",
-      "integrity": "sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==",
-      "cpu": [
-        "arm"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.4.tgz",
-      "integrity": "sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==",
-      "cpu": [
-        "arm"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-arm64-gnu": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.4.tgz",
-      "integrity": "sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==",
-      "cpu": [
-        "arm64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-arm64-musl": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.4.tgz",
-      "integrity": "sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==",
-      "cpu": [
-        "arm64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.4.tgz",
-      "integrity": "sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==",
-      "cpu": [
-        "ppc64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.4.tgz",
-      "integrity": "sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==",
-      "cpu": [
-        "riscv64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-s390x-gnu": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.4.tgz",
-      "integrity": "sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==",
-      "cpu": [
-        "s390x"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-x64-gnu": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz",
-      "integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==",
-      "cpu": [
-        "x64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-x64-musl": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.4.tgz",
-      "integrity": "sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==",
-      "cpu": [
-        "x64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-win32-arm64-msvc": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.4.tgz",
-      "integrity": "sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==",
-      "cpu": [
-        "arm64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
-    "node_modules/@rollup/rollup-win32-ia32-msvc": {
-      "version": "4.27.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.4.tgz",
-      "integrity": "sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==",
-      "cpu": [
-        "ia32"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
     "node_modules/@rollup/rollup-win32-x64-msvc": {
       "version": "4.27.4",
       "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.4.tgz",
@@ -1466,22 +1230,6 @@
         }
       }
     },
-    "node_modules/@swc/core-darwin-arm64": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.10.tgz",
-      "integrity": "sha512-TYp4x/9w/C/yMU1olK5hTKq/Hi7BjG71UJ4V1U1WxI1JA3uokjQ/GoktDfmH5V5pX4dgGSOJwUe2RjoN8Z/XnA==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=10"
-      }
-    },
     "node_modules/@swc/counter": {
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -2449,9 +2197,9 @@
       "dev": true
     },
     "node_modules/@yaireo/tagify": {
-      "version": "4.35.0",
-      "resolved": "https://registry.npmjs.org/@yaireo/tagify/-/tagify-4.35.0.tgz",
-      "integrity": "sha512-D+o/c2rAhNA/Cmiw1sV1YtuxPCqB0UpVOXdnR9DXPuMHvU0quIEkU3FA6JDXTYMQV6FL7YGwonq2yPMfkc83lQ==",
+      "version": "4.35.1",
+      "resolved": "https://registry.npmjs.org/@yaireo/tagify/-/tagify-4.35.1.tgz",
+      "integrity": "sha512-PF6nwp1lHMVS2IVpqZTxx53fQyEBUDjdDQsi7gD9Vx/EQ398gRH8LByEL8lawws9iJHB90on74VWG3pvwznrEg==",
       "license": "MIT",
       "engines": {
         "node": ">=16.15.0",
@@ -4873,19 +4621,6 @@
       "dev": true,
       "license": "ISC"
     },
-    "node_modules/fsevents": {
-      "version": "2.3.3",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
-      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
-      }
-    },
     "node_modules/function-bind": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
diff --git a/src/AiRecruiterCodeBase/dointo.airecruiter.web/package.json b/src/AiRecruiterCodeBase/dointo.airecruiter.web/package.json
index e635c768..77c7ef9a 100644
--- a/src/AiRecruiterCodeBase/dointo.airecruiter.web/package.json
+++ b/src/AiRecruiterCodeBase/dointo.airecruiter.web/package.json
@@ -19,7 +19,7 @@
     "@tanstack/react-query-devtools": "^5.59.20",
     "@tanstack/react-table": "^8.20.5",
     "@vitejs/plugin-react": "^4.3.4",
-    "@yaireo/tagify": "^4.35.0",
+    "@yaireo/tagify": "^4.35.1",
     "animate.css": "4.1.1",
     "apexcharts": "3.51.0",
     "axios": "1.7.2",
diff --git a/src/AiRecruiterCodeBase/dointo.airecruiter.web/src/_metronic/assets/ts/components/_ScrollTopComponent.ts b/src/AiRecruiterCodeBase/dointo.airecruiter.web/src/_metronic/assets/ts/components/_ScrollTopComponent.ts
index ea8d9248..121b5546 100644
--- a/src/AiRecruiterCodeBase/dointo.airecruiter.web/src/_metronic/assets/ts/components/_ScrollTopComponent.ts
+++ b/src/AiRecruiterCodeBase/dointo.airecruiter.web/src/_metronic/assets/ts/components/_ScrollTopComponent.ts
@@ -37,18 +37,23 @@ class ScrollTopComponent {
   }
 
   private _handlers = () => {
-    let timer: number
+    let timer: number | undefined = undefined; // ✅ Declare with proper type and initial value
+  
     window.addEventListener('scroll', () => {
-      throttle(timer, () => {
-        this._scroll()
-      })
-    })
-
+      if (!timer) {
+        timer = window.setTimeout(() => {
+          this._scroll();
+          timer = undefined; // ✅ Clear the timer after execution
+        }, 200); // you can adjust delay as needed
+      }
+    });
+  
     this.element.addEventListener('click', (e: Event) => {
-      e.preventDefault()
-      this._go()
-    })
+      e.preventDefault();
+      this._go();
+    });
   }
+  
 
   private _scroll = () => {
     const offset = parseInt(this._getOption('offset') as string)
diff --git a/src/AiRecruiterCodeBase/dointo.airecruiter.web/src/app/modules/jobs/JobPost.tsx b/src/AiRecruiterCodeBase/dointo.airecruiter.web/src/app/modules/jobs/JobPost.tsx
index 94a8314d..7e816cce 100644
--- a/src/AiRecruiterCodeBase/dointo.airecruiter.web/src/app/modules/jobs/JobPost.tsx
+++ b/src/AiRecruiterCodeBase/dointo.airecruiter.web/src/app/modules/jobs/JobPost.tsx
@@ -10,6 +10,24 @@ import axios from "axios";
 import toastr from "toastr";
 import "toastr/build/toastr.min.css";
 
+interface JobPost {
+	id: string;
+	jobTitle: string;
+	yearsOfExperience: number;
+	jobDescription: string;
+	requiredSkills: string[];
+	budgetAmount: number;
+	budgetCurrency: string;
+	status: "Open" | "Closed";
+	hasInterviews?: boolean;
+	additionalQuestions?: string;
+}
+
+interface JobPostResponse {
+	success?: boolean;
+	message?: string;
+	errors?: { propertyName: string; errorMessage: string }[];
+}
 function JobPost() {
 	const navigate = useNavigate();
 	const location = useLocation();
@@ -30,7 +48,9 @@ function JobPost() {
 	const [descriptionTouched, setDescriptionTouched] = useState(false);
 	const [budget, setBudget] = useState("");
 	const [budgetTouched, setBudgetTouched] = useState(false);
-	const [serverErrors, setServerErrors] = useState<{ [key: string]: string }>({});
+	const [serverErrors, setServerErrors] = useState<{ [key: string]: string }>(
+		{}
+	);
 	const [isEditable, setIsEditable] = useState(true); // NEW
 
 	useEffect(() => {
@@ -54,37 +74,53 @@ function JobPost() {
 		if (isEditMode && jobId) {
 			const fetchJob = async () => {
 				try {
-					const res = await axios.get(`${import.meta.env.VITE_APP_API_BASE_URL}/JobPosts/${jobId}`, {
-						withCredentials: true
-					});
+					const res = await axios.get<{ data: JobPost }>(
+						`${import.meta.env.VITE_APP_API_BASE_URL}/JobPosts/${jobId}`,
+						{ withCredentials: true }
+					);
 
-					const job = res.data?.data;
+					const job = res.data?.data as JobPost;
 
+					console.log("🟢 Full job response:", job);
+
+					// Set form fields
 					setJobTitle(job.jobTitle || "");
 					setExperience(job.yearsOfExperience?.toString() || "");
 					setJobDescription(job.jobDescription || "");
 					setBudget(job.budgetAmount?.toString() || "");
 
-					if (job.status === "closed" || job.hasInterviews) {
-						setIsEditable(false);
-					}
+					// Set editable status
+					setIsEditable(job.status !== "Closed" && !job.hasInterviews);
 
-					const currencyInput = formRef.current?.elements.namedItem("currency") as HTMLSelectElement;
-					const additionalInput = formRef.current?.elements.namedItem("additionalQuestions") as HTMLTextAreaElement;
+					// Set currency and additional questions
+					const currencyInput = formRef.current?.elements.namedItem(
+						"currency"
+					) as HTMLSelectElement;
+					const additionalInput = formRef.current?.elements.namedItem(
+						"additionalQuestions"
+					) as HTMLTextAreaElement;
 					if (currencyInput) currencyInput.value = job.budgetCurrency || "USD";
-					if (additionalInput) additionalInput.value = job.additionalQuestions || "";
+					if (additionalInput)
+						additionalInput.value = job.additionalQuestions || "";
 
+					// Set skills
 					setTimeout(() => {
-						if (tagifyInstanceRef.current && Array.isArray(job.requiredSkills)) {
+						if (
+							tagifyInstanceRef.current &&
+							Array.isArray(job.requiredSkills)
+						) {
 							tagifyInstanceRef.current.removeAllTags();
-							tagifyInstanceRef.current.addTags(job.requiredSkills.map((skill: string) => ({ value: skill })));
+							tagifyInstanceRef.current.addTags(
+								job.requiredSkills.map((skill: string) => ({ value: skill }))
+							);
 						}
 					}, 300);
 				} catch (err) {
-					console.error("Failed to load job post for edit:", err);
+					console.error("❌ Failed to load job post for edit:", err);
 					toastr.error("Unable to load job post.");
 				}
 			};
+
 			fetchJob();
 		}
 	}, [isEditMode, jobId]);
@@ -98,13 +134,18 @@ function JobPost() {
 				const response = await axios.post(
 					`${import.meta.env.VITE_APP_API_BASE_URL}/JobPosts/extract-skills`,
 					{ jobDescription: value },
-					{ headers: { "Content-Type": "application/json" }, withCredentials: true }
+					{
+						headers: { "Content-Type": "application/json" },
+						withCredentials: true,
+					}
 				);
 
-				const skills: string[] = response.data;
+				const skills = response.data as string[];
 				if (Array.isArray(skills)) {
 					tagifyInstanceRef.current?.removeAllTags();
-					tagifyInstanceRef.current?.addTags(skills.map(skill => ({ value: skill })));
+					tagifyInstanceRef.current?.addTags(
+						skills.map((skill) => ({ value: skill }))
+					);
 				}
 			} catch (err) {
 				console.error("❌ Failed to extract skills", err);
@@ -123,7 +164,9 @@ function JobPost() {
 		// Clear previous server errors
 		setServerErrors({});
 
-		const requiredSkills = (tagifyInstanceRef.current?.value ?? []).map((tag: any) => tag.value);
+		const requiredSkills = (tagifyInstanceRef.current?.value ?? []).map(
+			(tag: any) => tag.value
+		);
 
 		if (
 			jobTitle.trim().length < 3 ||
@@ -164,13 +207,14 @@ function JobPost() {
 					withCredentials: true,
 				}
 			);
-			const responseData = response.data;
-			if (responseData?.success) {
-				toastr.success("Job post has been saved.");
+			const responseData = response.data as JobPostResponse;
+
+			if (responseData.success || response.status === 200) {
+				toastr.success(responseData.message || "Job post has been saved.");
 				navigate("/jobs/list");
 			} else {
-				toastr.error(`Failed to create job post: ${responseData?.message}`);
-				if (responseData?.errors) {
+				toastr.error(`Failed to create job post: ${responseData.message}`);
+				if (responseData.errors) {
 					const errors: { [key: string]: string } = {};
 					responseData.errors.forEach(
 						(err: { propertyName: string; errorMessage: string }) => {
@@ -181,7 +225,10 @@ function JobPost() {
 				}
 			}
 		} catch (error: any) {
-			toastr.error(`Failed to ${isEditMode ? "update" : "create"} job post: ${error.response?.data?.message ?? error.message}`);
+			toastr.error(
+				`Failed to ${isEditMode ? "update" : "create"} job post: ${error.response?.data?.message ?? error.message
+				}`
+			);
 		} finally {
 			btnSaveRef.current?.removeAttribute("data-kt-indicator");
 			btnSaveRef.current?.classList.remove("disabled");
@@ -255,7 +302,7 @@ function JobPost() {
 	}
 
 	return (
-		<Container className="my-5 d-flex justify-content-center">
+		<Container className="d-flex justify-content-center my-5">
 			<div style={{ maxWidth: "900px", width: "100%" }}>
 				<KTCard className="rounded shadow">
 					<KTCardBody>
@@ -412,13 +459,17 @@ function JobPost() {
 									</Form.Group>
 								</Col>
 							</Row>
-							<div className="d-flex justify-content-end gap-3 mt-4">
+							<div className="d-flex justify-content-end mt-4 gap-3">
 								<Button
 									ref={btnSaveRef}
 									type="submit"
 									variant="primary"
 									disabled={!isEditable}
-									title={!isEditable ? "Editing disabled for closed job or one with interviews" : ""}
+									title={
+										!isEditable
+											? "Editing is disabled for closed job or one with interviews"
+											: ""
+									}
 								>
 									<span className="indicator-label">Save</span>
 									<span className="indicator-progress">
@@ -426,9 +477,21 @@ function JobPost() {
 										<span className="spinner-border spinner-border-sm ms-2 align-middle"></span>
 									</span>
 								</Button>
-								<Button type="button" variant="secondary" onClick={handleReset}>
+
+								<Button
+									type="button"
+									variant="secondary"
+									onClick={handleReset}
+									disabled={!isEditable}
+									title={
+										!isEditable
+											? "Reset disabled because job is closed or has interviews"
+											: ""
+									}
+								>
 									Reset
 								</Button>
+
 								<Button
 									type="button"
 									variant="dark"
@@ -440,7 +503,8 @@ function JobPost() {
 
 							{!isEditable && (
 								<div className="text-danger fw-semibold mt-3">
-									This job post is either closed or already has interviews. You cannot edit it.
+									This job post is either closed or already has interviews. You
+									cannot edit it.
 								</div>
 							)}
 						</Form>
diff --git a/src/AiRecruiterCodeBase/dointo.airecruiter.web/src/app/modules/jobs/JobsList.tsx b/src/AiRecruiterCodeBase/dointo.airecruiter.web/src/app/modules/jobs/JobsList.tsx
index 39ade6a7..c3d2010a 100644
--- a/src/AiRecruiterCodeBase/dointo.airecruiter.web/src/app/modules/jobs/JobsList.tsx
+++ b/src/AiRecruiterCodeBase/dointo.airecruiter.web/src/app/modules/jobs/JobsList.tsx
@@ -4,6 +4,8 @@ import { useNavigate } from "react-router-dom";
 import { KTCard, KTCardBody } from "../../../_metronic/helpers";
 import { Modal, Form, Button, Table } from "react-bootstrap";
 import { Link } from "react-router-dom";
+import toastr from "toastr";
+import "toastr/build/toastr.min.css";
 
 interface JobPost {
 	id: string;
@@ -14,18 +16,27 @@ interface JobPost {
 	numberOfInterviews: number;
 }
 
+interface DeleteResponse {
+	success: boolean;
+	message: string;
+}
+
 const JobPost: React.FC = () => {
 	const [jobPosts, setJobPosts] = useState<JobPost[]>([]);
 	const [selectedJobs, setSelectedJobs] = useState<string[]>([]);
 	const [isCloseModalOpen, setIsCloseModalOpen] = useState(false);
 	const [closeReason, setCloseReason] = useState("");
 	const [copiedJobId, setCopiedJobId] = useState<string | null>(null);
+	const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
+	const [jobToDelete, setJobToDelete] = useState<JobPost | null>(null);
 
 	const navigate = useNavigate();
 
 	useEffect(() => {
 		axios
-			.get(`${import.meta.env.VITE_APP_API_BASE_URL}/JobPosts`)
+			.get<{ data: JobPost[] }>(
+				`${import.meta.env.VITE_APP_API_BASE_URL}/JobPosts`
+			)
 			.then((res) => setJobPosts(res.data.data))
 			.catch((err) => console.error("Failed to fetch job posts:", err));
 	}, []);
@@ -38,10 +49,13 @@ const JobPost: React.FC = () => {
 
 	const handleCloseJobs = () => {
 		axios
-			.post("https://localhost:7072/api/JobPosts/close", {
-				jobIds: selectedJobs,
-				reason: closeReason,
-			})
+			.post(
+				`${import.meta.env.VITE_APP_API_BASE_URL}/JobPosts/close-multiple`,
+				{
+					jobIds: selectedJobs,
+					reason: closeReason,
+				}
+			)
 			.then(() => {
 				setJobPosts((prev) =>
 					prev.map((job) =>
@@ -61,10 +75,38 @@ const JobPost: React.FC = () => {
 	const handleButtonClick = () => {
 		navigate("/jobs/create");
 	};
+	const handleDeleteJobConfirmed = async () => {
+		if (!jobToDelete) return;
+
+		try {
+			const res = await axios.delete(
+				`${import.meta.env.VITE_APP_API_BASE_URL}/JobPosts/${jobToDelete.id}`,
+				{
+					withCredentials: true,
+				}
+			);
+
+			if (res.data.success) {
+				toastr.success(res.data.message);
+				setJobPosts((prev) => prev.filter((j) => j.id !== jobToDelete.id));
+			} else {
+				toastr.error(res.data.message || "Failed to delete job post.");
+			}
+		} catch (error: any) {
+			const msg = error?.response?.data?.message ?? "Something went wrong.";
+			console.error("Delete job error", error);
+			toastr.error(msg);
+		} finally {
+			setIsDeleteModalOpen(false);
+			setJobToDelete(null);
+		}
+	};
 
 	return (
 		<div>
-			<div style={{ display: "flex", justifyContent: "flex-end", padding: "10px" }}>
+			<div
+				style={{ display: "flex", justifyContent: "flex-end", padding: "10px" }}
+			>
 				<button
 					type="button"
 					className="btn btn-secondary"
@@ -114,6 +156,7 @@ const JobPost: React.FC = () => {
 										<input
 											type="checkbox"
 											checked={selectedJobs.includes(job.id)}
+											disabled={job.status === "Closed"}
 											onChange={() => toggleJobSelection(job.id)}
 										/>
 									</td>
@@ -127,11 +170,16 @@ const JobPost: React.FC = () => {
 										</Link>
 									</td>
 									<td>
-										<span className={`badge ${job.status === "Open" ? "bg-success" : "bg-danger"}`}>
+										<span
+											className={`badge ${job.status === "Open" ? "bg-success" : "bg-danger"
+												}`}
+										>
 											{job.status}
 										</span>
 									</td>
-									<td>{job.posted ? new Date(job.posted).toLocaleDateString() : "N/A"}</td>
+									<td>
+										{job.posted}
+									</td>
 									<td>{job.numberOfInterviews}</td>
 									<td>
 										{/* Copy URI Button */}
@@ -152,49 +200,91 @@ const JobPost: React.FC = () => {
 												cursor: "pointer",
 											}}
 										>
-											<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24">
-												<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
+											<svg
+												xmlns="http://www.w3.org/2000/svg"
+												width="24px"
+												height="24px"
+												viewBox="0 0 24 24"
+											>
+												<g
+													stroke="none"
+													strokeWidth="1"
+													fill="none"
+													fillRule="evenodd"
+												>
 													<rect x="0" y="0" width="24" height="24" />
 													<path
 														d="M8,3 L8,3.5 C8,4.32842712 8.67157288,5 9.5,5 L14.5,5 C15.3284271,5 16,4.32842712 16,3.5 L16,3 L18,3 C19.1045695,3 20,3.8954305 20,5 L20,21 C20,22.1045695 19.1045695,23 18,23 L6,23 C4.8954305,23 4,22.1045695 4,21 L4,5 C4,3.8954305 4.8954305,3 6,3 L8,3 Z"
-														fill={copiedJobId === job.id ? "#009EF7" : "#5E6278"}
+														fill={
+															copiedJobId === job.id ? "#009EF7" : "#5E6278"
+														}
 														opacity="0.3"
 													/>
 													<path
 														d="M11,2 C11,1.44771525 11.4477153,1 12,1 C12.5522847,1 13,1.44771525 13,2 L14.5,2 C14.7761424,2 15,2.22385763 15,2.5 L15,3.5 C15,3.77614237 14.7761424,4 14.5,4 L9.5,4 C9.22385763,4 9,3.77614237 9,3.5 L9,2.5 C9,2.22385763 9.22385763,2 9.5,2 L11,2 Z"
-														fill={copiedJobId === job.id ? "#009EF7" : "#5E6278"}
+														fill={
+															copiedJobId === job.id ? "#009EF7" : "#5E6278"
+														}
+													/>
+													<rect
+														fill={
+															copiedJobId === job.id ? "#009EF7" : "#5E6278"
+														}
+														opacity="0.3"
+														x="7"
+														y="10"
+														width="5"
+														height="2"
+														rx="1"
+													/>
+													<rect
+														fill={
+															copiedJobId === job.id ? "#009EF7" : "#5E6278"
+														}
+														opacity="0.3"
+														x="7"
+														y="14"
+														width="9"
+														height="2"
+														rx="1"
 													/>
-													<rect fill={copiedJobId === job.id ? "#009EF7" : "#5E6278"} opacity="0.3" x="7" y="10" width="5" height="2" rx="1" />
-													<rect fill={copiedJobId === job.id ? "#009EF7" : "#5E6278"} opacity="0.3" x="7" y="14" width="9" height="2" rx="1" />
 												</g>
 											</svg>
 										</button>
 
 										{/* Close Job Icon */}
-										<span
-											className="svg-icon svg-icon-2x"
-											style={{
-												cursor: "pointer",
-												color: selectedJobs.includes(job.id) ? "#F1416C" : "#A1A5B7",
-												transition: "color 0.2s ease",
-											}}
-											onClick={() => {
-												setSelectedJobs([job.id]);
-												setIsCloseModalOpen(true);
-											}}
-											title="Close Job"
-										>
-											<svg
-												xmlns="http://www.w3.org/2000/svg"
-												width="24px"
-												height="24px"
-												viewBox="0 0 24 24"
-												fill="currentColor"
+										{job.status !== "Closed" && (
+											<span
+												className="svg-icon svg-icon-2x"
+												style={{
+													cursor: "pointer",
+													color: selectedJobs.includes(job.id)
+														? "#F1416C"
+														: "#A1A5B7",
+													transition: "color 0.2s ease",
+												}}
+												onClick={() => {
+													setSelectedJobs([job.id]);
+													setIsCloseModalOpen(true);
+												}}
+												title="Close Job"
 											>
-												<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
-													<rect x="0" y="0" width="24" height="24" />
-													<path
-														d="M12,22 C6.4771525,22 2,17.5228475 2,12 
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													width="24px"
+													height="24px"
+													viewBox="0 0 24 24"
+													fill="currentColor"
+												>
+													<g
+														stroke="none"
+														strokeWidth="1"
+														fill="none"
+														fillRule="evenodd"
+													>
+														<rect x="0" y="0" width="24" height="24" />
+														<path
+															d="M12,22 C6.4771525,22 2,17.5228475 2,12 
 														C2,6.4771525 6.4771525,2 12,2 
 														C17.5228475,2 22,6.4771525 22,12 
 														C22,17.5228475 17.5228475,22 12,22 Z 
@@ -209,13 +299,44 @@ const JobPost: React.FC = () => {
 														C4.5384079,18.6805435 4.5384079,18.0473785 4.92893219,17.6568542 
 														L17.6568542,4.92893219 
 														C18.0473785,4.5384079 18.6805435,4.5384079 19.0710678,4.92893219 Z"
-														fill="currentColor"
-														fillRule="nonzero"
-														opacity="0.9"
-													/>
-												</g>
+															fill="currentColor"
+															fillRule="nonzero"
+															opacity="0.9"
+														/>
+													</g>
+												</svg>
+											</span>
+										)}
+										{/* Delete Button */}
+										<button
+											title="Delete Job"
+											onClick={() => {
+												if (job.numberOfInterviews > 0) {
+													toastr.warning(
+														"This job post cannot be deleted because candidates have already interviewed. You can only close it."
+													);
+													return;
+												}
+												setJobToDelete(job);
+												setIsDeleteModalOpen(true);
+											}}
+											style={{
+												background: "transparent",
+												border: "none",
+												marginLeft: "10px",
+												cursor: "pointer",
+											}}
+										>
+											<svg
+												xmlns="http://www.w3.org/2000/svg"
+												width="20"
+												height="20"
+												fill="red"
+												viewBox="0 0 24 24"
+											>
+												<path d="M3 6h18v2H3zm2 3h14l-1.5 12.5c-.1.8-.8 1.5-1.7 1.5H8.2c-.9 0-1.6-.7-1.7-1.5L5 9zm5 2v9h2v-9H10zm4 0v9h2v-9h-2z" />
 											</svg>
-										</span>
+										</button>
 									</td>
 								</tr>
 							))}
@@ -223,7 +344,11 @@ const JobPost: React.FC = () => {
 					</Table>
 
 					{/* Modal for closing job(s) */}
-					<Modal show={isCloseModalOpen} onHide={() => setIsCloseModalOpen(false)} centered>
+					<Modal
+						show={isCloseModalOpen}
+						onHide={() => setIsCloseModalOpen(false)}
+						centered
+					>
 						<Modal.Header closeButton>
 							<Modal.Title>Close Job Posts</Modal.Title>
 						</Modal.Header>
@@ -240,14 +365,46 @@ const JobPost: React.FC = () => {
 							</Form.Group>
 						</Modal.Body>
 						<Modal.Footer>
-							<Button variant="secondary" onClick={() => setIsCloseModalOpen(false)}>
+							<Button
+								variant="secondary"
+								onClick={() => setIsCloseModalOpen(false)}
+							>
 								Cancel
 							</Button>
-							<Button variant="danger" onClick={handleCloseJobs} disabled={!closeReason}>
+							<Button
+								variant="danger"
+								onClick={handleCloseJobs}
+								disabled={!closeReason}
+							>
 								Close Jobs
 							</Button>
 						</Modal.Footer>
 					</Modal>
+					<Modal
+						show={isDeleteModalOpen}
+						onHide={() => setIsDeleteModalOpen(false)}
+						centered
+					>
+						<Modal.Header closeButton>
+							<Modal.Title>Confirm Deletion</Modal.Title>
+						</Modal.Header>
+						<Modal.Body>
+							Are you sure you want to delete the job post{" "}
+							<strong>{jobToDelete?.title}</strong>? This action cannot be
+							undone.
+						</Modal.Body>
+						<Modal.Footer>
+							<Button
+								variant="secondary"
+								onClick={() => setIsDeleteModalOpen(false)}
+							>
+								Cancel
+							</Button>
+							<Button variant="danger" onClick={handleDeleteJobConfirmed}>
+								Delete
+							</Button>
+						</Modal.Footer>
+					</Modal>
 				</KTCardBody>
 			</KTCard>
 		</div>
diff --git a/src/AiRecruiterCodeBase/dointo.airecruiter.web/src/global.d.ts b/src/AiRecruiterCodeBase/dointo.airecruiter.web/src/global.d.ts
index 2d091c4d..1450b268 100644
--- a/src/AiRecruiterCodeBase/dointo.airecruiter.web/src/global.d.ts
+++ b/src/AiRecruiterCodeBase/dointo.airecruiter.web/src/global.d.ts
@@ -3,3 +3,6 @@ declare module '@yaireo/tagify' {
 	const Tagify: any;
 	export default Tagify;
 }
+declare module '@yaireo/tagify/dist/tagify.css';
+declare module 'toastr';
+declare module 'toastr/build/toastr.min.css';
\ No newline at end of file
diff --git a/src/AiRecruiterCodeBase/dointo.airecruiter.web/tsconfig.json b/src/AiRecruiterCodeBase/dointo.airecruiter.web/tsconfig.json
index f84a3ab3..7526732a 100644
--- a/src/AiRecruiterCodeBase/dointo.airecruiter.web/tsconfig.json
+++ b/src/AiRecruiterCodeBase/dointo.airecruiter.web/tsconfig.json
@@ -21,7 +21,7 @@
     "noUnusedParameters": false,
     "noFallthroughCasesInSwitch": true
   },
-  "include": ["src"],
+  "include": ["src", "vite-env.d.ts"],
   "references": [{ "path": "./tsconfig.app.json" },
     { "path": "./tsconfig.node.json" }]
 }
diff --git a/src/AiRecruiterCodeBase/dointo.airecruiter.web/yarn.lock b/src/AiRecruiterCodeBase/dointo.airecruiter.web/yarn.lock
index 4dcf5b96..2e1fb645 100644
--- a/src/AiRecruiterCodeBase/dointo.airecruiter.web/yarn.lock
+++ b/src/AiRecruiterCodeBase/dointo.airecruiter.web/yarn.lock
@@ -1203,10 +1203,10 @@
   resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz"
   integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
 
-"@yaireo/tagify@^4.35.0":
-  version "4.35.0"
-  resolved "https://registry.npmjs.org/@yaireo/tagify/-/tagify-4.35.0.tgz"
-  integrity sha512-D+o/c2rAhNA/Cmiw1sV1YtuxPCqB0UpVOXdnR9DXPuMHvU0quIEkU3FA6JDXTYMQV6FL7YGwonq2yPMfkc83lQ==
+"@yaireo/tagify@^4.35.1":
+  version "4.35.1"
+  resolved "https://registry.npmjs.org/@yaireo/tagify/-/tagify-4.35.1.tgz"
+  integrity sha512-PF6nwp1lHMVS2IVpqZTxx53fQyEBUDjdDQsi7gD9Vx/EQ398gRH8LByEL8lawws9iJHB90on74VWG3pvwznrEg==
 
 "@yr/monotone-cubic-spline@^1.0.3":
   version "1.0.3"


Reviews:
user: login: theumairtahir
body: Need a few changes!
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-28T13:20:52Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR implements the deletion of job posts (SR-149) along with improvements in the job listing and editing workflows while also updating configuration and dependencies.  
- Adds delete modal functionality and API endpoint for deleting job posts.  
- Updates the job listing, job post editing logic, and error handling.  
- Adjusts configurations (tsconfig, package.json) and database behavior.

### Reviewed Changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| tsconfig.json | Updated include array to support additional type files. |
| JobsList.tsx | Introduced delete modal and delete confirmation logic; refined UI elements. |
| JobPost.tsx | Updated job fetching, form reset, and error handling logic for editing job posts. |
| _ScrollTopComponent.ts | Replaced throttling mechanism with a timer-based scroll handler. |
| package.json | Upgraded dependency for tagify. |
| JobPostsController.cs | Modified delete endpoint to return proper error messages and status codes. |
| JobPostDto.cs, JobListDto.cs, CloseMultipleJobsDto.cs | Updated DTOs to support new data and behaviors. |
| Job.cs, AiRecruiterDbContext.cs, Messages.cs | Enhanced entity properties and configuration behavior. |
| JobPostsService.cs | Added logic for closing multiple job posts and retrieving job status with improved mapping. |
</details>



<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**src/AiRecruiterCodeBase/dointo.airecruiter.web/src/app/modules/jobs/JobsList.tsx:395**
* The deletion modal references 'jobToDelete?.title', but the JobPost interface does not define a 'title' property. Consider using the correct property (e.g. 'jobTitle') or updating the interface accordingly.
```
Are you sure you want to delete the job post <strong>{jobToDelete?.title}</strong>? This action cannot be
```
**src/AiRecruiterCodeBase/Dointo.AiRecruiter.Application/Services/JobPostsService.cs:130**
* The service maps 'job.Title' to the DTO property 'JobTitle', yet the Job entity does not explicitly define a 'Title' property. Verify whether 'Title' is inherited or if it should be renamed for consistency.
```
JobTitle = job.Title,
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-28T13:23:56Z
user: login: theumairtahir
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-28T13:37:46Z
user: login: theumairtahir
body: The changes have been approved
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-28T18:07:05Z

Review comments:
diff_hunk: @@ -75,18 +82,65 @@ public async Task<IProcessingState> GetByIdAsync(string id)
 	public async Task<IProcessingState> DeleteAsync(string id)
 	{
 		_messageBuilder.Clear( );
+
 		var jobPost = await _repository.GetByIdAsync(id);
 		if (jobPost is null)
 			return new BusinessErrorState(RecordNotFoundMessage( ));
 
+		if (jobPost.HasInterviews == true)
+			return new BusinessErrorState("This job post cannot be deleted because interviews have already occurred. You may close it instead.");
+
 		jobPost.IsDeleted = true;
 		await _repository.SaveAsync(jobPost, string.Empty);
-		return new SuccessState(_messageBuilder.AddFormat(Messages.RECORD_DELETED_FORMAT).AddString(JOB_STRING).Build( ));
+
+		return new SuccessState(
+		 _messageBuilder.AddFormat(Messages.RECORD_DELETED_FORMAT).AddString(JOB_STRING).Build( )
+		);
+	}
+
+
+	public async Task<IProcessingState> CloseMultipleJobsAsync(CloseMultipleJobsDto closeJobDto)
+	{
+		if (closeJobDto is { JobIds.Count: 0 })
+			return new BusinessErrorState(Messages.JOB_NOT_CLOSED);
+		foreach (var jobId in closeJobDto.JobIds)
+		{
+			var job = await _repository.GetByIdAsync(jobId);
+			if (job is null)
+				continue;
+			job.Status = JobStatus.Closed;
+			job.ClosedReason = !string.IsNullOrEmpty(closeJobDto.Reason) ? closeJobDto.Reason : Messages.JOB_CLOSE_NO_REASON;
+		}
+		return new SuccessState(Messages.JOB_CLOSED);
 	}
 
 	private string RecordNotFoundMessage( )
 	{
 		_messageBuilder.Clear( );
 		return _messageBuilder.AddFormat(Messages.RECORD_NOT_FOUND_FORMAT).AddString(JOB_STRING).Build( );
 	}
+	public async Task<JobPostDto> GetJobStatusByIdAsync(string id)
path: src/AiRecruiterCodeBase/Dointo.AiRecruiter.Application/Services/JobPostsService.cs
user: login: theumairtahir
body: Why this this method is returning a complete job dto? if it is intended then change the method name and use Iresolver for that purpose. If it is only supposed to return a status then return only a status instead
created_at: 2025-05-28T13:07:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -10,5 +10,8 @@ public class Job : BaseEntity
 	public List<string> RequiredSkills { get; set; } = [ ];
 	public Money? Budget { get; set; }
 	public JobStatus Status { get; set; } = JobStatus.Open;
+	public string? ClosedReason { get; set; }
+	public int? NumberOfInterviews { get; set; } = 0;
path: src/AiRecruiterCodeBase/Dointo.AiRecruiter.Domain/Entities/Job.cs
user: login: theumairtahir
body: Remove the properties `NumberOfInterviews` and `HasInterviews `from the domain entity. Instead, use `job.GetInterviewCount()` extension method wherever it is required to count the number of interviews for a job
created_at: 2025-05-28T13:09:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -6,7 +6,10 @@ public class JobListDto
 	public string Title { get; set; } = null!;
 	public string URL { get; set; } = null!;
 	public bool IsEditable { get; set; }
-	public int NumberOfInterviews { get; set; }
+	public int? NumberOfInterviews { get; set; } = 0;
path: src/AiRecruiterCodeBase/Dointo.AiRecruiter.Dtos/JobListDto.cs
user: login: theumairtahir
body: do not use nullable `?` if there's a default value
created_at: 2025-05-28T13:10:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,18 @@
+namespace Dointo.AiRecruiter.Dtos;
+
+public class JobPostDto
path: src/AiRecruiterCodeBase/Dointo.AiRecruiter.Dtos/JobPostDto.cs
user: login: theumairtahir
body: What is the different in purpose for `JobPostDto` vs `EditJobPostDto`
created_at: 2025-05-28T13:12:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,7 +16,12 @@ public class JobPostsController(IJobPostsService service) : ControllerBase
 	[HttpGet("{id}")]
 	[ProducesResponseType(StatusCodes.Status200OK)]
 	[ProducesResponseType(StatusCodes.Status404NotFound)]
-	public async Task<IActionResult> GetJobPost(string id) => Ok(await _service.GetByIdAsync(id));
path: src/AiRecruiterCodeBase/Dointo.AiRecruiter.RestApi/Controllers/JobPostsController.cs
user: login: theumairtahir
body: revert this change. If this is intended, then please share the reason
created_at: 2025-05-28T13:13:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -33,11 +40,23 @@ public async Task<IActionResult> CreateOrUpdateJobPost([FromBody] EditJobDto dto
 		return Ok(result);
 	}
 
+
 	// ✅ DELETE: api/JobPosts/{id}
 	[HttpDelete("{id}")]
 	[ProducesResponseType(StatusCodes.Status204NoContent)]
 	[ProducesResponseType(StatusCodes.Status404NotFound)]
-	public async Task<IActionResult> DeleteJobPost(string id) => Ok(await _service.DeleteAsync(id));
+	public async Task<IActionResult> DeleteJobPost(string id)
+	{
+		var result = await _service.DeleteAsync(id);
+
+		if (result is BusinessErrorState error)
+			return BadRequest(new { success = false, message = error.Message });
path: src/AiRecruiterCodeBase/Dointo.AiRecruiter.RestApi/Controllers/JobPostsController.cs
user: login: theumairtahir
body: Do not manipulate service messages here. The application is intelligent enough to send the appropriate response type if required. You just need to return `Ok()`
created_at: 2025-05-28T13:15:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -37,18 +37,23 @@ class ScrollTopComponent {
   }
 
   private _handlers = () => {
-    let timer: number
+    let timer: number | undefined = undefined; // ✅ Declare with proper type and initial value
path: src/AiRecruiterCodeBase/dointo.airecruiter.web/src/_metronic/assets/ts/components/_ScrollTopComponent.ts
user: login: theumairtahir
body: revert changes in this file, these should not be committed
created_at: 2025-05-28T13:18:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -10,7 +10,26 @@ import axios from "axios";
 import toastr from "toastr";
 import "toastr/build/toastr.min.css";
 
+interface JobPost {
+	id: string;
+	jobTitle: string;
+	yearsOfExperience: number;
+	jobDescription: string;
+	requiredSkills: string[];
+	budgetAmount: number;
+	budgetCurrency: string;
+	status: "Open" | "Closed";
+	hasInterviews?: boolean;
+	additionalQuestions?: string;
+}
+
+interface JobPostResponse {
+	success?: boolean;
+	message?: string;
+	errors?: { propertyName: string; errorMessage: string }[];
+}
 function JobPost() {
+	const [status, setStatus] = useState<string>("");
path: src/AiRecruiterCodeBase/dointo.airecruiter.web/src/app/modules/jobs/JobPost.tsx
user: login: theumairtahir
body: Removed these unused variables
created_at: 2025-05-28T13:19:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -75,18 +82,65 @@ public async Task<IProcessingState> GetByIdAsync(string id)
 	public async Task<IProcessingState> DeleteAsync(string id)
 	{
 		_messageBuilder.Clear( );
+
 		var jobPost = await _repository.GetByIdAsync(id);
 		if (jobPost is null)
 			return new BusinessErrorState(RecordNotFoundMessage( ));
 
+		if (jobPost.HasInterviews == true)
+			return new BusinessErrorState("This job post cannot be deleted because interviews have already occurred. You may close it instead.");
+
 		jobPost.IsDeleted = true;
 		await _repository.SaveAsync(jobPost, string.Empty);
-		return new SuccessState(_messageBuilder.AddFormat(Messages.RECORD_DELETED_FORMAT).AddString(JOB_STRING).Build( ));
+
+		return new SuccessState(
+		 _messageBuilder.AddFormat(Messages.RECORD_DELETED_FORMAT).AddString(JOB_STRING).Build( )
+		);
+	}
+
+
+	public async Task<IProcessingState> CloseMultipleJobsAsync(CloseMultipleJobsDto closeJobDto)
+	{
+		if (closeJobDto is { JobIds.Count: 0 })
+			return new BusinessErrorState(Messages.JOB_NOT_CLOSED);
+		foreach (var jobId in closeJobDto.JobIds)
+		{
+			var job = await _repository.GetByIdAsync(jobId);
+			if (job is null)
+				continue;
+			job.Status = JobStatus.Closed;
+			job.ClosedReason = !string.IsNullOrEmpty(closeJobDto.Reason) ? closeJobDto.Reason : Messages.JOB_CLOSE_NO_REASON;
+		}
+		return new SuccessState(Messages.JOB_CLOSED);
 	}
 
 	private string RecordNotFoundMessage( )
 	{
 		_messageBuilder.Clear( );
 		return _messageBuilder.AddFormat(Messages.RECORD_NOT_FOUND_FORMAT).AddString(JOB_STRING).Build( );
 	}
+	public async Task<JobPostDto> GetJobStatusByIdAsync(string id)
+	{
+		var job = await _repository.GetByIdAsync(id);
+		if (job == null) return null;
path: src/AiRecruiterCodeBase/Dointo.AiRecruiter.Application/Services/JobPostsService.cs
user: login: Copilot
body: Returning null from GetJobStatusByIdAsync can lead to ambiguity for consumers of this API. Consider either updating the method signature to allow nullable returns or throwing a not found exception to make the API behavior explicit.
created_at: 2025-05-28T13:23:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -75,18 +82,65 @@ public async Task<IProcessingState> GetByIdAsync(string id)
 	public async Task<IProcessingState> DeleteAsync(string id)
 	{
 		_messageBuilder.Clear( );
+
 		var jobPost = await _repository.GetByIdAsync(id);
 		if (jobPost is null)
 			return new BusinessErrorState(RecordNotFoundMessage( ));
 
+		if (jobPost.HasInterviews == true)
+			return new BusinessErrorState("This job post cannot be deleted because interviews have already occurred. You may close it instead.");
+
 		jobPost.IsDeleted = true;
 		await _repository.SaveAsync(jobPost, string.Empty);
-		return new SuccessState(_messageBuilder.AddFormat(Messages.RECORD_DELETED_FORMAT).AddString(JOB_STRING).Build( ));
+
+		return new SuccessState(
+		 _messageBuilder.AddFormat(Messages.RECORD_DELETED_FORMAT).AddString(JOB_STRING).Build( )
+		);
+	}
+
+
+	public async Task<IProcessingState> CloseMultipleJobsAsync(CloseMultipleJobsDto closeJobDto)
+	{
+		if (closeJobDto is { JobIds.Count: 0 })
+			return new BusinessErrorState(Messages.JOB_NOT_CLOSED);
+		foreach (var jobId in closeJobDto.JobIds)
+		{
+			var job = await _repository.GetByIdAsync(jobId);
+			if (job is null)
+				continue;
+			job.Status = JobStatus.Closed;
+			job.ClosedReason = !string.IsNullOrEmpty(closeJobDto.Reason) ? closeJobDto.Reason : Messages.JOB_CLOSE_NO_REASON;
+		}
+		return new SuccessState(Messages.JOB_CLOSED);
 	}
 
 	private string RecordNotFoundMessage( )
 	{
 		_messageBuilder.Clear( );
 		return _messageBuilder.AddFormat(Messages.RECORD_NOT_FOUND_FORMAT).AddString(JOB_STRING).Build( );
 	}
+	public async Task<JobPostDto> GetJobStatusByIdAsync(string id)
+	{
+		var job = await _repository.GetByIdAsync(id);
+		if (job == null) return null;
path: src/AiRecruiterCodeBase/Dointo.AiRecruiter.Application/Services/JobPostsService.cs
user: login: theumairtahir
body: @Abdullah66438 
created_at: 2025-05-28T13:37:46Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-05-28T18:07:56Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=Tech-Flurry_ai-recruiter&pullRequest=26) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=Tech-Flurry_ai-recruiter&pullRequest=26&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [6 Accepted issues](https://sonarcloud.io/project/issues?id=Tech-Flurry_ai-recruiter&pullRequest=26&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=Tech-Flurry_ai-recruiter&pullRequest=26&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=Tech-Flurry_ai-recruiter&pullRequest=26&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=Tech-Flurry_ai-recruiter&pullRequest=26&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=Tech-Flurry_ai-recruiter&pullRequest=26)


author_association: NONE