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.AspNetCore.Mvc;
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
using Surge365.Core.Controllers;
|
using Surge365.Core.Controllers;
|
||||||
|
using Surge365.MassEmailReact.Application.DTOs;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
using Surge365.MassEmailReact.Domain.Enums;
|
using Surge365.MassEmailReact.Domain.Enums;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Surge365.Core.Controllers;
|
using Surge365.Core.Controllers;
|
||||||
|
using Surge365.MassEmailReact.Application.DTOs;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
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<IBouncedEmailRepository, BouncedEmailRepository>();
|
||||||
builder.Services.AddScoped<IUnsubscribeUrlService, UnsubscribeUrlService>();
|
builder.Services.AddScoped<IUnsubscribeUrlService, UnsubscribeUrlService>();
|
||||||
builder.Services.AddScoped<IUnsubscribeUrlRepository, UnsubscribeUrlRepository>();
|
builder.Services.AddScoped<IUnsubscribeUrlRepository, UnsubscribeUrlRepository>();
|
||||||
|
builder.Services.AddScoped<IUnsubscribeListService, UnsubscribeListService>();
|
||||||
|
builder.Services.AddScoped<IUnsubscribeListRepository, UnsubscribeListRepository>();
|
||||||
builder.Services.AddScoped<ITemplateService, TemplateService>();
|
builder.Services.AddScoped<ITemplateService, TemplateService>();
|
||||||
builder.Services.AddScoped<ITemplateRepository, TemplateRepository>();
|
builder.Services.AddScoped<ITemplateRepository, TemplateRepository>();
|
||||||
builder.Services.AddScoped<IEmailDomainService, EmailDomainService>();
|
builder.Services.AddScoped<IEmailDomainService, EmailDomainService>();
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
namespace Surge365.MassEmailReact.Domain.Entities
|
namespace Surge365.MassEmailReact.Application.DTOs
|
||||||
{
|
{
|
||||||
public class MailingTemplateUpdateDto
|
public class MailingTemplateUpdateDto
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Domain.Entities
|
namespace Surge365.MassEmailReact.Application.DTOs
|
||||||
{
|
{
|
||||||
public class MailingUpdateDto
|
public class MailingUpdateDto
|
||||||
{
|
{
|
||||||
@ -15,6 +15,7 @@ namespace Surge365.MassEmailReact.Domain.Entities
|
|||||||
public Guid? SessionActivityId { get; set; }
|
public Guid? SessionActivityId { get; set; }
|
||||||
public string? RecurringTypeCode { get; set; }
|
public string? RecurringTypeCode { get; set; }
|
||||||
public DateTime? RecurringStartDate { get; set; }
|
public DateTime? RecurringStartDate { get; set; }
|
||||||
|
public string? UnsubscribeListCode { get; set; }
|
||||||
public MailingTemplateUpdateDto Template { get; set; } = new MailingTemplateUpdateDto();
|
public MailingTemplateUpdateDto Template { get; set; } = new MailingTemplateUpdateDto();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4,7 +4,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Domain.Entities
|
namespace Surge365.MassEmailReact.Application.DTOs
|
||||||
{
|
{
|
||||||
public class TargetColumnUpdateDto
|
public class TargetColumnUpdateDto
|
||||||
{
|
{
|
||||||
|
|||||||
@ -4,7 +4,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Domain.Entities
|
namespace Surge365.MassEmailReact.Application.DTOs
|
||||||
{
|
{
|
||||||
public class TargetUpdateDto
|
public class TargetUpdateDto
|
||||||
{
|
{
|
||||||
@ -16,6 +16,7 @@ namespace Surge365.MassEmailReact.Domain.Entities
|
|||||||
public string FilterQuery { get; set; } = "";
|
public string FilterQuery { get; set; } = "";
|
||||||
public bool AllowWriteBack { get; set; } = false;
|
public bool AllowWriteBack { get; set; } = false;
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
|
public string? DefaultUnsubscribeListCode { get; set; }
|
||||||
public List<TargetColumnUpdateDto> Columns { get; set; } = new List<TargetColumnUpdateDto>();
|
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
|
public class TestEmailListUpdateDto
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Domain.Entities
|
namespace Surge365.MassEmailReact.Application.DTOs
|
||||||
{
|
{
|
||||||
public class TestMailingDto
|
public class TestMailingDto
|
||||||
{
|
{
|
||||||
|
|||||||
@ -4,7 +4,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Domain.Entities
|
namespace Surge365.MassEmailReact.Application.DTOs
|
||||||
{
|
{
|
||||||
public class TestTargetDto
|
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.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
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.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
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 DateTime UpdateDate { get; set; } = DateTime.Now;
|
||||||
public string? RecurringTypeCode { get; set; }
|
public string? RecurringTypeCode { get; set; }
|
||||||
public DateTime? RecurringStartDate { get; set; }
|
public DateTime? RecurringStartDate { get; set; }
|
||||||
|
public string? UnsubscribeListCode { get; set; }
|
||||||
public MailingTemplate Template { get; set; } = new MailingTemplate();
|
public MailingTemplate Template { get; set; } = new MailingTemplate();
|
||||||
|
|
||||||
public Mailing() { }
|
public Mailing() { }
|
||||||
|
|
||||||
private Mailing(int id, string name, string description, int templateId, int targetId,
|
private Mailing(int id, string name, string description, int templateId, int targetId,
|
||||||
string statusCode, DateTime? scheduleDate, DateTime? sentDate, DateTime createDate,
|
string statusCode, DateTime? scheduleDate, DateTime? sentDate, DateTime createDate,
|
||||||
DateTime updateDate, string? recurringTypeCode, DateTime? recurringStartDate)
|
DateTime updateDate, string? recurringTypeCode, DateTime? recurringStartDate,
|
||||||
|
string? unsubscribeListCode)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
Name = name;
|
Name = name;
|
||||||
@ -36,14 +38,17 @@ namespace Surge365.MassEmailReact.Domain.Entities
|
|||||||
UpdateDate = updateDate;
|
UpdateDate = updateDate;
|
||||||
RecurringTypeCode = recurringTypeCode;
|
RecurringTypeCode = recurringTypeCode;
|
||||||
RecurringStartDate = recurringStartDate;
|
RecurringStartDate = recurringStartDate;
|
||||||
|
UnsubscribeListCode = unsubscribeListCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Mailing Create(int id, string name, string description, int templateId, int targetId,
|
public static Mailing Create(int id, string name, string description, int templateId, int targetId,
|
||||||
string statusCode, DateTime? scheduleDate, DateTime? sentDate, DateTime createDate,
|
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,
|
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 string FilterQuery { get; set; } = "";
|
||||||
public bool AllowWriteBack { get; set; }
|
public bool AllowWriteBack { get; set; }
|
||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
|
public string? DefaultUnsubscribeListCode { get; set; }
|
||||||
public List<TargetColumn> Columns { get; set; } = new List<TargetColumn>();
|
public List<TargetColumn> Columns { get; set; } = new List<TargetColumn>();
|
||||||
|
|
||||||
public Target() { }
|
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;
|
Id = id;
|
||||||
ServerId = ServerId;
|
ServerId = ServerId;
|
||||||
@ -29,10 +30,11 @@ namespace Surge365.MassEmailReact.Domain.Entities
|
|||||||
FilterQuery = filterQuery;
|
FilterQuery = filterQuery;
|
||||||
AllowWriteBack = allowWriteBack;
|
AllowWriteBack = allowWriteBack;
|
||||||
IsActive = isActive;
|
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 TestEmailListMap());
|
||||||
QueryMapper.AddMap(new BouncedEmailMap());
|
QueryMapper.AddMap(new BouncedEmailMap());
|
||||||
QueryMapper .AddMap(new UnsubscribeUrlMap());
|
QueryMapper .AddMap(new UnsubscribeUrlMap());
|
||||||
|
QueryMapper.AddMap(new UnsubscribeListMap());
|
||||||
QueryMapper.AddMap(new TemplateMap());
|
QueryMapper.AddMap(new TemplateMap());
|
||||||
QueryMapper.AddMap(new EmailDomainMap());
|
QueryMapper.AddMap(new EmailDomainMap());
|
||||||
QueryMapper .AddMap(new MailingMap());
|
QueryMapper .AddMap(new MailingMap());
|
||||||
|
|||||||
@ -19,6 +19,7 @@ namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
|||||||
Map(m => m.UpdateDate).ToColumn("update_date");
|
Map(m => m.UpdateDate).ToColumn("update_date");
|
||||||
Map(m => m.RecurringTypeCode).ToColumn("blast_recurring_type_code");
|
Map(m => m.RecurringTypeCode).ToColumn("blast_recurring_type_code");
|
||||||
Map(m => m.RecurringStartDate).ToColumn("recurring_start_date");
|
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.FilterQuery).ToColumn("filter_query");
|
||||||
Map(t => t.AllowWriteBack).ToColumn("allow_write_back");
|
Map(t => t.AllowWriteBack).ToColumn("allow_write_back");
|
||||||
Map(t => t.IsActive).ToColumn("is_active");
|
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("@sent_date", mailing.SentDate ?? (object)DBNull.Value),
|
||||||
new SqlParameter("@blast_recurring_type_code", mailing.RecurringTypeCode ?? (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("@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 })),
|
new SqlParameter("@template_json", JsonSerializer.Serialize(mailing.Template, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower })),
|
||||||
pmSuccess
|
pmSuccess
|
||||||
};
|
};
|
||||||
@ -294,6 +295,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
new SqlParameter("@sent_date", mailing.SentDate ?? (object)DBNull.Value),
|
new SqlParameter("@sent_date", mailing.SentDate ?? (object)DBNull.Value),
|
||||||
new SqlParameter("@blast_recurring_type_code", mailing.RecurringTypeCode ?? (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("@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 })),
|
new SqlParameter("@template_json", JsonSerializer.Serialize(mailing.Template, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower })),
|
||||||
pmSuccess
|
pmSuccess
|
||||||
};
|
};
|
||||||
|
|||||||
@ -118,6 +118,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
new SqlParameter("@filter_query", target.FilterQuery),
|
new SqlParameter("@filter_query", target.FilterQuery),
|
||||||
new SqlParameter("@allow_write_back", target.AllowWriteBack),
|
new SqlParameter("@allow_write_back", target.AllowWriteBack),
|
||||||
new SqlParameter("@is_active", target.IsActive),
|
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),
|
new SqlParameter("@column_json", target.Columns != null ? JsonSerializer.Serialize(target.Columns) : (object)DBNull.Value),
|
||||||
pmSuccess
|
pmSuccess
|
||||||
};
|
};
|
||||||
@ -152,6 +153,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
new SqlParameter("@filter_query", target.FilterQuery),
|
new SqlParameter("@filter_query", target.FilterQuery),
|
||||||
new SqlParameter("@allow_write_back", target.AllowWriteBack),
|
new SqlParameter("@allow_write_back", target.AllowWriteBack),
|
||||||
new SqlParameter("@is_active", target.IsActive),
|
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),
|
new SqlParameter("@column_json", target.Columns != null ? JsonSerializer.Serialize(target.Columns) : (object)DBNull.Value),
|
||||||
pmSuccess
|
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,
|
SentDate = mailingDto.SentDate,
|
||||||
RecurringTypeCode = mailingDto.RecurringTypeCode,
|
RecurringTypeCode = mailingDto.RecurringTypeCode,
|
||||||
RecurringStartDate = mailingDto.RecurringStartDate,
|
RecurringStartDate = mailingDto.RecurringStartDate,
|
||||||
|
UnsubscribeListCode = mailingDto.UnsubscribeListCode,
|
||||||
Template = new MailingTemplate
|
Template = new MailingTemplate
|
||||||
{
|
{
|
||||||
DomainId = mailingDto.Template.DomainId,
|
DomainId = mailingDto.Template.DomainId,
|
||||||
@ -139,6 +140,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Services
|
|||||||
mailing.SentDate = mailingDto.SentDate;
|
mailing.SentDate = mailingDto.SentDate;
|
||||||
mailing.RecurringTypeCode = mailingDto.RecurringTypeCode;
|
mailing.RecurringTypeCode = mailingDto.RecurringTypeCode;
|
||||||
mailing.RecurringStartDate = mailingDto.RecurringStartDate;
|
mailing.RecurringStartDate = mailingDto.RecurringStartDate;
|
||||||
|
mailing.UnsubscribeListCode = mailingDto.UnsubscribeListCode;
|
||||||
mailing.Template = new MailingTemplate
|
mailing.Template = new MailingTemplate
|
||||||
{
|
{
|
||||||
DomainId = mailingDto.Template.DomainId,
|
DomainId = mailingDto.Template.DomainId,
|
||||||
|
|||||||
@ -9,8 +9,8 @@ using System.IdentityModel.Tokens.Jwt;
|
|||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using Surge365.MassEmailReact.Application.DTOs;
|
using Surge365.MassEmailReact.Application.DTOs;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
|
|
||||||
@ -53,6 +53,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Services
|
|||||||
target.FilterQuery = targetDto.FilterQuery;
|
target.FilterQuery = targetDto.FilterQuery;
|
||||||
target.AllowWriteBack = targetDto.AllowWriteBack;
|
target.AllowWriteBack = targetDto.AllowWriteBack;
|
||||||
target.IsActive = targetDto.IsActive;
|
target.IsActive = targetDto.IsActive;
|
||||||
|
target.DefaultUnsubscribeListCode = targetDto.DefaultUnsubscribeListCode;
|
||||||
|
|
||||||
target.Columns = new List<TargetColumn>();
|
target.Columns = new List<TargetColumn>();
|
||||||
foreach (var columnDto in targetDto.Columns)
|
foreach (var columnDto in targetDto.Columns)
|
||||||
@ -77,6 +78,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Services
|
|||||||
target.FilterQuery = targetDto.FilterQuery;
|
target.FilterQuery = targetDto.FilterQuery;
|
||||||
target.AllowWriteBack = targetDto.AllowWriteBack;
|
target.AllowWriteBack = targetDto.AllowWriteBack;
|
||||||
target.IsActive = targetDto.IsActive;
|
target.IsActive = targetDto.IsActive;
|
||||||
|
target.DefaultUnsubscribeListCode = targetDto.DefaultUnsubscribeListCode;
|
||||||
|
|
||||||
target.Columns = new List<TargetColumn>();
|
target.Columns = new List<TargetColumn>();
|
||||||
foreach (var columnDto in targetDto.Columns)
|
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 Surge365.MassEmailReact.Domain.Entities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
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" />
|
<None Remove="src\components\layouts\Layout_backup.tsx" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Folder Include="Properties\" />
|
||||||
<Folder Include="src\hooks\" />
|
<Folder Include="src\hooks\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@ -23,6 +23,7 @@ import Template from "@/types/template";
|
|||||||
import Mailing from "@/types/mailing";
|
import Mailing from "@/types/mailing";
|
||||||
import TargetSample from "@/types/targetSample";
|
import TargetSample from "@/types/targetSample";
|
||||||
import Target from "@/types/target";
|
import Target from "@/types/target";
|
||||||
|
import UnsubscribeList from "@/types/unsubscribeList";
|
||||||
//import MailingTemplate from "@/types/mailingTemplate";
|
//import MailingTemplate from "@/types/mailingTemplate";
|
||||||
//import MailingTarget from "@/types/mailingTarget";
|
//import MailingTarget from "@/types/mailingTarget";
|
||||||
import EmailList from "@/components/forms/EmailList";
|
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 [currentTarget, setCurrentTarget] = useState<Target | null>(null);
|
||||||
const [targetSample, setTargetSample] = useState<TargetSample | null>(null);
|
const [targetSample, setTargetSample] = useState<TargetSample | null>(null);
|
||||||
const [targetSampleLoading, setTargetSampleLoading] = useState(false);
|
const [targetSampleLoading, setTargetSampleLoading] = useState(false);
|
||||||
|
const [availableUnsubscribeLists, setAvailableUnsubscribeLists] = useState<UnsubscribeList[]>([]);
|
||||||
|
|
||||||
const defaultMailing: Mailing = {
|
const defaultMailing: Mailing = {
|
||||||
id: 0,
|
id: 0,
|
||||||
@ -95,6 +97,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
sessionActivityId: null,
|
sessionActivityId: null,
|
||||||
recurringTypeCode: null,
|
recurringTypeCode: null,
|
||||||
recurringStartDate: null,
|
recurringStartDate: null,
|
||||||
|
unsubscribeListCode: null,
|
||||||
template: {
|
template: {
|
||||||
id: 0,
|
id: 0,
|
||||||
mailingId: 0,
|
mailingId: 0,
|
||||||
@ -208,6 +211,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
})
|
})
|
||||||
: schema.nullable();
|
: schema.nullable();
|
||||||
}),
|
}),
|
||||||
|
unsubscribeListCode: yup.string().nullable().optional(),
|
||||||
template: yup.object().shape({
|
template: yup.object().shape({
|
||||||
id: yup.number().nullable().default(0),
|
id: yup.number().nullable().default(0),
|
||||||
mailingId: yup.number().default(0),
|
mailingId: yup.number().default(0),
|
||||||
@ -254,6 +258,39 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
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(() => {
|
useEffect(() => {
|
||||||
const initializeMailingEdit = async () => {
|
const initializeMailingEdit = async () => {
|
||||||
@ -270,6 +307,10 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
setRecurring(false);
|
setRecurring(false);
|
||||||
setScheduleForLater(false);
|
setScheduleForLater(false);
|
||||||
reset(mailing || defaultMailing, { keepDefaultValues: true });
|
reset(mailing || defaultMailing, { keepDefaultValues: true });
|
||||||
|
|
||||||
|
// Initialize available unsubscribe lists
|
||||||
|
setAvailableUnsubscribeLists(setupData.unsubscribeLists);
|
||||||
|
|
||||||
if (setupData.testEmailLists.length > 0) {
|
if (setupData.testEmailLists.length > 0) {
|
||||||
setTestEmailListId(setupData.testEmailLists[0].id);
|
setTestEmailListId(setupData.testEmailLists[0].id);
|
||||||
setEmails(setupData.testEmailLists[0].emails);
|
setEmails(setupData.testEmailLists[0].emails);
|
||||||
@ -286,6 +327,13 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
if (mailing?.targetId) {
|
if (mailing?.targetId) {
|
||||||
const target = setupData.targets.find(t => t.id === mailing.targetId) || null;
|
const target = setupData.targets.find(t => t.id === mailing.targetId) || null;
|
||||||
setCurrentTarget(target);
|
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 {
|
} else {
|
||||||
setCurrentTarget(null);
|
setCurrentTarget(null);
|
||||||
}
|
}
|
||||||
@ -293,7 +341,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
initializeMailingEdit();
|
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 handleSave = async (formData: Mailing) => {
|
||||||
const apiUrl = isNew ? "/api/mailings" : `/api/mailings/${formData.id}`;
|
const apiUrl = isNew ? "/api/mailings" : `/api/mailings/${formData.id}`;
|
||||||
@ -565,33 +613,43 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<Controller
|
<Controller
|
||||||
name="targetId"
|
name="targetId"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
{...field}
|
{...field}
|
||||||
options={filteredTargets}
|
options={filteredTargets}
|
||||||
getOptionLabel={(option) => option.name}
|
getOptionLabel={(option) => option.name}
|
||||||
value={filteredTargets.find(t => t.id === field.value) || null}
|
value={filteredTargets.find(t => t.id === field.value) || null}
|
||||||
onChange={(_, newValue) => {
|
onChange={async (_, newValue) => {
|
||||||
field.onChange(newValue ? newValue.id : null);
|
field.onChange(newValue ? newValue.id : null);
|
||||||
trigger("targetId");
|
trigger("targetId");
|
||||||
setCurrentTarget(newValue);
|
setCurrentTarget(newValue);
|
||||||
}}
|
|
||||||
renderInput={(params) => (
|
// Update available unsubscribe lists when target changes
|
||||||
<TextField
|
await updateAvailableUnsubscribeLists(newValue);
|
||||||
{...params}
|
|
||||||
label="Target"
|
// Set default unsubscribe list from target if available
|
||||||
fullWidth
|
if (newValue?.defaultUnsubscribeListCode) {
|
||||||
margin="dense"
|
setValue("unsubscribeListCode", newValue.defaultUnsubscribeListCode);
|
||||||
error={!!errors.targetId}
|
} else {
|
||||||
helperText={errors.targetId?.message}
|
setValue("unsubscribeListCode", null);
|
||||||
/>
|
}
|
||||||
)}
|
}}
|
||||||
sx={{ flexGrow: 1 }}
|
renderInput={(params) => (
|
||||||
/>
|
<TextField
|
||||||
)}
|
{...params}
|
||||||
|
label="Target"
|
||||||
|
fullWidth
|
||||||
|
margin="dense"
|
||||||
|
error={!!errors.targetId}
|
||||||
|
helperText={errors.targetId?.message}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
sx={{ flexGrow: 1 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
{currentTarget && (
|
{currentTarget && (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0 }}>
|
||||||
@ -611,6 +669,32 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</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
|
<Autocomplete
|
||||||
options={setupData.testEmailLists}
|
options={setupData.testEmailLists}
|
||||||
getOptionLabel={(option) => option.name}
|
getOptionLabel={(option) => option.name}
|
||||||
@ -724,24 +808,24 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</>)}
|
</>)}
|
||||||
|
|
||||||
{templateViewerOpen && (
|
{templateViewerOpen && (
|
||||||
<TemplateViewer
|
<TemplateViewer
|
||||||
open={templateViewerOpen}
|
open={templateViewerOpen}
|
||||||
template={currentTemplate!}
|
template={currentTemplate!}
|
||||||
onClose={() => { setTemplateViewerOpen(false) }}
|
onClose={() => { setTemplateViewerOpen(false) }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{TargetSampleModalOpen && currentTarget && (
|
{TargetSampleModalOpen && currentTarget && (
|
||||||
<TargetSampleModal
|
<TargetSampleModal
|
||||||
open={TargetSampleModalOpen}
|
open={TargetSampleModalOpen}
|
||||||
target={currentTarget}
|
target={currentTarget}
|
||||||
targetSample={targetSample}
|
targetSample={targetSample}
|
||||||
onClose={() => { setTargetSampleModalOpen(false) }}
|
onClose={() => { setTargetSampleModalOpen(false) }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<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}>
|
<Button onClick={handleSubmit(handleSave)} color="primary" disabled={loading}>
|
||||||
{loading ? "Saving..." : "Save"}
|
{loading ? "Saving..." : "Save"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -51,6 +51,7 @@ const schema = yup.object().shape({
|
|||||||
filterQuery: yup.string().nullable(),
|
filterQuery: yup.string().nullable(),
|
||||||
allowWriteBack: yup.boolean().default(false),
|
allowWriteBack: yup.boolean().default(false),
|
||||||
isActive: yup.boolean().default(true),
|
isActive: yup.boolean().default(true),
|
||||||
|
defaultUnsubscribeListCode: yup.string().nullable().optional(),
|
||||||
columns: yup
|
columns: yup
|
||||||
.array().of(
|
.array().of(
|
||||||
yup.object().shape({
|
yup.object().shape({
|
||||||
@ -84,6 +85,7 @@ const defaultTarget: Target = {
|
|||||||
filterQuery: "",
|
filterQuery: "",
|
||||||
allowWriteBack: false,
|
allowWriteBack: false,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
defaultUnsubscribeListCode: null,
|
||||||
columns: [],
|
columns: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -315,6 +317,32 @@ const TargetEdit = ({ open, target, onClose, onSave }: TargetEditProps) => {
|
|||||||
error={!!errors.filterQuery}
|
error={!!errors.filterQuery}
|
||||||
helperText={errors.filterQuery?.message}
|
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
|
<FormControlLabel
|
||||||
control={<Switch {...register("allowWriteBack")} />}
|
control={<Switch {...register("allowWriteBack")} />}
|
||||||
label="Allow Write Back"
|
label="Allow Write Back"
|
||||||
|
|||||||
@ -53,6 +53,18 @@ function NewMailings() {
|
|||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
valueGetter: (_: number, row: Mailing) => setupData.templates.find(t => t.id === row.templateId)?.subject || 'Unknown',
|
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 () => {
|
const reloadMailings = async () => {
|
||||||
@ -153,6 +165,13 @@ function NewMailings() {
|
|||||||
<Typography variant="body2">ID: {row.id}</Typography>
|
<Typography variant="body2">ID: {row.id}</Typography>
|
||||||
<Typography variant="body2">Description: {row.description}</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">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>
|
</CardContent>
|
||||||
<IconButton onClick={(e) => { e.stopPropagation(); handleEdit(row); }}>
|
<IconButton onClick={(e) => { e.stopPropagation(); handleEdit(row); }}>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
|
|||||||
@ -38,6 +38,18 @@ function Targets() {
|
|||||||
{ field: "databaseName", headerName: "Database", flex: 1, minWidth: 100 },
|
{ field: "databaseName", headerName: "Database", flex: 1, minWidth: 100 },
|
||||||
{ field: "viewName", headerName: "View", flex: 1, minWidth: 300 },
|
{ field: "viewName", headerName: "View", flex: 1, minWidth: 300 },
|
||||||
{ field: "filterQuery", headerName: "Filter Query", flex: 1, minWidth: 100 },
|
{ 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: "allowWriteBack", headerName: "Write Back", width: 150 },
|
||||||
{ field: "isActive", headerName: "Active", width: 115 },
|
{ field: "isActive", headerName: "Active", width: 115 },
|
||||||
];
|
];
|
||||||
@ -102,6 +114,13 @@ function Targets() {
|
|||||||
<Typography variant="body2">Database: {row.databaseName}</Typography>
|
<Typography variant="body2">Database: {row.databaseName}</Typography>
|
||||||
<Typography variant="body2">View: {row.viewName}</Typography>
|
<Typography variant="body2">View: {row.viewName}</Typography>
|
||||||
<Typography variant="body2">Filter: {row.filterQuery}</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">Writeback: {row.allowWriteBack ? "Yes" : "No"}</Typography>
|
||||||
<Typography variant="body2">Active: {row.isActive ? "Yes" : "No"}</Typography>
|
<Typography variant="body2">Active: {row.isActive ? "Yes" : "No"}</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import Server from "@/types/server";
|
|||||||
import TestEmailList from '@/types/testEmailList';
|
import TestEmailList from '@/types/testEmailList';
|
||||||
import BouncedEmail from '@/types/bouncedEmail';
|
import BouncedEmail from '@/types/bouncedEmail';
|
||||||
import UnsubscribeUrl from '@/types/unsubscribeUrl';
|
import UnsubscribeUrl from '@/types/unsubscribeUrl';
|
||||||
|
import UnsubscribeList from '@/types/unsubscribeList';
|
||||||
import Template from '@/types/template';
|
import Template from '@/types/template';
|
||||||
import EmailDomain from '@/types/emailDomain';
|
import EmailDomain from '@/types/emailDomain';
|
||||||
import { useCustomFetchNoNavigate } from "@/utils/customFetch";
|
import { useCustomFetchNoNavigate } from "@/utils/customFetch";
|
||||||
@ -36,6 +37,11 @@ export type SetupData = {
|
|||||||
setUnsubscribeUrls: (updatedUnsubscribeUrl: UnsubscribeUrl) => void;
|
setUnsubscribeUrls: (updatedUnsubscribeUrl: UnsubscribeUrl) => void;
|
||||||
unsubscribeUrlsLoading: boolean;
|
unsubscribeUrlsLoading: boolean;
|
||||||
|
|
||||||
|
unsubscribeLists: UnsubscribeList[];
|
||||||
|
reloadUnsubscribeLists: () => void;
|
||||||
|
setUnsubscribeLists: (updatedUnsubscribeList: UnsubscribeList) => void;
|
||||||
|
unsubscribeListsLoading: boolean;
|
||||||
|
|
||||||
templates: Template[];
|
templates: Template[];
|
||||||
reloadTemplates: () => void;
|
reloadTemplates: () => void;
|
||||||
setTemplates: (updatedTemplate: Template) => void;
|
setTemplates: (updatedTemplate: Template) => void;
|
||||||
@ -71,6 +77,9 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
|||||||
const [unsubscribeUrls, setUnsubscribeUrls] = useState<UnsubscribeUrl[]>([]);
|
const [unsubscribeUrls, setUnsubscribeUrls] = useState<UnsubscribeUrl[]>([]);
|
||||||
const [unsubscribeUrlsLoading, setUnsubscribeUrlsLoading] = useState<boolean>(false);
|
const [unsubscribeUrlsLoading, setUnsubscribeUrlsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [unsubscribeLists, setUnsubscribeLists] = useState<UnsubscribeList[]>([]);
|
||||||
|
const [unsubscribeListsLoading, setUnsubscribeListsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
const [templates, setTemplates] = useState<Template[]>([]);
|
const [templates, setTemplates] = useState<Template[]>([]);
|
||||||
const [templatesLoading, setTemplatesLoading] = useState<boolean>(false);
|
const [templatesLoading, setTemplatesLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
@ -109,6 +118,10 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
|||||||
let unsubscribeUrlsData: UnsubscribeUrl[] | null = null;
|
let unsubscribeUrlsData: UnsubscribeUrl[] | null = null;
|
||||||
let loadUnsubscribeUrls = true;
|
let loadUnsubscribeUrls = true;
|
||||||
|
|
||||||
|
setUnsubscribeListsLoading(true);
|
||||||
|
let unsubscribeListsData: UnsubscribeList[] | null = null;
|
||||||
|
let loadUnsubscribeLists = true;
|
||||||
|
|
||||||
setTemplatesLoading(true);
|
setTemplatesLoading(true);
|
||||||
let templatesData: Template[] | null = null;
|
let templatesData: Template[] | null = null;
|
||||||
let loadTemplates = true;
|
let loadTemplates = true;
|
||||||
@ -144,6 +157,11 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
|||||||
setUnsubscribeUrls(parsedData.unsubscribeUrls);
|
setUnsubscribeUrls(parsedData.unsubscribeUrls);
|
||||||
setUnsubscribeUrlsLoading(false);
|
setUnsubscribeUrlsLoading(false);
|
||||||
}
|
}
|
||||||
|
if (parsedData.unsubscribeLists) {
|
||||||
|
loadUnsubscribeLists = false;
|
||||||
|
setUnsubscribeLists(parsedData.unsubscribeLists);
|
||||||
|
setUnsubscribeListsLoading(false);
|
||||||
|
}
|
||||||
if (parsedData.templates) {
|
if (parsedData.templates) {
|
||||||
loadTemplates = false;
|
loadTemplates = false;
|
||||||
setTemplates(parsedData.templates);
|
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) {
|
if (loadTemplates) {
|
||||||
const templatesResponse = await customFetch("/api/templates/GetAll?activeOnly=false");
|
const templatesResponse = await customFetch("/api/templates/GetAll?activeOnly=false");
|
||||||
templatesData = await templatesResponse.json();
|
templatesData = await templatesResponse.json();
|
||||||
@ -247,13 +278,24 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
setDataLoading(false);
|
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) {
|
} catch (error) {
|
||||||
setDataLoading(false);
|
setDataLoading(false);
|
||||||
setTargetsLoading(false);
|
setTargetsLoading(false);
|
||||||
setServersLoading(false);
|
setServersLoading(false);
|
||||||
setTestEmailListsLoading(false);
|
setTestEmailListsLoading(false);
|
||||||
setBouncedEmailsLoading(false);
|
setBouncedEmailsLoading(false);
|
||||||
|
setUnsubscribeUrlsLoading(false);
|
||||||
|
setUnsubscribeListsLoading(false);
|
||||||
setTemplatesLoading(false);
|
setTemplatesLoading(false);
|
||||||
setEmailDomainsLoading(false);
|
setEmailDomainsLoading(false);
|
||||||
console.error("Failed to fetch setup data:", error);
|
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 }));
|
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) => {
|
const updateTemplateCache = (updatedTemplate: Template) => {
|
||||||
setTemplates((prevTemplates) => {
|
setTemplates((prevTemplates) => {
|
||||||
const templateExists = prevTemplates.some((template) => template.id === updatedTemplate.id);
|
const templateExists = prevTemplates.some((template) => template.id === updatedTemplate.id);
|
||||||
@ -376,6 +429,11 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
|||||||
setUnsubscribeUrls: updateUnsubscribeUrlCache,
|
setUnsubscribeUrls: updateUnsubscribeUrlCache,
|
||||||
unsubscribeUrlsLoading,
|
unsubscribeUrlsLoading,
|
||||||
|
|
||||||
|
unsubscribeLists,
|
||||||
|
reloadUnsubscribeLists: reloadSetupData,
|
||||||
|
setUnsubscribeLists: updateUnsubscribeListCache,
|
||||||
|
unsubscribeListsLoading,
|
||||||
|
|
||||||
templates,
|
templates,
|
||||||
reloadTemplates: reloadSetupData,
|
reloadTemplates: reloadSetupData,
|
||||||
setTemplates: updateTemplateCache,
|
setTemplates: updateTemplateCache,
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export interface Mailing {
|
|||||||
sessionActivityId: string | null;
|
sessionActivityId: string | null;
|
||||||
recurringTypeCode: string | null;
|
recurringTypeCode: string | null;
|
||||||
recurringStartDate: string | null;
|
recurringStartDate: string | null;
|
||||||
|
unsubscribeListCode: string | null;
|
||||||
template: MailingTemplate | null;
|
template: MailingTemplate | null;
|
||||||
target: MailingTarget | null;
|
target: MailingTarget | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export interface Target {
|
|||||||
filterQuery: string;
|
filterQuery: string;
|
||||||
allowWriteBack: boolean;
|
allowWriteBack: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
defaultUnsubscribeListCode: string | null;
|
||||||
columns: TargetColumn[];
|
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: {
|
https: {
|
||||||
key: fs.readFileSync(keyFilePath),
|
key: fs.readFileSync(keyFilePath),
|
||||||
cert: fs.readFileSync(certFilePath),
|
cert: fs.readFileSync(certFilePath),
|
||||||
}
|
},
|
||||||
|
open: 'chrome' // This will try to open Chrome specifically
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user