diff --git a/AllReadyApp/Web-App/AllReady.UnitTest/TagHelpers/ActiveRouteTagHelperShould.cs b/AllReadyApp/Web-App/AllReady.UnitTest/TagHelpers/ActiveRouteTagHelperShould.cs new file mode 100644 index 000000000..660259c65 --- /dev/null +++ b/AllReadyApp/Web-App/AllReady.UnitTest/TagHelpers/ActiveRouteTagHelperShould.cs @@ -0,0 +1,189 @@ +using AllReady.TagHelpers; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Razor.TagHelpers; +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Xunit; + +namespace AllReady.UnitTest.TagHelpers +{ + public class ActiveRouteTagHelperShould + { + private const string controllerName = "Home"; + private const string actionName = "List"; + private Dictionary routeValue = new Dictionary { { "Id", "2" } }; + private const string page = "/index"; + + private const string notMatchingControllerName = "HomeX"; + private const string notMatchingActionName = "Index"; + private Dictionary notMathingRouteValue = new Dictionary { { "Id", "3" } }; + private const string notMatchingPage = "/index2"; + + private Func> GetEmptyChildContent() + { + TagHelperContent content = new DefaultTagHelperContent(); + return (b, encoder) => Task.FromResult(content); + } + + private TagHelperContext GetContext() + { + return new TagHelperContext(new TagHelperAttributeList(), new Dictionary(), Guid.NewGuid().ToString()); + } + + private TagHelperOutput GetOutput() + { + return new TagHelperOutput("time", new TagHelperAttributeList(), GetEmptyChildContent()); + } + + private ViewContext GetViewContextMVC() + { + var viewContext = new ViewContext(); + viewContext.RouteData = new Microsoft.AspNetCore.Routing.RouteData(); + viewContext.RouteData.Values.Add("Controller", controllerName); + viewContext.RouteData.Values.Add("Action", actionName); + viewContext.RouteData.Values.Add("Id", "2"); + return viewContext; + } + + private ViewContext GetViewContextPage() + { + var viewContext = new ViewContext(); + viewContext.RouteData = new Microsoft.AspNetCore.Routing.RouteData(); + viewContext.RouteData.Values.Add("page", page); + return viewContext; + } + + [Fact] + public void NotHaveClassAttributeIfNoClassPassedInAndIfNoParametersPassedIn() + { + ActiveRouteTagHelper tagHelper = new ActiveRouteTagHelper + { + ViewContext = GetViewContextMVC() + }; + var output = GetOutput(); + tagHelper.Process(GetContext(), output); + Assert.Equal(0, output.Attributes.Count); + } + + [Fact] + public void HaveActiveClassAttributeWhenControllerMatch() + { + ActiveRouteTagHelper tagHelper = new ActiveRouteTagHelper + { + ViewContext = GetViewContextMVC(), + Controller = controllerName + }; + var output = GetOutput(); + tagHelper.Process(GetContext(), output); + Assert.Equal(1, output.Attributes.Count); + Assert.Equal("class", output.Attributes[0].Name); + Assert.Equal("active", output.Attributes[0].Value); + } + + [Fact] + public void NotHaveClassAttributeWhenControllerNotMatching() + { + ActiveRouteTagHelper tagHelper = new ActiveRouteTagHelper + { + ViewContext = GetViewContextMVC(), + Controller = notMatchingControllerName + }; + var output = GetOutput(); + tagHelper.Process(GetContext(), output); + Assert.Equal(0, output.Attributes.Count); + } + + [Fact] + public void HaveActiveClassAttributeWhenActionMatch() + { + ActiveRouteTagHelper tagHelper = new ActiveRouteTagHelper + { + ViewContext = GetViewContextMVC(), + Controller = controllerName, + Action = actionName + }; + var output = GetOutput(); + tagHelper.Process(GetContext(), output); + Assert.Equal(1, output.Attributes.Count); + Assert.Equal("class", output.Attributes[0].Name); + Assert.Equal("active", output.Attributes[0].Value); + } + + [Fact] + public void NotHaveClassAttributeWhenActionNotMatching() + { + ActiveRouteTagHelper tagHelper = new ActiveRouteTagHelper + { + ViewContext = GetViewContextMVC(), + Controller = controllerName, + Action = notMatchingActionName + }; + var output = GetOutput(); + tagHelper.Process(GetContext(), output); + Assert.Equal(0, output.Attributes.Count); + } + + [Fact] + public void HaveActiveClassAttributeWhenRouteDataMatch() + { + ActiveRouteTagHelper tagHelper = new ActiveRouteTagHelper + { + ViewContext = GetViewContextMVC(), + Controller = controllerName, + Action = actionName, + RouteValues = routeValue + }; + var output = GetOutput(); + tagHelper.Process(GetContext(), output); + Assert.Equal(1, output.Attributes.Count); + Assert.Equal("class", output.Attributes[0].Name); + Assert.Equal("active", output.Attributes[0].Value); + } + + [Fact] + public void NotHaveClassAttributeWhenRouteDataNotMatching() + { + ActiveRouteTagHelper tagHelper = new ActiveRouteTagHelper + { + ViewContext = GetViewContextMVC(), + Controller = controllerName, + Action = actionName, + RouteValues = notMathingRouteValue + }; + var output = GetOutput(); + tagHelper.Process(GetContext(), output); + Assert.Equal(0, output.Attributes.Count); + } + + [Fact] + public void HaveActiveClassAttributePageMatch() + { + ActiveRouteTagHelper tagHelper = new ActiveRouteTagHelper + { + ViewContext = GetViewContextPage(), + Page = page + }; + var output = GetOutput(); + tagHelper.Process(GetContext(), output); + Assert.Equal(1, output.Attributes.Count); + Assert.Equal("class", output.Attributes[0].Name); + Assert.Equal("active", output.Attributes[0].Value); + } + + [Fact] + public void NotHaveClassAttributeWhenPageNotMatching() + { + ActiveRouteTagHelper tagHelper = new ActiveRouteTagHelper + { + ViewContext = GetViewContextPage(), + Page = notMatchingPage + }; + var output = GetOutput(); + tagHelper.Process(GetContext(), output); + Assert.Equal(0, output.Attributes.Count); + } + } +} diff --git a/AllReadyApp/Web-App/AllReady/TagHelpers/ActiveRouteTagHelper.cs b/AllReadyApp/Web-App/AllReady/TagHelpers/ActiveRouteTagHelper.cs new file mode 100644 index 000000000..6a9fa89a7 --- /dev/null +++ b/AllReadyApp/Web-App/AllReady/TagHelpers/ActiveRouteTagHelper.cs @@ -0,0 +1,143 @@ +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace AllReady.TagHelpers +{ + [HtmlTargetElement(Attributes = "is-active-route")] + public class ActiveRouteTagHelper : TagHelper + { + private IDictionary _routeValues; + + /// + /// Name of the action method + /// + [HtmlAttributeName("asp-action")] + public string Action { get; set; } + + /// + /// Name of the controller + /// + [HtmlAttributeName("asp-controller")] + public string Controller { get; set; } + + /// + /// Name of the razor page (e.g. /index) + /// + [HtmlAttributeName("asp-page")] + public string Page { get; set; } + + /// + /// Additional route parameters + /// + [HtmlAttributeName("asp-all-route-data", DictionaryAttributePrefix = "asp-route-")] + public IDictionary RouteValues + { + get + { + if (_routeValues == null) + _routeValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + return _routeValues; + } + set + { + _routeValues = value; + } + } + + /// + /// The ViewContext + /// + [HtmlAttributeNotBound] + [ViewContext] + public ViewContext ViewContext { get; set; } + + public override void Process(TagHelperContext context, TagHelperOutput output) + { + base.Process(context, output); + + if (ShouldBeActive()) + { + MakeActive(output); + } + + output.Attributes.RemoveAll("is-active-route"); + } + + private bool ShouldBeActive() + { + if (ViewContext.RouteData.Values.ContainsKey("Controller")) + { + return ShouldBeActieMvc(); + } + else if (ViewContext.RouteData.Values.ContainsKey("page")) + { + return ShouldBeActiveRazorPage(); + } + return true; + } + + private bool ShouldBeActieMvc() + { + if (string.IsNullOrWhiteSpace(Controller) && + string.IsNullOrWhiteSpace(Action) && + !RouteValues.Any(r => !ViewContext.RouteData.Values.ContainsKey(r.Key))) + { + return false; + } + + string currentController = ViewContext.RouteData.Values["Controller"].ToString(); + string currentAction = ViewContext.RouteData.Values["Action"].ToString(); + + if (!string.IsNullOrWhiteSpace(Controller) && Controller.ToLower() != currentController.ToLower()) + { + return false; + } + + if (!string.IsNullOrWhiteSpace(Action) && Action.ToLower() != currentAction.ToLower()) + { + return false; + } + + foreach (var routeValue in RouteValues) + { + if (!ViewContext.RouteData.Values.ContainsKey(routeValue.Key) || + ViewContext.RouteData.Values[routeValue.Key].ToString() != routeValue.Value) + { + return false; + } + } + return true; + } + + private bool ShouldBeActiveRazorPage() + { + string currentPage = ViewContext.RouteData.Values["Page"].ToString(); + + if (string.IsNullOrWhiteSpace(Page) || Page.ToLower() != currentPage.ToLower()) + { + return false; + } + return true; + } + + private void MakeActive(TagHelperOutput output) + { + var classAttr = output.Attributes.FirstOrDefault(a => a.Name == "class"); + if (classAttr == null) + { + classAttr = new TagHelperAttribute("class", "active"); + output.Attributes.Add(classAttr); + } + else if (classAttr.Value == null || classAttr.Value.ToString().IndexOf("active") < 0) + { + output.Attributes.SetAttribute("class", classAttr.Value == null + ? "active" + : classAttr.Value.ToString() + " active"); + } + } + } +} diff --git a/AllReadyApp/Web-App/AllReady/Views/Shared/_AdminNavigationPartial.cshtml b/AllReadyApp/Web-App/AllReady/Views/Shared/_AdminNavigationPartial.cshtml index 845b194c9..b99a3a95f 100644 --- a/AllReadyApp/Web-App/AllReady/Views/Shared/_AdminNavigationPartial.cshtml +++ b/AllReadyApp/Web-App/AllReady/Views/Shared/_AdminNavigationPartial.cshtml @@ -14,21 +14,21 @@ } diff --git a/AllReadyApp/Web-App/AllReady/appsettings.json b/AllReadyApp/Web-App/AllReady/appsettings.json index b717d4530..80bb00ff6 100644 --- a/AllReadyApp/Web-App/AllReady/appsettings.json +++ b/AllReadyApp/Web-App/AllReady/appsettings.json @@ -1,95 +1,95 @@ -{ - // Note: You should --=NOT=-- put sensitive information into this file. - // This file should only be used as placeholders for keys that - // can be resolved via UserSecrets or environment variables. - // http://docs.asp.net/en/latest/security/app-secrets.html#installing-the-secret-manager-tool - "SampleData": { - "DefaultAdminUsername": "Administrator@example.com", - "DefaultAdminPassword": "YouShouldChangeThisPassword1!", - "DefaultUsername": "User@example.com", - "DefaultOrganizationUsername": "organization@example.com", - "DefaultEventManagerUsername": "eventmanager@example.com", - "DefaultCampaignManagerUsername": "campaignmanager@example.com", - "DefaultFromEmailAddress": "Administrator@example.com", - "DefaultFromDisplayName": "allReady.com Administrator", - "InsertSampleData": "true", - "InsertTestUsers": "true" - }, - "General": { - "SiteBaseUrl": "http://localhost:48408/", - "DefaultTimeZone": "Central Standard Time", - "MaxImageSizeInBytes": 1048576 - }, - "GetASmokeAlarmApiSettings": { - "BaseAddress": "https://demo.getasmokealarm.org/", - "Token": "[getasmokealarmapisettingstoken]" - }, - "ApplicationInsights": { - "InstrumentationKey": "[instrumentationkey]" - }, - "Mapping": { - "EnableGoogleGeocodingService": "false", - "GoogleMapsApiKey": "[googlemapsapikey]" - }, - "Data": { - "DefaultConnection": { - "ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=AllReady;Integrated Security=true;MultipleActiveResultsets=true;" - }, - "HangfireConnection": { - "ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=AllReady;Integrated Security=true;MultipleActiveResultsets=true;" - }, - "Storage": { - "AzureStorage": "[storagekey]", - "EnableAzureQueueService": "false", - "EnableAzureBlobImageService": "false" - } - }, - "Email": { - "EmailFolder": "..." - }, +{ + // Note: You should --=NOT=-- put sensitive information into this file. + // This file should only be used as placeholders for keys that + // can be resolved via UserSecrets or environment variables. + // http://docs.asp.net/en/latest/security/app-secrets.html#installing-the-secret-manager-tool + "SampleData": { + "DefaultAdminUsername": "Administrator@example.com", + "DefaultAdminPassword": "YouShouldChangeThisPassword1!", + "DefaultUsername": "User@example.com", + "DefaultOrganizationUsername": "organization@example.com", + "DefaultEventManagerUsername": "eventmanager@example.com", + "DefaultCampaignManagerUsername": "campaignmanager@example.com", + "DefaultFromEmailAddress": "Administrator@example.com", + "DefaultFromDisplayName": "allReady.com Administrator", + "InsertSampleData": "true", + "InsertTestUsers": "true" + }, + "General": { + "SiteBaseUrl": "http://localhost:48408/", + "DefaultTimeZone": "Central Standard Time", + "MaxImageSizeInBytes": 1048576 + }, + "GetASmokeAlarmApiSettings": { + "BaseAddress": "https://demo.getasmokealarm.org/", + "Token": "[getasmokealarmapisettingstoken]" + }, + "ApplicationInsights": { + "InstrumentationKey": "[instrumentationkey]" + }, + "Mapping": { + "EnableGoogleGeocodingService": "false", + "GoogleMapsApiKey": "[googlemapsapikey]" + }, + "Data": { + "DefaultConnection": { + "ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=AllReady;Integrated Security=true;MultipleActiveResultsets=true;" + }, + "HangfireConnection": { + "ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=AllReady;Integrated Security=true;MultipleActiveResultsets=true;" + }, + "Storage": { + "AzureStorage": "[storagekey]", + "EnableAzureQueueService": "false", + "EnableAzureBlobImageService": "false" + } + }, + "Email": { + "EmailFolder": "..." + }, "Authentication": { - "MaxFailedAccessAttempts": 5, - "Facebook": { - "AppId": "[facebookappId]", - "AppSecret": "[facebookappsecret]" - }, - "Twitter": { - "ConsumerKey": "[twitterconsumerkey]", - "ConsumerSecret": "[twitterconsumersecret]" - }, - "MicrosoftAccount": { - "ClientId": "[microsoftclientid]", - "ClientSecret": "[microsoftclientsecret]" - }, - "Google": { - "ClientId": "[googleclientid]", - "ClientSecret": "[googleclientsecret]" - }, - "SendGrid": { - "UserName": "[sendgriduser]", - "Password": "[sendgridpassword]", - "FromEmail": "[sendgridemailsender]" - }, - "Twilio": { - "EnableTwilio": "false", - "Sid": "[twiliosid]", - "Token": "[twiliotoken]", - "PhoneNo": "[twilionumber]" - } - }, - "ApprovedRegions": { - "Enabled": true, - "Regions": [ - "IDMT", - "NDSD", - "KSNE", - "MINN", - "IOWA", - "WEMO", - "EAMO", - "WISC", - "CHNI", - "CSIL" - ] - } -} + "MaxFailedAccessAttempts": 5, + "Facebook": { + "AppId": "[facebookappId]", + "AppSecret": "[facebookappsecret]" + }, + "Twitter": { + "ConsumerKey": "[twitterconsumerkey]", + "ConsumerSecret": "[twitterconsumersecret]" + }, + "MicrosoftAccount": { + "ClientId": "[microsoftclientid]", + "ClientSecret": "[microsoftclientsecret]" + }, + "Google": { + "ClientId": "[googleclientid]", + "ClientSecret": "[googleclientsecret]" + }, + "SendGrid": { + "UserName": "[sendgriduser]", + "Password": "[sendgridpassword]", + "FromEmail": "[sendgridemailsender]" + }, + "Twilio": { + "EnableTwilio": "false", + "Sid": "[twiliosid]", + "Token": "[twiliotoken]", + "PhoneNo": "[twilionumber]" + } + }, + "ApprovedRegions": { + "Enabled": true, + "Regions": [ + "IDMT", + "NDSD", + "KSNE", + "MINN", + "IOWA", + "WEMO", + "EAMO", + "WISC", + "CHNI", + "CSIL" + ] + } +} diff --git a/AllReadyApp/Web-App/AllReady/wwwroot/css/site.css b/AllReadyApp/Web-App/AllReady/wwwroot/css/site.css index 4f674ef82..483d8b600 100644 --- a/AllReadyApp/Web-App/AllReady/wwwroot/css/site.css +++ b/AllReadyApp/Web-App/AllReady/wwwroot/css/site.css @@ -306,6 +306,7 @@ Navigation .navbar-nav > li > .dropdown-menu > li.active > a { font-weight: bold; + background-color: #cb7333; } .navbar-nav > li > .dropdown-menu > li:hover, diff --git a/docs/sample_campaign/data for site.txt b/docs/sample_campaign/data for site.txt new file mode 100644 index 000000000..3996a7e69 --- /dev/null +++ b/docs/sample_campaign/data for site.txt @@ -0,0 +1,17 @@ + + + +Flood Preparedness + +Spreading the word about the local risks of flooding + +Be Water Wise, Get Out In Time + +We live in an area where over 10,000 residents live in the flood plain and are at risk of being in the way of flood waters. Flooding can cause property and vehicle damage, put life at risk and put a strain on critical emergency services that could be used elsewhere. + +Help us spread the word about being prepared, having a plan and getting out of the way of the floods safely. + + +https://ourcommunitysite.com/floodpreparedness + +Our Community Flood Preparedness Site