diff --git a/Services/EmailSend/EmailToSend.cs b/Services/EmailSend/EmailToSend.cs index 67050e3..8371606 100644 --- a/Services/EmailSend/EmailToSend.cs +++ b/Services/EmailSend/EmailToSend.cs @@ -8,15 +8,19 @@ namespace Services.EmailSend { public class EmailToSend { - public EmailToSend(string to, string subject, string body) + public string To { get; set; } + public string Subject { get; set; } + public string HtmlBody { get; set; } + public Dictionary Headers { get; set; } + + public EmailToSend(string to, string subject, string htmlBody) { To = to; Subject = subject; - Body = body; + HtmlBody = htmlBody; + Headers = new Dictionary(); // optional unless needed } - - public string To { get; set; } - public string Subject { get; set; } - public string Body { get; set; } } + + } diff --git a/Services/Implemnetation/EmailServices.cs b/Services/Implemnetation/EmailServices.cs index 0e1fcc9..7d06c7e 100644 --- a/Services/Implemnetation/EmailServices.cs +++ b/Services/Implemnetation/EmailServices.cs @@ -17,37 +17,50 @@ namespace Services.Implemnetation { _configuration = configuration; } + public async Task SendConfirmationEmailAsync(EmailToSend emailSend) { + var apiKey = _configuration["MailJet:ApiKey"]; + var secretKey = _configuration["MailJet:SecretKey"]; + var fromEmail = _configuration["Email:From"]; + var fromName = _configuration["Email:ApplicationName"]; + var client = new MailjetClient(apiKey, secretKey); - - MailjetClient client = new MailjetClient(_configuration["MailJet:ApiKey"], _configuration["MailJet:SecretKey"]); - - var email = new TransactionalEmailBuilder() - .WithFrom(new SendContact(_configuration["Email:From"], _configuration["Email:ApplicationName"])) - .WithSubject(emailSend.Subject) - .WithHtmlPart(emailSend.Body) - .WithTo(new SendContact(emailSend.To)) - .Build(); - - - var response = await client.SendTransactionalEmailAsync(email); - - if (response.Messages != null) + var message = new JObject { - if (response.Messages[0].Status == "success") + ["From"] = new JObject { - return true; + ["Email"] = fromEmail, + ["Name"] = fromName + }, + ["To"] = new JArray + { + new JObject + { + ["Email"] = emailSend.To, + ["Name"] = emailSend.To.Split('@')[0] } + }, + ["Subject"] = emailSend.Subject, + ["HTMLPart"] = emailSend.HtmlBody + }; + + // ✨ Add headers if any + if (emailSend.Headers != null && emailSend.Headers.Any()) + { + message["Headers"] = JObject.FromObject(emailSend.Headers); } + var request = new MailjetRequest + { + Resource = SendV31.Resource + } + .Property(Send.Messages, new JArray { message }); - return false; - - + var response = await client.PostAsync(request); + return response.IsSuccessStatusCode; } - - } + } diff --git a/Web/Areas/Admin/Controllers/QuestionnaireController.cs b/Web/Areas/Admin/Controllers/QuestionnaireController.cs index bfc5589..6a35625 100644 --- a/Web/Areas/Admin/Controllers/QuestionnaireController.cs +++ b/Web/Areas/Admin/Controllers/QuestionnaireController.cs @@ -464,80 +464,327 @@ namespace Web.Areas.Admin.Controllers } + [HttpPost] [HttpPost] public async Task SendQuestionnaire(SendQuestionnaireViewModel viewModel) { - if (ModelState.IsValid) + if (!ModelState.IsValid) + return View(viewModel); + + var questionnairePath = _configuration["Email:Questionnaire"]; + var subject = _questionnaire.GetQuesById(viewModel.Id)?.Title ?? "Survey Invitation"; + + var currentDateTime = viewModel.ExpirationDateTime ?? DateTime.Now; + string token = Guid.NewGuid().ToString(); + string tokenWithExpiry = $"{token}|{currentDateTime:yyyy-MM-ddTHH:mm:ssZ}"; + + var emailList = viewModel.Emails.Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(e => e.Trim()) + .ToList(); + + bool allEmailsSent = true; + + foreach (var email in emailList) { - var questionnairePath = _configuration["Email:Questionnaire"]; - DateTime currentDateTime = viewModel.ExpirationDateTime.HasValue ? viewModel.ExpirationDateTime.Value : DateTime.Now; - DateTime expiryDateTime = currentDateTime; // This line might need adjustment if you are actually setting an expiry. - string token = Guid.NewGuid().ToString(); - string tokenWithExpiry = $"{token}|{expiryDateTime.ToString("yyyy-MM-ddTHH:mm:ssZ")}"; - var emailList = viewModel.Emails.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(email => email.Trim()) - .ToList(); - var question = _questionnaire.GetQuesById(viewModel.Id); - var subject = question.Title; + string userName = FormatUserNameFromEmail(email); + string userEmailEncoded = HttpUtility.UrlEncode(email); + string completeUrl = $"{Request.Scheme}://{Request.Host}/{questionnairePath}/{viewModel.QuestionnaireId}?t={tokenWithExpiry}&E={userEmailEncoded}"; - bool allEmailsSent = true; - foreach (var email in emailList) + string emailBody = GenerateEmailBody(userName, subject, completeUrl, currentDateTime); + + var emailSend = new EmailToSend(email, subject, emailBody) { - var userName = email.Substring(0, email.IndexOf('@')); // This assumes the email is valid and contains an '@' - userName = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(userName.Replace(".", " ")); // Optional: Improve formatting, replace dots and capitalize names - var userEmailEncoded = HttpUtility.UrlEncode(email); - var completeUrl = $"{Request.Scheme}://{Request.Host}/{questionnairePath}/{viewModel.QuestionnaireId}?t={tokenWithExpiry}&E={userEmailEncoded}"; + Headers = new Dictionary + { + { "X-Priority", "1" }, + { "Importance", "High" } + } + }; - string emailBody = $@" - - - - - -
-

Hey {userName},

-
{subject}
-

Thank you for participating in our survey. Your feedback is valuable to us.

-

Please click the button below to start the survey:

-

The survey will expire: {expiryDateTime.ToLongDateString()} Time: {expiryDateTime.ToShortTimeString()}

-
-

Søren Eggert Lundsteen Olsen
- Seosoft ApS
-


- Hovedgaden 3 Jordrup
- Kolding 6064
- Denmark

-
- - "; + bool emailSent = await _emailServices.SendConfirmationEmailAsync(emailSend); - var emailSend = new EmailToSend(email, subject, emailBody); - bool emailSent = await _emailServices.SendConfirmationEmailAsync(emailSend); - if (!emailSent) - { - allEmailsSent = false; - ModelState.AddModelError(string.Empty, "Failed to send questionnaire via email to: " + email); - } - } - - - if (allEmailsSent) + if (!emailSent) { - TempData["Success"] = "Questionnaire sent successfully to all recipients."; - return RedirectToAction(nameof(Index)); + allEmailsSent = false; + ModelState.AddModelError(string.Empty, $"Failed to send questionnaire to: {email}"); } } - // If model state is not valid, return the view with validation errors + if (allEmailsSent) + { + TempData["Success"] = "Questionnaire sent successfully to all recipients."; + return RedirectToAction(nameof(Index)); + } + return View(viewModel); } + private string FormatUserNameFromEmail(string email) + { + var usernamePart = email.Split('@')[0]; + return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(usernamePart.Replace('.', ' ')); + } + + + private static string GenerateEmailBody(string userName, string subject, string url, DateTime expiry) + { + var danishCulture = new CultureInfo("da-DK"); + string expiryDate = expiry.ToString("dd. MMMM yyyy", danishCulture); + string expiryTime = expiry.ToString("HH:mm", danishCulture); + + return $@" + + + + + + Invitation til undersøgelse + + + + + +"; + } diff --git a/Web/Areas/Admin/Views/Footer/Edit.cshtml b/Web/Areas/Admin/Views/Footer/Edit.cshtml index 072c5a0..2bfbdaa 100644 --- a/Web/Areas/Admin/Views/Footer/Edit.cshtml +++ b/Web/Areas/Admin/Views/Footer/Edit.cshtml @@ -12,7 +12,7 @@
-
Update banner
+
Update Footer
diff --git a/Web/Extesions/ServicesExtesions.cs b/Web/Extesions/ServicesExtesions.cs index 86d81d4..1d48782 100644 --- a/Web/Extesions/ServicesExtesions.cs +++ b/Web/Extesions/ServicesExtesions.cs @@ -1,14 +1,10 @@ using Data; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.EntityFrameworkCore; using Model; using OpenAI_API; using Services.Implemnetation; using Services.Interaces; -using System.Net; using Web.AIConfiguration; namespace Web.Extesions diff --git a/Web/Views/Shared/_Layout.cshtml b/Web/Views/Shared/_Layout.cshtml index 03fa353..6411918 100644 --- a/Web/Views/Shared/_Layout.cshtml +++ b/Web/Views/Shared/_Layout.cshtml @@ -8,6 +8,7 @@ + diff --git a/Web/Web.csproj b/Web/Web.csproj index 81009b7..bcb22c3 100644 --- a/Web/Web.csproj +++ b/Web/Web.csproj @@ -13,7 +13,7 @@ - + diff --git a/Web/appsettings.json b/Web/appsettings.json index 755616b..0e77ab4 100644 --- a/Web/appsettings.json +++ b/Web/appsettings.json @@ -17,7 +17,7 @@ // "SurveyVista": "data source=SQL1003.site4now.net; Initial Catalog=db_ab8a17_vistasurvey;User Id=db_ab8a17_vistasurvey_admin,Password=1!QaisYousuf;integrated security=True; TrustServerCertificate=True;" //}, "Email": { - "From": "mr.qais.yousuf@gmail.com", + "From": "survey@asurvey.dk", "ApplicationName": "Online Survey", "ConfirmEmailPath": "Subscription/Confirmation", "unsubscribePath": "Subscription/UnsubscribeConfirmation", @@ -25,8 +25,8 @@ }, "MailJet": { - "ApiKey": "f545eee3a4743464b9d25fb9c5ab3f6c", - "SecretKey": "8df3cf0337a090b1d6301f312ca51413" + "ApiKey": "f06e28f892a81377545360662d8fe250", + "SecretKey": "244883216ed68f83d2d4107bc53c6484" } } diff --git a/Web/wwwroot/Images/LogoForEmail.png b/Web/wwwroot/Images/LogoForEmail.png new file mode 100644 index 0000000..2182bf7 Binary files /dev/null and b/Web/wwwroot/Images/LogoForEmail.png differ diff --git a/Web/wwwroot/css/site.css b/Web/wwwroot/css/site.css index d19043a..2502211 100644 --- a/Web/wwwroot/css/site.css +++ b/Web/wwwroot/css/site.css @@ -35,6 +35,182 @@ --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } /*_______________________________________________________________________________start of the custom CSS_____________________________________________________________*/ +/* Additional Footer Improvements - Compact Version */ + +/* Footer section styling */ +footer { + background: linear-gradient(135deg, #141c27 0%, #1a2332 100%); + position: relative; +} + + footer::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, #33b3ae, transparent); + } + +/* Compact container spacing */ +.container-fluid.px-4 { + padding-left: 3rem !important; + padding-right: 3rem !important; +} + +@media (max-width: 768px) { + .container-fluid.px-4 { + padding-left: 1.5rem !important; + padding-right: 1.5rem !important; + } +} + +@media (max-width: 576px) { + .container-fluid.px-4 { + padding-left: 1rem !important; + padding-right: 1rem !important; + } +} + +/* Compact footer headings */ +footer h5 { + color: #33b3ae !important; + font-weight: 600; + font-size: 1.1rem; + position: relative; + padding-bottom: 5px; +} + + footer h5::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 25px; + height: 2px; + background: #33b3ae; + } + +footer h6 { + color: #ffffff !important; + font-weight: 500; + font-size: 0.95rem; +} + +/* Compact social media links styling */ +footer .d-flex a { + padding: 6px 10px; + border: 1px solid rgba(51, 179, 174, 0.3); + border-radius: 4px; + transition: all 0.3s ease; + background: rgba(51, 179, 174, 0.1); + font-size: 0.85rem; +} + + footer .d-flex a:hover { + background: #33b3ae; + color: #ffffff !important; + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(51, 179, 174, 0.3); + } + +/* Compact navigation links styling */ +footer .nav-item a { + transition: all 0.3s ease; + padding: 2px 0; + display: block; + font-size: 0.9rem; +} + + footer .nav-item a:hover { + color: #33b3ae !important; + padding-left: 5px; + } + +/* Compact contact links styling */ +footer a[href^="mailto:"], +footer a[href^="tel:"] { + transition: all 0.3s ease; + font-size: 0.85rem; +} + + footer a[href^="mailto:"]:hover, + footer a[href^="tel:"]:hover { + color: #33b3ae !important; + } + +/* Compact bottom section styling */ +footer hr { + margin: 1rem 0 0.5rem 0; + opacity: 0.3; +} + +footer .row:last-child span { + font-size: 0.8rem; + display: flex; + align-items: center; +} + +/* Compact text styling */ +footer .small { + font-size: 0.85rem !important; +} + +footer .text-muted.small { + font-size: 0.8rem !important; +} + +/* Responsive improvements for compact design */ +@media (max-width: 992px) { + #rowSection { + flex-direction: column; + } + + #box { + width: 100% !important; + margin-bottom: 1.5rem; + } + + footer .container-fluid .row { + text-align: center; + } + + footer .d-flex { + justify-content: center !important; + } +} + +/* Compact animation for footer elements */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(15px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +footer .col-lg-3, +footer .col-lg-2, +footer .col-lg-4 { + animation: fadeInUp 0.5s ease forwards; +} + + footer .col-lg-3:nth-child(1) { + animation-delay: 0.1s; + } + + footer .col-lg-2:nth-child(2) { + animation-delay: 0.15s; + } + + footer .col-lg-4:nth-child(3) { + animation-delay: 0.2s; + } #Background { background-color: #141c27 !important; } @@ -420,6 +596,8 @@ body, html { } /*_______________________________________________________________________________end of the custom CSS_____________________________________________________________*/ + + *, *::before, *::after {