Version 3 #40
|
@ -45,7 +45,7 @@ module DataConnection =
|
||||||
let dataSource () =
|
let dataSource () =
|
||||||
match theDataSource with
|
match theDataSource with
|
||||||
| Some ds -> Sql.fromDataSource ds
|
| Some ds -> Sql.fromDataSource ds
|
||||||
| None -> invalidOp "Connection.setUp() must be called before accessing the database"
|
| None -> invalidOp "DataConnection.setUp() must be called before accessing the database"
|
||||||
|
|
||||||
/// Create tables
|
/// Create tables
|
||||||
let private createTables () = backgroundTask {
|
let private createTables () = backgroundTask {
|
||||||
|
|
|
@ -195,7 +195,7 @@ let isLocal = Regex """^/[^\/\\].*"""
|
||||||
let redirectToGet (url : string) next ctx = task {
|
let redirectToGet (url : string) next ctx = task {
|
||||||
do! saveSession ctx
|
do! saveSession ctx
|
||||||
let action =
|
let action =
|
||||||
if Option.isSome (noneIfEmpty url) && isLocal.IsMatch url then
|
if Option.isSome (noneIfEmpty url) && (url = "/" || isLocal.IsMatch url) then
|
||||||
if isHtmx ctx then withHxRedirect url else redirectTo false url
|
if isHtmx ctx then withHxRedirect url else redirectTo false url
|
||||||
else RequestErrors.BAD_REQUEST "Invalid redirect URL"
|
else RequestErrors.BAD_REQUEST "Invalid redirect URL"
|
||||||
return! action next ctx
|
return! action next ctx
|
||||||
|
|
|
@ -252,7 +252,7 @@ module Layout =
|
||||||
navLink "/citizen/log-off" "logout-variant" "Log Off"
|
navLink "/citizen/log-off" "logout-variant" "Log Off"
|
||||||
else
|
else
|
||||||
navLink "/" "home" "Home"
|
navLink "/" "home" "Home"
|
||||||
navLink "/profile/seeking" "view-list-outline" "Job Seekers"
|
navLink "/profile/search" "view-list-outline" "Employment Profiles"
|
||||||
navLink "/citizen/log-on" "login-variant" "Log On"
|
navLink "/citizen/log-on" "login-variant" "Log On"
|
||||||
navLink "/how-it-works" "help-circle-outline" "How It Works"
|
navLink "/how-it-works" "help-circle-outline" "How It Works"
|
||||||
]
|
]
|
||||||
|
|
|
@ -29,6 +29,13 @@ let findById citizenId = backgroundTask {
|
||||||
| None -> return None
|
| None -> return None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a data row to a profile for viewing
|
||||||
|
let private toProfileForView row =
|
||||||
|
{ Profile = toDocument<Profile> row
|
||||||
|
Citizen = toDocumentFrom<Citizen> "cit_data" row
|
||||||
|
Continent = toDocumentFrom<Continent> "cont_data" row
|
||||||
|
}
|
||||||
|
|
||||||
/// Find a profile by citizen ID for viewing (includes citizen and continent information)
|
/// Find a profile by citizen ID for viewing (includes citizen and continent information)
|
||||||
let findByIdForView citizenId = backgroundTask {
|
let findByIdForView citizenId = backgroundTask {
|
||||||
let! tryCitizen =
|
let! tryCitizen =
|
||||||
|
@ -41,11 +48,7 @@ let findByIdForView citizenId = backgroundTask {
|
||||||
WHERE p.id = @id
|
WHERE p.id = @id
|
||||||
AND p.data ->> 'isLegacy' = 'false'"
|
AND p.data ->> 'isLegacy' = 'false'"
|
||||||
|> Sql.parameters [ "@id", Sql.string (CitizenId.toString citizenId) ]
|
|> Sql.parameters [ "@id", Sql.string (CitizenId.toString citizenId) ]
|
||||||
|> Sql.executeAsync (fun row ->
|
|> Sql.executeAsync toProfileForView
|
||||||
{ Profile = toDocument<Profile> row
|
|
||||||
Citizen = toDocumentFrom<Citizen> "cit_data" row
|
|
||||||
Continent = toDocumentFrom<Continent> "cont_data" row
|
|
||||||
})
|
|
||||||
return List.tryHead tryCitizen
|
return List.tryHead tryCitizen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,8 +56,8 @@ let findByIdForView citizenId = backgroundTask {
|
||||||
let save (profile : Profile) =
|
let save (profile : Profile) =
|
||||||
dataSource () |> saveDocument Table.Profile (CitizenId.toString profile.Id) <| mkDoc profile
|
dataSource () |> saveDocument Table.Profile (CitizenId.toString profile.Id) <| mkDoc profile
|
||||||
|
|
||||||
/// Search profiles (logged-on users)
|
/// Search profiles
|
||||||
let search (search : ProfileSearchForm) = backgroundTask {
|
let search (search : ProfileSearchForm) isPublic = backgroundTask {
|
||||||
let searches = [
|
let searches = [
|
||||||
if search.ContinentId <> "" then
|
if search.ContinentId <> "" then
|
||||||
"p.data ->> 'continentId' = @continentId", [ "@continentId", Sql.string search.ContinentId ]
|
"p.data ->> 'continentId' = @continentId", [ "@continentId", Sql.string search.ContinentId ]
|
||||||
|
@ -74,61 +77,21 @@ let search (search : ProfileSearchForm) = backgroundTask {
|
||||||
OR x ->> 'description' ILIKE @text)",
|
OR x ->> 'description' ILIKE @text)",
|
||||||
[ "@text", like search.Text ]
|
[ "@text", like search.Text ]
|
||||||
]
|
]
|
||||||
|
let vizSql =
|
||||||
|
if isPublic then
|
||||||
|
sprintf "IN ('%s', '%s')" (ProfileVisibility.toString Public) (ProfileVisibility.toString Anonymous)
|
||||||
|
else sprintf "<> '%s'" (ProfileVisibility.toString Hidden)
|
||||||
let! results =
|
let! results =
|
||||||
dataSource ()
|
dataSource ()
|
||||||
|> Sql.query $"
|
|> Sql.query $"
|
||||||
SELECT p.*, c.data AS cit_data
|
SELECT p.*, c.data AS cit_data, o.data AS cont_data
|
||||||
FROM {Table.Profile} p
|
FROM {Table.Profile} p
|
||||||
INNER JOIN {Table.Citizen} c ON c.id = p.id
|
INNER JOIN {Table.Citizen} c ON c.id = p.id
|
||||||
|
INNER JOIN {Table.Continent} o ON o.id = p.data ->> 'continentId'
|
||||||
WHERE p.data ->> 'isLegacy' = 'false'
|
WHERE p.data ->> 'isLegacy' = 'false'
|
||||||
AND p.data ->> 'visibility' <> '{ProfileVisibility.toString Hidden}'
|
AND p.data ->> 'visibility' {vizSql}
|
||||||
{searchSql searches}"
|
{searchSql searches}"
|
||||||
|> Sql.parameters (searches |> List.collect snd)
|
|> Sql.parameters (searches |> List.collect snd)
|
||||||
|> Sql.executeAsync (fun row ->
|
|> Sql.executeAsync toProfileForView
|
||||||
let profile = toDocument<Profile> row
|
return results |> List.sortBy (fun pfv -> (Citizen.name pfv.Citizen).ToLowerInvariant ())
|
||||||
let citizen = toDocumentFrom<Citizen> "cit_data" row
|
|
||||||
{ CitizenId = profile.Id
|
|
||||||
DisplayName = Citizen.name citizen
|
|
||||||
SeekingEmployment = profile.IsSeekingEmployment
|
|
||||||
RemoteWork = profile.IsRemote
|
|
||||||
FullTime = profile.IsFullTime
|
|
||||||
LastUpdatedOn = profile.LastUpdatedOn
|
|
||||||
})
|
|
||||||
return results |> List.sortBy (fun psr -> psr.DisplayName.ToLowerInvariant ())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search profiles (public)
|
|
||||||
let publicSearch (search : PublicSearchForm) =
|
|
||||||
let searches = [
|
|
||||||
if search.ContinentId <> "" then
|
|
||||||
"p.data ->> 'continentId' = @continentId", [ "@continentId", Sql.string search.ContinentId ]
|
|
||||||
if search.Region <> "" then
|
|
||||||
"p.data ->> 'region' ILIKE @region", [ "@region", like search.Region ]
|
|
||||||
if search.RemoteWork <> "" then
|
|
||||||
"p.data ->> 'isRemote' = @remote", [ "@remote", jsonBool (search.RemoteWork = "yes") ]
|
|
||||||
if search.Skill <> "" then
|
|
||||||
"EXISTS (
|
|
||||||
SELECT 1 FROM jsonb_array_elements(p.data['skills']) x(elt)
|
|
||||||
WHERE x ->> 'description' ILIKE @description)",
|
|
||||||
[ "@description", like search.Skill ]
|
|
||||||
]
|
|
||||||
dataSource ()
|
|
||||||
|> Sql.query $"
|
|
||||||
SELECT p.*, c.data AS cont_data
|
|
||||||
FROM {Table.Profile} p
|
|
||||||
INNER JOIN {Table.Continent} c ON c.id = p.data ->> 'continentId'
|
|
||||||
WHERE p.data ->> 'isPubliclySearchable' = 'true'
|
|
||||||
AND p.data ->> 'isLegacy' = 'false'
|
|
||||||
{searchSql searches}"
|
|
||||||
|> Sql.parameters (searches |> List.collect snd)
|
|
||||||
|> Sql.executeAsync (fun row ->
|
|
||||||
let profile = toDocument<Profile> row
|
|
||||||
let continent = toDocumentFrom<Continent> "cont_data" row
|
|
||||||
{ Continent = continent.Name
|
|
||||||
Region = profile.Region
|
|
||||||
RemoteWork = profile.IsRemote
|
|
||||||
Skills = profile.Skills
|
|
||||||
|> List.map (fun s ->
|
|
||||||
let notes = match s.Notes with Some n -> $" ({n})" | None -> ""
|
|
||||||
$"{s.Description}{notes}")
|
|
||||||
})
|
|
||||||
|
|
|
@ -71,35 +71,20 @@ let saveGeneralInfo : HttpHandler = requireUser >=> fun next ctx -> task {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /profile/search
|
// GET: /profile/search
|
||||||
let search : HttpHandler = requireUser >=> fun next ctx -> task {
|
let search : HttpHandler = fun next ctx -> task {
|
||||||
let! continents = Common.Data.Continents.all ()
|
let! continents = Common.Data.Continents.all ()
|
||||||
let form =
|
let form =
|
||||||
match ctx.TryBindQueryString<ProfileSearchForm> () with
|
match ctx.TryBindQueryString<ProfileSearchForm> () with
|
||||||
| Ok f -> f
|
| Ok f -> f
|
||||||
| Error _ -> { ContinentId = ""; RemoteWork = ""; Text = "" }
|
| Error _ -> { ContinentId = ""; RemoteWork = ""; Text = "" }
|
||||||
|
let isPublic = tryUser ctx |> Option.isNone
|
||||||
let! results = task {
|
let! results = task {
|
||||||
if string ctx.Request.Query["searched"] = "true" then
|
if string ctx.Request.Query["searched"] = "true" then
|
||||||
let! it = Data.search form
|
let! it = Data.search form isPublic
|
||||||
return Some it
|
return Some it
|
||||||
else return None
|
else return None
|
||||||
}
|
}
|
||||||
return! Views.search form continents (timeZone ctx) results |> render "Profile Search" next ctx
|
return! Views.search form continents (timeZone ctx) results isPublic |> render "Profile Search" next ctx
|
||||||
}
|
|
||||||
|
|
||||||
// GET: /profile/seeking
|
|
||||||
let seeking : HttpHandler = fun next ctx -> task {
|
|
||||||
let! continents = Common.Data.Continents.all ()
|
|
||||||
let form =
|
|
||||||
match ctx.TryBindQueryString<PublicSearchForm> () with
|
|
||||||
| Ok f -> f
|
|
||||||
| Error _ -> { ContinentId = ""; Region = ""; RemoteWork = ""; Skill = "" }
|
|
||||||
let! results = task {
|
|
||||||
if string ctx.Request.Query["searched"] = "true" then
|
|
||||||
let! it = Data.publicSearch form
|
|
||||||
return Some it
|
|
||||||
else return None
|
|
||||||
}
|
|
||||||
return! Views.publicSearch form continents results |> render "Profile Search" next ctx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /profile/edit/skills
|
// GET: /profile/edit/skills
|
||||||
|
@ -262,7 +247,6 @@ let endpoints =
|
||||||
route "/edit/skills" skills
|
route "/edit/skills" skills
|
||||||
route "/edit/skills/list" skillList
|
route "/edit/skills/list" skillList
|
||||||
route "/search" search
|
route "/search" search
|
||||||
route "/seeking" seeking
|
|
||||||
]
|
]
|
||||||
POST [
|
POST [
|
||||||
route "/delete" delete
|
route "/delete" delete
|
||||||
|
|
|
@ -353,96 +353,8 @@ let editHistory (history : EmploymentHistory list) idx csrf =
|
||||||
|
|
||||||
// ~~~ PROFILE SEARCH ~~~ //
|
// ~~~ PROFILE SEARCH ~~~ //
|
||||||
|
|
||||||
/// The public search page
|
/// The search form
|
||||||
let publicSearch (m : PublicSearchForm) continents (results : PublicSearchResult list option) =
|
let private searchForm (m : ProfileSearchForm) continents =
|
||||||
pageWithTitle "People Seeking Work" [
|
|
||||||
if Option.isNone results then
|
|
||||||
p [] [
|
|
||||||
txt "Enter one or more criteria to filter results, or just click “Search” to list all "
|
|
||||||
txt "publicly searchable profiles."
|
|
||||||
]
|
|
||||||
collapsePanel "Search Criteria" [
|
|
||||||
form [ _class "container"; _method "GET"; _action "/profile/seeking" ] [
|
|
||||||
input [ _type "hidden"; _name "searched"; _value "true" ]
|
|
||||||
div [ _class "row" ] [
|
|
||||||
div [ _class "col-12 col-sm-6 col-md-4 col-lg-3" ] [
|
|
||||||
continentList [] "ContinentId" continents (Some "Any") m.ContinentId false
|
|
||||||
]
|
|
||||||
div [ _class "col-12 col-sm-6 col-md-4 col-lg-3" ] [
|
|
||||||
textBox [ _maxlength "1000" ] (nameof m.Region) m.Region "Region" false
|
|
||||||
div [ _class "form-text" ] [ txt "(free-form text)" ]
|
|
||||||
]
|
|
||||||
div [ _class "col-12 col-sm-6 col-offset-md-2 col-lg-3 col-offset-lg-0" ] [
|
|
||||||
label [ _class "jjj-label" ] [ txt "Seeking Remote Work?" ]; br []
|
|
||||||
div [ _class "form-check form-check-inline" ] [
|
|
||||||
input [ _type "radio"; _id "remoteNull"; _name (nameof m.RemoteWork); _value ""
|
|
||||||
_class "form-check-input"; if m.RemoteWork = "" then _checked ]
|
|
||||||
label [ _class "form-check-label"; _for "remoteNull" ] [ txt "No Selection" ]
|
|
||||||
]
|
|
||||||
div [ _class "form-check form-check-inline" ] [
|
|
||||||
input [ _type "radio"; _id "remoteYes"; _name (nameof m.RemoteWork); _value "yes"
|
|
||||||
_class "form-check-input"; if m.RemoteWork = "yes" then _checked ]
|
|
||||||
label [ _class "form-check-label"; _for "remoteYes" ] [ txt "Yes" ]
|
|
||||||
]
|
|
||||||
div [ _class "form-check form-check-inline" ] [
|
|
||||||
input [ _type "radio"; _id "remoteNo"; _name (nameof m.RemoteWork); _value "no"
|
|
||||||
_class "form-check-input"; if m.RemoteWork = "no" then _checked ]
|
|
||||||
label [ _class "form-check-label"; _for "remoteNo" ] [ txt "No" ]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
div [ _class "col-12 col-sm-6 col-lg-3" ] [
|
|
||||||
textBox [ _maxlength "1000" ] (nameof m.Skill) m.Skill "Skill" false
|
|
||||||
div [ _class "form-text" ] [ txt "(free-form text)" ]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
div [ _class "row" ] [
|
|
||||||
div [ _class "col" ] [
|
|
||||||
br []
|
|
||||||
button [ _type "submit"; _class "btn btn-outline-primary" ] [ txt "Search" ]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
match results with
|
|
||||||
| Some r when List.isEmpty r -> p [ _class "pt-3" ] [ txt "No results found for the specified criteria" ]
|
|
||||||
| Some r ->
|
|
||||||
p [ _class "py-3" ] [
|
|
||||||
txt "These profiles match your search criteria. To learn more about these people, join the merry band "
|
|
||||||
txt "of human resources in the "
|
|
||||||
a [ _href "https://noagendashow.net"; _target "_blank"; _rel "noopener" ] [ txt "No Agenda" ]
|
|
||||||
txt " tribe!"
|
|
||||||
]
|
|
||||||
table [ _class "table table-sm table-hover" ] [
|
|
||||||
thead [] [
|
|
||||||
tr [] [
|
|
||||||
th [ _scope "col" ] [ txt "Continent" ]
|
|
||||||
th [ _scope "col"; _class "text-center" ] [ txt "Region" ]
|
|
||||||
th [ _scope "col"; _class "text-center" ] [ txt "Remote?" ]
|
|
||||||
th [ _scope "col"; _class "text-center" ] [ txt "Skills" ]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
r |> List.map (fun profile ->
|
|
||||||
tr [] [
|
|
||||||
td [] [ str profile.Continent ]
|
|
||||||
td [] [ str profile.Region ]
|
|
||||||
td [ _class "text-center" ] [ txt (yesOrNo profile.RemoteWork) ]
|
|
||||||
profile.Skills
|
|
||||||
|> List.collect (fun skill -> [ str skill; br [] ])
|
|
||||||
|> td []
|
|
||||||
])
|
|
||||||
|> tbody []
|
|
||||||
]
|
|
||||||
| None -> ()
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
/// Logged-on search page
|
|
||||||
let search (m : ProfileSearchForm) continents tz (results : ProfileSearchResult list option) =
|
|
||||||
[ if Option.isNone results then
|
|
||||||
p [] [
|
|
||||||
txt "Enter one or more criteria to filter results, or just click “Search” to list all "
|
|
||||||
txt "profiles."
|
|
||||||
]
|
|
||||||
collapsePanel "Search Criteria" [
|
collapsePanel "Search Criteria" [
|
||||||
form [ _class "container"; _method "GET"; _action "/profile/search" ] [
|
form [ _class "container"; _method "GET"; _action "/profile/search" ] [
|
||||||
input [ _type "hidden"; _name "searched"; _value "true" ]
|
input [ _type "hidden"; _name "searched"; _value "true" ]
|
||||||
|
@ -483,9 +395,51 @@ let search (m : ProfileSearchForm) continents tz (results : ProfileSearchResult
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
match results with
|
|
||||||
| Some r when List.isEmpty r -> p [ _class "pt-3" ] [ txt "No results found for the specified criteria" ]
|
/// Display search results for public users
|
||||||
| Some r ->
|
let private publicResults (results : ProfileForView list) =
|
||||||
|
[ p [ _class "py-3" ] [
|
||||||
|
txt "These profiles match your search criteria. To learn more about these people, join the merry band "
|
||||||
|
txt "of human resources in the "
|
||||||
|
a [ _href "https://noagendashow.net"; _target "_blank"; _rel "noopener" ] [ txt "No Agenda" ]
|
||||||
|
txt " tribe!"
|
||||||
|
]
|
||||||
|
table [ _class "table table-sm table-hover" ] [
|
||||||
|
thead [] [
|
||||||
|
tr [] [
|
||||||
|
th [ _scope "col" ] [ txt "Profile" ]
|
||||||
|
th [ _scope "col" ] [ txt "Continent / Region" ]
|
||||||
|
th [ _scope "col"; _class "text-center" ] [ txt "Remote?" ]
|
||||||
|
th [ _scope "col"; _class "text-center" ] [ txt "Skills" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
results
|
||||||
|
|> List.map (fun it ->
|
||||||
|
tr [] [
|
||||||
|
td [] [
|
||||||
|
match it.Profile.Visibility with
|
||||||
|
| Public -> a [ _href $"/profile/{CitizenId.toString it.Profile.Id}/view" ] [ txt "View" ]
|
||||||
|
| _ -> txt " "
|
||||||
|
]
|
||||||
|
td [] [ txt $"{it.Continent.Name} / "; str it.Profile.Region ]
|
||||||
|
td [ _class "text-center" ] [ txt (yesOrNo it.Profile.IsRemote) ]
|
||||||
|
match it.Profile.Visibility with
|
||||||
|
| Public -> td [ _class "text-muted fst-italic" ] [ txt "See Profile" ]
|
||||||
|
| _ when List.isEmpty it.Profile.Skills ->
|
||||||
|
td [ _class "text-muted fst-italic" ] [ txt "None Listed" ]
|
||||||
|
| _ ->
|
||||||
|
it.Profile.Skills
|
||||||
|
|> List.collect (fun skill ->
|
||||||
|
let notes = match skill.Notes with Some n -> $" ({n})" | None -> ""
|
||||||
|
[ str $"{skill.Description}{notes}"; br [] ])
|
||||||
|
|> td []
|
||||||
|
])
|
||||||
|
|> tbody []
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
/// Display search results for logged-on users
|
||||||
|
let private privateResults (results : ProfileForView list) tz =
|
||||||
// Bootstrap utility classes to only show at medium or above
|
// Bootstrap utility classes to only show at medium or above
|
||||||
let isWide = "d-none d-md-table-cell"
|
let isWide = "d-none d-md-table-cell"
|
||||||
table [ _class "table table-sm table-hover pt-3" ] [
|
table [ _class "table table-sm table-hover pt-3" ] [
|
||||||
|
@ -499,17 +453,31 @@ let search (m : ProfileSearchForm) continents tz (results : ProfileSearchResult
|
||||||
th [ _scope "col"; _class isWide ] [ txt "Last Updated" ]
|
th [ _scope "col"; _class isWide ] [ txt "Last Updated" ]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
r |> List.map (fun profile ->
|
results
|
||||||
|
|> List.map (fun it ->
|
||||||
tr [] [
|
tr [] [
|
||||||
td [] [ a [ _href $"/profile/{CitizenId.toString profile.CitizenId}/view" ] [ txt "View" ] ]
|
td [] [ a [ _href $"/profile/{CitizenId.toString it.Profile.Id}/view" ] [ txt "View" ] ]
|
||||||
td [ if profile.SeekingEmployment then _class "fw-bold" ] [ str profile.DisplayName ]
|
td [ if it.Profile.IsSeekingEmployment then _class "fw-bold" ] [ str (Citizen.name it.Citizen) ]
|
||||||
td [ _class $"{isWide} text-center" ] [ txt (yesOrNo profile.SeekingEmployment) ]
|
td [ _class $"{isWide} text-center" ] [ txt (yesOrNo it.Profile.IsSeekingEmployment) ]
|
||||||
td [ _class "text-center" ] [ txt (yesOrNo profile.RemoteWork) ]
|
td [ _class "text-center" ] [ txt (yesOrNo it.Profile.IsRemote) ]
|
||||||
td [ _class $"{isWide} text-center" ] [ txt (yesOrNo profile.FullTime) ]
|
td [ _class $"{isWide} text-center" ] [ txt (yesOrNo it.Profile.IsFullTime) ]
|
||||||
td [ _class isWide ] [ str (fullDate profile.LastUpdatedOn tz) ]
|
td [ _class isWide ] [ str (fullDate it.Profile.LastUpdatedOn tz) ]
|
||||||
])
|
])
|
||||||
|> tbody []
|
|> tbody []
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/// Logged-on search page
|
||||||
|
let search m continents tz (results : ProfileForView list option) isPublic =
|
||||||
|
[ if Option.isNone results then
|
||||||
|
p [] [
|
||||||
|
txt "Enter one or more criteria to filter results, or just click “Search” to list all "
|
||||||
|
if isPublic then txt "publicly searchable or viewable "
|
||||||
|
txt "profiles."
|
||||||
|
]
|
||||||
|
searchForm m continents
|
||||||
|
match results with
|
||||||
|
| Some r when List.isEmpty r -> p [ _class "pt-3" ] [ txt "No results found for the specified criteria" ]
|
||||||
|
| Some r -> if isPublic then yield! publicResults r else privateResults r tz
|
||||||
| None -> ()
|
| None -> ()
|
||||||
]
|
]
|
||||||
|> pageWithTitle "Search Profiles"
|
|> pageWithTitle "Search Profiles"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user