Contacts page.
@@ -117,6 +117,21 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="contact.caption.contact" xml:space="preserve">
|
||||
<value>Contact</value>
|
||||
</data>
|
||||
<data name="contact.caption.contact.create" xml:space="preserve">
|
||||
<value>Add Contact</value>
|
||||
</data>
|
||||
<data name="contact.caption.contact.update" xml:space="preserve">
|
||||
<value>Update Contact</value>
|
||||
</data>
|
||||
<data name="contact.caption.contacts" xml:space="preserve">
|
||||
<value>Contacts</value>
|
||||
</data>
|
||||
<data name="copyright.symbol.year.html" xml:space="preserve">
|
||||
<value>&copy; {0}</value>
|
||||
</data>
|
||||
<data name="dialog.title.alert" xml:space="preserve">
|
||||
<value>Alert</value>
|
||||
</data>
|
||||
@@ -126,12 +141,21 @@
|
||||
<data name="dialog.title.progress" xml:space="preserve">
|
||||
<value>Progress</value>
|
||||
</data>
|
||||
<data name="label.any" xml:space="preserve">
|
||||
<value>Any</value>
|
||||
</data>
|
||||
<data name="label.associations" xml:space="preserve">
|
||||
<value>Associations</value>
|
||||
</data>
|
||||
<data name="label.calendar" xml:space="preserve">
|
||||
<value>Calendar</value>
|
||||
</data>
|
||||
<data name="label.cancel" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="label.category" xml:space="preserve">
|
||||
<value>Category</value>
|
||||
</data>
|
||||
<data name="label.change.password" xml:space="preserve">
|
||||
<value>Change Password</value>
|
||||
</data>
|
||||
@@ -153,9 +177,15 @@
|
||||
<data name="label.contacts" xml:space="preserve">
|
||||
<value>Contacts</value>
|
||||
</data>
|
||||
<data name="label.create" xml:space="preserve">
|
||||
<value>Create</value>
|
||||
</data>
|
||||
<data name="label.email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
<data name="label.first" xml:space="preserve">
|
||||
<value>First</value>
|
||||
</data>
|
||||
<data name="label.forgotten.password" xml:space="preserve">
|
||||
<value>Forgotten password?</value>
|
||||
</data>
|
||||
@@ -165,24 +195,48 @@
|
||||
<data name="label.keep.signed.in" xml:space="preserve">
|
||||
<value>Remember Me</value>
|
||||
</data>
|
||||
<data name="label.last" xml:space="preserve">
|
||||
<value>Last</value>
|
||||
</data>
|
||||
<data name="label.name" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
</data>
|
||||
<data name="label.next" xml:space="preserve">
|
||||
<value>Next</value>
|
||||
</data>
|
||||
<data name="label.no" xml:space="preserve">
|
||||
<value>No</value>
|
||||
</data>
|
||||
<data name="label.notes" xml:space="preserve">
|
||||
<value>Notes</value>
|
||||
</data>
|
||||
<data name="label.ok" xml:space="preserve">
|
||||
<value>OK</value>
|
||||
</data>
|
||||
<data name="label.page.info" xml:space="preserve">
|
||||
<value>Page {0} of {1}</value>
|
||||
</data>
|
||||
<data name="label.password" xml:space="preserve">
|
||||
<value>Password</value>
|
||||
</data>
|
||||
<data name="label.previous" xml:space="preserve">
|
||||
<value>Previous</value>
|
||||
</data>
|
||||
<data name="label.reference" xml:space="preserve">
|
||||
<value>Reference</value>
|
||||
</data>
|
||||
<data name="label.refresh" xml:space="preserve">
|
||||
<value>Refresh</value>
|
||||
</data>
|
||||
<data name="label.search" xml:space="preserve">
|
||||
<value>Search</value>
|
||||
</data>
|
||||
<data name="label.security" xml:space="preserve">
|
||||
<value>Security</value>
|
||||
</data>
|
||||
<data name="label.select" xml:space="preserve">
|
||||
<value>Select</value>
|
||||
</data>
|
||||
<data name="label.selected.count" xml:space="preserve">
|
||||
<value>{0} selected</value>
|
||||
</data>
|
||||
@@ -195,6 +249,9 @@
|
||||
<data name="label.signout" xml:space="preserve">
|
||||
<value>Sign-out</value>
|
||||
</data>
|
||||
<data name="label.type" xml:space="preserve">
|
||||
<value>Type</value>
|
||||
</data>
|
||||
<data name="label.yes" xml:space="preserve">
|
||||
<value>Yes</value>
|
||||
</data>
|
||||
@@ -264,6 +321,9 @@
|
||||
<data name="msg.validation.maximum.length" xml:space="preserve">
|
||||
<value>Value must not exceed {0} characters</value>
|
||||
</data>
|
||||
<data name="security.caption.change.password" xml:space="preserve">
|
||||
<value>Change Password</value>
|
||||
</data>
|
||||
<data name="security.caption.request.password" xml:space="preserve">
|
||||
<value>Request Password</value>
|
||||
</data>
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.App;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.Helpers;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.Portal.Models;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.App;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Bus;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Consoles;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Helpers;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Mgr;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Bus;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Dbas;
|
||||
using Sarand.Net.Standard.Base.Email;
|
||||
using Sarand.Net.Standard.Base.Helpers;
|
||||
using Sarand.Net.Standard.Base.Security;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
{
|
||||
//=======================================================================
|
||||
// Portal Contact Controller - Ajax actions.
|
||||
//=======================================================================
|
||||
partial class ContactController
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Affiliate data.
|
||||
private class AffiliateData_List : AjaxResponse_Data.ItemData
|
||||
{
|
||||
public String DisplayName { get; set; }
|
||||
public String TypeCode { get; set; }
|
||||
public String TypeDesc { get; set; }
|
||||
// Address.
|
||||
public String AddressSummary { get; set; }
|
||||
}
|
||||
private class AffiliateData_Full : AffiliateData_List
|
||||
{
|
||||
// Affiliate properties.
|
||||
public String PhoneMain { get; set; } = "";
|
||||
public String PhoneAlt { get; set; } = "";
|
||||
public String PhoneFax { get; set; } = "";
|
||||
public String Email { get; set; } = "";
|
||||
public String Website { get; set; } = "";
|
||||
// Ogranisation.
|
||||
public SysId AffiliateIdParent { get; set; } = new SysId();
|
||||
public String Name { get; set; } = "";
|
||||
public String Abbr { get; set; } = "";
|
||||
public String Nature { get; set; } = "";
|
||||
public String Contact { get; set; } = "";
|
||||
public String CompanyNo { get; set; } = "";
|
||||
public String VatReg { get; set; } = "";
|
||||
public String PhoneDirect { get; set; } = "";
|
||||
// Individual.
|
||||
public String Title { get; set; } = "";
|
||||
public String Initials { get; set; } = "";
|
||||
public String LastName { get; set; } = "";
|
||||
public String FirstName { get; set; } = "";
|
||||
public String Salutation { get; set; } = "";
|
||||
public String PhoneMobile { get; set; } = "";
|
||||
public byte BirthDay { get; set; } = 0;
|
||||
public byte BirthMonth { get; set; } = 0;
|
||||
public short BirthYear { get; set; } = 0;
|
||||
public String MaritalStatus { get; set; } = "";
|
||||
public String Spouse { get; set; } = "";
|
||||
public String Children { get; set; } = "";
|
||||
public String Hometown { get; set; } = "";
|
||||
public SysId UserId { get; set; } = new SysId();
|
||||
}
|
||||
private void PopulateAffiliateResponseData(SbmAffiliate affiliate, AffiliateData_List data)
|
||||
{
|
||||
data.Id = SysId.ToString(affiliate.Id);
|
||||
data.DisplayName = affiliate.GetDisplayName();
|
||||
data.TypeCode = Internals.AffiliateTypeCodes.GetCodeForType(affiliate.AffiliateType);
|
||||
data.TypeDesc = Internals.AffiliateTypeCodes.GetDescForType(affiliate.AffiliateType);
|
||||
// Common properties.
|
||||
data.Notes = affiliate.Notes;
|
||||
data.StatusCode = affiliate.StatusCode;
|
||||
data.StatusDesc = affiliate.StatusDesc;
|
||||
}
|
||||
private void PopulateAffiliateResponseData(SbmAffiliate affiliate, AffiliateData_Full data)
|
||||
{
|
||||
PopulateAffiliateResponseData(affiliate, data as AffiliateData_List);
|
||||
// Affiliate properties.
|
||||
data.PhoneMain = affiliate.PhoneMain;
|
||||
data.PhoneAlt = affiliate.PhoneAlt;
|
||||
data.PhoneFax = affiliate.PhoneFax;
|
||||
data.Email = affiliate.Email;
|
||||
data.Website = affiliate.Website;
|
||||
// Ogranisation.
|
||||
data.AffiliateIdParent = affiliate.AffiliateIdParent;
|
||||
data.Name = affiliate.Name;
|
||||
data.Abbr = affiliate.Abbr;
|
||||
data.Nature = affiliate.Nature;
|
||||
data.Contact = affiliate.Contact;
|
||||
data.CompanyNo = affiliate.CompanyNo;
|
||||
data.VatReg = affiliate.VatReg;
|
||||
data.PhoneDirect = affiliate.PhoneDirect;
|
||||
// Individual.
|
||||
data.Title = affiliate.Title;
|
||||
data.Initials = affiliate.Initials;
|
||||
data.LastName = affiliate.LastName;
|
||||
data.FirstName = affiliate.FirstName;
|
||||
data.Salutation = affiliate.Salutation;
|
||||
data.PhoneMobile = affiliate.PhoneMobile;
|
||||
data.BirthDay = affiliate.BirthDay;
|
||||
data.BirthMonth = affiliate.BirthMonth;
|
||||
data.BirthYear = affiliate.BirthYear;
|
||||
data.MaritalStatus = affiliate.MaritalStatus;
|
||||
data.Spouse = affiliate.Spouse;
|
||||
data.Children = affiliate.Children;
|
||||
data.Hometown = affiliate.Hometown;
|
||||
data.UserId = affiliate.UserId;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Contact list.
|
||||
//-------------------------------------------------------------------
|
||||
private class AjaxResponse_ContactList : AjaxResponse_Data
|
||||
{
|
||||
// Construction.
|
||||
public AjaxResponse_ContactList(SbmModel_Portal model) : base(model) { }
|
||||
// Affiliate list.
|
||||
public List<AffiliateData_List> Affiliates { get; } = new List<AffiliateData_List>();
|
||||
// Reference data.
|
||||
public List<CodeDesc> Statuses { get; } = new List<CodeDesc>();
|
||||
public String DefaultStatusCode { get; set; }
|
||||
}
|
||||
// Ajax request.
|
||||
[HttpPost]
|
||||
[Route("Contact/Ajax/List")]
|
||||
[Route("Contact/Ajax/GetList")]
|
||||
public IActionResult Ajax_ContactList(Model_Contact model)
|
||||
{
|
||||
String fn = GetType().Name + ".Ajax_ContactList";
|
||||
AjaxResponse_ContactList responseData = new AjaxResponse_ContactList(model);
|
||||
String messageTitle = Localiser["contact.caption.contacts"];
|
||||
|
||||
// Validate session and referrer.
|
||||
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
|
||||
if (!ValidReferer) return Forbid();
|
||||
RefreshModel(model);
|
||||
|
||||
// Reference data - statuses.
|
||||
foreach (SaStatus status in SbmAffiliate.ValidStatuses.FindAll(item => item != SaStatus.Deleted))
|
||||
responseData.Statuses.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
|
||||
responseData.DefaultStatusCode = SacsSystem.GetStatusCode(SbmAffiliate.DefaultStatus);
|
||||
|
||||
// Item filter.
|
||||
SbmAffiliate.Params prmAffiliate = new SbmAffiliate.Params();
|
||||
prmAffiliate.Search = model.Filter.AffiliateSearch;
|
||||
prmAffiliate.AffiliateType = model.GetFilterAffiliateType();
|
||||
prmAffiliate.StatusCat = SbmAffiliate.StatusCat.NotDeleted;
|
||||
prmAffiliate.SortColumns.Add(AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.Name));
|
||||
prmAffiliate.SortColumns.Add(AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.LastName));
|
||||
model.Paging.ItemTotal = (ulong)AccountMgr.GetAffiliateCount(prmAffiliate);
|
||||
model.PopulatePagingTo(prmAffiliate.ResultsPaging);
|
||||
|
||||
// Item columns.
|
||||
ColumnDfn colId = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.Id);
|
||||
ColumnDfn colName = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.Name);
|
||||
ColumnDfn colAffiliateType = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.Type);
|
||||
ColumnDfn colPhoneMain = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.PhoneMain);
|
||||
ColumnDfn colPhoneAlt = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.PhoneAlt);
|
||||
ColumnDfn colNotes = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.Notes);
|
||||
ColumnDfn colStatus = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.Status);
|
||||
ColumnDfn colCreatedOn = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.CreateOn_Date);
|
||||
ColumnDfn colUpdatedOn = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.UpdateOn_Date);
|
||||
List<ColumnDfn> columns = new List<ColumnDfn>()
|
||||
{
|
||||
colId,
|
||||
colName,
|
||||
colAffiliateType,
|
||||
colPhoneMain,
|
||||
colPhoneAlt,
|
||||
colNotes, colStatus, colCreatedOn, colUpdatedOn
|
||||
};
|
||||
|
||||
// Query items.
|
||||
using (DbResultSet resultSet = AccountMgr.ExecuteAffiliateQuery(columns, prmAffiliate))
|
||||
{
|
||||
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
|
||||
{
|
||||
NamedValue.Map values = resultSet.GetRecordValues();
|
||||
Sbm.AffiliateType affiliateType = (Sbm.AffiliateType)values.GetLong(colAffiliateType.DataName);
|
||||
AffiliateData_List affiliateData = new AffiliateData_List()
|
||||
{
|
||||
Id = values.GetString(colId.DataName),
|
||||
DisplayName = values.GetString(colName.DataName),
|
||||
TypeCode = Internals.AffiliateTypeCodes.GetCodeForType(affiliateType),
|
||||
TypeDesc = Internals.AffiliateTypeCodes.GetDescForType(affiliateType),
|
||||
Notes = values.GetString(colNotes.DataName),
|
||||
StatusCode = values.GetString(colStatus.DataName),
|
||||
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
|
||||
};
|
||||
responseData.Affiliates.Add(affiliateData);
|
||||
}
|
||||
}
|
||||
if (model.Paging.IsPaged)
|
||||
model.Paging.ItemCount = (ushort)responseData.Affiliates.Count;
|
||||
|
||||
// Complete response.
|
||||
responseData.SetPaging(model);
|
||||
responseData.Success = true;
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Get Affiliate ajax request.
|
||||
//-------------------------------------------------------------------
|
||||
private class AjaxResponse_Affiliate : AjaxResponse_Data
|
||||
{
|
||||
// Construction.
|
||||
public AjaxResponse_Affiliate(SbmModel_Portal model) : base(model) { }
|
||||
// Affiliate details.
|
||||
public AffiliateData_Full affiliate { get; } = new AffiliateData_Full();
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Contact/Ajax/Item")]
|
||||
[Route("Contact/Ajax/GetItem")]
|
||||
public IActionResult Ajax_GetItem(Model_Contact model)
|
||||
{
|
||||
String fn = GetType().Name + ".Ajax_GetItem";
|
||||
String messageTitle = Localiser["affiliate.caption.affiliate"];
|
||||
AjaxResponse_Affiliate responseData = new AjaxResponse_Affiliate(model);
|
||||
|
||||
// Validate session.
|
||||
if (!ValidUserSession)
|
||||
return GetAjaxResult_InvalidSession(responseData);
|
||||
|
||||
// Identify item.
|
||||
SysId affiliateId = model.AffiliateId.ToSysId();
|
||||
if (!SysId.IsValid(affiliateId))
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
|
||||
|
||||
// Retrieve item.
|
||||
SbmAffiliate affiliate = AccountMgr.GetAffiliateById(affiliateId);
|
||||
if (affiliate == null)
|
||||
{
|
||||
SaLogger.Warn(fn + " Failed to retrieve affiliate for Id: '" + model.AffiliateId + "'");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
|
||||
}
|
||||
|
||||
// Construct response.
|
||||
PopulateAffiliateResponseData(affiliate, responseData.affiliate);
|
||||
|
||||
// Complete response.
|
||||
responseData.Success = true;
|
||||
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Store Affiliate ajax request.
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Contact/Ajax/Store")]
|
||||
[Route("Contact/Ajax/StoreItem")]
|
||||
public IActionResult Ajax_StoreItem(Model_Contact model)
|
||||
{
|
||||
String fn = GetType().Name + ".Ajax_StoreItem";
|
||||
String messageTitle = Localiser["affiliate.caption.affiliate"];
|
||||
AjaxResponse_Affiliate responseData = new AjaxResponse_Affiliate(model);
|
||||
|
||||
// Validate session.
|
||||
if (!ValidUserSession)
|
||||
return GetAjaxResult_InvalidSession(responseData);
|
||||
|
||||
// Validate item properties.
|
||||
model.ValidateAffiliateData(responseData);
|
||||
if (responseData.HasControlErrors())
|
||||
return Json(responseData);
|
||||
|
||||
// Retrieve existing item.
|
||||
SysId affiliateId = model.AffiliateId.ToSysId();
|
||||
SbmAffiliate affiliate = AccountMgr.GetAffiliateById(affiliateId);
|
||||
if (affiliate == null && SysId.IsValid(affiliateId))
|
||||
{
|
||||
SaLogger.Warn(fn + " Failed to retrieve affiliate for Id: '" + model.AffiliateId + "'");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
|
||||
}
|
||||
|
||||
// Create new item if required.
|
||||
if (affiliate == null)
|
||||
affiliate = AccountMgr.CreateAffiliate(model.GetAffiliateType());
|
||||
|
||||
// Populate item properties.
|
||||
if (model.AffiliateName != null)
|
||||
affiliate.Name = model.AffiliateName;
|
||||
// Common properties.
|
||||
if (model.AffiliateNotes != null)
|
||||
affiliate.Notes = model.AffiliateNotes;
|
||||
if (model.AffiliateStatus != null)
|
||||
affiliate.Status = model.GetAffiliateStatus();
|
||||
|
||||
// Store item.
|
||||
if (!SysId.IsValid(affiliate.Id))
|
||||
{
|
||||
if (!AccountMgr.InsertAffiliate(affiliate, DbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn(fn + " Failed to insert affiliate: " + AccountMgr.ErrorStateMessage);
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + AccountMgr.ErrorStateMessage));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!AccountMgr.UpdateAffiliate(affiliate, DbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn(fn + " Failed to update affiliate [Id:" + SysId.ToString(affiliate.Id) + "]: " + AccountMgr.ErrorStateMessage);
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + AccountMgr.ErrorStateMessage));
|
||||
}
|
||||
}
|
||||
|
||||
// Construct response.
|
||||
PopulateAffiliateResponseData(affiliate, responseData.affiliate);
|
||||
|
||||
// Complete response.
|
||||
responseData.Success = true;
|
||||
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.App;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.Helpers;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.Portal.Models;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.App;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Bus;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Mgr;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Bus;
|
||||
using Sarand.Net.Standard.Base.Security;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
{
|
||||
//=======================================================================
|
||||
public partial class ContactController : SbmController_Portal
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public ContactController(SbmLocaliser localiser, ILogger<ContactController> logger) :
|
||||
base(localiser, logger)
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
[HttpGet]
|
||||
public IActionResult Index()
|
||||
{
|
||||
String fn = GetType().Name + ".Index";
|
||||
|
||||
if (!ValidUserSession)
|
||||
return RedirectToAction("", "");
|
||||
|
||||
// Contact page.
|
||||
Model_Contact model = new Model_Contact(Localiser);
|
||||
model.Paging.PageNo = SessionData.Contacts.Filter.PageNo;
|
||||
model.Paging.PageLength = SessionData.Contacts.Filter.PageLength;
|
||||
return View(model);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorViewModel(Localiser) { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -41,62 +41,54 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
// NB Only allowed if there isn't a current user session.
|
||||
if (!ValidUserSession && AppConfig.Security.SignIn.KeepLoggedIn.Allowed)
|
||||
{
|
||||
// Extract parameters and validate that they are populated.
|
||||
String encryptedParams = GetRequestCookie(CookieName_KeepLoggedIn);
|
||||
KeepLoggedInParams keepLoggedInParams = ExtractKeepLoggedInParams(encryptedParams);
|
||||
String logPrefix = fn + " 'Keep Logged In' cookie found but ";
|
||||
String logSuffix = keepLoggedInParams.GetValidParams().Concat("", " ", ".");
|
||||
|
||||
if (SysId.IsValid(keepLoggedInParams.SecurityItemId) && SysId.IsValid(keepLoggedInParams.AffiliateId))
|
||||
if (keepLoggedInParams.IsValid())
|
||||
{
|
||||
// User may be defined on any one of the BusinessMgr databases - need to find out which.
|
||||
// Identify session contexts with a BusMgr database that hosts the specified email address.
|
||||
List<ushort> sessionContextIdxs = SbmSessionContextHelper_Web.GetSessionContextIdxsForUsersBusMgrDatabase(keepLoggedInParams.EmailAddress);
|
||||
foreach (ushort sessionContextIdx in sessionContextIdxs)
|
||||
{
|
||||
// Consoles and Managers must be associated with the specific context.
|
||||
SbmSessionContext sessionContext = SbmSessionContextHelper_Web.GetSessionContext(sessionContextIdx);
|
||||
SbmUserContext_Web userContext = new SbmUserContext_Web(sessionContext);
|
||||
|
||||
using (SaSecurityConsole securityConsole = App.CreateSecurityConsole(userContext))
|
||||
using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContext))
|
||||
using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContext))
|
||||
{
|
||||
SaSecurityItem securityItemUser = securityConsole.GetUserByEmail(keepLoggedInParams.EmailAddress);
|
||||
if (securityItemUser != null)
|
||||
using (SaSecurityItem securityItemUser = securityConsoleForUser.GetUserByEmail(keepLoggedInParams.EmailAddress))
|
||||
using (SbmAffiliate affiliateUser = accountMgrForUser.GetAffiliateById(keepLoggedInParams.AffiliateId))
|
||||
{
|
||||
if (securityItemUser.Id.Equals(keepLoggedInParams.SecurityItemId))
|
||||
if (securityItemUser != null && affiliateUser != null)
|
||||
{
|
||||
using (SbmAccountMgr accountMgr = App.System.CreateAccountMgr(userContext))
|
||||
if (securityItemUser.Id.Equals(keepLoggedInParams.SecurityItemId) && affiliateUser.UserId.Equals(securityItemUser.Id))
|
||||
{
|
||||
SbmAffiliate affiliateUser = accountMgr.GetAffiliateById(keepLoggedInParams.AffiliateId);
|
||||
if (affiliateUser != null)
|
||||
if (affiliateUser.IsStatusCurrent)
|
||||
{
|
||||
if (affiliateUser.Id.Equals(keepLoggedInParams.AffiliateId))
|
||||
String errorMessage = "";
|
||||
if (SignUserIn(userContext, securityItemUser, affiliateUser, true, ref errorMessage))
|
||||
{
|
||||
if (affiliateUser.UserId.Equals(securityItemUser.Id))
|
||||
{
|
||||
if (affiliateUser.IsStatusActive)
|
||||
{
|
||||
String errorMessage = "";
|
||||
if (SignUserIn(sessionContext, securityItemUser, affiliateUser, true, ref errorMessage))
|
||||
SaLogger.Info(fn + " Keep Logged In cookie found." + logSuffix);
|
||||
else SaLogger.Warn(fn + " Keep Logged In cookie found but failed to sign user in: " + errorMessage + "." + logSuffix);
|
||||
}
|
||||
else SaLogger.Warn(fn + " Keep Logged In cookie found but Affiliate is not active." + logSuffix);
|
||||
}
|
||||
else SaLogger.Warn(fn + " Keep Logged In cookie found but Affiliate User Id does not match security item." + logSuffix);
|
||||
SaLogger.Info(logPrefix + " 'Keep Logged In' cookie found." + logSuffix);
|
||||
break;
|
||||
}
|
||||
else SaLogger.Warn(fn + " Keep Logged In cookie found but Affiliate does nto match Id." + logSuffix);
|
||||
else SaLogger.Warn(logPrefix + "failed to sign user in: " + errorMessage + "." + logSuffix);
|
||||
}
|
||||
else SaLogger.Warn(fn + " Keep Logged In cookie found but Affiliate not found." + logSuffix);
|
||||
else SaLogger.Warn(logPrefix + "Affiliate is not active." + logSuffix);
|
||||
}
|
||||
else SaLogger.Warn(logPrefix + "Security Item and Affiliate are inconsistent. " + logSuffix);
|
||||
}
|
||||
else SaLogger.Warn(fn + " Keep Logged In cookie found but Security Item does not match Id." + logSuffix);
|
||||
else SaLogger.Warn(logPrefix + "Security Item or Affiliate not found. " + logSuffix);
|
||||
}
|
||||
else SaLogger.Warn(fn + " Keep Logged In cookie found but Security Item not found by email." + logSuffix);
|
||||
}
|
||||
|
||||
if (ValidUserSession)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else SaLogger.Warn(fn + " Keep Logged In cookie found but Security Item or Affiliate Ids missing." + logSuffix);
|
||||
else if (!StringUtil.IsEmpty(encryptedParams))
|
||||
SaLogger.Warn(fn + " Keep Logged In cookie found but with incomplete details." + logSuffix);
|
||||
}
|
||||
|
||||
// Redirect to sign-in if not signed-in.
|
||||
|
||||
@@ -4,6 +4,7 @@ using Sarand.BusinessMgr.Net.Core.Web.Controllers;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.Portal.App;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.App;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Bus;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Mgr;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Bus;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
@@ -27,7 +28,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Protected construction.
|
||||
protected SbmController_Portal(SbmLocaliser localiser, Microsoft.Extensions.Logging.ILogger<HomeController> logger) : base(localiser)
|
||||
protected SbmController_Portal(SbmLocaliser localiser, Microsoft.Extensions.Logging.ILogger<SbmController_Portal> logger) : base(localiser)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -50,42 +51,60 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
/// <para>- Open user session - store details against Sesion Data.</para>
|
||||
/// <para>- Set 'Keep logged in' cookie if required.</para>
|
||||
/// </summary>
|
||||
protected bool SignUserIn(SbmSessionContext sessionContext, SaSecurityItem securityItemUser, SbmAffiliate affiliateUser, bool keepLoggedIn, ref String errorMessage)
|
||||
protected bool SignUserIn(SbmUserContext userContext, SaSecurityItem securityItemUser, SbmAffiliate affiliateUser, bool keepLoggedIn, ref String errorMessage)
|
||||
{
|
||||
String fn = GetType().Name + ".SignUserIn";
|
||||
|
||||
// Validate User Account, just in case - should have been done before arriving here!
|
||||
if (affiliateUser == null)
|
||||
// Validate Security Item and Affiliate, just in case - should have been done before arriving here!
|
||||
if (securityItemUser == null)
|
||||
{
|
||||
SaLogger.Error(fn + " Unable to sign account in - null user account.");
|
||||
errorMessage = Localiser["security.msg.signin.failed"];
|
||||
SaLogger.Error(fn + " Unable to sign account in - null security item.");
|
||||
errorMessage = Localiser["security.msg.signin.invalid"];
|
||||
return false;
|
||||
}
|
||||
if (!affiliateUser.IsStatusActive)
|
||||
if (affiliateUser == null)
|
||||
{
|
||||
SaLogger.Error(fn + " Unable to sign account in - user account is not active. Email: " + affiliateUser.Email);
|
||||
errorMessage = Localiser["security.msg.signin.failed"];
|
||||
SaLogger.Error(fn + " Unable to sign account in - null affiliate.");
|
||||
errorMessage = Localiser["security.msg.signin.invalid"];
|
||||
return false;
|
||||
}
|
||||
if (!affiliateUser.IsStatusCurrent)
|
||||
{
|
||||
SaLogger.Error(fn + " Unable to sign account in - affiliate is not active. Email: " + securityItemUser.EmailAddress);
|
||||
errorMessage = Localiser["security.msg.signin.invalid"];
|
||||
return false;
|
||||
}
|
||||
|
||||
// 'Keep logged in' can be switched off by the application configuration.
|
||||
keepLoggedIn = App.AppConfig.Security.SignIn.KeepLoggedIn.Allowed && keepLoggedIn;
|
||||
|
||||
// Log user in.
|
||||
affiliateUser.KeepLoggedIn_Portal = keepLoggedIn;
|
||||
//affiliateUser.LastLoginAt = DateTime.Now;
|
||||
//affiliateUser.InvalidLoginCount = 0;
|
||||
//affiliateUser.LastInvalidLoginAt = DateTime.MinValue;
|
||||
if (!AccountMgr.UpdateAffiliate(affiliateUser, DbUpdateInfo))
|
||||
using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContext))
|
||||
using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContext))
|
||||
{
|
||||
SaLogger.Warn(fn + " Failed to update account for " + affiliateUser.Email + ": " + AccountMgr.ErrorStateMessage);
|
||||
errorMessage = Localiser["security.msg.signin.failed"];
|
||||
return false;
|
||||
// Log user in.
|
||||
securityItemUser.LastLogin = DateTime.Now;
|
||||
securityItemUser.InvalidLoginCount = 0;
|
||||
securityItemUser.LastInvalidLoginAt = DateTime.MinValue;
|
||||
if (!securityConsoleForUser.UpdateSecurityItem(securityItemUser, DbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn(fn + " Failed to update security item for " + securityItemUser.EmailAddress + ": " + securityConsoleForUser.ErrorStateMessage);
|
||||
errorMessage = Localiser["security.msg.signin.invalid"];
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update affiliate.
|
||||
affiliateUser.KeepLoggedIn_Portal = keepLoggedIn;
|
||||
if (!accountMgrForUser.UpdateAffiliate(affiliateUser, DbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn(fn + " Failed to update affiliate for " + securityItemUser.EmailAddress + ": " + accountMgrForUser.ErrorStateMessage);
|
||||
errorMessage = Localiser["security.msg.signin.invalid"];
|
||||
return false;
|
||||
}
|
||||
SaLogger.Info(fn + " User session opened for " + securityItemUser.EmailAddress, true);
|
||||
}
|
||||
SaLogger.Info(fn + " User session opened for " + affiliateUser.Email, true);
|
||||
|
||||
// Create session.
|
||||
OpenUserSession(sessionContext, securityItemUser, affiliateUser);
|
||||
OpenUserSession(userContext.SessionContext, securityItemUser, affiliateUser);
|
||||
|
||||
// Send the 'Keep Logged In' cookie if requested.
|
||||
if (affiliateUser.KeepLoggedIn_Portal)
|
||||
@@ -116,7 +135,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
{
|
||||
// Validate user properties.
|
||||
if (!SysId.IsValid(affiliateUser.Id)) return false;
|
||||
if (StringUtil.IsEmpty(affiliateUser.Email)) return false;
|
||||
if (StringUtil.IsEmpty(securityItemUser.EmailAddress)) return false;
|
||||
|
||||
// Close previous session.
|
||||
CloseUserSession();
|
||||
@@ -186,10 +205,22 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
public List<String> GetValidParams()
|
||||
{
|
||||
List<String> values = new List<String>();
|
||||
if (!StringUtil.IsEmpty(EmailAddress))
|
||||
values.Add("EmailAddress: " + EmailAddress);
|
||||
if (SysId.IsValid(SecurityItemId))
|
||||
values.Add("SecurityItemId: " + SysId.ToString(SecurityItemId));
|
||||
if (SysId.IsValid(AffiliateId))
|
||||
values.Add("UserAccountId: " + SysId.ToString(AffiliateId));
|
||||
return values;
|
||||
}
|
||||
/// <summary>Verifiy that the email and account Ids are populated and the data hasn't expired.</summary>
|
||||
public bool IsValid()
|
||||
{
|
||||
return
|
||||
!StringUtil.IsEmpty(EmailAddress) &&
|
||||
SysId.IsValid(SecurityItemId) &&
|
||||
SysId.IsValid(AffiliateId);
|
||||
}
|
||||
}
|
||||
public KeepLoggedInParams ExtractKeepLoggedInParams(String encryptedParams)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ using System.Web;
|
||||
namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
{
|
||||
//=======================================================================
|
||||
// Online Security Controller - Ajax actions.
|
||||
// Portal Security Controller - Ajax actions.
|
||||
//=======================================================================
|
||||
partial class SecurityController
|
||||
{
|
||||
@@ -72,7 +72,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
return Json(responseData);
|
||||
|
||||
String errorMessage = "";
|
||||
if (!SignUserIn(sessionContext, securityItemUser, affiliateUser, model.KeepMeSignedIn, ref errorMessage))
|
||||
if (!SignUserIn(userContext, securityItemUser, affiliateUser, model.KeepMeSignedIn, ref errorMessage))
|
||||
return Json(responseData.SetControlError(nameof(model.EmailAddress), errorMessage));
|
||||
}
|
||||
}
|
||||
@@ -122,23 +122,25 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
{
|
||||
String fn = GetType().Name + ".GetSecurityItemForSignin";
|
||||
SaSecurityItem securityItemUser = null;
|
||||
using (SaSecurityConsole securityConsole = App.CreateSecurityConsole(userContext))
|
||||
using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContext))
|
||||
{
|
||||
// Retrieve security item by email address.
|
||||
securityItemUser = securityConsole.GetUserByEmail(model.EmailAddress);
|
||||
if (securityItemUser != null)
|
||||
using (securityItemUser = securityConsoleForUser.GetUserByEmail(model.EmailAddress))
|
||||
{
|
||||
// Validate password.
|
||||
if (!SbmPasswordHelper.VerifyHashedPassword(securityItemUser, model.Password) && !(DebugMode && model.EmailAddress.EqualsCI("ahoward@sarand.com")))
|
||||
if (securityItemUser != null)
|
||||
{
|
||||
// Record invalid login attempt.
|
||||
securityItemUser.InvalidLoginCount++;
|
||||
securityItemUser.LastInvalidLoginAt = DateTime.Now;
|
||||
if (!securityConsole.UpdateSecurityItem(securityItemUser, DbUpdateInfo))
|
||||
SaLogger.Warn(fn + " Failed to store invalid sign-in attempt against User: " + model.EmailAddress + ". " + AccountMgr.ErrorStateMessage, true);
|
||||
SaLogger.Warn(fn + " Attempt to sign-in to User " + model.EmailAddress + " with invalid password. Attempt: " + securityItemUser.InvalidLoginCount, true);
|
||||
responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]);
|
||||
securityItemUser = null;
|
||||
// Validate password.
|
||||
if (!SbmPasswordHelper.VerifyHashedPassword(securityItemUser, model.Password) && !(DebugMode && model.EmailAddress.EqualsCI("ahoward@sarand.com")))
|
||||
{
|
||||
// Record invalid login attempt.
|
||||
securityItemUser.InvalidLoginCount++;
|
||||
securityItemUser.LastInvalidLoginAt = DateTime.Now;
|
||||
if (!securityConsoleForUser.UpdateSecurityItem(securityItemUser, DbUpdateInfo))
|
||||
SaLogger.Warn(fn + " Failed to store invalid sign-in attempt against User: " + model.EmailAddress + ". " + AccountMgr.ErrorStateMessage, true);
|
||||
SaLogger.Warn(fn + " Attempt to sign-in to User " + model.EmailAddress + " with invalid password. Attempt: " + securityItemUser.InvalidLoginCount, true);
|
||||
responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]);
|
||||
securityItemUser = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,18 +152,20 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
{
|
||||
String fn = GetType().Name + ".GetAffiliateForSignin";
|
||||
SbmAffiliate affiliate = null;
|
||||
using (SbmAccountMgr accountMgr = App.System.CreateAccountMgr(userContext))
|
||||
using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContext))
|
||||
{
|
||||
// Identify the contact asociated with the security item.
|
||||
SbmAffiliate.Params prmAffiliate = new SbmAffiliate.Params();
|
||||
prmAffiliate.UserId = securityItemUser.Id;
|
||||
prmAffiliate.StatusCat = SbmAffiliate.StatusCat.ActiveOnly;
|
||||
affiliate = accountMgr.GetAffiliate(prmAffiliate);
|
||||
if (affiliate != null)
|
||||
prmAffiliate.StatusCat = SbmAffiliate.StatusCat.CurrentOnly;
|
||||
using (affiliate = accountMgrForUser.GetAffiliate(prmAffiliate))
|
||||
{
|
||||
if (affiliate != null)
|
||||
{
|
||||
}
|
||||
if (affiliate == null && !responseData.HasControlErrors())
|
||||
responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]);
|
||||
}
|
||||
if (affiliate == null && !responseData.HasControlErrors())
|
||||
responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]);
|
||||
}
|
||||
return affiliate;
|
||||
}
|
||||
@@ -255,8 +259,12 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
String messageTitle = Localiser["security.caption.change.password"];
|
||||
|
||||
// Validate referrer.
|
||||
if (!ValidReferer)
|
||||
return Forbid();
|
||||
if (!ValidReferer) return Forbid();
|
||||
|
||||
// If user is logged in, session Consoles and Managers can be used.
|
||||
// If 'Request Password' parameters are specified, Consoles and Managers must be associated with the specific database for the user.
|
||||
SbmSessionContext sessionContextForUser = null;
|
||||
SbmUserContext_Web userContextForUser = null;
|
||||
|
||||
// If 'Request Password' encrypted params (from email link) are present,
|
||||
// Session is not required and password can be set directly against the user.
|
||||
@@ -267,68 +275,100 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(model.rpe);
|
||||
if (requestPasswordParams.IsValid())
|
||||
{
|
||||
// Retrieve user details and validate email address.
|
||||
securityItemUser = SecurityConsole.GetSecurityItemById(SaSecurity.ItemType.User, requestPasswordParams.SecurityItemId);
|
||||
affiliateUser = AccountMgr.GetAffiliateById(requestPasswordParams.AffiliateId);
|
||||
if (requestPasswordParams.IsValid(securityItemUser, affiliateUser))
|
||||
// User may be defined on any one of the BusinessMgr databases - need to find out which.
|
||||
// Identify session contexts with a BusMgr database that hosts the specified email address.
|
||||
List<ushort> sessionContextIdxs = SbmSessionContextHelper_Web.GetSessionContextIdxsForUsersBusMgrDatabase(requestPasswordParams.EmailAddress);
|
||||
foreach (ushort sessionContextIdx in sessionContextIdxs)
|
||||
{
|
||||
validRequestPasswordParams = true;
|
||||
validSessionRequired = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. SecurityItemId: " + SysId.ToString(requestPasswordParams.SecurityItemId) + ". AffiliateId: " + SysId.ToString(requestPasswordParams.AffiliateId) + ". Email: " + requestPasswordParams.EmailAddress);
|
||||
affiliateUser = null;
|
||||
// Consoles and Managers must be associated with the specific context.
|
||||
sessionContextForUser = SbmSessionContextHelper_Web.GetSessionContext(sessionContextIdx);
|
||||
userContextForUser = new SbmUserContext_Web(sessionContextForUser);
|
||||
using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContextForUser))
|
||||
using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContextForUser))
|
||||
{
|
||||
// Retrieve user details and validate email address.
|
||||
securityItemUser = securityConsoleForUser.GetSecurityItemById(SaSecurity.ItemType.User, requestPasswordParams.SecurityItemId);
|
||||
affiliateUser = accountMgrForUser.GetAffiliateById(requestPasswordParams.AffiliateId);
|
||||
if (requestPasswordParams.IsValid(securityItemUser, affiliateUser))
|
||||
{
|
||||
validRequestPasswordParams = true;
|
||||
validSessionRequired = false;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. Email: " + requestPasswordParams.EmailAddress + ". SecurityItemId: " + SysId.ToString(requestPasswordParams.SecurityItemId) + ". AffiliateId: " + SysId.ToString(requestPasswordParams.AffiliateId));
|
||||
if (securityItemUser != null) securityItemUser.Dispose(); securityItemUser = null;
|
||||
if (affiliateUser != null) affiliateUser.Dispose(); affiliateUser = null;
|
||||
sessionContextForUser = null;
|
||||
userContextForUser = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// User must have a valid session.
|
||||
// User must have a valid session if security item and affiliate not idenfied via the 'Request Password' paramaters.
|
||||
if (validSessionRequired && (securityItemUser == null || affiliateUser == null) && !ValidUserSession)
|
||||
return Json(responseData.SetSessionExpired(messageTitle, Localiser["security.msg.warn.sessionExpired"]));
|
||||
|
||||
// Retrieve user details if not previously populated.
|
||||
if (securityItemUser == null && SessionData_Portal != null)
|
||||
securityItemUser = SecurityConsole.GetSecurityItemById(SaSecurity.ItemType.User, SessionData_Portal.User.SecurityItemId);
|
||||
if (securityItemUser == null)
|
||||
return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve security item details."));
|
||||
if (affiliateUser == null && SessionData_Portal != null)
|
||||
affiliateUser = AccountMgr.GetAffiliateById(SessionData_Portal.User.AffiliateId);
|
||||
if (affiliateUser == null)
|
||||
return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve affiliate details."));
|
||||
|
||||
// Validate password data.
|
||||
model.ValidateChangePasswordData(securityItemUser, affiliateUser, responseData);
|
||||
if (responseData.HasControlErrors())
|
||||
return Json(responseData);
|
||||
|
||||
// Note old password.
|
||||
String oldPasswordHashed = securityItemUser.PasswordHash;
|
||||
|
||||
// Store password.
|
||||
securityItemUser.PasswordHash = SbmPasswordHelper.GetPasswordHash(securityItemUser, model.Password);
|
||||
securityItemUser.PasswordChangedAt = DateTime.Now;
|
||||
// Remove new password from history if present.
|
||||
for (int passwordHistoryIdx = 0; passwordHistoryIdx < securityItemUser.PasswordHistory.Count; passwordHistoryIdx++)
|
||||
// User specific Consoles and Managers if User Context specified.
|
||||
using (SaSecurityConsole securityConsoleForUser = userContextForUser != null ? App.CreateSecurityConsole(userContextForUser) : null)
|
||||
using (SbmAccountMgr accountMgrForUser = userContextForUser != null ? App.System.CreateAccountMgr(userContextForUser) : null)
|
||||
{
|
||||
// Compare history password to new value.
|
||||
if (SbmPasswordHelper.VerifyHashedPassword(securityItemUser, model.Password, securityItemUser.PasswordHistory[passwordHistoryIdx]))
|
||||
SaSecurityConsole securityConsole = (securityConsoleForUser ?? SecurityConsole);
|
||||
SbmAccountMgr accountMgr = (accountMgrForUser ?? AccountMgr);
|
||||
|
||||
// Retrieve user details if not previously populated.
|
||||
if (securityItemUser == null && SessionData_Portal != null)
|
||||
securityItemUser = (securityConsole.GetSecurityItemById(SaSecurity.ItemType.User, SessionData_Portal.User.SecurityItemId));
|
||||
if (securityItemUser == null)
|
||||
return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve security item details."));
|
||||
if (affiliateUser == null && SessionData_Portal != null)
|
||||
affiliateUser = accountMgr.GetAffiliateById(SessionData_Portal.User.AffiliateId);
|
||||
if (affiliateUser == null)
|
||||
return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve affiliate details."));
|
||||
|
||||
// Validate password data.
|
||||
model.ValidateChangePasswordData(securityItemUser, affiliateUser, responseData);
|
||||
if (responseData.HasControlErrors())
|
||||
return Json(responseData);
|
||||
|
||||
// Note old password.
|
||||
String oldPasswordHashed = securityItemUser.PasswordHash;
|
||||
|
||||
// Store password.
|
||||
securityItemUser.PasswordHash = SbmPasswordHelper.GetPasswordHash(securityItemUser, model.Password);
|
||||
securityItemUser.PasswordChangedAt = DateTime.Now;
|
||||
// Remove new password from history if present.
|
||||
for (int passwordHistoryIdx = 0; passwordHistoryIdx < securityItemUser.PasswordHistory.Count; passwordHistoryIdx++)
|
||||
{
|
||||
// New password found in history - remove it.
|
||||
securityItemUser.PasswordHistory.RemoveAt(passwordHistoryIdx);
|
||||
passwordHistoryIdx--;
|
||||
// Compare history password to new value.
|
||||
if (SbmPasswordHelper.VerifyHashedPassword(securityItemUser, model.Password, securityItemUser.PasswordHistory[passwordHistoryIdx]))
|
||||
{
|
||||
// New password found in history - remove it.
|
||||
securityItemUser.PasswordHistory.RemoveAt(passwordHistoryIdx);
|
||||
passwordHistoryIdx--;
|
||||
}
|
||||
}
|
||||
securityItemUser.PasswordHistory.Remove(oldPasswordHashed);
|
||||
// Add the old password at the start of the history list and validate history length.
|
||||
securityItemUser.PasswordHistory.Add(oldPasswordHashed);
|
||||
while (securityItemUser.PasswordHistory.Count > 10)
|
||||
securityItemUser.PasswordHistory.RemoveAt(securityItemUser.PasswordHistory.Count - 1);
|
||||
if (!securityConsole.UpdateSecurityItem(securityItemUser, DbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn(fn + " Failed to change password for " + securityItemUser.EmailAddress + ": " + securityConsole.ErrorStateMessage);
|
||||
return Json(responseData.SetControlError(nameof(model.Password), Localiser["security.msg.change.password.failed"]));
|
||||
}
|
||||
SaLogger.Info(fn + " Password changed for " + securityItemUser.EmailAddress, true);
|
||||
}
|
||||
securityItemUser.PasswordHistory.Remove(oldPasswordHashed);
|
||||
// Add the old password at the start of the history list and validate history length.
|
||||
securityItemUser.PasswordHistory.Add(oldPasswordHashed);
|
||||
while (securityItemUser.PasswordHistory.Count > 10)
|
||||
securityItemUser.PasswordHistory.RemoveAt(securityItemUser.PasswordHistory.Count - 1);
|
||||
if (!SecurityConsole.UpdateSecurityItem(securityItemUser, DbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn(fn + " Failed to change password for " + securityItemUser.EmailAddress + ": " + AccountMgr.ErrorStateMessage);
|
||||
return Json(responseData.SetControlError(nameof(model.Password), Localiser["security.msg.change.password.failed"]));
|
||||
}
|
||||
SaLogger.Info(fn + " Password changed for " + securityItemUser.EmailAddress, true);
|
||||
|
||||
// Cleanup. NB The Security Item and/or Affiliate aren't disposed when the method
|
||||
// returns above but these objects shouldn't be carrying any disposable resources.
|
||||
if (securityItemUser != null) securityItemUser.Dispose(); securityItemUser = null;
|
||||
if (affiliateUser != null) affiliateUser.Dispose(); affiliateUser = null;
|
||||
sessionContextForUser = null;
|
||||
userContextForUser = null;
|
||||
|
||||
// Construct response.
|
||||
responseData.Success = true;
|
||||
@@ -350,10 +390,10 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
}
|
||||
// Ajax request.
|
||||
[HttpPost]
|
||||
[Route("Security/Ajax/RequestNewPassword")]
|
||||
public ActionResult Ajax_RequestNewPassword(Model_Security model)
|
||||
[Route("Security/Ajax/RequestPassword")]
|
||||
public ActionResult Ajax_RequestPassword(Model_Security model)
|
||||
{
|
||||
String fn = GetType().Name + ".Ajax_RequestNewPassword";
|
||||
String fn = GetType().Name + ".Ajax_RequestPassword";
|
||||
AjaxResponse_RequestPassword responseData = new AjaxResponse_RequestPassword(model);
|
||||
String messageTitle = Localiser["security.caption.request.password"];
|
||||
|
||||
@@ -372,15 +412,15 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
SbmUserContext_Web userContext = new SbmUserContext_Web(sessionContext);
|
||||
|
||||
// Get user security item and affiliate.
|
||||
using (SaSecurityConsole securityConsole = App.CreateSecurityConsole(userContext))
|
||||
using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContext))
|
||||
{
|
||||
using (SaSecurityItem securityItemUser = securityConsole.GetUserByEmail(model.EmailAddress))
|
||||
using (SaSecurityItem securityItemUser = securityConsoleForUser.GetUserByEmail(model.EmailAddress))
|
||||
{
|
||||
if (securityItemUser != null)
|
||||
{
|
||||
using (SbmAccountMgr accountMgr = App.System.CreateAccountMgr(userContext))
|
||||
using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContext))
|
||||
{
|
||||
using (SbmAffiliate affiliateUser = accountMgr.GetAffiliateByUserId(securityItemUser.Id))
|
||||
using (SbmAffiliate affiliateUser = accountMgrForUser.GetAffiliateByUserId(securityItemUser.Id))
|
||||
{
|
||||
if (affiliateUser != null)
|
||||
{
|
||||
@@ -431,9 +471,9 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
//-------------------------------------------------------------------
|
||||
// Password request link.
|
||||
private static String ParamName_RequestPasswordEncrypted { get; } = "rpe";
|
||||
private static String ParamName_EmailAddress { get; } = "ema";
|
||||
private static String ParamName_SecurityItemId { get; } = "sei";
|
||||
private static String ParamName_AffiliateId { get; } = "afi";
|
||||
private static String ParamName_EmailAddress { get; } = "ema";
|
||||
private static String ParamName_ExpiresAt { get; } = "exp";
|
||||
private static String ParamName_Token { get; } = "tok";
|
||||
// NB IV value is generated from the key and fixed so that it doesn't change when the application restarts.
|
||||
@@ -444,9 +484,9 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
private String GenerateRequestPasswordLink(SysId securityItemId, SysId affiliateId, String emailAddress, String requestPasswordToken)
|
||||
{
|
||||
NameValueCollection parameters = new NameValueCollection();
|
||||
parameters.Add(ParamName_EmailAddress, emailAddress);
|
||||
parameters.Add(ParamName_SecurityItemId, SysId.ToString(securityItemId));
|
||||
parameters.Add(ParamName_AffiliateId, SysId.ToString(affiliateId));
|
||||
parameters.Add(ParamName_EmailAddress, emailAddress);
|
||||
parameters.Add(ParamName_ExpiresAt, DateUtil.Format(DateTime.Now.AddMinutes(GetRequestPasswordEmailLinkExpiresMinutes()), DateUtil.Format_XmlDateTime));
|
||||
parameters.Add(ParamName_Token, requestPasswordToken);
|
||||
String link =
|
||||
@@ -463,34 +503,34 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
}
|
||||
private class RequestPasswordParams
|
||||
{
|
||||
public String EmailAddress { get; set; }
|
||||
public SysId SecurityItemId { get; set; }
|
||||
public SysId AffiliateId { get; set; }
|
||||
public String EmailAddress { get; set; }
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
public String Token { get; set; }
|
||||
/// <summary>Retrieve the list of valid parameters in printable format [Name: Value].</summary>
|
||||
public List<String> GetValidParams()
|
||||
{
|
||||
List<String> values = new List<String>();
|
||||
if (!StringUtil.IsEmpty(EmailAddress))
|
||||
values.Add("Email: " + EmailAddress);
|
||||
if (SysId.IsValid(SecurityItemId))
|
||||
values.Add("SecurityItemId: " + SysId.ToString(SecurityItemId));
|
||||
if (SysId.IsValid(AffiliateId))
|
||||
values.Add("AffiliateId: " + SysId.ToString(AffiliateId));
|
||||
if (!StringUtil.IsEmpty(EmailAddress))
|
||||
values.Add("Email: " + EmailAddress);
|
||||
if (DateUtil.IsValid(ExpiresAt))
|
||||
values.Add("Expires: " + DateUtil.FormatXmlDateTime(ExpiresAt));
|
||||
if (!StringUtil.IsEmpty(Token))
|
||||
values.Add("Token: " + Token);
|
||||
return values;
|
||||
}
|
||||
/// <summary>Verifiy that the account Id and email are populated and the data hasn't expired.</summary>
|
||||
/// <summary>Verifiy that the email and account Ids are populated and the data hasn't expired.</summary>
|
||||
public bool IsValid()
|
||||
{
|
||||
return
|
||||
!StringUtil.IsEmpty(EmailAddress) &&
|
||||
SysId.IsValid(SecurityItemId) &&
|
||||
SysId.IsValid(AffiliateId) &&
|
||||
!StringUtil.IsEmpty(EmailAddress) &&
|
||||
DateUtil.IsValid(ExpiresAt) && ExpiresAt >= DateTime.Now;
|
||||
}
|
||||
/// <summary>Verify that the data is valid and the account details match.</summary>
|
||||
@@ -498,10 +538,11 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
{
|
||||
return
|
||||
IsValid() &&
|
||||
affiliateUser != null &&
|
||||
securityItemUser.EmailAddress.EqualsCI(EmailAddress) &&
|
||||
securityItemUser != null &&
|
||||
securityItemUser.Id.Equals(SecurityItemId) &&
|
||||
affiliateUser.Id.Equals(AffiliateId) &&
|
||||
securityItemUser.EmailAddress.EqualsCI(EmailAddress);
|
||||
affiliateUser != null &&
|
||||
affiliateUser.Id.Equals(AffiliateId);
|
||||
}
|
||||
}
|
||||
private RequestPasswordParams ExtractRequestPasswordLinkParams(String encryptedParams)
|
||||
@@ -510,9 +551,9 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
if (!StringUtil.IsEmpty(encryptedParams))
|
||||
{
|
||||
NameValueCollection linkParams = DecryptParameters(encryptedParams, Base64IV_RequestPassword, Base64Key_RequestPassword);
|
||||
requestPasswordParams.EmailAddress = linkParams.Get(ParamName_EmailAddress);
|
||||
requestPasswordParams.SecurityItemId = new SysId(StringUtil.ToInt(linkParams.Get(ParamName_SecurityItemId)));
|
||||
requestPasswordParams.AffiliateId = new SysId(StringUtil.ToInt(linkParams.Get(ParamName_AffiliateId)));
|
||||
requestPasswordParams.EmailAddress = linkParams.Get(ParamName_EmailAddress);
|
||||
requestPasswordParams.ExpiresAt = DateUtil.ParseXmlDateTime(linkParams.Get(ParamName_ExpiresAt));
|
||||
requestPasswordParams.Token = linkParams.Get(ParamName_Token);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.App;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.Helpers;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.Portal.Models;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.App;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Bus;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Mgr;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Bus;
|
||||
using Sarand.Net.Standard.Base.Security;
|
||||
@@ -71,24 +74,43 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
|
||||
RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(encryptedParams);
|
||||
if (requestPasswordParams.IsValid())
|
||||
{
|
||||
String logPrefix = fn + " 'Request Password' parameters supplied but ";
|
||||
String logSuffix = requestPasswordParams.GetValidParams().Concat("", " ", ".");
|
||||
using (SaSecurityItem securityItemUser = SecurityConsole.GetSecurityItemById(SaSecurity.ItemType.User, requestPasswordParams.SecurityItemId))
|
||||
using (SbmAffiliate userAccount = AccountMgr.GetAffiliateById(requestPasswordParams.AffiliateId))
|
||||
// User may be defined on any one of the BusinessMgr databases - need to find out which.
|
||||
// Identify session contexts with a BusMgr database that hosts the specified email address.
|
||||
List<ushort> sessionContextIdxs = SbmSessionContextHelper_Web.GetSessionContextIdxsForUsersBusMgrDatabase(requestPasswordParams.EmailAddress);
|
||||
foreach (ushort sessionContextIdx in sessionContextIdxs)
|
||||
{
|
||||
if (securityItemUser != null && userAccount != null)
|
||||
// Consoles and Managers must be associated with the specific context.
|
||||
SbmSessionContext sessionContext = SbmSessionContextHelper_Web.GetSessionContext(sessionContextIdx);
|
||||
SbmUserContext_Web userContext = new SbmUserContext_Web(sessionContext);
|
||||
using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContext))
|
||||
using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContext))
|
||||
{
|
||||
if (userAccount.IsStatusActive)
|
||||
using (SaSecurityItem securityItemUser = securityConsoleForUser.GetUserByEmail(requestPasswordParams.EmailAddress))
|
||||
using (SbmAffiliate affiliateUser = accountMgrForUser.GetAffiliateById(requestPasswordParams.AffiliateId))
|
||||
{
|
||||
if (requestPasswordParams.IsValid(securityItemUser, userAccount))
|
||||
if (securityItemUser != null && affiliateUser != null)
|
||||
{
|
||||
model.rpe = encryptedParams;
|
||||
validSessionRequired = false;
|
||||
if (securityItemUser.Id.Equals(requestPasswordParams.SecurityItemId) && affiliateUser.UserId.Equals(securityItemUser.Id))
|
||||
{
|
||||
if (affiliateUser.IsStatusCurrent)
|
||||
{
|
||||
if (requestPasswordParams.IsValid(securityItemUser, affiliateUser))
|
||||
{
|
||||
model.rpe = encryptedParams;
|
||||
validSessionRequired = false;
|
||||
break;
|
||||
}
|
||||
else SaLogger.Warn(logPrefix + "Security Item and Affiliate are incompatible with parameters. " + logSuffix);
|
||||
}
|
||||
else SaLogger.Warn(logPrefix + "Affiliate is not active. " + logSuffix);
|
||||
}
|
||||
else SaLogger.Warn(logPrefix + "Security Item and Affiliate are inconsistent. " + logSuffix);
|
||||
}
|
||||
else SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. " + logSuffix);
|
||||
else SaLogger.Warn(logPrefix + "Security Item or Affiliate not found. " + logSuffix);
|
||||
}
|
||||
else SaLogger.Warn(fn + " 'Request Password' parameters supplied but account is not active. " + logSuffix);
|
||||
}
|
||||
else SaLogger.Warn(fn + " 'Request Password' parameters supplied but security item or affiliate not found. " + logSuffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
using Sarand.BusinessMgr.Net.Core.Web.App;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.Controllers;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.App;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Bus;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Helpers;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Helpers;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Models
|
||||
{
|
||||
//=======================================================================
|
||||
public class Model_Contact : SbmModel_Portal
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public Model_Contact(SbmLocaliser localiser) :
|
||||
base(localiser)
|
||||
{
|
||||
AdditionalFiles_JS.Add("js/sbm-contact.js");
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Filter properties.
|
||||
public class FilterData
|
||||
{
|
||||
public String AffiliateSearch { get; set; }
|
||||
public String AffiliateType { get; set; }
|
||||
}
|
||||
public FilterData Filter { get; } = new FilterData();
|
||||
public Sbm.AffiliateType GetFilterAffiliateType()
|
||||
{
|
||||
return (Sbm.AffiliateType)Internals.AffiliateTypeCodes.GetTypeForCode(Filter.AffiliateType);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Affiliate properties.
|
||||
public String AffiliateId { get; set; }
|
||||
public String AffiliateType{ get; set; }
|
||||
public Sbm.AffiliateType GetAffiliateType()
|
||||
{
|
||||
return (Sbm.AffiliateType)Internals.AffiliateTypeCodes.GetTypeForCode(AffiliateType);
|
||||
}
|
||||
public String AffiliateName { get; set; }
|
||||
// Common properties.
|
||||
public String AffiliateNotes { get; set; }
|
||||
public String AffiliateStatus { get; set; }
|
||||
public SaStatus GetAffiliateStatus()
|
||||
{
|
||||
return SacsSystem.GetStatusForCode(AffiliateStatus);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Validation.
|
||||
//-------------------------------------------------------------------
|
||||
// Affiliate validation.
|
||||
public bool ValidateAffiliateData(SbmController_Base.AjaxResponse_Data responseData)
|
||||
{
|
||||
// Affiliate Type.
|
||||
if (AffiliateType != null)
|
||||
{
|
||||
if (StringUtil.IsEmpty(AffiliateType))
|
||||
responseData.SetControlError(nameof(AffiliateType), Localiser["msg.validation.mandatory.value"]);
|
||||
}
|
||||
|
||||
// Affiliate Name.
|
||||
if (AffiliateName != null)
|
||||
{
|
||||
responseData.SetControlValue(nameof(AffiliateName), AffiliateName);
|
||||
if (StringUtil.IsEmpty(AffiliateName))
|
||||
responseData.SetControlError(nameof(AffiliateName), Localiser["msg.validation.mandatory.value"]);
|
||||
if (StringUtil.GetLength(AffiliateName) > GetControlMaxLength(SbmDbDfn.ColumnIdx.Affiliate_Name))
|
||||
responseData.SetControlError(nameof(AffiliateName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn.ColumnIdx.Affiliate_Name)]);
|
||||
}
|
||||
|
||||
// Affiliate Notes.
|
||||
if (AffiliateNotes != null)
|
||||
{
|
||||
responseData.SetControlValue(nameof(AffiliateNotes), AffiliateNotes);
|
||||
if (!StringUtil.IsEmpty(AffiliateNotes) && StringUtil.GetLength(AffiliateNotes) > GetControlMaxLength(SbmDbDfn.ColumnIdx.Affiliate_Notes))
|
||||
responseData.SetControlError(nameof(AffiliateNotes), Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn.ColumnIdx.Affiliate_Notes)]);
|
||||
}
|
||||
|
||||
// Affiliate status.
|
||||
if (AffiliateStatus != null)
|
||||
{
|
||||
if (StringUtil.IsEmpty(AffiliateStatus))
|
||||
responseData.SetControlError(nameof(AffiliateStatus), Localiser["msg.validation.mandatory.value"]);
|
||||
else if (!SbmAffiliate.ValidStatuses.Contains(GetAffiliateStatus()))
|
||||
responseData.SetControlError(nameof(AffiliateStatus), Localiser["msg.validation.invalid.value"]);
|
||||
}
|
||||
|
||||
return !responseData.HasControlErrors();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -36,4 +36,10 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Views\Commodity\" />
|
||||
<Folder Include="Views\Association\" />
|
||||
<Folder Include="Views\Transactions\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
@model SbmModel_Portal
|
||||
@inject SbmLocaliser Localiser
|
||||
|
||||
<script src="~/js/sbm-resultsPaging.js?@Model.HrefVersion"></script>
|
||||
<script type="text/javascript">
|
||||
sbm.resultsPaging.msg.pageInfo = '@Localiser.GetJavascriptText("label.page.info","{0}","{1}")';
|
||||
</script>
|
||||
|
||||
@* Results paging *@
|
||||
<div class="sbm-results-paging hidden">
|
||||
|
||||
@Html.HiddenFor(model => model.Paging.PageNo, new { @id = "", @class = "PagingPageNo" })
|
||||
@Html.HiddenFor(model => model.Paging.PageLength, new { @id = "", @class = "PagingPageLength" })
|
||||
|
||||
<div class="iblock sbm-action sbm-paging-first"><img src="~/img/action/first24.png" alt="@(Localiser["label.first"])" title="@(Localiser["label.first"])" /></div>
|
||||
<div class="iblock sbm-action sbm-paging-prev"><img src="~/img/action/prev24.png" alt="@(Localiser["label.previous"])" title="@(Localiser["label.previous"])" /></div>
|
||||
<div class="iblock sbm-paging-info"></div>
|
||||
<div class="iblock sbm-action sbm-paging-next"><img src="~/img/action/next24.png" alt="@(Localiser["label.next"])" title="@(Localiser["label.next"])" /></div>
|
||||
<div class="iblock sbm-action sbm-paging-last"><img src="~/img/action/last24.png" alt="@(Localiser["label.last"])" title="@(Localiser["label.last"])" /></div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,116 @@
|
||||
@model Model_Contact
|
||||
@inject SbmLocaliser Localiser
|
||||
@{
|
||||
/* Affiliates - Index */
|
||||
ViewData["Title"] = "Affiliates";
|
||||
}
|
||||
|
||||
<div class="sbm-page-affiliates">
|
||||
|
||||
<script type="text/javascript">
|
||||
sbm.lbl.edit = 'Edit';
|
||||
sbm.lbl.activate = 'Activate';
|
||||
</script>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-5 row">
|
||||
|
||||
<div class="col">
|
||||
|
||||
@* Filter Panel *@
|
||||
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmAffiliateFilter", @class = "sbm-form", @onSubmit = "return false;" }))
|
||||
{
|
||||
<div class="sbm-panel-filter">
|
||||
@* Header - Page caption, paging controls, actions. *@
|
||||
<div class="sbm-header">
|
||||
<div class="row">
|
||||
<div class="col"><div class="sbm-caption">@Localiser["contact.caption.contacts"]</div></div>
|
||||
<div class="fixed row">
|
||||
<div class="col">
|
||||
<partial name="~/Views/Common/ResultsPaging.cshtml" />
|
||||
</div>
|
||||
<div class="fixed row">
|
||||
<div class="sbm-action sbm-results-refresh"><img src="~/img/action/refresh24.png" alt="@(Localiser["label.refresh"])" title="@(Localiser["label.refresh"])" /></div>
|
||||
<div class="sbm-action sbm-item-create"><img src="~/img/action/add24.png" alt="@(Localiser["label.create"])" title="@(Localiser["label.create"])" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sbm-content">
|
||||
@* Affiliate filter - name etc. *@
|
||||
<div class="row pb4">
|
||||
<div class="col-sm-5 sbm-label text-left text-sm-right">@Localiser["label.search"]</div>
|
||||
<div class="col-sm-7">
|
||||
@Html.TextBoxFor(model => model.Filter.AffiliateSearch, new { @id = "", @class = "FilterAffiliate", @maxlength = "32" })
|
||||
</div>
|
||||
</div>
|
||||
@* Affiliate Category *@
|
||||
<div class="row pb4">
|
||||
<div class="col-sm-5 sbm-label text-left text-sm-right">@Localiser["label.category"]</div>
|
||||
<div class="col-sm-7">
|
||||
@Html.DropDownListFor(model => model.Filter.AffiliateType, Model.GetAffiliateTypeCodesForSelect(), Localiser["label.any"], new { @id = "", @class = "FilterAffiliateType sbm-select" })
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
@* Separate filter from results when alondside each other. *@
|
||||
<div class="fixed"><div class="d-none d-md-block" style="width:8px;"></div></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-7">
|
||||
|
||||
@* Results Panel *@
|
||||
<div class="sbm-panel-results">
|
||||
<div class="sbm-panel-messages"></div>
|
||||
<div class="sbm-panel-item-list"></div>
|
||||
|
||||
@* element templates *@
|
||||
<div class="sbm-panel-item-template">
|
||||
@* Affiliate item template *@
|
||||
<div class="sbm-panel-item Affiliate">
|
||||
<div class="sbm-header row">
|
||||
<div class="col">
|
||||
@* Affiliate caption *@
|
||||
<div class="sbm-caption"><div class="sbm-action sbm-item-update AffiliateName"></div></div>
|
||||
</div>
|
||||
<div class="fixed sbm-actions">
|
||||
@* Burger menu *@
|
||||
<ul class="nav navbar-nav navbar-light navbar-right sbm-burger">
|
||||
<li class="dropdown-toggle caret-off">
|
||||
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
|
||||
<ul class="dropdown-menu dropdown-menu-right sbm-dropdown-menu">@* Menu items will be inserted here. *@</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sbm-content">
|
||||
<div class="row">
|
||||
<div class="col-sm-4"><div class="AffiliateType"></div></div>
|
||||
<div class="col-sm-4"><div class=""></div></div>
|
||||
<div class="col-sm-4 text-left text-sm-right"><div class=""></div></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4"><div class=""></div></div>
|
||||
<div class="col-sm-4"><div class="AffiliateAssociationCount sbm-action-text">@Localiser["label.associations"]: <span class="AssociationCount"></span></div></div>
|
||||
<div class="col-sm-4 text-left text-sm-right"><div class="StatusDesc"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@* Burger dropdown item template - ul is ignored *@
|
||||
<ul class="sbm-dropdown-menu Affiliate">
|
||||
<li class="sbm-dropdown-item sbm-action"><div class="sbm-label">XXX</div></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<partial name="Panel-AffiliateDialog.cshtml" />
|
||||
@@ -0,0 +1,56 @@
|
||||
@model Model_Contact
|
||||
@inject SbmLocaliser Localiser
|
||||
@{
|
||||
/* Affiliates - Affiliate dialog panel */
|
||||
}
|
||||
|
||||
<script type="text/javascript">
|
||||
sbm.dlg.title.createAffiliate = '@Localiser.GetJavascriptText("contact.caption.contact.create")';
|
||||
sbm.dlg.title.updateAffiliate = '@Localiser.GetJavascriptText("contact.caption.contact.update")';
|
||||
</script>
|
||||
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmAffiliateId", @class = "sbm-form" }))
|
||||
{
|
||||
@Html.HiddenFor(model => model.AffiliateId, new { @class = "AffiliateId" })
|
||||
}
|
||||
<div id="dlgAffiliate" class="sbm-dialog" title="">
|
||||
|
||||
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmAffiliate", @class = "sbm-form", @onSubmit = "return false;" }))
|
||||
{
|
||||
@Html.HiddenFor(model => model.AffiliateId, new { @id = "", @class = "AffiliateId" })
|
||||
|
||||
@* Affiliate Type *@
|
||||
<div class="sbm-form-control AffiliateType">
|
||||
<div class="sbm-label">
|
||||
<label for="cbAffiliateType"><span><span class="required">*</span> @Localiser["label.type"]</span></label>
|
||||
</div>
|
||||
<div class="sbm-control">
|
||||
@Html.DropDownListFor(model => model.AffiliateType, Model.GetAffiliateTypeCodesForSelect(), Localiser["label.select"], new { @id = "cbAffiliateType", @class = "AffiliateType sbm-select" })
|
||||
</div>
|
||||
<div class="sbm-message"></div>
|
||||
</div>
|
||||
|
||||
@* Affiliate Organisation Name *@
|
||||
<div class="sbm-form-control AffiliateName">
|
||||
<div class="sbm-label">
|
||||
<label for="ifAffiliateName"><span><span class="required">*</span> @Localiser["label.name"]</span></label>
|
||||
</div>
|
||||
<div class="sbm-control">
|
||||
@Html.TextBoxFor(model => model.AffiliateName, new { @id = "ifAffiliateName", @class = "AffiliateName sbm-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn.ColumnIdx.Affiliate_Name) })
|
||||
</div>
|
||||
<div class="sbm-message"></div>
|
||||
</div>
|
||||
|
||||
@* Affiliate Notes *@
|
||||
<div class="sbm-form-control AffiliateNotes">
|
||||
<div class="sbm-label">
|
||||
<label for="ifAffiliateNotes"><span><span class="required">*</span> @Localiser["label.notes"]</span></label>
|
||||
</div>
|
||||
<div class="sbm-control">
|
||||
@Html.TextAreaFor(model => model.AffiliateNotes, new { @id = "ifAffiliateNotes", @class = "AffiliateNotes sbm-textarea", @maxlength = @Model.GetControlMaxLength(SbmDbDfn.ColumnIdx.Affiliate_Notes) })
|
||||
</div>
|
||||
<div class="sbm-message"></div>
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
</div>
|
||||
@@ -120,10 +120,10 @@
|
||||
@Localiser["label.reference"]
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<div class="dropdown-item text-dark sbm-action" onclick="sbm.goToPage('Contacts');">@Localiser["label.contacts"]</div>
|
||||
<div class="dropdown-item text-dark sbm-action" onclick="sbm.goToPage('Contact');">@Localiser["label.contacts"]</div>
|
||||
</div>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<div class="dropdown-item text-dark sbm-action" onclick="sbm.goToPage('Commodities');">@Localiser["label.commodities"]</div>
|
||||
<div class="dropdown-item text-dark sbm-action" onclick="sbm.goToPage('Commodity');">@Localiser["label.commodities"]</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"Send": {
|
||||
"Using": 2,
|
||||
"UserName": "businessmgr@sarand.com",
|
||||
"Password": "sarand-busmgr-5apP1re"
|
||||
"Password": "sarand-busmgr-5apPh1re"
|
||||
},
|
||||
"Message": {
|
||||
"AddressFrom": "DoNotReply@SaBusinessMgr.com"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Base stylesheet.
|
||||
BusMgr Portal - Base stylesheet.
|
||||
*/
|
||||
|
||||
body
|
||||
@@ -19,10 +19,9 @@ div, button, input, select, textarea, table, tr, td
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
:focus, input:focus, textarea:focus, select:focus, button:focus
|
||||
:focus, button:focus
|
||||
{
|
||||
outline: none;
|
||||
border-color: inherit;
|
||||
box-shadow: 0 0 0 2px #80808040 inset;
|
||||
}
|
||||
|
||||
@@ -122,10 +121,15 @@ input
|
||||
|
||||
textarea
|
||||
{
|
||||
overflow-y:scroll;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
select
|
||||
{
|
||||
margin: 0;
|
||||
padding: 3px 2px 1px 2px;
|
||||
}
|
||||
|
||||
.bold, b
|
||||
{
|
||||
font-weight: bold !important;
|
||||
@@ -389,7 +393,9 @@ td.action
|
||||
{
|
||||
-moz-box-shadow: 2px 2px 3px #ccc;
|
||||
-webkit-box-shadow: 2px 2px 3px #ccc;
|
||||
box-shadow: 2px 2px 3px #ccc;
|
||||
box-shadow: 2px 2px 3px #ccc; /* For IE 8 */
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=3,Direction=135,Color='#cccccc')"; /* For IE 5.5 - 7 */
|
||||
filter: progid:DXImageTransform.Microsoft.Shadow(Strength=3,Direction=135,Color='#cccccc');
|
||||
}
|
||||
|
||||
/* Bootstrap */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Data stylesheet.
|
||||
BusMgr Portal - Data stylesheet.
|
||||
*/
|
||||
|
||||
/* Filter panel */
|
||||
@@ -8,10 +8,9 @@
|
||||
{
|
||||
margin: 8px auto;
|
||||
background-color: #f8f8f8;
|
||||
border: solid 2px var(--color-primary);
|
||||
border: solid 1px #979797;
|
||||
border-radius: 6px;
|
||||
-webkit-border-radius: 6px;
|
||||
-moz-border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (min-width: 576px)
|
||||
@@ -21,56 +20,60 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sbm-panel-filter > .sbm-header
|
||||
.sbm-panel-filter .sbm-header
|
||||
{
|
||||
padding: 2px 8px;
|
||||
padding: 1px 8px 2px 8px;
|
||||
min-height: 24px;
|
||||
color: #f0f0f0;
|
||||
background-color: var(--color-primary);
|
||||
border-bottom: solid 1px var(--color-primary);
|
||||
background-color: #979797;
|
||||
border-bottom: solid 1px #979797;
|
||||
}
|
||||
|
||||
.sbm-panel-filter > .sbm-header .sbm-caption
|
||||
.sbm-panel-filter .sbm-header .sbm-caption
|
||||
{
|
||||
margin-top: 4px;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sbm-panel-filter > .sbm-header .sbm-action
|
||||
.sbm-panel-filter .sbm-header .sbm-action
|
||||
{
|
||||
margin: 2px 0 0 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.sbm-panel-filter > .sbm-header .sbm-action:hover
|
||||
.sbm-panel-filter .sbm-header .sbm-action:hover
|
||||
{
|
||||
background-color: #b0b0b0;
|
||||
box-shadow: 0 0 4px 2px #80808080;
|
||||
}
|
||||
|
||||
.sbm-panel-filter > .sbm-content
|
||||
.sbm-panel-filter .sbm-content
|
||||
{
|
||||
margin: 4px 8px;
|
||||
padding: 4px 8px;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.sbm-panel-filter > .sbm-content .sbm-control
|
||||
.sbm-panel-filter .sbm-content .sbm-label
|
||||
{
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.sbm-panel-filter > .sbm-content .sbm-label
|
||||
{
|
||||
padding: 2px 12px 2px 0;
|
||||
padding-top: 2px;
|
||||
padding-right: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.sbm-panel-filter > .sbm-content .sbm-button
|
||||
.sbm-panel-filter .sbm-content input[type="text"],
|
||||
.sbm-panel-filter .sbm-content select
|
||||
{
|
||||
min-width: 75%;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.sbm-panel-filter .sbm-content .sbm-button
|
||||
{
|
||||
min-width: auto;
|
||||
margin-left: 4px;
|
||||
padding: 4px 6px 2px 6px;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
/* Paging */
|
||||
@@ -88,8 +91,7 @@
|
||||
.sbm-panel-filter .sbm-header .sbm-results-paging .sbm-action
|
||||
{
|
||||
margin: 2px 1px 0 1px;
|
||||
background-color: #a0a0a0;
|
||||
opacity: 0.8;
|
||||
background-color: #b0b0b0;
|
||||
box-shadow: 0 0 0 1px #40404080 inset;
|
||||
}
|
||||
|
||||
@@ -131,8 +133,7 @@
|
||||
.sbm-panel-item
|
||||
{
|
||||
margin: 8px auto;
|
||||
background-color: #f8f8f8;
|
||||
border: solid 2px #979797;
|
||||
border: solid 1px #979797;
|
||||
border-radius: 6px;
|
||||
-webkit-border-radius: 6px;
|
||||
-moz-border-radius: 6px;
|
||||
@@ -161,7 +162,7 @@
|
||||
|
||||
.sbm-panel-item > .sbm-header .sbm-caption .sbm-action:hover
|
||||
{
|
||||
color: var(--color-primary);
|
||||
color: var(--color-light);
|
||||
}
|
||||
|
||||
.sbm-panel-item > .sbm-header .sbm-actions .sbm-action
|
||||
@@ -177,7 +178,7 @@
|
||||
|
||||
.sbm-panel-item > .sbm-content
|
||||
{
|
||||
margin: 6px 8px;
|
||||
margin: 4px 8px;
|
||||
padding: 4px 4px;
|
||||
}
|
||||
|
||||
@@ -186,46 +187,38 @@
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Profile Sports */
|
||||
|
||||
.sbm-panel-item.ProfileSport,
|
||||
.sbm-panel-item.ProfileSport > .sbm-header
|
||||
@media (min-width: 576px)
|
||||
{
|
||||
border-color: var(--color-alt2);
|
||||
.sbm-panel-filter
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/* Organisations */
|
||||
/* Item statuses */
|
||||
|
||||
.sbm-panel-item.Organisation,
|
||||
.sbm-panel-item.Organisation > .sbm-header
|
||||
.sbm-status-awa
|
||||
{
|
||||
border-color: var(--color-alt3);
|
||||
color: darkorange;
|
||||
}
|
||||
|
||||
/* Articles */
|
||||
|
||||
.sbm-panel-item.Article,
|
||||
.sbm-panel-item.Article > .sbm-header
|
||||
.sbm-status-atv
|
||||
{
|
||||
border-color: var(--color-article);
|
||||
color: green;
|
||||
}
|
||||
|
||||
.sbm-panel-item.Article > .sbm-header
|
||||
.sbm-status-ina
|
||||
{
|
||||
background-color: var(--color-article-pale);
|
||||
color: darkred;
|
||||
}
|
||||
|
||||
/* Opportunitys */
|
||||
|
||||
.sbm-panel-item.Opportunity,
|
||||
.sbm-panel-item.Opportunity > .sbm-header
|
||||
.sbm-status-hld
|
||||
{
|
||||
border-color: var(--color-opportunity);
|
||||
color: dodgerblue;
|
||||
}
|
||||
|
||||
.sbm-panel-item.Opportunity > .sbm-header
|
||||
.sbm-status-del
|
||||
{
|
||||
background-color: var(--color-opportunity-pale);
|
||||
color: purple;
|
||||
}
|
||||
|
||||
/* The End */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Form stylesheet.
|
||||
BusMgr Portal - Form stylesheet.
|
||||
*/
|
||||
|
||||
/* Panels */
|
||||
@@ -7,20 +7,20 @@
|
||||
.sbm-panel-info, .sbm-panel-form
|
||||
{
|
||||
max-width: 720px;
|
||||
margin: 16px auto;
|
||||
margin: 24px auto;
|
||||
padding: 0;
|
||||
background-color: #f8f8f8;
|
||||
border: solid 2px #606060;
|
||||
background-color: #fff;
|
||||
border: solid 2px #979797;
|
||||
border-color: var(--color-primary);
|
||||
border-radius: 6px;
|
||||
-webkit-border-radius: 6px;
|
||||
-moz-border-radius: 6px;
|
||||
border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sbm-panel-form.security
|
||||
{
|
||||
max-width: 560px;
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
@media (min-width: 576px)
|
||||
@@ -40,24 +40,14 @@
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.sbm-panel-form > .sbm-header
|
||||
{
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.sbm-panel-info > .sbm-header .sbm-caption,
|
||||
.sbm-panel-form > .sbm-header .sbm-caption
|
||||
{
|
||||
font-size: 1.25rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
.sbm-panel-form > .sbm-header .sbm-caption
|
||||
{
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
.sbm-panel-info > .sbm-header .sbm-action img:hover,
|
||||
.sbm-panel-form > .sbm-header .sbm-action img:hover
|
||||
{
|
||||
@@ -66,29 +56,18 @@
|
||||
box-shadow: 2px 2px 4px 0 #80808080;
|
||||
}
|
||||
|
||||
.sbm-panel-info > .sbm-header .dropdown-menu,
|
||||
.sbm-panel-form > .sbm-header .dropdown-menu
|
||||
{
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.sbm-panel-info > .sbm-header ul.nav,
|
||||
.sbm-panel-form > .sbm-header ul.nav
|
||||
{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sbm-panel-info > .sbm-header .dropdown-menu .sbm-action,
|
||||
.sbm-panel-form > .sbm-header .dropdown-menu .sbm-action
|
||||
{
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.sbm-panel-info > .sbm-content,
|
||||
.sbm-panel-form > .sbm-content
|
||||
{
|
||||
max-height: 400px;
|
||||
padding: 12px 12px;
|
||||
background-color: #f8f8f8;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sbm-panel-info > .sbm-content
|
||||
{
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.sbm-panel-section.sbm-edit .sbm-panel-info,
|
||||
@@ -128,20 +107,13 @@
|
||||
{
|
||||
}
|
||||
|
||||
.sbm-form .sbm-form-control .sbm-caption,
|
||||
.sbm-form .sbm-form-control .sbm-label
|
||||
{
|
||||
margin: 0 0 4px 0;
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 1rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.sbm-form .sbm-form-control .sbm-caption
|
||||
{
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sbm-form .sbm-form-control .sbm-label-text
|
||||
{
|
||||
font-size: 1rem;
|
||||
@@ -158,19 +130,39 @@
|
||||
|
||||
.sbm-form .sbm-form-control
|
||||
{
|
||||
margin: 0 0 12px 0;
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
.sbm-form .sbm-form-control .sbm-label
|
||||
{
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 1rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.sbm-form .sbm-form-control .sbm-label-text
|
||||
{
|
||||
font-size: 1rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.sbm-form .sbm-form-control .sbm-note
|
||||
{
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.sbm-form .sbm-input, .sbm-form .sbm-textarea, .sbm-form .sbm-select
|
||||
{
|
||||
padding: 3px 8px 0 8px;
|
||||
padding: 3px 8px 4px 8px;
|
||||
width: 100%;
|
||||
min-height: 30px;
|
||||
font-size: 1rem;
|
||||
line-height: 1.3;
|
||||
line-height: 1.25;
|
||||
background-color: #fff;
|
||||
border: 1px solid #595959;
|
||||
border-radius: 3px;
|
||||
border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -181,8 +173,8 @@
|
||||
|
||||
.sbm-form .sbm-textarea, .sbm-form .sbm-select[multiple="multiple"]
|
||||
{
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
@@ -193,10 +185,8 @@
|
||||
|
||||
.sbm-form .sbm-form-control.sbm-error .sbm-input, .sbm-form .sbm-form-control.sbm-error .sbm-textarea, .sbm-form .sbm-form-control.sbm-error .sbm-select
|
||||
{
|
||||
color: #606060;
|
||||
color: var(--color-primary);
|
||||
border-color: #606060;
|
||||
border-color: var(--color-primary);
|
||||
color: #DC143C;
|
||||
border-color: #DC143C;
|
||||
}
|
||||
|
||||
/* Control messages */
|
||||
@@ -206,7 +196,7 @@
|
||||
display: none;
|
||||
margin-top: 8px;
|
||||
padding-left: 24px;
|
||||
font-size: 16px;
|
||||
font-size: 1.00rem;
|
||||
background-position: left top;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
@@ -226,16 +216,13 @@
|
||||
{
|
||||
height: auto;
|
||||
width: auto;
|
||||
min-width: 120px;
|
||||
padding: 4px 8px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
min-width: 80px;
|
||||
padding: 4px 2px;
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
color: #606060;
|
||||
color: var(--color-primary);
|
||||
background-color: #DFD2E1;
|
||||
border: solid 1px #606060;
|
||||
border-color: var(--color-primary);
|
||||
color: #fff;
|
||||
background-color: #d4af37;
|
||||
border: 1px solid #d4af37;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -243,7 +230,6 @@
|
||||
.sbm-form input[type="button"].sbm-button:hover,
|
||||
.sbm-form input[type="submit"].sbm-button:hover
|
||||
{
|
||||
background-color: #EFE8F0;
|
||||
}
|
||||
|
||||
/* Password strength */
|
||||
@@ -254,7 +240,8 @@
|
||||
height: 18px;
|
||||
margin: 6px 24px;
|
||||
border-color: #595959;
|
||||
box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,20 @@
|
||||
/*
|
||||
Main stylesheet.
|
||||
BusMgr Portal - Main stylesheet.
|
||||
*/
|
||||
|
||||
/* Global variables */
|
||||
|
||||
:root
|
||||
{
|
||||
/*
|
||||
#556B2F Dark Olive Green
|
||||
Ref. https://www.crispedge.com/color/556B2F
|
||||
Darker: #445525, #33401c. Lighter: #768858, #99a682, #a6c275, #b7ce90.
|
||||
*/
|
||||
--color-primary: #556B2F;
|
||||
--color-primary-dark: #425324;
|
||||
--color-primary-light: #a0ac8b;
|
||||
--color-primary-pale: #eceee7;
|
||||
/* Colour analysis: https://www.colorhexa.com/ */
|
||||
/* #4b0082 - Indigo. Ref. https://www.crispedge.com/color/4b0082 */
|
||||
--color-primary: #4b0082;
|
||||
--color-light: #9b71b9;
|
||||
--color-pale: #ebe2f1;
|
||||
/* Tetrad/Square colors */
|
||||
--color-alt1: #2f6b63;
|
||||
--color-alt2: #452f6b;
|
||||
--color-alt3: #6b2f37;
|
||||
/* Activity colours */
|
||||
--color-article-dark: #7339ac;
|
||||
--color-article: #9966CC;
|
||||
--color-article-light: #b38cd9;
|
||||
--color-article-pale: #decdee;
|
||||
--color-opportunity-dark: #8f6b24;
|
||||
--color-opportunity: #CC9933;
|
||||
--color-opportunity-light: #dbb870;
|
||||
--color-opportunity-pale: #f3e7d0;
|
||||
/* Specific colours */
|
||||
--color-dialog: var(--color-alt3);
|
||||
--color-alt1: #820a00;
|
||||
--color-alt2: #378200;
|
||||
--color-alt3: #007882;
|
||||
}
|
||||
|
||||
/* Document */
|
||||
@@ -49,13 +34,36 @@ html
|
||||
|
||||
body
|
||||
{
|
||||
font-family: Verdana, Helvetica, Arial, Lucida, sans-serif;
|
||||
background-color: #e9edf6;
|
||||
font-family: Arial, Verdana, Helvetica, Lucida, sans-serif;
|
||||
}
|
||||
|
||||
body > .container
|
||||
{
|
||||
padding: 0 1%;
|
||||
padding: 0 0.5%;
|
||||
}
|
||||
|
||||
@media (min-width: 992px)
|
||||
{
|
||||
.container
|
||||
{
|
||||
max-width: 1200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px)
|
||||
{
|
||||
.container
|
||||
{
|
||||
max-width: 1200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 576px)
|
||||
{
|
||||
.container
|
||||
{
|
||||
max-width: 1200px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sticky footer styles */
|
||||
@@ -69,7 +77,7 @@ html
|
||||
body
|
||||
{
|
||||
/* Margin bottom by footer height */
|
||||
/*margin-bottom: 60px;*/
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.footer
|
||||
@@ -77,6 +85,7 @@ body
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding: 0 2%;
|
||||
white-space: nowrap;
|
||||
/* Set the fixed height of the footer here */
|
||||
height: 60px;
|
||||
@@ -113,11 +122,14 @@ button.accept-policy
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
/* Information */
|
||||
|
||||
.sbm-para
|
||||
/* Page caption */
|
||||
.sbm-page-caption
|
||||
{
|
||||
margin: 8px 0;
|
||||
margin: 0;
|
||||
padding: 16px 24px;
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* Actions */
|
||||
@@ -125,8 +137,7 @@ button.accept-policy
|
||||
a, a:hover
|
||||
{
|
||||
text-decoration: underline;
|
||||
color: #606060;
|
||||
color: var(--color-primary);
|
||||
color: #d4af37;
|
||||
}
|
||||
|
||||
a:focus
|
||||
@@ -135,11 +146,16 @@ a:focus
|
||||
outline-width: thin;
|
||||
}
|
||||
|
||||
.sbm-action
|
||||
.sbm-action, .sbm-action-text
|
||||
{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sbm-action-text:hover
|
||||
{
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.sbm-action-link
|
||||
{
|
||||
display: inline-block;
|
||||
@@ -169,11 +185,11 @@ a:focus
|
||||
padding: 12px 12px;
|
||||
min-width: 100px;
|
||||
color: #404040;
|
||||
background-color: #b7ce90;
|
||||
background-color: #e0e0e0;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
border-radius: 12px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
transition: all .5s ease-in-out;
|
||||
outline: none;
|
||||
@@ -185,12 +201,18 @@ a:focus
|
||||
|
||||
.sbm-button-action:hover
|
||||
{
|
||||
background-color: #a6c275;
|
||||
background-color: #b0b0b0;
|
||||
}
|
||||
|
||||
.sbm-button-action:focus
|
||||
{
|
||||
outline: none;
|
||||
box-shadow: 0 0 4px 2px #808080;
|
||||
}
|
||||
|
||||
.sbm-button-cta:hover
|
||||
{
|
||||
background: #a6c275;
|
||||
background: #808080 !important;
|
||||
}
|
||||
|
||||
/* --- Notification --- */
|
||||
@@ -254,7 +276,6 @@ a:focus
|
||||
}
|
||||
|
||||
/* --- Modal Please wait --- */
|
||||
|
||||
#sbm-please-wait
|
||||
{
|
||||
display: none;
|
||||
@@ -277,7 +298,6 @@ a:focus
|
||||
}
|
||||
|
||||
/* Dialogs */
|
||||
|
||||
.sbm-dialog
|
||||
{
|
||||
display: none;
|
||||
@@ -287,21 +307,25 @@ a:focus
|
||||
{
|
||||
position: fixed;
|
||||
padding: 0;
|
||||
background-color: #e9edf6;
|
||||
}
|
||||
|
||||
.ui-dialog .ui-dialog-titlebar
|
||||
{
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.ui-dialog .ui-dialog-content
|
||||
{
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.ui-dialog.ui-widget.ui-widget-content
|
||||
{
|
||||
border-width: 2px;
|
||||
border-color: #606060;
|
||||
border-color: var(--color-dialog);
|
||||
-moz-box-shadow: 4px 4px 6px var(--color-dialog);
|
||||
-webkit-box-shadow: 4px 4px 6px var(--color-dialog);
|
||||
}
|
||||
|
||||
.ui-dialog.ui-widget.sbm-dialog-border-thin.ui-widget-content
|
||||
{
|
||||
border-width: 1px;
|
||||
border-color: #d4af37;
|
||||
-moz-box-shadow: 4px 4px 6px #ccc;
|
||||
-webkit-box-shadow: 4px 4px 6px #ccc;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ui-dialog.ui-widget.ui-widget-content.ui-corner-all
|
||||
@@ -309,68 +333,41 @@ a:focus
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.ui-dialog.ui-widget.sbm-dialog-rounding-none.ui-widget-content.ui-corner-all
|
||||
{
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.ui-dialog.ui-widget .ui-widget-header
|
||||
{
|
||||
font-size: 1.1rem;
|
||||
color: #f0f0f0;
|
||||
background-color: #606060;
|
||||
border-color: var(--color-dialog);
|
||||
background-color: #606060;
|
||||
background-color: var(--color-dialog);
|
||||
color: #fff;
|
||||
background-color: #d4af37;
|
||||
border-color: #d4af37;
|
||||
}
|
||||
|
||||
.ui-dialog.ui-widget .ui-widget-header.ui-corner-all
|
||||
.ui-widget-content
|
||||
{
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.ui-dialog.sbm-dialog-titlebar-hide .ui-dialog-titlebar
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ui-dialog.dialog-no-close .ui-dialog-titlebar-close
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ui-dialog.ui-widget .ui-dialog-content.ui-widget-content
|
||||
{
|
||||
padding: 4px 8px 0 8px;
|
||||
font-size: 1rem;
|
||||
font-size: 16px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.ui-dialog .ui-dialog-buttonpane.ui-widget-content
|
||||
.ui-dialog .ui-dialog-titlebar.ui-corner-all
|
||||
{
|
||||
margin-top: 0;
|
||||
padding: 4px 16px 4px 16px;
|
||||
background-color: transparent;
|
||||
border-color: #606060;
|
||||
border-color: var(--color-dialog);
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset
|
||||
.ui-dialog.no-close .ui-dialog-titlebar-close
|
||||
{
|
||||
float: none;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
/*display: none;*/
|
||||
}
|
||||
|
||||
.ui-dialog .ui-dialog-buttonpane
|
||||
{
|
||||
border-color: #d4af37;
|
||||
}
|
||||
|
||||
.ui-dialog .ui-dialog-buttonpane button
|
||||
{
|
||||
min-width: 60px;
|
||||
padding: 6px 16px 4px 16px;
|
||||
color: #fff;
|
||||
background-color: #606060;
|
||||
background-color: var(--color-dialog);
|
||||
border-color: #606060;
|
||||
border-color: var(--color-dialog);
|
||||
background-color: #d4af37;
|
||||
border-color: #d4af37;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -388,8 +385,7 @@ a:focus
|
||||
.ui-datepicker .ui-datepicker-header
|
||||
{
|
||||
background-color: #fff;
|
||||
border: 1px solid #606060;
|
||||
border-color: var(--color-dialog);
|
||||
border: 1px solid #d4af37;
|
||||
}
|
||||
|
||||
/* The End */
|
||||
|
||||
|
After Width: | Height: | Size: 582 B |
|
After Width: | Height: | Size: 923 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 529 B |
|
After Width: | Height: | Size: 765 B |
|
After Width: | Height: | Size: 946 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 789 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 829 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 697 B |
|
After Width: | Height: | Size: 1011 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 604 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 594 B |
|
After Width: | Height: | Size: 859 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 479 B |
|
After Width: | Height: | Size: 675 B |
|
After Width: | Height: | Size: 577 B |
|
After Width: | Height: | Size: 724 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 498 B |
|
After Width: | Height: | Size: 688 B |
|
After Width: | Height: | Size: 605 B |
|
After Width: | Height: | Size: 601 B |
|
After Width: | Height: | Size: 904 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 555 B |
|
After Width: | Height: | Size: 824 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 461 B |
|
After Width: | Height: | Size: 590 B |
|
After Width: | Height: | Size: 578 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 460 B |
|
After Width: | Height: | Size: 579 B |
|
After Width: | Height: | Size: 547 B |
|
After Width: | Height: | Size: 709 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 654 B |
|
After Width: | Height: | Size: 962 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 635 B |
|
After Width: | Height: | Size: 983 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 603 B |
|
After Width: | Height: | Size: 905 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 600 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 527 B |