Enhance email domain management and switch to Monaco Editor

Updated EmailDomainsController to support password retrieval.
Modified repository and service interfaces to include a new
optional `returnPassword` parameter. Transitioned from Tiptap
to Monaco Editor for rich text editing. Improved UI for
EmailDomainEdit and TemplateEdit components, added password
visibility toggle in EmailDomains, and refined data grid
layout in Templates.
This commit is contained in:
David Headrick 2025-03-04 18:54:57 -06:00
parent 2bdb1a8de6
commit d7b00cf335
11 changed files with 386 additions and 928 deletions

View File

@ -17,16 +17,19 @@ namespace Surge365.MassEmailReact.Server.Controllers
} }
[HttpGet("GetAll")] [HttpGet("GetAll")]
public async Task<IActionResult> GetAll([FromQuery] bool? activeOnly) public async Task<IActionResult> GetAll([FromQuery] bool? activeOnly, bool? returnPassword = null)
{ {
var emailDomains = await _emailDomainService.GetAllAsync(activeOnly == null || activeOnly.Value ? true : false); bool activeOnlyValue = activeOnly == null || activeOnly.Value ? true : false;
bool returnPasswordValue = returnPassword != null && returnPassword.Value ? true : false;
var emailDomains = await _emailDomainService.GetAllAsync(activeOnlyValue, returnPasswordValue);
return Ok(emailDomains); return Ok(emailDomains);
} }
[HttpGet("{id}")] [HttpGet("{id}")]
public async Task<IActionResult> GetByKey(int id) public async Task<IActionResult> GetByKey(int id, bool? returnPassword = null)
{ {
var emailDomain = await _emailDomainService.GetByIdAsync(id); bool returnPasswordValue = returnPassword != null && returnPassword.Value ? true : false;
var emailDomain = await _emailDomainService.GetByIdAsync(id, returnPasswordValue);
return emailDomain is not null ? Ok(emailDomain) : NotFound($"EmailDomain with key '{id}' not found."); return emailDomain is not null ? Ok(emailDomain) : NotFound($"EmailDomain with key '{id}' not found.");
} }

View File

@ -6,8 +6,8 @@ namespace Surge365.MassEmailReact.Application.Interfaces
{ {
public interface IEmailDomainRepository public interface IEmailDomainRepository
{ {
Task<EmailDomain?> GetByIdAsync(int id); Task<EmailDomain?> GetByIdAsync(int id, bool returnPassword = false);
Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true); Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true, bool returnPassword = false);
Task<int?> CreateAsync(EmailDomain emailDomain); Task<int?> CreateAsync(EmailDomain emailDomain);
Task<bool> UpdateAsync(EmailDomain emailDomain); Task<bool> UpdateAsync(EmailDomain emailDomain);
} }

View File

@ -6,8 +6,8 @@ namespace Surge365.MassEmailReact.Application.Interfaces
{ {
public interface IEmailDomainService public interface IEmailDomainService
{ {
Task<EmailDomain?> GetByIdAsync(int id); Task<EmailDomain?> GetByIdAsync(int id, bool returnPassword = false);
Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true); Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true, bool returnPassword = false);
Task<int?> CreateAsync(EmailDomainUpdateDto emailDomainDto); Task<int?> CreateAsync(EmailDomainUpdateDto emailDomainDto);
Task<bool> UpdateAsync(EmailDomainUpdateDto emailDomainDto); Task<bool> UpdateAsync(EmailDomainUpdateDto emailDomainDto);
} }

View File

@ -8,6 +8,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Linq; using System.Linq;
using System.Runtime.Intrinsics.Arm;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Surge365.MassEmailReact.Infrastructure.Repositories namespace Surge365.MassEmailReact.Infrastructure.Repositories
@ -35,22 +36,35 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
#endif #endif
} }
public async Task<EmailDomain?> GetByIdAsync(int id) public async Task<EmailDomain?> GetByIdAsync(int id, bool returnPassword = false)
{ {
ArgumentNullException.ThrowIfNull(_config); ArgumentNullException.ThrowIfNull(_config);
ArgumentNullException.ThrowIfNull(_connectionStringName); ArgumentNullException.ThrowIfNull(_connectionStringName);
using SqlConnection conn = new SqlConnection(ConnectionString); using SqlConnection conn = new SqlConnection(ConnectionString);
return (await conn.QueryAsync<EmailDomain>("mem_get_domain_by_id", new { domain_key = id }, commandType: CommandType.StoredProcedure)).FirstOrDefault(); EmailDomain? domain = (await conn.QueryAsync<EmailDomain>("mem_get_domain_by_id", new { domain_key = id }, commandType: CommandType.StoredProcedure)).FirstOrDefault();
if (domain != null && !returnPassword)
{
domain.Password = "";
}
return domain;
} }
public async Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true) public async Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true, bool returnPassword = false)
{ {
ArgumentNullException.ThrowIfNull(_config); ArgumentNullException.ThrowIfNull(_config);
ArgumentNullException.ThrowIfNull(_connectionStringName); ArgumentNullException.ThrowIfNull(_connectionStringName);
using SqlConnection conn = new SqlConnection(ConnectionString); using SqlConnection conn = new SqlConnection(ConnectionString);
return (await conn.QueryAsync<EmailDomain>("mem_get_domain_all", new { active_only = activeOnly }, commandType: CommandType.StoredProcedure)).ToList(); List<EmailDomain> domains = (await conn.QueryAsync<EmailDomain>("mem_get_domain_all", new { active_only = activeOnly }, commandType: CommandType.StoredProcedure)).ToList();
if(!returnPassword)
{
foreach (var domain in domains)
{
domain.Password = "";
}
}
return domains;
} }
public async Task<int?> CreateAsync(EmailDomain emailDomain) public async Task<int?> CreateAsync(EmailDomain emailDomain)

View File

@ -18,14 +18,14 @@ namespace Surge365.MassEmailReact.Infrastructure.Services
_config = config; _config = config;
} }
public async Task<EmailDomain?> GetByIdAsync(int id) public async Task<EmailDomain?> GetByIdAsync(int id, bool returnPassword = false)
{ {
return await _emailDomainRepository.GetByIdAsync(id); return await _emailDomainRepository.GetByIdAsync(id, returnPassword);
} }
public async Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true) public async Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true, bool returnPassword = false)
{ {
return await _emailDomainRepository.GetAllAsync(activeOnly); return await _emailDomainRepository.GetAllAsync(activeOnly, returnPassword);
} }
public async Task<int?> CreateAsync(EmailDomainUpdateDto emailDomainDto) public async Task<int?> CreateAsync(EmailDomainUpdateDto emailDomainDto)

View File

@ -12,13 +12,11 @@
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@fontsource/roboto": "^5.1.1", "@fontsource/roboto": "^5.1.1",
"@hookform/resolvers": "^4.1.2", "@hookform/resolvers": "^4.1.2",
"@monaco-editor/react": "^4.7.0",
"@mui/icons-material": "^6.4.5", "@mui/icons-material": "^6.4.5",
"@mui/material": "^6.4.5", "@mui/material": "^6.4.5",
"@mui/x-charts": "^7.27.1", "@mui/x-charts": "^7.27.1",
"@mui/x-data-grid": "^7.27.1", "@mui/x-data-grid": "^7.27.1",
"@tiptap/pm": "^2.11.5",
"@tiptap/react": "^2.11.5",
"@tiptap/starter-kit": "^2.11.5",
"admin-lte": "4.0.0-beta3", "admin-lte": "4.0.0-beta3",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
@ -1195,6 +1193,29 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@monaco-editor/loader": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz",
"integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==",
"license": "MIT",
"dependencies": {
"state-local": "^1.0.6"
}
},
"node_modules/@monaco-editor/react": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz",
"integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==",
"license": "MIT",
"dependencies": {
"@monaco-editor/loader": "^1.5.0"
},
"peerDependencies": {
"monaco-editor": ">= 0.25.0 < 1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@mui/core-downloads-tracker": { "node_modules/@mui/core-downloads-tracker": {
"version": "6.4.5", "version": "6.4.5",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.5.tgz", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.5.tgz",
@ -1688,12 +1709,6 @@
"integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==", "integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@remirror/core-constants": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
"integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
"license": "MIT"
},
"node_modules/@restart/hooks": { "node_modules/@restart/hooks": {
"version": "0.4.16", "version": "0.4.16",
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
@ -2042,391 +2057,6 @@
"tslib": "^2.8.0" "tslib": "^2.8.0"
} }
}, },
"node_modules/@tiptap/core": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.11.5.tgz",
"integrity": "sha512-jb0KTdUJaJY53JaN7ooY3XAxHQNoMYti/H6ANo707PsLXVeEqJ9o8+eBup1JU5CuwzrgnDc2dECt2WIGX9f8Jw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-blockquote": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.11.5.tgz",
"integrity": "sha512-MZfcRIzKRD8/J1hkt/eYv49060GTL6qGR3NY/oTDuw2wYzbQXXLEbjk8hxAtjwNn7G+pWQv3L+PKFzZDxibLuA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-bold": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.11.5.tgz",
"integrity": "sha512-OAq03MHEbl7MtYCUzGuwb0VpOPnM0k5ekMbEaRILFU5ZC7cEAQ36XmPIw1dQayrcuE8GZL35BKub2qtRxyC9iA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-bubble-menu": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.11.5.tgz",
"integrity": "sha512-rx+rMd7EEdht5EHLWldpkzJ56SWYA9799b33ustePqhXd6linnokJCzBqY13AfZ9+xp3RsR6C0ZHI9GGea0tIA==",
"license": "MIT",
"dependencies": {
"tippy.js": "^6.3.7"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-bullet-list": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.11.5.tgz",
"integrity": "sha512-VXwHlX6A/T6FAspnyjbKDO0TQ+oetXuat6RY1/JxbXphH42nLuBaGWJ6pgy6xMl6XY8/9oPkTNrfJw/8/eeRwA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-code": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.11.5.tgz",
"integrity": "sha512-xOvHevNIQIcCCVn9tpvXa1wBp0wHN/2umbAZGTVzS+AQtM7BTo0tz8IyzwxkcZJaImONcUVYLOLzt2AgW1LltA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-code-block": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.11.5.tgz",
"integrity": "sha512-ksxMMvqLDlC+ftcQLynqZMdlJT1iHYZorXsXw/n+wuRd7YElkRkd6YWUX/Pq/njFY6lDjKiqFLEXBJB8nrzzBA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-document": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.11.5.tgz",
"integrity": "sha512-7I4BRTpIux2a0O2qS3BDmyZ5LGp3pszKbix32CmeVh7lN9dV7W5reDqtJJ9FCZEEF+pZ6e1/DQA362dflwZw2g==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-dropcursor": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.11.5.tgz",
"integrity": "sha512-uIN7L3FU0904ec7FFFbndO7RQE/yiON4VzAMhNn587LFMyWO8US139HXIL4O8dpZeYwYL3d1FnDTflZl6CwLlg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-floating-menu": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.11.5.tgz",
"integrity": "sha512-HsMI0hV5Lwzm530Z5tBeyNCBNG38eJ3qjfdV2OHlfSf3+KOEfn6a5AUdoNaZO02LF79/8+7BaYU2drafag9cxQ==",
"license": "MIT",
"dependencies": {
"tippy.js": "^6.3.7"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-gapcursor": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.11.5.tgz",
"integrity": "sha512-kcWa+Xq9cb6lBdiICvLReuDtz/rLjFKHWpW3jTTF3FiP3wx4H8Rs6bzVtty7uOVTfwupxZRiKICAMEU6iT0xrQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-hard-break": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.11.5.tgz",
"integrity": "sha512-q9doeN+Yg9F5QNTG8pZGYfNye3tmntOwch683v0CCVCI4ldKaLZ0jG3NbBTq+mosHYdgOH2rNbIORlRRsQ+iYQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-heading": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.11.5.tgz",
"integrity": "sha512-x/MV53psJ9baRcZ4k4WjnCUBMt8zCX7mPlKVT+9C/o+DEs/j/qxPLs95nHeQv70chZpSwCQCt93xMmuF0kPoAg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-history": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.11.5.tgz",
"integrity": "sha512-b+wOS33Dz1azw6F1i9LFTEIJ/gUui0Jwz5ZvmVDpL2ZHBhq1Ui0/spTT+tuZOXq7Y/uCbKL8Liu4WoedIvhboQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-horizontal-rule": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.11.5.tgz",
"integrity": "sha512-3up2r1Du8/5/4ZYzTC0DjTwhgPI3dn8jhOCLu73m5F3OGvK/9whcXoeWoX103hYMnGDxBlfOje71yQuN35FL4A==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-italic": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.11.5.tgz",
"integrity": "sha512-9VGfb2/LfPhQ6TjzDwuYLRvw0A6VGbaIp3F+5Mql8XVdTBHb2+rhELbyhNGiGVR78CaB/EiKb6dO9xu/tBWSYA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-list-item": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.11.5.tgz",
"integrity": "sha512-Mp5RD/pbkfW1vdc6xMVxXYcta73FOwLmblQlFNn/l/E5/X1DUSA4iGhgDDH4EWO3swbs03x2f7Zka/Xoj3+WLg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-ordered-list": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.11.5.tgz",
"integrity": "sha512-Cu8KwruBNWAaEfshRQR0yOSaUKAeEwxW7UgbvF9cN/zZuKgK5uZosPCPTehIFCcRe+TBpRtZQh+06f/gNYpYYg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-paragraph": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.11.5.tgz",
"integrity": "sha512-YFBWeg7xu/sBnsDIF/+nh9Arf7R0h07VZMd0id5Ydd2Qe3c1uIZwXxeINVtH0SZozuPIQFAT8ICe9M0RxmE+TA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-strike": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.11.5.tgz",
"integrity": "sha512-PVfUiCqrjvsLpbIoVlegSY8RlkR64F1Rr2RYmiybQfGbg+AkSZXDeO0eIrc03//4gua7D9DfIozHmAKv1KN3ow==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-text": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.11.5.tgz",
"integrity": "sha512-Gq1WwyhFpCbEDrLPIHt5A8aLSlf8bfz4jm417c8F/JyU0J5dtYdmx0RAxjnLw1i7ZHE7LRyqqAoS0sl7JHDNSQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-text-style": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.11.5.tgz",
"integrity": "sha512-YUmYl0gILSd/u/ZkOmNxjNXVw+mu8fpC2f8G4I4tLODm0zCx09j9DDEJXSrM5XX72nxJQqtSQsCpNKnL0hfeEQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/pm": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.11.5.tgz",
"integrity": "sha512-z9JFtqc5ZOsdQLd9vRnXfTCQ8v5ADAfRt9Nm7SqP6FUHII8E1hs38ACzf5xursmth/VonJYb5+73Pqxk1hGIPw==",
"license": "MIT",
"dependencies": {
"prosemirror-changeset": "^2.2.1",
"prosemirror-collab": "^1.3.1",
"prosemirror-commands": "^1.6.2",
"prosemirror-dropcursor": "^1.8.1",
"prosemirror-gapcursor": "^1.3.2",
"prosemirror-history": "^1.4.1",
"prosemirror-inputrules": "^1.4.0",
"prosemirror-keymap": "^1.2.2",
"prosemirror-markdown": "^1.13.1",
"prosemirror-menu": "^1.2.4",
"prosemirror-model": "^1.23.0",
"prosemirror-schema-basic": "^1.2.3",
"prosemirror-schema-list": "^1.4.1",
"prosemirror-state": "^1.4.3",
"prosemirror-tables": "^1.6.3",
"prosemirror-trailing-node": "^3.0.0",
"prosemirror-transform": "^1.10.2",
"prosemirror-view": "^1.37.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
}
},
"node_modules/@tiptap/react": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.11.5.tgz",
"integrity": "sha512-Dp8eHL1G+R/C4+QzAczyb3t1ovexEIZx9ln7SGEM+cT1KHKAw9XGPRgsp92+NQaYI+EdEb/YqoBOSzQcd18/OQ==",
"license": "MIT",
"dependencies": {
"@tiptap/extension-bubble-menu": "^2.11.5",
"@tiptap/extension-floating-menu": "^2.11.5",
"@types/use-sync-external-store": "^0.0.6",
"fast-deep-equal": "^3",
"use-sync-external-store": "^1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@tiptap/starter-kit": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.11.5.tgz",
"integrity": "sha512-SLI7Aj2ruU1t//6Mk8f+fqW+18uTqpdfLUJYgwu0CkqBckrkRZYZh6GVLk/02k3H2ki7QkFxiFbZrdbZdng0JA==",
"license": "MIT",
"dependencies": {
"@tiptap/core": "^2.11.5",
"@tiptap/extension-blockquote": "^2.11.5",
"@tiptap/extension-bold": "^2.11.5",
"@tiptap/extension-bullet-list": "^2.11.5",
"@tiptap/extension-code": "^2.11.5",
"@tiptap/extension-code-block": "^2.11.5",
"@tiptap/extension-document": "^2.11.5",
"@tiptap/extension-dropcursor": "^2.11.5",
"@tiptap/extension-gapcursor": "^2.11.5",
"@tiptap/extension-hard-break": "^2.11.5",
"@tiptap/extension-heading": "^2.11.5",
"@tiptap/extension-history": "^2.11.5",
"@tiptap/extension-horizontal-rule": "^2.11.5",
"@tiptap/extension-italic": "^2.11.5",
"@tiptap/extension-list-item": "^2.11.5",
"@tiptap/extension-ordered-list": "^2.11.5",
"@tiptap/extension-paragraph": "^2.11.5",
"@tiptap/extension-strike": "^2.11.5",
"@tiptap/extension-text": "^2.11.5",
"@tiptap/extension-text-style": "^2.11.5",
"@tiptap/pm": "^2.11.5"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
}
},
"node_modules/@types/babel__core": { "node_modules/@types/babel__core": {
"version": "7.20.5", "version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@ -2543,28 +2173,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"license": "MIT"
},
"node_modules/@types/markdown-it": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
"license": "MIT",
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"license": "MIT"
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.13.4", "version": "22.13.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz",
@ -2614,12 +2222,6 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
"license": "MIT"
},
"node_modules/@types/warning": { "node_modules/@types/warning": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
@ -2931,6 +2533,7 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0" "license": "Python-2.0"
}, },
"node_modules/babel-plugin-macros": { "node_modules/babel-plugin-macros": {
@ -3161,12 +2764,6 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
"license": "MIT"
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -3368,18 +2965,6 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/error-ex": { "node_modules/error-ex": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@ -3633,6 +3218,7 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-glob": { "node_modules/fast-glob": {
@ -4091,15 +3677,6 @@
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/locate-path": { "node_modules/locate-path": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@ -4145,29 +3722,6 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/markdown-it": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"license": "MIT"
},
"node_modules/merge2": { "node_modules/merge2": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -4205,6 +3759,13 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/monaco-editor": {
"version": "0.52.2",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz",
"integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==",
"license": "MIT",
"peer": true
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -4271,12 +3832,6 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/orderedmap": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
"integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
"license": "MIT"
},
"node_modules/p-limit": { "node_modules/p-limit": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@ -4462,201 +4017,6 @@
"integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/prosemirror-changeset": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz",
"integrity": "sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==",
"license": "MIT",
"dependencies": {
"prosemirror-transform": "^1.0.0"
}
},
"node_modules/prosemirror-collab": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz",
"integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0"
}
},
"node_modules/prosemirror-commands": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.0.tgz",
"integrity": "sha512-6toodS4R/Aah5pdsrIwnTYPEjW70SlO5a66oo5Kk+CIrgJz3ukOoS+FYDGqvQlAX5PxoGWDX1oD++tn5X3pyRA==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.10.2"
}
},
"node_modules/prosemirror-dropcursor": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz",
"integrity": "sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0",
"prosemirror-view": "^1.1.0"
}
},
"node_modules/prosemirror-gapcursor": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz",
"integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==",
"license": "MIT",
"dependencies": {
"prosemirror-keymap": "^1.0.0",
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-view": "^1.0.0"
}
},
"node_modules/prosemirror-history": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz",
"integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.2.2",
"prosemirror-transform": "^1.0.0",
"prosemirror-view": "^1.31.0",
"rope-sequence": "^1.3.0"
}
},
"node_modules/prosemirror-inputrules": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz",
"integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.0.0"
}
},
"node_modules/prosemirror-keymap": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz",
"integrity": "sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"w3c-keyname": "^2.2.0"
}
},
"node_modules/prosemirror-markdown": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.1.tgz",
"integrity": "sha512-Sl+oMfMtAjWtlcZoj/5L/Q39MpEnVZ840Xo330WJWUvgyhNmLBLN7MsHn07s53nG/KImevWHSE6fEj4q/GihHw==",
"license": "MIT",
"dependencies": {
"@types/markdown-it": "^14.0.0",
"markdown-it": "^14.0.0",
"prosemirror-model": "^1.20.0"
}
},
"node_modules/prosemirror-menu": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz",
"integrity": "sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==",
"license": "MIT",
"dependencies": {
"crelt": "^1.0.0",
"prosemirror-commands": "^1.0.0",
"prosemirror-history": "^1.0.0",
"prosemirror-state": "^1.0.0"
}
},
"node_modules/prosemirror-model": {
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.24.1.tgz",
"integrity": "sha512-YM053N+vTThzlWJ/AtPtF1j0ebO36nvbmDy4U7qA2XQB8JVaQp1FmB9Jhrps8s+z+uxhhVTny4m20ptUvhk0Mg==",
"license": "MIT",
"dependencies": {
"orderedmap": "^2.0.0"
}
},
"node_modules/prosemirror-schema-basic": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.3.tgz",
"integrity": "sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.19.0"
}
},
"node_modules/prosemirror-schema-list": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.0.tgz",
"integrity": "sha512-gg1tAfH1sqpECdhIHOA/aLg2VH3ROKBWQ4m8Qp9mBKrOxQRW61zc+gMCI8nh22gnBzd1t2u1/NPLmO3nAa3ssg==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.7.3"
}
},
"node_modules/prosemirror-state": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz",
"integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-transform": "^1.0.0",
"prosemirror-view": "^1.27.0"
}
},
"node_modules/prosemirror-tables": {
"version": "1.6.4",
"resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.6.4.tgz",
"integrity": "sha512-TkDY3Gw52gRFRfRn2f4wJv5WOgAOXLJA2CQJYIJ5+kdFbfj3acR4JUW6LX2e1hiEBiUwvEhzH5a3cZ5YSztpIA==",
"license": "MIT",
"dependencies": {
"prosemirror-keymap": "^1.2.2",
"prosemirror-model": "^1.24.1",
"prosemirror-state": "^1.4.3",
"prosemirror-transform": "^1.10.2",
"prosemirror-view": "^1.37.2"
}
},
"node_modules/prosemirror-trailing-node": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz",
"integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==",
"license": "MIT",
"dependencies": {
"@remirror/core-constants": "3.0.0",
"escape-string-regexp": "^4.0.0"
},
"peerDependencies": {
"prosemirror-model": "^1.22.1",
"prosemirror-state": "^1.4.2",
"prosemirror-view": "^1.33.8"
}
},
"node_modules/prosemirror-transform": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz",
"integrity": "sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.21.0"
}
},
"node_modules/prosemirror-view": {
"version": "1.38.0",
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.38.0.tgz",
"integrity": "sha512-O45kxXQTaP9wPdXhp8TKqCR+/unS/gnfg9Q93svQcB3j0mlp2XSPAmsPefxHADwzC+fbNS404jqRxm3UQaGvgw==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.20.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0"
}
},
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@ -4667,15 +4027,6 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -4949,12 +4300,6 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/rope-sequence": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
"integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==",
"license": "MIT"
},
"node_modules/run-parallel": { "node_modules/run-parallel": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@ -5043,6 +4388,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/state-local": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
"license": "MIT"
},
"node_modules/strip-json-comments": { "node_modules/strip-json-comments": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@ -5093,15 +4444,6 @@
"integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/tippy.js": {
"version": "6.3.7",
"resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
"integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
"license": "MIT",
"dependencies": {
"@popperjs/core": "^2.9.0"
}
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -5208,12 +4550,6 @@
"typescript": ">=4.8.4 <5.8.0" "typescript": ">=4.8.4 <5.8.0"
} }
}, },
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/uncontrollable": { "node_modules/uncontrollable": {
"version": "7.2.1", "version": "7.2.1",
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
@ -5358,12 +4694,6 @@
} }
} }
}, },
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT"
},
"node_modules/warning": { "node_modules/warning": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",

View File

@ -15,13 +15,11 @@
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@fontsource/roboto": "^5.1.1", "@fontsource/roboto": "^5.1.1",
"@hookform/resolvers": "^4.1.2", "@hookform/resolvers": "^4.1.2",
"@monaco-editor/react": "^4.7.0",
"@mui/icons-material": "^6.4.5", "@mui/icons-material": "^6.4.5",
"@mui/material": "^6.4.5", "@mui/material": "^6.4.5",
"@mui/x-charts": "^7.27.1", "@mui/x-charts": "^7.27.1",
"@mui/x-data-grid": "^7.27.1", "@mui/x-data-grid": "^7.27.1",
"@tiptap/pm": "^2.11.5",
"@tiptap/react": "^2.11.5",
"@tiptap/starter-kit": "^2.11.5",
"admin-lte": "4.0.0-beta3", "admin-lte": "4.0.0-beta3",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",

View File

@ -19,7 +19,7 @@ type EmailDomainEditProps = {
open: boolean; open: boolean;
emailDomain: EmailDomain | null; emailDomain: EmailDomain | null;
onClose: () => void; onClose: () => void;
onSave: (updatedEmailDomain: EmailDomain) => void; onSave: (updatedEmailDomain: EmailDomain, password: string) => void;
}; };
const schema = yup.object().shape({ const schema = yup.object().shape({
@ -34,9 +34,13 @@ const schema = yup.object().shape({
(d) => d.name.toLowerCase() === value?.toLowerCase() && (d.id === 0 || d.id !== this.parent.id) (d) => d.name.toLowerCase() === value?.toLowerCase() && (d.id === 0 || d.id !== this.parent.id)
); );
}), }),
emailAddress: yup.string().required("Email address is required"), emailAddress: yup.string().email("Invalid email").required("Email address is required"),
username: yup.string().required("Username is required"), username: yup.string().required("Username is required"),
password: yup.string().required("Password is required"), password: yup.string().default("")
.test("required-if-new", "NamePassword is required", function (value) {
if (this.parent.id > 0) return true;
else return value.length > 0;
}),
isActive: yup.boolean().default(true), isActive: yup.boolean().default(true),
displayOrder: yup.number().required("Display order is required"), displayOrder: yup.number().required("Display order is required"),
}); });
@ -54,6 +58,7 @@ const defaultEmailDomain: EmailDomain = {
const EmailDomainEdit = ({ open, emailDomain, onClose, onSave }: EmailDomainEditProps) => { const EmailDomainEdit = ({ open, emailDomain, onClose, onSave }: EmailDomainEditProps) => {
const isNew = !emailDomain || emailDomain.id === 0; const isNew = !emailDomain || emailDomain.id === 0;
const setupData: SetupData = useSetupData(); const setupData: SetupData = useSetupData();
const originalEmailDomain: EmailDomain | null = emailDomain ? { ...emailDomain } : null;
const { register, trigger, control, handleSubmit, reset, formState: { errors } } = useForm<EmailDomain>({ const { register, trigger, control, handleSubmit, reset, formState: { errors } } = useForm<EmailDomain>({
mode: "onBlur", mode: "onBlur",
@ -84,7 +89,7 @@ const EmailDomainEdit = ({ open, emailDomain, onClose, onSave }: EmailDomainEdit
if (!response.ok) throw new Error(isNew ? "Failed to create" : "Failed to update"); if (!response.ok) throw new Error(isNew ? "Failed to create" : "Failed to update");
const updatedEmailDomain = await response.json(); const updatedEmailDomain = await response.json();
onSave(updatedEmailDomain); onSave(updatedEmailDomain, isNew || formData.password ? formData.password : originalEmailDomain?.password);
onClose(); onClose();
} catch (error) { } catch (error) {
console.error("Update error:", error); console.error("Update error:", error);

View File

@ -9,12 +9,14 @@ import {
Button, Button,
Switch, Switch,
FormControlLabel, FormControlLabel,
Grid,
Box,
Paper,
} from "@mui/material"; } from "@mui/material";
import { useForm, Controller } from "react-hook-form"; import { useForm, Controller } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup"; import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup"; import * as yup from "yup";
import { EditorContent, useEditor, Editor } from '@tiptap/react'; import Editor from "@monaco-editor/react";
import StarterKit from '@tiptap/starter-kit';
// Assuming these types and context are defined elsewhere // Assuming these types and context are defined elsewhere
import Template from "@/types/template"; import Template from "@/types/template";
@ -31,13 +33,13 @@ const schema = yup.object().shape({
id: yup.number().default(0), id: yup.number().default(0),
name: yup.string().required("Name is required"), name: yup.string().required("Name is required"),
domainId: yup.number().required("Domain is required").moreThan(0, "Domain is required"), domainId: yup.number().required("Domain is required").moreThan(0, "Domain is required"),
description: yup.string().required("Description is required"), description: yup.string().default(""),
htmlBody: yup.string().required("HTML Body is required"), htmlBody: yup.string().required("HTML Body is required"),
subject: yup.string().required("Subject is required"), subject: yup.string().required("Subject is required"),
toName: yup.string().default(""), toName: yup.string().default(""),
fromName: yup.string().required("From Name is required"), fromName: yup.string().required("From Name is required"),
fromEmail: yup.string().email("Invalid email").required("From Email is required"), fromEmail: yup.string().default(""),
replyToEmail: yup.string().email("Invalid email").required("Reply To Email is required"), replyToEmail: yup.string().default(""),
clickTracking: yup.boolean().default(false), clickTracking: yup.boolean().default(false),
openTracking: yup.boolean().default(false), openTracking: yup.boolean().default(false),
categoryXml: yup.string().default(""), categoryXml: yup.string().default(""),
@ -71,6 +73,7 @@ const TemplateEdit = ({ open, template, onClose, onSave }: TemplateEditProps) =>
handleSubmit, handleSubmit,
reset, reset,
setValue, setValue,
watch,
formState: { errors }, formState: { errors },
} = useForm<Template>({ } = useForm<Template>({
mode: "onBlur", mode: "onBlur",
@ -80,23 +83,19 @@ const TemplateEdit = ({ open, template, onClose, onSave }: TemplateEditProps) =>
}); });
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [isExpanded, setIsExpanded] = useState(false);
const editor = useEditor({ const [isPreviewMode, setIsPreviewMode] = useState(false);
extensions: [StarterKit],
content: template?.htmlBody || '',
onUpdate: ({ editor }) => {
const html = editor.getHTML();
setValue('htmlBody', html, { shouldValidate: true });
},
});
useEffect(() => { useEffect(() => {
if (open && editor) { if (open) {
editor.commands.setContent(template?.htmlBody || ''); reset(template || defaultTemplate, { keepDefaultValues: true });
} }
}, [open, template, editor]); }, [open, template, reset]);
const handleSave = async (formData: Template) => { const handleSave = async (formData: Template) => {
const domain = setupData.emailDomains.find(el => el.id === formData.domainId);
formData.fromEmail = domain.emailAddress;
formData.replyToEmail = domain.emailAddress;
const apiUrl = isNew ? "/api/templates" : `/api/templates/${formData.id}`; const apiUrl = isNew ? "/api/templates" : `/api/templates/${formData.id}`;
const method = isNew ? "POST" : "PUT"; const method = isNew ? "POST" : "PUT";
setLoading(true); setLoading(true);
@ -119,44 +118,104 @@ const TemplateEdit = ({ open, template, onClose, onSave }: TemplateEditProps) =>
} }
}; };
type MenuProps = { const renderEditorOrPreview = (height: string) => (
editor: Editor | null; <Box sx={{ flexGrow: 1, overflow: "hidden" }}>
}; {isPreviewMode ? (
const MenuBar = ({ editor }: MenuProps) => { <iframe
if (!editor) return null; srcDoc={watch("htmlBody")}
sandbox="allow-same-origin"
return ( style={{ width: "100%", height, border: "none" }}
<div> />
<button ) : (
onClick={() => editor.chain().focus().toggleBold().run()} <Editor
disabled={!editor.can().toggleBold()} height={height}
> defaultLanguage="html"
Bold value={watch("htmlBody")}
</button> onChange={(value) => setValue("htmlBody", value || "", { shouldValidate: true })}
<button options={{ wordWrap: "on" }}
onClick={() => editor.chain().focus().toggleItalic().run()} />
disabled={!editor.can().toggleItalic()} )}
> {!isExpanded && errors.htmlBody && (
Italic <p style={{ color: "red", marginTop: "8px" }}>{errors.htmlBody.message}</p>
</button> )}
<button </Box>
onClick={() => editor.chain().focus().toggleBulletList().run()}
>
Bullet List
</button>
<button
onClick={() => editor.chain().focus().toggleOrderedList().run()}
>
Ordered List
</button>
</div>
); );
};
return ( return (
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth> <Dialog
<DialogTitle>{isNew ? "Add Template" : "Edit Template id=" + template?.id}</DialogTitle> open={open}
<DialogContent> onClose={onClose}
maxWidth={isExpanded ? false : "lg"}
fullScreen={isExpanded}
sx={{ "& .MuiDialog-paper": { width: isExpanded ? "100%" : "auto" } }}
>
<DialogTitle>
{isExpanded
? "Editing HTML Body"
: isNew
? "Add Template"
: `Edit Template id=${template?.id}`}
</DialogTitle>
<DialogContent sx={{ p: isExpanded ? 0 : 2, display: "flex", flexDirection: "column" }}>
{isExpanded ? (
<Box sx={{ height: "100%", display: "flex", flexDirection: "column" }}>
<Box
sx={{
p: 1,
backgroundColor: "#f5f5f5",
display: "flex",
justifyContent: "space-between",
}}
>
<Button onClick={() => setIsExpanded(false)}>Collapse</Button>
<Button onClick={() => setIsPreviewMode(!isPreviewMode)}>
{isPreviewMode ? "Edit" : "Preview"}
</Button>
</Box>
{renderEditorOrPreview("calc(100vh - 120px)")}
</Box>
) : (
<Grid container spacing={2}>
{/* Top Section: Two Columns for All Fields Except HTML */}
<Grid item xs={12}>
<Grid container spacing={2} sx={{ marginTop: 0 }}>
<Grid item xs={6} >
<TextField
{...register("name")}
label="Name"
fullWidth
error={!!errors.name}
helperText={errors.name?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
{...register("description")}
label="Description"
fullWidth
error={!!errors.description}
helperText={errors.description?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register("subject")}
label="Subject"
fullWidth
error={!!errors.subject}
helperText={errors.subject?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
{...register("fromName")}
label="From Name"
fullWidth
error={!!errors.fromName}
helperText={errors.fromName?.message}
/>
</Grid>
<Grid item xs={6}>
<Controller <Controller
name="domainId" name="domainId"
control={control} control={control}
@ -170,7 +229,6 @@ const TemplateEdit = ({ open, template, onClose, onSave }: TemplateEditProps) =>
<TextField <TextField
{...params} {...params}
label="Domain" label="Domain"
margin="dense"
error={!!errors.domainId} error={!!errors.domainId}
helperText={errors.domainId?.message} helperText={errors.domainId?.message}
/> />
@ -178,95 +236,71 @@ const TemplateEdit = ({ open, template, onClose, onSave }: TemplateEditProps) =>
/> />
)} )}
/> />
<TextField </Grid>
{...register("name")} <Grid item xs={12}>
label="Name"
fullWidth
margin="dense"
error={!!errors.name}
helperText={errors.name?.message}
/>
<TextField
{...register("description")}
label="Description"
fullWidth
margin="dense"
error={!!errors.description}
helperText={errors.description?.message}
/>
<Controller <Controller
name="htmlBody" name="clickTracking"
control={control} control={control}
render={({ field }) => ( render={({ field }) => (
<div>
<MenuBar editor={editor} />
<EditorContent editor={editor} />
{errors.htmlBody && (
<p style={{ color: 'red' }}>{errors.htmlBody.message}</p>
)}
</div>
)}
/>
<TextField
{...register("subject")}
label="Subject"
fullWidth
margin="dense"
error={!!errors.subject}
helperText={errors.subject?.message}
/>
<TextField
{...register("fromName")}
label="From Name"
fullWidth
margin="dense"
error={!!errors.fromName}
helperText={errors.fromName?.message}
/>
<TextField
{...register("fromEmail")}
label="From Email"
fullWidth
margin="dense"
error={!!errors.fromEmail}
helperText={errors.fromEmail?.message}
/>
<TextField
{...register("replyToEmail")}
label="Reply To Email"
fullWidth
margin="dense"
error={!!errors.replyToEmail}
helperText={errors.replyToEmail?.message}
/>
<FormControlLabel <FormControlLabel
control={<Switch {...register("clickTracking")} />} control={
<Switch
checked={field.value}
onChange={(e) => field.onChange(e.target.checked)}
/>
}
label="Click Tracking" label="Click Tracking"
/> />
)}
/>
<Controller
name="openTracking"
control={control}
render={({ field }) => (
<FormControlLabel <FormControlLabel
control={<Switch {...register("openTracking")} />} control={
<Switch
checked={field.value}
onChange={(e) => field.onChange(e.target.checked)}
/>
}
label="Open Tracking" label="Open Tracking"
/> />
<TextField )}
{...register("categoryXml")}
label="Category XML"
fullWidth
margin="dense"
multiline
rows={4}
error={!!errors.categoryXml}
helperText={errors.categoryXml?.message}
/> />
<Controller <Controller
name="isActive" name="isActive"
control={control} control={control}
render={({ field }) => ( render={({ field }) => (
<FormControlLabel <FormControlLabel
control={<Switch checked={field.value} onChange={(e) => field.onChange(e.target.checked)} />} control={
<Switch
checked={field.value}
onChange={(e) => field.onChange(e.target.checked)}
/>
}
label="Active" label="Active"
/> />
)} )}
/> />
</Grid>
</Grid>
</Grid>
{/* Bottom Section: Single Column for HTML Control */}
<Grid item xs={12}>
<Box sx={{ display: "flex", justifyContent: "space-between", my: 1 }}>
<Button onClick={() => setIsExpanded(true)}>Expand Editor</Button>
<Button onClick={() => setIsPreviewMode(!isPreviewMode)}>
{isPreviewMode ? "Edit" : "Preview"}
</Button>
</Box>
<Paper variant="outlined" sx={{ p: 2 }}>
{renderEditorOrPreview("300px")}
</Paper>
</Grid>
</Grid>
)}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={onClose} disabled={loading}> <Button onClick={onClose} disabled={loading}>

View File

@ -1,6 +1,7 @@
import { useState, useRef } from 'react'; import { useState, useRef } from 'react';
import { useSetupData, SetupData } from "@/context/SetupDataContext"; import { useSetupData, SetupData } from "@/context/SetupDataContext";
import EditIcon from '@mui/icons-material/Edit'; import EditIcon from '@mui/icons-material/Edit';
import { Lock, LockOpen } from "@mui/icons-material";
import { List, Card, CardContent, Typography, Box, useTheme, useMediaQuery, Button, CircularProgress, IconButton } from '@mui/material'; import { List, Card, CardContent, Typography, Box, useTheme, useMediaQuery, Button, CircularProgress, IconButton } from '@mui/material';
import { DataGrid, GridColDef, GridRenderCellParams, GridRowModel, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid'; import { DataGrid, GridColDef, GridRenderCellParams, GridRowModel, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid';
import EmailDomain from '@/types/emailDomain'; import EmailDomain from '@/types/emailDomain';
@ -11,6 +12,8 @@ function EmailDomains() {
const setupData: SetupData = useSetupData(); const setupData: SetupData = useSetupData();
const isMobile = useMediaQuery(theme.breakpoints.down("sm")); const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const [emailDomainsWithPasswords, setEmailDomainsWithPasswords] = useState<EmailDomain[] | null>(null);
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const gridContainerRef = useRef<HTMLDivElement | null>(null); const gridContainerRef = useRef<HTMLDivElement | null>(null);
const [selectedRow, setSelectedRow] = useState<EmailDomain | null>(null); const [selectedRow, setSelectedRow] = useState<EmailDomain | null>(null);
const [open, setOpen] = useState<boolean>(false); const [open, setOpen] = useState<boolean>(false);
@ -30,11 +33,43 @@ function EmailDomains() {
{ field: "name", headerName: "Name", flex: 1, minWidth: 160 }, { field: "name", headerName: "Name", flex: 1, minWidth: 160 },
{ field: "emailAddress", headerName: "Email Address", flex: 1, minWidth: 200 }, { field: "emailAddress", headerName: "Email Address", flex: 1, minWidth: 200 },
{ field: "username", headerName: "Username", flex: 1, minWidth: 150 }, { field: "username", headerName: "Username", flex: 1, minWidth: 150 },
{ field: "password", headerName: "Password", flex: 1, minWidth: 150 }, {
field: "password",
headerName: "Password",
width: 175,
renderHeader: () => (
<div style={{ display: "flex", alignItems: "center" }}>
Password
<IconButton size="small" onClick={(e) => { e.stopPropagation(); togglePasswordVisibility(); }} sx={{ marginLeft: 1 }}>
{isPasswordVisible ? <LockOpen /> : <Lock />}
</IconButton>
</div>
),
renderCell: (params: GridRenderCellParams<EmailDomain>) =>
isPasswordVisible ? params.value : "••••••••",
},
{ field: "displayOrder", headerName: "Display Order", width: 120 }, { field: "displayOrder", headerName: "Display Order", width: 120 },
{ field: "isActive", headerName: "Active", width: 75 }, { field: "isActive", headerName: "Active", width: 75 },
]; ];
const togglePasswordVisibility = async () => {
if (isPasswordVisible) {
setIsPasswordVisible(false);
setEmailDomainsWithPasswords(null); // Revert to setupData.emailDomains (masked)
} else {
try {
setIsPasswordVisible(true);
await loadEmailDomainsWithPasswords();
} catch (error) {
console.error("Error fetching email domains:", error);
}
}
};
const loadEmailDomainsWithPasswords = async () => {
const response = await fetch("/api/emailDomains/GetAll?activeOnly=false&returnPassword=true");
const data = await response.json();
setEmailDomainsWithPasswords(data);
}
const handleNew = () => { const handleNew = () => {
setSelectedRow(null); setSelectedRow(null);
setOpen(true); setOpen(true);
@ -45,9 +80,27 @@ function EmailDomains() {
setOpen(true); setOpen(true);
}; };
const handleUpdateRow = (updatedRow: EmailDomain) => { const handleSaveRow = (savedRow: EmailDomain, password: string) => {
setupData.setEmailDomains(updatedRow); setupData.setEmailDomains(savedRow);
if (isPasswordVisible) {
if (password)
updateEmailDomains({ ...savedRow, password: password }); // Update local state
else
updateEmailDomains({ ...savedRow });
}
}; };
const updateEmailDomains = (updatedEmailDomain: EmailDomain) => {
setEmailDomainsWithPasswords((prev) => {
if (prev == null) return null;
const exists = prev.some((e) => e.id === updatedEmailDomain.id);
return exists
? prev.map((domain) => (domain.id === updatedEmailDomain.id ? updatedEmailDomain : domain))
: [...prev, updatedEmailDomain];
});
};
const displayRows = isPasswordVisible ? (emailDomainsWithPasswords ?? setupData.emailDomains) : setupData.emailDomains;
return ( return (
<Box ref={gridContainerRef} sx={{ <Box ref={gridContainerRef} sx={{
@ -59,8 +112,16 @@ function EmailDomains() {
}}> }}>
<Box sx={{ position: 'absolute', inset: 0 }}> <Box sx={{ position: 'absolute', inset: 0 }}>
{isMobile ? ( {isMobile ? (
<><Box sx={{ display: "flex", justifyContent: "flex-end", marginBottom: 2 }}>
<Button
onClick={togglePasswordVisibility}
startIcon={isPasswordVisible ? <LockOpen /> : <Lock />}
>
{isPasswordVisible ? "Hide Passwords" : "Show Passwords"}
</Button>
</Box>
<List> <List>
{setupData.emailDomains.map((row) => ( {displayRows.map((row) => (
<Card key={row.id} sx={{ marginBottom: 2 }}> <Card key={row.id} sx={{ marginBottom: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<CardContent> <CardContent>
@ -79,9 +140,10 @@ function EmailDomains() {
</Card> </Card>
))} ))}
</List> </List>
</>
) : ( ) : (
<DataGrid <DataGrid
rows={setupData.emailDomains} rows={displayRows}
columns={columns} columns={columns}
autoPageSize autoPageSize
sx={{ minWidth: "600px" }} sx={{ minWidth: "600px" }}
@ -94,12 +156,16 @@ function EmailDomains() {
onClick={() => handleNew()} onClick={() => handleNew()}
sx={{ marginRight: 2 }} sx={{ marginRight: 2 }}
> >
{setupData.emailDomainsLoading ? <CircularProgress size={24} color="inherit" /> : "Add New"} Add New
</Button> </Button>
<Button <Button
variant="contained" variant="contained"
color="primary" color="primary"
onClick={() => setupData.reloadEmailDomains()} onClick={() => {
if (isPasswordVisible)
loadEmailDomainsWithPasswords();
setupData.reloadEmailDomains();
}}
sx={{ marginRight: 2 }} sx={{ marginRight: 2 }}
> >
{setupData.emailDomainsLoading ? <CircularProgress size={24} color="inherit" /> : "Refresh"} {setupData.emailDomainsLoading ? <CircularProgress size={24} color="inherit" /> : "Refresh"}
@ -133,7 +199,7 @@ function EmailDomains() {
open={open} open={open}
emailDomain={selectedRow} emailDomain={selectedRow}
onClose={() => setOpen(false)} onClose={() => setOpen(false)}
onSave={handleUpdateRow} onSave={handleSaveRow}
/> />
)} )}
</Box> </Box>

View File

@ -27,12 +27,19 @@ function Templates() {
), ),
}, },
{ field: "id", headerName: "ID", width: 60 }, { field: "id", headerName: "ID", width: 60 },
{ field: "name", headerName: "Name", flex: 1, minWidth: 160 }, { field: "domainId", headerName: "Domain", maxWidth: 200 },
{ field: "domainId", headerName: "Domain ID", width: 100 }, { field: "name", headerName: "Name", flex: 1, minWidth: 160, maxWidth:600 },
{ field: "description", headerName: "Description", flex: 1, minWidth: 200 }, { field: "description", headerName: "Description", flex: 1, minWidth: 200, maxWidth: 600 },
{ field: "isActive", headerName: "Active", width: 75 }, { field: "isActive", headerName: "Active", width: 75 },
]; ];
const getMaxWidth = () => {
let maxWidth = 0;
for (const column of columns) {
maxWidth += column.width ?? column.maxWidth ?? 0;
}
return maxWidth;
}
const handleNew = () => { const handleNew = () => {
setSelectedRow(null); setSelectedRow(null);
setOpen(true); setOpen(true);
@ -80,7 +87,8 @@ function Templates() {
rows={setupData.templates} rows={setupData.templates}
columns={columns} columns={columns}
autoPageSize autoPageSize
sx={{ minWidth: "600px" }} sx={{
minWidth: "600px", maxWidth: getMaxWidth() }}
slots={{ slots={{
toolbar: () => ( toolbar: () => (
<GridToolbarContainer sx={{ display: "flex", alignItems: "center" }}> <GridToolbarContainer sx={{ display: "flex", alignItems: "center" }}>
@ -90,7 +98,7 @@ function Templates() {
onClick={() => handleNew()} onClick={() => handleNew()}
sx={{ marginRight: 2 }} sx={{ marginRight: 2 }}
> >
{setupData.templatesLoading ? <CircularProgress size={24} color="inherit" /> : "Add New"} {"Add New"}
</Button> </Button>
<Button <Button
variant="contained" variant="contained"