Help wanted #23

Merged
danieljsummers merged 20 commits from help-wanted into main 2021-09-01 01:16:43 +00:00
13 changed files with 250 additions and 244 deletions
Showing only changes of commit bda2b38eb4 - Show all commits

View File

@ -176,22 +176,42 @@ module Startup =
/// Determine if a record type (not nullable) is null
let toOption x = match x |> box |> isNull with true -> None | false -> Some x
/// A retry policy where we will reconnect to RethinkDB if it has gone away
let withReconn (conn : IConnection) =
[<AutoOpen>]
module private Reconnect =
open System.Threading.Tasks
/// Execute a query with a retry policy that will reconnect to RethinkDB if it has gone away
let withReconn (conn : IConnection) (f : IConnection -> Task<'T>) =
Policy
.Handle<ReqlDriverError>()
.RetryAsync(System.Action<exn, int>(fun ex _ ->
.RetryAsync(System.Action<exn, int> (fun ex _ ->
printf "Encountered RethinkDB exception: %s" ex.Message
match ex.Message.Contains "socket" with
| true ->
printf "Reconnecting to RethinkDB"
(conn :?> Connection).Reconnect()
(conn :?> Connection).Reconnect false
| false -> ()))
.ExecuteAsync(fun () -> f conn)
/// Execute a query that returns one or none item, using the reconnect logic
let withReconnOption (conn : IConnection) (f : IConnection -> Task<'T>) =
fun c -> task {
let! it = f c
return toOption it
}
|> withReconn conn
/// Execute a query that does not return a result, using the above reconnect logic
let withReconnIgnore (conn : IConnection) (f : IConnection -> Task<'T>) =
fun c -> task {
let! _ = f c
()
}
|> withReconn conn
/// Sanitize user input, and create a "contains" pattern for use with RethinkDB queries
let regexContains (it : string) =
System.Text.RegularExpressions.Regex.Escape it
|> sprintf "(?i)%s"
let regexContains = System.Text.RegularExpressions.Regex.Escape >> sprintf "(?i)%s"
open JobsJobsJobs.Domain
open JobsJobsJobs.Domain.SharedTypes
@ -202,127 +222,120 @@ open RethinkDb.Driver.Ast
module Profile =
let count conn =
withReconn(conn).ExecuteAsync(fun () ->
r.Table(Table.Profile)
.Count()
.RunResultAsync<int64> conn)
.RunResultAsync<int64>
|> withReconn conn
/// Find a profile by citizen ID
let findById (citizenId : CitizenId) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! profile =
r.Table(Table.Profile)
.Get(citizenId)
.RunResultAsync<Profile> conn
return toOption profile
})
.RunResultAsync<Profile>
|> withReconnOption conn
/// Insert or update a profile
let save (profile : Profile) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! _ =
r.Table(Table.Profile)
.Get(profile.id)
.Replace(profile)
.RunWriteAsync conn
()
})
.RunWriteAsync
|> withReconnIgnore conn
/// Delete a citizen's profile
let delete (citizenId : CitizenId) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! _ =
r.Table(Table.Profile)
.Get(citizenId)
.Delete()
.RunWriteAsync conn
()
})
.RunWriteAsync
|> withReconnIgnore conn
/// Search profiles (logged-on users)
let search (srch : ProfileSearch) conn =
withReconn(conn).ExecuteAsync(fun () ->
fun c ->
(seq {
match srch.continentId with
| Some conId ->
yield (fun (q : ReqlExpr) ->
q.Filter(r.HashMap(nameof srch.continentId, ContinentId.ofString conId)) :> ReqlExpr)
q.Filter (r.HashMap (nameof srch.continentId, ContinentId.ofString conId)) :> ReqlExpr)
| None -> ()
match srch.remoteWork with
| "" -> ()
| _ -> yield (fun q -> q.Filter(r.HashMap(nameof srch.remoteWork, srch.remoteWork = "yes")) :> ReqlExpr)
| _ -> yield (fun q -> q.Filter (r.HashMap (nameof srch.remoteWork, srch.remoteWork = "yes")) :> ReqlExpr)
match srch.skill with
| Some skl ->
yield (fun q -> q.Filter(ReqlFunction1(fun it ->
upcast it.G("skills").Contains(ReqlFunction1(fun s ->
upcast s.G("description").Match(regexContains skl))))) :> ReqlExpr)
yield (fun q -> q.Filter (ReqlFunction1(fun it ->
upcast it.G("skills").Contains (ReqlFunction1(fun s ->
upcast s.G("description").Match (regexContains skl))))) :> ReqlExpr)
| None -> ()
match srch.bioExperience with
| Some text ->
let txt = regexContains text
yield (fun q -> q.Filter(ReqlFunction1(fun it ->
upcast it.G("biography").Match(txt).Or(it.G("experience").Match(txt)))) :> ReqlExpr)
yield (fun q -> q.Filter (ReqlFunction1(fun it ->
upcast it.G("biography").Match(txt).Or (it.G("experience").Match txt))) :> ReqlExpr)
| None -> ()
}
|> Seq.toList
|> List.fold
(fun q f -> f q)
(r.Table(Table.Profile)
.EqJoin("id", r.Table(Table.Citizen))
.Without(r.HashMap("right", "id"))
.Zip() :> ReqlExpr))
.Merge(ReqlFunction1(fun it ->
.EqJoin("id", r.Table Table.Citizen)
.Without(r.HashMap ("right", "id"))
.Zip () :> ReqlExpr))
.Merge(ReqlFunction1 (fun it ->
upcast r
.HashMap("displayName",
r.Branch(it.G("realName" ).Default_("").Ne(""), it.G("realName"),
it.G("displayName").Default_("").Ne(""), it.G("displayName"),
it.G("naUser")))
.With("citizenId", it.G("id"))))
r.Branch (it.G("realName" ).Default_("").Ne "", it.G "realName",
it.G("displayName").Default_("").Ne "", it.G "displayName",
it.G "naUser"))
.With ("citizenId", it.G "id")))
.Pluck("citizenId", "displayName", "seekingEmployment", "remoteWork", "fullTime", "lastUpdatedOn")
.RunResultAsync<ProfileSearchResult list> conn)
.OrderBy(ReqlFunction1 (fun it -> upcast it.G("displayName").Downcase ()))
.RunResultAsync<ProfileSearchResult list> c
|> withReconn conn
// Search profiles (public)
let publicSearch (srch : PublicSearch) conn =
withReconn(conn).ExecuteAsync(fun () ->
fun c ->
(seq {
match srch.continentId with
| Some conId ->
yield (fun (q : ReqlExpr) ->
q.Filter(r.HashMap(nameof srch.continentId, ContinentId.ofString conId)) :> ReqlExpr)
q.Filter (r.HashMap (nameof srch.continentId, ContinentId.ofString conId)) :> ReqlExpr)
| None -> ()
match srch.region with
| Some reg ->
yield (fun q ->
q.Filter(ReqlFunction1(fun it -> upcast it.G("region").Match(regexContains reg))) :> ReqlExpr)
q.Filter (ReqlFunction1 (fun it -> upcast it.G("region").Match (regexContains reg))) :> ReqlExpr)
| None -> ()
match srch.remoteWork with
| "" -> ()
| _ -> yield (fun q -> q.Filter(r.HashMap(nameof srch.remoteWork, srch.remoteWork = "yes")) :> ReqlExpr)
| _ -> yield (fun q -> q.Filter (r.HashMap (nameof srch.remoteWork, srch.remoteWork = "yes")) :> ReqlExpr)
match srch.skill with
| Some skl ->
yield (fun q -> q.Filter(ReqlFunction1(fun it ->
upcast it.G("skills").Contains(ReqlFunction1(fun s ->
upcast s.G("description").Match(regexContains skl))))) :> ReqlExpr)
yield (fun q -> q.Filter (ReqlFunction1 (fun it ->
upcast it.G("skills").Contains (ReqlFunction1(fun s ->
upcast s.G("description").Match (regexContains skl))))) :> ReqlExpr)
| None -> ()
}
|> Seq.toList
|> List.fold
(fun q f -> f q)
(r.Table(Table.Profile)
.EqJoin("continentId", r.Table(Table.Continent))
.Without(r.HashMap("right", "id"))
.EqJoin("continentId", r.Table Table.Continent)
.Without(r.HashMap ("right", "id"))
.Zip()
.Filter(r.HashMap("isPublic", true)) :> ReqlExpr))
.Merge(ReqlFunction1(fun it ->
.Filter(r.HashMap ("isPublic", true)) :> ReqlExpr))
.Merge(ReqlFunction1 (fun it ->
upcast r
.HashMap("skills",
it.G("skills").Map(ReqlFunction1(fun skill ->
upcast r.Branch(skill.G("notes").Default_("").Eq(""), skill.G("description"),
skill.G("description").Add(" (").Add(skill.G("notes")).Add(")")))))
.With("continent", it.G("name"))))
it.G("skills").Map (ReqlFunction1 (fun skill ->
upcast r.Branch(skill.G("notes").Default_("").Eq "", skill.G "description",
skill.G("description").Add(" (").Add(skill.G("notes")).Add ")"))))
.With("continent", it.G "name")))
.Pluck("continent", "region", "skills", "remoteWork")
.RunResultAsync<PublicSearchResult list> conn)
.RunResultAsync<PublicSearchResult list> c
|> withReconn conn
/// Citizen data access functions
[<RequireQualifiedAccess>]
@ -330,78 +343,64 @@ module Citizen =
/// Find a citizen by their ID
let findById (citizenId : CitizenId) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! citizen =
r.Table(Table.Citizen)
.Get(citizenId)
.RunResultAsync<Citizen> conn
return toOption citizen
})
.RunResultAsync<Citizen>
|> withReconnOption conn
/// Find a citizen by their No Agenda Social username
let findByNaUser (naUser : string) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! citizen =
r.Table(Table.Citizen)
.GetAll(naUser).OptArg("index", "naUser").Nth(0)
.RunResultAsync<Citizen> conn
return toOption citizen
})
.RunResultAsync<Citizen>
|> withReconnOption conn
/// Add a citizen
let add (citizen : Citizen) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! _ =
r.Table(Table.Citizen)
.Insert(citizen)
.RunWriteAsync conn
()
})
.RunWriteAsync
|> withReconnIgnore conn
/// Update the display name and last seen on date for a citizen
let logOnUpdate (citizen : Citizen) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! _ =
r.Table(Table.Citizen)
.Get(citizen.id)
.Update(r.HashMap(nameof citizen.displayName, citizen.displayName)
.With(nameof citizen.lastSeenOn, citizen.lastSeenOn))
.RunWriteAsync conn
()
})
.Update(r.HashMap( nameof citizen.displayName, citizen.displayName)
.With (nameof citizen.lastSeenOn, citizen.lastSeenOn))
.RunWriteAsync
|> withReconnIgnore conn
/// Delete a citizen
let delete citizenId conn =
withReconn(conn).ExecuteAsync(fun () -> task {
do! Profile.delete citizenId conn
fun c -> task {
do! Profile.delete citizenId c
let! _ =
r.Table(Table.Success)
.GetAll(citizenId).OptArg("index", "citizenId")
.Delete()
.RunWriteAsync conn
.RunWriteAsync c
let! _ =
r.Table(Table.Listing)
.GetAll(citizenId).OptArg("index", "citizenId")
.Delete()
.RunWriteAsync conn
.RunWriteAsync c
let! _ =
r.Table(Table.Citizen)
.Get(citizenId)
.Delete()
.RunWriteAsync conn
.RunWriteAsync c
()
})
}
|> withReconnIgnore conn
/// Update a citizen's real name
let realNameUpdate (citizenId : CitizenId) (realName : string option) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! _ =
r.Table(Table.Citizen)
.Get(citizenId)
.Update(r.HashMap(nameof realName, realName))
.RunWriteAsync conn
()
})
.Update(r.HashMap (nameof realName, realName))
.RunWriteAsync
|> withReconnIgnore conn
/// Continent data access functions
@ -410,19 +409,16 @@ module Continent =
/// Get all continents
let all conn =
withReconn(conn).ExecuteAsync(fun () ->
r.Table(Table.Continent)
.RunResultAsync<Continent list> conn)
.RunResultAsync<Continent list>
|> withReconn conn
/// Get a continent by its ID
let findById (contId : ContinentId) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! continent =
r.Table(Table.Continent)
.Get(contId)
.RunResultAsync<Continent> conn
return toOption continent
})
.RunResultAsync<Continent>
|> withReconnOption conn
/// Job listing data access functions
@ -433,101 +429,91 @@ module Listing =
/// Find all job listings posted by the given citizen
let findByCitizen (citizenId : CitizenId) conn =
withReconn(conn).ExecuteAsync(fun () ->
r.Table(Table.Listing)
.GetAll(citizenId).OptArg("index", nameof citizenId)
.EqJoin("continentId", r.Table(Table.Continent))
.Map(ReqlFunction1(fun it -> upcast r.HashMap("listing", it.G("left")).With("continent", it.G("right"))))
.RunResultAsync<ListingForView list> conn)
.EqJoin("continentId", r.Table Table.Continent)
.Map(ReqlFunction1 (fun it -> upcast r.HashMap("listing", it.G "left").With ("continent", it.G "right")))
.RunResultAsync<ListingForView list>
|> withReconn conn
/// Find a listing by its ID
let findById (listingId : ListingId) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! listing =
r.Table(Table.Listing)
.Get(listingId)
.RunResultAsync<Listing> conn
return toOption listing
})
.RunResultAsync<Listing>
|> withReconnOption conn
/// Find a listing by its ID for viewing (includes continent information)
let findByIdForView (listingId : ListingId) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
fun c -> task {
let! listing =
r.Table(Table.Listing)
.Filter(r.HashMap("id", listingId))
.EqJoin("continentId", r.Table(Table.Continent))
.Map(ReqlFunction1(fun it -> upcast r.HashMap("listing", it.G("left")).With("continent", it.G("right"))))
.RunResultAsync<ListingForView list> conn
.Filter(r.HashMap ("id", listingId))
.EqJoin("continentId", r.Table Table.Continent)
.Map(ReqlFunction1 (fun it -> upcast r.HashMap("listing", it.G "left").With ("continent", it.G "right")))
.RunResultAsync<ListingForView list> c
return List.tryHead listing
})
}
|> withReconn conn
/// Add a listing
let add (listing : Listing) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! _ =
r.Table(Table.Listing)
.Insert(listing)
.RunWriteAsync conn
()
})
.RunWriteAsync
|> withReconnIgnore conn
/// Update a listing
let update (listing : Listing) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! _ =
r.Table(Table.Listing)
.Get(listing.id)
.Replace(listing)
.RunWriteAsync conn
()
})
.RunWriteAsync
|> withReconnIgnore conn
/// Expire a listing
let expire (listingId : ListingId) (fromHere : bool) (now : Instant) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! _ =
r.Table(Table.Listing)
.Get(listingId)
.Update(r.HashMap("isExpired", true).With("wasFilledHere", fromHere).With("updatedOn", now))
.RunWriteAsync conn
()
})
.Update(r.HashMap("isExpired", true).With("wasFilledHere", fromHere).With ("updatedOn", now))
.RunWriteAsync
|> withReconnIgnore conn
/// Search job listings
let search (srch : ListingSearch) conn =
withReconn(conn).ExecuteAsync(fun () ->
fun c ->
(seq {
match srch.continentId with
| Some conId ->
yield (fun (q : ReqlExpr) ->
q.Filter(r.HashMap(nameof srch.continentId, ContinentId.ofString conId)) :> ReqlExpr)
q.Filter (r.HashMap (nameof srch.continentId, ContinentId.ofString conId)) :> ReqlExpr)
| None -> ()
match srch.region with
| Some rgn ->
yield (fun q ->
q.Filter(ReqlFunction1(fun it ->
upcast it.G(nameof srch.region).Match(regexContains rgn))) :> ReqlExpr)
q.Filter (ReqlFunction1 (fun it ->
upcast it.G(nameof srch.region).Match (regexContains rgn))) :> ReqlExpr)
| None -> ()
match srch.remoteWork with
| "" -> ()
| _ ->
yield (fun q -> q.Filter(r.HashMap(nameof srch.remoteWork, srch.remoteWork = "yes")) :> ReqlExpr)
yield (fun q -> q.Filter (r.HashMap (nameof srch.remoteWork, srch.remoteWork = "yes")) :> ReqlExpr)
match srch.text with
| Some text ->
yield (fun q ->
q.Filter(ReqlFunction1(fun it ->
upcast it.G(nameof srch.text).Match(regexContains text))) :> ReqlExpr)
q.Filter (ReqlFunction1 (fun it ->
upcast it.G(nameof srch.text).Match (regexContains text))) :> ReqlExpr)
| None -> ()
}
|> Seq.toList
|> List.fold
(fun q f -> f q)
(r.Table(Table.Listing)
.GetAll(false).OptArg("index", "isExpired") :> ReqlExpr))
.EqJoin("continentId", r.Table(Table.Continent))
.Map(ReqlFunction1(fun it -> upcast r.HashMap("listing", it.G("left")).With("continent", it.G("right"))))
.RunResultAsync<ListingForView list> conn)
.GetAll(false).OptArg ("index", "isExpired") :> ReqlExpr))
.EqJoin("continentId", r.Table Table.Continent)
.Map(ReqlFunction1 (fun it -> upcast r.HashMap("listing", it.G "left").With ("continent", it.G "right")))
.RunResultAsync<ListingForView list> c
|> withReconn conn
/// Success story data access functions
@ -536,39 +522,33 @@ module Success =
/// Find a success report by its ID
let findById (successId : SuccessId) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! success =
r.Table(Table.Success)
.Get(successId)
.RunResultAsync<Success> conn
return toOption success
})
.RunResultAsync<Success>
|> withReconnOption conn
/// Insert or update a success story
let save (success : Success) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! _ =
r.Table(Table.Success)
.Get(success.id)
.Replace(success)
.RunWriteAsync conn
()
})
.RunWriteAsync
|> withReconnIgnore conn
// Retrieve all success stories
let all conn =
withReconn(conn).ExecuteAsync(fun () ->
r.Table(Table.Success)
.EqJoin("citizenId", r.Table(Table.Citizen))
.Without(r.HashMap("right", "id"))
.EqJoin("citizenId", r.Table Table.Citizen)
.Without(r.HashMap ("right", "id"))
.Zip()
.Merge(ReqlFunction1(fun it ->
.Merge(ReqlFunction1 (fun it ->
upcast r
.HashMap("citizenName",
r.Branch(it.G("realName" ).Default_("").Ne(""), it.G("realName"),
it.G("displayName").Default_("").Ne(""), it.G("displayName"),
it.G("naUser")))
.With("hasStory", it.G("story").Default_("").Gt(""))))
r.Branch(it.G("realName" ).Default_("").Ne "", it.G "realName",
it.G("displayName").Default_("").Ne "", it.G "displayName",
it.G "naUser"))
.With ("hasStory", it.G("story").Default_("").Gt "")))
.Pluck("id", "citizenId", "citizenName", "recordedOn", "fromHere", "hasStory")
.OrderBy(r.Desc("recordedOn"))
.RunResultAsync<StoryEntry list> conn)
.OrderBy(r.Desc "recordedOn")
.RunResultAsync<StoryEntry list>
|> withReconn conn

View File

@ -4,12 +4,12 @@
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build --mode development",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"apiserve": "vue-cli-service build --mode development && cd ../Api && dotnet run -c Debug"
"apiserve": "vue-cli-service build && cd ../Api && dotnet run -c Debug"
},
"dependencies": {
"@mdi/font": "5.9.55",
"@mdi/js": "^5.9.55",
"@vuelidate/core": "^2.0.0-alpha.24",
"@vuelidate/validators": "^2.0.0-alpha.21",
"bootstrap": "^5.1.0",

View File

@ -13,7 +13,6 @@
import { defineComponent } from "vue"
import "bootstrap/dist/css/bootstrap.min.css"
import "@mdi/font/css/materialdesignicons.css"
import { Citizen } from "./api"
import AppFooter from "./components/layout/AppFooter.vue"

View File

@ -1,12 +1,16 @@
<template lang="pug">
span(:class="iconClass")
svg(viewbox="0 0 24 24"): path(:fill="color || 'white'" :d="icon")
</template>
<script setup lang="ts">
const props = defineProps<{
color?: string
icon: string
}>()
/** The CSS class to display the requested icon */
const iconClass = `mdi mdi-${props.icon}`
</script>
<style lang="sass" scoped>
svg
width: 24px
height: 24px
</style>

View File

@ -4,25 +4,37 @@ aside.collapse.show.p-3
p &nbsp;
nav
template(v-if="isLoggedOn")
router-link(to="/citizen/dashboard") #[icon(icon="view-dashboard-variant")]&nbsp; Dashboard
router-link(to="/help-wanted") #[icon(icon="newspaper-variant-multiple-outline")]&nbsp; Help Wanted!
router-link(to="/profile/search") #[icon(icon="view-list-outline")]&nbsp; Employment Profiles
router-link(to="/success-story/list") #[icon(icon="thumb-up")]&nbsp; Success Stories
router-link(to="/citizen/dashboard") #[icon(:icon="mdiViewDashboardVariant")]&nbsp; Dashboard
router-link(to="/help-wanted") #[icon(:icon="mdiNewspaperVariantMultipleOutline")]&nbsp; Help Wanted!
router-link(to="/profile/search") #[icon(:icon="mdiViewListOutline")]&nbsp; Employment Profiles
router-link(to="/success-story/list") #[icon(:icon="mdiThumbUp")]&nbsp; Success Stories
.separator
router-link(to="/listings/mine") #[icon(icon="sign-text")]&nbsp; My Job Listings
router-link(to="/citizen/profile") #[icon(icon="pencil")]&nbsp; My Employment Profile
router-link(to="/listings/mine") #[icon(:icon="mdiSignText")]&nbsp; My Job Listings
router-link(to="/citizen/profile") #[icon(:icon="mdiPencil")]&nbsp; My Employment Profile
.separator
router-link(to="/citizen/log-off") #[icon(icon="logout-variant")]&nbsp; Log Off
router-link(to="/citizen/log-off") #[icon(:icon="mdiLogoutVariant")]&nbsp; Log Off
template(v-else)
router-link(to="/") #[icon(icon="home")]&nbsp; Home
router-link(to="/profile/seeking") #[icon(icon="view-list-outline")]&nbsp; Job Seekers
router-link(to="/citizen/log-on") #[icon(icon="login-variant")]&nbsp; Log On
router-link(to="/how-it-works") #[icon(icon="help-circle-outline")]&nbsp; How It Works
router-link(to="/") #[icon(:icon="mdiHome")]&nbsp; Home
router-link(to="/profile/seeking") #[icon(:icon="mdiViewListOutline")]&nbsp; Job Seekers
router-link(to="/citizen/log-on") #[icon(:icon="mdiLoginVariant")]&nbsp; Log On
router-link(to="/how-it-works") #[icon(:icon="mdiHelpCircleOutline")]&nbsp; How It Works
</template>
<script setup lang="ts">
import { computed } from "vue"
import { useStore } from "@/store"
import {
mdiHelpCircleOutline,
mdiHome,
mdiLoginVariant,
mdiLogoutVariant,
mdiNewspaperVariantMultipleOutline,
mdiPencil,
mdiSignText,
mdiThumbUp,
mdiViewDashboardVariant,
mdiViewListOutline
} from "@mdi/js"
const store = useStore()
@ -40,6 +52,10 @@ aside
min-width: 250px
position: sticky
top: 0
path
fill: white
path:hover
fill: black
a:link, a:visited
text-decoration: none
color: white

View File

@ -6,10 +6,12 @@ article
Welcome to Jobs, Jobs, Jobs (AKA No Agenda Careers), where citizens of Gitmo Nation can assist one another in
finding employment. This will enable them to continue providing value-for-value to Adam and John, as they continue
their work deconstructing the misinformation that passes for news on a day-to-day basis.
p.
Do you not understand the terms in the paragraph above? No worries; just head over to
#[a(href="https://noagendashow.net" target="_blank") The Best Podcast in the Universe]
#[= " "]#[em #[audio-clip(clip="thats-true") (that&rsquo;s true!)]] and find out what you&rsquo;re missing.
p
| Do you not understand the terms in the paragraph above? No worries; just head over to
|
a(href="https://noagendashow.net" target="_blank") The Best Podcast in the Universe
= " "
| #[em #[audio-clip(clip="thats-true") (that&rsquo;s true!)]] and find out what you&rsquo;re missing.
</template>
<script setup lang="ts">

View File

@ -2,7 +2,7 @@
article
page-title(title="How It Works")
h3 How It Works
h5.pb-3.text-muted: em Last Updated August 29th, 2021
h5.pb-3.text-muted: em Last Updated August 29#[sup th], 2021
p: em.
Show me how to #[a(href="#listing-search") find a job]
#[!= " &bull; "]#[a(href="#listing") list a job opportunity]

View File

@ -37,8 +37,8 @@ article.container
.card-footer: router-link.btn.btn-outline-secondary(to="/profile/search") Search Profiles
p &nbsp;
p.
To see how this application works, check out &ldquo;How It Works&rdquo; in the sidebar (last updated June
14#[sup th], 2021).
To see how this application works, check out &ldquo;How It Works&rdquo; in the sidebar (last updated August
29#[sup th], 2021).
</template>
<script setup lang="ts">

View File

@ -55,11 +55,11 @@ article
label.form-check-label(for="isPublic") Allow my profile to be searched publicly (outside NA Social)
.col-12
p.text-danger(v-if="v$.$error") Please correct the errors above
button.btn.btn-primary(@click.prevent="saveProfile") #[icon(icon="content-save-outline")]&nbsp; Save
button.btn.btn-primary(@click.prevent="saveProfile") #[icon(:icon="mdiContentSaveOutline")]&nbsp; Save
template(v-if="!isNew")
| &nbsp; &nbsp;
router-link.btn.btn-outline-secondary(:to="`/profile/${user.citizenId}/view`").
#[icon(icon="file-account-outline")]&nbsp; View Your User Profile
#[icon(color="#6c757d" :icon="mdiFileAccountOutline")]&nbsp; View Your User Profile
hr
p.text-muted.fst-italic.
(If you want to delete your profile, or your entire account,
@ -69,6 +69,7 @@ article
<script setup lang="ts">
import { computed, ref, reactive } from "vue"
import { mdiContentSaveOutline, mdiFileAccountOutline } from "@mdi/js"
import useVuelidate from "@vuelidate/core"
import { required } from "@vuelidate/validators"

View File

@ -31,13 +31,14 @@ article
label(for="neededBy") Needed By
.col-12
p.text-danger(v-if="v$.$error") Please correct the errors above
button.btn.btn-primary(@click.prevent="saveListing(true)") #[icon(icon="content-save-outline")]&nbsp; Save
button.btn.btn-primary(@click.prevent="saveListing(true)") #[icon(:icon="mdiContentSaveOutline")]&nbsp; Save
maybe-save(:saveAction="doSave" :validator="v$")
</template>
<script setup lang="ts">
import { computed, reactive } from "vue"
import { useRoute, useRouter } from "vue-router"
import { mdiContentSaveOutline } from "@mdi/js"
import useVuelidate from "@vuelidate/core"
import { required } from "@vuelidate/validators"

View File

@ -17,13 +17,14 @@ article
markdown-editor(id="successStory" label="Your Success Story" v-model:text="v$.successStory.$model")
.col-12
button.btn.btn-primary(@click.prevent="expireListing").
#[icon(icon="text-box-remove-outline")]&nbsp; Expire Listing
#[icon(:icon="mdiTextBoxRemoveOutline")]&nbsp; Expire Listing
maybe-save(:saveAction="doSave" :validator="v$")
</template>
<script setup lang="ts">
import { computed, reactive, Ref, ref } from "vue"
import { useRoute, useRouter } from "vue-router"
import { mdiTextBoxRemoveOutline } from "@mdi/js"
import useVuelidate from "@vuelidate/core"
import api, { Listing, ListingExpireForm, LogOnSuccess } from "@/api"

View File

@ -23,12 +23,13 @@ article
template(v-if="user.citizenId === it.citizen.id")
br
br
router-link.btn.btn-primary(to="/citizen/profile") #[icon(icon="pencil")]&nbsp; Edit Your Profile
router-link.btn.btn-primary(to="/citizen/profile") #[icon(:icon="mdiPencil")]&nbsp; Edit Your Profile
</template>
<script setup lang="ts">
import { computed, ref, Ref } from "vue"
import { useRoute } from "vue-router"
import { mdiPencil } from "@mdi/js"
import api, { LogOnSuccess, ProfileForView } from "@/api"
import { citizenName } from "@/App.vue"

View File

@ -13,7 +13,7 @@ article
markdown-editor(id="story" label="The Success Story" v-model:text="v$.story.$model")
.col-12
button.btn.btn-primary(type="submit" @click.prevent="saveStory(true)").
#[icon(icon="content-save-outline")]&nbsp; Save
#[icon(:icon="mdiContentSaveOutline")]&nbsp; Save
p(v-if="isNew"): em (Saving this will set &ldquo;Seeking Employment&rdquo; to &ldquo;No&rdquo; on your profile.)
maybe-save(:saveAction="doSave" :validator="v$")
</template>
@ -21,6 +21,7 @@ article
<script setup lang="ts">
import { computed, reactive } from "vue"
import { useRoute, useRouter } from "vue-router"
import { mdiContentSaveOutline } from "@mdi/js"
import useVuelidate from "@vuelidate/core"
import api, { LogOnSuccess, StoryForm } from "@/api"