Finish expire; mod Success Story
Add label display to several view pages; fix lint errors
This commit is contained in:
		
							parent
							
								
									8c2ff4c84d
								
							
						
					
					
						commit
						144c34919c
					
				| @ -489,7 +489,7 @@ module Listing = | ||||
|         let! _ = | ||||
|           r.Table(Table.Listing) | ||||
|             .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 | ||||
|         () | ||||
|       }) | ||||
| @ -569,4 +569,5 @@ module Success = | ||||
|                                                                     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) | ||||
|  | ||||
| @ -22,7 +22,7 @@ module.exports = { | ||||
|     "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", | ||||
|     "vue/no-multiple-template-root": "off", | ||||
|     "vue/script-setup-uses-vars": 1, | ||||
|     "quotes": ["error", "double", { avoidEscape: true }], | ||||
|     "quotes": ["error", "double", { avoidEscape: true, allowTemplateLiterals: true }], | ||||
|     "func-call-spacing": "off", | ||||
|     "@typescript-eslint/no-unused-vars": "off" | ||||
|   } | ||||
|  | ||||
| @ -3,8 +3,8 @@ | ||||
|   app-nav | ||||
|   .jjj-main | ||||
|     title-bar | ||||
|     main.container-fluid: router-view(v-slot="{ Component }"): transition(name='fade' mode='out-in') | ||||
|       component(:is='Component') | ||||
|     main.container-fluid: router-view(v-slot="{ Component }"): transition(name="fade" mode="out-in") | ||||
|       component(:is="Component") | ||||
|     app-footer | ||||
|     app-toaster | ||||
| </template> | ||||
| @ -63,6 +63,10 @@ a:not(.btn):hover | ||||
| label.jjj-required::after | ||||
|   color: red | ||||
|   content: ' *' | ||||
| .jjj-heading-label | ||||
|   display: inline-block | ||||
|   font-size: 1rem | ||||
|   text-transform: uppercase | ||||
| // Styles for this component | ||||
| .jjj-app | ||||
|   display: flex | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import { parseJSON } from 'date-fns' | ||||
| import { utcToZonedTime } from 'date-fns-tz' | ||||
| import { parseJSON } from "date-fns" | ||||
| import { utcToZonedTime } from "date-fns-tz" | ||||
| 
 | ||||
| /** | ||||
|  * Parse a date from its JSON representation to a UTC-aligned date | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
| nav.navbar.navbar-light.bg-light | ||||
|   span   | ||||
|   span.navbar-text. | ||||
|     (…and Jobs – #[audio-clip(clip="pelosi-jobs") Let's Vote for Jobs!]) | ||||
|     (…and Jobs – #[audio-clip(clip="pelosi-jobs") Let’s Vote for Jobs!]) | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
|  | ||||
| @ -9,7 +9,7 @@ article | ||||
|   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’s true!)]] and find out what you’re missing. | ||||
|     #[= " "]#[em #[audio-clip(clip="thats-true") (that’s true!)]] and find out what you’re missing. | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
|  | ||||
| @ -5,13 +5,13 @@ article | ||||
|   p: em (as of February 6#[sup th], 2021) | ||||
| 
 | ||||
|   p. | ||||
|     {{name}} (“we,” “our,” or “us”) is committed to protecting your privacy. This Privacy Policy explains how your | ||||
|     personal information is collected, used, and disclosed by {{name}}. | ||||
|     {{name}} (“we,” “our,” or “us”) is committed to protecting your privacy. This | ||||
|     Privacy Policy explains how your personal information is collected, used, and disclosed by {{name}}. | ||||
|   p. | ||||
|     This Privacy Policy applies to our website, and its associated subdomains (collectively, our “Service”) alongside | ||||
|     our application, {{name}}. By accessing or using our Service, you signify that you have read, understood, and agree | ||||
|     to our collection, storage, use, and disclosure of your personal information as described in this Privacy Policy and | ||||
|     our Terms of Service. | ||||
|     This Privacy Policy applies to our website, and its associated subdomains (collectively, our “Service”) | ||||
|     alongside our application, {{name}}. By accessing or using our Service, you signify that you have read, understood, | ||||
|     and agree to our collection, storage, use, and disclosure of your personal information as described in this Privacy | ||||
|     Policy and our Terms of Service. | ||||
| 
 | ||||
|   h4 Definitions and key terms | ||||
|   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 | ||||
|       browser, provide analytics, remember information about you such as your language preference or login information. | ||||
|     li. | ||||
|       Company: when this policy mentions “Company,” “we,” “us,” or “our,” it refers to {{name}}, that is responsible for | ||||
|       your information under this Privacy Policy. | ||||
|       Company: when this policy mentions “Company,” “we,” “us,” or | ||||
|       “our,” 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. | ||||
|       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 | ||||
|   p {{name}} does not use Cookies. | ||||
| 
 | ||||
|   h4 Kids' Privacy | ||||
|   h4 Kids’ Privacy | ||||
|   p. | ||||
|     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 | ||||
| @ -219,7 +219,7 @@ article | ||||
|     entity for any Third-Party Services. | ||||
|   p. | ||||
|     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’ terms and conditions. | ||||
| 
 | ||||
|   h4 Tracking Technologies | ||||
|   p. | ||||
| @ -278,9 +278,9 @@ article | ||||
|     Service and Privacy Policy, but we will not hold it longer than 60 days. | ||||
|   p. | ||||
|     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 | ||||
|     have always given you access to your data. Our customer support team is here for you to answer any questions you | ||||
|     might have about working with the API. | ||||
|     access, update, retrieve and remove personal data. We got you! We’ve been set up as self service from the | ||||
|     start and have always given you access to your data. Our customer support team is here for you to answer any | ||||
|     questions you might have about working with the API. | ||||
| 
 | ||||
|   h4 California Residents | ||||
|   p. | ||||
| @ -300,7 +300,7 @@ article | ||||
|     li. | ||||
|       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. | ||||
|     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’s personal data, not sell the consumer’s personal data. | ||||
|   p. | ||||
|     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. | ||||
| @ -323,7 +323,9 @@ article | ||||
|     li. | ||||
|       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. | ||||
|     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’s personal data, not sell the consumer’s | ||||
|       personal data. | ||||
|   p. | ||||
|     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. | ||||
| @ -331,7 +333,7 @@ article | ||||
|   p For more information about these rights, please contact us. | ||||
| 
 | ||||
|   h4 Contact Us | ||||
|   p Don't hesitate to contact us if you have any questions. | ||||
|   p Don’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] | ||||
| </template> | ||||
| 
 | ||||
|  | ||||
| @ -13,11 +13,11 @@ article | ||||
| /** The authorization URL to which the user should be directed */ | ||||
| const authUrl = (() => { | ||||
|   /** 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 scope = 'scope=read:accounts' | ||||
|   const scope = "scope=read:accounts" | ||||
|   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}` | ||||
| })() | ||||
| document.location.assign(authUrl) | ||||
|  | ||||
| @ -59,16 +59,16 @@ const user = store.state.user as LogOnSuccess | ||||
| 
 | ||||
| /** A new job listing */ | ||||
| const newListing : Listing = { | ||||
|   id: '', | ||||
|   id: "", | ||||
|   citizenId: user.citizenId, | ||||
|   createdOn: '', | ||||
|   title: '', | ||||
|   continentId: '', | ||||
|   region: '', | ||||
|   createdOn: "", | ||||
|   title: "", | ||||
|   continentId: "", | ||||
|   region: "", | ||||
|   remoteWork: false, | ||||
|   isExpired: false, | ||||
|   updatedOn: '', | ||||
|   text: '', | ||||
|   updatedOn: "", | ||||
|   text: "", | ||||
|   neededBy: undefined, | ||||
|   wasFilledHere: undefined | ||||
| } | ||||
|  | ||||
| @ -49,6 +49,7 @@ const listing : Ref<Listing | undefined> = ref(undefined) | ||||
| 
 | ||||
| /** The data needed to expire a job listing */ | ||||
| const expiration = reactive(new ListingExpireForm()) | ||||
| expiration.successStory = "" | ||||
| 
 | ||||
| /** The validation rules for the form */ | ||||
| const rules = computed(() => ({ | ||||
|  | ||||
| @ -2,14 +2,18 @@ | ||||
| article | ||||
|   page-title(:title="title") | ||||
|   load-data(:load="retrieveListing") | ||||
|     h3 {{it.listing.title}} | ||||
|     h3 | ||||
|       | {{it.listing.title}} | ||||
|       .jjj-heading-label(v-if="it.listing.isExpired") | ||||
|         |     #[span.badge.bg-warning.text-dark Expired] | ||||
|         template(v-if="it.listing.wasFilledHere")    #[span.badge.bg-success Filled via Jobs, Jobs, Jobs] | ||||
|     h4.pb-3.text-muted {{it.continent.name}} / {{it.listing.region}} | ||||
|     p | ||||
|       template(v-if="it.listing.neededBy"). | ||||
|         #[strong #[em NEEDED BY {{neededBy(it.listing.neededBy)}}]] • | ||||
|       |  Listed by #[a(:href="profileUrl" target="_blank") {{citizenName(citizen)}}] | ||||
|     hr | ||||
|     div(v-html='details') | ||||
|     div(v-html="details") | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
|  | ||||
| @ -4,25 +4,46 @@ article | ||||
|   h3.pb-3 My Job Listings | ||||
|   p: router-link.btn.btn-outline-primary(to="/listing/new/edit") Add a New Job Listing | ||||
|   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 | ||||
|         th(scope="col") Action | ||||
|         th(scope="col") Title | ||||
|         th(scope="col") Continent / Region | ||||
|         th(scope="col") Created | ||||
|         th(scope="col") Updated | ||||
|       tbody: tr(v-for='it in listings' :key='it.listing.id') | ||||
|         td: router-link(:to="`/listing/${it.listing.id}/edit`") Edit | ||||
|       tbody: tr(v-for="it in active" :key="it.listing.id") | ||||
|         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.continent.name}} / {{it.listing.region}} | ||||
|         td: full-date-time(:date='it.listing.createdOn') | ||||
|         td: full-date-time(:date='it.listing.updatedOn') | ||||
|     p.fst-italic(v-else) No job listings found | ||||
|         td: full-date-time(:date="it.listing.createdOn") | ||||
|         td: full-date-time(:date="it.listing.updatedOn") | ||||
|     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> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
| import { Ref, ref } from "vue" | ||||
| import { computed, Ref, ref } from "vue" | ||||
| import api, { ListingForView, LogOnSuccess } from "@/api" | ||||
| import { yesOrNo } from "@/App.vue" | ||||
| import { useStore } from "@/store" | ||||
| 
 | ||||
| import FullDateTime from "@/components/FullDateTime.vue" | ||||
| @ -33,6 +54,12 @@ const store = useStore() | ||||
| /** The listings for the user */ | ||||
| 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 */ | ||||
| const getListings = async (errors : string[]) => { | ||||
|   const listResult = await api.listings.mine(store.state.user as LogOnSuccess) | ||||
|  | ||||
| @ -19,7 +19,7 @@ article | ||||
|           th(scope="col") Last Updated | ||||
|         tbody: tr(v-for="profile in results" :key="profile.citzenId") | ||||
|           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.remoteWork)}} | ||||
|           td.text-center {{yesOrNo(profile.fullTime)}} | ||||
| @ -55,10 +55,10 @@ const searched = ref(false) | ||||
| 
 | ||||
| /** An empty set of search criteria */ | ||||
| const emptyCriteria = { | ||||
|   continentId: '', | ||||
|   continentId: "", | ||||
|   skill: undefined, | ||||
|   bioExperience: undefined, | ||||
|   remoteWork: '' | ||||
|   remoteWork: "" | ||||
| } | ||||
| 
 | ||||
| /** 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 } | ||||
| 
 | ||||
| /** Execute a search */ | ||||
| const doSearch = () => router.push({ query: { searched: 'true', ...criteria.value } }) | ||||
| const doSearch = () => router.push({ query: { searched: "true", ...criteria.value } }) | ||||
| </script> | ||||
|  | ||||
| @ -2,7 +2,10 @@ | ||||
| article | ||||
|   page-title(:title="title") | ||||
|   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") | ||||
|         |    #[span.badge.bg-dark Currently Seeking Employment] | ||||
|     h4.pb-3 {{it.continent.name}}, {{it.profile.region}} | ||||
|     p(v-html="workTypes") | ||||
|     hr | ||||
| @ -47,11 +50,6 @@ const workTypes = computed(() => { | ||||
|   const parts : string[] = [] | ||||
|   if (it.value) { | ||||
|     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.remoteWork ? "I" : "Not i"}nterested in remote opportunities`) | ||||
|   } | ||||
|  | ||||
| @ -52,10 +52,10 @@ const errors : Ref<string[]> = ref([]) | ||||
| 
 | ||||
| /** An empty set of search criteria */ | ||||
| const emptyCriteria = { | ||||
|   continentId: '', | ||||
|   continentId: "", | ||||
|   region: undefined, | ||||
|   skill: undefined, | ||||
|   remoteWork: '' | ||||
|   remoteWork: "" | ||||
| } | ||||
| 
 | ||||
| /** 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 } | ||||
| 
 | ||||
| /** Execute a search */ | ||||
| const doSearch = () => router.push({ query: { searched: 'true', ...criteria.value } }) | ||||
| const doSearch = () => router.push({ query: { searched: "true", ...criteria.value } }) | ||||
| </script> | ||||
|  | ||||
| @ -2,10 +2,11 @@ | ||||
| article | ||||
|   page-title(title="Success Story") | ||||
|   load-data(:load="retrieveStory") | ||||
|     h3.pb-3 {{citizenName}}’s Success Story | ||||
|     h4.text-muted: full-date-time(:date="story.recordedOn") | ||||
|     p.fst-italic(v-if="story.fromHere"): strong Found via Jobs, Jobs, Jobs | ||||
|     hr | ||||
|     h3 | ||||
|       | {{citizenName}}’s Success Story | ||||
|       .jjj-heading-label(v-if="story.fromHere") | ||||
|         |    #[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") | ||||
| </template> | ||||
| 
 | ||||
| @ -15,7 +16,7 @@ import { useRoute } from "vue-router" | ||||
| 
 | ||||
| import api, { LogOnSuccess, Success } from "@/api" | ||||
| import { citizenName as citName } from "@/App.vue" | ||||
| import { toHtml } from '@/markdown' | ||||
| import { toHtml } from "@/markdown" | ||||
| import { useStore } from "@/store" | ||||
| 
 | ||||
| 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 */ | ||||
| const successStory = computed(() => toHtml(story.value?.story ?? "")) | ||||
| </script> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user