URL expiration method completed
This commit is contained in:
parent
1f8eb6103c
commit
a399126e3b
8 changed files with 548 additions and 132 deletions
21
Model/SentQuestionnaire.cs
Normal file
21
Model/SentQuestionnaire.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Model
|
||||||
|
{
|
||||||
|
public class SentQuestionnaire
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string? Email { get; set; }
|
||||||
|
|
||||||
|
public int QuestionnaireId { get; set; }
|
||||||
|
[ForeignKey("QuestionnaireId")]
|
||||||
|
public Questionnaire? Questionnaire { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,10 +4,12 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Elfie.Extensions;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Model;
|
using Model;
|
||||||
using Services.EmailSend;
|
using Services.EmailSend;
|
||||||
using Services.Interaces;
|
using Services.Interaces;
|
||||||
|
using System.Globalization;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Web.ViewModel.QuestionnaireVM;
|
using Web.ViewModel.QuestionnaireVM;
|
||||||
|
|
@ -461,20 +463,40 @@ namespace Web.Areas.Admin.Controllers
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
Guid guid = Guid.NewGuid();
|
|
||||||
|
|
||||||
// Convert the GUID to a string
|
|
||||||
string guidString = guid.ToString();
|
|
||||||
|
|
||||||
// Construct the complete URL with the GUID-style ID
|
|
||||||
|
|
||||||
|
|
||||||
// Build the email body with questionnaire details
|
// Build the email body with questionnaire details
|
||||||
var questionnairePath = _configuration["Email:Questionnaire"];
|
var questionnairePath = _configuration["Email:Questionnaire"];
|
||||||
int surveyId = viewModel.QuestionnaireId;
|
int surveyId = viewModel.QuestionnaireId;
|
||||||
|
|
||||||
var completeUrl = $"{Request.Scheme}://{Request.Host}/{questionnairePath}/{viewModel.QuestionnaireId}";
|
//DateTime currentDateTime = DateTime.Now;
|
||||||
|
//DateTime currentDateTime = viewModel.ExpirationDateTime;
|
||||||
|
DateTime currentDateTime;
|
||||||
|
if (viewModel.ExpirationDateTime.HasValue)
|
||||||
|
{
|
||||||
|
currentDateTime = viewModel.ExpirationDateTime.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Handle the case when ExpirationDateTime is null
|
||||||
|
// For example, you can assign the current date and time
|
||||||
|
currentDateTime = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the expiration date and time by adding 5 minutes to the current date and time
|
||||||
|
DateTime expiryDateTime = currentDateTime;
|
||||||
|
|
||||||
|
// Generate a unique token, for example, using a cryptographic library or a GUID
|
||||||
|
string token = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
// Append the expiration date and time to the token (you might want to encrypt it for security)
|
||||||
|
string tokenWithExpiry = $"{token}|{expiryDateTime.ToString("yyyy-MM-ddTHH:mm:ssZ")}";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var completeUrl = $"{Request.Scheme}://{Request.Host}/{questionnairePath}/{viewModel.QuestionnaireId}?token={tokenWithExpiry}";
|
||||||
|
|
||||||
|
//var completeUrl = $"{Request.Scheme}://{Request.Host}/{questionnairePath}/{viewModel.QuestionnaireId}";
|
||||||
|
|
||||||
|
|
||||||
var toEmail = viewModel.Email;
|
var toEmail = viewModel.Email;
|
||||||
|
|
@ -491,6 +513,9 @@ namespace Web.Areas.Admin.Controllers
|
||||||
body {{
|
body {{
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
}}
|
}}
|
||||||
|
.text-danger {{
|
||||||
|
color:red;
|
||||||
|
}}
|
||||||
.container {{
|
.container {{
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
@ -517,12 +542,14 @@ namespace Web.Areas.Admin.Controllers
|
||||||
<h4>Hey {viewModel.Name},</h4>
|
<h4>Hey {viewModel.Name},</h4>
|
||||||
<h5>{subject}</h5>
|
<h5>{subject}</h5>
|
||||||
<p>Thank you for participating in our survey. Your feedback is valuable to us.</p>
|
<p>Thank you for participating in our survey. Your feedback is valuable to us.</p>
|
||||||
<p>Please click the button below to start the survey:</p><br>
|
<p>Please click the button below to start the survey:</p>
|
||||||
|
<p class=""text-danger"">The survey will be expire in Date:{expiryDateTime.ToLongDateString()} Time: {expiryDateTime.ToShortTimeString()} </p>
|
||||||
<div style='text-align: center;'>
|
<div style='text-align: center;'>
|
||||||
<a href='{completeUrl}' class='button'>Start Survey</a>
|
<a href='{completeUrl}' class='button'>Start Survey</a>
|
||||||
</div><br>
|
<br>
|
||||||
|
|
||||||
<p><strong>Søren Eggert Lundsteen Olsen</strong><br>
|
</div><br>
|
||||||
|
<p><strong>Søren Eggert Lundsteen Olsen</strong>
|
||||||
Seosoft ApS<br>
|
Seosoft ApS<br>
|
||||||
<hr>
|
<hr>
|
||||||
Hovedgaden 3
|
Hovedgaden 3
|
||||||
|
|
@ -558,6 +585,18 @@ namespace Web.Areas.Admin.Controllers
|
||||||
// If model state is not valid, return the view with validation errors
|
// If model state is not valid, return the view with validation errors
|
||||||
return View(viewModel);
|
return View(viewModel);
|
||||||
}
|
}
|
||||||
|
public string GenerateExpiryToken(DateTime expiryDate)
|
||||||
|
{
|
||||||
|
// Generate a unique token, for example, using a cryptographic library or a GUID
|
||||||
|
string token = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
// Append the expiration date to the token (you might want to encrypt it for security)
|
||||||
|
string tokenWithExpiry = $"{token}|{expiryDate.ToString("yyyy-MM-ddTHH:mm:ssZ")}";
|
||||||
|
|
||||||
|
return tokenWithExpiry;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,11 @@
|
||||||
<input asp-for="Email" class="form-control" />
|
<input asp-for="Email" class="form-control" />
|
||||||
<span asp-validation-for="Email" class="text-danger"></span>
|
<span asp-validation-for="Email" class="text-danger"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label asp-for="ExpirationDateTime" class="control-label"></label>
|
||||||
|
<input asp-for="ExpirationDateTime" class="form-control"/>
|
||||||
|
<span asp-validation-for="ExpirationDateTime" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="hidden" asp-for="QuestionnaireId" /> <!-- Use hidden input for QuestionnaireId -->
|
<input type="hidden" asp-for="QuestionnaireId" /> <!-- Use hidden input for QuestionnaireId -->
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Services.Interaces;
|
using Services.Interaces;
|
||||||
|
using System.Globalization;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Web.ViewModel.QuestionnaireVM;
|
||||||
|
|
||||||
namespace Web.Controllers
|
namespace Web.Controllers
|
||||||
{
|
{
|
||||||
|
|
@ -15,20 +17,102 @@ namespace Web.Controllers
|
||||||
}
|
}
|
||||||
public IActionResult Index()
|
public IActionResult Index()
|
||||||
{
|
{
|
||||||
|
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult DisplayQuestionnaire(int id)
|
public IActionResult Error()
|
||||||
{
|
{
|
||||||
|
ViewBag.ErrorMessage = "The survey link has expired. request a new link.";
|
||||||
|
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult DisplayQuestionnaire(int id, string token)
|
||||||
|
{
|
||||||
|
// Check if the token is null or empty
|
||||||
|
if (string.IsNullOrEmpty(token))
|
||||||
|
{
|
||||||
|
ViewBag.ErrorMessage = "The URL is invalid. Please provide a valid token.";
|
||||||
|
return View("Error");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the token to extract the expiration date and time
|
||||||
|
string[] tokenParts = token.Split('|');
|
||||||
|
if (tokenParts.Length != 2)
|
||||||
|
{
|
||||||
|
ViewBag.ErrorMessage = "The URL is invalid. Please provide a valid token.";
|
||||||
|
return View("Error");
|
||||||
|
}
|
||||||
|
|
||||||
|
string expiryDateTimeString = tokenParts[1];
|
||||||
|
|
||||||
|
// Parse the expiration datetime in UTC format
|
||||||
|
if (!DateTime.TryParseExact(expiryDateTimeString, "yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out DateTime expiryDateTimeUtc))
|
||||||
|
{
|
||||||
|
ViewBag.ErrorMessage = "The URL is invalid. Please provide a valid token.";
|
||||||
|
return View("Error");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the expiration datetime to local time
|
||||||
|
DateTime expiryDateTimeLocal = expiryDateTimeUtc.ToLocalTime();
|
||||||
|
|
||||||
|
// Check if the token is expired (accounting for UTC+2 offset)
|
||||||
|
if (expiryDateTimeLocal < DateTime.Now.AddHours(2))
|
||||||
|
{
|
||||||
|
|
||||||
|
return RedirectToAction(nameof(Error));
|
||||||
|
}
|
||||||
|
|
||||||
// Retrieve the questionnaire using the numeric ID
|
// Retrieve the questionnaire using the numeric ID
|
||||||
var questionnaire = _questionnaireRepository.GetQuestionnaireWithQuestionAndAnswer(id);
|
var questionnaire = _questionnaireRepository.GetQuestionnaireWithQuestionAndAnswer(id);
|
||||||
|
|
||||||
|
|
||||||
// Display the questionnaire
|
|
||||||
return View(questionnaire);
|
return View(questionnaire);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//public IActionResult DisplayQuestionnaire(int id, string token)
|
||||||
|
//{
|
||||||
|
// // Check if the token is null or empty
|
||||||
|
// if (string.IsNullOrEmpty(token))
|
||||||
|
// {
|
||||||
|
// ViewBag.ErrorMessage = "The URL is invalid. Please provide a valid token.";
|
||||||
|
// return View("Error");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Split the token to extract the expiration date and time
|
||||||
|
// string[] tokenParts = token.Split('|');
|
||||||
|
// if (tokenParts.Length != 2)
|
||||||
|
// {
|
||||||
|
// ViewBag.ErrorMessage = "The URL is invalid. Please provide a valid token.";
|
||||||
|
// return View("Error");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// string expiryDateTimeString = tokenParts[1];
|
||||||
|
|
||||||
|
// // Parse the expiration datetime in UTC format
|
||||||
|
// if (!DateTime.TryParseExact(expiryDateTimeString, "yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out DateTime expiryDateTimeUtc))
|
||||||
|
// {
|
||||||
|
// ViewBag.ErrorMessage = "The URL is invalid. Please provide a valid token.";
|
||||||
|
// return View("Error");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Convert the expiration datetime to local time
|
||||||
|
// DateTime expiryDateTimeLocal = expiryDateTimeUtc.ToLocalTime();
|
||||||
|
|
||||||
|
// // Check if the token is expired (accounting for time zone offset)
|
||||||
|
// if (expiryDateTimeLocal >= DateTime.Now.AddHours(1))
|
||||||
|
// {
|
||||||
|
|
||||||
|
// return RedirectToAction(nameof(Error));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Retrieve the questionnaire using the numeric ID
|
||||||
|
// var questionnaire = _questionnaireRepository.GetQuestionnaireWithQuestionAndAnswer(id);
|
||||||
|
|
||||||
|
// return View(questionnaire);
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using NuGet.Protocol.Core.Types;
|
using NuGet.Protocol.Core.Types;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Web.ViewModel.QuestionnaireVM
|
namespace Web.ViewModel.QuestionnaireVM
|
||||||
|
|
@ -12,6 +13,10 @@ namespace Web.ViewModel.QuestionnaireVM
|
||||||
[Required]
|
[Required]
|
||||||
public string? Email { get; set; }
|
public string? Email { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[DisplayName("Set expiration date and time for the URL")]
|
||||||
|
public DateTime? ExpirationDateTime { get; set; }
|
||||||
|
|
||||||
public int QuestionnaireId { get; set; }
|
public int QuestionnaireId { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,128 +3,192 @@
|
||||||
ViewData["Title"] = "DisplayQuestionnaire";
|
ViewData["Title"] = "DisplayQuestionnaire";
|
||||||
Layout = "~/Views/Shared/_QuestionnaireResponse.cshtml";
|
Layout = "~/Views/Shared/_QuestionnaireResponse.cshtml";
|
||||||
}
|
}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
h5,h4{
|
body{
|
||||||
color: #b9b9b9 !important;
|
|
||||||
}
|
background-repeat:no-repeat;
|
||||||
.rating {
|
background: linear-gradient(119deg, rgba(47,82,131,1) 0%, rgba(29,33,59,1) 34%, rgba(27,54,61,1) 67%, rgba(58,82,116,1) 100%) !important
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: row-reverse; /* Set the direction to row-reverse */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rating input {
|
.QuestionContainer{
|
||||||
display: none;
|
background-color:transparent !important;
|
||||||
|
margin-top:100px;
|
||||||
|
}
|
||||||
|
.stepper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rating label {
|
.step-indicator {
|
||||||
cursor: pointer;
|
width: 150px;
|
||||||
font-size: 0; /* Hide the labels */
|
height: 30px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color:transparent;
|
||||||
|
margin-bottom:10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: white; /* Text color */
|
||||||
|
font-weight: bold;
|
||||||
|
border: 0.05px solid #294255;
|
||||||
|
box-shadow: 0px 0px 6px 2px rgba(0,0,0,0.18);
|
||||||
|
-webkit-box-shadow: 0px 0px 6px 2px rgba(0,0,0,0.18);
|
||||||
|
-moz-box-shadow: 0px 0px 6px 2px rgba(0,0,0,0.18);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rating label:before {
|
.step-indicator.active {
|
||||||
content: "☆";
|
background-color: #33b3ae; /* Primary color for active step */
|
||||||
font-size: 25px; /* Set the font size of the stars */
|
box-shadow: 0px 0px 21px -4px rgba(0,0,0,0.45);
|
||||||
|
-webkit-box-shadow: 0px 0px 21px -4px rgba(0,0,0,0.45);
|
||||||
|
-moz-box-shadow: 0px 0px 21px -4px rgba(0,0,0,0.45);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rating input:checked ~ label:before {
|
|
||||||
content: "★";
|
h4, h5,h6, p, label {
|
||||||
color: orange;
|
color:aliceblue;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider-value {
|
.hero {
|
||||||
display: block;
|
background-color:transparent !important;
|
||||||
text-align: center;
|
}
|
||||||
margin-top: -20px; /* Adjust the margin to position the value */
|
section {
|
||||||
|
margin-top:30px;
|
||||||
|
}
|
||||||
|
.card{
|
||||||
|
box-shadow: 0px 0px 36px -12px rgba(20,101,230,1);
|
||||||
|
-webkit-box-shadow: 0px 0px 36px -12px rgba(20,101,230,1);
|
||||||
|
-moz-box-shadow: 0px 0px 36px -12px rgba(20,101,230,1);
|
||||||
|
border-radius:10px;
|
||||||
|
background-color:transparent;
|
||||||
|
padding:30px
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
width: 80%;
|
||||||
|
margin: 15px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="d-flex flex-column" id="BannerBackground">
|
|
||||||
|
|
||||||
|
|
||||||
<section class="hero text-white">
|
<div class="QuestionContainer">
|
||||||
<div class="container text-white mt-5">
|
<div class="container card">
|
||||||
<h4><strong>@Html.Raw(Model.Description)</strong> </h4>
|
|
||||||
@{
|
|
||||||
int questionNumber = 1; // Counter for question numbers, starting from 1
|
|
||||||
}
|
|
||||||
|
|
||||||
<form method="post" action="@Url.Action("Submit", "Questionnaire", new { id = Model.Id })">
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@foreach (var question in Model.Questions)
|
|
||||||
|
<h3 class="text-danger">@ViewBag.ErrorMessage</h3>
|
||||||
|
|
||||||
|
<h4>@Model.Title</h4>
|
||||||
|
<p>@Html.Raw(Model.Description) </p>
|
||||||
|
</div>
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<!-- Stepper -->
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="stepper">
|
||||||
|
@for (int i = 0; i < Model.Questions.Count; i++)
|
||||||
{
|
{
|
||||||
<h5>@questionNumber. @question.Text</h5> <!-- Display question number -->
|
var question = Model.Questions[i];
|
||||||
<div>
|
string stepClass = i == 0 ? "active" : ""; // Adjusted the index to start from the first question
|
||||||
@if (question.Type == QuestionType.Multiple_choice)
|
<div class="step-indicator @(stepClass)" data-step-index="@i">
|
||||||
|
<span class="step-number">@((i + 1)). </span> <!-- Adjusted to start from 1 -->
|
||||||
|
<span class="step-label">@question.Type</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Form Content -->
|
||||||
|
<div class="col-md-9">
|
||||||
|
<form id="questionnaireForm">
|
||||||
|
@for (int i = 0; i < Model.Questions.Count; i++)
|
||||||
{
|
{
|
||||||
|
var question = Model.Questions[i];
|
||||||
|
<div class="step @(i == 0 ? "active" : "")">
|
||||||
|
<p class="font-weight-normal">@(i + 1). @question.Text</p>
|
||||||
|
@switch (question.Type)
|
||||||
|
{
|
||||||
|
case QuestionType.Text:
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" class="form-control" name="question@(i + 1)" placeholder="Enter answer">
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
case QuestionType.CheckBox:
|
||||||
|
case QuestionType.Multiple_choice:
|
||||||
|
case QuestionType.Rating:
|
||||||
|
case QuestionType.Likert:
|
||||||
|
case QuestionType.Matrix:
|
||||||
|
case QuestionType.Demographic:
|
||||||
|
case QuestionType.Ranking:
|
||||||
|
<div class="form-group">
|
||||||
@foreach (var answer in question.Answers)
|
@foreach (var answer in question.Answers)
|
||||||
{
|
{
|
||||||
<label>
|
<div class="form-check">
|
||||||
<input type="checkbox" name="answers[@question.Id]" value="@answer.Id" class="form-check-input" /> @answer.Text
|
<input class="form-check-input" type="checkbox" id="question@(i + 1)_answer@(answer.Id)" name="question@(i + 1)" value="@answer.Id">
|
||||||
|
<label class="form-check-label" for="question@(i + 1)_answer@(answer.Id)">
|
||||||
|
@answer.Text
|
||||||
</label>
|
</label>
|
||||||
<br />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (question.Type == QuestionType.Ranking)
|
|
||||||
{
|
|
||||||
<select name="answers[@question.Id]" class="form-control">
|
|
||||||
<option value="">Select Rank</option>
|
|
||||||
@for (int i = 1; i <= question.Answers.Count; i++)
|
|
||||||
{
|
|
||||||
<option value="@i">@i</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
}
|
|
||||||
else if (question.Type == QuestionType.TrueFalse)
|
|
||||||
{
|
|
||||||
<label>
|
|
||||||
<input type="radio" name="answers[@question.Id]" value="true" class="form-check-input" /> True
|
|
||||||
</label>
|
|
||||||
<br />
|
|
||||||
<label>
|
|
||||||
<input type="radio" name="answers[@question.Id]" value="false" class="form-check-input" /> False
|
|
||||||
</label>
|
|
||||||
}
|
|
||||||
else if (question.Type == QuestionType.Rating)
|
|
||||||
{
|
|
||||||
<div class="rating">
|
|
||||||
@for (int i = 1; i <= question.Answers.Count; i++)
|
|
||||||
{
|
|
||||||
<input type="radio" id="star@(i)" name="answers[@question.Id]" value="@i" class="form-check-input" />
|
|
||||||
<label for="star@(i)" title="@i">☆</label>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else if (question.Type == QuestionType.Text)
|
|
||||||
{
|
|
||||||
<input type="text" name="answers[@question.Id]" class="form-control" />
|
|
||||||
}
|
|
||||||
else if (question.Type == QuestionType.Slider)
|
|
||||||
{
|
|
||||||
<input type="range" name="answers[@question.Id]" min="0" max="100" value="0" class="slider" oninput="updateSliderValue(this.value, 'sliderValue_@question.Id')" /> <!-- Set initial value to 0 -->
|
|
||||||
<span id="sliderValue_@question.Id" class="slider-value">0</span> <!-- Display the slider value -->
|
|
||||||
}
|
|
||||||
<!-- Add handling for other question types as needed -->
|
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
break;
|
||||||
@@questionNumber
|
case QuestionType.TrueFalse:
|
||||||
++; // Increment question number after displaying
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" id="question@(i + 1)_true" name="question@(i + 1)" value="true">
|
||||||
|
<label class="form-check-label" for="question@(i + 1)_true">True</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" id="question@(i + 1)_false" name="question@(i + 1)" value="false">
|
||||||
|
<label class="form-check-label" for="question@(i + 1)_false">False</label>
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
case QuestionType.Open_ended:
|
||||||
|
<textarea class="form-control" id="question@(i + 1)" name="question@(i + 1)" rows="3" placeholder="Enter answer"></textarea>
|
||||||
|
break;
|
||||||
|
case QuestionType.Image:
|
||||||
|
<input type="file" class="form-control-file" id="question@(i + 1)" name="question@(i + 1)">
|
||||||
|
break;
|
||||||
|
case QuestionType.Slider:
|
||||||
|
<input type="range" class="form-range" id="question@(i + 1)" name="question@(i + 1)" min="0" max="100" step="1">
|
||||||
|
<output id="question@(i + 1)_output">50</output>
|
||||||
|
<script>
|
||||||
|
document.getElementById('question@(i + 1)').addEventListener('input', function () {
|
||||||
|
document.getElementById('question@(i + 1)_output').value = this.value;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
Unsupported question type.
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
<div class="mt-3">
|
||||||
|
@if (i > 0)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm mr-3 prev" id="BannerButon"><i class="bi bi-arrow-left"></i> Previous </button>
|
||||||
|
}
|
||||||
|
@if (i < Model.Questions.Count - 1)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary btn-sm next" id="BannerButon">Next <i class="bi bi-arrow-right"></i></button>
|
||||||
}
|
}
|
||||||
<button type="submit" class="btn btn-primary">Submit</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary submit btn-sm mt-4" id="BannerButon" style="display: none;">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -135,14 +199,138 @@
|
||||||
@{
|
@{
|
||||||
<partial name="_ValidationScriptsPartial" />
|
<partial name="_ValidationScriptsPartial" />
|
||||||
}
|
}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function updateSliderValue(value, targetId) {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
// Increase value by 10
|
const form = document.getElementById('questionnaireForm');
|
||||||
value = parseInt(value) + 10;
|
const steps = form.querySelectorAll('.step');
|
||||||
document.getElementById(targetId).innerText = value;
|
const stepIndicators = document.querySelectorAll('.step-indicator');
|
||||||
|
const submitButton = form.querySelector('.submit');
|
||||||
|
let currentStep = 0;
|
||||||
|
|
||||||
|
function showStep(index) {
|
||||||
|
steps.forEach((step, i) => {
|
||||||
|
if (i === index) {
|
||||||
|
step.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
step.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stepIndicators.forEach((indicator, i) => {
|
||||||
|
if (i === index) {
|
||||||
|
indicator.classList.add('active');
|
||||||
|
} else {
|
||||||
|
indicator.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (index === steps.length - 1) {
|
||||||
|
submitButton.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
submitButton.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToNextStep() {
|
||||||
|
if (currentStep < steps.length - 1) {
|
||||||
|
currentStep++;
|
||||||
|
showStep(currentStep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToPrevStep() {
|
||||||
|
if (currentStep > 0) {
|
||||||
|
currentStep--;
|
||||||
|
showStep(currentStep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextButtons = form.querySelectorAll('.next');
|
||||||
|
nextButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
goToNextStep();
|
||||||
|
updateStepper();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const prevButtons = form.querySelectorAll('.prev');
|
||||||
|
prevButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
goToPrevStep();
|
||||||
|
updateStepper();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateStepper() {
|
||||||
|
const currentStepIndex = currentStep;
|
||||||
|
stepIndicators.forEach((indicator, i) => {
|
||||||
|
if (i === currentStepIndex) {
|
||||||
|
indicator.style.backgroundColor = '#33b3ae'; // Change to your primary color
|
||||||
|
} else {
|
||||||
|
indicator.style.backgroundColor = ''; // Reset to default color for non-active steps
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showStep(currentStep);
|
||||||
|
updateStepper();
|
||||||
|
function toggleCheckbox(labelElement) {
|
||||||
|
var checkbox = labelElement.previousElementSibling; // Find the nearest preceding checkbox
|
||||||
|
checkbox.checked = !checkbox.checked;
|
||||||
|
checkbox.focus(); // Focus the checkbox after clicking on the label
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@* <script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const form = document.getElementById('questionnaireForm');
|
||||||
|
const steps = form.querySelectorAll('.step');
|
||||||
|
const submitButton = form.querySelector('.submit');
|
||||||
|
let currentStep = 0;
|
||||||
|
|
||||||
|
function showStep(index) {
|
||||||
|
steps.forEach((step, i) => {
|
||||||
|
if (i === index) {
|
||||||
|
step.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
step.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (index === steps.length - 1) {
|
||||||
|
submitButton.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
submitButton.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToNextStep() {
|
||||||
|
if (currentStep < steps.length - 1) {
|
||||||
|
currentStep++;
|
||||||
|
showStep(currentStep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToPrevStep() {
|
||||||
|
if (currentStep > 0) {
|
||||||
|
currentStep--;
|
||||||
|
showStep(currentStep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextButtons = form.querySelectorAll('.next');
|
||||||
|
nextButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', goToNextStep);
|
||||||
|
});
|
||||||
|
|
||||||
|
const prevButtons = form.querySelectorAll('.prev');
|
||||||
|
prevButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', goToPrevStep);
|
||||||
|
});
|
||||||
|
|
||||||
|
showStep(currentStep);
|
||||||
|
});
|
||||||
|
</script> *@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
73
Web/Views/QuestionnaireResponse/Error.cshtml
Normal file
73
Web/Views/QuestionnaireResponse/Error.cshtml
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Error";
|
||||||
|
Layout = "~/Views/Shared/_QuestionnaireResponse.cshtml";
|
||||||
|
}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
height:100% !important;
|
||||||
|
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background: linear-gradient(119deg, rgba(47,82,131,1) 0%, rgba(29,33,59,1) 34%, rgba(27,54,61,1) 67%, rgba(58,82,116,1) 100%) !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.containerError {
|
||||||
|
|
||||||
|
margin-top:150px;
|
||||||
|
height:600px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.card{
|
||||||
|
position: relative;
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-box-direction: normal;
|
||||||
|
-ms-flex-direction: column;
|
||||||
|
flex-direction: row;
|
||||||
|
min-width: 0;
|
||||||
|
word-wrap: break-word;
|
||||||
|
box-shadow: 0px 0px 36px -12px rgba(20,101,230,1);
|
||||||
|
-webkit-box-shadow: 0px 0px 36px -12px rgba(20,101,230,1);
|
||||||
|
-moz-box-shadow: 0px 0px 36px -12px rgba(20,101,230,1);
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
height: 500px;
|
||||||
|
padding-left: 50px;
|
||||||
|
padding-right: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<div class="containerError">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="card">
|
||||||
|
<!-- Stepper -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h5 class="text-danger">@ViewBag.ErrorMessage</h5>
|
||||||
|
<a href="#" class="btn btn-sm mt-3" id="BannerButon"> Contact <i class="bi bi-envelope-at-fill"></i></a>
|
||||||
|
</div>
|
||||||
|
<!-- Form Content -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<script src="https://unpkg.com/@@dotlottie/player-component@latest/dist/dotlottie-player.mjs" type="module"></script>
|
||||||
|
<dotlottie-player src="https://lottie.host/e3084d59-5df1-44e9-a0fe-0a015257fba7/LVAJX2PbZo.json" class="img-fluid" speed="1" style="width: auto; height: auto;" direction="1" playMode="normal" loop autoplay></dotlottie-player>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -343,6 +343,7 @@ body, html {
|
||||||
color: #6c757d !important;
|
color: #6c757d !important;
|
||||||
transition: 394ms;
|
transition: 394ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*_______________________________________________________________________________end of the custom CSS_____________________________________________________________*/
|
/*_______________________________________________________________________________end of the custom CSS_____________________________________________________________*/
|
||||||
*,
|
*,
|
||||||
*::before,
|
*::before,
|
||||||
|
|
@ -370,7 +371,7 @@ body, html {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: #212529;
|
color: #212529;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
background-color: #dfdfdf !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[tabindex="-1"]:focus {
|
[tabindex="-1"]:focus {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue