Version 3 #40
|
@ -24,6 +24,11 @@ label[for]:hover {
|
||||||
.material-icons {
|
.material-icons {
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
|
@media print {
|
||||||
|
.jjj-hide-from-printer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
/* Material Design Icon / Bootstrap styling */
|
/* Material Design Icon / Bootstrap styling */
|
||||||
.mdi::before {
|
.mdi::before {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
|
|
@ -64,7 +64,9 @@ module private Auth =
|
||||||
let account : HttpHandler = fun next ctx -> task {
|
let account : HttpHandler = fun next ctx -> task {
|
||||||
match! Data.findById (currentCitizenId ctx) with
|
match! Data.findById (currentCitizenId ctx) with
|
||||||
| Some citizen ->
|
| Some citizen ->
|
||||||
return! Views.account (AccountProfileForm.fromCitizen citizen) (csrf ctx) |> render "Account Profile" next ctx
|
return!
|
||||||
|
Views.account (AccountProfileForm.fromCitizen citizen) (isHtmx ctx) (csrf ctx)
|
||||||
|
|> render "Account Profile" next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +178,7 @@ let register next ctx =
|
||||||
q2Index <- System.Random.Shared.Next(0, 5)
|
q2Index <- System.Random.Shared.Next(0, 5)
|
||||||
let qAndA = Auth.questions ctx
|
let qAndA = Auth.questions ctx
|
||||||
Views.register (fst qAndA[q1Index]) (fst qAndA[q2Index])
|
Views.register (fst qAndA[q1Index]) (fst qAndA[q2Index])
|
||||||
{ RegisterForm.empty with Question1Index = q1Index; Question2Index = q2Index } (csrf ctx)
|
{ RegisterForm.empty with Question1Index = q1Index; Question2Index = q2Index } (isHtmx ctx) (csrf ctx)
|
||||||
|> render "Register" next ctx
|
|> render "Register" next ctx
|
||||||
|
|
||||||
// POST: /citizen/register
|
// POST: /citizen/register
|
||||||
|
@ -199,7 +201,7 @@ let doRegistration : HttpHandler = validateCsrf >=> fun next ctx -> task {
|
||||||
]
|
]
|
||||||
let refreshPage () =
|
let refreshPage () =
|
||||||
Views.register (fst qAndA[form.Question1Index]) (fst qAndA[form.Question2Index]) { form with Password = "" }
|
Views.register (fst qAndA[form.Question1Index]) (fst qAndA[form.Question2Index]) { form with Password = "" }
|
||||||
(csrf ctx)
|
(isHtmx ctx) (csrf ctx)
|
||||||
|> renderHandler "Register"
|
|> renderHandler "Register"
|
||||||
|
|
||||||
if badForm then
|
if badForm then
|
||||||
|
@ -246,7 +248,8 @@ let resetPassword token : HttpHandler = fun next ctx -> task {
|
||||||
match! Data.trySecurityByToken token with
|
match! Data.trySecurityByToken token with
|
||||||
| Some security ->
|
| Some security ->
|
||||||
return!
|
return!
|
||||||
Views.resetPassword { Id = CitizenId.toString security.Id; Token = token; Password = "" } (csrf ctx)
|
Views.resetPassword { Id = CitizenId.toString security.Id; Token = token; Password = "" } (isHtmx ctx)
|
||||||
|
(csrf ctx)
|
||||||
|> render "Reset Password" next ctx
|
|> render "Reset Password" next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
@ -273,7 +276,7 @@ let doResetPassword : HttpHandler = validateCsrf >=> fun next ctx -> task {
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
else
|
else
|
||||||
do! addErrors errors ctx
|
do! addErrors errors ctx
|
||||||
return! Views.resetPassword form (csrf ctx) |> render "Reset Password" next ctx
|
return! Views.resetPassword form (isHtmx ctx) (csrf ctx) |> render "Reset Password" next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: /citizen/save-account
|
// POST: /citizen/save-account
|
||||||
|
@ -314,7 +317,7 @@ let saveAccount : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx ->
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
else
|
else
|
||||||
do! addErrors errors ctx
|
do! addErrors errors ctx
|
||||||
return! Views.account form (csrf ctx) |> render "Account Profile" next ctx
|
return! Views.account form (isHtmx ctx) (csrf ctx) |> render "Account Profile" next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /citizen/so-long
|
// GET: /citizen/so-long
|
||||||
|
|
|
@ -61,7 +61,7 @@ let contactEdit (contacts : OtherContactForm array) =
|
||||||
:: (contacts |> Array.mapi mapToInputs |> List.ofArray)
|
:: (contacts |> Array.mapi mapToInputs |> List.ofArray)
|
||||||
|
|
||||||
/// The account edit page
|
/// The account edit page
|
||||||
let account (m : AccountProfileForm) csrf =
|
let account (m : AccountProfileForm) isHtmx csrf =
|
||||||
pageWithTitle "Account Profile" [
|
pageWithTitle "Account Profile" [
|
||||||
p [] [
|
p [] [
|
||||||
txt "This information is visible to all fellow logged-on citizens. For publicly-visible employment "
|
txt "This information is visible to all fellow logged-on citizens. For publicly-visible employment "
|
||||||
|
@ -107,7 +107,7 @@ let account (m : AccountProfileForm) csrf =
|
||||||
]
|
]
|
||||||
jsOnLoad $"
|
jsOnLoad $"
|
||||||
jjj.citizen.nextIndex = {m.Contacts.Length}
|
jjj.citizen.nextIndex = {m.Contacts.Length}
|
||||||
jjj.citizen.validatePasswords('{nameof m.NewPassword}', '{nameof m.NewPasswordConfirm}', false)"
|
jjj.citizen.validatePasswords('{nameof m.NewPassword}', '{nameof m.NewPasswordConfirm}', false)" isHtmx
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -305,7 +305,7 @@ let logOn (m : LogOnForm) csrf =
|
||||||
]
|
]
|
||||||
|
|
||||||
/// The registration page
|
/// The registration page
|
||||||
let register q1 q2 (m : RegisterForm) csrf =
|
let register q1 q2 (m : RegisterForm) isHtmx csrf =
|
||||||
pageWithTitle "Register" [
|
pageWithTitle "Register" [
|
||||||
form [ _class "row g-3"; _hxPost "/citizen/register" ] [
|
form [ _class "row g-3"; _hxPost "/citizen/register" ] [
|
||||||
antiForgery csrf
|
antiForgery csrf
|
||||||
|
@ -343,7 +343,7 @@ let register q1 q2 (m : RegisterForm) csrf =
|
||||||
input [ _type "hidden"; _name (nameof m.Question2Index); _value (string m.Question2Index ) ]
|
input [ _type "hidden"; _name (nameof m.Question2Index); _value (string m.Question2Index ) ]
|
||||||
]
|
]
|
||||||
div [ _class "col-12" ] [ submitButton "content-save-outline" "Save" ]
|
div [ _class "col-12" ] [ submitButton "content-save-outline" "Save" ]
|
||||||
jsOnLoad $"jjj.citizen.validatePasswords('{nameof m.Password}', 'ConfirmPassword', true)"
|
jsOnLoad $"jjj.citizen.validatePasswords('{nameof m.Password}', 'ConfirmPassword', true)" isHtmx
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -376,7 +376,7 @@ let resetCanceled wasCanceled =
|
||||||
|
|
||||||
|
|
||||||
/// The password reset page
|
/// The password reset page
|
||||||
let resetPassword (m : ResetPasswordForm) csrf =
|
let resetPassword (m : ResetPasswordForm) isHtmx csrf =
|
||||||
pageWithTitle "Reset Password" [
|
pageWithTitle "Reset Password" [
|
||||||
p [] [ txt "Enter your new password in the fields below" ]
|
p [] [ txt "Enter your new password in the fields below" ]
|
||||||
form [ _class "row g-3"; _method "POST"; _action "/citizen/reset-password" ] [
|
form [ _class "row g-3"; _method "POST"; _action "/citizen/reset-password" ] [
|
||||||
|
@ -390,6 +390,6 @@ let resetPassword (m : ResetPasswordForm) csrf =
|
||||||
textBox [ _type "password"; _minlength "8" ] "ConfirmPassword" "" "Confirm New Password" true
|
textBox [ _type "password"; _minlength "8" ] "ConfirmPassword" "" "Confirm New Password" true
|
||||||
]
|
]
|
||||||
div [ _class "col-12" ] [ submitButton "lock-reset" "Reset Password" ]
|
div [ _class "col-12" ] [ submitButton "lock-reset" "Reset Password" ]
|
||||||
jsOnLoad $"jjj.citizen.validatePasswords('{nameof m.Password}', 'ConfirmPassword', true)"
|
jsOnLoad $"jjj.citizen.validatePasswords('{nameof m.Password}', 'ConfirmPassword', true)" isHtmx
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -7,7 +7,7 @@ open Microsoft.AspNetCore.Http
|
||||||
open Microsoft.Extensions.Logging
|
open Microsoft.Extensions.Logging
|
||||||
|
|
||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
module private HtmxHelpers =
|
module HtmxHelpers =
|
||||||
|
|
||||||
/// Is the request from htmx?
|
/// Is the request from htmx?
|
||||||
let isHtmx (ctx : HttpContext) =
|
let isHtmx (ctx : HttpContext) =
|
||||||
|
@ -145,27 +145,32 @@ let addErrors (errors : string list) ctx = task {
|
||||||
|
|
||||||
open JobsJobsJobs.Common.Views
|
open JobsJobsJobs.Common.Views
|
||||||
|
|
||||||
/// Render a page-level view
|
/// Create the render context for an HTML response
|
||||||
let render pageTitle (_ : HttpFunc) (ctx : HttpContext) content = task {
|
let private createContext (ctx : HttpContext) pageTitle content messages : Layout.PageRenderContext =
|
||||||
let! messages = popMessages ctx
|
{ IsLoggedOn = Option.isSome (tryUser ctx)
|
||||||
let renderCtx : Layout.PageRenderContext = {
|
|
||||||
IsLoggedOn = Option.isSome (tryUser ctx)
|
|
||||||
CurrentUrl = ctx.Request.Path.Value
|
CurrentUrl = ctx.Request.Path.Value
|
||||||
PageTitle = pageTitle
|
PageTitle = pageTitle
|
||||||
Content = content
|
Content = content
|
||||||
Messages = messages
|
Messages = messages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render a page-level view
|
||||||
|
let render pageTitle (_ : HttpFunc) (ctx : HttpContext) content = task {
|
||||||
|
let! messages = popMessages ctx
|
||||||
|
let renderCtx = createContext ctx pageTitle content messages
|
||||||
let renderFunc = if isHtmx ctx then Layout.partial else Layout.full
|
let renderFunc = if isHtmx ctx then Layout.partial else Layout.full
|
||||||
return! ctx.WriteHtmlViewAsync (renderFunc renderCtx)
|
return! ctx.WriteHtmlViewAsync (renderFunc renderCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render a printable view (content with styles, but no layout)
|
||||||
|
let renderPrint pageTitle (_ : HttpFunc) (ctx : HttpContext) content =
|
||||||
|
createContext ctx pageTitle content []
|
||||||
|
|> Layout.print
|
||||||
|
|> ctx.WriteHtmlViewAsync
|
||||||
|
|
||||||
|
/// Render a bare (component) view
|
||||||
let renderBare (_ : HttpFunc) (ctx : HttpContext) content =
|
let renderBare (_ : HttpFunc) (ctx : HttpContext) content =
|
||||||
({ IsLoggedOn = Option.isSome (tryUser ctx)
|
createContext ctx "" content []
|
||||||
CurrentUrl = ctx.Request.Path.Value
|
|
||||||
PageTitle = ""
|
|
||||||
Content = content
|
|
||||||
Messages = []
|
|
||||||
} : Layout.PageRenderContext)
|
|
||||||
|> Layout.bare
|
|> Layout.bare
|
||||||
|> ctx.WriteHtmlViewAsync
|
|> ctx.WriteHtmlViewAsync
|
||||||
|
|
||||||
|
|
|
@ -68,11 +68,14 @@ let emptyP =
|
||||||
p [] [ txt " " ]
|
p [] [ txt " " ]
|
||||||
|
|
||||||
/// Register JavaScript code to run in the DOMContentLoaded event on the page
|
/// Register JavaScript code to run in the DOMContentLoaded event on the page
|
||||||
let jsOnLoad js =
|
let jsOnLoad js isHtmx =
|
||||||
script [] [ txt """document.addEventListener("DOMContentLoaded", function () { """; txt js; txt " })" ]
|
script [] [
|
||||||
|
let (target, event) = if isHtmx then "document.body", "htmx:afterSettle" else "document", "DOMContentLoaded"
|
||||||
|
txt (sprintf """%s.addEventListener("%s", () => { %s }, { once: true })""" target event js)
|
||||||
|
]
|
||||||
|
|
||||||
/// Create a Markdown editor
|
/// Create a Markdown editor
|
||||||
let markdownEditor attrs name value editorLabel =
|
let markdownEditor attrs name value editorLabel isHtmx =
|
||||||
div [ _class "col-12"; _id $"{name}EditRow" ] [
|
div [ _class "col-12"; _id $"{name}EditRow" ] [
|
||||||
nav [ _class "nav nav-pills pb-1" ] [
|
nav [ _class "nav nav-pills pb-1" ] [
|
||||||
button [ _type "button"; _id $"{name}EditButton"; _class "btn btn-primary btn-sm rounded-pill" ] [
|
button [ _type "button"; _id $"{name}EditButton"; _class "btn btn-primary btn-sm rounded-pill" ] [
|
||||||
|
@ -93,7 +96,7 @@ let markdownEditor attrs name value editorLabel =
|
||||||
]
|
]
|
||||||
label [ _for name ] [ txt editorLabel ]
|
label [ _for name ] [ txt editorLabel ]
|
||||||
]
|
]
|
||||||
jsOnLoad $"jjj.markdownOnLoad('{name}')"
|
jsOnLoad $"jjj.markdownOnLoad('{name}')" isHtmx
|
||||||
]
|
]
|
||||||
|
|
||||||
/// Wrap content in a collapsing panel
|
/// Wrap content in a collapsing panel
|
||||||
|
@ -124,22 +127,44 @@ let contactInfo citizen isPublic =
|
||||||
|> List.collect (fun contact ->
|
|> List.collect (fun contact ->
|
||||||
match contact.ContactType with
|
match contact.ContactType with
|
||||||
| Website ->
|
| Website ->
|
||||||
[ i [ _class "mdi mdi-sm mdi-web" ] []; rawText " "
|
[ i [ _class "mdi mdi-sm mdi-web" ] []; txt " "
|
||||||
a [ _href contact.Value; _target "_blank"; _rel "noopener"; _class "me-4" ] [
|
a [ _href contact.Value; _target "_blank"; _rel "noopener"; _class "me-4" ] [
|
||||||
str (defaultArg contact.Name "Website")
|
str (defaultArg contact.Name "Website")
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
| Email ->
|
| Email ->
|
||||||
[ i [ _class "mdi mdi-sm mdi-email-outline" ] []; rawText " "
|
[ i [ _class "mdi mdi-sm mdi-email-outline" ] []; txt " "
|
||||||
a [ _href $"mailto:{contact.Value}"; _class "me-4" ] [ str (defaultArg contact.Name "E-mail") ]
|
a [ _href $"mailto:{contact.Value}"; _class "me-4" ] [ str (defaultArg contact.Name "E-mail") ]
|
||||||
]
|
]
|
||||||
| Phone ->
|
| Phone ->
|
||||||
[ span [ _class "me-4" ] [
|
[ span [ _class "me-4" ] [
|
||||||
i [ _class "mdi mdi-sm mdi-phone" ] []; rawText " "; str contact.Value
|
i [ _class "mdi mdi-sm mdi-phone" ] []; txt " "; str contact.Value
|
||||||
match contact.Name with Some name -> str $" ({name})" | None -> ()
|
match contact.Name with Some name -> str $" ({name})" | None -> ()
|
||||||
]
|
]
|
||||||
])
|
])
|
||||||
|
|
||||||
|
/// Display a citizen's contact information
|
||||||
|
let contactInfoPrint citizen isPublic =
|
||||||
|
citizen.OtherContacts
|
||||||
|
|> List.filter (fun it -> (isPublic && it.IsPublic) || not isPublic)
|
||||||
|
|> List.collect (fun contact ->
|
||||||
|
match contact.ContactType with
|
||||||
|
| Website ->
|
||||||
|
[ i [ _class "mdi mdi-sm mdi-web" ] []; txt " "; str (defaultArg contact.Name "Website"); txt " – "
|
||||||
|
str contact.Value; br []
|
||||||
|
]
|
||||||
|
| Email ->
|
||||||
|
[ i [ _class "mdi mdi-sm mdi-email-outline" ] []; txt " "; str (defaultArg contact.Name "E-mail")
|
||||||
|
txt " – "; str contact.Value; br []
|
||||||
|
]
|
||||||
|
| Phone ->
|
||||||
|
[ span [ _class "me-4" ] [
|
||||||
|
i [ _class "mdi mdi-sm mdi-phone" ] []; rawText " "
|
||||||
|
match contact.Name with Some name -> str name; txt " – " | None -> ()
|
||||||
|
str contact.Value; br []
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
open NodaTime
|
open NodaTime
|
||||||
open NodaTime.Text
|
open NodaTime.Text
|
||||||
|
|
||||||
|
@ -353,6 +378,13 @@ module Layout =
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/// Render a print view (styles, but no other layout)
|
||||||
|
let print ctx =
|
||||||
|
html [ _lang "en" ] [
|
||||||
|
htmlHead ctx
|
||||||
|
body [ _class "m-1" ] [ ctx.Content ]
|
||||||
|
]
|
||||||
|
|
||||||
/// Render a bare view (used for components)
|
/// Render a bare view (used for components)
|
||||||
let bare ctx =
|
let bare ctx =
|
||||||
html [ _lang "en" ] [
|
html [ _lang "en" ] [
|
||||||
|
|
|
@ -23,7 +23,7 @@ let edit listId : HttpHandler = requireUser >=> fun next ctx -> task {
|
||||||
| Some listing when listing.CitizenId = citizenId ->
|
| Some listing when listing.CitizenId = citizenId ->
|
||||||
let! continents = Common.Data.Continents.all ()
|
let! continents = Common.Data.Continents.all ()
|
||||||
return!
|
return!
|
||||||
Views.edit (EditListingForm.fromListing listing listId) continents (listId = "new") (csrf ctx)
|
Views.edit (EditListingForm.fromListing listing listId) continents (listId = "new") (isHtmx ctx) (csrf ctx)
|
||||||
|> render $"""{if listId = "new" then "Add a" else "Edit"} Job Listing""" next ctx
|
|> render $"""{if listId = "new" then "Add a" else "Edit"} Job Listing""" next ctx
|
||||||
| Some _ -> return! Error.notAuthorized next ctx
|
| Some _ -> return! Error.notAuthorized next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
|
@ -38,7 +38,7 @@ let expire listingId : HttpHandler = requireUser >=> fun next ctx -> task {
|
||||||
return! redirectToGet "/listings/mine" next ctx
|
return! redirectToGet "/listings/mine" next ctx
|
||||||
else
|
else
|
||||||
let form = { Id = ListingId.toString listing.Id; FromHere = false; SuccessStory = "" }
|
let form = { Id = ListingId.toString listing.Id; FromHere = false; SuccessStory = "" }
|
||||||
return! Views.expire form listing (csrf ctx) |> render "Expire Job Listing" next ctx
|
return! Views.expire form listing (isHtmx ctx) (csrf ctx) |> render "Expire Job Listing" next ctx
|
||||||
| Some _ -> return! Error.notAuthorized next ctx
|
| Some _ -> return! Error.notAuthorized next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ open JobsJobsJobs.Listings.Domain
|
||||||
|
|
||||||
|
|
||||||
/// Job listing edit page
|
/// Job listing edit page
|
||||||
let edit (m : EditListingForm) continents isNew csrf =
|
let edit (m : EditListingForm) continents isNew isHtmx csrf =
|
||||||
pageWithTitle $"""{if isNew then "Add a" else "Edit"} Job Listing""" [
|
pageWithTitle $"""{if isNew then "Add a" else "Edit"} Job Listing""" [
|
||||||
form [ _class "row g-3"; _method "POST"; _action "/listing/save" ] [
|
form [ _class "row g-3"; _method "POST"; _action "/listing/save" ] [
|
||||||
antiForgery csrf
|
antiForgery csrf
|
||||||
|
@ -29,7 +29,7 @@ let edit (m : EditListingForm) continents isNew csrf =
|
||||||
div [ _class "col-12" ] [
|
div [ _class "col-12" ] [
|
||||||
checkBox [] (nameof m.RemoteWork) m.RemoteWork "This opportunity is for remote work"
|
checkBox [] (nameof m.RemoteWork) m.RemoteWork "This opportunity is for remote work"
|
||||||
]
|
]
|
||||||
markdownEditor [ _required ] (nameof m.Text) m.Text "Job Description"
|
markdownEditor [ _required ] (nameof m.Text) m.Text "Job Description" isHtmx
|
||||||
div [ _class "col-12 col-md-4" ] [
|
div [ _class "col-12 col-md-4" ] [
|
||||||
textBox [ _type "date" ] (nameof m.NeededBy) m.NeededBy "Needed By" false
|
textBox [ _type "date" ] (nameof m.NeededBy) m.NeededBy "Needed By" false
|
||||||
]
|
]
|
||||||
|
@ -41,7 +41,7 @@ let edit (m : EditListingForm) continents isNew csrf =
|
||||||
open System.Net
|
open System.Net
|
||||||
|
|
||||||
/// Page to expire a job listing
|
/// Page to expire a job listing
|
||||||
let expire (m : ExpireListingForm) (listing : Listing) csrf =
|
let expire (m : ExpireListingForm) (listing : Listing) isHtmx csrf =
|
||||||
pageWithTitle $"Expire Job Listing ({WebUtility.HtmlEncode listing.Title})" [
|
pageWithTitle $"Expire Job Listing ({WebUtility.HtmlEncode listing.Title})" [
|
||||||
p [ _class "fst-italic" ] [
|
p [ _class "fst-italic" ] [
|
||||||
txt "Expiring this listing will remove it from search results. You will be able to see it via your "
|
txt "Expiring this listing will remove it from search results. You will be able to see it via your "
|
||||||
|
@ -60,10 +60,10 @@ let expire (m : ExpireListingForm) (listing : Listing) csrf =
|
||||||
txt "visible to logged-on users here, but not to the general public."
|
txt "visible to logged-on users here, but not to the general public."
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
markdownEditor [] (nameof m.SuccessStory) m.SuccessStory "Your Success Story"
|
markdownEditor [] (nameof m.SuccessStory) m.SuccessStory "Your Success Story" isHtmx
|
||||||
div [ _class "col-12" ] [ submitButton "text-box-remove-outline" "Expire Listing" ]
|
div [ _class "col-12" ] [ submitButton "text-box-remove-outline" "Expire Listing" ]
|
||||||
]
|
]
|
||||||
jsOnLoad "jjj.listing.toggleFromHere()"
|
jsOnLoad "jjj.listing.toggleFromHere()" isHtmx
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,8 @@ let editGeneralInfo : HttpHandler = requireUser >=> fun next ctx -> task {
|
||||||
let! continents = Common.Data.Continents.all ()
|
let! continents = Common.Data.Continents.all ()
|
||||||
let form = if Option.isNone profile then EditProfileForm.empty else EditProfileForm.fromProfile profile.Value
|
let form = if Option.isNone profile then EditProfileForm.empty else EditProfileForm.fromProfile profile.Value
|
||||||
return!
|
return!
|
||||||
Views.editGeneralInfo form continents (csrf ctx) |> render "General Information | Employment Profile" next ctx
|
Views.editGeneralInfo form continents (isHtmx ctx) (csrf ctx)
|
||||||
|
|> render "General Information | Employment Profile" next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: /profile/save
|
// POST: /profile/save
|
||||||
|
@ -65,7 +66,7 @@ let saveGeneralInfo : HttpHandler = requireUser >=> fun next ctx -> task {
|
||||||
do! addErrors errors ctx
|
do! addErrors errors ctx
|
||||||
let! continents = Common.Data.Continents.all ()
|
let! continents = Common.Data.Continents.all ()
|
||||||
return!
|
return!
|
||||||
Views.editGeneralInfo form continents (csrf ctx)
|
Views.editGeneralInfo form continents (isHtmx ctx) (csrf ctx)
|
||||||
|> render "General Information | Employment Profile" next ctx
|
|> render "General Information | Employment Profile" next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,14 +158,16 @@ let deleteSkill idx : HttpHandler = requireUser >=> validateCsrf >=> fun next ct
|
||||||
let history : HttpHandler = requireUser >=> fun next ctx -> task {
|
let history : HttpHandler = requireUser >=> fun next ctx -> task {
|
||||||
match! Data.findById (currentCitizenId ctx) with
|
match! Data.findById (currentCitizenId ctx) with
|
||||||
| Some profile ->
|
| Some profile ->
|
||||||
return! Views.history profile.History (csrf ctx) |> render "Employment History | Employment Profile" next ctx
|
return!
|
||||||
|
Views.history profile.History (isHtmx ctx) (csrf ctx)
|
||||||
|
|> render "Employment History | Employment Profile" next ctx
|
||||||
| None -> return! notFound ctx
|
| None -> return! notFound ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /profile/edit/history/list
|
// GET: /profile/edit/history/list
|
||||||
let historyList : HttpHandler = requireUser >=> fun next ctx -> task {
|
let historyList : HttpHandler = requireUser >=> fun next ctx -> task {
|
||||||
match! Data.findById (currentCitizenId ctx) with
|
match! Data.findById (currentCitizenId ctx) with
|
||||||
| Some profile -> return! Views.historyTable profile.History None (csrf ctx) |> renderBare next ctx
|
| Some profile -> return! Views.historyTable profile.History None (isHtmx ctx) (csrf ctx) |> renderBare next ctx
|
||||||
| None -> return! notFound ctx
|
| None -> return! notFound ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +176,7 @@ let editHistory idx : HttpHandler = requireUser >=> fun next ctx -> task {
|
||||||
match! Data.findById (currentCitizenId ctx) with
|
match! Data.findById (currentCitizenId ctx) with
|
||||||
| Some profile ->
|
| Some profile ->
|
||||||
if idx < -1 || idx >= List.length profile.History then return! notFound ctx
|
if idx < -1 || idx >= List.length profile.History then return! notFound ctx
|
||||||
else return! Views.editHistory profile.History idx (csrf ctx) |> renderBare next ctx
|
else return! Views.editHistory profile.History idx (isHtmx ctx) (csrf ctx) |> renderBare next ctx
|
||||||
| None -> return! notFound ctx
|
| None -> return! notFound ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +193,7 @@ let saveHistory idx : HttpHandler = requireUser >=> validateCsrf >=> fun next ct
|
||||||
else profile.History |> List.mapi (fun histIdx it -> if histIdx = idx then entry else it)
|
else profile.History |> List.mapi (fun histIdx it -> if histIdx = idx then entry else it)
|
||||||
|> List.sortByDescending (fun it -> defaultArg it.EndDate NodaTime.LocalDate.MaxIsoValue)
|
|> List.sortByDescending (fun it -> defaultArg it.EndDate NodaTime.LocalDate.MaxIsoValue)
|
||||||
do! Data.save { profile with History = history }
|
do! Data.save { profile with History = history }
|
||||||
return! Views.historyTable history None (csrf ctx) |> renderBare next ctx
|
return! Views.historyTable history None (isHtmx ctx) (csrf ctx) |> renderBare next ctx
|
||||||
| None -> return! notFound ctx
|
| None -> return! notFound ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +205,7 @@ let deleteHistory idx : HttpHandler = requireUser >=> validateCsrf >=> fun next
|
||||||
else
|
else
|
||||||
let history = profile.History |> List.indexed |> List.filter (fun it -> fst it <> idx) |> List.map snd
|
let history = profile.History |> List.indexed |> List.filter (fun it -> fst it <> idx) |> List.map snd
|
||||||
do! Data.save { profile with History = history }
|
do! Data.save { profile with History = history }
|
||||||
return! Views.historyTable history None (csrf ctx) |> renderBare next ctx
|
return! Views.historyTable history None (isHtmx ctx) (csrf ctx) |> renderBare next ctx
|
||||||
| None -> return! notFound ctx
|
| None -> return! notFound ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,6 +223,20 @@ let view citizenId : HttpHandler = fun next ctx -> task {
|
||||||
| None -> return! notFound ctx
|
| None -> return! notFound ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET: /profile/[id]/print
|
||||||
|
let print citizenId : HttpHandler = fun next ctx -> task {
|
||||||
|
let citId = CitizenId citizenId
|
||||||
|
match! Data.findByIdForView citId with
|
||||||
|
| Some profile ->
|
||||||
|
let currentCitizen = tryUser ctx |> Option.map CitizenId.ofString
|
||||||
|
if not (profile.Profile.Visibility = Public) && Option.isNone currentCitizen then
|
||||||
|
return! Error.notAuthorized next ctx
|
||||||
|
else
|
||||||
|
let pageTitle = $"Employment Profile for {Citizen.name profile.Citizen}"
|
||||||
|
return! Views.print profile (Option.isNone currentCitizen) |> renderPrint pageTitle next ctx
|
||||||
|
| None -> return! notFound ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
open Giraffe.EndpointRouting
|
open Giraffe.EndpointRouting
|
||||||
|
|
||||||
|
@ -228,6 +245,7 @@ let endpoints =
|
||||||
subRoute "/profile" [
|
subRoute "/profile" [
|
||||||
GET_HEAD [
|
GET_HEAD [
|
||||||
routef "/%O/view" view
|
routef "/%O/view" view
|
||||||
|
routef "/%O/print" print
|
||||||
route "/edit" edit
|
route "/edit" edit
|
||||||
route "/edit/general" editGeneralInfo
|
route "/edit/general" editGeneralInfo
|
||||||
routef "/edit/history/%i" editHistory
|
routef "/edit/history/%i" editHistory
|
||||||
|
|
|
@ -62,7 +62,7 @@ let backToEdit =
|
||||||
|
|
||||||
|
|
||||||
/// The profile edit page
|
/// The profile edit page
|
||||||
let editGeneralInfo (m : EditProfileForm) continents csrf =
|
let editGeneralInfo (m : EditProfileForm) continents isHtmx csrf =
|
||||||
pageWithTitle "Employment Profile: General Information" [
|
pageWithTitle "Employment Profile: General Information" [
|
||||||
backToEdit
|
backToEdit
|
||||||
form [ _class "row g-3"; _action "/profile/save"; _hxPost "/profile/save" ] [
|
form [ _class "row g-3"; _action "/profile/save"; _hxPost "/profile/save" ] [
|
||||||
|
@ -88,7 +88,7 @@ let editGeneralInfo (m : EditProfileForm) continents csrf =
|
||||||
div [ _class "col-12 col-md-4" ] [
|
div [ _class "col-12 col-md-4" ] [
|
||||||
checkBox [] (nameof m.FullTime) m.FullTime "I am interested in full-time work"
|
checkBox [] (nameof m.FullTime) m.FullTime "I am interested in full-time work"
|
||||||
]
|
]
|
||||||
markdownEditor [ _required ] (nameof m.Biography) m.Biography "Professional Biography"
|
markdownEditor [ _required ] (nameof m.Biography) m.Biography "Professional Biography" isHtmx
|
||||||
div [ _class "col-12" ] [
|
div [ _class "col-12" ] [
|
||||||
hr []
|
hr []
|
||||||
h4 [] [ txt "Experience" ]
|
h4 [] [ txt "Experience" ]
|
||||||
|
@ -98,7 +98,7 @@ let editGeneralInfo (m : EditProfileForm) continents csrf =
|
||||||
txt "entries, provide closing notes, etc."
|
txt "entries, provide closing notes, etc."
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
markdownEditor [] (nameof m.Experience) (defaultArg m.Experience "") "Experience"
|
markdownEditor [] (nameof m.Experience) (defaultArg m.Experience "") "Experience" isHtmx
|
||||||
div [ _class "col-12" ] [
|
div [ _class "col-12" ] [
|
||||||
hr []
|
hr []
|
||||||
h4 [] [ txt "Visibility" ]
|
h4 [] [ txt "Visibility" ]
|
||||||
|
@ -230,7 +230,7 @@ let editSkill (skills : Skill list) idx csrf =
|
||||||
open NodaTime
|
open NodaTime
|
||||||
|
|
||||||
/// Render the employment history edit template
|
/// Render the employment history edit template
|
||||||
let historyForm (m : HistoryForm) isNew =
|
let historyForm (m : HistoryForm) isNew isHtmx =
|
||||||
let maxDate = HistoryForm.dateFormat.Format (LocalDate.FromDateTime System.DateTime.Today)
|
let maxDate = HistoryForm.dateFormat.Format (LocalDate.FromDateTime System.DateTime.Today)
|
||||||
[ h4 [] [ txt $"""{if isNew then "Add" else "Edit"} Employment History""" ]
|
[ h4 [] [ txt $"""{if isNew then "Add" else "Edit"} Employment History""" ]
|
||||||
div [ _class "col-12 col-md-6" ] [
|
div [ _class "col-12 col-md-6" ] [
|
||||||
|
@ -239,7 +239,7 @@ let historyForm (m : HistoryForm) isNew =
|
||||||
div [ _class "col-12 col-md-6" ] [
|
div [ _class "col-12 col-md-6" ] [
|
||||||
textBox [ _type "text"; _maxlength "200" ] (nameof m.Position) m.Position "Title or Position" true
|
textBox [ _type "text"; _maxlength "200" ] (nameof m.Position) m.Position "Title or Position" true
|
||||||
]
|
]
|
||||||
p [ _class "col-12 text-center" ] [
|
p [ _class "col-12 text-center mb-0" ] [
|
||||||
txt "Select any date within the month; only the month and year will be displayed"
|
txt "Select any date within the month; only the month and year will be displayed"
|
||||||
]
|
]
|
||||||
div [ _class "col-12 col-md-6 col-xl-4 offset-xl-1" ] [
|
div [ _class "col-12 col-md-6 col-xl-4 offset-xl-1" ] [
|
||||||
|
@ -248,7 +248,7 @@ let historyForm (m : HistoryForm) isNew =
|
||||||
div [ _class "col-12 col-md-6 col-xl-4 offset-xl-2" ] [
|
div [ _class "col-12 col-md-6 col-xl-4 offset-xl-2" ] [
|
||||||
textBox [ _type "date"; _max maxDate ] (nameof m.EndDate) m.EndDate "End Date" false
|
textBox [ _type "date"; _max maxDate ] (nameof m.EndDate) m.EndDate "End Date" false
|
||||||
]
|
]
|
||||||
markdownEditor [] (nameof m.Description) m.Description "Description"
|
markdownEditor [] (nameof m.Description) m.Description "Description" isHtmx
|
||||||
div [ _class "col-12" ] [
|
div [ _class "col-12" ] [
|
||||||
submitButton "content-save-outline" "Save"; txt " "
|
submitButton "content-save-outline" "Save"; txt " "
|
||||||
a [ _href "/profile/edit/history/list"; _hxGet "/profile/edit/history/list"; _hxTarget "#historyList"
|
a [ _href "/profile/edit/history/list"; _hxGet "/profile/edit/history/list"; _hxTarget "#historyList"
|
||||||
|
@ -256,19 +256,19 @@ let historyForm (m : HistoryForm) isNew =
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
let private monthAndYear = Text.LocalDatePattern.CreateWithInvariantCulture "MMMM yyyy"
|
||||||
|
|
||||||
/// List the employment history entries for an employment profile
|
/// List the employment history entries for an employment profile
|
||||||
let historyTable (history : EmploymentHistory list) editIdx csrf =
|
let historyTable (history : EmploymentHistory list) editIdx isHtmx csrf =
|
||||||
let editingIdx = defaultArg editIdx -2
|
let editingIdx = defaultArg editIdx -2
|
||||||
let isEditing = editingIdx >= -1
|
let isEditing = editingIdx >= -1
|
||||||
let monthAndYear = Text.LocalDatePattern.CreateWithInvariantCulture "MMMM yyyy"
|
|
||||||
let renderTable () =
|
let renderTable () =
|
||||||
let editHistoryForm entry idx =
|
let editHistoryForm entry idx =
|
||||||
tr [] [
|
tr [] [
|
||||||
td [ _colspan "4" ] [
|
td [ _colspan "4" ] [
|
||||||
form [ _class "row g-3"; _hxPost $"/profile/edit/history/{idx}"; _hxTarget "#historyList" ] [
|
form [ _class "row g-3"; _hxPost $"/profile/edit/history/{idx}"; _hxTarget "#historyList" ] [
|
||||||
antiForgery csrf
|
antiForgery csrf
|
||||||
yield! historyForm (HistoryForm.fromHistory entry) (idx = -1)
|
yield! historyForm (HistoryForm.fromHistory entry) (idx = -1) isHtmx
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -314,7 +314,7 @@ let historyTable (history : EmploymentHistory list) editIdx csrf =
|
||||||
form [ _id "historyList"; _hxTarget "this"; _hxPost "/profile/edit/history/-1"; _hxSwap HxSwap.OuterHtml
|
form [ _id "historyList"; _hxTarget "this"; _hxPost "/profile/edit/history/-1"; _hxSwap HxSwap.OuterHtml
|
||||||
_class "row g-3" ] [
|
_class "row g-3" ] [
|
||||||
antiForgery csrf
|
antiForgery csrf
|
||||||
yield! historyForm (HistoryForm.fromHistory EmploymentHistory.empty) true
|
yield! historyForm (HistoryForm.fromHistory EmploymentHistory.empty) true isHtmx
|
||||||
]
|
]
|
||||||
else if isEditing then div [ _id "historyList" ] [ renderTable () ]
|
else if isEditing then div [ _id "historyList" ] [ renderTable () ]
|
||||||
else // not editing, there is history to show
|
else // not editing, there is history to show
|
||||||
|
@ -325,7 +325,7 @@ let historyTable (history : EmploymentHistory list) editIdx csrf =
|
||||||
|
|
||||||
|
|
||||||
/// The employment history maintenance page
|
/// The employment history maintenance page
|
||||||
let history (history : EmploymentHistory list) csrf =
|
let history (history : EmploymentHistory list) isHtmx csrf =
|
||||||
pageWithTitle "Employment Profile: Employment History" [
|
pageWithTitle "Employment Profile: Employment History" [
|
||||||
backToEdit
|
backToEdit
|
||||||
p [] [
|
p [] [
|
||||||
|
@ -334,7 +334,7 @@ let history (history : EmploymentHistory list) csrf =
|
||||||
txt "Add an Employment History Entry"
|
txt "Add an Employment History Entry"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
historyTable history None csrf
|
historyTable history None isHtmx csrf
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -507,11 +507,9 @@ let search (m : ProfileSearchForm) continents tz (results : ProfileSearchResult
|
||||||
| None -> ()
|
| None -> ()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/// Display a profile
|
||||||
/// Profile view template
|
let private displayProfile (it : ProfileForView) isPublic isPrint =
|
||||||
let view (it : ProfileForView) currentId =
|
[ h2 [] [
|
||||||
article [] [
|
|
||||||
h2 [] [
|
|
||||||
str (Citizen.name it.Citizen)
|
str (Citizen.name it.Citizen)
|
||||||
if it.Profile.IsSeekingEmployment then
|
if it.Profile.IsSeekingEmployment then
|
||||||
span [ _class "jjj-heading-label" ] [
|
span [ _class "jjj-heading-label" ] [
|
||||||
|
@ -519,17 +517,15 @@ let view (it : ProfileForView) currentId =
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
h4 [] [ str $"{it.Continent.Name}, {it.Profile.Region}" ]
|
h4 [] [ str $"{it.Continent.Name}, {it.Profile.Region}" ]
|
||||||
contactInfo it.Citizen (Option.isNone currentId)
|
(if isPrint then contactInfoPrint else contactInfo) it.Citizen isPublic
|
||||||
|> div [ _class "pb-3" ]
|
|> div [ _class "pb-3" ]
|
||||||
p [] [
|
p [] [
|
||||||
txt (if it.Profile.IsFullTime then "I" else "Not i"); txt "nterested in full-time employment • "
|
txt (if it.Profile.IsFullTime then "I" else "Not i"); txt "nterested in full-time employment • "
|
||||||
txt (if it.Profile.IsRemote then "I" else "Not i"); txt "nterested in remote opportunities"
|
txt (if it.Profile.IsRemote then "I" else "Not i"); txt "nterested in remote opportunities"
|
||||||
]
|
]
|
||||||
hr []
|
|
||||||
div [] [ md2html it.Profile.Biography ]
|
div [] [ md2html it.Profile.Biography ]
|
||||||
if not (List.isEmpty it.Profile.Skills) then
|
if not (List.isEmpty it.Profile.Skills) then
|
||||||
hr []
|
h4 [ _class "pb-3 border-top border-3" ] [ txt "Skills" ]
|
||||||
h4 [ _class "pb-3" ] [ txt "Skills" ]
|
|
||||||
it.Profile.Skills
|
it.Profile.Skills
|
||||||
|> List.map (fun skill ->
|
|> List.map (fun skill ->
|
||||||
li [] [
|
li [] [
|
||||||
|
@ -537,12 +533,55 @@ let view (it : ProfileForView) currentId =
|
||||||
match skill.Notes with Some notes -> txt " ("; str notes; txt ")" | None -> ()
|
match skill.Notes with Some notes -> txt " ("; str notes; txt ")" | None -> ()
|
||||||
])
|
])
|
||||||
|> ul []
|
|> ul []
|
||||||
|
if not (List.isEmpty it.Profile.History) then
|
||||||
|
h4 [ _class "mb-3 border-top border-3" ] [ txt "Employment History" ]
|
||||||
|
yield!
|
||||||
|
it.Profile.History
|
||||||
|
|> List.indexed
|
||||||
|
|> List.collect (fun (idx, entry) -> [
|
||||||
|
let maybeBorder = if idx > 0 then " border-top" else ""
|
||||||
|
div [ _class $"d-flex flex-row flex-wrap justify-content-between align-items-start mt-4 mb-2{maybeBorder}" ] [
|
||||||
|
div [] [
|
||||||
|
strong [] [ str entry.Employer ]
|
||||||
|
match entry.Position with Some pos -> br []; str pos | None -> ()
|
||||||
|
]
|
||||||
|
div [ _class "text-end" ] [
|
||||||
|
span [ _class "text-nowrap" ] [ str (monthAndYear.Format entry.StartDate) ]; txt " to "
|
||||||
|
match entry.EndDate with
|
||||||
|
| Some dt -> span [ _class "text-nowrap" ] [ str (monthAndYear.Format dt) ]
|
||||||
|
| None -> txt "Present"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
match entry.Description with Some d -> div [] [ md2html d ] | None -> ()
|
||||||
|
])
|
||||||
match it.Profile.Experience with
|
match it.Profile.Experience with
|
||||||
| Some exp -> hr []; h4 [ _class "pb-3" ] [ txt "Experience / Employment History" ]; div [] [ md2html exp ]
|
| Some exp -> div [ _class "border-top border-3" ] [ md2html exp ]
|
||||||
| None -> ()
|
| None -> ()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
/// Profile view template
|
||||||
|
let view (it : ProfileForView) currentId =
|
||||||
|
article [] [
|
||||||
|
yield! displayProfile it (Option.isNone currentId) false
|
||||||
if Option.isSome currentId && currentId.Value = it.Citizen.Id then
|
if Option.isSome currentId && currentId.Value = it.Citizen.Id then
|
||||||
br []; br []
|
br []; br []
|
||||||
a [ _href "/profile/edit"; _class "btn btn-primary" ] [
|
a [ _href "/profile/edit"; _class "btn btn-primary" ] [
|
||||||
i [ _class "mdi mdi-pencil" ] []; txt " Edit Your Profile"
|
i [ _class "mdi mdi-pencil" ] []; txt " Edit Your Profile"
|
||||||
]
|
]
|
||||||
|
txt " "
|
||||||
|
a [ _href $"/profile/{CitizenId.toString it.Profile.Id}/print"; _target "_blank"
|
||||||
|
_class "btn btn-outline-secondary" ] [
|
||||||
|
i [ _class "mdi mdi-printer-outline" ] []; txt " View Print Version"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
/// Printable profile view template
|
||||||
|
let print (it : ProfileForView) isPublic =
|
||||||
|
article [] [
|
||||||
|
yield! displayProfile it isPublic true
|
||||||
|
button [ _type "button"; _class "btn btn-sm btn-secondary jjj-hide-from-printer"; _onclick "window.print()" ] [
|
||||||
|
i [ _class "mdi mdi-printer-outline" ] []; txt " Print"
|
||||||
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -19,7 +19,8 @@ let edit successId : HttpHandler = requireUser >=> fun next ctx -> task {
|
||||||
| Some success when success.CitizenId = citizenId ->
|
| Some success when success.CitizenId = citizenId ->
|
||||||
let pgTitle = $"""{if isNew then "Tell Your" else "Edit"} Success Story"""
|
let pgTitle = $"""{if isNew then "Tell Your" else "Edit"} Success Story"""
|
||||||
return!
|
return!
|
||||||
Views.edit (EditSuccessForm.fromSuccess success) (success.Id = SuccessId Guid.Empty) pgTitle (csrf ctx)
|
Views.edit (EditSuccessForm.fromSuccess success) (success.Id = SuccessId Guid.Empty) pgTitle (isHtmx ctx)
|
||||||
|
(csrf ctx)
|
||||||
|> render pgTitle next ctx
|
|> render pgTitle next ctx
|
||||||
| Some _ -> return! Error.notAuthorized next ctx
|
| Some _ -> return! Error.notAuthorized next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
|
|
|
@ -7,7 +7,7 @@ open JobsJobsJobs.Domain
|
||||||
open JobsJobsJobs.SuccessStories.Domain
|
open JobsJobsJobs.SuccessStories.Domain
|
||||||
|
|
||||||
/// The add/edit success story page
|
/// The add/edit success story page
|
||||||
let edit (m : EditSuccessForm) isNew pgTitle csrf =
|
let edit (m : EditSuccessForm) isNew pgTitle isHtmx csrf =
|
||||||
pageWithTitle pgTitle [
|
pageWithTitle pgTitle [
|
||||||
if isNew then
|
if isNew then
|
||||||
p [] [
|
p [] [
|
||||||
|
@ -21,7 +21,7 @@ let edit (m : EditSuccessForm) isNew pgTitle csrf =
|
||||||
div [ _class "col-12" ] [
|
div [ _class "col-12" ] [
|
||||||
checkBox [] (nameof m.FromHere) m.FromHere "I found my employment here"
|
checkBox [] (nameof m.FromHere) m.FromHere "I found my employment here"
|
||||||
]
|
]
|
||||||
markdownEditor [] (nameof m.Story) m.Story "The Success Story"
|
markdownEditor [] (nameof m.Story) m.Story "The Success Story" isHtmx
|
||||||
div [ _class "col-12" ] [
|
div [ _class "col-12" ] [
|
||||||
submitButton "content-save-outline" "Save"
|
submitButton "content-save-outline" "Save"
|
||||||
if isNew then
|
if isNew then
|
||||||
|
|
Loading…
Reference in New Issue
Block a user