Fix casing on view models
- Light renaming / clean-up - Update dependencies
This commit is contained in:
parent
47fb9884f1
commit
e1bdad15f7
@ -1,9 +1,11 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<AssemblyVersion>7.6.0.0</AssemblyVersion>
|
||||
<FileVersion>7.6.0.0</FileVersion>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AssemblyVersion>8.0.0.0</AssemblyVersion>
|
||||
<FileVersion>8.0.0.0</FileVersion>
|
||||
<Authors>danieljsummers</Authors>
|
||||
<Company>Bit Badger Solutions</Company>
|
||||
<Version>7.6.0</Version>
|
||||
<Version>8.0.0</Version>
|
||||
<DebugType>Embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
@ -20,10 +20,11 @@ module private Helpers =
|
||||
.ThenByDescending (fun req -> req.enteredDate)
|
||||
|
||||
/// Paginate a prayer request query
|
||||
let paginate pageNbr pageSize (q : IQueryable<PrayerRequest>) =
|
||||
q.Skip((pageNbr - 1) * pageSize).Take pageSize
|
||||
let paginate (pageNbr : int) pageSize (q : IQueryable<PrayerRequest>) =
|
||||
if pageNbr > 0 then q.Skip((pageNbr - 1) * pageSize).Take pageSize else q
|
||||
|
||||
|
||||
open System
|
||||
open System.Collections.Generic
|
||||
open Microsoft.EntityFrameworkCore
|
||||
open Microsoft.FSharpLu
|
||||
@ -95,7 +96,7 @@ type AppDbContext with
|
||||
this.PrayerRequests.Where(fun req -> req.smallGroupId = grp.smallGroupId)
|
||||
|> function
|
||||
| q when activeOnly ->
|
||||
let asOf = theDate.AddDays(-(float grp.preferences.daysToExpire)).Date
|
||||
let asOf = DateTime (theDate.AddDays(-(float grp.preferences.daysToExpire)).Date.Ticks, DateTimeKind.Utc)
|
||||
q.Where(fun req ->
|
||||
( req.updatedDate > asOf
|
||||
|| req.expiration = Manual
|
||||
|
@ -642,18 +642,25 @@ with
|
||||
and [<CLIMutable; NoComparison; NoEquality>] SmallGroup =
|
||||
{ /// The Id of this small group
|
||||
smallGroupId : SmallGroupId
|
||||
|
||||
/// The church to which this group belongs
|
||||
churchId : ChurchId
|
||||
|
||||
/// The name of the group
|
||||
name : string
|
||||
|
||||
/// The church to which this small group belongs
|
||||
church : Church
|
||||
|
||||
/// The preferences for the request list
|
||||
preferences : ListPreferences
|
||||
|
||||
/// The members of the group
|
||||
members : ICollection<Member>
|
||||
|
||||
/// Prayer requests for this small group
|
||||
prayerRequests : ICollection<PrayerRequest>
|
||||
|
||||
/// The users authorized to manage this group
|
||||
users : ICollection<UserSmallGroup>
|
||||
}
|
||||
@ -699,10 +706,13 @@ with
|
||||
and [<CLIMutable; NoComparison; NoEquality>] TimeZone =
|
||||
{ /// The Id for this time zone (uses tzdata names)
|
||||
timeZoneId : TimeZoneId
|
||||
|
||||
/// The description of this time zone
|
||||
description : string
|
||||
|
||||
/// The order in which this timezone should be displayed
|
||||
sortOrder : int
|
||||
|
||||
/// Whether this timezone is active
|
||||
isActive : bool
|
||||
}
|
||||
@ -731,18 +741,25 @@ with
|
||||
and [<CLIMutable; NoComparison; NoEquality>] User =
|
||||
{ /// The Id of this user
|
||||
userId : UserId
|
||||
|
||||
/// The first name of this user
|
||||
firstName : string
|
||||
|
||||
/// The last name of this user
|
||||
lastName : string
|
||||
|
||||
/// The e-mail address of the user
|
||||
emailAddress : string
|
||||
|
||||
/// Whether this user is a PrayerTracker system administrator
|
||||
isAdmin : bool
|
||||
|
||||
/// The user's hashed password
|
||||
passwordHash : string
|
||||
|
||||
/// The salt for the user's hashed password
|
||||
salt : Guid option
|
||||
|
||||
/// The small groups which this user is authorized
|
||||
smallGroups : ICollection<UserSmallGroup>
|
||||
}
|
||||
@ -785,10 +802,13 @@ with
|
||||
and [<CLIMutable; NoComparison; NoEquality>] UserSmallGroup =
|
||||
{ /// The Id of the user who has access to the small group
|
||||
userId : UserId
|
||||
|
||||
/// The Id of the small group to which the user has access
|
||||
smallGroupId : SmallGroupId
|
||||
|
||||
/// The user who has access to the small group
|
||||
user : User
|
||||
|
||||
/// The small group to which the user has access
|
||||
smallGroup : SmallGroup
|
||||
}
|
||||
|
@ -15,8 +15,9 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FSharp.EFCore.OptionConverter" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.FSharpLu" Version="0.11.7" />
|
||||
<PackageReference Include="NodaTime" Version="3.0.5" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.10" />
|
||||
<PackageReference Include="NodaTime" Version="3.1.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.5" />
|
||||
<PackageReference Update="FSharp.Core" Version="6.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -7,356 +7,369 @@ open System
|
||||
|
||||
[<Tests>]
|
||||
let asOfDateDisplayTests =
|
||||
testList "AsOfDateDisplay" [
|
||||
test "NoDisplay code is correct" {
|
||||
Expect.equal NoDisplay.code "N" "The code for NoDisplay should have been \"N\""
|
||||
}
|
||||
test "ShortDate code is correct" {
|
||||
Expect.equal ShortDate.code "S" "The code for ShortDate should have been \"S\""
|
||||
}
|
||||
test "LongDate code is correct" {
|
||||
Expect.equal LongDate.code "L" "The code for LongDate should have been \"N\""
|
||||
}
|
||||
test "fromCode N should return NoDisplay" {
|
||||
Expect.equal (AsOfDateDisplay.fromCode "N") NoDisplay "\"N\" should have been converted to NoDisplay"
|
||||
}
|
||||
test "fromCode S should return ShortDate" {
|
||||
Expect.equal (AsOfDateDisplay.fromCode "S") ShortDate "\"S\" should have been converted to ShortDate"
|
||||
}
|
||||
test "fromCode L should return LongDate" {
|
||||
Expect.equal (AsOfDateDisplay.fromCode "L") LongDate "\"L\" should have been converted to LongDate"
|
||||
}
|
||||
test "fromCode X should raise" {
|
||||
Expect.throws (fun () -> AsOfDateDisplay.fromCode "X" |> ignore)
|
||||
"An unknown code should have raised an exception"
|
||||
}
|
||||
testList "AsOfDateDisplay" [
|
||||
test "NoDisplay code is correct" {
|
||||
Expect.equal NoDisplay.code "N" "The code for NoDisplay should have been \"N\""
|
||||
}
|
||||
test "ShortDate code is correct" {
|
||||
Expect.equal ShortDate.code "S" "The code for ShortDate should have been \"S\""
|
||||
}
|
||||
test "LongDate code is correct" {
|
||||
Expect.equal LongDate.code "L" "The code for LongDate should have been \"N\""
|
||||
}
|
||||
test "fromCode N should return NoDisplay" {
|
||||
Expect.equal (AsOfDateDisplay.fromCode "N") NoDisplay "\"N\" should have been converted to NoDisplay"
|
||||
}
|
||||
test "fromCode S should return ShortDate" {
|
||||
Expect.equal (AsOfDateDisplay.fromCode "S") ShortDate "\"S\" should have been converted to ShortDate"
|
||||
}
|
||||
test "fromCode L should return LongDate" {
|
||||
Expect.equal (AsOfDateDisplay.fromCode "L") LongDate "\"L\" should have been converted to LongDate"
|
||||
}
|
||||
test "fromCode X should raise" {
|
||||
Expect.throws (fun () -> AsOfDateDisplay.fromCode "X" |> ignore)
|
||||
"An unknown code should have raised an exception"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let churchTests =
|
||||
testList "Church" [
|
||||
test "empty is as expected" {
|
||||
let mt = Church.empty
|
||||
Expect.equal mt.churchId Guid.Empty "The church ID should have been an empty GUID"
|
||||
Expect.equal mt.name "" "The name should have been blank"
|
||||
Expect.equal mt.city "" "The city should have been blank"
|
||||
Expect.equal mt.st "" "The state should have been blank"
|
||||
Expect.isFalse mt.hasInterface "The church should not show that it has an interface"
|
||||
Expect.isNone mt.interfaceAddress "The interface address should not exist"
|
||||
Expect.isNotNull mt.smallGroups "The small groups navigation property should not be null"
|
||||
Expect.isEmpty mt.smallGroups "There should be no small groups for an empty church"
|
||||
}
|
||||
testList "Church" [
|
||||
test "empty is as expected" {
|
||||
let mt = Church.empty
|
||||
Expect.equal mt.churchId Guid.Empty "The church ID should have been an empty GUID"
|
||||
Expect.equal mt.name "" "The name should have been blank"
|
||||
Expect.equal mt.city "" "The city should have been blank"
|
||||
Expect.equal mt.st "" "The state should have been blank"
|
||||
Expect.isFalse mt.hasInterface "The church should not show that it has an interface"
|
||||
Expect.isNone mt.interfaceAddress "The interface address should not exist"
|
||||
Expect.isNotNull mt.smallGroups "The small groups navigation property should not be null"
|
||||
Expect.isEmpty mt.smallGroups "There should be no small groups for an empty church"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let emailFormatTests =
|
||||
testList "EmailFormat" [
|
||||
test "HtmlFormat code is correct" {
|
||||
Expect.equal HtmlFormat.code "H" "The code for HtmlFormat should have been \"H\""
|
||||
}
|
||||
test "PlainTextFormat code is correct" {
|
||||
Expect.equal PlainTextFormat.code "P" "The code for PlainTextFormat should have been \"P\""
|
||||
}
|
||||
test "fromCode H should return HtmlFormat" {
|
||||
Expect.equal (EmailFormat.fromCode "H") HtmlFormat "\"H\" should have been converted to HtmlFormat"
|
||||
}
|
||||
test "fromCode P should return ShortDate" {
|
||||
Expect.equal (EmailFormat.fromCode "P") PlainTextFormat "\"P\" should have been converted to PlainTextFormat"
|
||||
}
|
||||
test "fromCode Z should raise" {
|
||||
Expect.throws (fun () -> EmailFormat.fromCode "Z" |> ignore) "An unknown code should have raised an exception"
|
||||
}
|
||||
testList "EmailFormat" [
|
||||
test "HtmlFormat code is correct" {
|
||||
Expect.equal HtmlFormat.code "H" "The code for HtmlFormat should have been \"H\""
|
||||
}
|
||||
test "PlainTextFormat code is correct" {
|
||||
Expect.equal PlainTextFormat.code "P" "The code for PlainTextFormat should have been \"P\""
|
||||
}
|
||||
test "fromCode H should return HtmlFormat" {
|
||||
Expect.equal (EmailFormat.fromCode "H") HtmlFormat "\"H\" should have been converted to HtmlFormat"
|
||||
}
|
||||
test "fromCode P should return ShortDate" {
|
||||
Expect.equal (EmailFormat.fromCode "P") PlainTextFormat
|
||||
"\"P\" should have been converted to PlainTextFormat"
|
||||
}
|
||||
test "fromCode Z should raise" {
|
||||
Expect.throws (fun () -> EmailFormat.fromCode "Z" |> ignore)
|
||||
"An unknown code should have raised an exception"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let expirationTests =
|
||||
testList "Expiration" [
|
||||
test "Automatic code is correct" {
|
||||
Expect.equal Automatic.code "A" "The code for Automatic should have been \"A\""
|
||||
}
|
||||
test "Manual code is correct" {
|
||||
Expect.equal Manual.code "M" "The code for Manual should have been \"M\""
|
||||
}
|
||||
test "Forced code is correct" {
|
||||
Expect.equal Forced.code "F" "The code for Forced should have been \"F\""
|
||||
}
|
||||
test "fromCode A should return Automatic" {
|
||||
Expect.equal (Expiration.fromCode "A") Automatic "\"A\" should have been converted to Automatic"
|
||||
}
|
||||
test "fromCode M should return Manual" {
|
||||
Expect.equal (Expiration.fromCode "M") Manual "\"M\" should have been converted to Manual"
|
||||
}
|
||||
test "fromCode F should return Forced" {
|
||||
Expect.equal (Expiration.fromCode "F") Forced "\"F\" should have been converted to Forced"
|
||||
}
|
||||
test "fromCode V should raise" {
|
||||
Expect.throws (fun () -> Expiration.fromCode "V" |> ignore) "An unknown code should have raised an exception"
|
||||
}
|
||||
testList "Expiration" [
|
||||
test "Automatic code is correct" {
|
||||
Expect.equal Automatic.code "A" "The code for Automatic should have been \"A\""
|
||||
}
|
||||
test "Manual code is correct" {
|
||||
Expect.equal Manual.code "M" "The code for Manual should have been \"M\""
|
||||
}
|
||||
test "Forced code is correct" {
|
||||
Expect.equal Forced.code "F" "The code for Forced should have been \"F\""
|
||||
}
|
||||
test "fromCode A should return Automatic" {
|
||||
Expect.equal (Expiration.fromCode "A") Automatic "\"A\" should have been converted to Automatic"
|
||||
}
|
||||
test "fromCode M should return Manual" {
|
||||
Expect.equal (Expiration.fromCode "M") Manual "\"M\" should have been converted to Manual"
|
||||
}
|
||||
test "fromCode F should return Forced" {
|
||||
Expect.equal (Expiration.fromCode "F") Forced "\"F\" should have been converted to Forced"
|
||||
}
|
||||
test "fromCode V should raise" {
|
||||
Expect.throws (fun () -> Expiration.fromCode "V" |> ignore)
|
||||
"An unknown code should have raised an exception"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let listPreferencesTests =
|
||||
testList "ListPreferences" [
|
||||
test "empty is as expected" {
|
||||
let mt = ListPreferences.empty
|
||||
Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID"
|
||||
Expect.equal mt.daysToExpire 14 "The default days to expire should have been 14"
|
||||
Expect.equal mt.daysToKeepNew 7 "The default days to keep new should have been 7"
|
||||
Expect.equal mt.longTermUpdateWeeks 4 "The default long term update weeks should have been 4"
|
||||
Expect.equal mt.emailFromName "PrayerTracker" "The default e-mail from name should have been PrayerTracker"
|
||||
Expect.equal mt.emailFromAddress "prayer@djs-consulting.com"
|
||||
"The default e-mail from address should have been prayer@djs-consulting.com"
|
||||
Expect.equal mt.listFonts "Century Gothic,Tahoma,Luxi Sans,sans-serif" "The default list fonts were incorrect"
|
||||
Expect.equal mt.headingColor "maroon" "The default heading text color should have been maroon"
|
||||
Expect.equal mt.lineColor "navy" "The default heding line color should have been navy"
|
||||
Expect.equal mt.headingFontSize 16 "The default heading font size should have been 16"
|
||||
Expect.equal mt.textFontSize 12 "The default text font size should have been 12"
|
||||
Expect.equal mt.requestSort SortByDate "The default request sort should have been by date"
|
||||
Expect.equal mt.groupPassword "" "The default group password should have been blank"
|
||||
Expect.equal mt.defaultEmailType HtmlFormat "The default e-mail type should have been HTML"
|
||||
Expect.isFalse mt.isPublic "The isPublic flag should not have been set"
|
||||
Expect.equal mt.timeZoneId "America/Denver" "The default time zone should have been America/Denver"
|
||||
Expect.equal mt.timeZone.timeZoneId "" "The default preferences should have included an empty time zone"
|
||||
Expect.equal mt.pageSize 100 "The default page size should have been 100"
|
||||
Expect.equal mt.asOfDateDisplay NoDisplay "The as-of date display should have been No Display"
|
||||
}
|
||||
testList "ListPreferences" [
|
||||
test "empty is as expected" {
|
||||
let mt = ListPreferences.empty
|
||||
Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID"
|
||||
Expect.equal mt.daysToExpire 14 "The default days to expire should have been 14"
|
||||
Expect.equal mt.daysToKeepNew 7 "The default days to keep new should have been 7"
|
||||
Expect.equal mt.longTermUpdateWeeks 4 "The default long term update weeks should have been 4"
|
||||
Expect.equal mt.emailFromName "PrayerTracker" "The default e-mail from name should have been PrayerTracker"
|
||||
Expect.equal mt.emailFromAddress "prayer@djs-consulting.com"
|
||||
"The default e-mail from address should have been prayer@djs-consulting.com"
|
||||
Expect.equal mt.listFonts "Century Gothic,Tahoma,Luxi Sans,sans-serif"
|
||||
"The default list fonts were incorrect"
|
||||
Expect.equal mt.headingColor "maroon" "The default heading text color should have been maroon"
|
||||
Expect.equal mt.lineColor "navy" "The default heding line color should have been navy"
|
||||
Expect.equal mt.headingFontSize 16 "The default heading font size should have been 16"
|
||||
Expect.equal mt.textFontSize 12 "The default text font size should have been 12"
|
||||
Expect.equal mt.requestSort SortByDate "The default request sort should have been by date"
|
||||
Expect.equal mt.groupPassword "" "The default group password should have been blank"
|
||||
Expect.equal mt.defaultEmailType HtmlFormat "The default e-mail type should have been HTML"
|
||||
Expect.isFalse mt.isPublic "The isPublic flag should not have been set"
|
||||
Expect.equal mt.timeZoneId "America/Denver" "The default time zone should have been America/Denver"
|
||||
Expect.equal mt.timeZone.timeZoneId "" "The default preferences should have included an empty time zone"
|
||||
Expect.equal mt.pageSize 100 "The default page size should have been 100"
|
||||
Expect.equal mt.asOfDateDisplay NoDisplay "The as-of date display should have been No Display"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let memberTests =
|
||||
testList "Member" [
|
||||
test "empty is as expected" {
|
||||
let mt = Member.empty
|
||||
Expect.equal mt.memberId Guid.Empty "The member ID should have been an empty GUID"
|
||||
Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID"
|
||||
Expect.equal mt.memberName "" "The member name should have been blank"
|
||||
Expect.equal mt.email "" "The member e-mail address should have been blank"
|
||||
Expect.isNone mt.format "The preferred e-mail format should not exist"
|
||||
Expect.equal mt.smallGroup.smallGroupId Guid.Empty "The small group should have been an empty one"
|
||||
}
|
||||
testList "Member" [
|
||||
test "empty is as expected" {
|
||||
let mt = Member.empty
|
||||
Expect.equal mt.memberId Guid.Empty "The member ID should have been an empty GUID"
|
||||
Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID"
|
||||
Expect.equal mt.memberName "" "The member name should have been blank"
|
||||
Expect.equal mt.email "" "The member e-mail address should have been blank"
|
||||
Expect.isNone mt.format "The preferred e-mail format should not exist"
|
||||
Expect.equal mt.smallGroup.smallGroupId Guid.Empty "The small group should have been an empty one"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let prayerRequestTests =
|
||||
testList "PrayerRequest" [
|
||||
test "empty is as expected" {
|
||||
let mt = PrayerRequest.empty
|
||||
Expect.equal mt.prayerRequestId Guid.Empty "The request ID should have been an empty GUID"
|
||||
Expect.equal mt.requestType CurrentRequest "The request type should have been Current"
|
||||
Expect.equal mt.userId Guid.Empty "The user ID should have been an empty GUID"
|
||||
Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID"
|
||||
Expect.equal mt.enteredDate DateTime.MinValue "The entered date should have been the minimum"
|
||||
Expect.equal mt.updatedDate DateTime.MinValue "The updated date should have been the minimum"
|
||||
Expect.isNone mt.requestor "The requestor should not exist"
|
||||
Expect.equal mt.text "" "The request text should have been blank"
|
||||
Expect.isFalse mt.notifyChaplain "The notify chaplain flag should not have been set"
|
||||
Expect.equal mt.expiration Automatic "The expiration should have been Automatic"
|
||||
Expect.equal mt.user.userId Guid.Empty "The user should have been an empty one"
|
||||
Expect.equal mt.smallGroup.smallGroupId Guid.Empty "The small group should have been an empty one"
|
||||
}
|
||||
test "isExpired always returns false for expecting requests" {
|
||||
let req = { PrayerRequest.empty with requestType = Expecting }
|
||||
Expect.isFalse (req.isExpired DateTime.Now 0) "An expecting request should never be considered expired"
|
||||
}
|
||||
test "isExpired always returns false for manually-expired requests" {
|
||||
let req = { PrayerRequest.empty with updatedDate = DateTime.Now.AddMonths -1; expiration = Manual }
|
||||
Expect.isFalse (req.isExpired DateTime.Now 4) "A never-expired request should never be considered expired"
|
||||
}
|
||||
test "isExpired always returns false for long term/recurring requests" {
|
||||
let req = { PrayerRequest.empty with requestType = LongTermRequest }
|
||||
Expect.isFalse (req.isExpired DateTime.Now 0) "A recurring/long-term request should never be considered expired"
|
||||
}
|
||||
test "isExpired always returns true for force-expired requests" {
|
||||
let req = { PrayerRequest.empty with updatedDate = DateTime.Now; expiration = Forced }
|
||||
Expect.isTrue (req.isExpired DateTime.Now 5) "A force-expired request should always be considered expired"
|
||||
}
|
||||
test "isExpired returns false for non-expired requests" {
|
||||
let now = DateTime.Now
|
||||
let req = { PrayerRequest.empty with updatedDate = now.AddDays -5. }
|
||||
Expect.isFalse (req.isExpired now 7) "A request updated 5 days ago should not be considered expired"
|
||||
}
|
||||
test "isExpired returns true for expired requests" {
|
||||
let now = DateTime.Now
|
||||
let req = { PrayerRequest.empty with updatedDate = now.AddDays -8. }
|
||||
Expect.isTrue (req.isExpired now 7) "A request updated 8 days ago should be considered expired"
|
||||
}
|
||||
test "isExpired returns true for same-day expired requests" {
|
||||
let now = DateTime.Now
|
||||
let req = { PrayerRequest.empty with updatedDate = now.Date.AddDays(-7.).AddSeconds -1. }
|
||||
Expect.isTrue (req.isExpired now 7) "A request entered a second before midnight should be considered expired"
|
||||
}
|
||||
test "updateRequired returns false for expired requests" {
|
||||
let req = { PrayerRequest.empty with expiration = Forced }
|
||||
Expect.isFalse (req.updateRequired DateTime.Now 7 4) "An expired request should not require an update"
|
||||
}
|
||||
test "updateRequired returns false when an update is not required for an active request" {
|
||||
let now = DateTime.Now
|
||||
let req =
|
||||
{ PrayerRequest.empty with
|
||||
requestType = LongTermRequest
|
||||
updatedDate = now.AddDays -14.
|
||||
}
|
||||
Expect.isFalse (req.updateRequired now 7 4)
|
||||
"An active request updated 14 days ago should not require an update until 28 days"
|
||||
}
|
||||
test "updateRequired returns true when an update is required for an active request" {
|
||||
let now = DateTime.Now
|
||||
let req =
|
||||
{ PrayerRequest.empty with
|
||||
requestType = LongTermRequest
|
||||
updatedDate = now.AddDays -34.
|
||||
}
|
||||
Expect.isTrue (req.updateRequired now 7 4)
|
||||
"An active request updated 34 days ago should require an update (past 28 days)"
|
||||
}
|
||||
testList "PrayerRequest" [
|
||||
test "empty is as expected" {
|
||||
let mt = PrayerRequest.empty
|
||||
Expect.equal mt.prayerRequestId Guid.Empty "The request ID should have been an empty GUID"
|
||||
Expect.equal mt.requestType CurrentRequest "The request type should have been Current"
|
||||
Expect.equal mt.userId Guid.Empty "The user ID should have been an empty GUID"
|
||||
Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID"
|
||||
Expect.equal mt.enteredDate DateTime.MinValue "The entered date should have been the minimum"
|
||||
Expect.equal mt.updatedDate DateTime.MinValue "The updated date should have been the minimum"
|
||||
Expect.isNone mt.requestor "The requestor should not exist"
|
||||
Expect.equal mt.text "" "The request text should have been blank"
|
||||
Expect.isFalse mt.notifyChaplain "The notify chaplain flag should not have been set"
|
||||
Expect.equal mt.expiration Automatic "The expiration should have been Automatic"
|
||||
Expect.equal mt.user.userId Guid.Empty "The user should have been an empty one"
|
||||
Expect.equal mt.smallGroup.smallGroupId Guid.Empty "The small group should have been an empty one"
|
||||
}
|
||||
test "isExpired always returns false for expecting requests" {
|
||||
let req = { PrayerRequest.empty with requestType = Expecting }
|
||||
Expect.isFalse (req.isExpired DateTime.Now 0) "An expecting request should never be considered expired"
|
||||
}
|
||||
test "isExpired always returns false for manually-expired requests" {
|
||||
let req = { PrayerRequest.empty with updatedDate = DateTime.Now.AddMonths -1; expiration = Manual }
|
||||
Expect.isFalse (req.isExpired DateTime.Now 4) "A never-expired request should never be considered expired"
|
||||
}
|
||||
test "isExpired always returns false for long term/recurring requests" {
|
||||
let req = { PrayerRequest.empty with requestType = LongTermRequest }
|
||||
Expect.isFalse (req.isExpired DateTime.Now 0)
|
||||
"A recurring/long-term request should never be considered expired"
|
||||
}
|
||||
test "isExpired always returns true for force-expired requests" {
|
||||
let req = { PrayerRequest.empty with updatedDate = DateTime.Now; expiration = Forced }
|
||||
Expect.isTrue (req.isExpired DateTime.Now 5) "A force-expired request should always be considered expired"
|
||||
}
|
||||
test "isExpired returns false for non-expired requests" {
|
||||
let now = DateTime.Now
|
||||
let req = { PrayerRequest.empty with updatedDate = now.AddDays -5. }
|
||||
Expect.isFalse (req.isExpired now 7) "A request updated 5 days ago should not be considered expired"
|
||||
}
|
||||
test "isExpired returns true for expired requests" {
|
||||
let now = DateTime.Now
|
||||
let req = { PrayerRequest.empty with updatedDate = now.AddDays -8. }
|
||||
Expect.isTrue (req.isExpired now 7) "A request updated 8 days ago should be considered expired"
|
||||
}
|
||||
test "isExpired returns true for same-day expired requests" {
|
||||
let now = DateTime.Now
|
||||
let req = { PrayerRequest.empty with updatedDate = now.Date.AddDays(-7.).AddSeconds -1. }
|
||||
Expect.isTrue (req.isExpired now 7)
|
||||
"A request entered a second before midnight should be considered expired"
|
||||
}
|
||||
test "updateRequired returns false for expired requests" {
|
||||
let req = { PrayerRequest.empty with expiration = Forced }
|
||||
Expect.isFalse (req.updateRequired DateTime.Now 7 4) "An expired request should not require an update"
|
||||
}
|
||||
test "updateRequired returns false when an update is not required for an active request" {
|
||||
let now = DateTime.Now
|
||||
let req =
|
||||
{ PrayerRequest.empty with
|
||||
requestType = LongTermRequest
|
||||
updatedDate = now.AddDays -14.
|
||||
}
|
||||
Expect.isFalse (req.updateRequired now 7 4)
|
||||
"An active request updated 14 days ago should not require an update until 28 days"
|
||||
}
|
||||
test "updateRequired returns true when an update is required for an active request" {
|
||||
let now = DateTime.Now
|
||||
let req =
|
||||
{ PrayerRequest.empty with
|
||||
requestType = LongTermRequest
|
||||
updatedDate = now.AddDays -34.
|
||||
}
|
||||
Expect.isTrue (req.updateRequired now 7 4)
|
||||
"An active request updated 34 days ago should require an update (past 28 days)"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let prayerRequestTypeTests =
|
||||
testList "PrayerRequestType" [
|
||||
test "CurrentRequest code is correct" {
|
||||
Expect.equal CurrentRequest.code "C" "The code for CurrentRequest should have been \"C\""
|
||||
}
|
||||
test "LongTermRequest code is correct" {
|
||||
Expect.equal LongTermRequest.code "L" "The code for LongTermRequest should have been \"L\""
|
||||
}
|
||||
test "PraiseReport code is correct" {
|
||||
Expect.equal PraiseReport.code "P" "The code for PraiseReport should have been \"P\""
|
||||
}
|
||||
test "Expecting code is correct" {
|
||||
Expect.equal Expecting.code "E" "The code for Expecting should have been \"E\""
|
||||
}
|
||||
test "Announcement code is correct" {
|
||||
Expect.equal Announcement.code "A" "The code for Announcement should have been \"A\""
|
||||
}
|
||||
test "fromCode C should return CurrentRequest" {
|
||||
Expect.equal (PrayerRequestType.fromCode "C") CurrentRequest
|
||||
"\"C\" should have been converted to CurrentRequest"
|
||||
}
|
||||
test "fromCode L should return LongTermRequest" {
|
||||
Expect.equal (PrayerRequestType.fromCode "L") LongTermRequest
|
||||
"\"L\" should have been converted to LongTermRequest"
|
||||
}
|
||||
test "fromCode P should return PraiseReport" {
|
||||
Expect.equal (PrayerRequestType.fromCode "P") PraiseReport "\"P\" should have been converted to PraiseReport"
|
||||
}
|
||||
test "fromCode E should return Expecting" {
|
||||
Expect.equal (PrayerRequestType.fromCode "E") Expecting "\"E\" should have been converted to Expecting"
|
||||
}
|
||||
test "fromCode A should return Announcement" {
|
||||
Expect.equal (PrayerRequestType.fromCode "A") Announcement "\"A\" should have been converted to Announcement"
|
||||
}
|
||||
test "fromCode R should raise" {
|
||||
Expect.throws (fun () -> PrayerRequestType.fromCode "R" |> ignore)
|
||||
"An unknown code should have raised an exception"
|
||||
}
|
||||
testList "PrayerRequestType" [
|
||||
test "CurrentRequest code is correct" {
|
||||
Expect.equal CurrentRequest.code "C" "The code for CurrentRequest should have been \"C\""
|
||||
}
|
||||
test "LongTermRequest code is correct" {
|
||||
Expect.equal LongTermRequest.code "L" "The code for LongTermRequest should have been \"L\""
|
||||
}
|
||||
test "PraiseReport code is correct" {
|
||||
Expect.equal PraiseReport.code "P" "The code for PraiseReport should have been \"P\""
|
||||
}
|
||||
test "Expecting code is correct" {
|
||||
Expect.equal Expecting.code "E" "The code for Expecting should have been \"E\""
|
||||
}
|
||||
test "Announcement code is correct" {
|
||||
Expect.equal Announcement.code "A" "The code for Announcement should have been \"A\""
|
||||
}
|
||||
test "fromCode C should return CurrentRequest" {
|
||||
Expect.equal (PrayerRequestType.fromCode "C") CurrentRequest
|
||||
"\"C\" should have been converted to CurrentRequest"
|
||||
}
|
||||
test "fromCode L should return LongTermRequest" {
|
||||
Expect.equal (PrayerRequestType.fromCode "L") LongTermRequest
|
||||
"\"L\" should have been converted to LongTermRequest"
|
||||
}
|
||||
test "fromCode P should return PraiseReport" {
|
||||
Expect.equal (PrayerRequestType.fromCode "P") PraiseReport
|
||||
"\"P\" should have been converted to PraiseReport"
|
||||
}
|
||||
test "fromCode E should return Expecting" {
|
||||
Expect.equal (PrayerRequestType.fromCode "E") Expecting "\"E\" should have been converted to Expecting"
|
||||
}
|
||||
test "fromCode A should return Announcement" {
|
||||
Expect.equal (PrayerRequestType.fromCode "A") Announcement
|
||||
"\"A\" should have been converted to Announcement"
|
||||
}
|
||||
test "fromCode R should raise" {
|
||||
Expect.throws (fun () -> PrayerRequestType.fromCode "R" |> ignore)
|
||||
"An unknown code should have raised an exception"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let requestSortTests =
|
||||
testList "RequestSort" [
|
||||
test "SortByDate code is correct" {
|
||||
Expect.equal SortByDate.code "D" "The code for SortByDate should have been \"D\""
|
||||
}
|
||||
test "SortByRequestor code is correct" {
|
||||
Expect.equal SortByRequestor.code "R" "The code for SortByRequestor should have been \"R\""
|
||||
}
|
||||
test "fromCode D should return SortByDate" {
|
||||
Expect.equal (RequestSort.fromCode "D") SortByDate "\"D\" should have been converted to SortByDate"
|
||||
}
|
||||
test "fromCode R should return SortByRequestor" {
|
||||
Expect.equal (RequestSort.fromCode "R") SortByRequestor "\"R\" should have been converted to SortByRequestor"
|
||||
}
|
||||
test "fromCode Q should raise" {
|
||||
Expect.throws (fun () -> RequestSort.fromCode "Q" |> ignore) "An unknown code should have raised an exception"
|
||||
}
|
||||
testList "RequestSort" [
|
||||
test "SortByDate code is correct" {
|
||||
Expect.equal SortByDate.code "D" "The code for SortByDate should have been \"D\""
|
||||
}
|
||||
test "SortByRequestor code is correct" {
|
||||
Expect.equal SortByRequestor.code "R" "The code for SortByRequestor should have been \"R\""
|
||||
}
|
||||
test "fromCode D should return SortByDate" {
|
||||
Expect.equal (RequestSort.fromCode "D") SortByDate "\"D\" should have been converted to SortByDate"
|
||||
}
|
||||
test "fromCode R should return SortByRequestor" {
|
||||
Expect.equal (RequestSort.fromCode "R") SortByRequestor
|
||||
"\"R\" should have been converted to SortByRequestor"
|
||||
}
|
||||
test "fromCode Q should raise" {
|
||||
Expect.throws (fun () -> RequestSort.fromCode "Q" |> ignore)
|
||||
"An unknown code should have raised an exception"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let smallGroupTests =
|
||||
testList "SmallGroup" [
|
||||
let now = DateTime (2017, 5, 12, 12, 15, 0, DateTimeKind.Utc)
|
||||
let withFakeClock f () =
|
||||
FakeClock (Instant.FromDateTimeUtc now) |> f
|
||||
yield test "empty is as expected" {
|
||||
let mt = SmallGroup.empty
|
||||
Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID"
|
||||
Expect.equal mt.churchId Guid.Empty "The church ID should have been an empty GUID"
|
||||
Expect.equal mt.name "" "The name should have been blank"
|
||||
Expect.equal mt.church.churchId Guid.Empty "The church should have been an empty one"
|
||||
Expect.isNotNull mt.members "The members navigation property should not be null"
|
||||
Expect.isEmpty mt.members "There should be no members for an empty small group"
|
||||
Expect.isNotNull mt.prayerRequests "The prayer requests navigation property should not be null"
|
||||
Expect.isEmpty mt.prayerRequests "There should be no prayer requests for an empty small group"
|
||||
Expect.isNotNull mt.users "The users navigation property should not be null"
|
||||
Expect.isEmpty mt.users "There should be no users for an empty small group"
|
||||
}
|
||||
yield! testFixture withFakeClock [
|
||||
"localTimeNow adjusts the time ahead of UTC",
|
||||
fun clock ->
|
||||
let grp = { SmallGroup.empty with preferences = { ListPreferences.empty with timeZoneId = "Europe/Berlin" } }
|
||||
Expect.isGreaterThan (grp.localTimeNow clock) now "UTC to Europe/Berlin should have added hours"
|
||||
"localTimeNow adjusts the time behind UTC",
|
||||
fun clock ->
|
||||
Expect.isLessThan (SmallGroup.empty.localTimeNow clock) now
|
||||
"UTC to America/Denver should have subtracted hours"
|
||||
"localTimeNow returns UTC when the time zone is invalid",
|
||||
fun clock ->
|
||||
let grp = { SmallGroup.empty with preferences = { ListPreferences.empty with timeZoneId = "garbage" } }
|
||||
Expect.equal (grp.localTimeNow clock) now "UTC should have been returned for an invalid time zone"
|
||||
]
|
||||
yield test "localTimeNow fails when clock is not passed" {
|
||||
Expect.throws (fun () -> (SmallGroup.empty.localTimeNow >> ignore) null)
|
||||
"Should have raised an exception for null clock"
|
||||
}
|
||||
yield test "localDateNow returns the date portion" {
|
||||
let now' = DateTime (2017, 5, 12, 1, 15, 0, DateTimeKind.Utc)
|
||||
let clock = FakeClock (Instant.FromDateTimeUtc now')
|
||||
Expect.isLessThan (SmallGroup.empty.localDateNow clock) now.Date "The date should have been a day earlier"
|
||||
}
|
||||
testList "SmallGroup" [
|
||||
let now = DateTime (2017, 5, 12, 12, 15, 0, DateTimeKind.Utc)
|
||||
let withFakeClock f () =
|
||||
FakeClock (Instant.FromDateTimeUtc now) |> f
|
||||
yield test "empty is as expected" {
|
||||
let mt = SmallGroup.empty
|
||||
Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID"
|
||||
Expect.equal mt.churchId Guid.Empty "The church ID should have been an empty GUID"
|
||||
Expect.equal mt.name "" "The name should have been blank"
|
||||
Expect.equal mt.church.churchId Guid.Empty "The church should have been an empty one"
|
||||
Expect.isNotNull mt.members "The members navigation property should not be null"
|
||||
Expect.isEmpty mt.members "There should be no members for an empty small group"
|
||||
Expect.isNotNull mt.prayerRequests "The prayer requests navigation property should not be null"
|
||||
Expect.isEmpty mt.prayerRequests "There should be no prayer requests for an empty small group"
|
||||
Expect.isNotNull mt.users "The users navigation property should not be null"
|
||||
Expect.isEmpty mt.users "There should be no users for an empty small group"
|
||||
}
|
||||
yield! testFixture withFakeClock [
|
||||
"localTimeNow adjusts the time ahead of UTC",
|
||||
fun clock ->
|
||||
let grp =
|
||||
{ SmallGroup.empty with
|
||||
preferences = { ListPreferences.empty with timeZoneId = "Europe/Berlin" }
|
||||
}
|
||||
Expect.isGreaterThan (grp.localTimeNow clock) now "UTC to Europe/Berlin should have added hours"
|
||||
"localTimeNow adjusts the time behind UTC",
|
||||
fun clock ->
|
||||
Expect.isLessThan (SmallGroup.empty.localTimeNow clock) now
|
||||
"UTC to America/Denver should have subtracted hours"
|
||||
"localTimeNow returns UTC when the time zone is invalid",
|
||||
fun clock ->
|
||||
let grp = { SmallGroup.empty with preferences = { ListPreferences.empty with timeZoneId = "garbage" } }
|
||||
Expect.equal (grp.localTimeNow clock) now "UTC should have been returned for an invalid time zone"
|
||||
]
|
||||
yield test "localTimeNow fails when clock is not passed" {
|
||||
Expect.throws (fun () -> (SmallGroup.empty.localTimeNow >> ignore) null)
|
||||
"Should have raised an exception for null clock"
|
||||
}
|
||||
yield test "localDateNow returns the date portion" {
|
||||
let now' = DateTime (2017, 5, 12, 1, 15, 0, DateTimeKind.Utc)
|
||||
let clock = FakeClock (Instant.FromDateTimeUtc now')
|
||||
Expect.isLessThan (SmallGroup.empty.localDateNow clock) now.Date "The date should have been a day earlier"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let timeZoneTests =
|
||||
testList "TimeZone" [
|
||||
test "empty is as expected" {
|
||||
let mt = TimeZone.empty
|
||||
Expect.equal mt.timeZoneId "" "The time zone ID should have been blank"
|
||||
Expect.equal mt.description "" "The description should have been blank"
|
||||
Expect.equal mt.sortOrder 0 "The sort order should have been zero"
|
||||
Expect.isFalse mt.isActive "The is-active flag should not have been set"
|
||||
}
|
||||
testList "TimeZone" [
|
||||
test "empty is as expected" {
|
||||
let mt = TimeZone.empty
|
||||
Expect.equal mt.timeZoneId "" "The time zone ID should have been blank"
|
||||
Expect.equal mt.description "" "The description should have been blank"
|
||||
Expect.equal mt.sortOrder 0 "The sort order should have been zero"
|
||||
Expect.isFalse mt.isActive "The is-active flag should not have been set"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let userTests =
|
||||
testList "User" [
|
||||
test "empty is as expected" {
|
||||
let mt = User.empty
|
||||
Expect.equal mt.userId Guid.Empty "The user ID should have been an empty GUID"
|
||||
Expect.equal mt.firstName "" "The first name should have been blank"
|
||||
Expect.equal mt.lastName "" "The last name should have been blank"
|
||||
Expect.equal mt.emailAddress "" "The e-mail address should have been blank"
|
||||
Expect.isFalse mt.isAdmin "The is admin flag should not have been set"
|
||||
Expect.equal mt.passwordHash "" "The password hash should have been blank"
|
||||
Expect.isNone mt.salt "The password salt should not exist"
|
||||
Expect.isNotNull mt.smallGroups "The small groups navigation property should not have been null"
|
||||
Expect.isEmpty mt.smallGroups "There should be no small groups for an empty user"
|
||||
}
|
||||
test "fullName concatenates first and last names" {
|
||||
let user = { User.empty with firstName = "Unit"; lastName = "Test" }
|
||||
Expect.equal user.fullName "Unit Test" "The full name should be the first and last, separated by a space"
|
||||
}
|
||||
testList "User" [
|
||||
test "empty is as expected" {
|
||||
let mt = User.empty
|
||||
Expect.equal mt.userId Guid.Empty "The user ID should have been an empty GUID"
|
||||
Expect.equal mt.firstName "" "The first name should have been blank"
|
||||
Expect.equal mt.lastName "" "The last name should have been blank"
|
||||
Expect.equal mt.emailAddress "" "The e-mail address should have been blank"
|
||||
Expect.isFalse mt.isAdmin "The is admin flag should not have been set"
|
||||
Expect.equal mt.passwordHash "" "The password hash should have been blank"
|
||||
Expect.isNone mt.salt "The password salt should not exist"
|
||||
Expect.isNotNull mt.smallGroups "The small groups navigation property should not have been null"
|
||||
Expect.isEmpty mt.smallGroups "There should be no small groups for an empty user"
|
||||
}
|
||||
test "fullName concatenates first and last names" {
|
||||
let user = { User.empty with firstName = "Unit"; lastName = "Test" }
|
||||
Expect.equal user.fullName "Unit Test" "The full name should be the first and last, separated by a space"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let userSmallGroupTests =
|
||||
testList "UserSmallGroup" [
|
||||
test "empty is as expected" {
|
||||
let mt = UserSmallGroup.empty
|
||||
Expect.equal mt.userId Guid.Empty "The user ID should have been an empty GUID"
|
||||
Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID"
|
||||
Expect.equal mt.user.userId Guid.Empty "The user should have been an empty one"
|
||||
Expect.equal mt.smallGroup.smallGroupId Guid.Empty "The small group should have been an empty one"
|
||||
}
|
||||
testList "UserSmallGroup" [
|
||||
test "empty is as expected" {
|
||||
let mt = UserSmallGroup.empty
|
||||
Expect.equal mt.userId Guid.Empty "The user ID should have been an empty GUID"
|
||||
Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID"
|
||||
Expect.equal mt.user.userId Guid.Empty "The user should have been an empty one"
|
||||
Expect.equal mt.smallGroup.smallGroupId Guid.Empty "The small group should have been an empty one"
|
||||
}
|
||||
]
|
||||
|
@ -17,7 +17,8 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Expecto" Version="9.0.4" />
|
||||
<PackageReference Include="Expecto.VisualStudio.TestAdapter" Version="10.0.2" />
|
||||
<PackageReference Include="NodaTime.Testing" Version="3.0.5" />
|
||||
<PackageReference Include="NodaTime.Testing" Version="3.1.0" />
|
||||
<PackageReference Update="FSharp.Core" Version="6.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,208 +1,214 @@
|
||||
module PrayerTracker.UI.CommonFunctionsTests
|
||||
|
||||
open System.IO
|
||||
open Expecto
|
||||
open Giraffe.ViewEngine
|
||||
open Microsoft.AspNetCore.Mvc.Localization
|
||||
open Microsoft.Extensions.Localization
|
||||
open PrayerTracker.Tests.TestLocalization
|
||||
open PrayerTracker.Views
|
||||
open System.IO
|
||||
|
||||
|
||||
[<Tests>]
|
||||
let iconSizedTests =
|
||||
testList "iconSized" [
|
||||
test "succeeds" {
|
||||
let ico = iconSized 18 "tom-&-jerry" |> renderHtmlNode
|
||||
Expect.equal ico "<i class=\"material-icons md-18\">tom-&-jerry</i>" "icon HTML not correct"
|
||||
}
|
||||
testList "iconSized" [
|
||||
test "succeeds" {
|
||||
let ico = iconSized 18 "tom-&-jerry" |> renderHtmlNode
|
||||
Expect.equal ico """<i class="material-icons md-18">tom-&-jerry</i>""" "icon HTML not correct"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let iconTests =
|
||||
testList "icon" [
|
||||
test "succeeds" {
|
||||
let ico = icon "bob-&-tom" |> renderHtmlNode
|
||||
Expect.equal ico "<i class=\"material-icons\">bob-&-tom</i>" "icon HTML not correct"
|
||||
}
|
||||
testList "icon" [
|
||||
test "succeeds" {
|
||||
let ico = icon "bob-&-tom" |> renderHtmlNode
|
||||
Expect.equal ico """<i class="material-icons">bob-&-tom</i>""" "icon HTML not correct"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let locStrTests =
|
||||
testList "locStr" [
|
||||
test "succeeds" {
|
||||
let enc = locStr (LocalizedString ("test", "test&")) |> renderHtmlNode
|
||||
Expect.equal enc "test&" "string not encoded correctly"
|
||||
}
|
||||
testList "locStr" [
|
||||
test "succeeds" {
|
||||
let enc = locStr (LocalizedString ("test", "test&")) |> renderHtmlNode
|
||||
Expect.equal enc "test&" "string not encoded correctly"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let namedColorListTests =
|
||||
testList "namedColorList" [
|
||||
test "succeeds with default values" {
|
||||
let expected =
|
||||
[ "<select name=\"the-name\">"
|
||||
"<option value=\"aqua\" style=\"background-color:aqua;color:black;\">aqua</option>"
|
||||
"<option value=\"black\" style=\"background-color:black;color:white;\">black</option>"
|
||||
"<option value=\"blue\" style=\"background-color:blue;color:white;\">blue</option>"
|
||||
"<option value=\"fuchsia\" style=\"background-color:fuchsia;color:black;\">fuchsia</option>"
|
||||
"<option value=\"gray\" style=\"background-color:gray;color:white;\">gray</option>"
|
||||
"<option value=\"green\" style=\"background-color:green;color:white;\">green</option>"
|
||||
"<option value=\"lime\" style=\"background-color:lime;color:black;\">lime</option>"
|
||||
"<option value=\"maroon\" style=\"background-color:maroon;color:white;\">maroon</option>"
|
||||
"<option value=\"navy\" style=\"background-color:navy;color:white;\">navy</option>"
|
||||
"<option value=\"olive\" style=\"background-color:olive;color:white;\">olive</option>"
|
||||
"<option value=\"purple\" style=\"background-color:purple;color:white;\">purple</option>"
|
||||
"<option value=\"red\" style=\"background-color:red;color:black;\">red</option>"
|
||||
"<option value=\"silver\" style=\"background-color:silver;color:black;\">silver</option>"
|
||||
"<option value=\"teal\" style=\"background-color:teal;color:white;\">teal</option>"
|
||||
"<option value=\"white\" style=\"background-color:white;color:black;\">white</option>"
|
||||
"<option value=\"yellow\" style=\"background-color:yellow;color:black;\">yellow</option>"
|
||||
"</select>"
|
||||
]
|
||||
|> String.concat ""
|
||||
let selectList = namedColorList "the-name" "" [] _s |> renderHtmlNode
|
||||
Expect.equal expected selectList "The default select list was not generated correctly"
|
||||
}
|
||||
test "succeeds with a selected value" {
|
||||
let selectList = namedColorList "the-name" "white" [] _s |> renderHtmlNode
|
||||
Expect.stringContains selectList " selected>white</option>" "Selected option not generated correctly"
|
||||
}
|
||||
test "succeeds with extra attributes" {
|
||||
let selectList = namedColorList "the-name" "" [ _id "myId" ] _s |> renderHtmlNode
|
||||
Expect.stringStarts selectList "<select name=\"the-name\" id=\"myId\">" "Attributes not included correctly"
|
||||
}
|
||||
testList "namedColorList" [
|
||||
test "succeeds with default values" {
|
||||
let expected =
|
||||
[ """<select name="the-name">"""
|
||||
"""<option value="aqua" style="background-color:aqua;color:black;">aqua</option>"""
|
||||
"""<option value="black" style="background-color:black;color:white;">black</option>"""
|
||||
"""<option value="blue" style="background-color:blue;color:white;">blue</option>"""
|
||||
"""<option value="fuchsia" style="background-color:fuchsia;color:black;">fuchsia</option>"""
|
||||
"""<option value="gray" style="background-color:gray;color:white;">gray</option>"""
|
||||
"""<option value="green" style="background-color:green;color:white;">green</option>"""
|
||||
"""<option value="lime" style="background-color:lime;color:black;">lime</option>"""
|
||||
"""<option value="maroon" style="background-color:maroon;color:white;">maroon</option>"""
|
||||
"""<option value="navy" style="background-color:navy;color:white;">navy</option>"""
|
||||
"""<option value="olive" style="background-color:olive;color:white;">olive</option>"""
|
||||
"""<option value="purple" style="background-color:purple;color:white;">purple</option>"""
|
||||
"""<option value="red" style="background-color:red;color:black;">red</option>"""
|
||||
"""<option value="silver" style="background-color:silver;color:black;">silver</option>"""
|
||||
"""<option value="teal" style="background-color:teal;color:white;">teal</option>"""
|
||||
"""<option value="white" style="background-color:white;color:black;">white</option>"""
|
||||
"""<option value="yellow" style="background-color:yellow;color:black;">yellow</option>"""
|
||||
"</select>"
|
||||
]
|
||||
|> String.concat ""
|
||||
let selectList = namedColorList "the-name" "" [] _s |> renderHtmlNode
|
||||
Expect.equal expected selectList "The default select list was not generated correctly"
|
||||
}
|
||||
test "succeeds with a selected value" {
|
||||
let selectList = namedColorList "the-name" "white" [] _s |> renderHtmlNode
|
||||
Expect.stringContains selectList " selected>white</option>" "Selected option not generated correctly"
|
||||
}
|
||||
test "succeeds with extra attributes" {
|
||||
let selectList = namedColorList "the-name" "" [ _id "myId" ] _s |> renderHtmlNode
|
||||
Expect.stringStarts selectList """<select name="the-name" id="myId">""" "Attributes not included correctly"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let radioTests =
|
||||
testList "radio" [
|
||||
test "succeeds when not selected" {
|
||||
let rad = radio "a-name" "anId" "test" "unit" |> renderHtmlNode
|
||||
Expect.equal rad "<input type=\"radio\" name=\"a-name\" id=\"anId\" value=\"test\">"
|
||||
"Unselected radio button not generated correctly"
|
||||
}
|
||||
test "succeeds when selected" {
|
||||
let rad = radio "a-name" "anId" "unit" "unit" |> renderHtmlNode
|
||||
Expect.equal rad "<input type=\"radio\" name=\"a-name\" id=\"anId\" value=\"unit\" checked>"
|
||||
"Selected radio button not generated correctly"
|
||||
}
|
||||
testList "radio" [
|
||||
test "succeeds when not selected" {
|
||||
let rad = radio "a-name" "anId" "test" "unit" |> renderHtmlNode
|
||||
Expect.equal rad """<input type="radio" name="a-name" id="anId" value="test">"""
|
||||
"Unselected radio button not generated correctly"
|
||||
}
|
||||
test "succeeds when selected" {
|
||||
let rad = radio "a-name" "anId" "unit" "unit" |> renderHtmlNode
|
||||
Expect.equal rad """<input type="radio" name="a-name" id="anId" value="unit" checked>"""
|
||||
"Selected radio button not generated correctly"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let rawLocTextTests =
|
||||
testList "rawLocText" [
|
||||
test "succeeds" {
|
||||
use sw = new StringWriter ()
|
||||
let raw = rawLocText sw (LocalizedHtmlString ("test", "test&")) |> renderHtmlNode
|
||||
Expect.equal raw "test&" "string not written correctly"
|
||||
}
|
||||
testList "rawLocText" [
|
||||
test "succeeds" {
|
||||
use sw = new StringWriter ()
|
||||
let raw = rawLocText sw (LocalizedHtmlString ("test", "test&")) |> renderHtmlNode
|
||||
Expect.equal raw "test&" "string not written correctly"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let selectDefaultTests =
|
||||
testList "selectDefault" [
|
||||
test "succeeds" {
|
||||
Expect.equal (selectDefault "a&b") "— a&b —" "Default selection not generated correctly"
|
||||
}
|
||||
testList "selectDefault" [
|
||||
test "succeeds" {
|
||||
Expect.equal (selectDefault "a&b") "— a&b —" "Default selection not generated correctly"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let selectListTests =
|
||||
testList "selectList" [
|
||||
test "succeeds with minimum options" {
|
||||
let theList = selectList "a-list" "" [] [] |> renderHtmlNode
|
||||
Expect.equal theList "<select name=\"a-list\" id=\"a-list\"></select>" "Empty select list not generated correctly"
|
||||
}
|
||||
test "succeeds with all options" {
|
||||
let theList =
|
||||
[ "tom", "Tom&"
|
||||
"bob", "Bob"
|
||||
"jan", "Jan"
|
||||
]
|
||||
|> selectList "the-list" "bob" [ _style "ugly" ]
|
||||
|> renderHtmlNode
|
||||
let expected =
|
||||
[ "<select name=\"the-list\" id=\"the-list\" style=\"ugly\">"
|
||||
"<option value=\"tom\">Tom&</option>"
|
||||
"<option value=\"bob\" selected>Bob</option>"
|
||||
"<option value=\"jan\">Jan</option>"
|
||||
"</select>"
|
||||
]
|
||||
|> String.concat ""
|
||||
Expect.equal theList expected "Filled select list not generated correctly"
|
||||
}
|
||||
testList "selectList" [
|
||||
test "succeeds with minimum options" {
|
||||
let theList = selectList "a-list" "" [] [] |> renderHtmlNode
|
||||
Expect.equal theList """<select name="a-list" id="a-list"></select>"""
|
||||
"Empty select list not generated correctly"
|
||||
}
|
||||
test "succeeds with all options" {
|
||||
let theList =
|
||||
[ "tom", "Tom&"
|
||||
"bob", "Bob"
|
||||
"jan", "Jan"
|
||||
]
|
||||
|> selectList "the-list" "bob" [ _style "ugly" ]
|
||||
|> renderHtmlNode
|
||||
let expected =
|
||||
[ """<select name="the-list" id="the-list" style="ugly">"""
|
||||
"""<option value="tom">Tom&</option>"""
|
||||
"""<option value="bob" selected>Bob</option>"""
|
||||
"""<option value="jan">Jan</option>"""
|
||||
"""</select>"""
|
||||
]
|
||||
|> String.concat ""
|
||||
Expect.equal theList expected "Filled select list not generated correctly"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let spaceTests =
|
||||
testList "space" [
|
||||
test "succeeds" {
|
||||
Expect.equal (renderHtmlNode space) " " "space literal not correct"
|
||||
}
|
||||
testList "space" [
|
||||
test "succeeds" {
|
||||
Expect.equal (renderHtmlNode space) " " "space literal not correct"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
[<Tests>]
|
||||
let submitTests =
|
||||
testList "submit" [
|
||||
test "succeeds" {
|
||||
let btn = submit [ _class "slick" ] "file-ico" _s.["a&b"] |> renderHtmlNode
|
||||
Expect.equal
|
||||
btn
|
||||
"<button type=\"submit\" class=\"slick\"><i class=\"material-icons\">file-ico</i> a&b</button>"
|
||||
"Submit button not generated correctly"
|
||||
}
|
||||
testList "submit" [
|
||||
test "succeeds" {
|
||||
let btn = submit [ _class "slick" ] "file-ico" _s["a&b"] |> renderHtmlNode
|
||||
Expect.equal
|
||||
btn
|
||||
"""<button type="submit" class="slick"><i class="material-icons">file-ico</i> a&b</button>"""
|
||||
"Submit button not generated correctly"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let tableSummaryTests =
|
||||
testList "tableSummary" [
|
||||
test "succeeds for no entries" {
|
||||
let sum = tableSummary 0 _s |> renderHtmlNode
|
||||
Expect.equal sum "<div class=\"pt-center-text\"><small>No Entries to Display</small></div>"
|
||||
"Summary for no items is incorrect"
|
||||
}
|
||||
test "succeeds for one entry" {
|
||||
let sum = tableSummary 1 _s |> renderHtmlNode
|
||||
Expect.equal sum "<div class=\"pt-center-text\"><small>Displaying 1 Entry</small></div>"
|
||||
"Summary for one item is incorrect"
|
||||
}
|
||||
test "succeeds for many entries" {
|
||||
let sum = tableSummary 5 _s |> renderHtmlNode
|
||||
Expect.equal sum "<div class=\"pt-center-text\"><small>Displaying 5 Entries</small></div>"
|
||||
"Summary for many items is incorrect"
|
||||
}
|
||||
testList "tableSummary" [
|
||||
test "succeeds for no entries" {
|
||||
let sum = tableSummary 0 _s |> renderHtmlNode
|
||||
Expect.equal sum """<div class="pt-center-text"><small>No Entries to Display</small></div>"""
|
||||
"Summary for no items is incorrect"
|
||||
}
|
||||
test "succeeds for one entry" {
|
||||
let sum = tableSummary 1 _s |> renderHtmlNode
|
||||
Expect.equal sum """<div class="pt-center-text"><small>Displaying 1 Entry</small></div>"""
|
||||
"Summary for one item is incorrect"
|
||||
}
|
||||
test "succeeds for many entries" {
|
||||
let sum = tableSummary 5 _s |> renderHtmlNode
|
||||
Expect.equal sum """<div class="pt-center-text"><small>Displaying 5 Entries</small></div>"""
|
||||
"Summary for many items is incorrect"
|
||||
}
|
||||
]
|
||||
|
||||
module TimeZones =
|
||||
|
||||
open PrayerTracker.Views.CommonFunctions.TimeZones
|
||||
open PrayerTracker.Views.CommonFunctions.TimeZones
|
||||
|
||||
[<Tests>]
|
||||
let nameTests =
|
||||
testList "TimeZones.name" [
|
||||
test "succeeds for US Eastern time" {
|
||||
Expect.equal (name "America/New_York" _s |> string) "Eastern" "US Eastern time zone not returned correctly"
|
||||
}
|
||||
test "succeeds for US Central time" {
|
||||
Expect.equal (name "America/Chicago" _s |> string) "Central" "US Central time zone not returned correctly"
|
||||
}
|
||||
test "succeeds for US Mountain time" {
|
||||
Expect.equal (name "America/Denver" _s |> string) "Mountain" "US Mountain time zone not returned correctly"
|
||||
}
|
||||
test "succeeds for US Mountain (AZ) time" {
|
||||
Expect.equal (name "America/Phoenix" _s |> string) "Mountain (Arizona)"
|
||||
"US Mountain (AZ) time zone not returned correctly"
|
||||
}
|
||||
test "succeeds for US Pacific time" {
|
||||
Expect.equal (name "America/Los_Angeles" _s |> string) "Pacific" "US Pacific time zone not returned correctly"
|
||||
}
|
||||
test "succeeds for Central European time" {
|
||||
Expect.equal (name "Europe/Berlin" _s |> string) "Central European"
|
||||
"Central European time zone not returned correctly"
|
||||
}
|
||||
test "fails for unexpected time zone" {
|
||||
Expect.equal (name "Wakanda" _s |> string) "Wakanda" "Unexpected time zone should have returned the original ID"
|
||||
}
|
||||
]
|
||||
[<Tests>]
|
||||
let nameTests =
|
||||
testList "TimeZones.name" [
|
||||
test "succeeds for US Eastern time" {
|
||||
Expect.equal (name "America/New_York" _s |> string) "Eastern"
|
||||
"US Eastern time zone not returned correctly"
|
||||
}
|
||||
test "succeeds for US Central time" {
|
||||
Expect.equal (name "America/Chicago" _s |> string) "Central"
|
||||
"US Central time zone not returned correctly"
|
||||
}
|
||||
test "succeeds for US Mountain time" {
|
||||
Expect.equal (name "America/Denver" _s |> string) "Mountain"
|
||||
"US Mountain time zone not returned correctly"
|
||||
}
|
||||
test "succeeds for US Mountain (AZ) time" {
|
||||
Expect.equal (name "America/Phoenix" _s |> string) "Mountain (Arizona)"
|
||||
"US Mountain (AZ) time zone not returned correctly"
|
||||
}
|
||||
test "succeeds for US Pacific time" {
|
||||
Expect.equal (name "America/Los_Angeles" _s |> string) "Pacific"
|
||||
"US Pacific time zone not returned correctly"
|
||||
}
|
||||
test "succeeds for Central European time" {
|
||||
Expect.equal (name "Europe/Berlin" _s |> string) "Central European"
|
||||
"Central European time zone not returned correctly"
|
||||
}
|
||||
test "fails for unexpected time zone" {
|
||||
Expect.equal (name "Wakanda" _s |> string) "Wakanda"
|
||||
"Unexpected time zone should have returned the original ID"
|
||||
}
|
||||
]
|
||||
|
@ -136,7 +136,7 @@ module StringTests =
|
||||
|
||||
[<Tests>]
|
||||
let stripTagsTests =
|
||||
let testString = "<p class=\"testing\">Here is some text<br> <br />and some more</p>"
|
||||
let testString = """<p class="testing">Here is some text<br> <br />and some more</p>"""
|
||||
testList "stripTags" [
|
||||
test "does nothing if all tags are allowed" {
|
||||
Expect.equal (stripTags [ "p"; "br" ] testString) testString
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -6,45 +6,50 @@ open PrayerTracker.ViewModels
|
||||
|
||||
/// View for the church edit page
|
||||
let edit (m : EditChurch) ctx vi =
|
||||
let pageTitle = match m.isNew () with true -> "Add a New Church" | false -> "Edit Church"
|
||||
let pageTitle = match m.IsNew with true -> "Add a New Church" | false -> "Edit Church"
|
||||
let s = I18N.localizer.Force ()
|
||||
[ form [ _action "/web/church/save"; _method "post"; _class "pt-center-columns" ] [
|
||||
style [ _scoped ] [
|
||||
rawText "#name { width: 20rem; } #city { width: 10rem; } #st { width: 3rem; } #interfaceAddress { width: 30rem; }"
|
||||
]
|
||||
csrfToken ctx
|
||||
input [ _type "hidden"; _name "churchId"; _value (flatGuid m.churchId) ]
|
||||
input [ _type "hidden"; _name (nameof m.ChurchId); _value (flatGuid m.ChurchId) ]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "name" ] [ locStr s["Church Name"] ]
|
||||
input [ _type "text"; _name "name"; _id "name"; _required; _autofocus; _value m.name ]
|
||||
input [ _type "text"; _name (nameof m.Name); _id "name"; _required; _autofocus; _value m.Name ]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "City"] [ locStr s["City"] ]
|
||||
input [ _type "text"; _name "city"; _id "city"; _required; _value m.city ]
|
||||
input [ _type "text"; _name (nameof m.City); _id "city"; _required; _value m.City ]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "ST" ] [ locStr s["State"] ]
|
||||
input [ _type "text"; _name "st"; _id "st"; _required; _minlength "2"; _maxlength "2"; _value m.st ]
|
||||
label [ _for "state" ] [ locStr s["State or Province"] ]
|
||||
input [ _type "text"
|
||||
_name (nameof m.State)
|
||||
_id "state"
|
||||
_required
|
||||
_minlength "2"; _maxlength "2"
|
||||
_value m.State ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-checkbox-field" ] [
|
||||
input [ _type "checkbox"
|
||||
_name "hasInterface"
|
||||
_name (nameof m.HasInterface)
|
||||
_id "hasInterface"
|
||||
_value "True"
|
||||
match m.hasInterface with Some x when x -> _checked | _ -> () ]
|
||||
if defaultArg m.HasInterface false then _checked ]
|
||||
label [ _for "hasInterface" ] [ locStr s["Has an interface with Virtual Prayer Room"] ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row pt-fadeable"; _id "divInterfaceAddress" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "interfaceAddress" ] [ locStr s["VPR Interface URL"] ]
|
||||
input
|
||||
[ _type "url"; _name "interfaceAddress"; _id "interfaceAddress";
|
||||
_value (match m.interfaceAddress with Some ia -> ia | None -> "")
|
||||
]
|
||||
input [ _type "url"
|
||||
_name (nameof m.InterfaceAddress)
|
||||
_id "interfaceAddress";
|
||||
_value (defaultArg m.InterfaceAddress "") ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [ submit [] "save" s["Save Church"] ]
|
||||
|
@ -37,7 +37,7 @@ let error code vi =
|
||||
_alt $"""%A{s["PrayerTracker"]} %A{s["from Bit Badger Solutions"]}"""
|
||||
_title $"""%A{s["PrayerTracker"]} %A{s["from Bit Badger Solutions"]}"""
|
||||
_style "vertical-align:text-bottom;" ]
|
||||
str vi.version
|
||||
str vi.Version
|
||||
]
|
||||
]
|
||||
|> div []
|
||||
|
@ -20,7 +20,7 @@ module Navigation =
|
||||
let s = I18N.localizer.Force ()
|
||||
let menuSpacer = rawText " "
|
||||
let leftLinks = [
|
||||
match m.user with
|
||||
match m.User with
|
||||
| Some u ->
|
||||
li [ _class "dropdown" ] [
|
||||
a [ _class "dropbtn"; _role "button"; _aria "label" s["Requests"].Value; _title s["Requests"].Value ]
|
||||
@ -56,7 +56,7 @@ module Navigation =
|
||||
]
|
||||
]
|
||||
| None ->
|
||||
match m.group with
|
||||
match m.Group with
|
||||
| Some _ ->
|
||||
li [] [
|
||||
a [ _href "/web/prayer-requests/view"
|
||||
@ -91,9 +91,9 @@ module Navigation =
|
||||
]
|
||||
]
|
||||
let rightLinks =
|
||||
match m.group with
|
||||
match m.Group with
|
||||
| Some _ -> [
|
||||
match m.user with
|
||||
match m.User with
|
||||
| Some _ ->
|
||||
li [] [
|
||||
a [ _href "/web/user/password"
|
||||
@ -137,9 +137,9 @@ module Navigation =
|
||||
rawText " • "
|
||||
a [ _href "/web/language/es" ] [ locStr s["Cambie a Español"] ]
|
||||
]
|
||||
match m.group with
|
||||
match m.Group with
|
||||
| Some g ->[
|
||||
match m.user with
|
||||
match m.User with
|
||||
| Some u ->
|
||||
span [ _class "u" ] [ locStr s["Currently Logged On"] ]
|
||||
rawText " "
|
||||
@ -151,7 +151,7 @@ module Navigation =
|
||||
rawText " "
|
||||
icon "group"
|
||||
space
|
||||
match m.user with
|
||||
match m.User with
|
||||
| Some _ -> a [ _href "/web/small-group" ] [ strong [] [ str g.name ] ]
|
||||
| None -> strong [] [ str g.name ]
|
||||
rawText " "
|
||||
@ -190,9 +190,9 @@ let private htmlHead m pageTitle =
|
||||
meta [ _charset "UTF-8" ]
|
||||
title [] [ locStr pageTitle; titleSep; locStr s["PrayerTracker"] ]
|
||||
yield! commonHead
|
||||
for cssFile in m.style do
|
||||
for cssFile in m.Style do
|
||||
link [ _rel "stylesheet"; _href $"/css/{cssFile}.css"; _type "text/css" ]
|
||||
for jsFile in m.script do
|
||||
for jsFile in m.Script do
|
||||
script [ _src $"/js/{jsFile}.js" ] []
|
||||
]
|
||||
|
||||
@ -207,25 +207,25 @@ let private helpLink link =
|
||||
/// Render the page title, and optionally a help link
|
||||
let private renderPageTitle m pageTitle =
|
||||
h2 [ _id "pt-page-title" ] [
|
||||
match m.helpLink with Some link -> Help.fullLink (langCode ()) link |> helpLink | None -> ()
|
||||
match m.HelpLink with Some link -> Help.fullLink (langCode ()) link |> helpLink | None -> ()
|
||||
locStr pageTitle
|
||||
]
|
||||
|
||||
/// Render the messages that may need to be displayed to the user
|
||||
let private messages m =
|
||||
let s = I18N.localizer.Force ()
|
||||
m.messages
|
||||
m.Messages
|
||||
|> List.map (fun msg ->
|
||||
table [ _class $"pt-msg {msg.level.ToLower ()}" ] [
|
||||
table [ _class $"pt-msg {MessageLevel.toCssClass msg.Level}" ] [
|
||||
tr [] [
|
||||
td [] [
|
||||
match msg.level with
|
||||
| "Info" -> ()
|
||||
match msg.Level with
|
||||
| Info -> ()
|
||||
| lvl ->
|
||||
strong [] [ locStr s[lvl] ]
|
||||
strong [] [ locStr s[MessageLevel.toString lvl] ]
|
||||
rawText " » "
|
||||
rawText msg.text.Value
|
||||
match msg.description with
|
||||
rawText msg.Text.Value
|
||||
match msg.Description with
|
||||
| Some desc ->
|
||||
br []
|
||||
div [ _class "description" ] [ rawText desc.Value ]
|
||||
@ -238,7 +238,7 @@ let private messages m =
|
||||
let private htmlFooter m =
|
||||
let s = I18N.localizer.Force ()
|
||||
let imgText = sprintf "%O %O" s["PrayerTracker"] s["from Bit Badger Solutions"]
|
||||
let resultTime = TimeSpan(DateTime.Now.Ticks - m.requestStart).TotalSeconds
|
||||
let resultTime = TimeSpan(DateTime.Now.Ticks - m.RequestStart).TotalSeconds
|
||||
footer [] [
|
||||
div [ _id "pt-legal" ] [
|
||||
a [ _href "/web/legal/privacy-policy" ] [ locStr s["Privacy Policy"] ]
|
||||
@ -255,7 +255,7 @@ let private htmlFooter m =
|
||||
div [ _id "pt-footer" ] [
|
||||
a [ _href "/web/"; _style "line-height:28px;" ]
|
||||
[ img [ _src $"""/img/%O{s["footer_en"]}.png"""; _alt imgText; _title imgText ] ]
|
||||
str m.version
|
||||
str m.Version
|
||||
space
|
||||
i [ _title s["This page loaded in {0:N3} seconds", resultTime].Value; _class "material-icons md-18" ]
|
||||
[ str "schedule" ]
|
||||
|
@ -13,35 +13,35 @@ open PrayerTracker.ViewModels
|
||||
/// View for the prayer request edit page
|
||||
let edit (m : EditRequest) today ctx vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let pageTitle = if m.isNew () then "Add a New Request" else "Edit Request"
|
||||
let pageTitle = if m.IsNew then "Add a New Request" else "Edit Request"
|
||||
[ form [ _action "/web/prayer-request/save"; _method "post"; _class "pt-center-columns" ] [
|
||||
csrfToken ctx
|
||||
input [ _type "hidden"; _name "requestId"; _value (flatGuid m.requestId) ]
|
||||
input [ _type "hidden"; _name (nameof m.RequestId); _value (flatGuid m.RequestId) ]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "requestType" ] [ locStr s["Request Type"] ]
|
||||
label [ _for (nameof m.RequestType) ] [ locStr s["Request Type"] ]
|
||||
ReferenceList.requestTypeList s
|
||||
|> Seq.ofList
|
||||
|> Seq.map (fun (typ, desc) -> typ.code, desc.Value)
|
||||
|> selectList "requestType" m.requestType [ _required; _autofocus ]
|
||||
|> selectList (nameof m.RequestType) m.RequestType [ _required; _autofocus ]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "requestor" ] [ locStr s["Requestor / Subject"] ]
|
||||
input [ _type "text"
|
||||
_name "requestor"
|
||||
_name (nameof m.Requestor)
|
||||
_id "requestor"
|
||||
_value (match m.requestor with Some x -> x | None -> "") ]
|
||||
_value (defaultArg m.Requestor "") ]
|
||||
]
|
||||
if m.isNew () then
|
||||
if m.IsNew then
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "enteredDate" ] [ locStr s["Date"] ]
|
||||
input [ _type "date"; _name "enteredDate"; _id "enteredDate"; _placeholder today ]
|
||||
input [ _type "date"; _name (nameof m.EnteredDate); _id "enteredDate"; _placeholder today ]
|
||||
]
|
||||
else
|
||||
div [ _class "pt-field" ] [
|
||||
div [ _class "pt-checkbox-field" ] [
|
||||
br []
|
||||
input [ _type "checkbox"; _name "skipDateUpdate"; _id "skipDateUpdate"; _value "True" ]
|
||||
input [ _type "checkbox"; _name (nameof m.SkipDateUpdate); _id "skipDateUpdate"; _value "True" ]
|
||||
label [ _for "skipDateUpdate" ] [ locStr s["Check to not update the date"] ]
|
||||
br []
|
||||
small [] [ em [] [ str (s["Typo Corrections"].Value.ToLower ()); rawText ", etc." ] ]
|
||||
@ -51,11 +51,11 @@ let edit (m : EditRequest) today ctx vi =
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [] [ locStr s["Expiration"] ]
|
||||
ReferenceList.expirationList s ((m.isNew >> not) ())
|
||||
ReferenceList.expirationList s (not m.IsNew)
|
||||
|> List.map (fun exp ->
|
||||
let radioId = $"expiration_{fst exp}"
|
||||
span [ _class "text-nowrap" ] [
|
||||
radio "expiration" radioId (fst exp) m.expiration
|
||||
radio (nameof m.Expiration) radioId (fst exp) m.Expiration
|
||||
label [ _for radioId ] [ locStr (snd exp) ]
|
||||
rawText " "
|
||||
])
|
||||
@ -65,7 +65,7 @@ let edit (m : EditRequest) today ctx vi =
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field pt-editor" ] [
|
||||
label [ _for "text" ] [ locStr s["Request"] ]
|
||||
textarea [ _name "text"; _id "text" ] [ str m.text ]
|
||||
textarea [ _name (nameof m.Text); _id "text" ] [ str m.Text ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [ submit [] "save" s["Save Request"] ]
|
||||
@ -78,9 +78,9 @@ let edit (m : EditRequest) today ctx vi =
|
||||
/// View for the request e-mail results page
|
||||
let email m vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let pageTitle = $"""{s["Prayer Requests"].Value} • {m.listGroup.name}"""
|
||||
let prefs = m.listGroup.preferences
|
||||
let addresses = String.Join (", ", m.recipients |> List.map (fun mbr -> $"{mbr.memberName} <{mbr.email}>"))
|
||||
let pageTitle = $"""{s["Prayer Requests"].Value} • {m.SmallGroup.name}"""
|
||||
let prefs = m.SmallGroup.preferences
|
||||
let addresses = m.Recipients |> List.map (fun mbr -> $"{mbr.memberName} <{mbr.email}>") |> String.concat ", "
|
||||
[ p [ _style $"font-family:{prefs.listFonts};font-size:%i{prefs.textFontSize}pt;" ] [
|
||||
locStr s["The request list was sent to the following people, via individual e-mails"]
|
||||
rawText ":"
|
||||
@ -88,11 +88,11 @@ let email m vi =
|
||||
small [] [ str addresses ]
|
||||
]
|
||||
span [ _class "pt-email-heading" ] [ locStr s["HTML Format"]; rawText ":" ]
|
||||
div [ _class "pt-email-canvas" ] [ rawText (m.asHtml s) ]
|
||||
div [ _class "pt-email-canvas" ] [ rawText (m.AsHtml s) ]
|
||||
br []
|
||||
br []
|
||||
span [ _class "pt-email-heading" ] [ locStr s["Plain-Text Format"]; rawText ":" ]
|
||||
div [ _class "pt-email-canvas" ] [ pre [] [ str (m.asText s) ] ]
|
||||
div [ _class "pt-email-canvas" ] [ pre [] [ str (m.AsText s) ] ]
|
||||
]
|
||||
|> Layout.Content.standard
|
||||
|> Layout.standard vi pageTitle
|
||||
@ -101,7 +101,7 @@ let email m vi =
|
||||
/// View for a small group's public prayer request list
|
||||
let list (m : RequestList) vi =
|
||||
[ br []
|
||||
I18N.localizer.Force () |> (m.asHtml >> rawText)
|
||||
I18N.localizer.Force () |> (m.AsHtml >> rawText)
|
||||
]
|
||||
|> Layout.Content.standard
|
||||
|> Layout.standard vi "View Request List"
|
||||
@ -154,24 +154,24 @@ let lists (groups : SmallGroup list) vi =
|
||||
|
||||
|
||||
/// View for the prayer request maintenance page
|
||||
let maintain m (ctx : HttpContext) vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let l = I18N.forView "Requests/Maintain"
|
||||
use sw = new StringWriter ()
|
||||
let raw = rawLocText sw
|
||||
let now = m.smallGroup.localDateNow (ctx.GetService<IClock> ())
|
||||
let typs = ReferenceList.requestTypeList s |> Map.ofList
|
||||
let maintain (m : MaintainRequests) (ctx : HttpContext) vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let l = I18N.forView "Requests/Maintain"
|
||||
use sw = new StringWriter ()
|
||||
let raw = rawLocText sw
|
||||
let now = m.SmallGroup.localDateNow (ctx.GetService<IClock> ())
|
||||
let types = ReferenceList.requestTypeList s |> Map.ofList
|
||||
let updReq (req : PrayerRequest) =
|
||||
if req.updateRequired now m.smallGroup.preferences.daysToExpire m.smallGroup.preferences.longTermUpdateWeeks then
|
||||
if req.updateRequired now m.SmallGroup.preferences.daysToExpire m.SmallGroup.preferences.longTermUpdateWeeks then
|
||||
"pt-request-update"
|
||||
else ""
|
||||
|> _class
|
||||
let reqExp (req : PrayerRequest) =
|
||||
_class (if req.isExpired now m.smallGroup.preferences.daysToExpire then "pt-request-expired" else "")
|
||||
_class (if req.isExpired now m.SmallGroup.preferences.daysToExpire then "pt-request-expired" else "")
|
||||
/// Iterate the sequence once, before we render, so we can get the count of it at the top of the table
|
||||
let requests =
|
||||
m.requests
|
||||
|> Seq.map (fun req ->
|
||||
m.Requests
|
||||
|> List.map (fun req ->
|
||||
let reqId = flatGuid req.prayerRequestId
|
||||
let reqText = htmlToPlainText req.text
|
||||
let delAction = $"/web/prayer-request/{reqId}/delete"
|
||||
@ -187,7 +187,7 @@ let maintain m (ctx : HttpContext) vi =
|
||||
td [] [
|
||||
a [ _href $"/web/prayer-request/{reqId}/edit"; _title l["Edit This Prayer Request"].Value ]
|
||||
[ icon "edit" ]
|
||||
if req.isExpired now m.smallGroup.preferences.daysToExpire then
|
||||
if req.isExpired now m.SmallGroup.preferences.daysToExpire then
|
||||
a [ _href $"/web/prayer-request/{reqId}/restore"
|
||||
_title l["Restore This Inactive Request"].Value ]
|
||||
[ icon "visibility" ]
|
||||
@ -202,7 +202,7 @@ let maintain m (ctx : HttpContext) vi =
|
||||
td [ updReq req ] [
|
||||
str (req.updatedDate.ToString(s["MMMM d, yyyy"].Value, Globalization.CultureInfo.CurrentUICulture))
|
||||
]
|
||||
td [] [ locStr typs[req.requestType] ]
|
||||
td [] [ locStr types[req.requestType] ]
|
||||
td [ reqExp req ] [ str (match req.requestor with Some r -> r | None -> " ") ]
|
||||
td [] [
|
||||
match reqText.Length with
|
||||
@ -218,7 +218,7 @@ let maintain m (ctx : HttpContext) vi =
|
||||
rawText " "
|
||||
a [ _href "/web/prayer-requests/view"; _title s["View Prayer Request List"].Value ]
|
||||
[ icon "list"; rawText " "; locStr s["View Prayer Request List"] ]
|
||||
match m.searchTerm with
|
||||
match m.SearchTerm with
|
||||
| Some _ ->
|
||||
rawText " "
|
||||
a [ _href "/web/prayer-requests"; _title l["Clear Search Criteria"].Value ]
|
||||
@ -229,7 +229,7 @@ let maintain m (ctx : HttpContext) vi =
|
||||
input [ _type "text"
|
||||
_name "search"
|
||||
_placeholder l["Search requests..."].Value
|
||||
_value (defaultArg m.searchTerm "")
|
||||
_value (defaultArg m.SearchTerm "")
|
||||
]
|
||||
space
|
||||
submit [] "search" s["Search"]
|
||||
@ -253,54 +253,52 @@ let maintain m (ctx : HttpContext) vi =
|
||||
]
|
||||
div [ _class "pt-center-text" ] [
|
||||
br []
|
||||
match m.onlyActive with
|
||||
match m.OnlyActive with
|
||||
| Some true ->
|
||||
raw l["Inactive requests are currently not shown"]
|
||||
br []
|
||||
a [ _href "/web/prayer-requests/inactive" ] [ raw l["Show Inactive Requests"] ]
|
||||
| _ ->
|
||||
match Option.isSome m.onlyActive with
|
||||
| true ->
|
||||
if defaultArg m.OnlyActive false then
|
||||
raw l["Inactive requests are currently shown"]
|
||||
br []
|
||||
a [ _href "/web/prayer-requests" ] [ raw l["Do Not Show Inactive Requests"] ]
|
||||
br []
|
||||
br []
|
||||
| false -> ()
|
||||
let srch = [ match m.searchTerm with Some s -> "search", s | None -> () ]
|
||||
let pg = defaultArg m.pageNbr 1
|
||||
let url =
|
||||
match m.onlyActive with Some true | None -> "" | _ -> "/inactive" |> sprintf "/web/prayer-requests%s"
|
||||
let search = [ match m.SearchTerm with Some s -> "search", s | None -> () ]
|
||||
let pg = defaultArg m.PageNbr 1
|
||||
let url =
|
||||
match m.OnlyActive with Some true | None -> "" | _ -> "/inactive" |> sprintf "/web/prayer-requests%s"
|
||||
match pg with
|
||||
| 1 -> ()
|
||||
| _ ->
|
||||
// button (_type "submit" :: attrs) [ icon ico; rawText " "; locStr text ]
|
||||
let withPage = match pg with 2 -> srch | _ -> ("page", string (pg - 1)) :: srch
|
||||
let withPage = match pg with 2 -> search | _ -> ("page", string (pg - 1)) :: search
|
||||
a [ _href (makeUrl url withPage) ]
|
||||
[ icon "keyboard_arrow_left"; space; raw l["Previous Page"] ]
|
||||
rawText " "
|
||||
match requests.Length = m.smallGroup.preferences.pageSize with
|
||||
match requests.Length = m.SmallGroup.preferences.pageSize with
|
||||
| true ->
|
||||
a [ _href (makeUrl url (("page", string (pg + 1)) :: srch)) ]
|
||||
a [ _href (makeUrl url (("page", string (pg + 1)) :: search)) ]
|
||||
[ raw l["Next Page"]; space; icon "keyboard_arrow_right" ]
|
||||
| false -> ()
|
||||
]
|
||||
form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ]
|
||||
]
|
||||
|> Layout.Content.wide
|
||||
|> Layout.standard vi (match m.searchTerm with Some _ -> "Search Results" | None -> "Maintain Requests")
|
||||
|> Layout.standard vi (match m.SearchTerm with Some _ -> "Search Results" | None -> "Maintain Requests")
|
||||
|
||||
|
||||
/// View for the printable prayer request list
|
||||
let print m version =
|
||||
let s = I18N.localizer.Force ()
|
||||
let pageTitle = $"""{s["Prayer Requests"].Value} • {m.listGroup.name}"""
|
||||
let pageTitle = $"""{s["Prayer Requests"].Value} • {m.SmallGroup.name}"""
|
||||
let imgAlt = $"""{s["PrayerTracker"].Value} {s["from Bit Badger Solutions"].Value}"""
|
||||
article [] [
|
||||
rawText (m.asHtml s)
|
||||
rawText (m.AsHtml s)
|
||||
br []
|
||||
hr []
|
||||
div [ _style $"font-size:70%%;font-family:{m.listGroup.preferences.listFonts};" ] [
|
||||
div [ _style $"font-size:70%%;font-family:{m.SmallGroup.preferences.listFonts};" ] [
|
||||
img [ _src $"""/img/{s["footer_en"].Value}.png"""
|
||||
_style "vertical-align:text-bottom;"
|
||||
_alt imgAlt
|
||||
@ -315,21 +313,21 @@ let print m version =
|
||||
/// View for the prayer request list
|
||||
let view m vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let pageTitle = $"""{s["Prayer Requests"].Value} • {m.listGroup.name}"""
|
||||
let pageTitle = $"""{s["Prayer Requests"].Value} • {m.SmallGroup.name}"""
|
||||
let spacer = rawText " "
|
||||
let dtString = m.date.ToString "yyyy-MM-dd"
|
||||
let dtString = m.Date.ToString "yyyy-MM-dd"
|
||||
[ div [ _class "pt-center-text" ] [
|
||||
br []
|
||||
a [ _class "pt-icon-link"
|
||||
_href $"/web/prayer-requests/print/{dtString}"
|
||||
_title s["View Printable"].Value
|
||||
] [ icon "print"; rawText " "; locStr s["View Printable"] ]
|
||||
if m.canEmail then
|
||||
if m.CanEmail then
|
||||
spacer
|
||||
if m.date.DayOfWeek <> DayOfWeek.Sunday then
|
||||
if m.Date.DayOfWeek <> DayOfWeek.Sunday then
|
||||
let rec findSunday (date : DateTime) =
|
||||
if date.DayOfWeek = DayOfWeek.Sunday then date else findSunday (date.AddDays 1.)
|
||||
let sunday = findSunday m.date
|
||||
let sunday = findSunday m.Date
|
||||
a [ _class "pt-icon-link"
|
||||
_href $"""/web/prayer-requests/view/{sunday.ToString "yyyy-MM-dd"}"""
|
||||
_title s["List for Next Sunday"].Value ] [
|
||||
@ -349,7 +347,7 @@ let view m vi =
|
||||
]
|
||||
]
|
||||
br []
|
||||
rawText (m.asHtml s)
|
||||
rawText (m.AsHtml s)
|
||||
]
|
||||
|> Layout.Content.standard
|
||||
|> Layout.standard vi pageTitle
|
||||
|
@ -18,14 +18,15 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Giraffe" Version="5.0.0" />
|
||||
<PackageReference Include="Giraffe" Version="6.0.0" />
|
||||
<PackageReference Include="Giraffe.ViewEngine" Version="1.4.0" />
|
||||
<PackageReference Include="MailKit" Version="2.15.0" />
|
||||
<PackageReference Include="MailKit" Version="3.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Html.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Update="FSharp.Core" Version="6.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -822,4 +822,7 @@
|
||||
<data name="as of" xml:space="preserve">
|
||||
<value>como de</value>
|
||||
</data>
|
||||
<data name="State or Province" xml:space="preserve">
|
||||
<value>Estado o Provincia</value>
|
||||
</data>
|
||||
</root>
|
@ -10,13 +10,14 @@ open PrayerTracker.ViewModels
|
||||
/// View for the announcement page
|
||||
let announcement isAdmin ctx vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let m = { SendToClass = ""; Text = ""; AddToRequestList = None; RequestType = None }
|
||||
let reqTypes = ReferenceList.requestTypeList s
|
||||
[ form [ _action "/web/small-group/announcement/send"; _method "post"; _class "pt-center-columns" ] [
|
||||
csrfToken ctx
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field pt-editor" ] [
|
||||
label [ _for "text" ] [ locStr s["Announcement Text"] ]
|
||||
textarea [ _name "text"; _id "text"; _autofocus ] []
|
||||
textarea [ _name (nameof m.Text); _id "text"; _autofocus ] []
|
||||
]
|
||||
]
|
||||
if isAdmin then
|
||||
@ -24,27 +25,27 @@ let announcement isAdmin ctx vi =
|
||||
div [ _class "pt-field" ] [
|
||||
label [] [ locStr s["Send Announcement to"]; rawText ":" ]
|
||||
div [ _class "pt-center-text" ] [
|
||||
radio "sendToClass" "sendY" "Y" "Y"
|
||||
radio (nameof m.SendToClass) "sendY" "Y" "Y"
|
||||
label [ _for "sendY" ] [ locStr s["This Group"]; rawText " " ]
|
||||
radio "sendToClass" "sendN" "N" "Y"
|
||||
radio (nameof m.SendToClass) "sendN" "N" "Y"
|
||||
label [ _for "sendN" ] [ locStr s["All {0} Users", s["PrayerTracker"]] ]
|
||||
]
|
||||
]
|
||||
]
|
||||
else input [ _type "hidden"; _name "sendToClass"; _value "Y" ]
|
||||
else input [ _type "hidden"; _name (nameof m.SendToClass); _value "Y" ]
|
||||
div [ _class "pt-field-row pt-fadeable pt-shown"; _id "divAddToList" ] [
|
||||
div [ _class "pt-checkbox-field" ] [
|
||||
input [ _type "checkbox"; _name "addToRequestList"; _id "addToRequestList"; _value "True" ]
|
||||
input [ _type "checkbox"; _name (nameof m.AddToRequestList); _id "addToRequestList"; _value "True" ]
|
||||
label [ _for "addToRequestList" ] [ locStr s["Add to Request List"] ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row pt-fadeable"; _id "divCategory" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "requestType" ] [ locStr s["Request Type"] ]
|
||||
label [ _for (nameof m.RequestType) ] [ locStr s["Request Type"] ]
|
||||
reqTypes
|
||||
|> Seq.ofList
|
||||
|> Seq.map (fun (typ, desc) -> typ.code, desc.Value)
|
||||
|> selectList "requestType" "Announcement" []
|
||||
|> selectList (nameof m.RequestType) Announcement.code []
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [ submit [] "send" s["Send Announcement"] ]
|
||||
@ -59,12 +60,12 @@ let announcement isAdmin ctx vi =
|
||||
let announcementSent (m : Announcement) vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
[ span [ _class "pt-email-heading" ] [ locStr s["HTML Format"]; rawText ":" ]
|
||||
div [ _class "pt-email-canvas" ] [ rawText m.text ]
|
||||
div [ _class "pt-email-canvas" ] [ rawText m.Text ]
|
||||
br []
|
||||
br []
|
||||
span [ _class "pt-email-heading" ] [ locStr s["Plain-Text Format"]; rawText ":" ]
|
||||
div [ _class "pt-email-canvas" ] [ pre [] [ str (m.plainText ()) ] ]
|
||||
]
|
||||
div [ _class "pt-email-canvas" ] [ pre [] [ str m.PlainText ] ]
|
||||
]
|
||||
|> Layout.Content.standard
|
||||
|> Layout.standard vi "Announcement Sent"
|
||||
|
||||
@ -72,24 +73,24 @@ let announcementSent (m : Announcement) vi =
|
||||
/// View for the small group add/edit page
|
||||
let edit (m : EditSmallGroup) (churches : Church list) ctx vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let pageTitle = match m.isNew () with true -> "Add a New Group" | false -> "Edit Group"
|
||||
let pageTitle = if m.IsNew then "Add a New Group" else "Edit Group"
|
||||
form [ _action "/web/small-group/save"; _method "post"; _class "pt-center-columns" ] [
|
||||
csrfToken ctx
|
||||
input [ _type "hidden"; _name "smallGroupId"; _value (flatGuid m.smallGroupId) ]
|
||||
input [ _type "hidden"; _name (nameof m.SmallGroupId); _value (flatGuid m.SmallGroupId) ]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "name" ] [ locStr s["Group Name"] ]
|
||||
input [ _type "text"; _name "name"; _id "name"; _value m.name; _required; _autofocus ]
|
||||
input [ _type "text"; _name (nameof m.Name); _id "name"; _value m.Name; _required; _autofocus ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "churchId" ] [ locStr s["Church"] ]
|
||||
label [ _for (nameof m.ChurchId) ] [ locStr s["Church"] ]
|
||||
seq {
|
||||
"", selectDefault s["Select Church"].Value
|
||||
yield! churches |> List.map (fun c -> flatGuid c.churchId, c.name)
|
||||
}
|
||||
|> selectList "churchId" (flatGuid m.churchId) [ _required ]
|
||||
|> selectList (nameof m.ChurchId) (flatGuid m.ChurchId) [ _required ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [ submit [] "save" s["Save Group"] ]
|
||||
@ -100,29 +101,29 @@ let edit (m : EditSmallGroup) (churches : Church list) ctx vi =
|
||||
|
||||
|
||||
/// View for the member edit page
|
||||
let editMember (m : EditMember) (typs : (string * LocalizedString) seq) ctx vi =
|
||||
let editMember (m : EditMember) (types : (string * LocalizedString) seq) ctx vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let pageTitle = match m.isNew () with true -> "Add a New Group Member" | false -> "Edit Group Member"
|
||||
let pageTitle = if m.IsNew then "Add a New Group Member" else "Edit Group Member"
|
||||
form [ _action "/web/small-group/member/save"; _method "post"; _class "pt-center-columns" ] [
|
||||
style [ _scoped ] [ rawText "#memberName { width: 15rem; } #emailAddress { width: 20rem; }" ]
|
||||
style [ _scoped ] [ rawText "#name { width: 15rem; } #email { width: 20rem; }" ]
|
||||
csrfToken ctx
|
||||
input [ _type "hidden"; _name "memberId"; _value (flatGuid m.memberId) ]
|
||||
input [ _type "hidden"; _name (nameof m.MemberId); _value (flatGuid m.MemberId) ]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "memberName" ] [ locStr s["Member Name"] ]
|
||||
input [ _type "text"; _name "memberName"; _id "memberName"; _required; _autofocus; _value m.memberName ]
|
||||
label [ _for "name" ] [ locStr s["Member Name"] ]
|
||||
input [ _type "text"; _name (nameof m.Name); _id "name"; _required; _autofocus; _value m.Name ]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "emailAddress" ] [ locStr s["E-mail Address"] ]
|
||||
input [ _type "email"; _name "emailAddress"; _id "emailAddress"; _required; _value m.emailAddress ]
|
||||
label [ _for "email" ] [ locStr s["E-mail Address"] ]
|
||||
input [ _type "email"; _name (nameof m.Email); _id "email"; _required; _value m.Email ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "emailType" ] [ locStr s["E-mail Format"] ]
|
||||
typs
|
||||
label [ _for (nameof m.Format) ] [ locStr s["E-mail Format"] ]
|
||||
types
|
||||
|> Seq.map (fun typ -> fst typ, (snd typ).Value)
|
||||
|> selectList "emailType" m.emailType []
|
||||
|> selectList (nameof m.Format) m.Format []
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [ submit [] "save" s["Save"] ]
|
||||
@ -133,32 +134,36 @@ let editMember (m : EditMember) (typs : (string * LocalizedString) seq) ctx vi =
|
||||
|
||||
|
||||
/// View for the small group log on page
|
||||
let logOn (grps : SmallGroup list) grpId ctx vi =
|
||||
let logOn (groups : SmallGroup list) grpId ctx vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let m = { SmallGroupId = System.Guid.Empty; Password = ""; RememberMe = None }
|
||||
[ form [ _action "/web/small-group/log-on/submit"; _method "post"; _class "pt-center-columns" ] [
|
||||
csrfToken ctx
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "smallGroupId" ] [ locStr s["Group"] ]
|
||||
label [ _for (nameof m.SmallGroupId) ] [ locStr s["Group"] ]
|
||||
seq {
|
||||
match grps.Length with
|
||||
match groups.Length with
|
||||
| 0 -> "", s["There are no classes with passwords defined"].Value
|
||||
| _ ->
|
||||
"", selectDefault s["Select Group"].Value
|
||||
yield!
|
||||
grps
|
||||
groups
|
||||
|> List.map (fun grp -> flatGuid grp.smallGroupId, $"{grp.church.name} | {grp.name}")
|
||||
}
|
||||
|> selectList "smallGroupId" grpId [ _required ]
|
||||
|> selectList (nameof m.SmallGroupId) grpId [ _required ]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "password" ] [ locStr s["Password"] ]
|
||||
input [ _type "password"; _name "password"; _id "password"; _required;
|
||||
input [ _type "password"
|
||||
_name (nameof m.Password)
|
||||
_id "password"
|
||||
_required;
|
||||
_placeholder (s["Case-Sensitive"].Value.ToLower ()) ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-checkbox-field" ] [
|
||||
input [ _type "checkbox"; _name "rememberMe"; _id "rememberMe"; _value "True" ]
|
||||
input [ _type "checkbox"; _name (nameof m.RememberMe); _id "rememberMe"; _value "True" ]
|
||||
label [ _for "rememberMe" ] [ locStr s["Remember Me"] ]
|
||||
br []
|
||||
small [] [ em [] [ str (s["Requires Cookies"].Value.ToLower ()) ] ]
|
||||
@ -172,10 +177,10 @@ let logOn (grps : SmallGroup list) grpId ctx vi =
|
||||
|
||||
|
||||
/// View for the small group maintenance page
|
||||
let maintain (grps : SmallGroup list) ctx vi =
|
||||
let maintain (groups : SmallGroup list) ctx vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let grpTbl =
|
||||
match grps with
|
||||
match groups with
|
||||
| [] -> space
|
||||
| _ ->
|
||||
table [ _class "pt-table pt-action-table" ] [
|
||||
@ -187,7 +192,7 @@ let maintain (grps : SmallGroup list) ctx vi =
|
||||
th [] [ locStr s["Time Zone"] ]
|
||||
]
|
||||
]
|
||||
grps
|
||||
groups
|
||||
|> List.map (fun g ->
|
||||
let grpId = flatGuid g.smallGroupId
|
||||
let delAction = $"/web/small-group/{grpId}/delete"
|
||||
@ -218,7 +223,7 @@ let maintain (grps : SmallGroup list) ctx vi =
|
||||
br []
|
||||
br []
|
||||
]
|
||||
tableSummary grps.Length s
|
||||
tableSummary groups.Length s
|
||||
grpTbl
|
||||
form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ]
|
||||
]
|
||||
@ -227,10 +232,10 @@ let maintain (grps : SmallGroup list) ctx vi =
|
||||
|
||||
|
||||
/// View for the member maintenance page
|
||||
let members (mbrs : Member list) (emailTyps : Map<string, LocalizedString>) ctx vi =
|
||||
let members (members : Member list) (emailTyps : Map<string, LocalizedString>) ctx vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let mbrTbl =
|
||||
match mbrs with
|
||||
match members with
|
||||
| [] -> space
|
||||
| _ ->
|
||||
table [ _class "pt-table pt-action-table" ] [
|
||||
@ -242,7 +247,7 @@ let members (mbrs : Member list) (emailTyps : Map<string, LocalizedString>) ctx
|
||||
th [] [ locStr s["Format"] ]
|
||||
]
|
||||
]
|
||||
mbrs
|
||||
members
|
||||
|> List.map (fun mbr ->
|
||||
let mbrId = flatGuid mbr.memberId
|
||||
let delAction = $"/web/small-group/member/{mbrId}/delete"
|
||||
@ -271,7 +276,7 @@ let members (mbrs : Member list) (emailTyps : Map<string, LocalizedString>) ctx
|
||||
br []
|
||||
br []
|
||||
]
|
||||
tableSummary mbrs.Length s
|
||||
tableSummary members.Length s
|
||||
mbrTbl
|
||||
form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ]
|
||||
]
|
||||
@ -283,7 +288,7 @@ let members (mbrs : Member list) (emailTyps : Map<string, LocalizedString>) ctx
|
||||
let overview m vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let linkSpacer = rawText " "
|
||||
let typs = ReferenceList.requestTypeList s |> dict
|
||||
let types = ReferenceList.requestTypeList s |> dict
|
||||
article [ _class "pt-overview" ] [
|
||||
section [] [
|
||||
header [ _role "heading" ] [
|
||||
@ -306,16 +311,16 @@ let overview m vi =
|
||||
]
|
||||
div [] [
|
||||
p [ _class "pt-center-text" ] [
|
||||
strong [] [ str (m.totalActiveReqs.ToString "N0"); space; locStr s["Active Requests"] ]
|
||||
strong [] [ str (m.TotalActiveReqs.ToString "N0"); space; locStr s["Active Requests"] ]
|
||||
]
|
||||
hr []
|
||||
for cat in m.activeReqsByCat do
|
||||
for cat in m.ActiveReqsByType do
|
||||
str (cat.Value.ToString "N0")
|
||||
space
|
||||
locStr typs[cat.Key]
|
||||
locStr types[cat.Key]
|
||||
br []
|
||||
br []
|
||||
str (m.allReqs.ToString "N0")
|
||||
str (m.AllReqs.ToString "N0")
|
||||
space
|
||||
locStr s["Total Requests"]
|
||||
hr []
|
||||
@ -332,7 +337,7 @@ let overview m vi =
|
||||
locStr s["Group Members"]
|
||||
]
|
||||
div [ _class "pt-center-text" ] [
|
||||
strong [] [ str (m.totalMbrs.ToString "N0"); space; locStr s["Members"] ]
|
||||
strong [] [ str (m.TotalMembers.ToString "N0"); space; locStr s["Members"] ]
|
||||
hr []
|
||||
a [ _href "/web/small-group/members" ] [ icon "email"; linkSpacer; locStr s["Maintain Group Members"] ]
|
||||
]
|
||||
@ -350,7 +355,8 @@ let preferences (m : EditPreferences) (tzs : TimeZone list) ctx vi =
|
||||
use sw = new StringWriter ()
|
||||
let raw = rawLocText sw
|
||||
[ form [ _action "/web/small-group/preferences/save"; _method "post"; _class "pt-center-columns" ] [
|
||||
style [ _scoped ] [ rawText "#expireDays, #daysToKeepNew, #longTermUpdateWeeks, #headingFontSize, #listFontSize, #pageSize { width: 3rem; } #emailFromAddress { width: 20rem; } #listFonts { width: 40rem; } @media screen and (max-width: 40rem) { #listFonts { width: 100%; } }" ]
|
||||
style [ _scoped ]
|
||||
[ rawText "#expireDays, #daysToKeepNew, #longTermUpdateWeeks, #headingFontSize, #listFontSize, #pageSize { width: 3rem; } #emailFromAddress { width: 20rem; } #fonts { width: 40rem; } @media screen and (max-width: 40rem) { #fonts { width: 100%; } }" ]
|
||||
csrfToken ctx
|
||||
fieldset [] [
|
||||
legend [] [ strong [] [ icon "date_range"; rawText " "; locStr s["Dates"] ] ]
|
||||
@ -358,24 +364,37 @@ let preferences (m : EditPreferences) (tzs : TimeZone list) ctx vi =
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "expireDays" ] [ locStr s["Requests Expire After"] ]
|
||||
span [] [
|
||||
input [ _type "number"; _name "expireDays"; _id "expireDays"; _min "1"; _max "30"; _required
|
||||
_autofocus; _value (string m.expireDays) ]
|
||||
input [ _type "number"
|
||||
_name (nameof m.ExpireDays)
|
||||
_id "expireDays"
|
||||
_min "1"; _max "30"
|
||||
_required
|
||||
_autofocus
|
||||
_value (string m.ExpireDays) ]
|
||||
space; str (s["Days"].Value.ToLower ())
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "daysToKeepNew" ] [ locStr s["Requests “New” For"] ]
|
||||
span [] [
|
||||
input [ _type "number"; _name "daysToKeepNew"; _id "daysToKeepNew"; _min "1"; _max "30"
|
||||
_required; _value (string m.daysToKeepNew) ]
|
||||
input [ _type "number"
|
||||
_name (nameof m.DaysToKeepNew)
|
||||
_id "daysToKeepNew"
|
||||
_min "1"; _max "30"
|
||||
_required
|
||||
_value (string m.DaysToKeepNew) ]
|
||||
space; str (s["Days"].Value.ToLower ())
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "longTermUpdateWeeks" ] [ locStr s["Long-Term Requests Alerted for Update"] ]
|
||||
span [] [
|
||||
input [ _type "number"; _name "longTermUpdateWeeks"; _id "longTermUpdateWeeks"; _min "1"
|
||||
_max "30"; _required; _value (string m.longTermUpdateWeeks) ]
|
||||
input [ _type "number"
|
||||
_name (nameof m.LongTermUpdateWeeks)
|
||||
_id "longTermUpdateWeeks"
|
||||
_min "1"; _max "30"
|
||||
_required
|
||||
_value (string m.LongTermUpdateWeeks) ]
|
||||
space; str (s["Weeks"].Value.ToLower ())
|
||||
]
|
||||
]
|
||||
@ -383,10 +402,10 @@ let preferences (m : EditPreferences) (tzs : TimeZone list) ctx vi =
|
||||
]
|
||||
fieldset [] [
|
||||
legend [] [ strong [] [ icon "sort"; rawText " "; locStr s["Request Sorting"] ] ]
|
||||
radio "requestSort" "requestSort_D" "D" m.requestSort
|
||||
radio (nameof m.RequestSort) "requestSort_D" "D" m.RequestSort
|
||||
label [ _for "requestSort_D" ] [ locStr s["Sort by Last Updated Date"] ]
|
||||
rawText " "
|
||||
radio "requestSort" "requestSort_R" "R" m.requestSort
|
||||
radio (nameof m.RequestSort) "requestSort_R" "R" m.RequestSort
|
||||
label [ _for "requestSort_R" ] [ locStr s["Sort by Requestor Name"] ]
|
||||
]
|
||||
fieldset [] [
|
||||
@ -394,17 +413,24 @@ let preferences (m : EditPreferences) (tzs : TimeZone list) ctx vi =
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "emailFromName" ] [ locStr s["From Name"] ]
|
||||
input [ _type "text"; _name "emailFromName"; _id "emailFromName"; _required; _value m.emailFromName ]
|
||||
input [ _type "text"
|
||||
_name (nameof m.EmailFromName)
|
||||
_id "emailFromName"
|
||||
_required
|
||||
_value m.EmailFromName ]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "emailFromAddress" ] [ locStr s["From Address"] ]
|
||||
input [ _type "email"; _name "emailFromAddress"; _id "emailFromAddress"; _required
|
||||
_value m.emailFromAddress ]
|
||||
input [ _type "email"
|
||||
_name (nameof m.EmailFromAddress)
|
||||
_id "emailFromAddress"
|
||||
_required
|
||||
_value m.EmailFromAddress ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "defaultEmailType" ] [ locStr s["E-mail Format"] ]
|
||||
label [ _for (nameof m.DefaultEmailType) ] [ locStr s["E-mail Format"] ]
|
||||
seq {
|
||||
"", selectDefault s["Select"].Value
|
||||
yield!
|
||||
@ -412,7 +438,7 @@ let preferences (m : EditPreferences) (tzs : TimeZone list) ctx vi =
|
||||
|> Seq.skip 1
|
||||
|> Seq.map (fun typ -> fst typ, (snd typ).Value)
|
||||
}
|
||||
|> selectList "defaultEmailType" m.defaultEmailType [ _required ]
|
||||
|> selectList (nameof m.DefaultEmailType) m.DefaultEmailType [ _required ]
|
||||
]
|
||||
]
|
||||
]
|
||||
@ -422,19 +448,19 @@ let preferences (m : EditPreferences) (tzs : TimeZone list) ctx vi =
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _class "pt-center-text" ] [ locStr s["Color of Heading Lines"] ]
|
||||
span [] [
|
||||
radio "headingLineType" "headingLineType_Name" "Name" m.headingLineType
|
||||
label [ _for "headingLineType_Name" ] [ locStr s["Named Color"] ]
|
||||
namedColorList "headingLineColor" m.headingLineColor
|
||||
[ _id "headingLineColor_Select"
|
||||
match m.headingLineColor.StartsWith "#" with true -> _disabled | false -> () ] s
|
||||
radio (nameof m.LineColorType) "lineColorType_Name" "Name" m.LineColorType
|
||||
label [ _for "lineColorType_Name" ] [ locStr s["Named Color"] ]
|
||||
namedColorList (nameof m.LineColor) m.LineColor
|
||||
[ _id "lineColor_Select"
|
||||
if m.LineColor.StartsWith "#" then _disabled ] s
|
||||
rawText " "; str (s["or"].Value.ToUpper ())
|
||||
radio "headingLineType" "headingLineType_RGB" "RGB" m.headingLineType
|
||||
label [ _for "headingLineType_RGB" ] [ locStr s["Custom Color"] ]
|
||||
radio (nameof m.LineColorType) "lineColorType_RGB" "RGB" m.LineColorType
|
||||
label [ _for "lineColorType_RGB" ] [ locStr s["Custom Color"] ]
|
||||
input [ _type "color"
|
||||
_name "headingLineColor"
|
||||
_id "headingLineColor_Color"
|
||||
_value m.headingLineColor
|
||||
match m.headingLineColor.StartsWith "#" with true -> () | false -> _disabled ]
|
||||
_name (nameof m.LineColor)
|
||||
_id "lineColor_Color"
|
||||
_value m.LineColor // TODO: convert to hex or skip if named
|
||||
if not (m.LineColor.StartsWith "#") then _disabled ]
|
||||
]
|
||||
]
|
||||
]
|
||||
@ -442,19 +468,19 @@ let preferences (m : EditPreferences) (tzs : TimeZone list) ctx vi =
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _class "pt-center-text" ] [ locStr s["Color of Heading Text"] ]
|
||||
span [] [
|
||||
radio "headingTextType" "headingTextType_Name" "Name" m.headingTextType
|
||||
label [ _for "headingTextType_Name" ] [ locStr s["Named Color"] ]
|
||||
namedColorList "headingTextColor" m.headingTextColor
|
||||
[ _id "headingTextColor_Select"
|
||||
match m.headingTextColor.StartsWith "#" with true -> _disabled | false -> () ] s
|
||||
radio (nameof m.HeadingColorType) "headingColorType_Name" "Name" m.HeadingColorType
|
||||
label [ _for "headingColorType_Name" ] [ locStr s["Named Color"] ]
|
||||
namedColorList (nameof m.HeadingColor) m.HeadingColor
|
||||
[ _id "headingColor_Select"
|
||||
if m.HeadingColor.StartsWith "#" then _disabled ] s
|
||||
rawText " "; str (s["or"].Value.ToUpper ())
|
||||
radio "headingTextType" "headingTextType_RGB" "RGB" m.headingTextType
|
||||
label [ _for "headingTextType_RGB" ] [ locStr s["Custom Color"] ]
|
||||
radio (nameof m.HeadingColorType) "headingColorType_RGB" "RGB" m.HeadingColorType
|
||||
label [ _for "headingColorType_RGB" ] [ locStr s["Custom Color"] ]
|
||||
input [ _type "color"
|
||||
_name "headingTextColor"
|
||||
_id "headingTextColor_Color"
|
||||
_value m.headingTextColor
|
||||
match m.headingTextColor.StartsWith "#" with true -> () | false -> _disabled ]
|
||||
_name (nameof m.HeadingColor)
|
||||
_id "headingColor_Color"
|
||||
_value m.HeadingColor // TODO: convert to hex or skip if named
|
||||
if not (m.HeadingColor.StartsWith "#") then _disabled ]
|
||||
]
|
||||
]
|
||||
]
|
||||
@ -462,19 +488,27 @@ let preferences (m : EditPreferences) (tzs : TimeZone list) ctx vi =
|
||||
fieldset [] [
|
||||
legend [] [ strong [] [ icon "font_download"; rawText " "; locStr s["Fonts"] ] ]
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "listFonts" ] [ locStr s["Fonts** for List"] ]
|
||||
input [ _type "text"; _name "listFonts"; _id "listFonts"; _required; _value m.listFonts ]
|
||||
label [ _for "fonts" ] [ locStr s["Fonts** for List"] ]
|
||||
input [ _type "text"; _name (nameof m.Fonts); _id "fonts"; _required; _value m.Fonts ]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "headingFontSize" ] [ locStr s["Heading Text Size"] ]
|
||||
input [ _type "number"; _name "headingFontSize"; _id "headingFontSize"; _min "8"; _max "24"
|
||||
_required; _value (string m.headingFontSize) ]
|
||||
input [ _type "number"
|
||||
_name (nameof m.HeadingFontSize)
|
||||
_id "headingFontSize"
|
||||
_min "8"; _max "24"
|
||||
_required
|
||||
_value (string m.HeadingFontSize) ]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "listFontSize" ] [ locStr s["List Text Size"] ]
|
||||
input [ _type "number"; _name "listFontSize"; _id "listFontSize"; _min "8"; _max "24"; _required
|
||||
_value (string m.listFontSize) ]
|
||||
input [ _type "number"
|
||||
_name (nameof m.ListFontSize)
|
||||
_id "listFontSize"
|
||||
_min "8"; _max "24"
|
||||
_required
|
||||
_value (string m.ListFontSize) ]
|
||||
]
|
||||
]
|
||||
]
|
||||
@ -482,48 +516,54 @@ let preferences (m : EditPreferences) (tzs : TimeZone list) ctx vi =
|
||||
legend [] [ strong [] [ icon "settings"; rawText " "; locStr s["Other Settings"] ] ]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "timeZone" ] [ locStr s["Time Zone"] ]
|
||||
label [ _for (nameof m.TimeZone) ] [ locStr s["Time Zone"] ]
|
||||
seq {
|
||||
"", selectDefault s["Select"].Value
|
||||
yield! tzs |> List.map (fun tz -> tz.timeZoneId, (TimeZones.name tz.timeZoneId s).Value)
|
||||
}
|
||||
|> selectList "timeZone" m.timeZone [ _required ]
|
||||
|> selectList (nameof m.TimeZone) m.TimeZone [ _required ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [] [ locStr s["Request List Visibility"] ]
|
||||
span [] [
|
||||
radio "listVisibility" "viz_Public" (string RequestVisibility.``public``) (string m.listVisibility)
|
||||
radio (nameof m.Visibility) "viz_Public" (string RequestVisibility.``public``) (string m.Visibility)
|
||||
label [ _for "viz_Public" ] [ locStr s["Public"] ]
|
||||
rawText " "
|
||||
radio "listVisibility" "viz_Private" (string RequestVisibility.``private``)
|
||||
(string m.listVisibility)
|
||||
radio (nameof m.Visibility) "viz_Private" (string RequestVisibility.``private``)
|
||||
(string m.Visibility)
|
||||
label [ _for "viz_Private" ] [ locStr s["Private"] ]
|
||||
rawText " "
|
||||
radio "listVisibility" "viz_Password" (string RequestVisibility.passwordProtected)
|
||||
(string m.listVisibility)
|
||||
radio (nameof m.Visibility) "viz_Password" (string RequestVisibility.passwordProtected)
|
||||
(string m.Visibility)
|
||||
label [ _for "viz_Password" ] [ locStr s["Password Protected"] ]
|
||||
]
|
||||
]
|
||||
let classSuffix = if m.listVisibility = RequestVisibility.passwordProtected then " pt-show" else ""
|
||||
let classSuffix = if m.Visibility = RequestVisibility.passwordProtected then " pt-show" else ""
|
||||
div [ _id "divClassPassword"; _class $"pt-field-row pt-fadeable{classSuffix}" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "groupPassword" ] [ locStr s["Group Password (Used to Read Online)"] ]
|
||||
input [ _type "text"; _name "groupPassword"; _id "groupPassword";
|
||||
_value (match m.groupPassword with Some x -> x | None -> "") ]
|
||||
input [ _type "text"
|
||||
_name (nameof m.GroupPassword)
|
||||
_id "groupPassword"
|
||||
_value (defaultArg m.GroupPassword "") ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "pageSize" ] [ locStr s["Page Size"] ]
|
||||
input [ _type "number"; _name "pageSize"; _id "pageSize"; _min "10"; _max "255"; _required
|
||||
_value (string m.pageSize) ]
|
||||
input [ _type "number"
|
||||
_name (nameof m.PageSize)
|
||||
_id "pageSize"
|
||||
_min "10"; _max "255"
|
||||
_required
|
||||
_value (string m.PageSize) ]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "asOfDate" ] [ locStr s["“As of” Date Display"] ]
|
||||
label [ _for (nameof m.AsOfDate) ] [ locStr s["“As of” Date Display"] ]
|
||||
ReferenceList.asOfDateList s
|
||||
|> List.map (fun (code, desc) -> code, desc.Value)
|
||||
|> selectList "asOfDate" m.asOfDate [ _required ]
|
||||
|> selectList (nameof m.AsOfDate) m.AsOfDate [ _required ]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
@ -7,11 +7,11 @@ open PrayerTracker.ViewModels
|
||||
/// View for the group assignment page
|
||||
let assignGroups m groups curGroups ctx vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let pageTitle = sprintf "%s • %A" m.userName s["Assign Groups"]
|
||||
let pageTitle = sprintf "%s • %A" m.UserName s["Assign Groups"]
|
||||
form [ _action "/web/user/small-groups/save"; _method "post"; _class "pt-center-columns" ] [
|
||||
csrfToken ctx
|
||||
input [ _type "hidden"; _name "userId"; _value (flatGuid m.userId) ]
|
||||
input [ _type "hidden"; _name "userName"; _value m.userName ]
|
||||
input [ _type "hidden"; _name (nameof m.UserId); _value (flatGuid m.UserId) ]
|
||||
input [ _type "hidden"; _name (nameof m.UserName); _value m.UserName ]
|
||||
table [ _class "pt-table" ] [
|
||||
thead [] [
|
||||
tr [] [
|
||||
@ -25,10 +25,10 @@ let assignGroups m groups curGroups ctx vi =
|
||||
tr [] [
|
||||
td [] [
|
||||
input [ _type "checkbox"
|
||||
_name "smallGroups"
|
||||
_name (nameof m.SmallGroups)
|
||||
_id inputId
|
||||
_value grpId
|
||||
match curGroups |> List.contains grpId with true -> _checked | false -> () ]
|
||||
if List.contains grpId curGroups then _checked ]
|
||||
]
|
||||
td [] [ label [ _for inputId ] [ str grpName ] ]
|
||||
])
|
||||
@ -44,6 +44,7 @@ let assignGroups m groups curGroups ctx vi =
|
||||
/// View for the password change page
|
||||
let changePassword ctx vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let m = { OldPassword = ""; NewPassword = ""; NewPasswordConfirm = "" }
|
||||
[ p [ _class "pt-center-text" ] [
|
||||
locStr s["To change your password, enter your current password in the specified box below, then enter your new password twice."]
|
||||
]
|
||||
@ -55,17 +56,17 @@ let changePassword ctx vi =
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "oldPassword" ] [ locStr s["Current Password"] ]
|
||||
input [ _type "password"; _name "oldPassword"; _id "oldPassword"; _required; _autofocus ]
|
||||
input [ _type "password"; _name (nameof m.OldPassword); _id "oldPassword"; _required; _autofocus ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "newPassword" ] [ locStr s["New Password Twice"] ]
|
||||
input [ _type "password"; _name "newPassword"; _id "newPassword"; _required ]
|
||||
input [ _type "password"; _name (nameof m.NewPassword); _id "newPassword"; _required ]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [] [ rawText " " ]
|
||||
input [ _type "password"; _name "newPasswordConfirm"; _id "newPasswordConfirm"; _required ]
|
||||
input [ _type "password"; _name (nameof m.NewPasswordConfirm); _id "newPasswordConfirm"; _required ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [
|
||||
@ -81,49 +82,57 @@ let changePassword ctx vi =
|
||||
/// View for the edit user page
|
||||
let edit (m : EditUser) ctx vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let pageTitle = if m.isNew () then "Add a New User" else "Edit User"
|
||||
let pwPlaceholder = s[if m.isNew () then "" else "No change"].Value
|
||||
let pageTitle = if m.IsNew then "Add a New User" else "Edit User"
|
||||
let pwPlaceholder = s[if m.IsNew then "" else "No change"].Value
|
||||
[ form [ _action "/web/user/edit/save"; _method "post"; _class "pt-center-columns"
|
||||
_onsubmit $"""return PT.compareValidation('password','passwordConfirm','%A{s["The passwords do not match"]}')""" ] [
|
||||
style [ _scoped ]
|
||||
[ rawText "#firstName, #lastName, #password, #passwordConfirm { width: 10rem; } #emailAddress { width: 20rem; } " ]
|
||||
[ rawText "#firstName, #lastName, #password, #passwordConfirm { width: 10rem; } #email { width: 20rem; } " ]
|
||||
csrfToken ctx
|
||||
input [ _type "hidden"; _name "userId"; _value (flatGuid m.userId) ]
|
||||
input [ _type "hidden"; _name (nameof m.UserId); _value (flatGuid m.UserId) ]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "firstName" ] [ locStr s["First Name"] ]
|
||||
input [ _type "text"; _name "firstName"; _id "firstName"; _value m.firstName; _required; _autofocus ]
|
||||
input [ _type "text"
|
||||
_name (nameof m.FirstName)
|
||||
_id "firstName"
|
||||
_value m.FirstName
|
||||
_required
|
||||
_autofocus ]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "lastName" ] [ locStr s["Last Name"] ]
|
||||
input [ _type "text"; _name "lastName"; _id "lastName"; _value m.lastName; _required ]
|
||||
input [ _type "text"; _name (nameof m.LastName); _id "lastName"; _value m.LastName; _required ]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "emailAddress" ] [ locStr s["E-mail Address"] ]
|
||||
input [ _type "email"; _name "emailAddress"; _id "emailAddress"; _value m.emailAddress; _required ]
|
||||
label [ _for "email" ] [ locStr s["E-mail Address"] ]
|
||||
input [ _type "email"; _name (nameof m.Email); _id "email"; _value m.Email; _required ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "password" ] [ locStr s["Password"] ]
|
||||
input [ _type "password"; _name "password"; _id "password"; _placeholder pwPlaceholder ]
|
||||
input [ _type "password"; _name (nameof m.Password); _id "password"; _placeholder pwPlaceholder ]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "passwordConfirm" ] [ locStr s["Password Again"] ]
|
||||
input [ _type "password"; _name "passwordConfirm"; _id "passwordConfirm"; _placeholder pwPlaceholder ]
|
||||
input [ _type "password"
|
||||
_name (nameof m.PasswordConfirm)
|
||||
_id "passwordConfirm"
|
||||
_placeholder pwPlaceholder ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-checkbox-field" ] [
|
||||
input [ _type "checkbox"
|
||||
_name "isAdmin"
|
||||
_name (nameof m.IsAdmin)
|
||||
_id "isAdmin"
|
||||
_value "True"
|
||||
match m.isAdmin with Some x when x -> _checked | _ -> () ]
|
||||
if defaultArg m.IsAdmin false then _checked ]
|
||||
label [ _for "isAdmin" ] [ locStr s["This user is a PrayerTracker administrator"] ]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [ submit [] "save" s["Save User"] ]
|
||||
]
|
||||
script [] [ rawText $"PT.onLoad(PT.user.edit.onPageLoad({(string (m.isNew ())).ToLower ()}))" ]
|
||||
script [] [ rawText $"PT.onLoad(PT.user.edit.onPageLoad({(string m.IsNew).ToLowerInvariant ()}))" ]
|
||||
]
|
||||
|> Layout.Content.standard
|
||||
|> Layout.standard vi pageTitle
|
||||
@ -133,33 +142,35 @@ let edit (m : EditUser) ctx vi =
|
||||
let logOn (m : UserLogOn) groups ctx vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
form [ _action "/web/user/log-on"; _method "post"; _class "pt-center-columns" ] [
|
||||
style [ _scoped ] [ rawText "#emailAddress { width: 20rem; }" ]
|
||||
style [ _scoped ] [ rawText "#email { width: 20rem; }" ]
|
||||
csrfToken ctx
|
||||
input [ _type "hidden"; _name "redirectUrl"; _value (defaultArg m.redirectUrl "") ]
|
||||
input [ _type "hidden"; _name (nameof m.RedirectUrl); _value (defaultArg m.RedirectUrl "") ]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "emailAddress"] [ locStr s["E-mail Address"] ]
|
||||
input [ _type "email"; _name "emailAddress"; _id "emailAddress"; _value m.emailAddress; _required
|
||||
_autofocus ]
|
||||
label [ _for "email"] [ locStr s["E-mail Address"] ]
|
||||
input [ _type "email"; _name (nameof m.Email); _id "email"; _value m.Email; _required; _autofocus ]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "password" ] [ locStr s["Password"] ]
|
||||
input [ _type "password"; _name "password"; _id "password"; _required;
|
||||
input [ _type "password"
|
||||
_name (nameof m.Password)
|
||||
_id "password"
|
||||
_required;
|
||||
_placeholder (sprintf "(%s)" (s["Case-Sensitive"].Value.ToLower ())) ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "smallGroupId" ] [ locStr s["Group"] ]
|
||||
label [ _for (nameof m.SmallGroupId) ] [ locStr s["Group"] ]
|
||||
seq {
|
||||
"", selectDefault s["Select Group"].Value
|
||||
yield! groups
|
||||
}
|
||||
|> selectList "smallGroupId" "" [ _required ]
|
||||
|> selectList (nameof m.SmallGroupId) "" [ _required ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-checkbox-field" ] [
|
||||
input [ _type "checkbox"; _name "rememberMe"; _id "rememberMe"; _value "True" ]
|
||||
input [ _type "checkbox"; _name (nameof m.RememberMe); _id "rememberMe"; _value "True" ]
|
||||
label [ _for "rememberMe" ] [ locStr s["Remember Me"] ]
|
||||
br []
|
||||
small [] [ em [] [ rawText "("; str (s["Requires Cookies"].Value.ToLower ()); rawText ")" ] ]
|
||||
|
@ -32,12 +32,7 @@ module String =
|
||||
let replaceFirst (needle : string) replacement (haystack : string) =
|
||||
match haystack.IndexOf needle with
|
||||
| -1 -> haystack
|
||||
| idx ->
|
||||
[ haystack[0..idx - 1]
|
||||
replacement
|
||||
haystack[idx + needle.Length..]
|
||||
]
|
||||
|> String.concat ""
|
||||
| idx -> String.concat "" [ haystack[0..idx - 1]; replacement; haystack[idx + needle.Length..] ]
|
||||
|
||||
|
||||
open System.Text.RegularExpressions
|
||||
@ -49,14 +44,15 @@ let stripTags allowedTags input =
|
||||
let mutable output = input
|
||||
for tag in stripHtmlExp.Matches input do
|
||||
let htmlTag = tag.Value.ToLower ()
|
||||
let isAllowed =
|
||||
let shouldReplace =
|
||||
allowedTags
|
||||
|> List.fold (fun acc t ->
|
||||
acc
|
||||
|| htmlTag.IndexOf $"<{t}>" = 0
|
||||
|| htmlTag.IndexOf $"<{t} " = 0
|
||||
|| htmlTag.IndexOf $"</{t}" = 0) false
|
||||
if isAllowed then output <- String.replaceFirst tag.Value "" output
|
||||
|> not
|
||||
if shouldReplace then output <- String.replaceFirst tag.Value "" output
|
||||
output
|
||||
|
||||
|
||||
@ -88,9 +84,9 @@ let wordWrap charPerLine (input : string) =
|
||||
remaining <- remaining[spaceIdx + 1..]
|
||||
// Leftovers - yum!
|
||||
match remaining.Length with 0 -> () | _ -> yield remaining
|
||||
yield ""
|
||||
}
|
||||
|> Seq.fold (fun (acc : StringBuilder) -> acc.AppendLine) (StringBuilder ())
|
||||
|> string
|
||||
|> String.concat "\n"
|
||||
|
||||
/// Modify the text returned by CKEditor into the format we need for request and announcement text
|
||||
let ckEditorToText (text : string) =
|
||||
|
@ -43,19 +43,40 @@ module ReferenceList =
|
||||
Announcement, s["Announcements"]
|
||||
]
|
||||
|
||||
// fsharplint:disable RecordFieldNames MemberNames
|
||||
|
||||
/// A user message level
|
||||
type MessageLevel =
|
||||
/// An informational message to the user
|
||||
| Info
|
||||
/// A message with information the user should consider
|
||||
| Warning
|
||||
/// A message indicating that something went wrong
|
||||
| Error
|
||||
|
||||
/// Support for the MessageLevel type
|
||||
module MessageLevel =
|
||||
|
||||
/// Convert a message level to its string representation
|
||||
let toString =
|
||||
function
|
||||
| Info -> "Info"
|
||||
| Warning -> "WARNING"
|
||||
| Error -> "ERROR"
|
||||
|
||||
let toCssClass level = (toString level).ToLowerInvariant ()
|
||||
|
||||
|
||||
/// This is used to create a message that is displayed to the user
|
||||
[<NoComparison; NoEquality>]
|
||||
type UserMessage =
|
||||
{ /// The type
|
||||
level : string
|
||||
Level : MessageLevel
|
||||
|
||||
/// The actual message
|
||||
text : HtmlString
|
||||
Text : HtmlString
|
||||
|
||||
/// The description (further information)
|
||||
description : HtmlString option
|
||||
Description : HtmlString option
|
||||
}
|
||||
|
||||
/// Support for the UserMessage type
|
||||
@ -63,23 +84,23 @@ module UserMessage =
|
||||
|
||||
/// Error message template
|
||||
let error =
|
||||
{ level = "ERROR"
|
||||
text = HtmlString.Empty
|
||||
description = None
|
||||
{ Level = Error
|
||||
Text = HtmlString.Empty
|
||||
Description = None
|
||||
}
|
||||
|
||||
/// Warning message template
|
||||
let warning =
|
||||
{ level = "WARNING"
|
||||
text = HtmlString.Empty
|
||||
description = None
|
||||
{ Level = Warning
|
||||
Text = HtmlString.Empty
|
||||
Description = None
|
||||
}
|
||||
|
||||
/// Info message template
|
||||
let info =
|
||||
{ level = "Info"
|
||||
text = HtmlString.Empty
|
||||
description = None
|
||||
{ Level = Info
|
||||
Text = HtmlString.Empty
|
||||
Description = None
|
||||
}
|
||||
|
||||
|
||||
@ -89,28 +110,28 @@ open System
|
||||
[<NoComparison; NoEquality>]
|
||||
type AppViewInfo =
|
||||
{ /// CSS files for the page
|
||||
style : string list
|
||||
Style : string list
|
||||
|
||||
/// JavaScript files for the page
|
||||
script : string list
|
||||
Script : string list
|
||||
|
||||
/// The link for help on this page
|
||||
helpLink : string option
|
||||
HelpLink : string option
|
||||
|
||||
/// Messages to be displayed to the user
|
||||
messages : UserMessage list
|
||||
Messages : UserMessage list
|
||||
|
||||
/// The current version of PrayerTracker
|
||||
version : string
|
||||
Version : string
|
||||
|
||||
/// The ticks when the request started
|
||||
requestStart : int64
|
||||
RequestStart : int64
|
||||
|
||||
/// The currently logged on user, if there is one
|
||||
user : User option
|
||||
User : User option
|
||||
|
||||
/// The currently logged on small group, if there is one
|
||||
group : SmallGroup option
|
||||
Group : SmallGroup option
|
||||
}
|
||||
|
||||
/// Support for the AppViewInfo type
|
||||
@ -118,14 +139,14 @@ module AppViewInfo =
|
||||
|
||||
/// A fresh version that can be populated to process the current request
|
||||
let fresh =
|
||||
{ style = []
|
||||
script = []
|
||||
helpLink = None
|
||||
messages = []
|
||||
version = ""
|
||||
requestStart = DateTime.Now.Ticks
|
||||
user = None
|
||||
group = None
|
||||
{ Style = []
|
||||
Script = []
|
||||
HelpLink = None
|
||||
Messages = []
|
||||
Version = ""
|
||||
RequestStart = DateTime.Now.Ticks
|
||||
User = None
|
||||
Group = None
|
||||
}
|
||||
|
||||
|
||||
@ -133,34 +154,35 @@ module AppViewInfo =
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type Announcement =
|
||||
{ /// Whether the announcement should be sent to the class or to PrayerTracker users
|
||||
sendToClass : string
|
||||
SendToClass : string
|
||||
|
||||
/// The text of the announcement
|
||||
text : string
|
||||
Text : string
|
||||
|
||||
/// Whether this announcement should be added to the "Announcements" of the prayer list
|
||||
addToRequestList : bool option
|
||||
AddToRequestList : bool option
|
||||
|
||||
/// The ID of the request type to which this announcement should be added
|
||||
requestType : string option
|
||||
RequestType : string option
|
||||
}
|
||||
with
|
||||
|
||||
/// The text of the announcement, in plain text
|
||||
member this.plainText () = (htmlToPlainText >> wordWrap 74) this.text
|
||||
member this.PlainText
|
||||
with get () = (htmlToPlainText >> wordWrap 74) this.Text
|
||||
|
||||
|
||||
/// Form for assigning small groups to a user
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type AssignGroups =
|
||||
{ /// The Id of the user being assigned
|
||||
userId : UserId
|
||||
UserId : UserId
|
||||
|
||||
/// The full name of the user being assigned
|
||||
userName : string
|
||||
UserName : string
|
||||
|
||||
/// The Ids of the small groups to which the user is authorized
|
||||
smallGroups : string
|
||||
SmallGroups : string
|
||||
}
|
||||
|
||||
/// Support for the AssignGroups type
|
||||
@ -168,9 +190,9 @@ module AssignGroups =
|
||||
|
||||
/// Create an instance of this form from an existing user
|
||||
let fromUser (u : User) =
|
||||
{ userId = u.userId
|
||||
userName = u.fullName
|
||||
smallGroups = ""
|
||||
{ UserId = u.userId
|
||||
UserName = u.fullName
|
||||
SmallGroups = ""
|
||||
}
|
||||
|
||||
|
||||
@ -178,11 +200,13 @@ module AssignGroups =
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type ChangePassword =
|
||||
{ /// The user's current password
|
||||
oldPassword : string
|
||||
OldPassword : string
|
||||
|
||||
/// The user's new password
|
||||
newPassword : string
|
||||
NewPassword : string
|
||||
|
||||
/// The user's new password, confirmed
|
||||
newPasswordConfirm : string
|
||||
NewPasswordConfirm : string
|
||||
}
|
||||
|
||||
|
||||
@ -190,36 +214,37 @@ type ChangePassword =
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type EditChurch =
|
||||
{ /// The Id of the church
|
||||
churchId : ChurchId
|
||||
ChurchId : ChurchId
|
||||
|
||||
/// The name of the church
|
||||
name : string
|
||||
Name : string
|
||||
|
||||
/// The city for the church
|
||||
city : string
|
||||
City : string
|
||||
|
||||
/// The state for the church
|
||||
st : string
|
||||
/// The state or province for the church
|
||||
State : string
|
||||
|
||||
/// Whether the church has an active VPR interface
|
||||
hasInterface : bool option
|
||||
/// Whether the church has an active Virtual Prayer Room interface
|
||||
HasInterface : bool option
|
||||
|
||||
/// The address for the interface
|
||||
interfaceAddress : string option
|
||||
InterfaceAddress : string option
|
||||
}
|
||||
with
|
||||
|
||||
/// Is this a new church?
|
||||
member this.isNew () = Guid.Empty = this.churchId
|
||||
member this.IsNew
|
||||
with get () = Guid.Empty = this.ChurchId
|
||||
|
||||
/// Populate a church from this form
|
||||
member this.populateChurch (church : Church) =
|
||||
member this.PopulateChurch (church : Church) =
|
||||
{ church with
|
||||
name = this.name
|
||||
city = this.city
|
||||
st = this.st
|
||||
hasInterface = match this.hasInterface with Some x -> x | None -> false
|
||||
interfaceAddress = match this.hasInterface with Some x when x -> this.interfaceAddress | _ -> None
|
||||
name = this.Name
|
||||
city = this.City
|
||||
st = this.State
|
||||
hasInterface = match this.HasInterface with Some x -> x | None -> false
|
||||
interfaceAddress = match this.HasInterface with Some x when x -> this.InterfaceAddress | _ -> None
|
||||
}
|
||||
|
||||
/// Support for the EditChurch type
|
||||
@ -227,22 +252,22 @@ module EditChurch =
|
||||
|
||||
/// Create an instance from an existing church
|
||||
let fromChurch (ch : Church) =
|
||||
{ churchId = ch.churchId
|
||||
name = ch.name
|
||||
city = ch.city
|
||||
st = ch.st
|
||||
hasInterface = match ch.hasInterface with true -> Some true | false -> None
|
||||
interfaceAddress = ch.interfaceAddress
|
||||
{ ChurchId = ch.churchId
|
||||
Name = ch.name
|
||||
City = ch.city
|
||||
State = ch.st
|
||||
HasInterface = match ch.hasInterface with true -> Some true | false -> None
|
||||
InterfaceAddress = ch.interfaceAddress
|
||||
}
|
||||
|
||||
/// An instance to use for adding churches
|
||||
let empty =
|
||||
{ churchId = Guid.Empty
|
||||
name = ""
|
||||
city = ""
|
||||
st = ""
|
||||
hasInterface = None
|
||||
interfaceAddress = None
|
||||
{ ChurchId = Guid.Empty
|
||||
Name = ""
|
||||
City = ""
|
||||
State = ""
|
||||
HasInterface = None
|
||||
InterfaceAddress = None
|
||||
}
|
||||
|
||||
|
||||
@ -250,39 +275,40 @@ module EditChurch =
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type EditMember =
|
||||
{ /// The Id for this small group member (not user-entered)
|
||||
memberId : MemberId
|
||||
MemberId : MemberId
|
||||
|
||||
/// The name of the member
|
||||
memberName : string
|
||||
Name : string
|
||||
|
||||
/// The e-mail address
|
||||
emailAddress : string
|
||||
Email : string
|
||||
|
||||
/// The e-mail format
|
||||
emailType : string
|
||||
Format : string
|
||||
}
|
||||
with
|
||||
|
||||
/// Is this a new member?
|
||||
member this.isNew () = Guid.Empty = this.memberId
|
||||
member this.IsNew
|
||||
with get () = Guid.Empty = this.MemberId
|
||||
|
||||
/// Support for the EditMember type
|
||||
module EditMember =
|
||||
|
||||
/// Create an instance from an existing member
|
||||
let fromMember (m : Member) =
|
||||
{ memberId = m.memberId
|
||||
memberName = m.memberName
|
||||
emailAddress = m.email
|
||||
emailType = match m.format with Some f -> f | None -> ""
|
||||
{ MemberId = m.memberId
|
||||
Name = m.memberName
|
||||
Email = m.email
|
||||
Format = match m.format with Some f -> f | None -> ""
|
||||
}
|
||||
|
||||
/// An empty instance
|
||||
let empty =
|
||||
{ memberId = Guid.Empty
|
||||
memberName = ""
|
||||
emailAddress = ""
|
||||
emailType = ""
|
||||
{ MemberId = Guid.Empty
|
||||
Name = ""
|
||||
Email = ""
|
||||
Format = ""
|
||||
}
|
||||
|
||||
|
||||
@ -290,90 +316,90 @@ module EditMember =
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type EditPreferences =
|
||||
{ /// The number of days after which requests are automatically expired
|
||||
expireDays : int
|
||||
ExpireDays : int
|
||||
|
||||
/// The number of days requests are considered "new"
|
||||
daysToKeepNew : int
|
||||
DaysToKeepNew : int
|
||||
|
||||
/// The number of weeks after which a long-term requests is flagged as requiring an update
|
||||
longTermUpdateWeeks : int
|
||||
LongTermUpdateWeeks : int
|
||||
|
||||
/// Whether to sort by updated date or requestor/subject
|
||||
requestSort : string
|
||||
RequestSort : string
|
||||
|
||||
/// The name from which e-mail will be sent
|
||||
emailFromName : string
|
||||
EmailFromName : string
|
||||
|
||||
/// The e-mail address from which e-mail will be sent
|
||||
emailFromAddress : string
|
||||
EmailFromAddress : string
|
||||
|
||||
/// The default e-mail type for this group
|
||||
defaultEmailType : string
|
||||
DefaultEmailType : string
|
||||
|
||||
/// Whether the heading line color uses named colors or R/G/B
|
||||
headingLineType : string
|
||||
LineColorType : string
|
||||
|
||||
/// The named color for the heading lines
|
||||
headingLineColor : string
|
||||
LineColor : string
|
||||
|
||||
/// Whether the heading text color uses named colors or R/G/B
|
||||
headingTextType : string
|
||||
HeadingColorType : string
|
||||
|
||||
/// The named color for the heading text
|
||||
headingTextColor : string
|
||||
HeadingColor : string
|
||||
|
||||
/// The fonts to use for the list
|
||||
listFonts : string
|
||||
Fonts : string
|
||||
|
||||
/// The font size for the heading text
|
||||
headingFontSize : int
|
||||
HeadingFontSize : int
|
||||
|
||||
/// The font size for the list text
|
||||
listFontSize : int
|
||||
ListFontSize : int
|
||||
|
||||
/// The time zone for the class
|
||||
timeZone : string
|
||||
TimeZone : string
|
||||
|
||||
/// The list visibility
|
||||
listVisibility : int
|
||||
Visibility : int
|
||||
|
||||
/// The small group password
|
||||
groupPassword : string option
|
||||
GroupPassword : string option
|
||||
|
||||
/// The page size for search / inactive requests
|
||||
pageSize : int
|
||||
PageSize : int
|
||||
|
||||
/// How the as-of date should be displayed
|
||||
asOfDate : string
|
||||
AsOfDate : string
|
||||
}
|
||||
with
|
||||
|
||||
/// Set the properties of a small group based on the form's properties
|
||||
member this.populatePreferences (prefs : ListPreferences) =
|
||||
member this.PopulatePreferences (prefs : ListPreferences) =
|
||||
let isPublic, grpPw =
|
||||
match this.listVisibility with
|
||||
match this.Visibility with
|
||||
| RequestVisibility.``public`` -> true, ""
|
||||
| RequestVisibility.passwordProtected -> false, (defaultArg this.groupPassword "")
|
||||
| RequestVisibility.passwordProtected -> false, (defaultArg this.GroupPassword "")
|
||||
| RequestVisibility.``private``
|
||||
| _ -> false, ""
|
||||
{ prefs with
|
||||
daysToExpire = this.expireDays
|
||||
daysToKeepNew = this.daysToKeepNew
|
||||
longTermUpdateWeeks = this.longTermUpdateWeeks
|
||||
requestSort = RequestSort.fromCode this.requestSort
|
||||
emailFromName = this.emailFromName
|
||||
emailFromAddress = this.emailFromAddress
|
||||
defaultEmailType = EmailFormat.fromCode this.defaultEmailType
|
||||
lineColor = this.headingLineColor
|
||||
headingColor = this.headingTextColor
|
||||
listFonts = this.listFonts
|
||||
headingFontSize = this.headingFontSize
|
||||
textFontSize = this.listFontSize
|
||||
timeZoneId = this.timeZone
|
||||
daysToExpire = this.ExpireDays
|
||||
daysToKeepNew = this.DaysToKeepNew
|
||||
longTermUpdateWeeks = this.LongTermUpdateWeeks
|
||||
requestSort = RequestSort.fromCode this.RequestSort
|
||||
emailFromName = this.EmailFromName
|
||||
emailFromAddress = this.EmailFromAddress
|
||||
defaultEmailType = EmailFormat.fromCode this.DefaultEmailType
|
||||
lineColor = this.LineColor
|
||||
headingColor = this.HeadingColor
|
||||
listFonts = this.Fonts
|
||||
headingFontSize = this.HeadingFontSize
|
||||
textFontSize = this.ListFontSize
|
||||
timeZoneId = this.TimeZone
|
||||
isPublic = isPublic
|
||||
groupPassword = grpPw
|
||||
pageSize = this.pageSize
|
||||
asOfDateDisplay = AsOfDateDisplay.fromCode this.asOfDate
|
||||
pageSize = this.PageSize
|
||||
asOfDateDisplay = AsOfDateDisplay.fromCode this.AsOfDate
|
||||
}
|
||||
|
||||
/// Support for the EditPreferences type
|
||||
@ -381,25 +407,25 @@ module EditPreferences =
|
||||
/// Populate an edit form from existing preferences
|
||||
let fromPreferences (prefs : ListPreferences) =
|
||||
let setType (x : string) = match x.StartsWith "#" with true -> "RGB" | false -> "Name"
|
||||
{ expireDays = prefs.daysToExpire
|
||||
daysToKeepNew = prefs.daysToKeepNew
|
||||
longTermUpdateWeeks = prefs.longTermUpdateWeeks
|
||||
requestSort = prefs.requestSort.code
|
||||
emailFromName = prefs.emailFromName
|
||||
emailFromAddress = prefs.emailFromAddress
|
||||
defaultEmailType = prefs.defaultEmailType.code
|
||||
headingLineType = setType prefs.lineColor
|
||||
headingLineColor = prefs.lineColor
|
||||
headingTextType = setType prefs.headingColor
|
||||
headingTextColor = prefs.headingColor
|
||||
listFonts = prefs.listFonts
|
||||
headingFontSize = prefs.headingFontSize
|
||||
listFontSize = prefs.textFontSize
|
||||
timeZone = prefs.timeZoneId
|
||||
groupPassword = Some prefs.groupPassword
|
||||
pageSize = prefs.pageSize
|
||||
asOfDate = prefs.asOfDateDisplay.code
|
||||
listVisibility =
|
||||
{ ExpireDays = prefs.daysToExpire
|
||||
DaysToKeepNew = prefs.daysToKeepNew
|
||||
LongTermUpdateWeeks = prefs.longTermUpdateWeeks
|
||||
RequestSort = prefs.requestSort.code
|
||||
EmailFromName = prefs.emailFromName
|
||||
EmailFromAddress = prefs.emailFromAddress
|
||||
DefaultEmailType = prefs.defaultEmailType.code
|
||||
LineColorType = setType prefs.lineColor
|
||||
LineColor = prefs.lineColor
|
||||
HeadingColorType = setType prefs.headingColor
|
||||
HeadingColor = prefs.headingColor
|
||||
Fonts = prefs.listFonts
|
||||
HeadingFontSize = prefs.headingFontSize
|
||||
ListFontSize = prefs.textFontSize
|
||||
TimeZone = prefs.timeZoneId
|
||||
GroupPassword = Some prefs.groupPassword
|
||||
PageSize = prefs.pageSize
|
||||
AsOfDate = prefs.asOfDateDisplay.code
|
||||
Visibility =
|
||||
match true with
|
||||
| _ when prefs.isPublic -> RequestVisibility.``public``
|
||||
| _ when prefs.groupPassword = "" -> RequestVisibility.``private``
|
||||
@ -411,53 +437,54 @@ module EditPreferences =
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type EditRequest =
|
||||
{ /// The Id of the request
|
||||
requestId : PrayerRequestId
|
||||
RequestId : PrayerRequestId
|
||||
|
||||
/// The type of the request
|
||||
requestType : string
|
||||
RequestType : string
|
||||
|
||||
/// The date of the request
|
||||
enteredDate : DateTime option
|
||||
EnteredDate : DateTime option
|
||||
|
||||
/// Whether to update the date or not
|
||||
skipDateUpdate : bool option
|
||||
SkipDateUpdate : bool option
|
||||
|
||||
/// The requestor or subject
|
||||
requestor : string option
|
||||
Requestor : string option
|
||||
|
||||
/// How this request is expired
|
||||
expiration : string
|
||||
Expiration : string
|
||||
|
||||
/// The text of the request
|
||||
text : string
|
||||
Text : string
|
||||
}
|
||||
with
|
||||
|
||||
/// Is this a new request?
|
||||
member this.isNew () = Guid.Empty = this.requestId
|
||||
member this.IsNew
|
||||
with get () = Guid.Empty = this.RequestId
|
||||
|
||||
/// Support for the EditRequest type
|
||||
module EditRequest =
|
||||
|
||||
/// An empty instance to use for new requests
|
||||
let empty =
|
||||
{ requestId = Guid.Empty
|
||||
requestType = CurrentRequest.code
|
||||
enteredDate = None
|
||||
skipDateUpdate = None
|
||||
requestor = None
|
||||
expiration = Automatic.code
|
||||
text = ""
|
||||
{ RequestId = Guid.Empty
|
||||
RequestType = CurrentRequest.code
|
||||
EnteredDate = None
|
||||
SkipDateUpdate = None
|
||||
Requestor = None
|
||||
Expiration = Automatic.code
|
||||
Text = ""
|
||||
}
|
||||
|
||||
/// Create an instance from an existing request
|
||||
let fromRequest req =
|
||||
{ empty with
|
||||
requestId = req.prayerRequestId
|
||||
requestType = req.requestType.code
|
||||
requestor = req.requestor
|
||||
expiration = req.expiration.code
|
||||
text = req.text
|
||||
RequestId = req.prayerRequestId
|
||||
RequestType = req.requestType.code
|
||||
Requestor = req.requestor
|
||||
Expiration = req.expiration.code
|
||||
Text = req.text
|
||||
}
|
||||
|
||||
|
||||
@ -465,41 +492,42 @@ module EditRequest =
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type EditSmallGroup =
|
||||
{ /// The Id of the small group
|
||||
smallGroupId : SmallGroupId
|
||||
SmallGroupId : SmallGroupId
|
||||
|
||||
/// The name of the small group
|
||||
name : string
|
||||
Name : string
|
||||
|
||||
/// The Id of the church to which this small group belongs
|
||||
churchId : ChurchId
|
||||
ChurchId : ChurchId
|
||||
}
|
||||
with
|
||||
|
||||
/// Is this a new small group?
|
||||
member this.isNew () = Guid.Empty = this.smallGroupId
|
||||
member this.IsNew
|
||||
with get () = Guid.Empty = this.SmallGroupId
|
||||
|
||||
/// Populate a small group from this form
|
||||
member this.populateGroup (grp : SmallGroup) =
|
||||
{ grp with
|
||||
name = this.name
|
||||
churchId = this.churchId
|
||||
}
|
||||
name = this.Name
|
||||
churchId = this.ChurchId
|
||||
}
|
||||
|
||||
/// Support for the EditSmallGroup type
|
||||
module EditSmallGroup =
|
||||
|
||||
/// Create an instance from an existing small group
|
||||
let fromGroup (g : SmallGroup) =
|
||||
{ smallGroupId = g.smallGroupId
|
||||
name = g.name
|
||||
churchId = g.churchId
|
||||
{ SmallGroupId = g.smallGroupId
|
||||
Name = g.name
|
||||
ChurchId = g.churchId
|
||||
}
|
||||
|
||||
/// An empty instance (used when adding a new group)
|
||||
let empty =
|
||||
{ smallGroupId = Guid.Empty
|
||||
name = ""
|
||||
churchId = Guid.Empty
|
||||
{ SmallGroupId = Guid.Empty
|
||||
Name = ""
|
||||
ChurchId = Guid.Empty
|
||||
}
|
||||
|
||||
|
||||
@ -507,65 +535,66 @@ module EditSmallGroup =
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type EditUser =
|
||||
{ /// The Id of the user
|
||||
userId : UserId
|
||||
UserId : UserId
|
||||
|
||||
/// The first name of the user
|
||||
firstName : string
|
||||
FirstName : string
|
||||
|
||||
/// The last name of the user
|
||||
lastName : string
|
||||
LastName : string
|
||||
|
||||
/// The e-mail address for the user
|
||||
emailAddress : string
|
||||
Email : string
|
||||
|
||||
/// The password for the user
|
||||
password : string
|
||||
Password : string
|
||||
|
||||
/// The password hash for the user a second time
|
||||
passwordConfirm : string
|
||||
PasswordConfirm : string
|
||||
|
||||
/// Is this user a PrayerTracker administrator?
|
||||
isAdmin : bool option
|
||||
IsAdmin : bool option
|
||||
}
|
||||
with
|
||||
|
||||
/// Is this a new user?
|
||||
member this.isNew () = Guid.Empty = this.userId
|
||||
member this.IsNew
|
||||
with get () = Guid.Empty = this.UserId
|
||||
|
||||
/// Populate a user from the form
|
||||
member this.populateUser (user : User) hasher =
|
||||
member this.PopulateUser (user : User) hasher =
|
||||
{ user with
|
||||
firstName = this.firstName
|
||||
lastName = this.lastName
|
||||
emailAddress = this.emailAddress
|
||||
isAdmin = match this.isAdmin with Some x -> x | None -> false
|
||||
firstName = this.FirstName
|
||||
lastName = this.LastName
|
||||
emailAddress = this.Email
|
||||
isAdmin = defaultArg this.IsAdmin false
|
||||
}
|
||||
|> function
|
||||
| u when isNull this.password || this.password = "" -> u
|
||||
| u -> { u with passwordHash = hasher this.password }
|
||||
| u when isNull this.Password || this.Password = "" -> u
|
||||
| u -> { u with passwordHash = hasher this.Password }
|
||||
|
||||
/// Support for the EditUser type
|
||||
module EditUser =
|
||||
|
||||
/// An empty instance
|
||||
let empty =
|
||||
{ userId = Guid.Empty
|
||||
firstName = ""
|
||||
lastName = ""
|
||||
emailAddress = ""
|
||||
password = ""
|
||||
passwordConfirm = ""
|
||||
isAdmin = None
|
||||
{ UserId = Guid.Empty
|
||||
FirstName = ""
|
||||
LastName = ""
|
||||
Email = ""
|
||||
Password = ""
|
||||
PasswordConfirm = ""
|
||||
IsAdmin = None
|
||||
}
|
||||
|
||||
/// Create an instance from an existing user
|
||||
let fromUser (user : User) =
|
||||
{ empty with
|
||||
userId = user.userId
|
||||
firstName = user.firstName
|
||||
lastName = user.lastName
|
||||
emailAddress = user.emailAddress
|
||||
isAdmin = match user.isAdmin with true -> Some true | false -> None
|
||||
UserId = user.userId
|
||||
FirstName = user.firstName
|
||||
LastName = user.lastName
|
||||
Email = user.emailAddress
|
||||
IsAdmin = if user.isAdmin then Some true else None
|
||||
}
|
||||
|
||||
|
||||
@ -573,13 +602,13 @@ module EditUser =
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type GroupLogOn =
|
||||
{ /// The ID of the small group to which the user is logging on
|
||||
smallGroupId : SmallGroupId
|
||||
SmallGroupId : SmallGroupId
|
||||
|
||||
/// The password entered
|
||||
password : string
|
||||
Password : string
|
||||
|
||||
/// Whether to remember the login
|
||||
rememberMe : bool option
|
||||
RememberMe : bool option
|
||||
}
|
||||
|
||||
/// Support for the GroupLogOn type
|
||||
@ -587,9 +616,9 @@ module GroupLogOn =
|
||||
|
||||
/// An empty instance
|
||||
let empty =
|
||||
{ smallGroupId = Guid.Empty
|
||||
password = ""
|
||||
rememberMe = None
|
||||
{ SmallGroupId = Guid.Empty
|
||||
Password = ""
|
||||
RememberMe = None
|
||||
}
|
||||
|
||||
|
||||
@ -597,19 +626,19 @@ module GroupLogOn =
|
||||
[<NoComparison; NoEquality>]
|
||||
type MaintainRequests =
|
||||
{ /// The requests to be displayed
|
||||
requests : PrayerRequest seq
|
||||
Requests : PrayerRequest list
|
||||
|
||||
/// The small group to which the requests belong
|
||||
smallGroup : SmallGroup
|
||||
SmallGroup : SmallGroup
|
||||
|
||||
/// Whether only active requests are included
|
||||
onlyActive : bool option
|
||||
OnlyActive : bool option
|
||||
|
||||
/// The search term for the requests
|
||||
searchTerm : string option
|
||||
SearchTerm : string option
|
||||
|
||||
/// The page number of the results
|
||||
pageNbr : int option
|
||||
PageNbr : int option
|
||||
}
|
||||
|
||||
/// Support for the MaintainRequests type
|
||||
@ -617,11 +646,11 @@ module MaintainRequests =
|
||||
|
||||
/// An empty instance
|
||||
let empty =
|
||||
{ requests = Seq.empty
|
||||
smallGroup = SmallGroup.empty
|
||||
onlyActive = None
|
||||
searchTerm = None
|
||||
pageNbr = None
|
||||
{ Requests = []
|
||||
SmallGroup = SmallGroup.empty
|
||||
OnlyActive = None
|
||||
SearchTerm = None
|
||||
PageNbr = None
|
||||
}
|
||||
|
||||
|
||||
@ -629,16 +658,16 @@ module MaintainRequests =
|
||||
[<NoComparison; NoEquality>]
|
||||
type Overview =
|
||||
{ /// The total number of active requests
|
||||
totalActiveReqs : int
|
||||
TotalActiveReqs : int
|
||||
|
||||
/// The numbers of active requests by category
|
||||
activeReqsByCat : Map<PrayerRequestType, int>
|
||||
/// The numbers of active requests by request type
|
||||
ActiveReqsByType : Map<PrayerRequestType, int>
|
||||
|
||||
/// A count of all requests
|
||||
allReqs : int
|
||||
AllReqs : int
|
||||
|
||||
/// A count of all members
|
||||
totalMbrs : int
|
||||
TotalMembers : int
|
||||
}
|
||||
|
||||
|
||||
@ -646,19 +675,19 @@ type Overview =
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type UserLogOn =
|
||||
{ /// The e-mail address of the user
|
||||
emailAddress : string
|
||||
Email : string
|
||||
|
||||
/// The password entered
|
||||
password : string
|
||||
Password : string
|
||||
|
||||
/// The ID of the small group to which the user is logging on
|
||||
smallGroupId : SmallGroupId
|
||||
SmallGroupId : SmallGroupId
|
||||
|
||||
/// Whether to remember the login
|
||||
rememberMe : bool option
|
||||
RememberMe : bool option
|
||||
|
||||
/// The URL to which the user should be redirected once login is successful
|
||||
redirectUrl : string option
|
||||
RedirectUrl : string option
|
||||
}
|
||||
|
||||
/// Support for the UserLogOn type
|
||||
@ -666,11 +695,11 @@ module UserLogOn =
|
||||
|
||||
/// An empty instance
|
||||
let empty =
|
||||
{ emailAddress = ""
|
||||
password = ""
|
||||
smallGroupId = Guid.Empty
|
||||
rememberMe = None
|
||||
redirectUrl = None
|
||||
{ Email = ""
|
||||
Password = ""
|
||||
SmallGroupId = Guid.Empty
|
||||
RememberMe = None
|
||||
RedirectUrl = None
|
||||
}
|
||||
|
||||
|
||||
@ -679,64 +708,64 @@ open Giraffe.ViewEngine
|
||||
/// This represents a list of requests
|
||||
type RequestList =
|
||||
{ /// The prayer request list
|
||||
requests : PrayerRequest list
|
||||
Requests : PrayerRequest list
|
||||
|
||||
/// The date for which this list is being generated
|
||||
date : DateTime
|
||||
Date : DateTime
|
||||
|
||||
/// The small group to which this list belongs
|
||||
listGroup : SmallGroup
|
||||
SmallGroup : SmallGroup
|
||||
|
||||
/// Whether to show the class header
|
||||
showHeader : bool
|
||||
ShowHeader : bool
|
||||
|
||||
/// The list of recipients (populated if requests are e-mailed)
|
||||
recipients : Member list
|
||||
Recipients : Member list
|
||||
|
||||
/// Whether the user can e-mail this list
|
||||
canEmail : bool
|
||||
CanEmail : bool
|
||||
}
|
||||
with
|
||||
|
||||
/// Group requests by their type, along with the type and its localized string
|
||||
member private this.requestsByType (s : IStringLocalizer) =
|
||||
member this.RequestsByType (s : IStringLocalizer) =
|
||||
ReferenceList.requestTypeList s
|
||||
|> List.map (fun (typ, name) -> typ, name, this.requests |> List.filter (fun req -> req.requestType = typ))
|
||||
|> List.map (fun (typ, name) ->
|
||||
let sort =
|
||||
match this.SmallGroup.preferences.requestSort with
|
||||
| SortByDate -> Seq.sortByDescending (fun req -> req.updatedDate)
|
||||
| SortByRequestor -> Seq.sortBy (fun req -> req.requestor)
|
||||
let reqs =
|
||||
this.Requests
|
||||
|> Seq.ofList
|
||||
|> Seq.filter (fun req -> req.requestType = typ)
|
||||
|> sort
|
||||
|> List.ofSeq
|
||||
typ, name, reqs)
|
||||
|> List.filter (fun (_, _, reqs) -> not (List.isEmpty reqs))
|
||||
|
||||
/// Get the requests for a specified type
|
||||
member this.requestsInCategory cat =
|
||||
let reqs =
|
||||
this.requests
|
||||
|> Seq.ofList
|
||||
|> Seq.filter (fun req -> req.requestType = cat)
|
||||
match this.listGroup.preferences.requestSort with
|
||||
| SortByDate -> reqs |> Seq.sortByDescending (fun req -> req.updatedDate)
|
||||
| SortByRequestor -> reqs |> Seq.sortBy (fun req -> req.requestor)
|
||||
|> List.ofSeq
|
||||
|
||||
/// Is this request new?
|
||||
member this.isNew (req : PrayerRequest) =
|
||||
(this.date - req.updatedDate).Days <= this.listGroup.preferences.daysToKeepNew
|
||||
member this.IsNew (req : PrayerRequest) =
|
||||
(this.Date - req.updatedDate).Days <= this.SmallGroup.preferences.daysToKeepNew
|
||||
|
||||
/// Generate this list as HTML
|
||||
member this.asHtml (s : IStringLocalizer) =
|
||||
let prefs = this.listGroup.preferences
|
||||
member this.AsHtml (s : IStringLocalizer) =
|
||||
let prefs = this.SmallGroup.preferences
|
||||
let asOfSize = Math.Round (float prefs.textFontSize * 0.8, 2)
|
||||
[ if this.showHeader then
|
||||
[ if this.ShowHeader then
|
||||
div [ _style $"text-align:center;font-family:{prefs.listFonts}" ] [
|
||||
span [ _style $"font-size:%i{prefs.headingFontSize}pt;" ] [
|
||||
strong [] [ str s["Prayer Requests"].Value ]
|
||||
]
|
||||
br []
|
||||
span [ _style $"font-size:%i{prefs.textFontSize}pt;" ] [
|
||||
strong [] [ str this.listGroup.name ]
|
||||
strong [] [ str this.SmallGroup.name ]
|
||||
br []
|
||||
str (this.date.ToString s["MMMM d, yyyy"].Value)
|
||||
str (this.Date.ToString s["MMMM d, yyyy"].Value)
|
||||
]
|
||||
]
|
||||
br []
|
||||
for _, name, reqs in this.requestsByType s do
|
||||
for _, name, reqs in this.RequestsByType s do
|
||||
div [ _style "padding-left:10px;padding-bottom:.5em;" ] [
|
||||
table [ _style $"font-family:{prefs.listFonts};page-break-inside:avoid;" ] [
|
||||
tr [] [
|
||||
@ -748,7 +777,7 @@ with
|
||||
]
|
||||
reqs
|
||||
|> List.map (fun req ->
|
||||
let bullet = if this.isNew req then "circle" else "disc"
|
||||
let bullet = if this.IsNew req then "circle" else "disc"
|
||||
li [ _style $"list-style-type:{bullet};font-family:{prefs.listFonts};font-size:%i{prefs.textFontSize}pt;padding-bottom:.25em;" ] [
|
||||
match req.requestor with
|
||||
| Some r when r <> "" ->
|
||||
@ -776,25 +805,25 @@ with
|
||||
|> RenderView.AsString.htmlNodes
|
||||
|
||||
/// Generate this list as plain text
|
||||
member this.asText (s : IStringLocalizer) =
|
||||
member this.AsText (s : IStringLocalizer) =
|
||||
seq {
|
||||
this.listGroup.name
|
||||
this.SmallGroup.name
|
||||
s["Prayer Requests"].Value
|
||||
this.date.ToString s["MMMM d, yyyy"].Value
|
||||
this.Date.ToString s["MMMM d, yyyy"].Value
|
||||
" "
|
||||
for _, name, reqs in this.requestsByType s do
|
||||
for _, name, reqs in this.RequestsByType s do
|
||||
let dashes = String.replicate (name.Value.Length + 4) "-"
|
||||
dashes
|
||||
$" {name.Value.ToUpper ()}"
|
||||
dashes
|
||||
for req in reqs do
|
||||
let bullet = if this.isNew req then "+" else "-"
|
||||
let bullet = if this.IsNew req then "+" else "-"
|
||||
let requestor = match req.requestor with Some r -> $"{r} - " | None -> ""
|
||||
match this.listGroup.preferences.asOfDateDisplay with
|
||||
match this.SmallGroup.preferences.asOfDateDisplay with
|
||||
| NoDisplay -> ""
|
||||
| _ ->
|
||||
let dt =
|
||||
match this.listGroup.preferences.asOfDateDisplay with
|
||||
match this.SmallGroup.preferences.asOfDateDisplay with
|
||||
| ShortDate -> req.updatedDate.ToShortDateString ()
|
||||
| LongDate -> req.updatedDate.ToLongDateString ()
|
||||
| _ -> ""
|
||||
|
@ -70,17 +70,17 @@ let save : HttpHandler = requireAccess [ Admin ] >=> validateCSRF >=> fun next c
|
||||
match! ctx.TryBindFormAsync<EditChurch> () with
|
||||
| Ok m ->
|
||||
let! church =
|
||||
if m.isNew () then Task.FromResult (Some { Church.empty with churchId = Guid.NewGuid () })
|
||||
else ctx.db.TryChurchById m.churchId
|
||||
if m.IsNew then Task.FromResult (Some { Church.empty with churchId = Guid.NewGuid () })
|
||||
else ctx.db.TryChurchById m.ChurchId
|
||||
match church with
|
||||
| Some ch ->
|
||||
m.populateChurch ch
|
||||
|> (if m.isNew () then ctx.db.AddEntry else ctx.db.UpdateEntry)
|
||||
m.PopulateChurch ch
|
||||
|> (if m.IsNew then ctx.db.AddEntry else ctx.db.UpdateEntry)
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let act = s[if m.isNew () then "Added" else "Updated"].Value.ToLower ()
|
||||
addInfo ctx s["Successfully {0} church “{1}”", act, m.name]
|
||||
let act = s[if m.IsNew then "Added" else "Updated"].Value.ToLower ()
|
||||
addInfo ctx s["Successfully {0} church “{1}”", act, m.Name]
|
||||
return! redirectTo false "/web/churches" next ctx
|
||||
| None -> return! fourOhFour next ctx
|
||||
| Error e -> return! bindError e next ctx
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
}
|
||||
|
@ -85,11 +85,11 @@ let viewInfo (ctx : HttpContext) startTicks =
|
||||
HttpOnly = true))
|
||||
| None -> ()
|
||||
{ AppViewInfo.fresh with
|
||||
version = appVersion
|
||||
messages = msg
|
||||
requestStart = startTicks
|
||||
user = ctx.Session.user
|
||||
group = ctx.Session.smallGroup
|
||||
Version = appVersion
|
||||
Messages = msg
|
||||
RequestStart = startTicks
|
||||
User = ctx.Session.user
|
||||
Group = ctx.Session.smallGroup
|
||||
}
|
||||
|
||||
/// The view is the last parameter, so it can be composed
|
||||
@ -130,19 +130,19 @@ let htmlString (x : LocalizedString) =
|
||||
|
||||
/// Add an error message to the session
|
||||
let addError ctx msg =
|
||||
addUserMessage ctx { UserMessage.error with text = htmlLocString msg }
|
||||
addUserMessage ctx { UserMessage.error with Text = htmlLocString msg }
|
||||
|
||||
/// Add an informational message to the session
|
||||
let addInfo ctx msg =
|
||||
addUserMessage ctx { UserMessage.info with text = htmlLocString msg }
|
||||
addUserMessage ctx { UserMessage.info with Text = htmlLocString msg }
|
||||
|
||||
/// Add an informational HTML message to the session
|
||||
let addHtmlInfo ctx msg =
|
||||
addUserMessage ctx { UserMessage.info with text = htmlString msg }
|
||||
addUserMessage ctx { UserMessage.info with Text = htmlString msg }
|
||||
|
||||
/// Add a warning message to the session
|
||||
let addWarning ctx msg =
|
||||
addUserMessage ctx { UserMessage.warning with text = htmlLocString msg }
|
||||
addUserMessage ctx { UserMessage.warning with Text = htmlLocString msg }
|
||||
|
||||
|
||||
/// A level of required access
|
||||
|
@ -21,7 +21,7 @@ let getConnection () = task {
|
||||
|
||||
/// Create a mail message object, filled with everything but the body content
|
||||
let createMessage (grp : SmallGroup) subj =
|
||||
let msg = MimeMessage ()
|
||||
let msg = new MimeMessage ()
|
||||
msg.From.Add (MailboxAddress (grp.preferences.emailFromName, fromAddress))
|
||||
msg.Subject <- subj
|
||||
msg.ReplyTo.Add (MailboxAddress (grp.preferences.emailFromName, grp.preferences.emailFromAddress))
|
||||
@ -40,7 +40,7 @@ let createHtmlMessage grp subj body (s : IStringLocalizer) =
|
||||
]
|
||||
|> String.concat ""
|
||||
let msg = createMessage grp subj
|
||||
msg.Body <- TextPart (TextFormat.Html, Text = bodyText)
|
||||
msg.Body <- new TextPart (TextFormat.Html, Text = bodyText)
|
||||
msg
|
||||
|
||||
/// Create a plain-text-format e-mail message
|
||||
@ -54,13 +54,13 @@ let createTextMessage grp subj body (s : IStringLocalizer) =
|
||||
]
|
||||
|> String.concat ""
|
||||
let msg = createMessage grp subj
|
||||
msg.Body <- TextPart (TextFormat.Plain, Text = bodyText)
|
||||
msg.Body <- new TextPart (TextFormat.Plain, Text = bodyText)
|
||||
msg
|
||||
|
||||
/// Send e-mails to a class
|
||||
let sendEmails (client : SmtpClient) (recipients : Member list) grp subj html text s = task {
|
||||
let htmlMsg = createHtmlMessage grp subj html s
|
||||
let plainTextMsg = createTextMessage grp subj text s
|
||||
use htmlMsg = createHtmlMessage grp subj html s
|
||||
use plainTextMsg = createTextMessage grp subj text s
|
||||
|
||||
for mbr in recipients do
|
||||
let emailType =
|
||||
@ -71,10 +71,10 @@ let sendEmails (client : SmtpClient) (recipients : Member list) grp subj html te
|
||||
match emailType with
|
||||
| HtmlFormat ->
|
||||
htmlMsg.To.Add emailTo
|
||||
do! client.SendAsync htmlMsg
|
||||
let! _ = client.SendAsync htmlMsg
|
||||
htmlMsg.To.Clear ()
|
||||
| PlainTextFormat ->
|
||||
plainTextMsg.To.Add emailTo
|
||||
do! client.SendAsync plainTextMsg
|
||||
let! _ = client.SendAsync plainTextMsg
|
||||
plainTextMsg.To.Clear ()
|
||||
}
|
||||
|
@ -16,8 +16,8 @@ let private findRequest (ctx : HttpContext) reqId = task {
|
||||
| Some _ ->
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
addError ctx s["The prayer request you tried to access is not assigned to your group"]
|
||||
return Error (redirectTo false "/web/unauthorized")
|
||||
| None -> return Error fourOhFour
|
||||
return Result.Error (redirectTo false "/web/unauthorized")
|
||||
| None -> return Result.Error fourOhFour
|
||||
}
|
||||
|
||||
/// Generate a list of requests for the given date
|
||||
@ -27,12 +27,12 @@ let private generateRequestList ctx date = task {
|
||||
let listDate = match date with Some d -> d | None -> grp.localDateNow clock
|
||||
let! reqs = ctx.db.AllRequestsForSmallGroup grp clock (Some listDate) true 0
|
||||
return
|
||||
{ requests = reqs |> List.ofSeq
|
||||
date = listDate
|
||||
listGroup = grp
|
||||
showHeader = true
|
||||
canEmail = ctx.Session.user |> Option.isSome
|
||||
recipients = []
|
||||
{ Requests = reqs
|
||||
Date = listDate
|
||||
SmallGroup = grp
|
||||
ShowHeader = true
|
||||
CanEmail = Option.isSome ctx.Session.user
|
||||
Recipients = []
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ let edit (reqId : PrayerRequestId) : HttpHandler = requireAccess [ User ] >=> fu
|
||||
let now = grp.localDateNow (ctx.GetService<IClock> ())
|
||||
if reqId = Guid.Empty then
|
||||
return!
|
||||
{ viewInfo ctx startTicks with script = [ "ckeditor/ckeditor" ]; helpLink = Some Help.editRequest }
|
||||
{ viewInfo ctx startTicks with Script = [ "ckeditor/ckeditor" ]; HelpLink = Some Help.editRequest }
|
||||
|> Views.PrayerRequest.edit EditRequest.empty (now.ToString "yyyy-MM-dd") ctx
|
||||
|> renderHtml next ctx
|
||||
else
|
||||
@ -59,18 +59,18 @@ let edit (reqId : PrayerRequestId) : HttpHandler = requireAccess [ User ] >=> fu
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
if req.isExpired now grp.preferences.daysToExpire then
|
||||
{ UserMessage.warning with
|
||||
text = htmlLocString s["This request is expired."]
|
||||
description =
|
||||
Text = htmlLocString s["This request is expired."]
|
||||
Description =
|
||||
s["To make it active again, update it as necessary, leave “{0}” and “{1}” unchecked, and it will return as an active request.",
|
||||
s["Expire Immediately"], s["Check to not update the date"]]
|
||||
|> (htmlLocString >> Some)
|
||||
}
|
||||
|> addUserMessage ctx
|
||||
return!
|
||||
{ viewInfo ctx startTicks with script = [ "ckeditor/ckeditor" ]; helpLink = Some Help.editRequest }
|
||||
{ viewInfo ctx startTicks with Script = [ "ckeditor/ckeditor" ]; HelpLink = Some Help.editRequest }
|
||||
|> Views.PrayerRequest.edit (EditRequest.fromRequest req) "" ctx
|
||||
|> renderHtml next ctx
|
||||
| Error e -> return! e next ctx
|
||||
| Result.Error e -> return! e next ctx
|
||||
}
|
||||
|
||||
|
||||
@ -84,11 +84,11 @@ let email date : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||
let! recipients = ctx.db.AllMembersForSmallGroup grp.smallGroupId
|
||||
use! client = Email.getConnection ()
|
||||
do! Email.sendEmails client recipients
|
||||
grp s["Prayer Requests for {0} - {1:MMMM d, yyyy}", grp.name, list.date].Value
|
||||
(list.asHtml s) (list.asText s) s
|
||||
grp s["Prayer Requests for {0} - {1:MMMM d, yyyy}", grp.name, list.Date].Value
|
||||
(list.AsHtml s) (list.AsText s) s
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
|> Views.PrayerRequest.email { list with recipients = recipients }
|
||||
|> Views.PrayerRequest.email { list with Recipients = recipients }
|
||||
|> renderHtml next ctx
|
||||
}
|
||||
|
||||
@ -102,7 +102,7 @@ let delete reqId : HttpHandler = requireAccess [ User ] >=> validateCSRF >=> fun
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
addInfo ctx s["The prayer request was deleted successfully"]
|
||||
return! redirectTo false "/web/prayer-requests" next ctx
|
||||
| Error e -> return! e next ctx
|
||||
| Result.Error e -> return! e next ctx
|
||||
}
|
||||
|
||||
|
||||
@ -115,7 +115,7 @@ let expire reqId : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
addInfo ctx s["Successfully {0} prayer request", s["Expired"].Value.ToLower ()]
|
||||
return! redirectTo false "/web/prayer-requests" next ctx
|
||||
| Error e -> return! e next ctx
|
||||
| Result.Error e -> return! e next ctx
|
||||
}
|
||||
|
||||
|
||||
@ -129,12 +129,12 @@ let list groupId : HttpHandler = requireAccess [ AccessLevel.Public ] >=> fun ne
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
|> Views.PrayerRequest.list
|
||||
{ requests = reqs
|
||||
date = grp.localDateNow clock
|
||||
listGroup = grp
|
||||
showHeader = true
|
||||
canEmail = ctx.Session.user |> Option.isSome
|
||||
recipients = []
|
||||
{ Requests = reqs
|
||||
Date = grp.localDateNow clock
|
||||
SmallGroup = grp
|
||||
ShowHeader = true
|
||||
CanEmail = Option.isSome ctx.Session.user
|
||||
Recipients = []
|
||||
}
|
||||
|> renderHtml next ctx
|
||||
| Some _ ->
|
||||
@ -165,29 +165,29 @@ let maintain onlyActive : HttpHandler = requireAccess [ User ] >=> fun next ctx
|
||||
let pageNbr =
|
||||
match ctx.GetQueryStringValue "page" with
|
||||
| Ok pg -> match Int32.TryParse pg with true, p -> p | false, _ -> 1
|
||||
| Error _ -> 1
|
||||
| Result.Error _ -> 1
|
||||
let! m = backgroundTask {
|
||||
match ctx.GetQueryStringValue "search" with
|
||||
| Ok search ->
|
||||
let! reqs = ctx.db.SearchRequestsForSmallGroup grp search pageNbr
|
||||
return
|
||||
{ MaintainRequests.empty with
|
||||
requests = reqs
|
||||
searchTerm = Some search
|
||||
pageNbr = Some pageNbr
|
||||
Requests = reqs
|
||||
SearchTerm = Some search
|
||||
PageNbr = Some pageNbr
|
||||
}
|
||||
| Error _ ->
|
||||
| Result.Error _ ->
|
||||
let! reqs = ctx.db.AllRequestsForSmallGroup grp (ctx.GetService<IClock> ()) None onlyActive pageNbr
|
||||
return
|
||||
{ MaintainRequests.empty with
|
||||
requests = reqs
|
||||
onlyActive = Some onlyActive
|
||||
pageNbr = match onlyActive with true -> None | false -> Some pageNbr
|
||||
Requests = reqs
|
||||
OnlyActive = Some onlyActive
|
||||
PageNbr = if onlyActive then None else Some pageNbr
|
||||
}
|
||||
}
|
||||
return!
|
||||
{ viewInfo ctx startTicks with helpLink = Some Help.maintainRequests }
|
||||
|> Views.PrayerRequest.maintain { m with smallGroup = grp } ctx
|
||||
{ viewInfo ctx startTicks with HelpLink = Some Help.maintainRequests }
|
||||
|> Views.PrayerRequest.maintain { m with SmallGroup = grp } ctx
|
||||
|> renderHtml next ctx
|
||||
}
|
||||
|
||||
@ -210,7 +210,7 @@ let restore reqId : HttpHandler = requireAccess [ User ] >=> fun next ctx -> tas
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
addInfo ctx s["Successfully {0} prayer request", s["Restored"].Value.ToLower ()]
|
||||
return! redirectTo false "/web/prayer-requests" next ctx
|
||||
| Error e -> return! e next ctx
|
||||
| Result.Error e -> return! e next ctx
|
||||
}
|
||||
|
||||
|
||||
@ -219,38 +219,38 @@ let save : HttpHandler = requireAccess [ User ] >=> validateCSRF >=> fun next ct
|
||||
match! ctx.TryBindFormAsync<EditRequest> () with
|
||||
| Ok m ->
|
||||
let! req =
|
||||
if m.isNew () then Task.FromResult (Some { PrayerRequest.empty with prayerRequestId = Guid.NewGuid () })
|
||||
else ctx.db.TryRequestById m.requestId
|
||||
if m.IsNew then Task.FromResult (Some { PrayerRequest.empty with prayerRequestId = Guid.NewGuid () })
|
||||
else ctx.db.TryRequestById m.RequestId
|
||||
match req with
|
||||
| Some pr ->
|
||||
let upd8 =
|
||||
{ pr with
|
||||
requestType = PrayerRequestType.fromCode m.requestType
|
||||
requestor = match m.requestor with Some x when x.Trim () = "" -> None | x -> x
|
||||
text = ckEditorToText m.text
|
||||
expiration = Expiration.fromCode m.expiration
|
||||
requestType = PrayerRequestType.fromCode m.RequestType
|
||||
requestor = match m.Requestor with Some x when x.Trim () = "" -> None | x -> x
|
||||
text = ckEditorToText m.Text
|
||||
expiration = Expiration.fromCode m.Expiration
|
||||
}
|
||||
let grp = currentGroup ctx
|
||||
let now = grp.localDateNow (ctx.GetService<IClock> ())
|
||||
match m.isNew () with
|
||||
match m.IsNew with
|
||||
| true ->
|
||||
let dt = match m.enteredDate with Some x -> x | None -> now
|
||||
let dt = defaultArg m.EnteredDate now
|
||||
{ upd8 with
|
||||
smallGroupId = grp.smallGroupId
|
||||
userId = (currentUser ctx).userId
|
||||
enteredDate = dt
|
||||
updatedDate = dt
|
||||
}
|
||||
| false when Option.isSome m.skipDateUpdate && Option.get m.skipDateUpdate -> upd8
|
||||
| false when defaultArg m.SkipDateUpdate false -> upd8
|
||||
| false -> { upd8 with updatedDate = now }
|
||||
|> (if m.isNew () then ctx.db.AddEntry else ctx.db.UpdateEntry)
|
||||
|> if m.IsNew then ctx.db.AddEntry else ctx.db.UpdateEntry
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let act = if m.isNew () then "Added" else "Updated"
|
||||
addInfo ctx s["Successfully {0} prayer request", s.[act].Value.ToLower ()]
|
||||
let act = if m.IsNew then "Added" else "Updated"
|
||||
addInfo ctx s["Successfully {0} prayer request", s[act].Value.ToLower ()]
|
||||
return! redirectTo false "/web/prayer-requests" next ctx
|
||||
| None -> return! fourOhFour next ctx
|
||||
| Error e -> return! bindError e next ctx
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
}
|
||||
|
||||
|
||||
@ -260,6 +260,6 @@ let view date : HttpHandler = requireAccess [ User; Group ] >=> fun next ctx ->
|
||||
let! list = generateRequestList ctx (parseListDate date)
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
|> Views.PrayerRequest.view { list with showHeader = false }
|
||||
|> Views.PrayerRequest.view { list with ShowHeader = false }
|
||||
|> renderHtml next ctx
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<PublishSingleFile>True</PublishSingleFile>
|
||||
<SelfContained>False</SelfContained>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -23,9 +25,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Giraffe" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.1" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.10" />
|
||||
<PackageReference Include="Giraffe" Version="6.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.5" />
|
||||
<PackageReference Update="FSharp.Core" Version="6.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -21,7 +21,7 @@ let private setGroupCookie (ctx : HttpContext) pwHash =
|
||||
|
||||
/// GET /small-group/announcement
|
||||
let announcement : HttpHandler = requireAccess [ User ] >=> fun next ctx ->
|
||||
{ viewInfo ctx DateTime.Now.Ticks with helpLink = Some Help.sendAnnouncement; script = [ "ckeditor/ckeditor" ] }
|
||||
{ viewInfo ctx DateTime.Now.Ticks with HelpLink = Some Help.sendAnnouncement; Script = [ "ckeditor/ckeditor" ] }
|
||||
|> Views.SmallGroup.announcement (currentUser ctx).isAdmin ctx
|
||||
|> renderHtml next ctx
|
||||
|
||||
@ -31,13 +31,13 @@ let delete groupId : HttpHandler = requireAccess [ Admin ] >=> validateCSRF >=>
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
match! ctx.db.TryGroupById groupId with
|
||||
| Some grp ->
|
||||
let! reqs = ctx.db.CountRequestsBySmallGroup groupId
|
||||
let! usrs = ctx.db.CountUsersBySmallGroup groupId
|
||||
let! reqs = ctx.db.CountRequestsBySmallGroup groupId
|
||||
let! users = ctx.db.CountUsersBySmallGroup groupId
|
||||
ctx.db.RemoveEntry grp
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
addInfo ctx
|
||||
s["The group {0} and its {1} prayer request(s) were deleted successfully; revoked access from {2} user(s)",
|
||||
grp.name, reqs, usrs]
|
||||
grp.name, reqs, users]
|
||||
return! redirectTo false "/web/small-groups" next ctx
|
||||
| None -> return! fourOhFour next ctx
|
||||
}
|
||||
@ -82,18 +82,18 @@ let editMember (memberId : MemberId) : HttpHandler = requireAccess [ User ] >=>
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let grp = currentGroup ctx
|
||||
let typs = ReferenceList.emailTypeList grp.preferences.defaultEmailType s
|
||||
let types = ReferenceList.emailTypeList grp.preferences.defaultEmailType s
|
||||
if memberId = Guid.Empty then
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
|> Views.SmallGroup.editMember EditMember.empty typs ctx
|
||||
|> Views.SmallGroup.editMember EditMember.empty types ctx
|
||||
|> renderHtml next ctx
|
||||
else
|
||||
match! ctx.db.TryMemberById memberId with
|
||||
| Some mbr when mbr.smallGroupId = grp.smallGroupId ->
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
|> Views.SmallGroup.editMember (EditMember.fromMember mbr) typs ctx
|
||||
|> Views.SmallGroup.editMember (EditMember.fromMember mbr) types ctx
|
||||
|> renderHtml next ctx
|
||||
| Some _
|
||||
| None -> return! fourOhFour next ctx
|
||||
@ -102,12 +102,12 @@ let editMember (memberId : MemberId) : HttpHandler = requireAccess [ User ] >=>
|
||||
|
||||
/// GET /small-group/log-on/[group-id?]
|
||||
let logOn (groupId : SmallGroupId option) : HttpHandler = requireAccess [ AccessLevel.Public ] >=> fun next ctx -> task {
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
let! grps = ctx.db.ProtectedGroups ()
|
||||
let grpId = match groupId with Some gid -> flatGuid gid | None -> ""
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
let! groups = ctx.db.ProtectedGroups ()
|
||||
let grpId = match groupId with Some gid -> flatGuid gid | None -> ""
|
||||
return!
|
||||
{ viewInfo ctx startTicks with helpLink = Some Help.logOn }
|
||||
|> Views.SmallGroup.logOn grps grpId ctx
|
||||
{ viewInfo ctx startTicks with HelpLink = Some Help.logOn }
|
||||
|> Views.SmallGroup.logOn groups grpId ctx
|
||||
|> renderHtml next ctx
|
||||
}
|
||||
|
||||
@ -117,28 +117,26 @@ let logOnSubmit : HttpHandler = requireAccess [ AccessLevel.Public ] >=> validat
|
||||
match! ctx.TryBindFormAsync<GroupLogOn> () with
|
||||
| Ok m ->
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
match! ctx.db.TryGroupLogOnByPassword m.smallGroupId m.password with
|
||||
match! ctx.db.TryGroupLogOnByPassword m.SmallGroupId m.Password with
|
||||
| Some grp ->
|
||||
ctx.Session.smallGroup <- Some grp
|
||||
match m.rememberMe with
|
||||
| Some x when x -> (setGroupCookie ctx << sha1Hash) m.password
|
||||
| _ -> ()
|
||||
if defaultArg m.RememberMe false then (setGroupCookie ctx << sha1Hash) m.Password
|
||||
addInfo ctx s["Log On Successful • Welcome to {0}", s["PrayerTracker"]]
|
||||
return! redirectTo false "/web/prayer-requests/view" next ctx
|
||||
| None ->
|
||||
addError ctx s["Password incorrect - login unsuccessful"]
|
||||
return! redirectTo false $"/web/small-group/log-on/{flatGuid m.smallGroupId}" next ctx
|
||||
| Error e -> return! bindError e next ctx
|
||||
return! redirectTo false $"/web/small-group/log-on/{flatGuid m.SmallGroupId}" next ctx
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
}
|
||||
|
||||
|
||||
/// GET /small-groups
|
||||
let maintain : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
let! grps = ctx.db.AllGroups ()
|
||||
let! groups = ctx.db.AllGroups ()
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
|> Views.SmallGroup.maintain grps ctx
|
||||
|> Views.SmallGroup.maintain groups ctx
|
||||
|> renderHtml next ctx
|
||||
}
|
||||
|
||||
@ -148,11 +146,11 @@ let members : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
let grp = currentGroup ctx
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let! mbrs = ctx.db.AllMembersForSmallGroup grp.smallGroupId
|
||||
let typs = ReferenceList.emailTypeList grp.preferences.defaultEmailType s |> Map.ofSeq
|
||||
let! members = ctx.db.AllMembersForSmallGroup grp.smallGroupId
|
||||
let types = ReferenceList.emailTypeList grp.preferences.defaultEmailType s |> Map.ofSeq
|
||||
return!
|
||||
{ viewInfo ctx startTicks with helpLink = Some Help.maintainGroupMembers }
|
||||
|> Views.SmallGroup.members mbrs typs ctx
|
||||
{ viewInfo ctx startTicks with HelpLink = Some Help.maintainGroupMembers }
|
||||
|> Views.SmallGroup.members members types ctx
|
||||
|> renderHtml next ctx
|
||||
}
|
||||
|
||||
@ -165,10 +163,10 @@ let overview : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||
let! reqCount = ctx.db.CountRequestsBySmallGroup (currentGroup ctx).smallGroupId
|
||||
let! mbrCount = ctx.db.CountMembersForSmallGroup (currentGroup ctx).smallGroupId
|
||||
let m =
|
||||
{ totalActiveReqs = List.length reqs
|
||||
allReqs = reqCount
|
||||
totalMbrs = mbrCount
|
||||
activeReqsByCat =
|
||||
{ TotalActiveReqs = List.length reqs
|
||||
AllReqs = reqCount
|
||||
TotalMembers = mbrCount
|
||||
ActiveReqsByType =
|
||||
(reqs
|
||||
|> Seq.ofList
|
||||
|> Seq.map (fun req -> req.requestType)
|
||||
@ -188,7 +186,7 @@ let preferences : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
let! tzs = ctx.db.AllTimeZones ()
|
||||
return!
|
||||
{ viewInfo ctx startTicks with helpLink = Some Help.groupPreferences }
|
||||
{ viewInfo ctx startTicks with HelpLink = Some Help.groupPreferences }
|
||||
|> Views.SmallGroup.preferences (EditPreferences.fromPreferences (currentGroup ctx).preferences) tzs ctx
|
||||
|> renderHtml next ctx
|
||||
}
|
||||
@ -200,22 +198,22 @@ let save : HttpHandler = requireAccess [ Admin ] >=> validateCSRF >=> fun next c
|
||||
| Ok m ->
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let! group =
|
||||
if m.isNew () then Task.FromResult (Some { SmallGroup.empty with smallGroupId = Guid.NewGuid () })
|
||||
else ctx.db.TryGroupById m.smallGroupId
|
||||
if m.IsNew then Task.FromResult (Some { SmallGroup.empty with smallGroupId = Guid.NewGuid () })
|
||||
else ctx.db.TryGroupById m.SmallGroupId
|
||||
match group with
|
||||
| Some grp ->
|
||||
m.populateGroup grp
|
||||
|> function
|
||||
| grp when m.isNew () ->
|
||||
| grp when m.IsNew ->
|
||||
ctx.db.AddEntry grp
|
||||
ctx.db.AddEntry { grp.preferences with smallGroupId = grp.smallGroupId }
|
||||
| grp -> ctx.db.UpdateEntry grp
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
let act = s[if m.isNew () then "Added" else "Updated"].Value.ToLower ()
|
||||
addHtmlInfo ctx s["Successfully {0} group “{1}”", act, m.name]
|
||||
let act = s[if m.IsNew then "Added" else "Updated"].Value.ToLower ()
|
||||
addHtmlInfo ctx s["Successfully {0} group “{1}”", act, m.Name]
|
||||
return! redirectTo false "/web/small-groups" next ctx
|
||||
| None -> return! fourOhFour next ctx
|
||||
| Error e -> return! bindError e next ctx
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
}
|
||||
|
||||
|
||||
@ -225,25 +223,25 @@ let saveMember : HttpHandler = requireAccess [ User ] >=> validateCSRF >=> fun n
|
||||
| Ok m ->
|
||||
let grp = currentGroup ctx
|
||||
let! mMbr =
|
||||
if m.isNew () then
|
||||
if m.IsNew then
|
||||
Task.FromResult (Some { Member.empty with memberId = Guid.NewGuid (); smallGroupId = grp.smallGroupId })
|
||||
else ctx.db.TryMemberById m.memberId
|
||||
else ctx.db.TryMemberById m.MemberId
|
||||
match mMbr with
|
||||
| Some mbr when mbr.smallGroupId = grp.smallGroupId ->
|
||||
{ mbr with
|
||||
memberName = m.memberName
|
||||
email = m.emailAddress
|
||||
format = match m.emailType with "" | null -> None | _ -> Some m.emailType
|
||||
memberName = m.Name
|
||||
email = m.Email
|
||||
format = match m.Format with "" | null -> None | _ -> Some m.Format
|
||||
}
|
||||
|> (if m.isNew () then ctx.db.AddEntry else ctx.db.UpdateEntry)
|
||||
|> if m.IsNew then ctx.db.AddEntry else ctx.db.UpdateEntry
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let act = s[if m.isNew () then "Added" else "Updated"].Value.ToLower ()
|
||||
let act = s[if m.IsNew then "Added" else "Updated"].Value.ToLower ()
|
||||
addInfo ctx s["Successfully {0} group member", act]
|
||||
return! redirectTo false "/web/small-group/members" next ctx
|
||||
| Some _
|
||||
| None -> return! fourOhFour next ctx
|
||||
| Error e -> return! bindError e next ctx
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
}
|
||||
|
||||
|
||||
@ -256,7 +254,7 @@ let savePreferences : HttpHandler = requireAccess [ User ] >=> validateCSRF >=>
|
||||
// database values, not the then out-of-sync session ones.
|
||||
match! ctx.db.TryGroupById (currentGroup ctx).smallGroupId with
|
||||
| Some grp ->
|
||||
let prefs = m.populatePreferences grp.preferences
|
||||
let prefs = m.PopulatePreferences grp.preferences
|
||||
ctx.db.UpdateEntry prefs
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
// Refresh session instance
|
||||
@ -265,7 +263,7 @@ let savePreferences : HttpHandler = requireAccess [ User ] >=> validateCSRF >=>
|
||||
addInfo ctx s["Group preferences updated successfully"]
|
||||
return! redirectTo false "/web/small-group/preferences" next ctx
|
||||
| None -> return! fourOhFour next ctx
|
||||
| Error e -> return! bindError e next ctx
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
}
|
||||
|
||||
|
||||
@ -279,7 +277,7 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCSRF >=>
|
||||
let now = grp.localTimeNow (ctx.GetService<IClock> ())
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
// Reformat the text to use the class's font stylings
|
||||
let requestText = ckEditorToText m.text
|
||||
let requestText = ckEditorToText m.Text
|
||||
let htmlText =
|
||||
p [ _style $"font-family:{grp.preferences.listFonts};font-size:%d{grp.preferences.textFontSize}pt;" ]
|
||||
[ rawText requestText ]
|
||||
@ -287,7 +285,7 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCSRF >=>
|
||||
let plainText = (htmlToPlainText >> wordWrap 74) htmlText
|
||||
// Send the e-mails
|
||||
let! recipients =
|
||||
match m.sendToClass with
|
||||
match m.SendToClass with
|
||||
| "N" when usr.isAdmin -> ctx.db.AllUsersAsMembers ()
|
||||
| _ -> ctx.db.AllMembersForSmallGroup grp.smallGroupId
|
||||
use! client = Email.getConnection ()
|
||||
@ -296,7 +294,7 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCSRF >=>
|
||||
(now.ToString "h:mm tt").ToLower ()].Value
|
||||
htmlText plainText s
|
||||
// Add to the request list if desired
|
||||
match m.sendToClass, m.addToRequestList with
|
||||
match m.SendToClass, m.AddToRequestList with
|
||||
| "N", _
|
||||
| _, None -> ()
|
||||
| _, Some x when not x -> ()
|
||||
@ -305,7 +303,7 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCSRF >=>
|
||||
prayerRequestId = Guid.NewGuid ()
|
||||
smallGroupId = grp.smallGroupId
|
||||
userId = usr.userId
|
||||
requestType = (Option.get >> PrayerRequestType.fromCode) m.requestType
|
||||
requestType = (Option.get >> PrayerRequestType.fromCode) m.RequestType
|
||||
text = requestText
|
||||
enteredDate = now
|
||||
updatedDate = now
|
||||
@ -315,14 +313,14 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCSRF >=>
|
||||
()
|
||||
// Tell 'em what they've won, Johnny!
|
||||
let toWhom =
|
||||
match m.sendToClass with
|
||||
match m.SendToClass with
|
||||
| "N" -> s["{0} users", s["PrayerTracker"]].Value
|
||||
| _ -> s["Group Members"].Value.ToLower ()
|
||||
let andAdded = match m.addToRequestList with Some x when x -> "and added it to the request list" | _ -> ""
|
||||
let andAdded = match m.AddToRequestList with Some x when x -> "and added it to the request list" | _ -> ""
|
||||
addInfo ctx s["Successfully sent announcement to all {0} {1}", toWhom, s[andAdded]]
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
|> Views.SmallGroup.announcementSent { m with text = htmlText }
|
||||
|> Views.SmallGroup.announcementSent { m with Text = htmlText }
|
||||
|> renderHtml next ctx
|
||||
| Error e -> return! bindError e next ctx
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
}
|
||||
|
@ -23,18 +23,18 @@ let private setUserCookie (ctx : HttpContext) pwHash =
|
||||
/// Retrieve a user from the database by password
|
||||
// If the hashes do not match, determine if it matches a previous scheme, and upgrade them if it does
|
||||
let private findUserByPassword m (db : AppDbContext) = task {
|
||||
match! db.TryUserByEmailAndGroup m.emailAddress m.smallGroupId with
|
||||
match! db.TryUserByEmailAndGroup m.Email m.SmallGroupId with
|
||||
| Some u when Option.isSome u.salt ->
|
||||
// Already upgraded; match = success
|
||||
let pwHash = pbkdf2Hash (Option.get u.salt) m.password
|
||||
let pwHash = pbkdf2Hash (Option.get u.salt) m.Password
|
||||
if u.passwordHash = pwHash then
|
||||
return Some { u with passwordHash = ""; salt = None; smallGroups = List<UserSmallGroup>() }, pwHash
|
||||
else return None, ""
|
||||
| Some u when u.passwordHash = sha1Hash m.password ->
|
||||
| Some u when u.passwordHash = sha1Hash m.Password ->
|
||||
// Not upgraded, but password is good; upgrade 'em!
|
||||
// Upgrade 'em!
|
||||
let salt = Guid.NewGuid ()
|
||||
let pwHash = pbkdf2Hash salt m.password
|
||||
let pwHash = pbkdf2Hash salt m.Password
|
||||
let upgraded = { u with salt = Some salt; passwordHash = pwHash }
|
||||
db.UpdateEntry upgraded
|
||||
let! _ = db.SaveChangesAsync ()
|
||||
@ -54,16 +54,16 @@ let changePassword : HttpHandler = requireAccess [ User ] >=> validateCSRF >=> f
|
||||
match dbUsr with
|
||||
| Some usr ->
|
||||
// Check the old password against a possibly non-salted hash
|
||||
(match usr.salt with Some salt -> pbkdf2Hash salt | None -> sha1Hash) m.oldPassword
|
||||
(match usr.salt with Some salt -> pbkdf2Hash salt | None -> sha1Hash) m.OldPassword
|
||||
|> ctx.db.TryUserLogOnByCookie curUsr.userId (currentGroup ctx).smallGroupId
|
||||
| _ -> Task.FromResult None
|
||||
match user with
|
||||
| Some _ when m.newPassword = m.newPasswordConfirm ->
|
||||
| Some _ when m.NewPassword = m.NewPasswordConfirm ->
|
||||
match dbUsr with
|
||||
| Some usr ->
|
||||
// Generate new salt whenever the password is changed
|
||||
let salt = Guid.NewGuid ()
|
||||
ctx.db.UpdateEntry { usr with passwordHash = pbkdf2Hash salt m.newPassword; salt = Some salt }
|
||||
ctx.db.UpdateEntry { usr with passwordHash = pbkdf2Hash salt m.NewPassword; salt = Some salt }
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
// If the user is remembered, update the cookie with the new hash
|
||||
if ctx.Request.Cookies.Keys.Contains Key.Cookie.user then setUserCookie ctx usr.passwordHash
|
||||
@ -76,7 +76,7 @@ let changePassword : HttpHandler = requireAccess [ User ] >=> validateCSRF >=> f
|
||||
| None ->
|
||||
addError ctx s["The old password was incorrect - your password was NOT changed"]
|
||||
return! redirectTo false "/web/user/password" next ctx
|
||||
| Error e -> return! bindError e next ctx
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
}
|
||||
|
||||
|
||||
@ -99,26 +99,26 @@ let doLogOn : HttpHandler = requireAccess [ AccessLevel.Public ] >=> validateCSR
|
||||
| Ok m ->
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let! usr, pwHash = findUserByPassword m ctx.db
|
||||
let! grp = ctx.db.TryGroupById m.smallGroupId
|
||||
let! grp = ctx.db.TryGroupById m.SmallGroupId
|
||||
let nextUrl =
|
||||
match usr with
|
||||
| Some _ ->
|
||||
ctx.Session.user <- usr
|
||||
ctx.Session.smallGroup <- grp
|
||||
match m.rememberMe with Some x when x -> setUserCookie ctx pwHash | _ -> ()
|
||||
if defaultArg m.RememberMe false then setUserCookie ctx pwHash
|
||||
addHtmlInfo ctx s["Log On Successful • Welcome to {0}", s["PrayerTracker"]]
|
||||
match m.redirectUrl with
|
||||
match m.RedirectUrl with
|
||||
| None -> "/web/small-group"
|
||||
| Some x when x = "" -> "/web/small-group"
|
||||
| Some x -> x
|
||||
| _ ->
|
||||
let grpName = match grp with Some g -> g.name | _ -> "N/A"
|
||||
{ UserMessage.error with
|
||||
text = htmlLocString s["Invalid credentials - log on unsuccessful"]
|
||||
description =
|
||||
Text = htmlLocString s["Invalid credentials - log on unsuccessful"]
|
||||
Description =
|
||||
[ s["This is likely due to one of the following reasons"].Value
|
||||
":<ul><li>"
|
||||
s["The e-mail address “{0}” is invalid.", WebUtility.HtmlEncode m.emailAddress].Value
|
||||
s["The e-mail address “{0}” is invalid.", WebUtility.HtmlEncode m.Email].Value
|
||||
"</li><li>"
|
||||
s["The password entered does not match the password for the given e-mail address."].Value
|
||||
"</li><li>"
|
||||
@ -132,7 +132,7 @@ let doLogOn : HttpHandler = requireAccess [ AccessLevel.Public ] >=> validateCSR
|
||||
|> addUserMessage ctx
|
||||
"/web/user/log-on"
|
||||
return! redirectTo false nextUrl next ctx
|
||||
| Error e -> return! bindError e next ctx
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
}
|
||||
|
||||
|
||||
@ -167,8 +167,8 @@ let logOn : HttpHandler = requireAccess [ AccessLevel.Public ] >=> fun next ctx
|
||||
addWarning ctx s["The page you requested requires authentication; please log on below."]
|
||||
| None -> ()
|
||||
return!
|
||||
{ viewInfo ctx startTicks with helpLink = Some Help.logOn }
|
||||
|> Views.User.logOn { UserLogOn.empty with redirectUrl = url } groups ctx
|
||||
{ viewInfo ctx startTicks with HelpLink = Some Help.logOn }
|
||||
|> Views.User.logOn { UserLogOn.empty with RedirectUrl = url } groups ctx
|
||||
|> renderHtml next ctx
|
||||
}
|
||||
|
||||
@ -186,7 +186,7 @@ let maintain : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||
|
||||
/// GET /user/password
|
||||
let password : HttpHandler = requireAccess [ User ] >=> fun next ctx ->
|
||||
{ viewInfo ctx DateTime.Now.Ticks with helpLink = Some Help.changePassword }
|
||||
{ viewInfo ctx DateTime.Now.Ticks with HelpLink = Some Help.changePassword }
|
||||
|> Views.User.changePassword ctx
|
||||
|> renderHtml next ctx
|
||||
|
||||
@ -196,13 +196,13 @@ let save : HttpHandler = requireAccess [ Admin ] >=> validateCSRF >=> fun next c
|
||||
match! ctx.TryBindFormAsync<EditUser> () with
|
||||
| Ok m ->
|
||||
let! user =
|
||||
if m.isNew () then Task.FromResult (Some { User.empty with userId = Guid.NewGuid () })
|
||||
else ctx.db.TryUserById m.userId
|
||||
if m.IsNew then Task.FromResult (Some { User.empty with userId = Guid.NewGuid () })
|
||||
else ctx.db.TryUserById m.UserId
|
||||
let saltedUser =
|
||||
match user with
|
||||
| Some u ->
|
||||
match u.salt with
|
||||
| None when m.password <> "" ->
|
||||
| None when m.Password <> "" ->
|
||||
// Generate salt so that a new password hash can be generated
|
||||
Some { u with salt = Some (Guid.NewGuid ()) }
|
||||
| _ ->
|
||||
@ -211,15 +211,15 @@ let save : HttpHandler = requireAccess [ Admin ] >=> validateCSRF >=> fun next c
|
||||
| _ -> user
|
||||
match saltedUser with
|
||||
| Some u ->
|
||||
let updatedUser = m.populateUser u (pbkdf2Hash (Option.get u.salt))
|
||||
updatedUser |> (if m.isNew () then ctx.db.AddEntry else ctx.db.UpdateEntry)
|
||||
let updatedUser = m.PopulateUser u (pbkdf2Hash (Option.get u.salt))
|
||||
updatedUser |> if m.IsNew then ctx.db.AddEntry else ctx.db.UpdateEntry
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
if m.isNew () then
|
||||
if m.IsNew then
|
||||
let h = CommonFunctions.htmlString
|
||||
{ UserMessage.info with
|
||||
text = h s["Successfully {0} user", s["Added"].Value.ToLower ()]
|
||||
description =
|
||||
Text = h s["Successfully {0} user", s["Added"].Value.ToLower ()]
|
||||
Description =
|
||||
h s["Please select at least one group for which this user ({0}) is authorized",
|
||||
updatedUser.fullName]
|
||||
|> Some
|
||||
@ -230,7 +230,7 @@ let save : HttpHandler = requireAccess [ Admin ] >=> validateCSRF >=> fun next c
|
||||
addInfo ctx s["Successfully {0} user", s["Updated"].Value.ToLower ()]
|
||||
return! redirectTo false "/web/users" next ctx
|
||||
| None -> return! fourOhFour next ctx
|
||||
| Error e -> return! bindError e next ctx
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
}
|
||||
|
||||
|
||||
@ -239,31 +239,31 @@ let saveGroups : HttpHandler = requireAccess [ Admin ] >=> validateCSRF >=> fun
|
||||
match! ctx.TryBindFormAsync<AssignGroups> () with
|
||||
| Ok m ->
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
match Seq.length m.smallGroups with
|
||||
match Seq.length m.SmallGroups with
|
||||
| 0 ->
|
||||
addError ctx s["You must select at least one group to assign"]
|
||||
return! redirectTo false $"/web/user/{flatGuid m.userId}/small-groups" next ctx
|
||||
return! redirectTo false $"/web/user/{flatGuid m.UserId}/small-groups" next ctx
|
||||
| _ ->
|
||||
match! ctx.db.TryUserByIdWithGroups m.userId with
|
||||
match! ctx.db.TryUserByIdWithGroups m.UserId with
|
||||
| Some user ->
|
||||
let grps =
|
||||
m.smallGroups.Split ','
|
||||
let groups =
|
||||
m.SmallGroups.Split ','
|
||||
|> Array.map Guid.Parse
|
||||
|> List.ofArray
|
||||
user.smallGroups
|
||||
|> Seq.filter (fun x -> not (grps |> List.exists (fun y -> y = x.smallGroupId)))
|
||||
|> Seq.filter (fun x -> not (groups |> List.exists (fun y -> y = x.smallGroupId)))
|
||||
|> ctx.db.UserGroupXref.RemoveRange
|
||||
grps
|
||||
groups
|
||||
|> Seq.ofList
|
||||
|> Seq.filter (fun x -> not (user.smallGroups |> Seq.exists (fun y -> y.smallGroupId = x)))
|
||||
|> Seq.map (fun x -> { UserSmallGroup.empty with userId = user.userId; smallGroupId = x })
|
||||
|> List.ofSeq
|
||||
|> List.iter ctx.db.AddEntry
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
addInfo ctx s["Successfully updated group permissions for {0}", m.userName]
|
||||
addInfo ctx s["Successfully updated group permissions for {0}", m.UserName]
|
||||
return! redirectTo false "/web/users" next ctx
|
||||
| _ -> return! fourOhFour next ctx
|
||||
| Error e -> return! bindError e next ctx
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
}
|
||||
|
||||
|
||||
@ -272,11 +272,11 @@ let smallGroups userId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
match! ctx.db.TryUserByIdWithGroups userId with
|
||||
| Some user ->
|
||||
let! grps = ctx.db.GroupList ()
|
||||
let! groups = ctx.db.GroupList ()
|
||||
let curGroups = user.smallGroups |> Seq.map (fun g -> flatGuid g.smallGroupId) |> List.ofSeq
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
|> Views.User.assignGroups (AssignGroups.fromUser user) grps curGroups ctx
|
||||
|> Views.User.assignGroups (AssignGroups.fromUser user) groups curGroups ctx
|
||||
|> renderHtml next ctx
|
||||
| None -> return! fourOhFour next ctx
|
||||
}
|
||||
|
@ -210,8 +210,8 @@ const PT = {
|
||||
*/
|
||||
toggleType(name) {
|
||||
const isNamed = document.getElementById(`${name}Type_Name`)
|
||||
const named = document.getElementById(`${name}Color_Select`)
|
||||
const custom = document.getElementById(`${name}Color_Color`)
|
||||
const named = document.getElementById(`${name}_Select`)
|
||||
const custom = document.getElementById(`${name}_Color`)
|
||||
if (isNamed.checked) {
|
||||
custom.disabled = true
|
||||
named.disabled = false
|
||||
@ -244,7 +244,7 @@ const PT = {
|
||||
PT.smallGroup.preferences.checkVisibility)
|
||||
})
|
||||
PT.smallGroup.preferences.checkVisibility()
|
||||
;['headingLine', 'headingText'].map(name => {
|
||||
;['lineColor', 'headingColor'].map(name => {
|
||||
document.getElementById(`${name}Type_Name`).addEventListener('click', () => {
|
||||
PT.smallGroup.preferences.toggleType(name)
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user