feat: Add GDPR compliance and improve UI/UX

GDPR Compliance:
- Delete CV files immediately after processing
- Add DataRetentionJob to auto-purge data after 30 days
- Add DeleteAllUserDataAsync for right to erasure
- Add Privacy Policy page with GDPR information
- Add privacy link and GDPR badge to footer

UI/UX Improvements:
- Add "Why Choose RealCV" benefits section to homepage
- Fix pricing page: Professional card highlight, consistent button styles
- Improve text contrast on dark backgrounds (0.75 → 0.85 opacity)
- Fix auth page messaging consistency
- Fix CSS --realcv-accent variable (was self-referencing)

UK Terminology:
- Change "hiring" to "recruitment" throughout
- Change "bad hires" to "poor appointments"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-22 02:00:39 +00:00
parent 2b29e19306
commit 358b0328e7
13 changed files with 483 additions and 23 deletions

View File

@@ -185,17 +185,78 @@ public sealed class CVCheckService : ICVCheckService
var fileName = cvCheck.OriginalFileName;
// GDPR: Delete the uploaded file if it still exists
if (!string.IsNullOrWhiteSpace(cvCheck.BlobUrl))
{
try
{
await _fileStorageService.DeleteAsync(cvCheck.BlobUrl);
_logger.LogDebug("Deleted file for CV check {CheckId}", checkId);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to delete file for CV check {CheckId}", checkId);
// Continue with deletion even if file deletion fails
}
}
_dbContext.CVFlags.RemoveRange(cvCheck.Flags);
_dbContext.CVChecks.Remove(cvCheck);
await _dbContext.SaveChangesAsync();
_logger.LogInformation("Deleted CV check {CheckId} for user {UserId}", checkId, userId);
_logger.LogInformation("GDPR: Deleted CV check {CheckId} and associated data for user {UserId}", checkId, userId);
await _auditService.LogAsync(userId, AuditActions.CVDeleted, "CVCheck", checkId, $"File: {fileName}");
return true;
}
public async Task<int> DeleteAllUserDataAsync(Guid userId)
{
_logger.LogInformation("GDPR: Deleting all CV data for user {UserId}", userId);
var userChecks = await _dbContext.CVChecks
.Include(c => c.Flags)
.Where(c => c.UserId == userId)
.ToListAsync();
if (userChecks.Count == 0)
{
_logger.LogDebug("No CV checks found for user {UserId}", userId);
return 0;
}
var deletedCount = 0;
foreach (var check in userChecks)
{
// Delete the file if it exists
if (!string.IsNullOrWhiteSpace(check.BlobUrl))
{
try
{
await _fileStorageService.DeleteAsync(check.BlobUrl);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to delete file for CV check {CheckId}", check.Id);
}
}
_dbContext.CVFlags.RemoveRange(check.Flags);
_dbContext.CVChecks.Remove(check);
deletedCount++;
}
await _dbContext.SaveChangesAsync();
_logger.LogInformation("GDPR: Deleted {Count} CV checks for user {UserId}", deletedCount, userId);
await _auditService.LogAsync(userId, AuditActions.CVDeleted, null, null, $"Deleted all data: {deletedCount} checks");
return deletedCount;
}
private static CVCheckDto MapToDto(CVCheck cvCheck)
{
return new CVCheckDto