Finish expire; mod Success Story

Add label display to several view pages; fix lint errors
This commit is contained in:
Daniel J. Summers 2021-08-29 15:42:18 -04:00
parent 8c2ff4c84d
commit 144c34919c
16 changed files with 102 additions and 61 deletions

View File

@ -489,7 +489,7 @@ module Listing =
let! _ = let! _ =
r.Table(Table.Listing) r.Table(Table.Listing)
.Get(listingId) .Get(listingId)
.Update(r.HashMap("isExpired", true).With("fromHere", fromHere).With("updatedOn", now)) .Update(r.HashMap("isExpired", true).With("wasFilledHere", fromHere).With("updatedOn", now))
.RunWriteAsync conn .RunWriteAsync conn
() ()
}) })
@ -569,4 +569,5 @@ module Success =
it.G("naUser"))) it.G("naUser")))
.With("hasStory", it.G("story").Default_("").Gt("")))) .With("hasStory", it.G("story").Default_("").Gt(""))))
.Pluck("id", "citizenId", "citizenName", "recordedOn", "fromHere", "hasStory") .Pluck("id", "citizenId", "citizenName", "recordedOn", "fromHere", "hasStory")
.OrderBy(r.Desc("recordedOn"))
.RunResultAsync<StoryEntry list> conn) .RunResultAsync<StoryEntry list> conn)

View File

@ -22,7 +22,7 @@ module.exports = {
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"vue/no-multiple-template-root": "off", "vue/no-multiple-template-root": "off",
"vue/script-setup-uses-vars": 1, "vue/script-setup-uses-vars": 1,
"quotes": ["error", "double", { avoidEscape: true }], "quotes": ["error", "double", { avoidEscape: true, allowTemplateLiterals: true }],
"func-call-spacing": "off", "func-call-spacing": "off",
"@typescript-eslint/no-unused-vars": "off" "@typescript-eslint/no-unused-vars": "off"
} }

View File

@ -3,8 +3,8 @@
app-nav app-nav
.jjj-main .jjj-main
title-bar title-bar
main.container-fluid: router-view(v-slot="{ Component }"): transition(name='fade' mode='out-in') main.container-fluid: router-view(v-slot="{ Component }"): transition(name="fade" mode="out-in")
component(:is='Component') component(:is="Component")
app-footer app-footer
app-toaster app-toaster
</template> </template>
@ -63,6 +63,10 @@ a:not(.btn):hover
label.jjj-required::after label.jjj-required::after
color: red color: red
content: ' *' content: ' *'
.jjj-heading-label
display: inline-block
font-size: 1rem
text-transform: uppercase
// Styles for this component // Styles for this component
.jjj-app .jjj-app
display: flex display: flex

View File

@ -1,5 +1,5 @@
import { parseJSON } from 'date-fns' import { parseJSON } from "date-fns"
import { utcToZonedTime } from 'date-fns-tz' import { utcToZonedTime } from "date-fns-tz"
/** /**
* Parse a date from its JSON representation to a UTC-aligned date * Parse a date from its JSON representation to a UTC-aligned date

View File

@ -2,7 +2,7 @@
nav.navbar.navbar-light.bg-light nav.navbar.navbar-light.bg-light
span &nbsp; span &nbsp;
span.navbar-text. span.navbar-text.
(&hellip;and Jobs &ndash; #[audio-clip(clip="pelosi-jobs") Let's Vote for Jobs!]) (&hellip;and Jobs &ndash; #[audio-clip(clip="pelosi-jobs") Let&rsquo;s Vote for Jobs!])
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -9,7 +9,7 @@ article
p. p.
Do you not understand the terms in the paragraph above? No worries; just head over to 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] #[a(href="https://noagendashow.net" target="_blank") The Best Podcast in the Universe]
#[em &nbsp;#[audio-clip(clip="thats-true") (that&rsquo;s true!)]] and find out what you&rsquo;re missing. #[= " "]#[em #[audio-clip(clip="thats-true") (that&rsquo;s true!)]] and find out what you&rsquo;re missing.
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -5,13 +5,13 @@ article
p: em (as of February 6#[sup th], 2021) p: em (as of February 6#[sup th], 2021)
p. p.
{{name}} (we, our, or us) is committed to protecting your privacy. This Privacy Policy explains how your {{name}} (&ldquo;we,&rdquo; &ldquo;our,&rdquo; or &ldquo;us&rdquo;) is committed to protecting your privacy. This
personal information is collected, used, and disclosed by {{name}}. Privacy Policy explains how your personal information is collected, used, and disclosed by {{name}}.
p. p.
This Privacy Policy applies to our website, and its associated subdomains (collectively, our Service) alongside This Privacy Policy applies to our website, and its associated subdomains (collectively, our &ldquo;Service&rdquo;)
our application, {{name}}. By accessing or using our Service, you signify that you have read, understood, and agree alongside our application, {{name}}. By accessing or using our Service, you signify that you have read, understood,
to our collection, storage, use, and disclosure of your personal information as described in this Privacy Policy and and agree to our collection, storage, use, and disclosure of your personal information as described in this Privacy
our Terms of Service. Policy and our Terms of Service.
h4 Definitions and key terms h4 Definitions and key terms
p. p.
@ -22,8 +22,8 @@ article
Cookie: small amount of data generated by a website and saved by your web browser. It is used to identify your Cookie: small amount of data generated by a website and saved by your web browser. It is used to identify your
browser, provide analytics, remember information about you such as your language preference or login information. browser, provide analytics, remember information about you such as your language preference or login information.
li. li.
Company: when this policy mentions Company, we, us, or our, it refers to {{name}}, that is responsible for Company: when this policy mentions &ldquo;Company,&rdquo; &ldquo;we,&rdquo; &ldquo;us,&rdquo; or
your information under this Privacy Policy. &ldquo;our,&rdquo; it refers to {{name}}, that is responsible for your information under this Privacy Policy.
li Country: where {{name}} or the owners/founders of {{name}} are based, in this case is US. li Country: where {{name}} or the owners/founders of {{name}} are based, in this case is US.
li. li.
Customer: refers to the company, organization or person that signs up to use the {{name}} Service to manage the Customer: refers to the company, organization or person that signs up to use the {{name}} Service to manage the
@ -193,7 +193,7 @@ article
h4 Cookies h4 Cookies
p {{name}} does not use Cookies. p {{name}} does not use Cookies.
h4 Kids' Privacy h4 Kids&rsquo; Privacy
p. p.
We do not address anyone under the age of 13. We do not knowingly collect personally identifiable information from We do not address anyone under the age of 13. We do not knowingly collect personally identifiable information from
anyone under the age of 13. If You are a parent or guardian and You are aware that Your child has provided Us with anyone under the age of 13. If You are a parent or guardian and You are aware that Your child has provided Us with
@ -219,7 +219,7 @@ article
entity for any Third-Party Services. entity for any Third-Party Services.
p. p.
Third-Party Services and links thereto are provided solely as a convenience to you and you access and use them Third-Party Services and links thereto are provided solely as a convenience to you and you access and use them
entirely at your own risk and subject to such third parties' terms and conditions. entirely at your own risk and subject to such third parties&rsquo; terms and conditions.
h4 Tracking Technologies h4 Tracking Technologies
p. p.
@ -278,9 +278,9 @@ article
Service and Privacy Policy, but we will not hold it longer than 60 days. Service and Privacy Policy, but we will not hold it longer than 60 days.
p. p.
We are aware that if you are working with EU customers, you need to be able to provide them with the ability to We are aware that if you are working with EU customers, you need to be able to provide them with the ability to
access, update, retrieve and remove personal data. We got you! We've been set up as self service from the start and access, update, retrieve and remove personal data. We got you! We&rsquo;ve been set up as self service from the
have always given you access to your data. Our customer support team is here for you to answer any questions you start and have always given you access to your data. Our customer support team is here for you to answer any
might have about working with the API. questions you might have about working with the API.
h4 California Residents h4 California Residents
p. p.
@ -300,7 +300,7 @@ article
li. li.
Right to Delete. You may submit a verifiable request to close your account and we will delete Personal Information Right to Delete. You may submit a verifiable request to close your account and we will delete Personal Information
about you that we have collected. about you that we have collected.
li Request that a business that sells a consumer's personal data, not sell the consumer's personal data. li Request that a business that sells a consumer&rsquo;s personal data, not sell the consumer&rsquo;s personal data.
p. p.
If you make a request, we have one month to respond to you. If you would like to exercise any of these rights, If you make a request, we have one month to respond to you. If you would like to exercise any of these rights,
please contact us. please contact us.
@ -323,7 +323,9 @@ article
li. li.
Right to Delete. You may submit a verifiable request to close your account and we will delete Personal Information Right to Delete. You may submit a verifiable request to close your account and we will delete Personal Information
about you that we have collected. about you that we have collected.
li Right to request that a business that sells a consumer's personal data, not sell the consumer's personal data. li.
Right to request that a business that sells a consumer&rsquo;s personal data, not sell the consumer&rsquo;s
personal data.
p. p.
If you make a request, we have one month to respond to you. If you would like to exercise any of these rights, If you make a request, we have one month to respond to you. If you would like to exercise any of these rights,
please contact us. please contact us.
@ -331,7 +333,7 @@ article
p For more information about these rights, please contact us. p For more information about these rights, please contact us.
h4 Contact Us h4 Contact Us
p Don't hesitate to contact us if you have any questions. p Don&rsquo;t hesitate to contact us if you have any questions.
ul: li Via this Link: #[router-link(to="/how-it-works") https://noagendacareers.com/how-it-works] ul: li Via this Link: #[router-link(to="/how-it-works") https://noagendacareers.com/how-it-works]
</template> </template>

View File

@ -13,11 +13,11 @@ article
/** The authorization URL to which the user should be directed */ /** The authorization URL to which the user should be directed */
const authUrl = (() => { const authUrl = (() => {
/** The client ID for Jobs, Jobs, Jobs at No Agenda Social */ /** The client ID for Jobs, Jobs, Jobs at No Agenda Social */
const id = 'k_06zlMy0N451meL4AqlwMQzs5PYr6g3d2Q_dCT-OjU' const id = "k_06zlMy0N451meL4AqlwMQzs5PYr6g3d2Q_dCT-OjU"
const client = `client_id=${id}` const client = `client_id=${id}`
const scope = 'scope=read:accounts' const scope = "scope=read:accounts"
const redirect = `redirect_uri=${document.location.origin}/citizen/authorized` const redirect = `redirect_uri=${document.location.origin}/citizen/authorized`
const respType = 'response_type=code' const respType = "response_type=code"
return `https://noagendasocial.com/oauth/authorize?${client}&${scope}&${redirect}&${respType}` return `https://noagendasocial.com/oauth/authorize?${client}&${scope}&${redirect}&${respType}`
})() })()
document.location.assign(authUrl) document.location.assign(authUrl)

View File

@ -59,16 +59,16 @@ const user = store.state.user as LogOnSuccess
/** A new job listing */ /** A new job listing */
const newListing : Listing = { const newListing : Listing = {
id: '', id: "",
citizenId: user.citizenId, citizenId: user.citizenId,
createdOn: '', createdOn: "",
title: '', title: "",
continentId: '', continentId: "",
region: '', region: "",
remoteWork: false, remoteWork: false,
isExpired: false, isExpired: false,
updatedOn: '', updatedOn: "",
text: '', text: "",
neededBy: undefined, neededBy: undefined,
wasFilledHere: undefined wasFilledHere: undefined
} }

View File

@ -49,6 +49,7 @@ const listing : Ref<Listing | undefined> = ref(undefined)
/** The data needed to expire a job listing */ /** The data needed to expire a job listing */
const expiration = reactive(new ListingExpireForm()) const expiration = reactive(new ListingExpireForm())
expiration.successStory = ""
/** The validation rules for the form */ /** The validation rules for the form */
const rules = computed(() => ({ const rules = computed(() => ({

View File

@ -2,14 +2,18 @@
article article
page-title(:title="title") page-title(:title="title")
load-data(:load="retrieveListing") load-data(:load="retrieveListing")
h3 {{it.listing.title}} h3
| {{it.listing.title}}
.jjj-heading-label(v-if="it.listing.isExpired")
| &nbsp; &nbsp; #[span.badge.bg-warning.text-dark Expired]
template(v-if="it.listing.wasFilledHere") &nbsp; &nbsp;#[span.badge.bg-success Filled via Jobs, Jobs, Jobs]
h4.pb-3.text-muted {{it.continent.name}} / {{it.listing.region}} h4.pb-3.text-muted {{it.continent.name}} / {{it.listing.region}}
p p
template(v-if="it.listing.neededBy"). template(v-if="it.listing.neededBy").
#[strong #[em NEEDED BY {{neededBy(it.listing.neededBy)}}]] &bull; #[strong #[em NEEDED BY {{neededBy(it.listing.neededBy)}}]] &bull;
| Listed by #[a(:href="profileUrl" target="_blank") {{citizenName(citizen)}}] | Listed by #[a(:href="profileUrl" target="_blank") {{citizenName(citizen)}}]
hr hr
div(v-html='details') div(v-html="details")
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -4,25 +4,46 @@ article
h3.pb-3 My Job Listings h3.pb-3 My Job Listings
p: router-link.btn.btn-outline-primary(to="/listing/new/edit") Add a New Job Listing p: router-link.btn.btn-outline-primary(to="/listing/new/edit") Add a New Job Listing
load-data(:load="getListings") load-data(:load="getListings")
table.table.table-sm.table-hover.pt-3(v-if='listings.length > 0') h4.pb-2(v-if="expired.length > 0") Active Job Listings
table.pb-3.table.table-sm.table-hover.pt-3(v-if="active.length > 0")
thead: tr thead: tr
th(scope="col") Action th(scope="col") Action
th(scope="col") Title th(scope="col") Title
th(scope="col") Continent / Region th(scope="col") Continent / Region
th(scope="col") Created th(scope="col") Created
th(scope="col") Updated th(scope="col") Updated
tbody: tr(v-for='it in listings' :key='it.listing.id') tbody: tr(v-for="it in active" :key="it.listing.id")
td: router-link(:to="`/listing/${it.listing.id}/edit`") Edit td
router-link(:to="`/listing/${it.listing.id}/edit`") Edit
= " ~ "
router-link(:to="`/listing/${it.listing.id}/view`") View
= " ~ "
router-link(:to="`/listing/${it.listing.id}/expire`") Expire
td {{it.listing.title}} td {{it.listing.title}}
td {{it.continent.name}} / {{it.listing.region}} td {{it.continent.name}} / {{it.listing.region}}
td: full-date-time(:date='it.listing.createdOn') td: full-date-time(:date="it.listing.createdOn")
td: full-date-time(:date='it.listing.updatedOn') td: full-date-time(:date="it.listing.updatedOn")
p.fst-italic(v-else) No job listings found p.pb-3.fst-italic(v-else) You have no active job listings
template(v-if="expired.length > 0")
h4.pb-2 Expired Job Listings
table.table.table-sm.table-hover.pt-3
thead: tr
th(scope="col") Action
th(scope="col") Title
th(scope="col") Filled Here?
th(scope="col") Expired
tbody: tr(v-for="it in expired" :key="it.listing.id")
td
router-link(:to="`/listing/${it.listing.id}/view`") View
td {{it.listing.title}}
td {{yesOrNo(it.listing.wasFilledHere)}}
td: full-date-time(:date="it.listing.updatedOn")
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Ref, ref } from "vue" import { computed, Ref, ref } from "vue"
import api, { ListingForView, LogOnSuccess } from "@/api" import api, { ListingForView, LogOnSuccess } from "@/api"
import { yesOrNo } from "@/App.vue"
import { useStore } from "@/store" import { useStore } from "@/store"
import FullDateTime from "@/components/FullDateTime.vue" import FullDateTime from "@/components/FullDateTime.vue"
@ -33,6 +54,12 @@ const store = useStore()
/** The listings for the user */ /** The listings for the user */
const listings : Ref<ListingForView[]> = ref([]) const listings : Ref<ListingForView[]> = ref([])
/** The active (non-expired) listings entered by this user */
const active = computed(() => listings.value.filter(it => !it.listing.isExpired))
/** The expired listings entered by this user */
const expired = computed(() => listings.value.filter(it => it.listing.isExpired))
/** Retrieve the job listing posted by the current citizen */ /** Retrieve the job listing posted by the current citizen */
const getListings = async (errors : string[]) => { const getListings = async (errors : string[]) => {
const listResult = await api.listings.mine(store.state.user as LogOnSuccess) const listResult = await api.listings.mine(store.state.user as LogOnSuccess)

View File

@ -19,7 +19,7 @@ article
th(scope="col") Last Updated th(scope="col") Last Updated
tbody: tr(v-for="profile in results" :key="profile.citzenId") tbody: tr(v-for="profile in results" :key="profile.citzenId")
td: router-link(:to="`/profile/${profile.citizenId}/view`") View td: router-link(:to="`/profile/${profile.citizenId}/view`") View
td(:class="{ 'font-weight-bold' : profile.seekingEmployment }") {{profile.displayName}} td(:class="{ 'fw-bold' : profile.seekingEmployment }") {{profile.displayName}}
td.text-center {{yesOrNo(profile.seekingEmployment)}} td.text-center {{yesOrNo(profile.seekingEmployment)}}
td.text-center {{yesOrNo(profile.remoteWork)}} td.text-center {{yesOrNo(profile.remoteWork)}}
td.text-center {{yesOrNo(profile.fullTime)}} td.text-center {{yesOrNo(profile.fullTime)}}
@ -55,10 +55,10 @@ const searched = ref(false)
/** An empty set of search criteria */ /** An empty set of search criteria */
const emptyCriteria = { const emptyCriteria = {
continentId: '', continentId: "",
skill: undefined, skill: undefined,
bioExperience: undefined, bioExperience: undefined,
remoteWork: '' remoteWork: ""
} }
/** The search criteria being built from the page */ /** The search criteria being built from the page */
@ -113,5 +113,5 @@ watch(() => route.query, setUpPage, { immediate: true })
const toggleCollapse = (it : boolean) => { isCollapsed.value = it } const toggleCollapse = (it : boolean) => { isCollapsed.value = it }
/** Execute a search */ /** Execute a search */
const doSearch = () => router.push({ query: { searched: 'true', ...criteria.value } }) const doSearch = () => router.push({ query: { searched: "true", ...criteria.value } })
</script> </script>

View File

@ -2,7 +2,10 @@
article article
page-title(:title="title") page-title(:title="title")
load-data(:load="retrieveProfile") load-data(:load="retrieveProfile")
h2: a(:href="it.citizen.profileUrl" target="_blank") {{citizenName(it.citizen)}} h2
a(:href="it.citizen.profileUrl" target="_blank") {{citizenName(it.citizen)}}
.jjj-heading-label(v-if="it.profile.seekingEmployment")
| &nbsp; &nbsp;#[span.badge.bg-dark Currently Seeking Employment]
h4.pb-3 {{it.continent.name}}, {{it.profile.region}} h4.pb-3 {{it.continent.name}}, {{it.profile.region}}
p(v-html="workTypes") p(v-html="workTypes")
hr hr
@ -47,11 +50,6 @@ const workTypes = computed(() => {
const parts : string[] = [] const parts : string[] = []
if (it.value) { if (it.value) {
const p = it.value.profile const p = it.value.profile
if (p.seekingEmployment) {
parts.push("<strong><em>CURRENTLY SEEKING EMPLOYMENT</em></strong>")
} else {
parts.push("Not actively seeking employment")
}
parts.push(`${p.fullTime ? "I" : "Not i"}nterested in full-time employment`) parts.push(`${p.fullTime ? "I" : "Not i"}nterested in full-time employment`)
parts.push(`${p.remoteWork ? "I" : "Not i"}nterested in remote opportunities`) parts.push(`${p.remoteWork ? "I" : "Not i"}nterested in remote opportunities`)
} }

View File

@ -52,10 +52,10 @@ const errors : Ref<string[]> = ref([])
/** An empty set of search criteria */ /** An empty set of search criteria */
const emptyCriteria = { const emptyCriteria = {
continentId: '', continentId: "",
region: undefined, region: undefined,
skill: undefined, skill: undefined,
remoteWork: '' remoteWork: ""
} }
/** The search criteria being built from the page */ /** The search criteria being built from the page */
@ -109,5 +109,5 @@ watch(() => route.query, setUpPage, { immediate: true })
const toggleCollapse = (it : boolean) => { isCollapsed.value = it } const toggleCollapse = (it : boolean) => { isCollapsed.value = it }
/** Execute a search */ /** Execute a search */
const doSearch = () => router.push({ query: { searched: 'true', ...criteria.value } }) const doSearch = () => router.push({ query: { searched: "true", ...criteria.value } })
</script> </script>

View File

@ -2,10 +2,11 @@
article article
page-title(title="Success Story") page-title(title="Success Story")
load-data(:load="retrieveStory") load-data(:load="retrieveStory")
h3.pb-3 {{citizenName}}&rsquo;s Success Story h3
h4.text-muted: full-date-time(:date="story.recordedOn") | {{citizenName}}&rsquo;s Success Story
p.fst-italic(v-if="story.fromHere"): strong Found via Jobs, Jobs, Jobs .jjj-heading-label(v-if="story.fromHere")
hr | &nbsp; &nbsp;#[span.badge.bg-success Via {{profileOrListing}} on Jobs, Jobs, Jobs]
h4.pb-3.text-muted: full-date-time(:date="story.recordedOn")
div(v-if="story.story" v-html="successStory") div(v-if="story.story" v-html="successStory")
</template> </template>
@ -15,7 +16,7 @@ import { useRoute } from "vue-router"
import api, { LogOnSuccess, Success } from "@/api" import api, { LogOnSuccess, Success } from "@/api"
import { citizenName as citName } from "@/App.vue" import { citizenName as citName } from "@/App.vue"
import { toHtml } from '@/markdown' import { toHtml } from "@/markdown"
import { useStore } from "@/store" import { useStore } from "@/store"
import FullDateTime from "@/components/FullDateTime.vue" import FullDateTime from "@/components/FullDateTime.vue"
@ -55,6 +56,9 @@ const retrieveStory = async (errors : string []) => {
} }
} }
/** Whether this success is from an employment profile or a job listing */
const profileOrListing = computed(() => story.value?.source === "profile" ? "employment profile" : "job listing")
/** The HTML success story */ /** The HTML success story */
const successStory = computed(() => toHtml(story.value?.story ?? "")) const successStory = computed(() => toHtml(story.value?.story ?? ""))
</script> </script>