diff --git a/DysonNetwork.Sphere/Account/Email/EmailService.cs b/DysonNetwork.Sphere/Account/Email/EmailService.cs index 24284c1..5a49815 100644 --- a/DysonNetwork.Sphere/Account/Email/EmailService.cs +++ b/DysonNetwork.Sphere/Account/Email/EmailService.cs @@ -62,19 +62,45 @@ public class EmailService await client.DisconnectAsync(true); } + private static string _ConvertHtmlToPlainText(string html) + { + // Remove style tags and their contents + html = System.Text.RegularExpressions.Regex.Replace(html, "]*>.*?", "", + System.Text.RegularExpressions.RegexOptions.Singleline); + + // Replace header tags with text + newlines + html = System.Text.RegularExpressions.Regex.Replace(html, "]*>(.*?)", "$1\n\n", + System.Text.RegularExpressions.RegexOptions.IgnoreCase); + + // Replace line breaks + html = html.Replace("
", "\n").Replace("
", "\n").Replace("
", "\n"); + + // Remove all remaining HTML tags + html = System.Text.RegularExpressions.Regex.Replace(html, "<[^>]+>", ""); + + // Decode HTML entities + html = System.Net.WebUtility.HtmlDecode(html); + + // Remove excess whitespace + html = System.Text.RegularExpressions.Regex.Replace(html, @"\s+", " ").Trim(); + + return html; + } + public async Task SendTemplatedEmailAsync(string? recipientName, string recipientEmail, - string subject, TModel model, string fallbackTextBody) + string subject, TModel model) where TComponent : IComponent { try { var htmlBody = await _viewRenderer.RenderComponentToStringAsync(model); + var fallbackTextBody = _ConvertHtmlToPlainText(htmlBody); await SendEmailAsync(recipientName, recipientEmail, subject, fallbackTextBody, htmlBody); } catch (Exception err) { _logger.LogError(err, "Failed to render email template..."); - await SendEmailAsync(recipientName, recipientEmail, subject, fallbackTextBody); + throw; } } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/Email/RazorViewRenderer.cs b/DysonNetwork.Sphere/Account/Email/RazorViewRenderer.cs index 0e89e86..964cd5a 100644 --- a/DysonNetwork.Sphere/Account/Email/RazorViewRenderer.cs +++ b/DysonNetwork.Sphere/Account/Email/RazorViewRenderer.cs @@ -17,23 +17,21 @@ public class RazorViewRenderer( ILogger logger ) { - public async Task RenderComponentToStringAsync(TModel model) + public async Task RenderComponentToStringAsync(TModel? model) where TComponent : IComponent { await using var htmlRenderer = new HtmlRenderer(serviceProvider, loggerFactory); - - var viewDictionary = new ViewDataDictionary( - new EmptyModelMetadataProvider(), - new ModelStateDictionary()) - { - Model = model - }; - + return await htmlRenderer.Dispatcher.InvokeAsync(async () => { try { - var parameterView = ParameterView.FromDictionary(viewDictionary); + var dictionary = model?.GetType().GetProperties() + .ToDictionary( + prop => prop.Name, + prop => prop.GetValue(model, null) + ) ?? new Dictionary(); + var parameterView = ParameterView.FromDictionary(dictionary); var output = await htmlRenderer.RenderComponentAsync(parameterView); return output.ToHtmlString(); } diff --git a/DysonNetwork.Sphere/Account/MagicSpellService.cs b/DysonNetwork.Sphere/Account/MagicSpellService.cs index c772502..f5e92f3 100644 --- a/DysonNetwork.Sphere/Account/MagicSpellService.cs +++ b/DysonNetwork.Sphere/Account/MagicSpellService.cs @@ -44,9 +44,9 @@ public class MagicSpellService(AppDatabase db, EmailService email, IConfiguratio .FirstOrDefaultAsync(); if (contact is null) throw new ArgumentException("Account has no contact method that can use"); - var link = $"${configuration.GetValue("BaseUrl")}/spells/{Uri.EscapeDataString(spell.Spell)}"; + var link = $"{configuration.GetValue("BaseUrl")}/spells/{Uri.EscapeDataString(spell.Spell)}"; - logger.LogInformation($"Sending magic spell... {link}"); + logger.LogInformation("Sending magic spell... {Link}", link); try { @@ -61,8 +61,7 @@ public class MagicSpellService(AppDatabase db, EmailService email, IConfiguratio { Name = contact.Account.Name, VerificationLink = link - }, - $"Thank you for creating an account.\nFor accessing all the features, confirm your registration with the link below:\n\n{link}" + } ); break; default: diff --git a/DysonNetwork.Sphere/Pages/Emails/EmailLayout.razor b/DysonNetwork.Sphere/Pages/Emails/EmailLayout.razor index 23046ec..a76e6b4 100644 --- a/DysonNetwork.Sphere/Pages/Emails/EmailLayout.razor +++ b/DysonNetwork.Sphere/Pages/Emails/EmailLayout.razor @@ -1,18 +1,29 @@ @inherits LayoutComponentBase -
-
-
+ + + + + + + + + + + + + + +
@ChildContent - - -
-

© @DateTime.Now.Year DysonNetwork. All rights reserved.

-
- - +
+ + @code { - [Parameter] - public RenderFragment? ChildContent { get; set; } + [Parameter] public RenderFragment? ChildContent { get; set; } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Emails/LandingEmail.razor b/DysonNetwork.Sphere/Pages/Emails/LandingEmail.razor index 5259a74..04321a7 100644 --- a/DysonNetwork.Sphere/Pages/Emails/LandingEmail.razor +++ b/DysonNetwork.Sphere/Pages/Emails/LandingEmail.razor @@ -1,44 +1,56 @@ -
-

Welcome to DysonNetwork!

-
+ + + + -
-

- Dear @Name, -

+
+ + -

- Thank you for creating an account with DysonNetwork. We're excited to have you join our community! -

+ + + -

- To access all features and ensure the security of your account, please confirm your registration by clicking - the button below: -

- - - -

- If the button doesn't work, you can also copy and paste this link into your browser: -
- @VerificationLink -

- -

- If you didn't create this account, please ignore this email. -

- -

- Best regards,
- The DysonNetwork Team -

- + + + +
+

+ Welcome to the Solar Network! +

+
+

+ Dear @Name, +

+

+ Thank you for creating an account on the Solar Network. We're excited to have you join our community! +

+

+ To access all features and ensure the security of your account, please confirm your registration by + clicking the button below: +

+
+
+ +
+
+

+ If the button doesn't work, you can also copy and paste this link into your browser: +
+ @VerificationLink +

+

+ If you didn't create this account, please ignore this email. +

+

+ Best regards,
+ The Solar Network Team +

+
@code { diff --git a/DysonNetwork.Sphere/Pages/Emails/_EmailLayout.cshtml b/DysonNetwork.Sphere/Pages/Emails/_EmailLayout.cshtml deleted file mode 100644 index 1ca2dc7..0000000 --- a/DysonNetwork.Sphere/Pages/Emails/_EmailLayout.cshtml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - @ViewData["Title"] - - -
-
- @RenderBody() -
- -
-

© @DateTime.Now.Year DysonNetwork. All rights reserved.

-
-
- - \ No newline at end of file diff --git a/DysonNetwork.Sphere/appsettings.json b/DysonNetwork.Sphere/appsettings.json index a592d9f..362e818 100644 --- a/DysonNetwork.Sphere/appsettings.json +++ b/DysonNetwork.Sphere/appsettings.json @@ -1,6 +1,6 @@ { "Debug": true, - "BaseUrl": "http://localhost:5017", + "BaseUrl": "http://localhost:5071", "Logging": { "LogLevel": { "Default": "Information", diff --git a/DysonNetwork.Sphere/wwwroot/css/styles.css b/DysonNetwork.Sphere/wwwroot/css/styles.css index 0c9b08e..ef55696 100644 --- a/DysonNetwork.Sphere/wwwroot/css/styles.css +++ b/DysonNetwork.Sphere/wwwroot/css/styles.css @@ -227,15 +227,27 @@ .z-50 { z-index: 50; } - .m-0 { - margin: calc(var(--spacing) * 0); + .container { + width: 100%; + @media (width >= 40rem) { + max-width: 40rem; + } + @media (width >= 48rem) { + max-width: 48rem; + } + @media (width >= 64rem) { + max-width: 64rem; + } + @media (width >= 80rem) { + max-width: 80rem; + } + @media (width >= 96rem) { + max-width: 96rem; + } } .mx-auto { margin-inline: auto; } - .my-8 { - margin-block: calc(var(--spacing) * 8); - } .mt-4 { margin-top: calc(var(--spacing) * 4); } @@ -269,15 +281,15 @@ .block { display: block; } + .contents { + display: contents; + } .flex { display: flex; } .hidden { display: none; } - .inline-block { - display: inline-block; - } .table { display: table; } @@ -290,9 +302,6 @@ .h-full { height: 100%; } - .min-h-screen { - min-height: 100vh; - } .w-full { width: 100%; } @@ -317,13 +326,6 @@ .justify-center { justify-content: center; } - .space-y-6 { - :where(& > :not(:last-child)) { - --tw-space-y-reverse: 0; - margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse)); - margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse))); - } - } .gap-x-6 { column-gap: calc(var(--spacing) * 6); } @@ -333,12 +335,6 @@ .bg-blue-500 { background-color: var(--color-blue-500); } - .bg-blue-600 { - background-color: var(--color-blue-600); - } - .bg-gray-100 { - background-color: var(--color-gray-100); - } .bg-green-100 { background-color: var(--color-green-100); } @@ -354,33 +350,21 @@ .p-6 { padding: calc(var(--spacing) * 6); } - .p-8 { - padding: calc(var(--spacing) * 8); - } .px-4 { padding-inline: calc(var(--spacing) * 4); } .px-6 { padding-inline: calc(var(--spacing) * 6); } - .px-8 { - padding-inline: calc(var(--spacing) * 8); - } .py-3 { padding-block: calc(var(--spacing) * 3); } - .py-8 { - padding-block: calc(var(--spacing) * 8); - } .py-12 { padding-block: calc(var(--spacing) * 12); } .text-center { text-align: center; } - .font-sans { - font-family: var(--font-sans); - } .text-2xl { font-size: var(--text-2xl); line-height: var(--tw-leading, var(--text-2xl--line-height)); @@ -405,10 +389,6 @@ font-size: var(--text-xl); line-height: var(--tw-leading, var(--text-xl--line-height)); } - .leading-6 { - --tw-leading: calc(var(--spacing) * 6); - line-height: calc(var(--spacing) * 6); - } .leading-8 { --tw-leading: calc(var(--spacing) * 8); line-height: calc(var(--spacing) * 8); @@ -429,12 +409,6 @@ --tw-tracking: var(--tracking-tight); letter-spacing: var(--tracking-tight); } - .break-all { - word-break: break-all; - } - .text-blue-600 { - color: var(--color-blue-600); - } .text-gray-500 { color: var(--color-gray-500); } @@ -467,10 +441,6 @@ --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } - .shadow-md { - --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); - box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); - } .shadow-sm { --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); @@ -504,13 +474,6 @@ } } } - .hover\:bg-blue-700 { - &:hover { - @media (hover: hover) { - background-color: var(--color-blue-700); - } - } - } .hover\:text-blue-600 { &:hover { @media (hover: hover) { @@ -518,13 +481,6 @@ } } } - .hover\:text-gray-700 { - &:hover { - @media (hover: hover) { - color: var(--color-gray-700); - } - } - } .focus\:ring-2 { &:focus { --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); @@ -568,11 +524,6 @@ background-color: var(--color-yellow-900); } } - .dark\:text-gray-100 { - @media (prefers-color-scheme: dark) { - color: var(--color-gray-100); - } - } .dark\:text-gray-300 { @media (prefers-color-scheme: dark) { color: var(--color-gray-300); @@ -607,15 +558,6 @@ } } } - .dark\:hover\:text-gray-300 { - @media (prefers-color-scheme: dark) { - &:hover { - @media (hover: hover) { - color: var(--color-gray-300); - } - } - } - } } @layer theme, base, components, utilities; @layer theme; @@ -858,11 +800,6 @@ syntax: "*"; inherits: false; } -@property --tw-space-y-reverse { - syntax: "*"; - inherits: false; - initial-value: 0; -} @property --tw-leading { syntax: "*"; inherits: false; @@ -1016,7 +953,6 @@ --tw-rotate-z: initial; --tw-skew-x: initial; --tw-skew-y: initial; - --tw-space-y-reverse: 0; --tw-leading: initial; --tw-font-weight: initial; --tw-tracking: initial;