one time password
This commit is contained in:
		
							parent
							
								
									1d29b6ef4d
								
							
						
					
					
						commit
						77c665f64e
					
				| @ -0,0 +1,9 @@ | ||||
| namespace EnotaryoPH.Data.Constants | ||||
| { | ||||
|     public enum DocumentSigningStatus | ||||
|     { | ||||
|         New = 0, | ||||
|         ReadyForSigning = 10, | ||||
|         Signed = 20 | ||||
|     } | ||||
| } | ||||
| @ -22,6 +22,9 @@ | ||||
|         TransactionApproved = 41, | ||||
|         TransactionRejected = 42, | ||||
| 
 | ||||
|         OTPSent = 45, | ||||
|         OTPVerified = 46, | ||||
| 
 | ||||
|         VideoConferenceStarted = 50, | ||||
|         VideoRecordingStarted = 51, | ||||
|         VideoRecordingStopped = 52, | ||||
|  | ||||
| @ -8,6 +8,15 @@ namespace EnotaryoPH.Data.Entities | ||||
|         [Column("CreatedOn")] | ||||
|         public DateTime? CreatedOn { get; set; } | ||||
| 
 | ||||
|         [Column("Device")] | ||||
|         public string? Device { get; set; } | ||||
| 
 | ||||
|         [Column("IPAddress")] | ||||
|         public string? IPAddress { get; set; } | ||||
| 
 | ||||
|         [Column("Latitude")] | ||||
|         public decimal? Latitude { get; set; } | ||||
| 
 | ||||
|         [Column("LawyerVideoConferenceParticipant_UID")] | ||||
|         public Guid? LawyerVideoConferenceParticipant_UID { get; set; } | ||||
| 
 | ||||
| @ -20,12 +29,21 @@ namespace EnotaryoPH.Data.Entities | ||||
|         [Column("LawyerVideoConferenceScheduleID")] | ||||
|         public int LawyerVideoConferenceScheduleID { get; set; } | ||||
| 
 | ||||
|         [Column("Longitude")] | ||||
|         public decimal? Longitude { get; set; } | ||||
| 
 | ||||
|         [Column("MeetingRoomTokenID")] | ||||
|         public string? MeetingRoomTokenID { get; set; } | ||||
| 
 | ||||
|         [Column("MeetingRoomUserID")] | ||||
|         public string? MeetingRoomUserID { get; set; } | ||||
| 
 | ||||
|         [Column("OTPEntered")] | ||||
|         public string? OTPEntered { get; set; } | ||||
| 
 | ||||
|         [Column("OTPHash")] | ||||
|         public string? OTPHash { get; set; } | ||||
| 
 | ||||
|         [ForeignKey("ParticipantID")] | ||||
|         public User Participant { get; set; } | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,8 @@ | ||||
| namespace EnotaryoPH.Web.Common.Jobs.Models | ||||
| { | ||||
|     public class OTPEmailModel | ||||
|     { | ||||
|         public Dictionary<Guid, string> ParticipantOTP { get; set; } | ||||
|         public Guid Transaction_UID { get; set; } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,75 @@ | ||||
| using Coravel.Invocable; | ||||
| using Coravel.Mailer.Mail.Interfaces; | ||||
| using EnotaryoPH.Data; | ||||
| using EnotaryoPH.Web.Common.Jobs.Models; | ||||
| using EnotaryoPH.Web.Common.Models; | ||||
| using EnotaryoPH.Web.Mailables; | ||||
| 
 | ||||
| namespace EnotaryoPH.Web.Common.Jobs | ||||
| { | ||||
|     public class OneTimePasswordInvocable : IInvocable, IInvocableWithPayload<OTPEmailModel> | ||||
|     { | ||||
|         private readonly IMailer _mailer; | ||||
|         private readonly NotaryoDBContext _notaryoDBContext; | ||||
|         private readonly Settings _settings; | ||||
|         private readonly IPasswordService _passwordService; | ||||
|         private readonly IEventService _eventService; | ||||
| 
 | ||||
|         public OneTimePasswordInvocable(IMailer mailer, NotaryoDBContext notaryoDBContext, Settings settings, IPasswordService passwordService, IEventService eventService) | ||||
|         { | ||||
|             _mailer = mailer; | ||||
|             _notaryoDBContext = notaryoDBContext; | ||||
|             _settings = settings; | ||||
|             _passwordService = passwordService; | ||||
|             _eventService = eventService; | ||||
|         } | ||||
| 
 | ||||
|         public OTPEmailModel Payload { get; set; } | ||||
| 
 | ||||
|         public async Task Invoke() | ||||
|         { | ||||
|             var schedule = _notaryoDBContext.LawyerVideoConferenceSchedules | ||||
|                 .AsNoTracking() | ||||
|                 .Include(sched => sched.Transaction) | ||||
|                 .ThenInclude(transaction => transaction.TransactionDocument) | ||||
|                 .Include(sched => sched.Transaction) | ||||
|                 .ThenInclude(transaction => transaction.Lawyer) | ||||
|                 .ThenInclude(lawyer => lawyer.User) | ||||
|                 .Include(sched => sched.LawyerVideoConferenceParticipants) | ||||
|                 .ThenInclude(participant => participant.Participant) | ||||
|                 .FirstOrDefault(sched => sched.Transaction.Transaction_UID == Payload.Transaction_UID); | ||||
| 
 | ||||
|             if (schedule == null) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             var participantDic = schedule.LawyerVideoConferenceParticipants.ToDictionary(p => p.LawyerVideoConferenceParticipant_UID.GetValueOrDefault(), p => p); | ||||
|             foreach (var otp in Payload.ParticipantOTP) | ||||
|             { | ||||
|                 if (!participantDic.ContainsKey(otp.Key)) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|                 var participant = participantDic[otp.Key]; | ||||
| 
 | ||||
|                 var emailViewModel = new OneTimePasswordViewModel | ||||
|                 { | ||||
|                     DocumentType = schedule.Transaction.TransactionDocument.DocumentType, | ||||
|                     Email = participant.Participant.Email, | ||||
|                     OTP = otp.Value, | ||||
|                     ParticipantName = participant.Participant.Fullname, | ||||
|                     LawyerName = schedule.Transaction.Lawyer.User.Fullname, | ||||
|                     MeetingRoomURL = $"{_settings.BaseUrl}/Participant/VideoCall/Room/{schedule.Transaction.Transaction_UID}", | ||||
|                 }; | ||||
|                 await _mailer.SendAsync(new OneTimePasswordMailable(emailViewModel)); | ||||
|                 await _eventService.LogAsync(NotaryoEvent.OTPSent, Payload.Transaction_UID, new | ||||
|                 { | ||||
|                     emailViewModel.Email, | ||||
|                     emailViewModel.ParticipantName, | ||||
|                     emailViewModel.MeetingRoomURL | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,10 +1,14 @@ | ||||
| using Azure; | ||||
| using System.Security.Cryptography; | ||||
| using Azure; | ||||
| using Azure.Communication; | ||||
| using Azure.Communication.CallAutomation; | ||||
| using Azure.Communication.Identity; | ||||
| using Azure.Communication.Rooms; | ||||
| using Coravel.Queuing.Interfaces; | ||||
| using EnotaryoPH.Data; | ||||
| using EnotaryoPH.Data.Entities; | ||||
| using EnotaryoPH.Web.Common.Jobs; | ||||
| using EnotaryoPH.Web.Common.Jobs.Models; | ||||
| 
 | ||||
| namespace EnotaryoPH.Web.Common.Services | ||||
| { | ||||
| @ -17,9 +21,11 @@ namespace EnotaryoPH.Web.Common.Services | ||||
|         private readonly IConfiguration _configuration; | ||||
|         private readonly NotaryoDBContext _dbContext; | ||||
|         private readonly IEventService _eventService; | ||||
|         private readonly IPasswordService _passwordService; | ||||
|         private readonly IQueue _queue; | ||||
|         private readonly RoomsClient _roomsClient; | ||||
| 
 | ||||
|         public VideoConferenceService(NotaryoDBContext dbContext, CommunicationIdentityClient communicationIdentityClient, RoomsClient roomsClient, CallAutomationClient callAutomationClient, IConfiguration configuration, IEventService eventService) | ||||
|         public VideoConferenceService(NotaryoDBContext dbContext, CommunicationIdentityClient communicationIdentityClient, RoomsClient roomsClient, CallAutomationClient callAutomationClient, IConfiguration configuration, IEventService eventService, IPasswordService passwordService, IQueue queue) | ||||
|         { | ||||
|             _dbContext = dbContext; | ||||
|             _communicationIdentityClient = communicationIdentityClient; | ||||
| @ -27,6 +33,8 @@ namespace EnotaryoPH.Web.Common.Services | ||||
|             _callAutomationClient = callAutomationClient; | ||||
|             _configuration = configuration; | ||||
|             _eventService = eventService; | ||||
|             _passwordService = passwordService; | ||||
|             _queue = queue; | ||||
|         } | ||||
| 
 | ||||
|         public async Task ApproveTransactionAsync(Guid transaction_UID) | ||||
| @ -128,6 +136,7 @@ namespace EnotaryoPH.Web.Common.Services | ||||
|             if (schedule.Status == nameof(VideoConferenceStatus.New)) | ||||
|             { | ||||
|                 await CreateMeetingRoomAsync(schedule); | ||||
|                 await CreateAndEmailOTPAsync(schedule); | ||||
|                 _dbContext.UpdateOrCreate(schedule); | ||||
|                 _dbContext.SaveChanges(); | ||||
|             } | ||||
| @ -154,6 +163,23 @@ namespace EnotaryoPH.Web.Common.Services | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private async Task CreateAndEmailOTPAsync(LawyerVideoConferenceSchedule schedule) | ||||
|         { | ||||
|             var participantOTP = new Dictionary<Guid, string>(); | ||||
|             const string validChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; | ||||
|             foreach (var participant in schedule.LawyerVideoConferenceParticipants) | ||||
|             { | ||||
|                 var otp = RandomNumberGenerator.GetString(validChars, 5); | ||||
|                 participant.OTPHash = _passwordService.HashPassword(otp); | ||||
|                 participantOTP.Add(participant.LawyerVideoConferenceParticipant_UID.GetValueOrDefault(), otp); | ||||
|             } | ||||
|             _queue.QueueInvocableWithPayload<OneTimePasswordInvocable, OTPEmailModel>(new OTPEmailModel | ||||
|             { | ||||
|                 ParticipantOTP = participantOTP, | ||||
|                 Transaction_UID = schedule.Transaction.Transaction_UID.GetValueOrDefault() | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         private async Task CreateMeetingRoomAsync(LawyerVideoConferenceSchedule schedule) | ||||
|         { | ||||
|             var roomParticipants = new List<RoomParticipant>(); | ||||
| @ -181,7 +207,9 @@ namespace EnotaryoPH.Web.Common.Services | ||||
|                 .Include(t => t.TransactionSignatories) | ||||
|                 .Single(t => t.Transaction_UID == transaction_UID); | ||||
| 
 | ||||
|             var schedule = _dbContext.LawyerVideoConferenceSchedules.FirstOrDefault(sched => sched.TransactionID == transactionEntity.TransactionID); | ||||
|             var schedule = _dbContext.LawyerVideoConferenceSchedules | ||||
|                 .Include(sched => sched.Transaction) | ||||
|                 .FirstOrDefault(sched => sched.TransactionID == transactionEntity.TransactionID); | ||||
|             if (schedule != null) | ||||
|             { | ||||
|                 return schedule; | ||||
| @ -200,7 +228,7 @@ namespace EnotaryoPH.Web.Common.Services | ||||
|                 CreatedOn = DateTime.UtcNow, | ||||
|                 Status = nameof(VideoConferenceStatus.New), | ||||
|                 LawyerVideoConferenceParticipant_UID = Guid.CreateVersion7(DateTime.UtcNow), | ||||
|                 ParticipantID = signatory.UserID, | ||||
|                 ParticipantID = signatory.UserID | ||||
|             }); | ||||
|             participants.Add(new LawyerVideoConferenceParticipant | ||||
|             { | ||||
| @ -212,6 +240,7 @@ namespace EnotaryoPH.Web.Common.Services | ||||
| 
 | ||||
|             schedule.MeetingDate = DateTime.UtcNow; | ||||
|             schedule.LawyerVideoConferenceParticipants = participants.ToList(); | ||||
|             schedule.Transaction = transactionEntity; | ||||
|             return schedule; | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,16 @@ | ||||
| using Coravel.Mailer.Mail; | ||||
| 
 | ||||
| namespace EnotaryoPH.Web.Mailables | ||||
| { | ||||
|     public class OneTimePasswordMailable : Mailable<OneTimePasswordViewModel> | ||||
|     { | ||||
|         private readonly OneTimePasswordViewModel _model; | ||||
| 
 | ||||
|         public OneTimePasswordMailable(OneTimePasswordViewModel model) => _model = model; | ||||
| 
 | ||||
|         public override void Build() => this | ||||
|             .To(_model.Email) | ||||
|             .From("noreply@enotaryoph.com") | ||||
|             .View("~/Views/Mail/OneTimePassword.cshtml", _model); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,12 @@ | ||||
| namespace EnotaryoPH.Web.Mailables | ||||
| { | ||||
|     public class OneTimePasswordViewModel | ||||
|     { | ||||
|         public string DocumentType { get; set; } | ||||
|         public string Email { get; set; } | ||||
|         public string LawyerName { get; set; } | ||||
|         public string MeetingRoomURL { get; set; } | ||||
|         public string OTP { get; set; } | ||||
|         public string ParticipantName { get; set; } | ||||
|     } | ||||
| } | ||||
| @ -14,7 +14,6 @@ | ||||
|                 <div class="sidemenu"> | ||||
|                     <div class="align-items-center sidemenu__menuitem"><a class="d-flex flex-grow-1 justify-content-center align-items-center justify-content-md-start p-1 text-decoration-none" href="#available-transactions"><i class="far fa-check-circle fs-4 text-success sidemenu__menuitem__icon" style="padding: 5px;"></i><span class="d-none d-md-inline-block ms-1 sidemenu__menuitem__text">Available</span></a></div> | ||||
|                     <div class="align-items-center sidemenu__menuitem"><a class="d-flex flex-grow-1 justify-content-center align-items-center justify-content-md-start p-1 text-decoration-none" href="#incomplete-docs"><i class="far fa-clock fs-4 text-warning sidemenu__menuitem__icon" style="padding: 5px;"></i><span class="d-none d-md-inline-block ms-1 sidemenu__menuitem__text">My Jobs</span></a></div> | ||||
|                     <div class="align-items-center sidemenu__menuitem"><a class="d-flex flex-grow-1 justify-content-center align-items-center justify-content-md-start p-1 text-decoration-none" href="#identification-docs"><i class="far fa-address-card fs-4 text-dark sidemenu__menuitem__icon" style="padding: 5px;"></i><span class="d-none d-md-inline-block ms-1 sidemenu__menuitem__text">Identification Docs</span></a></div> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="col g-0 mx-2"> | ||||
| @ -27,7 +26,6 @@ | ||||
|                                     <tr> | ||||
|                                         <th>Type</th> | ||||
|                                         <th>Date</th> | ||||
|                                         <th>Status</th> | ||||
|                                         <th>Link</th> | ||||
|                                     </tr> | ||||
|                                 </thead> | ||||
| @ -37,8 +35,7 @@ | ||||
|                                         <tr> | ||||
|                                             <td>@transaction.Type</td> | ||||
|                                             <td>@transaction.Date.ToString("g")</td> | ||||
|                                             <td>@transaction.Status</td> | ||||
|                                             <td><a href="@transaction.Link">Link</a></td> | ||||
|                                             <td><a href="@transaction.Link">View</a></td> | ||||
|                                         </tr> | ||||
|                                     } | ||||
|                                 </tbody> | ||||
| @ -67,7 +64,7 @@ | ||||
|                                             <td>@transaction.Type</td> | ||||
|                                             <td>@transaction.Date.ToShortDateString()</td> | ||||
|                                             <td>@transaction.Status</td> | ||||
|                                             <td><a href="@transaction.Link">Link</a></td> | ||||
|                                             <td><a href="@transaction.Link">View</a></td> | ||||
|                                         </tr> | ||||
|                                     } | ||||
|                                 </tbody> | ||||
| @ -79,3 +76,9 @@ | ||||
|         </div> | ||||
|     </div> | ||||
| </section> | ||||
| 
 | ||||
| 
 | ||||
| @section Scripts { | ||||
|     <script src="~/lib/fontawesome-free-6.7.1-web/js/all.min.js"></script> | ||||
| 
 | ||||
| } | ||||
| @ -51,6 +51,8 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall | ||||
|                 return Redirect("/"); | ||||
|             } | ||||
| 
 | ||||
|             DocumentType = _Transaction.TransactionDocument.DocumentType; | ||||
| 
 | ||||
|             var schedule_UID = await _videoConferenceService.StartAsync(Transaction_UID); | ||||
| 
 | ||||
|             CommunicationUserToken = currentUser.Role == nameof(UserType.Notary) | ||||
| @ -70,7 +72,58 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall | ||||
|             return Page(); | ||||
|         } | ||||
| 
 | ||||
|         private LawyerVideoConferenceParticipant GetParticipant(User currentUser) => _Transaction.Schedule.LawyerVideoConferenceParticipants.First(u => u.ParticipantID == currentUser.UserID); | ||||
|         public IActionResult OnGetDocument() | ||||
|         { | ||||
|             var document = _dbContext.TransactionDocuments | ||||
|                 .Include(doc => doc.Transaction) | ||||
|                 .FirstOrDefault(doc => doc.Transaction.Transaction_UID == Transaction_UID); | ||||
| 
 | ||||
|             return new FileContentResult(document.File, "application/pdf"); | ||||
|         } | ||||
| 
 | ||||
|         public IActionResult OnGetIdentificationDocument(string meetingRoomUserID) | ||||
|         { | ||||
|             var participant = _dbContext.LawyerVideoConferenceParticipants | ||||
|                 .AsNoTracking() | ||||
|                 .FirstOrDefault(participant => participant.MeetingRoomUserID == meetingRoomUserID); | ||||
|             if (participant == null) | ||||
|             { | ||||
|                 return NotFound(); | ||||
|             } | ||||
|             var identificationDocumentID = _dbContext.TransactionSelfies.AsNoTracking() | ||||
|                 .Where(selfie => selfie.UserID == participant.ParticipantID && selfie.Transaction.Transaction_UID == Transaction_UID) | ||||
|                 .Select(selfie => selfie.IdentificationDocumentID).FirstOrDefault(); | ||||
|             if (identificationDocumentID == 0) | ||||
|             { | ||||
|                 return NotFound(); | ||||
|             } | ||||
| 
 | ||||
|             var identificationDocument = _dbContext.IdentificationDocuments.FirstOrDefault(id => id.IdentificationDocumentID == identificationDocumentID); | ||||
|             if (identificationDocument == null) | ||||
|             { | ||||
|                 return NotFound(); | ||||
|             } | ||||
| 
 | ||||
|             return new FileContentResult(identificationDocument.File, "image/jpeg"); | ||||
|         } | ||||
| 
 | ||||
|         public IActionResult OnGetSelfieImage(string meetingRoomUserID) | ||||
|         { | ||||
|             var participant = _dbContext.LawyerVideoConferenceParticipants | ||||
|                 .AsNoTracking() | ||||
|                 .FirstOrDefault(participant => participant.MeetingRoomUserID == meetingRoomUserID); | ||||
|             if (participant == null) | ||||
|             { | ||||
|                 return NotFound(); | ||||
|             } | ||||
|             var transactionSelfie = _dbContext.TransactionSelfies.FirstOrDefault(selfie => selfie.UserID == participant.ParticipantID && selfie.Transaction.Transaction_UID == Transaction_UID); | ||||
|             if (transactionSelfie == null) | ||||
|             { | ||||
|                 return NotFound(); | ||||
|             } | ||||
| 
 | ||||
|             return new FileContentResult(transactionSelfie.File, "image/jpeg"); | ||||
|         } | ||||
| 
 | ||||
|         public async Task<JsonResult> OnPostApproveAsync() | ||||
|         { | ||||
| @ -85,6 +138,8 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall | ||||
|             return new JsonResult(true); | ||||
|         } | ||||
| 
 | ||||
|         private LawyerVideoConferenceParticipant GetParticipant(User currentUser) => _Transaction.Schedule.LawyerVideoConferenceParticipants.First(u => u.ParticipantID == currentUser.UserID); | ||||
| 
 | ||||
|         private void LoadTransaction() => _Transaction = _dbContext.Transactions | ||||
|             .Include(t => t.TransactionSignatories) | ||||
|             .Include(t => t.Lawyer) | ||||
| @ -92,6 +147,7 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall | ||||
|             .Include(t => t.Principal) | ||||
|             .Include(t => t.Schedule) | ||||
|             .ThenInclude(sch => sch.LawyerVideoConferenceParticipants) | ||||
|             .Include(t => t.TransactionDocument) | ||||
|             .FirstOrDefault(t => t.Transaction_UID == Transaction_UID); | ||||
| 
 | ||||
|         public string CommunicationRoomId { get; private set; } | ||||
| @ -102,7 +158,7 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall | ||||
| 
 | ||||
|         public string DisplayName { get; private set; } | ||||
| 
 | ||||
|         public string ParticipantType { get; set; } | ||||
|         public string DocumentType { get; set; } | ||||
| 
 | ||||
|         public List<RoomParticipantViewModel> Participants | ||||
|         { | ||||
| @ -134,52 +190,9 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public string ParticipantType { get; set; } | ||||
|         [BindProperty(SupportsGet = true)] public string ServerCallID { get; set; } | ||||
| 
 | ||||
|         [BindProperty(SupportsGet = true)] public Guid Transaction_UID { get; set; } | ||||
| 
 | ||||
|         public IActionResult OnGetSelfieImage(string meetingRoomUserID) | ||||
|         { | ||||
|             var participant = _dbContext.LawyerVideoConferenceParticipants | ||||
|                 .AsNoTracking() | ||||
|                 .FirstOrDefault(participant => participant.MeetingRoomUserID == meetingRoomUserID); | ||||
|             if (participant == null) | ||||
|             { | ||||
|                 return NotFound(); | ||||
|             } | ||||
|             var transactionSelfie = _dbContext.TransactionSelfies.FirstOrDefault(selfie => selfie.UserID == participant.ParticipantID && selfie.Transaction.Transaction_UID == Transaction_UID); | ||||
|             if (transactionSelfie == null) | ||||
|             { | ||||
|                 return NotFound(); | ||||
|             } | ||||
| 
 | ||||
|             return new FileContentResult(transactionSelfie.File, "image/jpeg"); | ||||
|         } | ||||
| 
 | ||||
|         public IActionResult OnGetIdentificationDocument(string meetingRoomUserID) | ||||
|         { | ||||
|             var participant = _dbContext.LawyerVideoConferenceParticipants | ||||
|                 .AsNoTracking() | ||||
|                 .FirstOrDefault(participant => participant.MeetingRoomUserID == meetingRoomUserID); | ||||
|             if (participant == null) | ||||
|             { | ||||
|                 return NotFound(); | ||||
|             } | ||||
|             var identificationDocumentID = _dbContext.TransactionSelfies.AsNoTracking() | ||||
|                 .Where(selfie => selfie.UserID == participant.ParticipantID && selfie.Transaction.Transaction_UID == Transaction_UID) | ||||
|                 .Select(selfie => selfie.IdentificationDocumentID).FirstOrDefault(); | ||||
|             if (identificationDocumentID == 0) | ||||
|             { | ||||
|                 return NotFound(); | ||||
|             } | ||||
| 
 | ||||
|             var identificationDocument = _dbContext.IdentificationDocuments.FirstOrDefault(id => id.IdentificationDocumentID == identificationDocumentID); | ||||
|             if (identificationDocument == null) | ||||
|             { | ||||
|                 return NotFound(); | ||||
|             } | ||||
| 
 | ||||
|             return new FileContentResult(identificationDocument.File, "image/jpeg"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -6,21 +6,22 @@ | ||||
|     control_communicationUserId = document.getElementById("CommunicationUserId"), | ||||
|     control_communicationUserToken = document.getElementById("CommunicationUserToken"), | ||||
|     control_displayName = document.getElementById("DisplayName"), | ||||
|     control_participants = document.getElementById("Participants"), | ||||
|     control_reject = document.getElementById("Reject"), | ||||
|     control_templateSidePane = document.getElementById("TemplateSidePane"), | ||||
|     control_templateParticipantItem = document.getElementById("TemplateParticipantItem"), | ||||
|     control_draggableModal = document.getElementById("DraggableModal"), | ||||
|     control_identificationImage = document.getElementById("IdentificationImage"), | ||||
|     control_otpForm = document.getElementById("OtpForm"), | ||||
|     control_participantListGroup = document.getElementById("ParticipantListGroup"), | ||||
|     control_participants = document.getElementById("Participants"), | ||||
|     control_participantType = document.getElementById("ParticipantType"), | ||||
|     control_pdfViewer = document.getElementById("PdfViewer"), | ||||
|     control_reject = document.getElementById("Reject"), | ||||
|     control_rightSidebarModal = document.getElementById("RightSidebarModal"), | ||||
|     /*control_videoGrid = document.getElementById("VideoGrid"),*/ | ||||
|     control_selfieImage = document.getElementById("SelfieImage"), | ||||
|     control_serverCallIID = document.getElementById("ServerCallID"), | ||||
|     control_signatoryName = document.getElementById("SignatoryName"), | ||||
|     control_templateParticipantItem = document.getElementById("TemplateParticipantItem"), | ||||
|     control_templateSidePane = document.getElementById("TemplateSidePane"), | ||||
|     control_videoGridContainer = document.getElementById("videoGrid-container"), | ||||
|     control_viewDocument = document.getElementById("ViewDocument"), | ||||
|     control_serverCallIID = document.getElementById("ServerCallID"), | ||||
|     control_participantType = document.getElementById("ParticipantType"), | ||||
|     control_participantListGroup = document.getElementById("ParticipantListGroup"), | ||||
|     control_selfieImage = document.getElementById("SelfieImage"), | ||||
|     control_identificationImage = document.getElementById("IdentificationImage"), | ||||
|     control_signatoryName = document.getElementById("SignatoryName"), | ||||
|     x = 1; | ||||
| 
 | ||||
|   let participants = JSON.parse(control_participants.value); | ||||
| @ -40,7 +41,21 @@ | ||||
|               tooltipContent: 'View the document.' | ||||
|             }, | ||||
|             onItemClick: function () { | ||||
|               alert('Document Modal goes here.'); | ||||
|               const modal = bootstrap.Modal.getOrCreateInstance(control_pdfViewer); | ||||
|               modal.show(); | ||||
|             } | ||||
|           }; | ||||
|         }, | ||||
|         function (args) { | ||||
|           return { | ||||
|             placement: 'primary', | ||||
|             strings: { | ||||
|               label: 'OTP', | ||||
|               tooltipContent: 'Enter your OTP' | ||||
|             }, | ||||
|             onItemClick: function () { | ||||
|               const modal = bootstrap.Modal.getOrCreateInstance(control_otpForm); | ||||
|               modal.show(); | ||||
|             } | ||||
|           }; | ||||
|         }, | ||||
| @ -180,56 +195,16 @@ | ||||
|   } | ||||
| 
 | ||||
|   function approveTransaction(e) { | ||||
|     jfa.communication.videocall.stopCall(true); | ||||
|     let url = jfa.utilities.routing.getCurrentURLWithHandler("Approve"); | ||||
|     jfa.utilities.request.post(url, {}) | ||||
|       .then(resp => { | ||||
|         if (resp.ok === true) { | ||||
|           debugger; | ||||
|           jfa.communication.videocall.stopCall(true); | ||||
|         } | ||||
|       }) | ||||
|       .catch(err => console.error(err)); | ||||
|   } | ||||
| 
 | ||||
|   //function _updateGrid() {
 | ||||
|   //  control_videoGrid.innerHTML = '';
 | ||||
| 
 | ||||
|   //  participants.forEach((participant, index) => {
 | ||||
|   //    const col = document.createElement('div');
 | ||||
|   //    col.className = 'participant-col';
 | ||||
|   //    const tmpl = control_templateVideo.cloneNode(true).content;
 | ||||
|   //    const vidcontainer = tmpl.querySelector(".video-container")
 | ||||
|   //    if (vidcontainer) {
 | ||||
|   //      vidcontainer.id = participant.Id;
 | ||||
|   //      vidcontainer.classList.add(participant.Id);
 | ||||
|   //      vidcontainer.classList.add(participant.Type == 'Notary' ? 'local-video-container' : 'remote-video-container');
 | ||||
|   //    }
 | ||||
|   //    let participantName = tmpl.querySelector(".participant-name")
 | ||||
|   //    if (participantName) {
 | ||||
|   //      participantName.textContent = participant.DisplayName;
 | ||||
|   //    }
 | ||||
|   //    col.appendChild(tmpl);
 | ||||
|   //    control_videoGrid.appendChild(col);
 | ||||
|   //  });
 | ||||
| 
 | ||||
|   //  // Dynamically adjust grid columns based on participant count
 | ||||
|   //  const count = participants.length;
 | ||||
|   //  const cols = count <= 2 ? 'col-12 col-sm-12 offset-md-1 col-md-10 offset-lg-0 col-lg-6' :
 | ||||
|   //    count <= 4 ? 'col-6 col-sm-6 col-md-6 col-lg-6 col-xl-6' :
 | ||||
|   //      count <= 8 ? 'col-6 col-md-6 col-lg-4 col-xl-4' :
 | ||||
|   //        count <= 9 ? 'col-4' :
 | ||||
|   //          'col-6 col-sm-4 col-lg-3';
 | ||||
| 
 | ||||
|   //  document.querySelectorAll('.participant-col').forEach(el => {
 | ||||
|   //    el.className = `participant-col ${cols}`;
 | ||||
|   //  });
 | ||||
| 
 | ||||
|   //  const fluid = count <= 2 ? 'container-fluid' :
 | ||||
|   //    count <= 8 ? 'container-xxl' :
 | ||||
|   //      'container-xl'
 | ||||
|   //  control_videoGridContainer.className = `${fluid} py-3`;
 | ||||
|   //}
 | ||||
| 
 | ||||
|   function _createParticipantListItems() { | ||||
|     control_participantListGroup.innerHTML = ''; | ||||
|     participants.forEach(participant => { | ||||
| @ -250,7 +225,6 @@ | ||||
|     control_participantListGroup.addEventListener('click', function (event) { | ||||
|       let target = event.target?.closest('.list-group-item'); | ||||
|       if (target) { | ||||
| 
 | ||||
|         const participant = participants.find(p => p.UID == target.dataset.participantUid); | ||||
|         if (!participant) { | ||||
|           return; | ||||
| @ -260,7 +234,6 @@ | ||||
|         let selfieUrl = jfa.utilities.routing.getCurrentURLWithHandler("SelfieImage"); | ||||
|         selfieUrl.searchParams.append("meetingRoomUserID", participant.RoomUserID); | ||||
| 
 | ||||
| 
 | ||||
|         let identificationUrl = jfa.utilities.routing.getCurrentURLWithHandler("IdentificationDocument"); | ||||
|         identificationUrl.searchParams.append("meetingRoomUserID", participant.RoomUserID); | ||||
|         control_selfieImage.src = selfieUrl; | ||||
| @ -304,9 +277,7 @@ | ||||
| 
 | ||||
|   async function _init() { | ||||
|     _bindEvents(); | ||||
|     //_updateGrid();
 | ||||
|     await _initVideoCall(); | ||||
|     debugger; | ||||
|     _createParticipantListItems(); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -32,7 +32,6 @@ namespace EnotaryoPH.Web.Pages.Principal.TransactionStatus | ||||
| 
 | ||||
|             Title = transaction.TransactionDocument.DocumentType; | ||||
|             StartedOn = transaction.CreatedOn.GetValueOrDefault(); | ||||
|             StatusDescription = "Not all signatories have signed up to enotaryo and completed the onboarding process. This transaction cannot proceed until this has been resolved."; | ||||
| 
 | ||||
|             Signatories = transaction.TransactionSignatories.ConvertAll(s => new SignatoryViewModel | ||||
|             { | ||||
| @ -41,6 +40,10 @@ namespace EnotaryoPH.Web.Pages.Principal.TransactionStatus | ||||
|                 Status = s.Status | ||||
|             }); | ||||
| 
 | ||||
|             StatusDescription = Signatories.Count > 1 | ||||
|                 ? "Not all signatories have signed up to enotaryo and completed the onboarding process. This transaction cannot proceed until this has been resolved." | ||||
|                 : "Please wait while our Notary Public team reviews your document."; | ||||
| 
 | ||||
|             return Page(); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -64,6 +64,7 @@ namespace EnotaryoPH.Web | ||||
|             builder.Services.AddScheduler(); | ||||
|             builder.Services.AddMailer(config); | ||||
|             builder.Services.AddTransient<SignatoryInvitationInvocable>(); | ||||
|             builder.Services.AddTransient<OneTimePasswordInvocable>(); | ||||
|             builder.Services.AddTransient<CheckRecordingAvailabilityInvocable>(); | ||||
|             builder.Services.AddTransient<IVideoConferenceService, VideoConferenceService>(); | ||||
|             builder.Services.AddTransient<IEventService, EventService>(); | ||||
|  | ||||
							
								
								
									
										25
									
								
								EnotaryoPH/EnotaryoPH.Web/Views/Mail/OneTimePassword.cshtml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								EnotaryoPH/EnotaryoPH.Web/Views/Mail/OneTimePassword.cshtml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| @using EnotaryoPH.Web.Mailables | ||||
| @model OneTimePasswordViewModel | ||||
| @{ | ||||
|     ViewBag.Heading = "One Time Password"; | ||||
|     ViewBag.Preview = "Your OTP"; | ||||
| } | ||||
| 
 | ||||
| <p> | ||||
|     Hello @Model.ParticipantName, | ||||
| 
 | ||||
|     This is your one time password: | ||||
| 
 | ||||
|     <blockquote> | ||||
|     @Model.OTP | ||||
|     </blockquote> | ||||
| 
 | ||||
|     Please do not share this code with anyone other than the Notary Public, Atty. @Model.LawyerName during the Video Conference. | ||||
| 
 | ||||
|     You can join the e-Notaryo video conference by clicking this button: @await Component.InvokeAsync("EmailLinkButton", new { text = "Video Conference", url = Model.MeetingRoomURL }) | ||||
| </p> | ||||
| 
 | ||||
| @section links | ||||
| { | ||||
|     <a href="@Model.MeetingRoomURL">Meeting Room Page</a> | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user