Add unsubscribe list management functionality
This commit introduces comprehensive functionality for managing unsubscribe lists within the application. Key changes include: - Creation of new DTOs, services, repositories, and controllers for unsubscribe list operations. - Updates to `MailingsController.cs` and `TestEmailListsController.cs` to include necessary using directives. - Registration of `IUnsubscribeListService` and `IUnsubscribeListRepository` in `Program.cs`. - Implementation of the `UnsubscribeListsController` with API endpoints for CRUD operations. - Introduction of the `UnsubscribeList` entity and its mapping to database columns. - Modifications to existing classes, including `Mailing` and `Target`, to reference unsubscribe lists. - Frontend updates to TypeScript interfaces and components for displaying and selecting unsubscribe lists. These enhancements provide a more robust email management system.
This commit is contained in:
parent
0208536f67
commit
5a6c57bade
@ -2,6 +2,7 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Surge365.Core.Controllers;
|
||||
using Surge365.MassEmailReact.Application.DTOs;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using Surge365.MassEmailReact.Domain.Enums;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Surge365.Core.Controllers;
|
||||
using Surge365.MassEmailReact.Application.DTOs;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
|
||||
@ -0,0 +1,93 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Surge365.MassEmailReact.Application.DTOs;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Surge365.MassEmailReact.API.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class UnsubscribeListsController : ControllerBase
|
||||
{
|
||||
private readonly IUnsubscribeListService _unsubscribeListService;
|
||||
|
||||
public UnsubscribeListsController(IUnsubscribeListService unsubscribeListService)
|
||||
{
|
||||
_unsubscribeListService = unsubscribeListService;
|
||||
}
|
||||
|
||||
[HttpGet("GetByCode/{unsubscribeListCode}")]
|
||||
public async Task<IActionResult> GetByCode(string unsubscribeListCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
var unsubscribeList = await _unsubscribeListService.GetByCodeAsync(unsubscribeListCode);
|
||||
if (unsubscribeList == null)
|
||||
return NotFound();
|
||||
|
||||
return Ok(unsubscribeList);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, $"An error occurred: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("GetAll")]
|
||||
public async Task<IActionResult> GetAll([FromQuery] bool activeOnly = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var unsubscribeLists = await _unsubscribeListService.GetAllAsync(activeOnly);
|
||||
return Ok(unsubscribeLists);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, $"An error occurred: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("Create")]
|
||||
public async Task<IActionResult> Create([FromBody] UnsubscribeListUpdateDto unsubscribeListDto)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(ModelState);
|
||||
|
||||
var success = await _unsubscribeListService.CreateAsync(unsubscribeListDto);
|
||||
if (success)
|
||||
return Ok(new { success = true });
|
||||
else
|
||||
return BadRequest("Failed to create unsubscribe list");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, $"An error occurred: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("Update")]
|
||||
public async Task<IActionResult> Update([FromBody] UnsubscribeListUpdateDto unsubscribeListDto)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(ModelState);
|
||||
|
||||
var success = await _unsubscribeListService.UpdateAsync(unsubscribeListDto);
|
||||
if (success)
|
||||
return Ok(new { success = true });
|
||||
else
|
||||
return BadRequest("Failed to update unsubscribe list");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, $"An error occurred: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -69,6 +69,8 @@ try
|
||||
builder.Services.AddScoped<IBouncedEmailRepository, BouncedEmailRepository>();
|
||||
builder.Services.AddScoped<IUnsubscribeUrlService, UnsubscribeUrlService>();
|
||||
builder.Services.AddScoped<IUnsubscribeUrlRepository, UnsubscribeUrlRepository>();
|
||||
builder.Services.AddScoped<IUnsubscribeListService, UnsubscribeListService>();
|
||||
builder.Services.AddScoped<IUnsubscribeListRepository, UnsubscribeListRepository>();
|
||||
builder.Services.AddScoped<ITemplateService, TemplateService>();
|
||||
builder.Services.AddScoped<ITemplateRepository, TemplateRepository>();
|
||||
builder.Services.AddScoped<IEmailDomainService, EmailDomainService>();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Surge365.MassEmailReact.Domain.Entities
|
||||
namespace Surge365.MassEmailReact.Application.DTOs
|
||||
{
|
||||
public class MailingTemplateUpdateDto
|
||||
{
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Surge365.MassEmailReact.Domain.Entities
|
||||
namespace Surge365.MassEmailReact.Application.DTOs
|
||||
{
|
||||
public class MailingUpdateDto
|
||||
{
|
||||
@ -15,6 +15,7 @@ namespace Surge365.MassEmailReact.Domain.Entities
|
||||
public Guid? SessionActivityId { get; set; }
|
||||
public string? RecurringTypeCode { get; set; }
|
||||
public DateTime? RecurringStartDate { get; set; }
|
||||
public string? UnsubscribeListCode { get; set; }
|
||||
public MailingTemplateUpdateDto Template { get; set; } = new MailingTemplateUpdateDto();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Surge365.MassEmailReact.Domain.Entities
|
||||
namespace Surge365.MassEmailReact.Application.DTOs
|
||||
{
|
||||
public class TargetColumnUpdateDto
|
||||
{
|
||||
|
||||
@ -4,7 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Surge365.MassEmailReact.Domain.Entities
|
||||
namespace Surge365.MassEmailReact.Application.DTOs
|
||||
{
|
||||
public class TargetUpdateDto
|
||||
{
|
||||
@ -16,6 +16,7 @@ namespace Surge365.MassEmailReact.Domain.Entities
|
||||
public string FilterQuery { get; set; } = "";
|
||||
public bool AllowWriteBack { get; set; } = false;
|
||||
public bool IsActive { get; set; } = true;
|
||||
public string? DefaultUnsubscribeListCode { get; set; }
|
||||
public List<TargetColumnUpdateDto> Columns { get; set; } = new List<TargetColumnUpdateDto>();
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,6 @@
|
||||
namespace Surge365.MassEmailReact.Domain.Entities
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Surge365.MassEmailReact.Application.DTOs
|
||||
{
|
||||
public class TestEmailListUpdateDto
|
||||
{
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Surge365.MassEmailReact.Domain.Entities
|
||||
namespace Surge365.MassEmailReact.Application.DTOs
|
||||
{
|
||||
public class TestMailingDto
|
||||
{
|
||||
|
||||
@ -4,7 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Surge365.MassEmailReact.Domain.Entities
|
||||
namespace Surge365.MassEmailReact.Application.DTOs
|
||||
{
|
||||
public class TestTargetDto
|
||||
{
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Surge365.MassEmailReact.Application.DTOs
|
||||
{
|
||||
public class UnsubscribeListUpdateDto
|
||||
{
|
||||
public string UnsubscribeListCode { get; set; } = "";
|
||||
public string FriendlyName { get; set; } = "";
|
||||
public string? FriendlyDescription { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public short DisplayOrder { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using Surge365.MassEmailReact.Application.DTOs;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using Surge365.MassEmailReact.Application.DTOs;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Surge365.MassEmailReact.Application.Interfaces
|
||||
{
|
||||
public interface IUnsubscribeListRepository
|
||||
{
|
||||
Task<UnsubscribeList?> GetByCodeAsync(string unsubscribeListCode);
|
||||
Task<List<UnsubscribeList>> GetAllAsync(bool activeOnly = true);
|
||||
Task<bool> CreateAsync(UnsubscribeList unsubscribeList);
|
||||
Task<bool> UpdateAsync(UnsubscribeList unsubscribeList);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
using Surge365.MassEmailReact.Application.DTOs;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Surge365.MassEmailReact.Application.Interfaces
|
||||
{
|
||||
public interface IUnsubscribeListService
|
||||
{
|
||||
Task<UnsubscribeList?> GetByCodeAsync(string unsubscribeListCode);
|
||||
Task<List<UnsubscribeList>> GetAllAsync(bool activeOnly = true);
|
||||
Task<bool> CreateAsync(UnsubscribeListUpdateDto unsubscribeListDto);
|
||||
Task<bool> UpdateAsync(UnsubscribeListUpdateDto unsubscribeListDto);
|
||||
}
|
||||
}
|
||||
@ -16,13 +16,15 @@ namespace Surge365.MassEmailReact.Domain.Entities
|
||||
public DateTime UpdateDate { get; set; } = DateTime.Now;
|
||||
public string? RecurringTypeCode { get; set; }
|
||||
public DateTime? RecurringStartDate { get; set; }
|
||||
public string? UnsubscribeListCode { get; set; }
|
||||
public MailingTemplate Template { get; set; } = new MailingTemplate();
|
||||
|
||||
public Mailing() { }
|
||||
|
||||
private Mailing(int id, string name, string description, int templateId, int targetId,
|
||||
string statusCode, DateTime? scheduleDate, DateTime? sentDate, DateTime createDate,
|
||||
DateTime updateDate, string? recurringTypeCode, DateTime? recurringStartDate)
|
||||
DateTime updateDate, string? recurringTypeCode, DateTime? recurringStartDate,
|
||||
string? unsubscribeListCode)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
@ -36,14 +38,17 @@ namespace Surge365.MassEmailReact.Domain.Entities
|
||||
UpdateDate = updateDate;
|
||||
RecurringTypeCode = recurringTypeCode;
|
||||
RecurringStartDate = recurringStartDate;
|
||||
UnsubscribeListCode = unsubscribeListCode;
|
||||
}
|
||||
|
||||
public static Mailing Create(int id, string name, string description, int templateId, int targetId,
|
||||
string statusCode, DateTime? scheduleDate, DateTime? sentDate, DateTime createDate,
|
||||
DateTime updateDate, string? recurringTypeCode, DateTime? recurringStartDate)
|
||||
DateTime updateDate, string? recurringTypeCode, DateTime? recurringStartDate,
|
||||
string? unsubscribeListCode)
|
||||
{
|
||||
return new Mailing(id, name, description, templateId, targetId, statusCode, scheduleDate,
|
||||
sentDate, createDate, updateDate, recurringTypeCode, recurringStartDate);
|
||||
sentDate, createDate, updateDate, recurringTypeCode, recurringStartDate,
|
||||
unsubscribeListCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,10 +16,11 @@ namespace Surge365.MassEmailReact.Domain.Entities
|
||||
public string FilterQuery { get; set; } = "";
|
||||
public bool AllowWriteBack { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public string? DefaultUnsubscribeListCode { get; set; }
|
||||
public List<TargetColumn> Columns { get; set; } = new List<TargetColumn>();
|
||||
|
||||
public Target() { }
|
||||
private Target(int id, int serverId, string name, string databaseName, string viewName, string filterQuery, bool allowWriteBack, bool isActive)
|
||||
private Target(int id, int serverId, string name, string databaseName, string viewName, string filterQuery, bool allowWriteBack, bool isActive, string? defaultUnsubscribeListCode)
|
||||
{
|
||||
Id = id;
|
||||
ServerId = ServerId;
|
||||
@ -29,10 +30,11 @@ namespace Surge365.MassEmailReact.Domain.Entities
|
||||
FilterQuery = filterQuery;
|
||||
AllowWriteBack = allowWriteBack;
|
||||
IsActive = isActive;
|
||||
DefaultUnsubscribeListCode = defaultUnsubscribeListCode;
|
||||
}
|
||||
public static Target Create(int id, int serverId, string name, string databaseName, string viewName, string filterQuery, bool allowWriteBack, bool isActive)
|
||||
public static Target Create(int id, int serverId, string name, string databaseName, string viewName, string filterQuery, bool allowWriteBack, bool isActive, string? defaultUnsubscribeListCode)
|
||||
{
|
||||
return new Target(id, serverId, name, databaseName, viewName, filterQuery, allowWriteBack, isActive);
|
||||
return new Target(id, serverId, name, databaseName, viewName, filterQuery, allowWriteBack, isActive, defaultUnsubscribeListCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
36
Surge365.MassEmailReact.Domain/Entities/UnsubscribeList.cs
Normal file
36
Surge365.MassEmailReact.Domain/Entities/UnsubscribeList.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
|
||||
namespace Surge365.MassEmailReact.Domain.Entities
|
||||
{
|
||||
public class UnsubscribeList
|
||||
{
|
||||
public string UnsubscribeListCode { get; private set; } = "";
|
||||
public string FriendlyName { get; set; } = "";
|
||||
public string? FriendlyDescription { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public short DisplayOrder { get; set; } = 0;
|
||||
public DateTime CreateDate { get; set; } = DateTime.Now;
|
||||
public DateTime UpdateDate { get; set; } = DateTime.Now;
|
||||
|
||||
public UnsubscribeList() { }
|
||||
|
||||
private UnsubscribeList(string unsubscribeListCode, string friendlyName, string? friendlyDescription,
|
||||
bool isActive, short displayOrder, DateTime createDate, DateTime updateDate)
|
||||
{
|
||||
UnsubscribeListCode = unsubscribeListCode;
|
||||
FriendlyName = friendlyName;
|
||||
FriendlyDescription = friendlyDescription;
|
||||
IsActive = isActive;
|
||||
DisplayOrder = displayOrder;
|
||||
CreateDate = createDate;
|
||||
UpdateDate = updateDate;
|
||||
}
|
||||
|
||||
public static UnsubscribeList Create(string unsubscribeListCode, string friendlyName, string? friendlyDescription,
|
||||
bool isActive, short displayOrder, DateTime createDate, DateTime updateDate)
|
||||
{
|
||||
return new UnsubscribeList(unsubscribeListCode, friendlyName, friendlyDescription, isActive,
|
||||
displayOrder, createDate, updateDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,6 +12,7 @@ namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
QueryMapper.AddMap(new TestEmailListMap());
|
||||
QueryMapper.AddMap(new BouncedEmailMap());
|
||||
QueryMapper .AddMap(new UnsubscribeUrlMap());
|
||||
QueryMapper.AddMap(new UnsubscribeListMap());
|
||||
QueryMapper.AddMap(new TemplateMap());
|
||||
QueryMapper.AddMap(new EmailDomainMap());
|
||||
QueryMapper .AddMap(new MailingMap());
|
||||
|
||||
@ -19,6 +19,7 @@ namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
Map(m => m.UpdateDate).ToColumn("update_date");
|
||||
Map(m => m.RecurringTypeCode).ToColumn("blast_recurring_type_code");
|
||||
Map(m => m.RecurringStartDate).ToColumn("recurring_start_date");
|
||||
Map(m => m.UnsubscribeListCode).ToColumn("unsubscribe_list_code");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -20,6 +20,7 @@ namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
Map(t => t.FilterQuery).ToColumn("filter_query");
|
||||
Map(t => t.AllowWriteBack).ToColumn("allow_write_back");
|
||||
Map(t => t.IsActive).ToColumn("is_active");
|
||||
Map(t => t.DefaultUnsubscribeListCode).ToColumn("default_unsubscribe_list_code");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
{
|
||||
public class UnsubscribeListMap : EntityMap<UnsubscribeList>
|
||||
{
|
||||
public UnsubscribeListMap()
|
||||
{
|
||||
Map(u => u.UnsubscribeListCode).ToColumn("unsubscribe_list_code");
|
||||
Map(u => u.FriendlyName).ToColumn("friendly_name");
|
||||
Map(u => u.FriendlyDescription).ToColumn("friendly_description");
|
||||
Map(u => u.IsActive).ToColumn("is_active");
|
||||
Map(u => u.DisplayOrder).ToColumn("display_order");
|
||||
Map(u => u.CreateDate).ToColumn("create_date");
|
||||
Map(u => u.UpdateDate).ToColumn("update_date");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -259,6 +259,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
new SqlParameter("@sent_date", mailing.SentDate ?? (object)DBNull.Value),
|
||||
new SqlParameter("@blast_recurring_type_code", mailing.RecurringTypeCode ?? (object)DBNull.Value),
|
||||
new SqlParameter("@recurring_start_date", mailing.RecurringStartDate ?? (object)DBNull.Value),
|
||||
new SqlParameter("@unsubscribe_list_code", mailing.UnsubscribeListCode ?? (object)DBNull.Value),
|
||||
new SqlParameter("@template_json", JsonSerializer.Serialize(mailing.Template, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower })),
|
||||
pmSuccess
|
||||
};
|
||||
@ -294,6 +295,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
new SqlParameter("@sent_date", mailing.SentDate ?? (object)DBNull.Value),
|
||||
new SqlParameter("@blast_recurring_type_code", mailing.RecurringTypeCode ?? (object)DBNull.Value),
|
||||
new SqlParameter("@recurring_start_date", mailing.RecurringStartDate ?? (object)DBNull.Value),
|
||||
new SqlParameter("@unsubscribe_list_code", mailing.UnsubscribeListCode ?? (object)DBNull.Value),
|
||||
new SqlParameter("@template_json", JsonSerializer.Serialize(mailing.Template, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower })),
|
||||
pmSuccess
|
||||
};
|
||||
|
||||
@ -118,6 +118,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
new SqlParameter("@filter_query", target.FilterQuery),
|
||||
new SqlParameter("@allow_write_back", target.AllowWriteBack),
|
||||
new SqlParameter("@is_active", target.IsActive),
|
||||
new SqlParameter("@default_unsubscribe_list_code", target.DefaultUnsubscribeListCode ?? (object)DBNull.Value),
|
||||
new SqlParameter("@column_json", target.Columns != null ? JsonSerializer.Serialize(target.Columns) : (object)DBNull.Value),
|
||||
pmSuccess
|
||||
};
|
||||
@ -152,6 +153,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
new SqlParameter("@filter_query", target.FilterQuery),
|
||||
new SqlParameter("@allow_write_back", target.AllowWriteBack),
|
||||
new SqlParameter("@is_active", target.IsActive),
|
||||
new SqlParameter("@default_unsubscribe_list_code", target.DefaultUnsubscribeListCode ?? (object)DBNull.Value),
|
||||
new SqlParameter("@column_json", target.Columns != null ? JsonSerializer.Serialize(target.Columns) : (object)DBNull.Value),
|
||||
pmSuccess
|
||||
};
|
||||
|
||||
@ -0,0 +1,122 @@
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Surge365.Core.Interfaces;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.Core.Services;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
{
|
||||
public class UnsubscribeListRepository : IUnsubscribeListRepository
|
||||
{
|
||||
private IConfiguration _config;
|
||||
private DataAccessFactory _dataAccessFactory;
|
||||
private IQueryMapper _queryMapper;
|
||||
public DataAccess GetDataAccess(string connectionStringName = "MassEmail") => _dataAccessFactory.Get(connectionStringName) ?? throw new ArgumentNullException(nameof(_dataAccessFactory), $"DataAccess context for '{connectionStringName}' not found.");
|
||||
|
||||
public UnsubscribeListRepository(IConfiguration config, DataAccessFactory dataAccessFactory, IQueryMapper queryMapper)
|
||||
{
|
||||
_config = config;
|
||||
_dataAccessFactory = dataAccessFactory ?? throw new ArgumentNullException(nameof(dataAccessFactory), "DataAccessFactory cannot be null.");
|
||||
_queryMapper = queryMapper;
|
||||
#if DEBUG
|
||||
if (!_queryMapper.EntityMaps.ContainsKey(typeof(UnsubscribeList)))
|
||||
{
|
||||
throw new InvalidOperationException("UnsubscribeList query mapping is missing. Make sure ConfigureCustomMaps() is called inside program.cs (program startup).");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public async Task<UnsubscribeList?> GetByCodeAsync(string unsubscribeListCode)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_config);
|
||||
|
||||
var parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@unsubscribe_list_code", unsubscribeListCode)
|
||||
};
|
||||
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_unsubscribe_list_by_code");
|
||||
|
||||
var results = await _queryMapper.MapAsync<UnsubscribeList>(dataSet);
|
||||
return results.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task<List<UnsubscribeList>> GetAllAsync(bool activeOnly = true)
|
||||
{
|
||||
var parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@active_only", activeOnly)
|
||||
};
|
||||
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_unsubscribe_list_all");
|
||||
|
||||
var results = await _queryMapper.MapAsync<UnsubscribeList>(dataSet);
|
||||
return results.ToList();
|
||||
}
|
||||
|
||||
public async Task<bool> CreateAsync(UnsubscribeList unsubscribeList)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(unsubscribeList);
|
||||
if (string.IsNullOrWhiteSpace(unsubscribeList.UnsubscribeListCode))
|
||||
throw new Exception("UnsubscribeListCode cannot be null or empty");
|
||||
|
||||
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
|
||||
List<SqlParameter> parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@unsubscribe_list_code", unsubscribeList.UnsubscribeListCode),
|
||||
new SqlParameter("@friendly_name", unsubscribeList.FriendlyName),
|
||||
new SqlParameter("@friendly_description", unsubscribeList.FriendlyDescription ?? (object)DBNull.Value),
|
||||
new SqlParameter("@is_active", unsubscribeList.IsActive),
|
||||
new SqlParameter("@display_order", unsubscribeList.DisplayOrder),
|
||||
pmSuccess
|
||||
};
|
||||
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_unsubscribe_list");
|
||||
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(UnsubscribeList unsubscribeList)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(unsubscribeList);
|
||||
if (string.IsNullOrWhiteSpace(unsubscribeList.UnsubscribeListCode))
|
||||
throw new Exception("UnsubscribeListCode cannot be null or empty");
|
||||
|
||||
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
|
||||
List<SqlParameter> parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@unsubscribe_list_code", unsubscribeList.UnsubscribeListCode),
|
||||
new SqlParameter("@friendly_name", unsubscribeList.FriendlyName),
|
||||
new SqlParameter("@friendly_description", unsubscribeList.FriendlyDescription ?? (object)DBNull.Value),
|
||||
new SqlParameter("@is_active", unsubscribeList.IsActive),
|
||||
new SqlParameter("@display_order", unsubscribeList.DisplayOrder),
|
||||
pmSuccess
|
||||
};
|
||||
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_unsubscribe_list");
|
||||
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -111,6 +111,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Services
|
||||
SentDate = mailingDto.SentDate,
|
||||
RecurringTypeCode = mailingDto.RecurringTypeCode,
|
||||
RecurringStartDate = mailingDto.RecurringStartDate,
|
||||
UnsubscribeListCode = mailingDto.UnsubscribeListCode,
|
||||
Template = new MailingTemplate
|
||||
{
|
||||
DomainId = mailingDto.Template.DomainId,
|
||||
@ -139,6 +140,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Services
|
||||
mailing.SentDate = mailingDto.SentDate;
|
||||
mailing.RecurringTypeCode = mailingDto.RecurringTypeCode;
|
||||
mailing.RecurringStartDate = mailingDto.RecurringStartDate;
|
||||
mailing.UnsubscribeListCode = mailingDto.UnsubscribeListCode;
|
||||
mailing.Template = new MailingTemplate
|
||||
{
|
||||
DomainId = mailingDto.Template.DomainId,
|
||||
|
||||
@ -9,8 +9,8 @@ using System.IdentityModel.Tokens.Jwt;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using System.Security.Cryptography;
|
||||
using Surge365.MassEmailReact.Application.DTOs;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
@ -53,6 +53,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Services
|
||||
target.FilterQuery = targetDto.FilterQuery;
|
||||
target.AllowWriteBack = targetDto.AllowWriteBack;
|
||||
target.IsActive = targetDto.IsActive;
|
||||
target.DefaultUnsubscribeListCode = targetDto.DefaultUnsubscribeListCode;
|
||||
|
||||
target.Columns = new List<TargetColumn>();
|
||||
foreach (var columnDto in targetDto.Columns)
|
||||
@ -77,6 +78,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Services
|
||||
target.FilterQuery = targetDto.FilterQuery;
|
||||
target.AllowWriteBack = targetDto.AllowWriteBack;
|
||||
target.IsActive = targetDto.IsActive;
|
||||
target.DefaultUnsubscribeListCode = targetDto.DefaultUnsubscribeListCode;
|
||||
|
||||
target.Columns = new List<TargetColumn>();
|
||||
foreach (var columnDto in targetDto.Columns)
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Application.DTOs;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@ -0,0 +1,66 @@
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Application.DTOs;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.Services
|
||||
{
|
||||
public class UnsubscribeListService : IUnsubscribeListService
|
||||
{
|
||||
private readonly IUnsubscribeListRepository _unsubscribeListRepository;
|
||||
private readonly IConfiguration _config;
|
||||
|
||||
public UnsubscribeListService(IUnsubscribeListRepository unsubscribeListRepository, IConfiguration config)
|
||||
{
|
||||
_unsubscribeListRepository = unsubscribeListRepository;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public async Task<UnsubscribeList?> GetByCodeAsync(string unsubscribeListCode)
|
||||
{
|
||||
return await _unsubscribeListRepository.GetByCodeAsync(unsubscribeListCode);
|
||||
}
|
||||
|
||||
public async Task<List<UnsubscribeList>> GetAllAsync(bool activeOnly = true)
|
||||
{
|
||||
return await _unsubscribeListRepository.GetAllAsync(activeOnly);
|
||||
}
|
||||
|
||||
public async Task<bool> CreateAsync(UnsubscribeListUpdateDto unsubscribeListDto)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(unsubscribeListDto, nameof(unsubscribeListDto));
|
||||
if (string.IsNullOrWhiteSpace(unsubscribeListDto.UnsubscribeListCode))
|
||||
throw new Exception("UnsubscribeListCode cannot be null or empty");
|
||||
|
||||
var unsubscribeList = new UnsubscribeList
|
||||
{
|
||||
FriendlyName = unsubscribeListDto.FriendlyName,
|
||||
FriendlyDescription = unsubscribeListDto.FriendlyDescription,
|
||||
IsActive = unsubscribeListDto.IsActive,
|
||||
DisplayOrder = unsubscribeListDto.DisplayOrder
|
||||
};
|
||||
|
||||
return await _unsubscribeListRepository.CreateAsync(unsubscribeList);
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(UnsubscribeListUpdateDto unsubscribeListDto)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(unsubscribeListDto, nameof(unsubscribeListDto));
|
||||
if (string.IsNullOrWhiteSpace(unsubscribeListDto.UnsubscribeListCode))
|
||||
throw new Exception("UnsubscribeListCode cannot be null or empty");
|
||||
|
||||
var unsubscribeList = await _unsubscribeListRepository.GetByCodeAsync(unsubscribeListDto.UnsubscribeListCode);
|
||||
if (unsubscribeList == null) return false;
|
||||
|
||||
unsubscribeList.FriendlyName = unsubscribeListDto.FriendlyName;
|
||||
unsubscribeList.FriendlyDescription = unsubscribeListDto.FriendlyDescription;
|
||||
unsubscribeList.IsActive = unsubscribeListDto.IsActive;
|
||||
unsubscribeList.DisplayOrder = unsubscribeListDto.DisplayOrder;
|
||||
|
||||
return await _unsubscribeListRepository.UpdateAsync(unsubscribeList);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -19,6 +19,7 @@
|
||||
<None Remove="src\components\layouts\Layout_backup.tsx" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
<Folder Include="src\hooks\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -23,6 +23,7 @@ import Template from "@/types/template";
|
||||
import Mailing from "@/types/mailing";
|
||||
import TargetSample from "@/types/targetSample";
|
||||
import Target from "@/types/target";
|
||||
import UnsubscribeList from "@/types/unsubscribeList";
|
||||
//import MailingTemplate from "@/types/mailingTemplate";
|
||||
//import MailingTarget from "@/types/mailingTarget";
|
||||
import EmailList from "@/components/forms/EmailList";
|
||||
@ -82,6 +83,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
const [currentTarget, setCurrentTarget] = useState<Target | null>(null);
|
||||
const [targetSample, setTargetSample] = useState<TargetSample | null>(null);
|
||||
const [targetSampleLoading, setTargetSampleLoading] = useState(false);
|
||||
const [availableUnsubscribeLists, setAvailableUnsubscribeLists] = useState<UnsubscribeList[]>([]);
|
||||
|
||||
const defaultMailing: Mailing = {
|
||||
id: 0,
|
||||
@ -95,6 +97,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
sessionActivityId: null,
|
||||
recurringTypeCode: null,
|
||||
recurringStartDate: null,
|
||||
unsubscribeListCode: null,
|
||||
template: {
|
||||
id: 0,
|
||||
mailingId: 0,
|
||||
@ -208,6 +211,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
})
|
||||
: schema.nullable();
|
||||
}),
|
||||
unsubscribeListCode: yup.string().nullable().optional(),
|
||||
template: yup.object().shape({
|
||||
id: yup.number().nullable().default(0),
|
||||
mailingId: yup.number().default(0),
|
||||
@ -254,6 +258,39 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Function to fetch unsubscribe list by code if not in active list
|
||||
const fetchUnsubscribeListByCode = async (code: string): Promise<UnsubscribeList | null> => {
|
||||
try {
|
||||
const response = await customFetch(`/api/unsubscribeLists/GetByCode/${code}`);
|
||||
if (!response.ok) return null;
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("Error fetching unsubscribe list by code:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Function to update available unsubscribe lists based on target selection
|
||||
const updateAvailableUnsubscribeLists = async (selectedTarget: Target | null) => {
|
||||
let lists = [...setupData.unsubscribeLists];
|
||||
|
||||
if (selectedTarget?.defaultUnsubscribeListCode) {
|
||||
// Check if the target's default unsubscribe list is already in the active list
|
||||
const isDefaultInActiveList = lists.some(
|
||||
list => list.unsubscribeListCode === selectedTarget.defaultUnsubscribeListCode
|
||||
);
|
||||
|
||||
// If not in active list, fetch it and add it
|
||||
if (!isDefaultInActiveList) {
|
||||
const defaultList = await fetchUnsubscribeListByCode(selectedTarget.defaultUnsubscribeListCode);
|
||||
if (defaultList) {
|
||||
lists = [...lists, defaultList];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setAvailableUnsubscribeLists(lists);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const initializeMailingEdit = async () => {
|
||||
@ -270,6 +307,10 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
setRecurring(false);
|
||||
setScheduleForLater(false);
|
||||
reset(mailing || defaultMailing, { keepDefaultValues: true });
|
||||
|
||||
// Initialize available unsubscribe lists
|
||||
setAvailableUnsubscribeLists(setupData.unsubscribeLists);
|
||||
|
||||
if (setupData.testEmailLists.length > 0) {
|
||||
setTestEmailListId(setupData.testEmailLists[0].id);
|
||||
setEmails(setupData.testEmailLists[0].emails);
|
||||
@ -286,6 +327,13 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
if (mailing?.targetId) {
|
||||
const target = setupData.targets.find(t => t.id === mailing.targetId) || null;
|
||||
setCurrentTarget(target);
|
||||
// Update available unsubscribe lists and set default if needed
|
||||
await updateAvailableUnsubscribeLists(target);
|
||||
|
||||
// If this is a new mailing or no unsubscribe list is set, default to target's default
|
||||
if (target && (!mailing?.unsubscribeListCode || mailing.id === 0) && target.defaultUnsubscribeListCode) {
|
||||
setValue("unsubscribeListCode", target.defaultUnsubscribeListCode);
|
||||
}
|
||||
} else {
|
||||
setCurrentTarget(null);
|
||||
}
|
||||
@ -293,7 +341,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
};
|
||||
|
||||
initializeMailingEdit();
|
||||
}, [open, mailing, reset, setupData.testEmailLists, setupData.targets, setupData.templates]);
|
||||
}, [open, mailing, reset, setupData.testEmailLists, setupData.targets, setupData.templates, setupData.unsubscribeLists]);
|
||||
|
||||
const handleSave = async (formData: Mailing) => {
|
||||
const apiUrl = isNew ? "/api/mailings" : `/api/mailings/${formData.id}`;
|
||||
@ -574,10 +622,20 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
options={filteredTargets}
|
||||
getOptionLabel={(option) => option.name}
|
||||
value={filteredTargets.find(t => t.id === field.value) || null}
|
||||
onChange={(_, newValue) => {
|
||||
onChange={async (_, newValue) => {
|
||||
field.onChange(newValue ? newValue.id : null);
|
||||
trigger("targetId");
|
||||
setCurrentTarget(newValue);
|
||||
|
||||
// Update available unsubscribe lists when target changes
|
||||
await updateAvailableUnsubscribeLists(newValue);
|
||||
|
||||
// Set default unsubscribe list from target if available
|
||||
if (newValue?.defaultUnsubscribeListCode) {
|
||||
setValue("unsubscribeListCode", newValue.defaultUnsubscribeListCode);
|
||||
} else {
|
||||
setValue("unsubscribeListCode", null);
|
||||
}
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
@ -611,6 +669,32 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Controller
|
||||
name="unsubscribeListCode"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...field}
|
||||
options={availableUnsubscribeLists}
|
||||
getOptionLabel={(option) => option.friendlyName}
|
||||
value={availableUnsubscribeLists.find((list) => list.unsubscribeListCode === field.value) || null}
|
||||
onChange={(_, newValue) => {
|
||||
field.onChange(newValue ? newValue.unsubscribeListCode : null);
|
||||
trigger("unsubscribeListCode");
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Unsubscribe List"
|
||||
fullWidth
|
||||
margin="dense"
|
||||
error={!!errors.unsubscribeListCode}
|
||||
helperText={errors.unsubscribeListCode?.message || "Optional - Select an unsubscribe list for this mailing"}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Autocomplete
|
||||
options={setupData.testEmailLists}
|
||||
getOptionLabel={(option) => option.name}
|
||||
@ -741,7 +825,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => { onClose( 'cancelled'); }} disabled={loading}>Cancel</Button>
|
||||
<Button onClick={() => { onClose('cancelled'); }} disabled={loading}>Cancel</Button>
|
||||
<Button onClick={handleSubmit(handleSave)} color="primary" disabled={loading}>
|
||||
{loading ? "Saving..." : "Save"}
|
||||
</Button>
|
||||
|
||||
@ -51,6 +51,7 @@ const schema = yup.object().shape({
|
||||
filterQuery: yup.string().nullable(),
|
||||
allowWriteBack: yup.boolean().default(false),
|
||||
isActive: yup.boolean().default(true),
|
||||
defaultUnsubscribeListCode: yup.string().nullable().optional(),
|
||||
columns: yup
|
||||
.array().of(
|
||||
yup.object().shape({
|
||||
@ -84,6 +85,7 @@ const defaultTarget: Target = {
|
||||
filterQuery: "",
|
||||
allowWriteBack: false,
|
||||
isActive: true,
|
||||
defaultUnsubscribeListCode: null,
|
||||
columns: [],
|
||||
};
|
||||
|
||||
@ -315,6 +317,32 @@ const TargetEdit = ({ open, target, onClose, onSave }: TargetEditProps) => {
|
||||
error={!!errors.filterQuery}
|
||||
helperText={errors.filterQuery?.message}
|
||||
/>
|
||||
<Controller
|
||||
name="defaultUnsubscribeListCode"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...field}
|
||||
options={setupData.unsubscribeLists}
|
||||
getOptionLabel={(option) => option.friendlyName}
|
||||
value={setupData.unsubscribeLists.find((list) => list.unsubscribeListCode === field.value) || null}
|
||||
onChange={(_, newValue) => {
|
||||
field.onChange(newValue ? newValue.unsubscribeListCode : null);
|
||||
trigger("defaultUnsubscribeListCode");
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Default Unsubscribe List"
|
||||
fullWidth
|
||||
margin="dense"
|
||||
error={!!errors.defaultUnsubscribeListCode}
|
||||
helperText={errors.defaultUnsubscribeListCode?.message || "Optional - Select a default unsubscribe list for this target"}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={<Switch {...register("allowWriteBack")} />}
|
||||
label="Allow Write Back"
|
||||
|
||||
@ -53,6 +53,18 @@ function NewMailings() {
|
||||
minWidth: 160,
|
||||
valueGetter: (_: number, row: Mailing) => setupData.templates.find(t => t.id === row.templateId)?.subject || 'Unknown',
|
||||
},
|
||||
{
|
||||
field: "unsubscribeListCode",
|
||||
headerName: "Unsubscribe List",
|
||||
flex: 1,
|
||||
minWidth: 160,
|
||||
renderCell: (params: GridRenderCellParams<Mailing>) => {
|
||||
const unsubscribeList = setupData.unsubscribeLists.find(
|
||||
list => list.unsubscribeListCode === params.row.unsubscribeListCode
|
||||
);
|
||||
return unsubscribeList ? unsubscribeList.friendlyName : (params.row.unsubscribeListCode || "None");
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
const reloadMailings = async () => {
|
||||
@ -153,6 +165,13 @@ function NewMailings() {
|
||||
<Typography variant="body2">ID: {row.id}</Typography>
|
||||
<Typography variant="body2">Description: {row.description}</Typography>
|
||||
<Typography variant="body2">Subject: {setupData.templates.find(t => t.id === row.templateId)?.subject || 'Unknown'}</Typography>
|
||||
<Typography variant="body2">
|
||||
Unsubscribe List: {
|
||||
setupData.unsubscribeLists.find(
|
||||
list => list.unsubscribeListCode === row.unsubscribeListCode
|
||||
)?.friendlyName || row.unsubscribeListCode || "None"
|
||||
}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<IconButton onClick={(e) => { e.stopPropagation(); handleEdit(row); }}>
|
||||
<EditIcon />
|
||||
|
||||
@ -38,6 +38,18 @@ function Targets() {
|
||||
{ field: "databaseName", headerName: "Database", flex: 1, minWidth: 100 },
|
||||
{ field: "viewName", headerName: "View", flex: 1, minWidth: 300 },
|
||||
{ field: "filterQuery", headerName: "Filter Query", flex: 1, minWidth: 100 },
|
||||
{
|
||||
field: "defaultUnsubscribeListCode",
|
||||
headerName: "Default Unsubscribe List",
|
||||
flex: 1,
|
||||
minWidth: 180,
|
||||
renderCell: (params: GridRenderCellParams<Target>) => {
|
||||
const unsubscribeList = setupData.unsubscribeLists.find(
|
||||
list => list.unsubscribeListCode === params.row.defaultUnsubscribeListCode
|
||||
);
|
||||
return unsubscribeList ? unsubscribeList.friendlyName : (params.row.defaultUnsubscribeListCode || "None");
|
||||
}
|
||||
},
|
||||
{ field: "allowWriteBack", headerName: "Write Back", width: 150 },
|
||||
{ field: "isActive", headerName: "Active", width: 115 },
|
||||
];
|
||||
@ -102,6 +114,13 @@ function Targets() {
|
||||
<Typography variant="body2">Database: {row.databaseName}</Typography>
|
||||
<Typography variant="body2">View: {row.viewName}</Typography>
|
||||
<Typography variant="body2">Filter: {row.filterQuery}</Typography>
|
||||
<Typography variant="body2">
|
||||
Default Unsubscribe List: {
|
||||
setupData.unsubscribeLists.find(
|
||||
list => list.unsubscribeListCode === row.defaultUnsubscribeListCode
|
||||
)?.friendlyName || row.defaultUnsubscribeListCode || "None"
|
||||
}
|
||||
</Typography>
|
||||
<Typography variant="body2">Writeback: {row.allowWriteBack ? "Yes" : "No"}</Typography>
|
||||
<Typography variant="body2">Active: {row.isActive ? "Yes" : "No"}</Typography>
|
||||
</CardContent>
|
||||
|
||||
@ -4,6 +4,7 @@ import Server from "@/types/server";
|
||||
import TestEmailList from '@/types/testEmailList';
|
||||
import BouncedEmail from '@/types/bouncedEmail';
|
||||
import UnsubscribeUrl from '@/types/unsubscribeUrl';
|
||||
import UnsubscribeList from '@/types/unsubscribeList';
|
||||
import Template from '@/types/template';
|
||||
import EmailDomain from '@/types/emailDomain';
|
||||
import { useCustomFetchNoNavigate } from "@/utils/customFetch";
|
||||
@ -36,6 +37,11 @@ export type SetupData = {
|
||||
setUnsubscribeUrls: (updatedUnsubscribeUrl: UnsubscribeUrl) => void;
|
||||
unsubscribeUrlsLoading: boolean;
|
||||
|
||||
unsubscribeLists: UnsubscribeList[];
|
||||
reloadUnsubscribeLists: () => void;
|
||||
setUnsubscribeLists: (updatedUnsubscribeList: UnsubscribeList) => void;
|
||||
unsubscribeListsLoading: boolean;
|
||||
|
||||
templates: Template[];
|
||||
reloadTemplates: () => void;
|
||||
setTemplates: (updatedTemplate: Template) => void;
|
||||
@ -71,6 +77,9 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
||||
const [unsubscribeUrls, setUnsubscribeUrls] = useState<UnsubscribeUrl[]>([]);
|
||||
const [unsubscribeUrlsLoading, setUnsubscribeUrlsLoading] = useState<boolean>(false);
|
||||
|
||||
const [unsubscribeLists, setUnsubscribeLists] = useState<UnsubscribeList[]>([]);
|
||||
const [unsubscribeListsLoading, setUnsubscribeListsLoading] = useState<boolean>(false);
|
||||
|
||||
const [templates, setTemplates] = useState<Template[]>([]);
|
||||
const [templatesLoading, setTemplatesLoading] = useState<boolean>(false);
|
||||
|
||||
@ -109,6 +118,10 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
||||
let unsubscribeUrlsData: UnsubscribeUrl[] | null = null;
|
||||
let loadUnsubscribeUrls = true;
|
||||
|
||||
setUnsubscribeListsLoading(true);
|
||||
let unsubscribeListsData: UnsubscribeList[] | null = null;
|
||||
let loadUnsubscribeLists = true;
|
||||
|
||||
setTemplatesLoading(true);
|
||||
let templatesData: Template[] | null = null;
|
||||
let loadTemplates = true;
|
||||
@ -144,6 +157,11 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
||||
setUnsubscribeUrls(parsedData.unsubscribeUrls);
|
||||
setUnsubscribeUrlsLoading(false);
|
||||
}
|
||||
if (parsedData.unsubscribeLists) {
|
||||
loadUnsubscribeLists = false;
|
||||
setUnsubscribeLists(parsedData.unsubscribeLists);
|
||||
setUnsubscribeListsLoading(false);
|
||||
}
|
||||
if (parsedData.templates) {
|
||||
loadTemplates = false;
|
||||
setTemplates(parsedData.templates);
|
||||
@ -220,6 +238,19 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
||||
}
|
||||
}
|
||||
|
||||
if (loadUnsubscribeLists) {
|
||||
const unsubscribeListsResponse = await customFetch("/api/unsubscribeLists/GetAll?activeOnly=true");
|
||||
unsubscribeListsData = await unsubscribeListsResponse.json();
|
||||
if (unsubscribeListsData) {
|
||||
setUnsubscribeLists(unsubscribeListsData);
|
||||
setUnsubscribeListsLoading(false);
|
||||
}
|
||||
else {
|
||||
console.error("Failed to fetch unsubscribeLists");
|
||||
setUnsubscribeListsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (loadTemplates) {
|
||||
const templatesResponse = await customFetch("/api/templates/GetAll?activeOnly=false");
|
||||
templatesData = await templatesResponse.json();
|
||||
@ -247,13 +278,24 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
||||
}
|
||||
|
||||
setDataLoading(false);
|
||||
sessionStorage.setItem("setupData", JSON.stringify({ targets: targetsData, servers: serversData, testEmailLists: testEmailListsData, bouncedEmails: bouncedEmailsData, unsubscribeUrls: unsubscribeUrlsData, templates: templatesData, emailDomains: emailDomainsData }));
|
||||
sessionStorage.setItem("setupData", JSON.stringify({
|
||||
targets: targetsData,
|
||||
servers: serversData,
|
||||
testEmailLists: testEmailListsData,
|
||||
bouncedEmails: bouncedEmailsData,
|
||||
unsubscribeUrls: unsubscribeUrlsData,
|
||||
unsubscribeLists: unsubscribeListsData,
|
||||
templates: templatesData,
|
||||
emailDomains: emailDomainsData
|
||||
}));
|
||||
} catch (error) {
|
||||
setDataLoading(false);
|
||||
setTargetsLoading(false);
|
||||
setServersLoading(false);
|
||||
setTestEmailListsLoading(false);
|
||||
setBouncedEmailsLoading(false);
|
||||
setUnsubscribeUrlsLoading(false);
|
||||
setUnsubscribeListsLoading(false);
|
||||
setTemplatesLoading(false);
|
||||
setEmailDomainsLoading(false);
|
||||
console.error("Failed to fetch setup data:", error);
|
||||
@ -322,6 +364,17 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
||||
sessionStorage.setItem("setupData", JSON.stringify({ unsubscribeUrls, targets, testEmailLists, bouncedEmails }));
|
||||
};
|
||||
|
||||
const updateUnsubscribeListCache = (updatedUnsubscribeList: UnsubscribeList) => {
|
||||
setUnsubscribeLists((prevUnsubscribeLists) => {
|
||||
const unsubscribeListExists = prevUnsubscribeLists.some((unsubscribeList) => unsubscribeList.unsubscribeListCode === updatedUnsubscribeList.unsubscribeListCode);
|
||||
return unsubscribeListExists
|
||||
? prevUnsubscribeLists.map((unsubscribeList) => (unsubscribeList.unsubscribeListCode === updatedUnsubscribeList.unsubscribeListCode ? updatedUnsubscribeList : unsubscribeList))
|
||||
: [...prevUnsubscribeLists, updatedUnsubscribeList]; // Push new unsubscribeList if not found
|
||||
});
|
||||
|
||||
sessionStorage.setItem("setupData", JSON.stringify({ unsubscribeLists, targets, testEmailLists, bouncedEmails }));
|
||||
};
|
||||
|
||||
const updateTemplateCache = (updatedTemplate: Template) => {
|
||||
setTemplates((prevTemplates) => {
|
||||
const templateExists = prevTemplates.some((template) => template.id === updatedTemplate.id);
|
||||
@ -376,6 +429,11 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
||||
setUnsubscribeUrls: updateUnsubscribeUrlCache,
|
||||
unsubscribeUrlsLoading,
|
||||
|
||||
unsubscribeLists,
|
||||
reloadUnsubscribeLists: reloadSetupData,
|
||||
setUnsubscribeLists: updateUnsubscribeListCache,
|
||||
unsubscribeListsLoading,
|
||||
|
||||
templates,
|
||||
reloadTemplates: reloadSetupData,
|
||||
setTemplates: updateTemplateCache,
|
||||
|
||||
@ -12,6 +12,7 @@ export interface Mailing {
|
||||
sessionActivityId: string | null;
|
||||
recurringTypeCode: string | null;
|
||||
recurringStartDate: string | null;
|
||||
unsubscribeListCode: string | null;
|
||||
template: MailingTemplate | null;
|
||||
target: MailingTarget | null;
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ export interface Target {
|
||||
filterQuery: string;
|
||||
allowWriteBack: boolean;
|
||||
isActive: boolean;
|
||||
defaultUnsubscribeListCode: string | null;
|
||||
columns: TargetColumn[];
|
||||
}
|
||||
|
||||
|
||||
11
Surge365.MassEmailReact.Web/src/types/unsubscribeList.ts
Normal file
11
Surge365.MassEmailReact.Web/src/types/unsubscribeList.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export interface UnsubscribeList {
|
||||
unsubscribeListCode: string;
|
||||
friendlyName: string;
|
||||
friendlyDescription: string | null;
|
||||
isActive: boolean;
|
||||
displayOrder: number;
|
||||
createDate: string;
|
||||
updateDate: string;
|
||||
}
|
||||
|
||||
export default UnsubscribeList;
|
||||
@ -56,6 +56,7 @@ export default defineConfig({
|
||||
https: {
|
||||
key: fs.readFileSync(keyFilePath),
|
||||
cert: fs.readFileSync(certFilePath),
|
||||
}
|
||||
},
|
||||
open: 'chrome' // This will try to open Chrome specifically
|
||||
}
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user