├── .github └── dependabot.yml ├── .gitignore ├── AirTNG.Web.Tests ├── AirTNG.Web.Tests.csproj ├── Controllers │ ├── ReservationsControllerTest.cs │ └── VacationPropertiesControllerTest.cs ├── Domain │ └── Reservations │ │ └── NotifierTest.cs ├── Extensions │ └── ControllerResultTestExtensions.cs ├── Properties │ └── AssemblyInfo.cs ├── app.config └── packages.config ├── AirTNG.Web ├── AirTNG.Web.csproj ├── App_Data │ └── .gitkeep ├── App_Start │ ├── BundleConfig.cs │ ├── FilterConfig.cs │ ├── IdentityConfig.cs │ ├── RouteConfig.cs │ └── Startup.Auth.cs ├── Content │ ├── Images │ │ ├── airtng-logo.png │ │ ├── spock.png │ │ └── tngbg.jpg │ ├── Site.css │ ├── bootstrap-theme.css │ ├── bootstrap-theme.css.map │ ├── bootstrap-theme.min.css │ ├── bootstrap.css │ ├── bootstrap.css.map │ ├── bootstrap.min.css │ ├── font-awesome.css │ ├── font-awesome.min.css │ ├── main.css │ ├── scaffolds.css │ └── vacation_properties.css ├── Controllers │ ├── AccountController.cs │ ├── HomeController.cs │ ├── ManageController.cs │ ├── ReservationsController.cs │ └── VacationPropertiesController.cs ├── Domain │ ├── Reservations │ │ ├── Notification.cs │ │ ├── Notifier.cs │ │ └── TwilioMessageSender.cs │ └── Twilio │ │ └── Configuration.cs ├── Global.asax ├── Global.asax.cs ├── Migrations │ ├── 201510281842434_CreateDatabase.Designer.cs │ ├── 201510281842434_CreateDatabase.cs │ ├── 201510281842434_CreateDatabase.resx │ ├── 201510282125527_AddNameToUsers.Designer.cs │ ├── 201510282125527_AddNameToUsers.cs │ ├── 201510282125527_AddNameToUsers.resx │ ├── 201510301945368_CreateReservations.Designer.cs │ ├── 201510301945368_CreateReservations.cs │ ├── 201510301945368_CreateReservations.resx │ ├── 201510301956088_CreateVacationProperties.Designer.cs │ ├── 201510301956088_CreateVacationProperties.cs │ ├── 201510301956088_CreateVacationProperties.resx │ └── Configuration.cs ├── Models │ ├── AccountViewModels.cs │ ├── IdentityModels.cs │ ├── ManageViewModels.cs │ ├── Repository │ │ ├── ReservationsRepository.cs │ │ ├── UsersRepository.cs │ │ └── VacationPropertiesRepository.cs │ ├── Reservation.cs │ ├── ReservationStatus.cs │ └── VacationProperty.cs ├── Properties │ └── AssemblyInfo.cs ├── Scripts │ ├── _references.js │ ├── bootstrap.js │ ├── bootstrap.min.js │ ├── jquery-1.10.2.intellisense.js │ ├── jquery-2.1.4-vsdoc.js │ ├── jquery-2.1.4.js │ ├── jquery-2.1.4.min.js │ ├── jquery-2.1.4.min.map │ ├── jquery.validate-vsdoc.js │ ├── jquery.validate.js │ ├── jquery.validate.min.js │ ├── jquery.validate.unobtrusive.js │ ├── jquery.validate.unobtrusive.min.js │ ├── modernizr-2.8.3.js │ ├── respond.js │ ├── respond.matchmedia.addListener.js │ ├── respond.matchmedia.addListener.min.js │ └── respond.min.js ├── Startup.cs ├── ViewModels │ ├── ReservationViewModel.cs │ └── VacationPropertyViewModel.cs ├── Views │ ├── Account │ │ ├── ConfirmEmail.cshtml │ │ ├── ExternalLoginConfirmation.cshtml │ │ ├── ExternalLoginFailure.cshtml │ │ ├── ForgotPassword.cshtml │ │ ├── ForgotPasswordConfirmation.cshtml │ │ ├── Login.cshtml │ │ ├── Register.cshtml │ │ ├── ResetPassword.cshtml │ │ ├── ResetPasswordConfirmation.cshtml │ │ ├── SendCode.cshtml │ │ ├── VerifyCode.cshtml │ │ └── _ExternalLoginsListPartial.cshtml │ ├── Home │ │ └── Index.cshtml │ ├── Manage │ │ ├── AddPhoneNumber.cshtml │ │ ├── ChangePassword.cshtml │ │ ├── Index.cshtml │ │ ├── ManageLogins.cshtml │ │ ├── SetPassword.cshtml │ │ └── VerifyPhoneNumber.cshtml │ ├── Reservations │ │ └── Create.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ ├── Lockout.cshtml │ │ ├── _Layout.cshtml │ │ └── _LoginPartial.cshtml │ ├── VacationProperties │ │ ├── Create.cshtml │ │ ├── Edit.cshtml │ │ ├── Index.cshtml │ │ └── _Form.cshtml │ ├── Web.config │ └── _ViewStart.cshtml ├── Web.Debug.config ├── Web.Release.config ├── Web.config ├── favicon.ico ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── fontawesome-webfont.woff2 │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 └── packages.config ├── AirTNG.sln ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── appveyor.yml └── webhook.png /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/AirTNG.Web.Tests" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: Twilio 10 | versions: 11 | - 5.53.1 12 | - 5.54.0 13 | - 5.55.0 14 | - 5.56.0 15 | - 5.57.0 16 | - 5.58.0 17 | - dependency-name: NUnit 18 | versions: 19 | - 3.13.0 20 | - 3.13.1 21 | - dependency-name: Newtonsoft.Json 22 | versions: 23 | - 12.0.3 24 | - package-ecosystem: nuget 25 | directory: "/AirTNG.Web" 26 | schedule: 27 | interval: daily 28 | open-pull-requests-limit: 10 29 | ignore: 30 | - dependency-name: System.IdentityModel.Tokens.Jwt 31 | versions: 32 | - 6.10.0 33 | - 6.10.1 34 | - 6.10.2 35 | - 6.8.0 36 | - 6.9.0 37 | - dependency-name: Twilio 38 | versions: 39 | - 5.53.1 40 | - 5.54.0 41 | - 5.55.0 42 | - 5.56.0 43 | - 5.57.0 44 | - 5.58.0 45 | - dependency-name: jQuery 46 | versions: 47 | - 3.5.1 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Copied from: https://github.com/github/gitignore/blob/1587f288ee3c5132ad5e14be232d9e5784209286/VisualStudio.gitignore 2 | ## Ignore Visual Studio temporary files, build results, and 3 | ## files generated by popular Visual Studio add-ons. 4 | 5 | # User-specific files 6 | *.suo 7 | *.user 8 | *.userosscache 9 | *.sln.docstates 10 | 11 | # User-specific files (MonoDevelop/Xamarin Studio) 12 | *.userprefs 13 | 14 | # Build results 15 | [Dd]ebug/ 16 | [Dd]ebugPublic/ 17 | [Rr]elease/ 18 | [Rr]eleases/ 19 | x64/ 20 | x86/ 21 | build/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | 26 | # Visual Studio 2015 cache/options directory 27 | .vs/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opensdf 79 | *.sdf 80 | *.cachefile 81 | 82 | # Visual Studio profiler 83 | *.psess 84 | *.vsp 85 | *.vspx 86 | 87 | # TFS 2012 Local Workspace 88 | $tf/ 89 | 90 | # Guidance Automation Toolkit 91 | *.gpState 92 | 93 | # ReSharper is a .NET coding add-in 94 | _ReSharper*/ 95 | *.[Rr]e[Ss]harper 96 | *.DotSettings.user 97 | 98 | # JustCode is a .NET coding add-in 99 | .JustCode 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | _NCrunch_* 109 | .*crunch*.local.xml 110 | 111 | # MightyMoose 112 | *.mm.* 113 | AutoTest.Net/ 114 | 115 | # Web workbench (sass) 116 | .sass-cache/ 117 | 118 | # Installshield output folder 119 | [Ee]xpress/ 120 | 121 | # DocProject is a documentation generator add-in 122 | DocProject/buildhelp/ 123 | DocProject/Help/*.HxT 124 | DocProject/Help/*.HxC 125 | DocProject/Help/*.hhc 126 | DocProject/Help/*.hhk 127 | DocProject/Help/*.hhp 128 | DocProject/Help/Html2 129 | DocProject/Help/html 130 | 131 | # Click-Once directory 132 | publish/ 133 | 134 | # Publish Web Output 135 | *.[Pp]ublish.xml 136 | *.azurePubxml 137 | # TODO: Comment the next line if you want to checkin your web deploy settings 138 | # but database connection strings (with potential passwords) will be unencrypted 139 | *.pubxml 140 | *.publishproj 141 | 142 | # NuGet Packages 143 | *.nupkg 144 | # The packages folder can be ignored because of Package Restore 145 | **/packages/* 146 | # except build/, which is used as an MSBuild target. 147 | !**/packages/build/ 148 | # Uncomment if necessary however generally it will be regenerated when needed 149 | #!**/packages/repositories.config 150 | 151 | # Windows Azure Build Output 152 | csx/ 153 | *.build.csdef 154 | 155 | # Windows Store app package directory 156 | AppPackages/ 157 | 158 | # Visual Studio cache files 159 | # files ending in .cache can be ignored 160 | *.[Cc]ache 161 | # but keep track of directories ending in .cache 162 | !*.[Cc]ache/ 163 | 164 | # Others 165 | ClientBin/ 166 | [Ss]tyle[Cc]op.* 167 | ~$* 168 | *~ 169 | *.dbmdl 170 | *.dbproj.schemaview 171 | *.pfx 172 | *.publishsettings 173 | node_modules/ 174 | orleans.codegen.cs 175 | 176 | # RIA/Silverlight projects 177 | Generated_Code/ 178 | 179 | # Backup & report files from converting an old project file 180 | # to a newer Visual Studio version. Backup files are not needed, 181 | # because we have git ;-) 182 | _UpgradeReport_Files/ 183 | Backup*/ 184 | UpgradeLog*.XML 185 | UpgradeLog*.htm 186 | 187 | # SQL Server files 188 | *.mdf 189 | *.ldf 190 | 191 | # Business Intelligence projects 192 | *.rdl.data 193 | *.bim.layout 194 | *.bim_*.settings 195 | 196 | # Microsoft Fakes 197 | FakesAssemblies/ 198 | 199 | # Node.js Tools for Visual Studio 200 | .ntvs_analysis.dat 201 | 202 | # Visual Studio 6 build log 203 | *.plg 204 | 205 | # Visual Studio 6 workspace options fPile 206 | *.opt 207 | 208 | # Project 209 | AirTNG.Web/Local.config 210 | -------------------------------------------------------------------------------- /AirTNG.Web.Tests/Controllers/ReservationsControllerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Xml.XPath; 3 | using AirTNG.Web.Controllers; 4 | using AirTNG.Web.Domain.Reservations; 5 | using AirTNG.Web.Models; 6 | using AirTNG.Web.Models.Repository; 7 | using AirTNG.Web.Tests.Extensions; 8 | using AirTNG.Web.ViewModels; 9 | using Moq; 10 | using NUnit.Framework; 11 | using TestStack.FluentMVCTesting; 12 | 13 | // ReSharper disable PossibleNullReferenceException 14 | 15 | namespace AirTNG.Web.Tests.Controllers 16 | { 17 | public class ReservationsControllerTest 18 | { 19 | [Test] 20 | public void GivenACreateAction_ThenRendersTheDefaultView() 21 | { 22 | var vacationProperty = new VacationProperty {User = new ApplicationUser()}; 23 | var mockVacationsRepository = new Mock(); 24 | mockVacationsRepository.Setup(r => r.FindAsync(It.IsAny())).ReturnsAsync(vacationProperty); 25 | var stubReservationsRepository = Mock.Of(); 26 | var stubUsersRepository = Mock.Of(); 27 | var stubNotifier = Mock.Of(); 28 | var controller = new ReservationsController( 29 | mockVacationsRepository.Object, stubReservationsRepository, stubUsersRepository, stubNotifier); 30 | controller.WithCallTo(c => c.Create(1)) 31 | .ShouldRenderDefaultView(); 32 | } 33 | 34 | [Test] 35 | public void GivenACreateAction_WhenTheModelStateIsValid_ThenItRedirectsToVacationPropertiesIndex() 36 | { 37 | var model = new ReservationViewModel(); 38 | 39 | var stubVacationPropertiesRepository = Mock.Of(); 40 | var mockReservationsRepository = new Mock(); 41 | var stubUsersRepository = Mock.Of(); 42 | var mockNotifier = new Mock(); 43 | 44 | var controller = new ReservationsController( 45 | stubVacationPropertiesRepository, mockReservationsRepository.Object, stubUsersRepository, 46 | mockNotifier.Object); 47 | 48 | controller.WithCallTo(c => c.Create(model)) 49 | .ShouldRedirectTo(c => c.Index()); 50 | 51 | mockReservationsRepository.Verify(r => r.CreateAsync(It.IsAny()), Times.Once); 52 | mockNotifier.Verify(n => n.SendNotificationAsync(It.IsAny()), Times.Once()); 53 | } 54 | 55 | [Test] 56 | public void GivenACreateAction_WhenTheModelStateIsInalid_ThenRenderTheDefaultView() 57 | { 58 | var model = new ReservationViewModel(); 59 | var stubVacationPropertiesRepository = Mock.Of(); 60 | var stubReservationsRepository = Mock.Of(); 61 | var stubUsersRepository = Mock.Of(); 62 | var stubNotifier = Mock.Of(); 63 | 64 | var controller = new ReservationsController( 65 | stubVacationPropertiesRepository, stubReservationsRepository, stubUsersRepository, stubNotifier); 66 | controller.ModelState.AddModelError("Message", "The Message field is required"); 67 | 68 | controller.WithCallTo(c => c.Create(model)) 69 | .ShouldRenderDefaultView(); 70 | } 71 | 72 | [TestCase("yes", "Confirmed")] 73 | [TestCase("no", "Rejected")] 74 | public void GivenAHandleAction_WhenThereAreAPendingReservation_AndTheUserRespondsYesOrNo_ThenRespondWithReservationStatus( 75 | string smsRequestBody, string expectedMessage) 76 | { 77 | var host = new ApplicationUser {Id = "user-id"}; 78 | var stubVacationPropertiesRepository = Mock.Of(); 79 | var mockUsersRepository = new Mock(); 80 | var mockReservationsRepository = new Mock(); 81 | mockReservationsRepository 82 | .Setup(r => r.FindFirstPendingReservationByHostAsync(host.Id)) 83 | .ReturnsAsync(new Reservation()); 84 | mockUsersRepository 85 | .Setup(r => r.FindByPhoneNumberAsync(It.IsAny())) 86 | .ReturnsAsync(host); 87 | var stubNotifier = Mock.Of(); 88 | 89 | var controller = new ReservationsController( 90 | stubVacationPropertiesRepository, 91 | mockReservationsRepository.Object, 92 | mockUsersRepository.Object, 93 | stubNotifier); 94 | 95 | controller.WithCallTo(c => c.Handle("from-number", smsRequestBody)) 96 | .ShouldReturnTwiMLResult(data => 97 | { 98 | StringAssert.Contains(expectedMessage, data.XPathSelectElement("Response/Message").Value); 99 | }); 100 | } 101 | 102 | [Test] 103 | public void GivenAHandleAction_WhenThereAreNoPendingReservations_TheResponseContainsSorryMessage() 104 | { 105 | var host = new ApplicationUser {Id = "user-id"}; 106 | var stubVacationPropertiesRepository = Mock.Of(); 107 | var mockUsersRepository = new Mock(); 108 | var mockReservationsRepository = new Mock(); 109 | mockReservationsRepository 110 | .Setup(r => r.FindFirstPendingReservationByHostAsync(host.Id)) 111 | .ThrowsAsync(new InvalidOperationException()); // There are no reservations 112 | mockUsersRepository 113 | .Setup(r => r.FindByPhoneNumberAsync(It.IsAny())) 114 | .ReturnsAsync(host); 115 | var stubNotifier = Mock.Of(); 116 | 117 | var controller = new ReservationsController( 118 | stubVacationPropertiesRepository, 119 | mockReservationsRepository.Object, 120 | mockUsersRepository.Object, 121 | stubNotifier); 122 | 123 | controller.WithCallTo(c => c.Handle("from-number", "yes")) 124 | .ShouldReturnTwiMLResult(data => 125 | { 126 | StringAssert.Contains("Sorry", data.XPathSelectElement("Response/Message").Value); 127 | }); 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /AirTNG.Web.Tests/Controllers/VacationPropertiesControllerTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using AirTNG.Web.Controllers; 3 | using AirTNG.Web.Models; 4 | using AirTNG.Web.Models.Repository; 5 | using AirTNG.Web.ViewModels; 6 | using Moq; 7 | using NUnit.Framework; 8 | using TestStack.FluentMVCTesting; 9 | 10 | namespace AirTNG.Web.Tests.Controllers 11 | { 12 | public class VacationPropertiesControllerTest 13 | { 14 | [Test] 15 | public void GivenAnIndexAction_ThenRendersTheDefaultView() 16 | { 17 | var properties = new List 18 | { 19 | new VacationProperty {Description = "bob's property"} 20 | }; 21 | 22 | var mockRepository = new Mock(); 23 | mockRepository.Setup(r => r.AllAsync()).ReturnsAsync(properties); 24 | 25 | var controller = new VacationPropertiesController(mockRepository.Object); 26 | controller.WithCallTo(c => c.Index()) 27 | .ShouldRenderDefaultView() 28 | .WithModel(properties); 29 | } 30 | 31 | [Test] 32 | public void GivenACreateAction_ThenRendersTheDefaultView() 33 | { 34 | var stubRepository = Mock.Of(); 35 | var controller = new VacationPropertiesController(stubRepository); 36 | controller.WithCallTo(c => c.Create()) 37 | .ShouldRenderDefaultView(); 38 | } 39 | 40 | [Test] 41 | public void GivenACreateAction_WhenTheModelStateIsValid_ThenItRedirectsToIndex() 42 | { 43 | var model = new VacationPropertyViewModel(); 44 | var mockRepository = new Mock(); 45 | mockRepository.Setup(r => r.CreateAsync(It.IsAny())).ReturnsAsync(1); 46 | 47 | var controller = new VacationPropertiesController(mockRepository.Object) 48 | { 49 | UserId = () => "bob-id" 50 | }; 51 | 52 | controller.WithCallTo(c => c.Create(model)) 53 | .ShouldRedirectTo(c => c.Index()); 54 | 55 | mockRepository.Verify(r => r.CreateAsync(It.IsAny()), Times.Once); 56 | } 57 | 58 | [Test] 59 | public void GivenACreateAction_WhenTheModelStateIsInalid_ThenRenderTheDefaultView() 60 | { 61 | var model = new VacationPropertyViewModel(); 62 | var stubRepository = Mock.Of(); 63 | 64 | var controller = new VacationPropertiesController(stubRepository); 65 | controller.ModelState.AddModelError("Description", "The Description field is required"); 66 | controller.WithCallTo(c => c.Create(model)) 67 | .ShouldRenderDefaultView(); 68 | } 69 | 70 | [Test] 71 | public void GivenAnEditAction_ThenRendersTheDefaultView() 72 | { 73 | var vacationProperty = new VacationProperty(); 74 | var mockRepository = new Mock(); 75 | mockRepository.Setup(r => r.FindAsync(It.IsAny())).ReturnsAsync(vacationProperty); 76 | 77 | var controller = new VacationPropertiesController(mockRepository.Object); 78 | controller.WithCallTo(c => c.Edit(1)) 79 | .ShouldRenderDefaultView() 80 | .WithModel(vacationProperty); 81 | } 82 | 83 | [Test] 84 | public void GivenAnEditAction_WhenTheModelStateIsValid_ThenItRedirectsToIndex() 85 | { 86 | var model = new VacationPropertyViewModel(); 87 | var vacationProperty = new VacationProperty(); 88 | 89 | var mockRepository = new Mock(); 90 | mockRepository.Setup(r => r.FindAsync(It.IsAny())).ReturnsAsync(vacationProperty); 91 | mockRepository.Setup(r => r.UpdateAsync(It.IsAny())).ReturnsAsync(1); 92 | 93 | var controller = new VacationPropertiesController(mockRepository.Object); 94 | 95 | controller.WithCallTo(c => c.Edit(model)) 96 | .ShouldRedirectTo(c => c.Index()); 97 | 98 | mockRepository.Verify(r => r.UpdateAsync(It.IsAny()), Times.Once); 99 | } 100 | 101 | [Test] 102 | public void GivenAnEditAction_WhenTheModelStateIsInalid_ThenRenderTheDefaultView() 103 | { 104 | var model = new VacationPropertyViewModel(); 105 | var stubRepository = Mock.Of(); 106 | 107 | var controller = new VacationPropertiesController(stubRepository); 108 | controller.ModelState.AddModelError("Description", "The Description field is required"); 109 | 110 | controller.WithCallTo(c => c.Edit(model)) 111 | .ShouldRenderDefaultView(); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /AirTNG.Web.Tests/Domain/Reservations/NotifierTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using AirTNG.Web.Domain.Reservations; 4 | using AirTNG.Web.Models; 5 | using AirTNG.Web.Models.Repository; 6 | using Moq; 7 | using NUnit.Framework; 8 | 9 | namespace AirTNG.Web.Tests.Domain.Reservations 10 | { 11 | public class NotifierTest 12 | { 13 | [Test] 14 | public async Task WhenThereAreMoreThanOneReservations_ThenNoMessageIsSentAsync() 15 | { 16 | // Given 17 | var mockMessageSender = new Mock(); 18 | var mockRepository = SetupRepositoryMock(new List() { 19 | new Reservation(), 20 | new Reservation() 21 | }); 22 | var notifier = BuildNotifier(mockMessageSender, mockRepository); 23 | 24 | // When 25 | await notifier.SendNotificationAsync(new Reservation()); 26 | 27 | // Then 28 | mockMessageSender.Verify(c => c.SendMessageAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); 29 | } 30 | 31 | [Test] 32 | public async Task WhenThereIsOneOrNoReservation_ThenAMessageIsSent() 33 | { 34 | // Given 35 | const string hostPhoneNumber = "host-phone-number"; 36 | var mockMessageSender = new Mock(); 37 | var mockRepository = SetupRepositoryMock(new List()); 38 | var notifier = BuildNotifier(mockMessageSender, mockRepository); 39 | 40 | // When 41 | await notifier.SendNotificationAsync(new Reservation 42 | { 43 | VacationProperty = new VacationProperty(), 44 | PhoneNumber = hostPhoneNumber 45 | }); 46 | 47 | // Then 48 | mockMessageSender.Verify(c => c.SendMessageAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); 49 | } 50 | 51 | private static Notifier BuildNotifier(Mock mockClient, Mock mockRepository) 52 | { 53 | return new Notifier(mockClient.Object, mockRepository.Object); 54 | } 55 | 56 | private static Mock SetupRepositoryMock(List reservations) 57 | { 58 | var mockRepository = new Mock(); 59 | mockRepository 60 | .Setup(r => r.FindPendingReservationsAsync()) 61 | .ReturnsAsync(reservations); 62 | return mockRepository; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /AirTNG.Web.Tests/Extensions/ControllerResultTestExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Web.Mvc; 3 | using System.Xml.Linq; 4 | using TestStack.FluentMVCTesting; 5 | using Twilio.AspNet.Mvc; 6 | 7 | namespace AirTNG.Web.Tests.Extensions 8 | { 9 | public static class ControllerResultTestExtensions 10 | { 11 | public static TwiMLResult ShouldReturnTwiMLResult( 12 | this ControllerResultTest controllerResultTest) where T : Controller 13 | { 14 | controllerResultTest.ValidateActionReturnType(); 15 | return (TwiMLResult)controllerResultTest.ActionResult; 16 | } 17 | 18 | public static TwiMLResult ShouldReturnTwiMLResult( 19 | this ControllerResultTest controllerResultTest, 20 | Action assertion) where T : Controller 21 | { 22 | controllerResultTest.ValidateActionReturnType(); 23 | 24 | var twiMLResult = (TwiMLResult)controllerResultTest.ActionResult; 25 | var xdocument = twiMLResult.Data as XDocument; 26 | 27 | assertion(xdocument); 28 | 29 | return (TwiMLResult)controllerResultTest.ActionResult; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /AirTNG.Web.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AirTNG.Web.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("AirTNG.Web.Test")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("05e8d7bb-ab88-4680-8543-59079856e265")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /AirTNG.Web.Tests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /AirTNG.Web.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /AirTNG.Web/App_Data/.gitkeep: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /AirTNG.Web/App_Start/BundleConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Optimization; 2 | 3 | namespace AirTNG.Web 4 | { 5 | public class BundleConfig 6 | { 7 | // For more information on bundling, visit http://go.microsoft.com/fwlink/?LinkId=301862 8 | public static void RegisterBundles(BundleCollection bundles) 9 | { 10 | bundles.UseCdn = true; 11 | 12 | bundles.Add(new ScriptBundle("~/bundles/jquery").Include( 13 | "~/Scripts/jquery-{version}.js")); 14 | 15 | bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( 16 | "~/Scripts/jquery.validate*")); 17 | 18 | // Use the development version of Modernizr to develop with and learn from. Then, when you're 19 | // ready for production, use the build tool at http://modernizr.com to pick only the tests you need. 20 | bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( 21 | "~/Scripts/modernizr-*")); 22 | 23 | bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( 24 | "~/Scripts/bootstrap.js", 25 | "~/Scripts/respond.js")); 26 | 27 | bundles.Add(new StyleBundle("~/Content/bootstrap", "http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css") 28 | .Include("~/Content/bootstrap.css")); 29 | 30 | bundles.Add(new StyleBundle("~/Content/font-awesome", "http://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css") 31 | .Include("~/Content/font-awesome.css")); 32 | 33 | bundles.Add(new StyleBundle("~/Content/css").Include( 34 | "~/Content/main.css", 35 | "~/Content/scaffolds.css", 36 | "~/Content/vacation_properties.css", 37 | "~/Content/site.css")); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /AirTNG.Web/App_Start/FilterConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | 3 | namespace AirTNG.Web 4 | { 5 | public class FilterConfig 6 | { 7 | public static void RegisterGlobalFilters(GlobalFilterCollection filters) 8 | { 9 | filters.Add(new HandleErrorAttribute()); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AirTNG.Web/App_Start/IdentityConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Claims; 3 | using System.Threading.Tasks; 4 | using AirTNG.Web.Models; 5 | using Microsoft.AspNet.Identity; 6 | using Microsoft.AspNet.Identity.EntityFramework; 7 | using Microsoft.AspNet.Identity.Owin; 8 | using Microsoft.Owin; 9 | using Microsoft.Owin.Security; 10 | 11 | namespace AirTNG.Web 12 | { 13 | public class EmailService : IIdentityMessageService 14 | { 15 | public Task SendAsync(IdentityMessage message) 16 | { 17 | // Plug in your email service here to send an email. 18 | return Task.FromResult(0); 19 | } 20 | } 21 | 22 | public class SmsService : IIdentityMessageService 23 | { 24 | public Task SendAsync(IdentityMessage message) 25 | { 26 | // Plug in your SMS service here to send a text message. 27 | return Task.FromResult(0); 28 | } 29 | } 30 | 31 | // Configure the application user manager used in this application. UserManager is defined in ASP.NET Identity and is used by the application. 32 | public class ApplicationUserManager : UserManager 33 | { 34 | public ApplicationUserManager(IUserStore store) 35 | : base(store) 36 | { 37 | } 38 | 39 | public static ApplicationUserManager Create(IdentityFactoryOptions options, IOwinContext context) 40 | { 41 | var manager = new ApplicationUserManager(new UserStore(context.Get())); 42 | // Configure validation logic for usernames 43 | manager.UserValidator = new UserValidator(manager) 44 | { 45 | AllowOnlyAlphanumericUserNames = false, 46 | RequireUniqueEmail = true 47 | }; 48 | 49 | // Configure validation logic for passwords 50 | manager.PasswordValidator = new PasswordValidator 51 | { 52 | RequiredLength = 6, 53 | RequireNonLetterOrDigit = true, 54 | RequireDigit = true, 55 | RequireLowercase = true, 56 | RequireUppercase = true, 57 | }; 58 | 59 | // Configure user lockout defaults 60 | manager.UserLockoutEnabledByDefault = true; 61 | manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5); 62 | manager.MaxFailedAccessAttemptsBeforeLockout = 5; 63 | 64 | // Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user 65 | // You can write your own provider and plug it in here. 66 | manager.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider 67 | { 68 | MessageFormat = "Your security code is {0}" 69 | }); 70 | manager.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider 71 | { 72 | Subject = "Security Code", 73 | BodyFormat = "Your security code is {0}" 74 | }); 75 | manager.EmailService = new EmailService(); 76 | manager.SmsService = new SmsService(); 77 | var dataProtectionProvider = options.DataProtectionProvider; 78 | if (dataProtectionProvider != null) 79 | { 80 | manager.UserTokenProvider = 81 | new DataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity")); 82 | } 83 | return manager; 84 | } 85 | } 86 | 87 | // Configure the application sign-in manager which is used in this application. 88 | public class ApplicationSignInManager : SignInManager 89 | { 90 | public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) 91 | : base(userManager, authenticationManager) 92 | { 93 | } 94 | 95 | public override Task CreateUserIdentityAsync(ApplicationUser user) 96 | { 97 | return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager); 98 | } 99 | 100 | public static ApplicationSignInManager Create(IdentityFactoryOptions options, IOwinContext context) 101 | { 102 | return new ApplicationSignInManager(context.GetUserManager(), context.Authentication); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /AirTNG.Web/App_Start/RouteConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | using System.Web.Routing; 3 | 4 | namespace AirTNG.Web 5 | { 6 | public class RouteConfig 7 | { 8 | public static void RegisterRoutes(RouteCollection routes) 9 | { 10 | routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 11 | 12 | routes.MapRoute( 13 | name: "Default", 14 | url: "{controller}/{action}/{id}", 15 | defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } 16 | ); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AirTNG.Web/App_Start/Startup.Auth.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AirTNG.Web.Models; 3 | using Microsoft.AspNet.Identity; 4 | using Microsoft.AspNet.Identity.Owin; 5 | using Microsoft.Owin; 6 | using Microsoft.Owin.Security.Cookies; 7 | using Owin; 8 | 9 | namespace AirTNG.Web 10 | { 11 | public partial class Startup 12 | { 13 | // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864 14 | public void ConfigureAuth(IAppBuilder app) 15 | { 16 | // Configure the db context, user manager and signin manager to use a single instance per request 17 | app.CreatePerOwinContext(ApplicationDbContext.Create); 18 | app.CreatePerOwinContext(ApplicationUserManager.Create); 19 | app.CreatePerOwinContext(ApplicationSignInManager.Create); 20 | 21 | // Enable the application to use a cookie to store information for the signed in user 22 | // and to use a cookie to temporarily store information about a user logging in with a third party login provider 23 | // Configure the sign in cookie 24 | app.UseCookieAuthentication(new CookieAuthenticationOptions 25 | { 26 | AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, 27 | LoginPath = new PathString("/Account/Login"), 28 | Provider = new CookieAuthenticationProvider 29 | { 30 | // Enables the application to validate the security stamp when the user logs in. 31 | // This is a security feature which is used when you change a password or add an external login to your account. 32 | OnValidateIdentity = SecurityStampValidator.OnValidateIdentity( 33 | validateInterval: TimeSpan.FromMinutes(30), 34 | regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)) 35 | } 36 | }); 37 | app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); 38 | 39 | // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process. 40 | app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5)); 41 | 42 | // Enables the application to remember the second login verification factor such as phone or email. 43 | // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from. 44 | // This is similar to the RememberMe option when you log in. 45 | app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie); 46 | 47 | // Uncomment the following lines to enable logging in with third party login providers 48 | //app.UseMicrosoftAccountAuthentication( 49 | // clientId: "", 50 | // clientSecret: ""); 51 | 52 | //app.UseTwitterAuthentication( 53 | // consumerKey: "", 54 | // consumerSecret: ""); 55 | 56 | //app.UseFacebookAuthentication( 57 | // appId: "", 58 | // appSecret: ""); 59 | 60 | //app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions() 61 | //{ 62 | // ClientId = "", 63 | // ClientSecret = "" 64 | //}); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /AirTNG.Web/Content/Images/airtng-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/airtng-csharp/22b4448e23ae012de9a8673bbd0680b565ddc630/AirTNG.Web/Content/Images/airtng-logo.png -------------------------------------------------------------------------------- /AirTNG.Web/Content/Images/spock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/airtng-csharp/22b4448e23ae012de9a8673bbd0680b565ddc630/AirTNG.Web/Content/Images/spock.png -------------------------------------------------------------------------------- /AirTNG.Web/Content/Images/tngbg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/airtng-csharp/22b4448e23ae012de9a8673bbd0680b565ddc630/AirTNG.Web/Content/Images/tngbg.jpg -------------------------------------------------------------------------------- /AirTNG.Web/Content/Site.css: -------------------------------------------------------------------------------- 1 | @import url(http://fonts.googleapis.com/css?family=Raleway:400,600,300); 2 | 3 | html { 4 | box-sizing:border-box; 5 | } 6 | 7 | body { 8 | font-family: 'Raleway', sans-serif; 9 | } 10 | 11 | *, *:before, *:after { 12 | box-sizing:inherit; 13 | } 14 | 15 | a:visited { 16 | color:white; 17 | } 18 | 19 | footer { 20 | font-size:12px; 21 | color:#787878; 22 | margin-top:20px; 23 | padding-top:20px; 24 | text-align:center; 25 | } 26 | 27 | footer i { 28 | color:#ff0000; 29 | } 30 | 31 | nav a:focus { 32 | text-decoration:none; 33 | color:#337ab7; 34 | } 35 | 36 | td { 37 | padding:10px; 38 | } 39 | 40 | #main { 41 | margin-top:10px; 42 | } 43 | 44 | #messages p { 45 | padding:10px; 46 | border:1px solid #eee; 47 | } 48 | 49 | #messages i { 50 | float:right; 51 | margin-left:5px; 52 | cursor:pointer; 53 | } 54 | 55 | #countries-input-0{ 56 | display: block; 57 | width: 100%; 58 | height: 34px; 59 | padding: 6px 12px; 60 | font-size: 14px; 61 | line-height: 1.42857; 62 | color: #555; 63 | background-color: #FFF; 64 | background-image: none; 65 | border: 1px solid #CCC; 66 | border-radius: 4px; 67 | box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.075) inset; 68 | transition: border-color 0.15s ease-in-out 0s, box-shadow 0.15s ease-in-out 0s; 69 | } -------------------------------------------------------------------------------- /AirTNG.Web/Content/main.css: -------------------------------------------------------------------------------- 1 | body.cover footer { 2 | text-align: center; 3 | position: absolute; 4 | bottom: 20px; 5 | width: 100%; 6 | } 7 | 8 | .hero-text { 9 | background: url(Images/tngbg.jpg); 10 | margin: 0px; 11 | top: 0px; 12 | text-align: center; 13 | padding: 130px; 14 | color: white; 15 | background-size: cover; 16 | } 17 | 18 | .hero-text.full-page { 19 | height: 100%; 20 | position: absolute; 21 | width: 100%; 22 | } 23 | 24 | .hero-text h1 { 25 | font-size: 60px; 26 | text-transform: uppercase; 27 | } 28 | 29 | .hero-text p { 30 | font-size: 30px; 31 | } 32 | 33 | .hero-text .btn-transparent { 34 | background: rgba(255, 255, 255, 0.6); 35 | margin: 30px; 36 | color: white; 37 | font-weight: 600; 38 | letter-spacing: 1px; 39 | } 40 | 41 | .navbar { 42 | border-radius: 0px !important; 43 | } 44 | 45 | .navbar.navbar-transparent { 46 | background: transparent; 47 | position: absolute; 48 | width: 100%; 49 | top: 0px; 50 | z-index: 1020; 51 | padding: 20px; 52 | } 53 | 54 | .navbar.navbar-space { 55 | background: url(Images/tngbg.jpg); 56 | height: 90px; 57 | } 58 | 59 | .navbar .navbar-brand { 60 | background: url(Images/airtng-logo.png) no-repeat; 61 | padding-left: 40px; 62 | color: white; 63 | font-size: 32px; 64 | } 65 | 66 | .navbar img { 67 | width: 20px; 68 | } 69 | 70 | .navbar li { 71 | list-style: none; 72 | margin: 10px; 73 | } 74 | 75 | .navbar li a { 76 | color: #337ab7 !important; 77 | font-size: 14px; 78 | } 79 | 80 | .navbar li a:hover { 81 | background: transparent; 82 | text-decoration: none; 83 | } 84 | 85 | .navbar li a:visited { 86 | color: #337ab7 !important; 87 | } 88 | 89 | #main.push-hero { 90 | margin-top: 30px; 91 | } 92 | 93 | #main.push-nav { 94 | margin-top: 60px; 95 | } -------------------------------------------------------------------------------- /AirTNG.Web/Content/scaffolds.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #fff; 3 | color: #333; 4 | font-family: verdana, arial, helvetica, sans-serif; 5 | font-size: 13px; 6 | line-height: 18px; 7 | } 8 | 9 | p, ol, ul, td { 10 | font-family: verdana, arial, helvetica, sans-serif; 11 | font-size: 13px; 12 | line-height: 18px; 13 | } 14 | 15 | pre { 16 | background-color: #eee; 17 | padding: 10px; 18 | font-size: 11px; 19 | } 20 | 21 | a { 22 | color: #000; 23 | } 24 | 25 | a:visited { 26 | color: #666; 27 | } 28 | 29 | a:hover { 30 | color: #fff; 31 | background-color: #000; 32 | } 33 | 34 | div.field, div.actions { 35 | margin-bottom: 10px; 36 | } 37 | 38 | #notice { 39 | color: green; 40 | } 41 | 42 | .field_with_errors { 43 | padding: 2px; 44 | background-color: red; 45 | display: table; 46 | } 47 | 48 | #error_explanation { 49 | width: 450px; 50 | border: 2px solid red; 51 | padding: 7px; 52 | padding-bottom: 0; 53 | margin-bottom: 20px; 54 | background-color: #f0f0f0; 55 | } 56 | 57 | #error_explanation h2 { 58 | text-align: left; 59 | font-weight: bold; 60 | padding: 5px 5px 5px 15px; 61 | font-size: 12px; 62 | margin: -7px; 63 | margin-bottom: 0px; 64 | background-color: #c00; 65 | color: #fff; 66 | } 67 | 68 | #error_explanation ul li { 69 | font-size: 12px; 70 | list-style: square; 71 | } -------------------------------------------------------------------------------- /AirTNG.Web/Content/vacation_properties.css: -------------------------------------------------------------------------------- 1 | body.property-page footer { 2 | position: relative; 3 | } 4 | 5 | body.property-page #main { 6 | min-height: 900px; 7 | } 8 | 9 | .property { 10 | display: block; 11 | position: relative; 12 | border-radius: 10px; 13 | text-align: center; 14 | padding-top: 88px; 15 | height: 240px; 16 | text-transform: uppercase; 17 | overflow: hidden; 18 | margin-bottom: 20px; 19 | } 20 | 21 | .property img { 22 | vertical-align: middle; 23 | width: 100%; 24 | position: absolute; 25 | left: 0px; 26 | top: 0px; 27 | border-radius: 10px; 28 | } 29 | 30 | .property h2 { 31 | color: white; 32 | z-index: 1000; 33 | position: relative; 34 | } 35 | 36 | .property-detail { 37 | position: absolute; 38 | top: 0px; 39 | width: 100%; 40 | } 41 | 42 | .property-detail .overview { 43 | height: 400px; 44 | overflow: hidden; 45 | } 46 | 47 | .property-detail img { 48 | width: 100%; 49 | } -------------------------------------------------------------------------------- /AirTNG.Web/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | 3 | namespace AirTNG.Web.Controllers 4 | { 5 | public class HomeController : Controller 6 | { 7 | public ActionResult Index() 8 | { 9 | return View(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /AirTNG.Web/Controllers/ReservationsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Web.Mvc; 4 | using AirTNG.Web.Domain.Reservations; 5 | using AirTNG.Web.Models; 6 | using AirTNG.Web.Models.Repository; 7 | using AirTNG.Web.ViewModels; 8 | using Twilio.AspNet.Mvc; 9 | using Twilio.TwiML; 10 | 11 | namespace AirTNG.Web.Controllers 12 | { 13 | [Authorize] 14 | public class ReservationsController : TwilioController 15 | { 16 | private readonly IVacationPropertiesRepository _vacationPropertiesRepository; 17 | private readonly IReservationsRepository _reservationsRepository; 18 | private readonly IUsersRepository _usersRepository; 19 | private readonly INotifier _notifier; 20 | 21 | public ReservationsController() : this( 22 | new VacationPropertiesRepository(), 23 | new ReservationsRepository(), 24 | new UsersRepository(), 25 | new Notifier()) { } 26 | 27 | public ReservationsController( 28 | IVacationPropertiesRepository vacationPropertiesRepository, 29 | IReservationsRepository reservationsRepository, 30 | IUsersRepository usersRepository, 31 | INotifier notifier) 32 | { 33 | _vacationPropertiesRepository = vacationPropertiesRepository; 34 | _reservationsRepository = reservationsRepository; 35 | _usersRepository = usersRepository; 36 | _notifier = notifier; 37 | } 38 | 39 | // GET: Reservations/Create 40 | public async Task Create(int id) 41 | { 42 | var vacationProperty = await _vacationPropertiesRepository.FindAsync(id); 43 | var reservation = new ReservationViewModel 44 | { 45 | ImageUrl = vacationProperty.ImageUrl, 46 | Description = vacationProperty.Description, 47 | VacationPropertyId = vacationProperty.Id, 48 | VacationPropertyDescription = vacationProperty.Description, 49 | UserName = vacationProperty.User.Name, 50 | UserPhoneNumber = vacationProperty.User.PhoneNumber, 51 | }; 52 | 53 | return View(reservation); 54 | } 55 | 56 | // POST: Reservations/Create 57 | [HttpPost] 58 | public async Task Create(ReservationViewModel model) 59 | { 60 | if (ModelState.IsValid) 61 | { 62 | var reservation = new Reservation 63 | { 64 | Message = model.Message, 65 | PhoneNumber = model.UserPhoneNumber, 66 | Name = model.UserName, 67 | VactionPropertyId = model.VacationPropertyId, 68 | Status = ReservationStatus.Pending, 69 | CreatedAt = DateTime.Now 70 | }; 71 | 72 | await _reservationsRepository.CreateAsync(reservation); 73 | reservation.VacationProperty = new VacationProperty {Description = model.VacationPropertyDescription}; 74 | await _notifier.SendNotificationAsync(reservation); 75 | 76 | return RedirectToAction("Index", "VacationProperties"); 77 | } 78 | 79 | return View(model); 80 | } 81 | 82 | // POST Reservations/Handle 83 | [HttpPost] 84 | [AllowAnonymous] 85 | public async Task Handle(string from, string body) 86 | { 87 | string smsResponse; 88 | 89 | try 90 | { 91 | var host = await _usersRepository.FindByPhoneNumberAsync(from); 92 | var reservation = await _reservationsRepository.FindFirstPendingReservationByHostAsync(host.Id); 93 | 94 | var smsRequest = body; 95 | reservation.Status = 96 | smsRequest.Equals("accept", StringComparison.InvariantCultureIgnoreCase) || 97 | smsRequest.Equals("yes", StringComparison.InvariantCultureIgnoreCase) 98 | ? ReservationStatus.Confirmed 99 | : ReservationStatus.Rejected; 100 | 101 | await _reservationsRepository.UpdateAsync(reservation); 102 | smsResponse = 103 | string.Format("You have successfully {0} the reservation", reservation.Status); 104 | } 105 | catch (Exception) 106 | { 107 | smsResponse = "Sorry, it looks like you don't have any reservations to respond to."; 108 | } 109 | 110 | return TwiML(Respond(smsResponse)); 111 | } 112 | 113 | private static MessagingResponse Respond(string message) 114 | { 115 | var response = new MessagingResponse(); 116 | response.Message(message); 117 | 118 | return response; 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /AirTNG.Web/Controllers/VacationPropertiesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Web.Mvc; 4 | using AirTNG.Web.Models; 5 | using AirTNG.Web.Models.Repository; 6 | using AirTNG.Web.ViewModels; 7 | using Microsoft.AspNet.Identity; 8 | 9 | namespace AirTNG.Web.Controllers 10 | { 11 | [Authorize] 12 | public class VacationPropertiesController : Controller 13 | { 14 | private readonly IVacationPropertiesRepository _repository; 15 | 16 | public Func UserId; 17 | 18 | public VacationPropertiesController() : this(new VacationPropertiesRepository()) { } 19 | 20 | public VacationPropertiesController(IVacationPropertiesRepository repository) 21 | { 22 | _repository = repository; 23 | UserId = () => User.Identity.GetUserId(); 24 | } 25 | 26 | // GET: VacationProperties 27 | public async Task Index() 28 | { 29 | var vacationProperties = await _repository.AllAsync(); 30 | return View(vacationProperties); 31 | } 32 | 33 | // GET: VacationProperties/Create 34 | public ActionResult Create() 35 | { 36 | return View(); 37 | } 38 | 39 | // POST: VacationProperties/Create 40 | [HttpPost] 41 | public async Task Create(VacationPropertyViewModel model) 42 | { 43 | if (ModelState.IsValid) 44 | { 45 | var vacationProperty = new VacationProperty 46 | { 47 | UserId = UserId(), 48 | Description = model.Description, 49 | ImageUrl = model.ImageUrl, 50 | CreatedAt = DateTime.Now 51 | }; 52 | 53 | await _repository.CreateAsync(vacationProperty); 54 | 55 | return RedirectToAction("Index"); 56 | } 57 | 58 | return View(); 59 | } 60 | 61 | // GET: VacationProperties/Edit/1 62 | public async Task Edit(int id) 63 | { 64 | var vacationProperty = await _repository.FindAsync(id); 65 | return View(vacationProperty); 66 | } 67 | 68 | // POST: VacationProperties/Edit 69 | [HttpPost] 70 | public async Task Edit(VacationPropertyViewModel model) 71 | { 72 | if (ModelState.IsValid) 73 | { 74 | var vacationProperty = await _repository.FindAsync(model.Id); 75 | vacationProperty.Description = model.Description; 76 | vacationProperty.ImageUrl = model.ImageUrl; 77 | 78 | await _repository.UpdateAsync(vacationProperty); 79 | 80 | return RedirectToAction("Index"); 81 | } 82 | 83 | return View(); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /AirTNG.Web/Domain/Reservations/Notification.cs: -------------------------------------------------------------------------------- 1 | using Twilio.Types; 2 | 3 | namespace AirTNG.Web.Domain.Reservations 4 | { 5 | public class Notification 6 | { 7 | public string From { get; set; } 8 | public string To { get; set; } 9 | public string Messsage { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /AirTNG.Web/Domain/Reservations/Notifier.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using AirTNG.Web.Domain.Twilio; 6 | using AirTNG.Web.Models; 7 | using AirTNG.Web.Models.Repository; 8 | 9 | namespace AirTNG.Web.Domain.Reservations 10 | { 11 | public interface INotifier 12 | { 13 | Task SendNotificationAsync(Reservation reservation); 14 | } 15 | 16 | public class Notifier : INotifier 17 | { 18 | private readonly ITwilioMessageSender _client; 19 | private readonly IReservationsRepository _repository; 20 | 21 | public Notifier() : this( 22 | new TwilioMessageSender(), 23 | new ReservationsRepository()) { } 24 | 25 | public Notifier(ITwilioMessageSender client, IReservationsRepository repository) 26 | { 27 | _client = client; 28 | _repository = repository; 29 | } 30 | 31 | public async Task SendNotificationAsync(Reservation reservation) 32 | { 33 | var pendingReservations = await _repository.FindPendingReservationsAsync(); 34 | if (pendingReservations.Count() < 2) 35 | { 36 | var notification = BuildNotification(reservation); 37 | await _client.SendMessageAsync(notification.To, notification.From, notification.Messsage); 38 | } 39 | } 40 | 41 | private static Notification BuildNotification(Reservation reservation) 42 | { 43 | var message = new StringBuilder(); 44 | message.AppendFormat("You have a new reservation request from {0} for {1}:{2}", 45 | reservation.Name, 46 | reservation.VacationProperty.Description, 47 | Environment.NewLine); 48 | message.AppendFormat("{0}{1}", 49 | reservation.Message, 50 | Environment.NewLine); 51 | message.Append("Reply [accept] or [reject]"); 52 | 53 | return new Notification 54 | { 55 | From = PhoneNumbers.Twilio, 56 | To = reservation.PhoneNumber, 57 | Messsage = message.ToString() 58 | }; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /AirTNG.Web/Domain/Reservations/TwilioMessageSender.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using AirTNG.Web.Domain.Twilio; 3 | using Twilio; 4 | using Twilio.Rest.Api.V2010.Account; 5 | using Twilio.Types; 6 | 7 | namespace AirTNG.Web.Domain.Reservations 8 | { 9 | public interface ITwilioMessageSender 10 | { 11 | Task SendMessageAsync(string to, string from, string body); 12 | } 13 | public class TwilioMessageSender : ITwilioMessageSender 14 | { 15 | public TwilioMessageSender() 16 | { 17 | TwilioClient.Init(Credentials.AccountSid, Credentials.AuthToken); 18 | } 19 | 20 | public async Task SendMessageAsync(string to, string from, string body) 21 | { 22 | await MessageResource.CreateAsync(new PhoneNumber(to), 23 | from: new PhoneNumber(from), 24 | body: body); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /AirTNG.Web/Domain/Twilio/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Configuration; 2 | 3 | namespace AirTNG.Web.Domain.Twilio 4 | { 5 | public class Credentials 6 | { 7 | public static string AccountSid => WebConfigurationManager.AppSettings["TwilioAccountSid"] ?? 8 | "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; 9 | 10 | public static string AuthToken => WebConfigurationManager.AppSettings["TwilioAuthToken"] ?? 11 | "aXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; 12 | } 13 | 14 | 15 | public class PhoneNumbers 16 | { 17 | public static string Twilio => WebConfigurationManager.AppSettings["TwilioPhoneNumber"] ?? 18 | "+123456"; 19 | } 20 | } -------------------------------------------------------------------------------- /AirTNG.Web/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="AirTNG.Web.MvcApplication" Language="C#" %> 2 | -------------------------------------------------------------------------------- /AirTNG.Web/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Mvc; 3 | using System.Web.Optimization; 4 | using System.Web.Routing; 5 | 6 | namespace AirTNG.Web 7 | { 8 | public class MvcApplication : HttpApplication 9 | { 10 | protected void Application_Start() 11 | { 12 | AreaRegistration.RegisterAllAreas(); 13 | FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 14 | RouteConfig.RegisterRoutes(RouteTable.Routes); 15 | BundleConfig.RegisterBundles(BundleTable.Bundles); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AirTNG.Web/Migrations/201510281842434_CreateDatabase.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | namespace AirTNG.Web.Migrations 3 | { 4 | using System.CodeDom.Compiler; 5 | using System.Data.Entity.Migrations; 6 | using System.Data.Entity.Migrations.Infrastructure; 7 | using System.Resources; 8 | 9 | [GeneratedCode("EntityFramework.Migrations", "6.1.1-30610")] 10 | public sealed partial class CreateDatabase : IMigrationMetadata 11 | { 12 | private readonly ResourceManager Resources = new ResourceManager(typeof(CreateDatabase)); 13 | 14 | string IMigrationMetadata.Id 15 | { 16 | get { return "201510281842434_CreateDatabase"; } 17 | } 18 | 19 | string IMigrationMetadata.Source 20 | { 21 | get { return null; } 22 | } 23 | 24 | string IMigrationMetadata.Target 25 | { 26 | get { return Resources.GetString("Target"); } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /AirTNG.Web/Migrations/201510281842434_CreateDatabase.cs: -------------------------------------------------------------------------------- 1 | namespace AirTNG.Web.Migrations 2 | { 3 | using System; 4 | using System.Data.Entity.Migrations; 5 | 6 | public partial class CreateDatabase : DbMigration 7 | { 8 | public override void Up() 9 | { 10 | CreateTable( 11 | "dbo.AspNetRoles", 12 | c => new 13 | { 14 | Id = c.String(nullable: false, maxLength: 128), 15 | Name = c.String(nullable: false, maxLength: 256), 16 | }) 17 | .PrimaryKey(t => t.Id) 18 | .Index(t => t.Name, unique: true, name: "RoleNameIndex"); 19 | 20 | CreateTable( 21 | "dbo.AspNetUserRoles", 22 | c => new 23 | { 24 | UserId = c.String(nullable: false, maxLength: 128), 25 | RoleId = c.String(nullable: false, maxLength: 128), 26 | }) 27 | .PrimaryKey(t => new { t.UserId, t.RoleId }) 28 | .ForeignKey("dbo.AspNetRoles", t => t.RoleId, cascadeDelete: true) 29 | .ForeignKey("dbo.AspNetUsers", t => t.UserId, cascadeDelete: true) 30 | .Index(t => t.UserId) 31 | .Index(t => t.RoleId); 32 | 33 | CreateTable( 34 | "dbo.AspNetUsers", 35 | c => new 36 | { 37 | Id = c.String(nullable: false, maxLength: 128), 38 | Email = c.String(maxLength: 256), 39 | EmailConfirmed = c.Boolean(nullable: false), 40 | PasswordHash = c.String(), 41 | SecurityStamp = c.String(), 42 | PhoneNumber = c.String(), 43 | PhoneNumberConfirmed = c.Boolean(nullable: false), 44 | TwoFactorEnabled = c.Boolean(nullable: false), 45 | LockoutEndDateUtc = c.DateTime(), 46 | LockoutEnabled = c.Boolean(nullable: false), 47 | AccessFailedCount = c.Int(nullable: false), 48 | UserName = c.String(nullable: false, maxLength: 256), 49 | }) 50 | .PrimaryKey(t => t.Id) 51 | .Index(t => t.UserName, unique: true, name: "UserNameIndex"); 52 | 53 | CreateTable( 54 | "dbo.AspNetUserClaims", 55 | c => new 56 | { 57 | Id = c.Int(nullable: false, identity: true), 58 | UserId = c.String(nullable: false, maxLength: 128), 59 | ClaimType = c.String(), 60 | ClaimValue = c.String(), 61 | }) 62 | .PrimaryKey(t => t.Id) 63 | .ForeignKey("dbo.AspNetUsers", t => t.UserId, cascadeDelete: true) 64 | .Index(t => t.UserId); 65 | 66 | CreateTable( 67 | "dbo.AspNetUserLogins", 68 | c => new 69 | { 70 | LoginProvider = c.String(nullable: false, maxLength: 128), 71 | ProviderKey = c.String(nullable: false, maxLength: 128), 72 | UserId = c.String(nullable: false, maxLength: 128), 73 | }) 74 | .PrimaryKey(t => new { t.LoginProvider, t.ProviderKey, t.UserId }) 75 | .ForeignKey("dbo.AspNetUsers", t => t.UserId, cascadeDelete: true) 76 | .Index(t => t.UserId); 77 | 78 | } 79 | 80 | public override void Down() 81 | { 82 | DropForeignKey("dbo.AspNetUserRoles", "UserId", "dbo.AspNetUsers"); 83 | DropForeignKey("dbo.AspNetUserLogins", "UserId", "dbo.AspNetUsers"); 84 | DropForeignKey("dbo.AspNetUserClaims", "UserId", "dbo.AspNetUsers"); 85 | DropForeignKey("dbo.AspNetUserRoles", "RoleId", "dbo.AspNetRoles"); 86 | DropIndex("dbo.AspNetUserLogins", new[] { "UserId" }); 87 | DropIndex("dbo.AspNetUserClaims", new[] { "UserId" }); 88 | DropIndex("dbo.AspNetUsers", "UserNameIndex"); 89 | DropIndex("dbo.AspNetUserRoles", new[] { "RoleId" }); 90 | DropIndex("dbo.AspNetUserRoles", new[] { "UserId" }); 91 | DropIndex("dbo.AspNetRoles", "RoleNameIndex"); 92 | DropTable("dbo.AspNetUserLogins"); 93 | DropTable("dbo.AspNetUserClaims"); 94 | DropTable("dbo.AspNetUsers"); 95 | DropTable("dbo.AspNetUserRoles"); 96 | DropTable("dbo.AspNetRoles"); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /AirTNG.Web/Migrations/201510281842434_CreateDatabase.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 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 122 | 123 | 124 | dbo 125 | 126 | -------------------------------------------------------------------------------- /AirTNG.Web/Migrations/201510282125527_AddNameToUsers.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | namespace AirTNG.Web.Migrations 3 | { 4 | using System.CodeDom.Compiler; 5 | using System.Data.Entity.Migrations; 6 | using System.Data.Entity.Migrations.Infrastructure; 7 | using System.Resources; 8 | 9 | [GeneratedCode("EntityFramework.Migrations", "6.1.1-30610")] 10 | public sealed partial class AddNameToUsers : IMigrationMetadata 11 | { 12 | private readonly ResourceManager Resources = new ResourceManager(typeof(AddNameToUsers)); 13 | 14 | string IMigrationMetadata.Id 15 | { 16 | get { return "201510282125527_AddNameToUsers"; } 17 | } 18 | 19 | string IMigrationMetadata.Source 20 | { 21 | get { return null; } 22 | } 23 | 24 | string IMigrationMetadata.Target 25 | { 26 | get { return Resources.GetString("Target"); } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /AirTNG.Web/Migrations/201510282125527_AddNameToUsers.cs: -------------------------------------------------------------------------------- 1 | namespace AirTNG.Web.Migrations 2 | { 3 | using System; 4 | using System.Data.Entity.Migrations; 5 | 6 | public partial class AddNameToUsers : DbMigration 7 | { 8 | public override void Up() 9 | { 10 | AddColumn("dbo.AspNetUsers", "Name", c => c.String()); 11 | } 12 | 13 | public override void Down() 14 | { 15 | DropColumn("dbo.AspNetUsers", "Name"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AirTNG.Web/Migrations/201510282125527_AddNameToUsers.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 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 122 | 123 | 124 | dbo 125 | 126 | -------------------------------------------------------------------------------- /AirTNG.Web/Migrations/201510301945368_CreateReservations.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | namespace AirTNG.Web.Migrations 3 | { 4 | using System.CodeDom.Compiler; 5 | using System.Data.Entity.Migrations; 6 | using System.Data.Entity.Migrations.Infrastructure; 7 | using System.Resources; 8 | 9 | [GeneratedCode("EntityFramework.Migrations", "6.1.3-40302")] 10 | public sealed partial class CreateReservations : IMigrationMetadata 11 | { 12 | private readonly ResourceManager Resources = new ResourceManager(typeof(CreateReservations)); 13 | 14 | string IMigrationMetadata.Id 15 | { 16 | get { return "201510301945368_CreateReservations"; } 17 | } 18 | 19 | string IMigrationMetadata.Source 20 | { 21 | get { return null; } 22 | } 23 | 24 | string IMigrationMetadata.Target 25 | { 26 | get { return Resources.GetString("Target"); } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /AirTNG.Web/Migrations/201510301945368_CreateReservations.cs: -------------------------------------------------------------------------------- 1 | namespace AirTNG.Web.Migrations 2 | { 3 | using System; 4 | using System.Data.Entity.Migrations; 5 | 6 | public partial class CreateReservations : DbMigration 7 | { 8 | public override void Up() 9 | { 10 | CreateTable( 11 | "dbo.Reservations", 12 | c => new 13 | { 14 | Id = c.Int(nullable: false, identity: true), 15 | Name = c.String(), 16 | PhoneNumber = c.String(), 17 | Status = c.Int(nullable: false), 18 | Message = c.String(), 19 | CreatedAt = c.DateTime(nullable: false), 20 | }) 21 | .PrimaryKey(t => t.Id); 22 | 23 | } 24 | 25 | public override void Down() 26 | { 27 | DropTable("dbo.Reservations"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /AirTNG.Web/Migrations/201510301945368_CreateReservations.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 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 122 | 123 | 124 | dbo 125 | 126 | -------------------------------------------------------------------------------- /AirTNG.Web/Migrations/201510301956088_CreateVacationProperties.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | namespace AirTNG.Web.Migrations 3 | { 4 | using System.CodeDom.Compiler; 5 | using System.Data.Entity.Migrations; 6 | using System.Data.Entity.Migrations.Infrastructure; 7 | using System.Resources; 8 | 9 | [GeneratedCode("EntityFramework.Migrations", "6.1.3-40302")] 10 | public sealed partial class CreateVacationProperties : IMigrationMetadata 11 | { 12 | private readonly ResourceManager Resources = new ResourceManager(typeof(CreateVacationProperties)); 13 | 14 | string IMigrationMetadata.Id 15 | { 16 | get { return "201510301956088_CreateVacationProperties"; } 17 | } 18 | 19 | string IMigrationMetadata.Source 20 | { 21 | get { return null; } 22 | } 23 | 24 | string IMigrationMetadata.Target 25 | { 26 | get { return Resources.GetString("Target"); } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /AirTNG.Web/Migrations/201510301956088_CreateVacationProperties.cs: -------------------------------------------------------------------------------- 1 | namespace AirTNG.Web.Migrations 2 | { 3 | using System; 4 | using System.Data.Entity.Migrations; 5 | 6 | public partial class CreateVacationProperties : DbMigration 7 | { 8 | public override void Up() 9 | { 10 | CreateTable( 11 | "dbo.VacationProperties", 12 | c => new 13 | { 14 | Id = c.Int(nullable: false, identity: true), 15 | UserId = c.String(maxLength: 128), 16 | Description = c.String(), 17 | ImageUrl = c.String(), 18 | CreatedAt = c.DateTime(nullable: false), 19 | }) 20 | .PrimaryKey(t => t.Id) 21 | .ForeignKey("dbo.AspNetUsers", t => t.UserId) 22 | .Index(t => t.UserId); 23 | 24 | AddColumn("dbo.Reservations", "VactionPropertyId", c => c.Int(nullable: false)); 25 | CreateIndex("dbo.Reservations", "VactionPropertyId"); 26 | AddForeignKey("dbo.Reservations", "VactionPropertyId", "dbo.VacationProperties", "Id", cascadeDelete: true); 27 | } 28 | 29 | public override void Down() 30 | { 31 | DropForeignKey("dbo.VacationProperties", "UserId", "dbo.AspNetUsers"); 32 | DropForeignKey("dbo.Reservations", "VactionPropertyId", "dbo.VacationProperties"); 33 | DropIndex("dbo.VacationProperties", new[] { "UserId" }); 34 | DropIndex("dbo.Reservations", new[] { "VactionPropertyId" }); 35 | DropColumn("dbo.Reservations", "VactionPropertyId"); 36 | DropTable("dbo.VacationProperties"); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /AirTNG.Web/Migrations/201510301956088_CreateVacationProperties.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 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 122 | 123 | 124 | dbo 125 | 126 | -------------------------------------------------------------------------------- /AirTNG.Web/Migrations/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Entity.Migrations; 2 | using AirTNG.Web.Models; 3 | 4 | namespace AirTNG.Web.Migrations 5 | { 6 | internal sealed class Configuration : DbMigrationsConfiguration 7 | { 8 | public Configuration() 9 | { 10 | AutomaticMigrationsEnabled = false; 11 | } 12 | 13 | protected override void Seed(ApplicationDbContext context) 14 | { 15 | // This method will be called after migrating to the latest version. 16 | 17 | // You can use the DbSet.AddOrUpdate() helper extension method 18 | // to avoid creating duplicate seed data. E.g. 19 | // 20 | // context.People.AddOrUpdate( 21 | // p => p.FullName, 22 | // new Person { FullName = "Andrew Peters" }, 23 | // new Person { FullName = "Brice Lambson" }, 24 | // new Person { FullName = "Rowan Miller" } 25 | // ); 26 | // 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /AirTNG.Web/Models/AccountViewModels.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace AirTNG.Web.Models 5 | { 6 | public class ExternalLoginConfirmationViewModel 7 | { 8 | [Required] 9 | [Display(Name = "Email")] 10 | public string Email { get; set; } 11 | } 12 | 13 | public class ExternalLoginListViewModel 14 | { 15 | public string ReturnUrl { get; set; } 16 | } 17 | 18 | public class SendCodeViewModel 19 | { 20 | public string SelectedProvider { get; set; } 21 | public ICollection Providers { get; set; } 22 | public string ReturnUrl { get; set; } 23 | public bool RememberMe { get; set; } 24 | } 25 | 26 | public class VerifyCodeViewModel 27 | { 28 | [Required] 29 | public string Provider { get; set; } 30 | 31 | [Required] 32 | [Display(Name = "Code")] 33 | public string Code { get; set; } 34 | public string ReturnUrl { get; set; } 35 | 36 | [Display(Name = "Remember this browser?")] 37 | public bool RememberBrowser { get; set; } 38 | 39 | public bool RememberMe { get; set; } 40 | } 41 | 42 | public class ForgotViewModel 43 | { 44 | [Required] 45 | [Display(Name = "Email")] 46 | public string Email { get; set; } 47 | } 48 | 49 | public class LoginViewModel 50 | { 51 | [Required] 52 | [Display(Name = "Email")] 53 | [EmailAddress] 54 | public string Email { get; set; } 55 | 56 | [Required] 57 | [DataType(DataType.Password)] 58 | [Display(Name = "Password")] 59 | public string Password { get; set; } 60 | 61 | [Display(Name = "Remember me?")] 62 | public bool RememberMe { get; set; } 63 | } 64 | 65 | public class RegisterViewModel 66 | { 67 | [Required] 68 | [Display(Name = "Tell us your name")] 69 | public string Name { get; set; } 70 | 71 | [Required] 72 | [EmailAddress] 73 | [Display(Name = "Enter Your E-mail Address")] 74 | public string Email { get; set; } 75 | 76 | [Required] 77 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 78 | [DataType(DataType.Password)] 79 | [Display(Name = "Enter a password")] 80 | public string Password { get; set; } 81 | 82 | [Required] 83 | [Display(Name = "Country code")] 84 | public string CountryCode { get; set; } 85 | 86 | [Required] 87 | [Display(Name = "Phone number")] 88 | public string PhoneNumber { get; set; } 89 | } 90 | 91 | public class ResetPasswordViewModel 92 | { 93 | [Required] 94 | [EmailAddress] 95 | [Display(Name = "Email")] 96 | public string Email { get; set; } 97 | 98 | [Required] 99 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 100 | [DataType(DataType.Password)] 101 | [Display(Name = "Password")] 102 | public string Password { get; set; } 103 | 104 | [DataType(DataType.Password)] 105 | [Display(Name = "Confirm password")] 106 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 107 | public string ConfirmPassword { get; set; } 108 | 109 | public string Code { get; set; } 110 | } 111 | 112 | public class ForgotPasswordViewModel 113 | { 114 | [Required] 115 | [EmailAddress] 116 | [Display(Name = "Email")] 117 | public string Email { get; set; } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /AirTNG.Web/Models/IdentityModels.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data.Entity; 3 | using System.Security.Claims; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNet.Identity; 6 | using Microsoft.AspNet.Identity.EntityFramework; 7 | 8 | namespace AirTNG.Web.Models 9 | { 10 | // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more. 11 | public class ApplicationUser : IdentityUser 12 | { 13 | public string Name { get; set; } 14 | 15 | public virtual IList VacationProperties { get; set; } 16 | 17 | public async Task GenerateUserIdentityAsync(UserManager manager) 18 | { 19 | // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType 20 | var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie); 21 | // Add custom user claims here 22 | return userIdentity; 23 | } 24 | } 25 | 26 | public class ApplicationDbContext : IdentityDbContext 27 | { 28 | public ApplicationDbContext() 29 | : base("AirTNGConnection", false) 30 | { 31 | } 32 | 33 | public static ApplicationDbContext Create() 34 | { 35 | return new ApplicationDbContext(); 36 | } 37 | 38 | public DbSet VacationProperties { get; set; } 39 | public DbSet Reservations { get; set; } 40 | } 41 | } -------------------------------------------------------------------------------- /AirTNG.Web/Models/ManageViewModels.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Web.Mvc; 4 | using Microsoft.AspNet.Identity; 5 | using Microsoft.Owin.Security; 6 | 7 | namespace AirTNG.Web.Models 8 | { 9 | public class IndexViewModel 10 | { 11 | public bool HasPassword { get; set; } 12 | public IList Logins { get; set; } 13 | public string PhoneNumber { get; set; } 14 | public bool TwoFactor { get; set; } 15 | public bool BrowserRemembered { get; set; } 16 | } 17 | 18 | public class ManageLoginsViewModel 19 | { 20 | public IList CurrentLogins { get; set; } 21 | public IList OtherLogins { get; set; } 22 | } 23 | 24 | public class FactorViewModel 25 | { 26 | public string Purpose { get; set; } 27 | } 28 | 29 | public class SetPasswordViewModel 30 | { 31 | [Required] 32 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 33 | [DataType(DataType.Password)] 34 | [Display(Name = "New password")] 35 | public string NewPassword { get; set; } 36 | 37 | [DataType(DataType.Password)] 38 | [Display(Name = "Confirm new password")] 39 | [System.ComponentModel.DataAnnotations.Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 40 | public string ConfirmPassword { get; set; } 41 | } 42 | 43 | public class ChangePasswordViewModel 44 | { 45 | [Required] 46 | [DataType(DataType.Password)] 47 | [Display(Name = "Current password")] 48 | public string OldPassword { get; set; } 49 | 50 | [Required] 51 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 52 | [DataType(DataType.Password)] 53 | [Display(Name = "New password")] 54 | public string NewPassword { get; set; } 55 | 56 | [DataType(DataType.Password)] 57 | [Display(Name = "Confirm new password")] 58 | [System.ComponentModel.DataAnnotations.Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 59 | public string ConfirmPassword { get; set; } 60 | } 61 | 62 | public class AddPhoneNumberViewModel 63 | { 64 | [Required] 65 | [Phone] 66 | [Display(Name = "Phone Number")] 67 | public string Number { get; set; } 68 | } 69 | 70 | public class VerifyPhoneNumberViewModel 71 | { 72 | [Required] 73 | [Display(Name = "Code")] 74 | public string Code { get; set; } 75 | 76 | [Required] 77 | [Phone] 78 | [Display(Name = "Phone Number")] 79 | public string PhoneNumber { get; set; } 80 | } 81 | 82 | public class ConfigureTwoFactorViewModel 83 | { 84 | public string SelectedProvider { get; set; } 85 | public ICollection Providers { get; set; } 86 | } 87 | } -------------------------------------------------------------------------------- /AirTNG.Web/Models/Repository/ReservationsRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data.Entity; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace AirTNG.Web.Models.Repository 7 | { 8 | public interface IReservationsRepository 9 | { 10 | Task> FindPendingReservationsAsync(); 11 | Task FindFirstPendingReservationByHostAsync(string userId); 12 | Task CreateAsync(Reservation reservation); 13 | Task UpdateAsync(Reservation reservation); 14 | } 15 | 16 | public class ReservationsRepository : IReservationsRepository 17 | { 18 | private readonly ApplicationDbContext _context; 19 | 20 | public ReservationsRepository() 21 | { 22 | _context = new ApplicationDbContext(); 23 | } 24 | 25 | public async Task> FindPendingReservationsAsync() 26 | { 27 | return await _context.Reservations 28 | .Where(r => r.Status == ReservationStatus.Pending).ToListAsync(); 29 | } 30 | 31 | public async Task CreateAsync(Reservation reservation) 32 | { 33 | _context.Reservations.Add(reservation); 34 | return await _context.SaveChangesAsync(); 35 | } 36 | 37 | public async Task UpdateAsync(Reservation reservation) 38 | { 39 | _context.Entry(reservation).State = EntityState.Modified; 40 | return await _context.SaveChangesAsync(); 41 | } 42 | 43 | public async Task FindFirstPendingReservationByHostAsync(string userId) 44 | { 45 | return await _context.Reservations.FirstAsync( 46 | r => r.VacationProperty.UserId == userId && r.Status == ReservationStatus.Pending); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /AirTNG.Web/Models/Repository/UsersRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Entity; 2 | using System.Threading.Tasks; 3 | 4 | namespace AirTNG.Web.Models.Repository 5 | { 6 | public interface IUsersRepository 7 | { 8 | Task FindByPhoneNumberAsync(string phoneNumber); 9 | } 10 | 11 | public class UsersRepository : IUsersRepository 12 | { 13 | private readonly ApplicationDbContext _context; 14 | 15 | public UsersRepository() 16 | { 17 | _context = new ApplicationDbContext(); 18 | } 19 | 20 | public async Task FindByPhoneNumberAsync(string phoneNumber) 21 | { 22 | return await _context.Users.FirstAsync(u => u.PhoneNumber == phoneNumber); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /AirTNG.Web/Models/Repository/VacationPropertiesRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data.Entity; 3 | using System.Threading.Tasks; 4 | 5 | namespace AirTNG.Web.Models.Repository 6 | { 7 | public interface IVacationPropertiesRepository 8 | { 9 | Task> AllAsync(); 10 | Task CreateAsync(VacationProperty property); 11 | Task UpdateAsync(VacationProperty property); 12 | Task FindAsync(int id); 13 | } 14 | 15 | public class VacationPropertiesRepository : IVacationPropertiesRepository 16 | { 17 | private readonly ApplicationDbContext _context; 18 | 19 | public VacationPropertiesRepository() 20 | { 21 | _context = new ApplicationDbContext(); 22 | } 23 | 24 | public async Task> AllAsync() 25 | { 26 | return await _context.VacationProperties.ToListAsync(); 27 | } 28 | 29 | public async Task CreateAsync(VacationProperty property) 30 | { 31 | _context.VacationProperties.Add(property); 32 | return await _context.SaveChangesAsync(); 33 | } 34 | 35 | public async Task UpdateAsync(VacationProperty property) 36 | { 37 | _context.Entry(property).State = EntityState.Modified; 38 | return await _context.SaveChangesAsync(); 39 | } 40 | 41 | public async Task FindAsync(int id) 42 | { 43 | return await _context.VacationProperties.FindAsync(id); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /AirTNG.Web/Models/Reservation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace AirTNG.Web.Models 5 | { 6 | public class Reservation 7 | { 8 | public int Id { get; set; } 9 | public string Name { get; set; } 10 | public string PhoneNumber { get; set; } 11 | public ReservationStatus Status { get; set; } 12 | public string Message { get; set; } 13 | public DateTime CreatedAt { get; set; } 14 | public int VactionPropertyId { get; set; } 15 | [ForeignKey("VactionPropertyId")] 16 | public virtual VacationProperty VacationProperty { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /AirTNG.Web/Models/ReservationStatus.cs: -------------------------------------------------------------------------------- 1 | namespace AirTNG.Web.Models 2 | { 3 | public enum ReservationStatus 4 | { 5 | Pending, 6 | Confirmed, 7 | Rejected 8 | } 9 | } -------------------------------------------------------------------------------- /AirTNG.Web/Models/VacationProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace AirTNG.Web.Models 5 | { 6 | public class VacationProperty 7 | { 8 | public int Id { get; set; } 9 | public string UserId { get; set; } 10 | public virtual ApplicationUser User { get; set; } 11 | public string Description { get; set; } 12 | public string ImageUrl { get; set; } 13 | public DateTime CreatedAt { get; set; } 14 | public virtual IList Reservations { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /AirTNG.Web/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("AirTNG.Web")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("AirTNG.Web")] 12 | [assembly: AssemblyCopyright("Copyright © 2015")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("e735b06e-08b6-473d-9c4a-6ada6926c220")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Revision and Build Numbers 32 | // by using the '*' as shown below: 33 | [assembly: AssemblyVersion("1.0.0.0")] 34 | [assembly: AssemblyFileVersion("1.0.0.0")] 35 | -------------------------------------------------------------------------------- /AirTNG.Web/Scripts/_references.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/airtng-csharp/22b4448e23ae012de9a8673bbd0680b565ddc630/AirTNG.Web/Scripts/_references.js -------------------------------------------------------------------------------- /AirTNG.Web/Scripts/jquery.validate.unobtrusive.min.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /* 16 | ** Unobtrusive validation support library for jQuery and jQuery Validate 17 | ** Copyright (C) Microsoft Corporation. All rights reserved. 18 | */ 19 | (function(a){var d=a.validator,b,e="unobtrusiveValidation";function c(a,b,c){a.rules[b]=c;if(a.message)a.messages[b]=a.message}function j(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function f(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function h(a){return a.substr(0,a.lastIndexOf(".")+1)}function g(a,b){if(a.indexOf("*.")===0)a=a.replace("*.",b);return a}function m(c,e){var b=a(this).find("[data-valmsg-for='"+f(e[0].name)+"']"),d=b.attr("data-valmsg-replace"),g=d?a.parseJSON(d)!==false:null;b.removeClass("field-validation-valid").addClass("field-validation-error");c.data("unobtrusiveContainer",b);if(g){b.empty();c.removeClass("input-validation-error").appendTo(b)}else c.hide()}function l(e,d){var c=a(this).find("[data-valmsg-summary=true]"),b=c.find("ul");if(b&&b.length&&d.errorList.length){b.empty();c.addClass("validation-summary-errors").removeClass("validation-summary-valid");a.each(d.errorList,function(){a("
  • ").html(this.message).appendTo(b)})}}function k(d){var b=d.data("unobtrusiveContainer"),c=b.attr("data-valmsg-replace"),e=c?a.parseJSON(c):null;if(b){b.addClass("field-validation-valid").removeClass("field-validation-error");d.removeData("unobtrusiveContainer");e&&b.empty()}}function n(){var b=a(this),c="__jquery_unobtrusive_validation_form_reset";if(b.data(c))return;b.data(c,true);try{b.data("validator").resetForm()}finally{b.removeData(c)}b.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors");b.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}function i(b){var c=a(b),f=c.data(e),i=a.proxy(n,b),g=d.unobtrusive.options||{},h=function(e,d){var c=g[e];c&&a.isFunction(c)&&c.apply(b,d)};if(!f){f={options:{errorClass:g.errorClass||"input-validation-error",errorElement:g.errorElement||"span",errorPlacement:function(){m.apply(b,arguments);h("errorPlacement",arguments)},invalidHandler:function(){l.apply(b,arguments);h("invalidHandler",arguments)},messages:{},rules:{},success:function(){k.apply(b,arguments);h("success",arguments)}},attachValidation:function(){c.off("reset."+e,i).on("reset."+e,i).validate(this.options)},validate:function(){c.validate();return c.valid()}};c.data(e,f)}return f}d.unobtrusive={adapters:[],parseElement:function(b,h){var d=a(b),f=d.parents("form")[0],c,e,g;if(!f)return;c=i(f);c.options.rules[b.name]=e={};c.options.messages[b.name]=g={};a.each(this.adapters,function(){var c="data-val-"+this.name,i=d.attr(c),h={};if(i!==undefined){c+="-";a.each(this.params,function(){h[this]=d.attr(c+this)});this.adapt({element:b,form:f,message:i,params:h,rules:e,messages:g})}});a.extend(e,{__dummy__:true});!h&&c.attachValidation()},parse:function(c){var b=a(c),e=b.parents().addBack().filter("form").add(b.find("form")).has("[data-val=true]");b.find("[data-val=true]").each(function(){d.unobtrusive.parseElement(this,true)});e.each(function(){var a=i(this);a&&a.attachValidation()})}};b=d.unobtrusive.adapters;b.add=function(c,a,b){if(!b){b=a;a=[]}this.push({name:c,params:a,adapt:b});return this};b.addBool=function(a,b){return this.add(a,function(d){c(d,b||a,true)})};b.addMinMax=function(e,g,f,a,d,b){return this.add(e,[d||"min",b||"max"],function(b){var e=b.params.min,d=b.params.max;if(e&&d)c(b,a,[e,d]);else if(e)c(b,g,e);else d&&c(b,f,d)})};b.addSingleVal=function(a,b,d){return this.add(a,[b||"val"],function(e){c(e,d||a,e.params[b])})};d.addMethod("__dummy__",function(){return true});d.addMethod("regex",function(b,c,d){var a;if(this.optional(c))return true;a=(new RegExp(d)).exec(b);return a&&a.index===0&&a[0].length===b.length});d.addMethod("nonalphamin",function(c,d,b){var a;if(b){a=c.match(/\W/g);a=a&&a.length>=b}return a});if(d.methods.extension){b.addSingleVal("accept","mimtype");b.addSingleVal("extension","extension")}else b.addSingleVal("extension","extension","accept");b.addSingleVal("regex","pattern");b.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");b.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range");b.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength");b.add("equalto",["other"],function(b){var i=h(b.element.name),j=b.params.other,d=g(j,i),e=a(b.form).find(":input").filter("[name='"+f(d)+"']")[0];c(b,"equalTo",e)});b.add("required",function(a){(a.element.tagName.toUpperCase()!=="INPUT"||a.element.type.toUpperCase()!=="CHECKBOX")&&c(a,"required",true)});b.add("remote",["url","type","additionalfields"],function(b){var d={url:b.params.url,type:b.params.type||"GET",data:{}},e=h(b.element.name);a.each(j(b.params.additionalfields||b.element.name),function(i,h){var c=g(h,e);d.data[c]=function(){var d=a(b.form).find(":input").filter("[name='"+f(c)+"']");return d.is(":checkbox")?d.filter(":checked").val()||d.filter(":hidden").val()||"":d.is(":radio")?d.filter(":checked").val()||"":d.val()}});c(b,"remote",d)});b.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&c(a,"minlength",a.params.min);a.params.nonalphamin&&c(a,"nonalphamin",a.params.nonalphamin);a.params.regex&&c(a,"regex",a.params.regex)});a(function(){d.unobtrusive.parse(document)})})(jQuery); -------------------------------------------------------------------------------- /AirTNG.Web/Scripts/respond.js: -------------------------------------------------------------------------------- 1 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ 2 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */ 3 | (function(w) { 4 | "use strict"; 5 | w.matchMedia = w.matchMedia || function(doc, undefined) { 6 | var bool, docElem = doc.documentElement, refNode = docElem.firstElementChild || docElem.firstChild, fakeBody = doc.createElement("body"), div = doc.createElement("div"); 7 | div.id = "mq-test-1"; 8 | div.style.cssText = "position:absolute;top:-100em"; 9 | fakeBody.style.background = "none"; 10 | fakeBody.appendChild(div); 11 | return function(q) { 12 | div.innerHTML = '­'; 13 | docElem.insertBefore(fakeBody, refNode); 14 | bool = div.offsetWidth === 42; 15 | docElem.removeChild(fakeBody); 16 | return { 17 | matches: bool, 18 | media: q 19 | }; 20 | }; 21 | }(w.document); 22 | })(this); 23 | 24 | /*! Respond.js v1.4.0: min/max-width media query polyfill. (c) Scott Jehl. MIT Lic. j.mp/respondjs */ 25 | (function(w) { 26 | "use strict"; 27 | var respond = {}; 28 | w.respond = respond; 29 | respond.update = function() {}; 30 | var requestQueue = [], xmlHttp = function() { 31 | var xmlhttpmethod = false; 32 | try { 33 | xmlhttpmethod = new w.XMLHttpRequest(); 34 | } catch (e) { 35 | xmlhttpmethod = new w.ActiveXObject("Microsoft.XMLHTTP"); 36 | } 37 | return function() { 38 | return xmlhttpmethod; 39 | }; 40 | }(), ajax = function(url, callback) { 41 | var req = xmlHttp(); 42 | if (!req) { 43 | return; 44 | } 45 | req.open("GET", url, true); 46 | req.onreadystatechange = function() { 47 | if (req.readyState !== 4 || req.status !== 200 && req.status !== 304) { 48 | return; 49 | } 50 | callback(req.responseText); 51 | }; 52 | if (req.readyState === 4) { 53 | return; 54 | } 55 | req.send(null); 56 | }; 57 | respond.ajax = ajax; 58 | respond.queue = requestQueue; 59 | respond.regex = { 60 | media: /@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi, 61 | keyframes: /@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi, 62 | urls: /(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g, 63 | findStyles: /@media *([^\{]+)\{([\S\s]+?)$/, 64 | only: /(only\s+)?([a-zA-Z]+)\s?/, 65 | minw: /\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/, 66 | maxw: /\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/ 67 | }; 68 | respond.mediaQueriesSupported = w.matchMedia && w.matchMedia("only all") !== null && w.matchMedia("only all").matches; 69 | if (respond.mediaQueriesSupported) { 70 | return; 71 | } 72 | var doc = w.document, docElem = doc.documentElement, mediastyles = [], rules = [], appendedEls = [], parsedSheets = {}, resizeThrottle = 30, head = doc.getElementsByTagName("head")[0] || docElem, base = doc.getElementsByTagName("base")[0], links = head.getElementsByTagName("link"), lastCall, resizeDefer, eminpx, getEmValue = function() { 73 | var ret, div = doc.createElement("div"), body = doc.body, originalHTMLFontSize = docElem.style.fontSize, originalBodyFontSize = body && body.style.fontSize, fakeUsed = false; 74 | div.style.cssText = "position:absolute;font-size:1em;width:1em"; 75 | if (!body) { 76 | body = fakeUsed = doc.createElement("body"); 77 | body.style.background = "none"; 78 | } 79 | docElem.style.fontSize = "100%"; 80 | body.style.fontSize = "100%"; 81 | body.appendChild(div); 82 | if (fakeUsed) { 83 | docElem.insertBefore(body, docElem.firstChild); 84 | } 85 | ret = div.offsetWidth; 86 | if (fakeUsed) { 87 | docElem.removeChild(body); 88 | } else { 89 | body.removeChild(div); 90 | } 91 | docElem.style.fontSize = originalHTMLFontSize; 92 | if (originalBodyFontSize) { 93 | body.style.fontSize = originalBodyFontSize; 94 | } 95 | ret = eminpx = parseFloat(ret); 96 | return ret; 97 | }, applyMedia = function(fromResize) { 98 | var name = "clientWidth", docElemProp = docElem[name], currWidth = doc.compatMode === "CSS1Compat" && docElemProp || doc.body[name] || docElemProp, styleBlocks = {}, lastLink = links[links.length - 1], now = new Date().getTime(); 99 | if (fromResize && lastCall && now - lastCall < resizeThrottle) { 100 | w.clearTimeout(resizeDefer); 101 | resizeDefer = w.setTimeout(applyMedia, resizeThrottle); 102 | return; 103 | } else { 104 | lastCall = now; 105 | } 106 | for (var i in mediastyles) { 107 | if (mediastyles.hasOwnProperty(i)) { 108 | var thisstyle = mediastyles[i], min = thisstyle.minw, max = thisstyle.maxw, minnull = min === null, maxnull = max === null, em = "em"; 109 | if (!!min) { 110 | min = parseFloat(min) * (min.indexOf(em) > -1 ? eminpx || getEmValue() : 1); 111 | } 112 | if (!!max) { 113 | max = parseFloat(max) * (max.indexOf(em) > -1 ? eminpx || getEmValue() : 1); 114 | } 115 | if (!thisstyle.hasquery || (!minnull || !maxnull) && (minnull || currWidth >= min) && (maxnull || currWidth <= max)) { 116 | if (!styleBlocks[thisstyle.media]) { 117 | styleBlocks[thisstyle.media] = []; 118 | } 119 | styleBlocks[thisstyle.media].push(rules[thisstyle.rules]); 120 | } 121 | } 122 | } 123 | for (var j in appendedEls) { 124 | if (appendedEls.hasOwnProperty(j)) { 125 | if (appendedEls[j] && appendedEls[j].parentNode === head) { 126 | head.removeChild(appendedEls[j]); 127 | } 128 | } 129 | } 130 | appendedEls.length = 0; 131 | for (var k in styleBlocks) { 132 | if (styleBlocks.hasOwnProperty(k)) { 133 | var ss = doc.createElement("style"), css = styleBlocks[k].join("\n"); 134 | ss.type = "text/css"; 135 | ss.media = k; 136 | head.insertBefore(ss, lastLink.nextSibling); 137 | if (ss.styleSheet) { 138 | ss.styleSheet.cssText = css; 139 | } else { 140 | ss.appendChild(doc.createTextNode(css)); 141 | } 142 | appendedEls.push(ss); 143 | } 144 | } 145 | }, translate = function(styles, href, media) { 146 | var qs = styles.replace(respond.regex.keyframes, "").match(respond.regex.media), ql = qs && qs.length || 0; 147 | href = href.substring(0, href.lastIndexOf("/")); 148 | var repUrls = function(css) { 149 | return css.replace(respond.regex.urls, "$1" + href + "$2$3"); 150 | }, useMedia = !ql && media; 151 | if (href.length) { 152 | href += "/"; 153 | } 154 | if (useMedia) { 155 | ql = 1; 156 | } 157 | for (var i = 0; i < ql; i++) { 158 | var fullq, thisq, eachq, eql; 159 | if (useMedia) { 160 | fullq = media; 161 | rules.push(repUrls(styles)); 162 | } else { 163 | fullq = qs[i].match(respond.regex.findStyles) && RegExp.$1; 164 | rules.push(RegExp.$2 && repUrls(RegExp.$2)); 165 | } 166 | eachq = fullq.split(","); 167 | eql = eachq.length; 168 | for (var j = 0; j < eql; j++) { 169 | thisq = eachq[j]; 170 | mediastyles.push({ 171 | media: thisq.split("(")[0].match(respond.regex.only) && RegExp.$2 || "all", 172 | rules: rules.length - 1, 173 | hasquery: thisq.indexOf("(") > -1, 174 | minw: thisq.match(respond.regex.minw) && parseFloat(RegExp.$1) + (RegExp.$2 || ""), 175 | maxw: thisq.match(respond.regex.maxw) && parseFloat(RegExp.$1) + (RegExp.$2 || "") 176 | }); 177 | } 178 | } 179 | applyMedia(); 180 | }, makeRequests = function() { 181 | if (requestQueue.length) { 182 | var thisRequest = requestQueue.shift(); 183 | ajax(thisRequest.href, function(styles) { 184 | translate(styles, thisRequest.href, thisRequest.media); 185 | parsedSheets[thisRequest.href] = true; 186 | w.setTimeout(function() { 187 | makeRequests(); 188 | }, 0); 189 | }); 190 | } 191 | }, ripCSS = function() { 192 | for (var i = 0; i < links.length; i++) { 193 | var sheet = links[i], href = sheet.href, media = sheet.media, isCSS = sheet.rel && sheet.rel.toLowerCase() === "stylesheet"; 194 | if (!!href && isCSS && !parsedSheets[href]) { 195 | if (sheet.styleSheet && sheet.styleSheet.rawCssText) { 196 | translate(sheet.styleSheet.rawCssText, href, media); 197 | parsedSheets[href] = true; 198 | } else { 199 | if (!/^([a-zA-Z:]*\/\/)/.test(href) && !base || href.replace(RegExp.$1, "").split("/")[0] === w.location.host) { 200 | if (href.substring(0, 2) === "//") { 201 | href = w.location.protocol + href; 202 | } 203 | requestQueue.push({ 204 | href: href, 205 | media: media 206 | }); 207 | } 208 | } 209 | } 210 | } 211 | makeRequests(); 212 | }; 213 | ripCSS(); 214 | respond.update = ripCSS; 215 | respond.getEmValue = getEmValue; 216 | function callMedia() { 217 | applyMedia(true); 218 | } 219 | if (w.addEventListener) { 220 | w.addEventListener("resize", callMedia, false); 221 | } else if (w.attachEvent) { 222 | w.attachEvent("onresize", callMedia); 223 | } 224 | })(this); -------------------------------------------------------------------------------- /AirTNG.Web/Scripts/respond.matchmedia.addListener.min.js: -------------------------------------------------------------------------------- 1 | /*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl 2 | * Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT 3 | * */ 4 | 5 | !function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";if(a.matchMedia&&a.matchMedia("all").addListener)return!1;var b=a.matchMedia,c=b("only all").matches,d=!1,e=0,f=[],g=function(){a.clearTimeout(e),e=a.setTimeout(function(){for(var c=0,d=f.length;d>c;c++){var e=f[c].mql,g=f[c].listeners||[],h=b(e.media).matches;if(h!==e.matches){e.matches=h;for(var i=0,j=g.length;j>i;i++)g[i].call(a,e)}}},30)};a.matchMedia=function(e){var h=b(e),i=[],j=0;return h.addListener=function(b){c&&(d||(d=!0,a.addEventListener("resize",g,!0)),0===j&&(j=f.push({mql:h,listeners:i})),i.push(b))},h.removeListener=function(a){for(var b=0,c=i.length;c>b;b++)i[b]===a&&i.splice(b,1)},h}}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b #mq-test-1 { width: 42px; }',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b@ViewBag.Title. 6 |
    7 |

    8 | Thank you for confirming your email. Please @Html.ActionLink("Click here to Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" }) 9 |

    10 |
    11 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Account/ExternalLoginConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @model AirTNG.Web.Models.ExternalLoginConfirmationViewModel 2 | @{ 3 | ViewBag.Title = "Register"; 4 | } 5 |

    @ViewBag.Title.

    6 |

    Associate your @ViewBag.LoginProvider account.

    7 | 8 | @using (Html.BeginForm("ExternalLoginConfirmation", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 9 | { 10 | @Html.AntiForgeryToken() 11 | 12 |

    Association Form

    13 |
    14 | @Html.ValidationSummary(true, "", new { @class = "text-danger" }) 15 |

    16 | You've successfully authenticated with @ViewBag.LoginProvider. 17 | Please enter a user name for this site below and click the Register button to finish 18 | logging in. 19 |

    20 |
    21 | @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" }) 22 |
    23 | @Html.TextBoxFor(m => m.Email, new { @class = "form-control" }) 24 | @Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" }) 25 |
    26 |
    27 |
    28 |
    29 | 30 |
    31 |
    32 | } 33 | 34 | @section Scripts { 35 | @Scripts.Render("~/bundles/jqueryval") 36 | } 37 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Account/ExternalLoginFailure.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Login Failure"; 3 | } 4 | 5 |
    6 |

    @ViewBag.Title.

    7 |

    Unsuccessful login with service.

    8 |
    9 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Account/ForgotPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model AirTNG.Web.Models.ForgotPasswordViewModel 2 | @{ 3 | ViewBag.Title = "Forgot your password?"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 | 8 | @using (Html.BeginForm("ForgotPassword", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 9 | { 10 | @Html.AntiForgeryToken() 11 |

    Enter your email.

    12 |
    13 | @Html.ValidationSummary("", new { @class = "text-danger" }) 14 |
    15 | @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" }) 16 |
    17 | @Html.TextBoxFor(m => m.Email, new { @class = "form-control" }) 18 |
    19 |
    20 |
    21 |
    22 | 23 |
    24 |
    25 | } 26 | 27 | @section Scripts { 28 | @Scripts.Render("~/bundles/jqueryval") 29 | } 30 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Account/ForgotPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Forgot Password Confirmation"; 3 | } 4 | 5 |
    6 |

    @ViewBag.Title.

    7 |
    8 |
    9 |

    10 | Please check your email to reset your password. 11 |

    12 |
    13 | 14 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @using AirTNG.Web.Models 2 | @model LoginViewModel 3 | @{ 4 | ViewBag.Title = "User Auth | Login"; 5 | } 6 | 7 | @section nav_bg 8 | { 9 | 10 | 12 | } 13 | 14 |
    15 | 33 |
    34 | 35 | @section Scripts { 36 | @Scripts.Render("~/bundles/jqueryval") 37 | } -------------------------------------------------------------------------------- /AirTNG.Web/Views/Account/Register.cshtml: -------------------------------------------------------------------------------- 1 | @model AirTNG.Web.Models.RegisterViewModel 2 | @{ 3 | ViewBag.Title = "User Auth | Login"; 4 | } 5 | 6 | @section nav_bg 7 | { 8 | 9 | 11 | } 12 | 13 |
    14 | 48 |
    49 | 50 | @section Scripts { 51 | @Scripts.Render("~/bundles/jqueryval") 52 | } 53 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Account/ResetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model AirTNG.Web.Models.ResetPasswordViewModel 2 | @{ 3 | ViewBag.Title = "Reset password"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 | 8 | @using (Html.BeginForm("ResetPassword", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 9 | { 10 | @Html.AntiForgeryToken() 11 |

    Reset your password.

    12 |
    13 | @Html.ValidationSummary("", new { @class = "text-danger" }) 14 | @Html.HiddenFor(model => model.Code) 15 |
    16 | @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" }) 17 |
    18 | @Html.TextBoxFor(m => m.Email, new { @class = "form-control" }) 19 |
    20 |
    21 |
    22 | @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" }) 23 |
    24 | @Html.PasswordFor(m => m.Password, new { @class = "form-control" }) 25 |
    26 |
    27 |
    28 | @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" }) 29 |
    30 | @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" }) 31 |
    32 |
    33 |
    34 |
    35 | 36 |
    37 |
    38 | } 39 | 40 | @section Scripts { 41 | @Scripts.Render("~/bundles/jqueryval") 42 | } 43 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Account/ResetPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Reset password confirmation"; 3 | } 4 | 5 |
    6 |

    @ViewBag.Title.

    7 |
    8 |
    9 |

    10 | Your password has been reset. Please @Html.ActionLink("click here to log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" }) 11 |

    12 |
    13 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Account/SendCode.cshtml: -------------------------------------------------------------------------------- 1 | @model AirTNG.Web.Models.SendCodeViewModel 2 | @{ 3 | ViewBag.Title = "Send"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 | 8 | @using (Html.BeginForm("SendCode", "Account", new { ReturnUrl = Model.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" })) { 9 | @Html.AntiForgeryToken() 10 | @Html.Hidden("rememberMe", @Model.RememberMe) 11 |

    Send verification code

    12 |
    13 |
    14 |
    15 | Select Two-Factor Authentication Provider: 16 | @Html.DropDownListFor(model => model.SelectedProvider, Model.Providers) 17 | 18 |
    19 |
    20 | } 21 | 22 | @section Scripts { 23 | @Scripts.Render("~/bundles/jqueryval") 24 | } 25 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Account/VerifyCode.cshtml: -------------------------------------------------------------------------------- 1 | @model AirTNG.Web.Models.VerifyCodeViewModel 2 | @{ 3 | ViewBag.Title = "Verify"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 | 8 | @using (Html.BeginForm("VerifyCode", "Account", new { ReturnUrl = Model.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" })) { 9 | @Html.AntiForgeryToken() 10 | @Html.Hidden("provider", @Model.Provider) 11 | @Html.Hidden("rememberMe", @Model.RememberMe) 12 |

    Enter verification code

    13 |
    14 | @Html.ValidationSummary("", new { @class = "text-danger" }) 15 |
    16 | @Html.LabelFor(m => m.Code, new { @class = "col-md-2 control-label" }) 17 |
    18 | @Html.TextBoxFor(m => m.Code, new { @class = "form-control" }) 19 |
    20 |
    21 |
    22 |
    23 |
    24 | @Html.CheckBoxFor(m => m.RememberBrowser) 25 | @Html.LabelFor(m => m.RememberBrowser) 26 |
    27 |
    28 |
    29 |
    30 |
    31 | 32 |
    33 |
    34 | } 35 | 36 | @section Scripts { 37 | @Scripts.Render("~/bundles/jqueryval") 38 | } 39 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Account/_ExternalLoginsListPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.Owin.Security 2 | @model AirTNG.Web.Models.ExternalLoginListViewModel 3 | 4 |

    Use another service to log in.

    5 |
    6 | @{ 7 | var loginProviders = Context.GetOwinContext().Authentication.GetExternalAuthenticationTypes(); 8 | if (loginProviders.Count() == 0) { 9 |
    10 |

    11 | There are no external authentication services configured. See this article 12 | for details on setting up this ASP.NET application to support logging in via external services. 13 |

    14 |
    15 | } 16 | else { 17 | using (Html.BeginForm("ExternalLogin", "Account", new { ReturnUrl = Model.ReturnUrl })) { 18 | @Html.AntiForgeryToken() 19 |
    20 |

    21 | @foreach (AuthenticationDescription p in loginProviders) { 22 | 23 | } 24 |

    25 |
    26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.CssBody = "cover"; 3 | } 4 | 5 | @section hero 6 | { 7 |
    8 |

    Lodging fit for a captain

    9 |

    The Next Generation of vacation rentals.

    10 | 11 | @Html.ActionLink("View Properties", "Index", "VacationProperties", null, new { @class = "btn btn-transparent btn-lg" }) 12 |
    13 | } 14 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Manage/AddPhoneNumber.cshtml: -------------------------------------------------------------------------------- 1 | @model AirTNG.Web.Models.AddPhoneNumberViewModel 2 | @{ 3 | ViewBag.Title = "Phone Number"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 | 8 | @using (Html.BeginForm("AddPhoneNumber", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 9 | { 10 | @Html.AntiForgeryToken() 11 |

    Add a phone number

    12 |
    13 | @Html.ValidationSummary("", new { @class = "text-danger" }) 14 |
    15 | @Html.LabelFor(m => m.Number, new { @class = "col-md-2 control-label" }) 16 |
    17 | @Html.TextBoxFor(m => m.Number, new { @class = "form-control" }) 18 |
    19 |
    20 |
    21 |
    22 | 23 |
    24 |
    25 | } 26 | 27 | @section Scripts { 28 | @Scripts.Render("~/bundles/jqueryval") 29 | } 30 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Manage/ChangePassword.cshtml: -------------------------------------------------------------------------------- 1 | @model AirTNG.Web.Models.ChangePasswordViewModel 2 | @{ 3 | ViewBag.Title = "Change Password"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 | 8 | @using (Html.BeginForm("ChangePassword", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 9 | { 10 | @Html.AntiForgeryToken() 11 |

    Change Password Form

    12 |
    13 | @Html.ValidationSummary("", new { @class = "text-danger" }) 14 |
    15 | @Html.LabelFor(m => m.OldPassword, new { @class = "col-md-2 control-label" }) 16 |
    17 | @Html.PasswordFor(m => m.OldPassword, new { @class = "form-control" }) 18 |
    19 |
    20 |
    21 | @Html.LabelFor(m => m.NewPassword, new { @class = "col-md-2 control-label" }) 22 |
    23 | @Html.PasswordFor(m => m.NewPassword, new { @class = "form-control" }) 24 |
    25 |
    26 |
    27 | @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" }) 28 |
    29 | @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" }) 30 |
    31 |
    32 |
    33 |
    34 | 35 |
    36 |
    37 | } 38 | @section Scripts { 39 | @Scripts.Render("~/bundles/jqueryval") 40 | } -------------------------------------------------------------------------------- /AirTNG.Web/Views/Manage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model AirTNG.Web.Models.IndexViewModel 2 | @{ 3 | ViewBag.Title = "Manage"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 | 8 |

    @ViewBag.StatusMessage

    9 |
    10 |

    Change your account settings

    11 |
    12 |
    13 |
    Password:
    14 |
    15 | [ 16 | @if (Model.HasPassword) 17 | { 18 | @Html.ActionLink("Change your password", "ChangePassword") 19 | } 20 | else 21 | { 22 | @Html.ActionLink("Create", "SetPassword") 23 | } 24 | ] 25 |
    26 |
    External Logins:
    27 |
    28 | @Model.Logins.Count [ 29 | @Html.ActionLink("Manage", "ManageLogins") ] 30 |
    31 | @* 32 | Phone Numbers can used as a second factor of verification in a two-factor authentication system. 33 | 34 | See this article 35 | for details on setting up this ASP.NET application to support two-factor authentication using SMS. 36 | 37 | Uncomment the following block after you have set up two-factor authentication 38 | *@ 39 | @* 40 |
    Phone Number:
    41 |
    42 | @(Model.PhoneNumber ?? "None") [ 43 | @if (Model.PhoneNumber != null) 44 | { 45 | @Html.ActionLink("Change", "AddPhoneNumber") 46 | @:  |  47 | @Html.ActionLink("Remove", "RemovePhoneNumber") 48 | } 49 | else 50 | { 51 | @Html.ActionLink("Add", "AddPhoneNumber") 52 | } 53 | ] 54 |
    55 | *@ 56 |
    Two-Factor Authentication:
    57 |
    58 |

    59 | There are no two-factor authentication providers configured. See this article 60 | for details on setting up this ASP.NET application to support two-factor authentication. 61 |

    62 | @*@if (Model.TwoFactor) 63 | { 64 | using (Html.BeginForm("DisableTwoFactorAuthentication", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 65 | { 66 | @Html.AntiForgeryToken() 67 | Enabled 68 | 69 | 70 | } 71 | } 72 | else 73 | { 74 | using (Html.BeginForm("EnableTwoFactorAuthentication", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 75 | { 76 | @Html.AntiForgeryToken() 77 | Disabled 78 | 79 | 80 | } 81 | }*@ 82 |
    83 |
    84 |
    85 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Manage/ManageLogins.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.Owin.Security 2 | @model AirTNG.Web.Models.ManageLoginsViewModel 3 | @{ 4 | ViewBag.Title = "Manage your external logins"; 5 | } 6 | 7 |

    @ViewBag.Title.

    8 | 9 |

    @ViewBag.StatusMessage

    10 | @{ 11 | var loginProviders = Context.GetOwinContext().Authentication.GetExternalAuthenticationTypes(); 12 | if (loginProviders.Count() == 0) { 13 |
    14 |

    15 | There are no external authentication services configured. See this article 16 | for details on setting up this ASP.NET application to support logging in via external services. 17 |

    18 |
    19 | } 20 | else 21 | { 22 | if (Model.CurrentLogins.Count > 0) 23 | { 24 |

    Registered Logins

    25 | 26 | 27 | @foreach (var account in Model.CurrentLogins) 28 | { 29 | 30 | 31 | 49 | 50 | } 51 | 52 |
    @account.LoginProvider 32 | @if (ViewBag.ShowRemoveButton) 33 | { 34 | using (Html.BeginForm("RemoveLogin", "Manage")) 35 | { 36 | @Html.AntiForgeryToken() 37 |
    38 | @Html.Hidden("loginProvider", account.LoginProvider) 39 | @Html.Hidden("providerKey", account.ProviderKey) 40 | 41 |
    42 | } 43 | } 44 | else 45 | { 46 | @:   47 | } 48 |
    53 | } 54 | if (Model.OtherLogins.Count > 0) 55 | { 56 | using (Html.BeginForm("LinkLogin", "Manage")) 57 | { 58 | @Html.AntiForgeryToken() 59 |
    60 |

    61 | @foreach (AuthenticationDescription p in Model.OtherLogins) 62 | { 63 | 64 | } 65 |

    66 |
    67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Manage/SetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model AirTNG.Web.Models.SetPasswordViewModel 2 | @{ 3 | ViewBag.Title = "Create Password"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 |

    8 | You do not have a local username/password for this site. Add a local 9 | account so you can log in without an external login. 10 |

    11 | 12 | @using (Html.BeginForm("SetPassword", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 13 | { 14 | @Html.AntiForgeryToken() 15 | 16 |

    Create Local Login

    17 |
    18 | @Html.ValidationSummary("", new { @class = "text-danger" }) 19 |
    20 | @Html.LabelFor(m => m.NewPassword, new { @class = "col-md-2 control-label" }) 21 |
    22 | @Html.PasswordFor(m => m.NewPassword, new { @class = "form-control" }) 23 |
    24 |
    25 |
    26 | @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" }) 27 |
    28 | @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" }) 29 |
    30 |
    31 |
    32 |
    33 | 34 |
    35 |
    36 | } 37 | @section Scripts { 38 | @Scripts.Render("~/bundles/jqueryval") 39 | } -------------------------------------------------------------------------------- /AirTNG.Web/Views/Manage/VerifyPhoneNumber.cshtml: -------------------------------------------------------------------------------- 1 | @model AirTNG.Web.Models.VerifyPhoneNumberViewModel 2 | @{ 3 | ViewBag.Title = "Verify Phone Number"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 | 8 | @using (Html.BeginForm("VerifyPhoneNumber", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 9 | { 10 | @Html.AntiForgeryToken() 11 | @Html.Hidden("phoneNumber", @Model.PhoneNumber) 12 |

    Enter verification code

    13 |
    @ViewBag.Status
    14 |
    15 | @Html.ValidationSummary("", new { @class = "text-danger" }) 16 |
    17 | @Html.LabelFor(m => m.Code, new { @class = "col-md-2 control-label" }) 18 |
    19 | @Html.TextBoxFor(m => m.Code, new { @class = "form-control" }) 20 |
    21 |
    22 |
    23 |
    24 | 25 |
    26 |
    27 | } 28 | 29 | @section Scripts { 30 | @Scripts.Render("~/bundles/jqueryval") 31 | } 32 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Reservations/Create.cshtml: -------------------------------------------------------------------------------- 1 | @model AirTNG.Web.ViewModels.ReservationViewModel 2 | 3 | @{ ViewBag.CssBody = "property-page"; } 4 | 5 |
    6 |
    7 | Vacation property image 8 |
    9 |
    10 |

    @Model.Description

    11 |
    12 |
    13 |
    14 |

    Make a Reservation

    15 |
    16 |
    17 | @using (Html.BeginForm("Create", "Reservations")) 18 | { 19 |
    20 | @Html.LabelFor(x => x.Message) 21 | @Html.TextAreaFor(x => x.Message, new 22 | { 23 | @class = "form-control", 24 | size = "10x10", 25 | placeholder = "Hello! I am hoping to stay in your intergalactic suite..." 26 | }) 27 | @Html.ValidationMessageFor(x => x.Message, "", new { @class = "text-danger" }) 28 |
    29 | 37 | 38 | } 39 |
    40 |
    41 |
    42 |
    -------------------------------------------------------------------------------- /AirTNG.Web/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model HandleErrorInfo 2 | 3 | @{ 4 | ViewBag.Title = "Error"; 5 | } 6 | 7 |

    Error.

    8 |

    An error occurred while processing your request.

    9 | 10 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Shared/Lockout.cshtml: -------------------------------------------------------------------------------- 1 | @model HandleErrorInfo 2 | 3 | @{ 4 | ViewBag.Title = "Locked Out"; 5 | } 6 | 7 |
    8 |

    Locked out.

    9 |

    This account has been locked out, please try again later.

    10 |
    11 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Airtng - The Next Generation of Vacation rentals 6 | @Styles.Render("~/Content/bootstrap") 7 | @Styles.Render("~/Content/font-awesome") 8 | @Styles.Render("~/Content/css") 9 | @Scripts.Render("~/bundles/modernizr") 10 | 11 | 12 | 13 | 14 | @RenderSection("nav_bg", false) 15 | 21 | 22 | @RenderSection("hero", false) 23 | 24 |
    25 | @RenderBody() 26 |
    27 | 28 |
    29 | Made with by your pals 30 | @@twilio 31 |
    32 | 33 | @Scripts.Render("~/bundles/jquery") 34 | @Scripts.Render("~/bundles/bootstrap") 35 | @RenderSection("scripts", false) 36 | 37 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @if (Request.IsAuthenticated) 2 | { 3 | using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @style = "display:none;" })) 4 | { 5 | @Html.AntiForgeryToken() 6 | } 7 |
  • Spock
  • 8 |
  • @Html.ActionLink("New Vacation property", "Create", "VacationProperties")
  • 9 |
  • Log Out
  • 10 | } 11 | else 12 | { 13 |
  • @Html.ActionLink("Sign Up", "Register", "Account", null, new { id = "registerLink" })
  • 14 |
  • @Html.ActionLink("Log In", "Login", "Account", null, new { id = "loginLink" })
  • 15 | } -------------------------------------------------------------------------------- /AirTNG.Web/Views/VacationProperties/Create.cshtml: -------------------------------------------------------------------------------- 1 | @model AirTNG.Web.ViewModels.VacationPropertyViewModel 2 | 3 | @section nav_bg 4 | { 5 | 6 | 8 | } 9 | 10 |
    11 |

    New Vacation Property

    12 | 13 | @Html.Partial("_Form", Model) 14 | 15 | @Html.ActionLink("Back", "Index", "VacationProperties") 16 |
    17 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/VacationProperties/Edit.cshtml: -------------------------------------------------------------------------------- 1 | @model AirTNG.Web.Models.VacationProperty 2 | 3 | @section nav_bg 4 | { 5 | 6 | 8 | } 9 | 10 |
    11 |

    Edit Vacation Property

    12 | 13 | @Html.Partial("_Form", Model) 14 | 15 | @Html.ActionLink("Back", "Index", "VacationProperties") 16 |
    17 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/VacationProperties/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IList 2 | 3 | @section hero 4 | { 5 |
    6 |

    Lodging fit for a captain

    7 |

    The Next Generation of vacation rentals.

    8 |
    9 | } 10 | 11 |
    12 |
    13 | @foreach (var vacationProperty in Model) 14 | { 15 | 21 | } 22 |
    23 |
    -------------------------------------------------------------------------------- /AirTNG.Web/Views/VacationProperties/_Form.cshtml: -------------------------------------------------------------------------------- 1 | @model AirTNG.Web.ViewModels.VacationPropertyViewModel 2 | 3 | @using (Html.BeginForm("Create", "VacationProperties")) 4 | { 5 | @Html.AntiForgeryToken() 6 |
    7 | @Html.LabelFor(x => x.Description) 8 | @Html.TextBoxFor(x => x.Description, new { @class = "form-control" }) 9 | @Html.ValidationMessageFor(x => x.Description, "", new { @class = "text-danger" }) 10 |
    11 |
    12 | @Html.LabelFor(x => x.ImageUrl) 13 | @Html.TextBoxFor(x => x.ImageUrl, new { @class = "form-control" }) 14 | @Html.ValidationMessageFor(x => x.ImageUrl, "", new { @class = "text-danger" }) 15 |
    16 |
    17 | 18 |
    19 | } -------------------------------------------------------------------------------- /AirTNG.Web/Views/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 |
    7 |
    8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /AirTNG.Web/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /AirTNG.Web/Web.Debug.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /AirTNG.Web/Web.Release.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /AirTNG.Web/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 9 |
    10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /AirTNG.Web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/airtng-csharp/22b4448e23ae012de9a8673bbd0680b565ddc630/AirTNG.Web/favicon.ico -------------------------------------------------------------------------------- /AirTNG.Web/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/airtng-csharp/22b4448e23ae012de9a8673bbd0680b565ddc630/AirTNG.Web/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /AirTNG.Web/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/airtng-csharp/22b4448e23ae012de9a8673bbd0680b565ddc630/AirTNG.Web/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /AirTNG.Web/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/airtng-csharp/22b4448e23ae012de9a8673bbd0680b565ddc630/AirTNG.Web/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /AirTNG.Web/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/airtng-csharp/22b4448e23ae012de9a8673bbd0680b565ddc630/AirTNG.Web/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /AirTNG.Web/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/airtng-csharp/22b4448e23ae012de9a8673bbd0680b565ddc630/AirTNG.Web/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /AirTNG.Web/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/airtng-csharp/22b4448e23ae012de9a8673bbd0680b565ddc630/AirTNG.Web/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /AirTNG.Web/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/airtng-csharp/22b4448e23ae012de9a8673bbd0680b565ddc630/AirTNG.Web/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /AirTNG.Web/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/airtng-csharp/22b4448e23ae012de9a8673bbd0680b565ddc630/AirTNG.Web/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /AirTNG.Web/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/airtng-csharp/22b4448e23ae012de9a8673bbd0680b565ddc630/AirTNG.Web/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /AirTNG.Web/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /AirTNG.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2015 4 | VisualStudioVersion = 14.1.20907.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AirTNG.Web", "AirTNG.Web\AirTNG.Web.csproj", "{98EE4018-F658-4FE8-AAFE-26CABF42B885}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AirTNG.Web.Tests", "AirTNG.Web.Tests\AirTNG.Web.Tests.csproj", "{8453894B-2881-4AF5-BF47-E672AACEC871}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {98EE4018-F658-4FE8-AAFE-26CABF42B885}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {98EE4018-F658-4FE8-AAFE-26CABF42B885}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {98EE4018-F658-4FE8-AAFE-26CABF42B885}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {98EE4018-F658-4FE8-AAFE-26CABF42B885}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {8453894B-2881-4AF5-BF47-E672AACEC871}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {8453894B-2881-4AF5-BF47-E672AACEC871}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {8453894B-2881-4AF5-BF47-E672AACEC871}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {8453894B-2881-4AF5-BF47-E672AACEC871}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at open-source@twilio.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Twilio 2 | 3 | All third party contributors acknowledge that any contributions they provide will be made under the same open source license that the open source project is provided under. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Twilio Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Twilio 3 | 4 | 5 | # AirTNG App: Part 1 - Workflow Automation with Twilio - ASP.NET MVC 6 | > We are currently in the process of updating this sample template. If you are encountering any issues with the sample, please open an issue at [github.com/twilio-labs/code-exchange/issues](https://github.com/twilio-labs/code-exchange/issues) and we'll try to help you. 7 | 8 | [![Build status](https://ci.appveyor.com/api/projects/status/t8vnms8v35y1mul4?svg=true)](https://ci.appveyor.com/project/TwilioDevEd/airtng-csharp) 9 | 10 | Learn how to automate your workflow using Twilio's REST API and Twilio SMS. This example app is a vacation rental site, where the host can confirm a reservation via SMS. 11 | 12 | [Read the full tutorial here](https://www.twilio.com/docs/tutorials/walkthrough/workflow-automation/csharp/mvc)! 13 | 14 | ## Local Development 15 | 16 | 1. You will need to configure Twilio to send requests to your application when SMS are received. 17 | 18 | You will need to provision at least one Twilio number with sms capabilities so the application's users can make property reservations. You can buy a number [right here](https://www.twilio.com/user/account/phone-numbers/search). Once you have a number you need to configure it to work with your application. Open [the number management page](https://www.twilio.com/user/account/phone-numbers/incoming) and open a number's configuration by clicking on it. 19 | 20 | Remember that the number where you change the _SMS webhook_ must be the same one you set on the `TwilioPhoneNumber` settings. 21 | 22 | To start using `ngrok` in our project you'll have execute to the following line in the _command prompt_. 23 | 24 | ``` 25 | ngrok http 4567 -host-header="localhost:4567" 26 | ``` 27 | 28 | Keep in mind that our endpoint is: 29 | 30 | ``` 31 | http://.ngrok.io/Reservations/Handle 32 | ``` 33 | 34 | 2. Clone this repository and `cd` into it. 35 | 36 | ``` 37 | git clone git@github.com:TwilioDevEd/airtng-csharp.git 38 | 39 | cd airtng-csharp 40 | ``` 41 | 42 | 3. Create a new file `AirTNG.Web/Local.config` and update the content. 43 | 44 | ``` 45 | 46 | 47 | 48 | 49 | 50 | 51 | ``` 52 | 53 | 4. Build the solution. 54 | 55 | 5. Run `Update-Database` at [Package Manager 56 | Console](https://docs.nuget.org/consume/package-manager-console) to execute the migrations. 57 | 58 | 6. Run the application. 59 | 60 | 7. Check it out at [http://localhost:4567](http://localhost:4567) 61 | 62 | That's it! 63 | 64 | To let our Twilio Phone number use the callback endpoint we exposed, our development server will need to be publicly accessible. [We recommend using ngrok to solve this problem](https://www.twilio.com/blog/2015/09/6-awesome-reasons-to-use-ngrok-when-testing-webhooks.html). 65 | 66 | ## Meta 67 | 68 | * No warranty expressed or implied. Software is as is. Diggity. 69 | * The CodeExchange repository can be found [here](https://github.com/twilio-labs/code-exchange/). 70 | * [MIT License](http://www.opensource.org/licenses/mit-license.html) 71 | * Lovingly crafted by Twilio Developer Education. 72 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | branches: 3 | except: 4 | - gh-pages 5 | before_build: 6 | - ps: nuget restore 7 | build: 8 | verbosity: minimal 9 | -------------------------------------------------------------------------------- /webhook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/airtng-csharp/22b4448e23ae012de9a8673bbd0680b565ddc630/webhook.png --------------------------------------------------------------------------------