Mobile layout / .NET 6 #28
|
@ -20,7 +20,6 @@ type MastodonAccount () =
|
||||||
member val Url = "" with get, set
|
member val Url = "" with get, set
|
||||||
|
|
||||||
|
|
||||||
open FSharp.Control.Tasks
|
|
||||||
open Microsoft.Extensions.Logging
|
open Microsoft.Extensions.Logging
|
||||||
open System
|
open System
|
||||||
open System.Net.Http
|
open System.Net.Http
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/// Data access functions for Jobs, Jobs, Jobs
|
/// Data access functions for Jobs, Jobs, Jobs
|
||||||
module JobsJobsJobs.Api.Data
|
module JobsJobsJobs.Api.Data
|
||||||
|
|
||||||
open FSharp.Control.Tasks
|
|
||||||
open JobsJobsJobs.Domain.Types
|
open JobsJobsJobs.Domain.Types
|
||||||
open Polly
|
open Polly
|
||||||
open RethinkDb.Driver
|
open RethinkDb.Driver
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/// Route handlers for Giraffe endpoints
|
/// Route handlers for Giraffe endpoints
|
||||||
module JobsJobsJobs.Api.Handlers
|
module JobsJobsJobs.Api.Handlers
|
||||||
|
|
||||||
open FSharp.Control.Tasks
|
|
||||||
open Giraffe
|
open Giraffe
|
||||||
open JobsJobsJobs.Domain
|
open JobsJobsJobs.Domain
|
||||||
open JobsJobsJobs.Domain.SharedTypes
|
open JobsJobsJobs.Domain.SharedTypes
|
||||||
|
@ -288,7 +287,7 @@ module Listing =
|
||||||
// PATCH: /api/listing/[id]
|
// PATCH: /api/listing/[id]
|
||||||
let expire listingId : HttpHandler =
|
let expire listingId : HttpHandler =
|
||||||
authorize
|
authorize
|
||||||
>=> fun next ctx -> task {
|
>=> fun next ctx -> FSharp.Control.Tasks.Affine.task {
|
||||||
let dbConn = conn ctx
|
let dbConn = conn ctx
|
||||||
let now = clock(ctx).GetCurrentInstant ()
|
let now = clock(ctx).GetCurrentInstant ()
|
||||||
match! Data.Listing.findById (ListingId listingId) dbConn with
|
match! Data.Listing.findById (ListingId listingId) dbConn with
|
||||||
|
@ -309,8 +308,7 @@ module Listing =
|
||||||
| None -> ()
|
| None -> ()
|
||||||
return! ok next ctx
|
return! ok next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// GET: /api/listing/search
|
// GET: /api/listing/search
|
||||||
let search : HttpHandler =
|
let search : HttpHandler =
|
||||||
|
|
18620
src/JobsJobsJobs/App/package-lock.json
generated
18620
src/JobsJobsJobs/App/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "jobs-jobs-jobs",
|
"name": "jobs-jobs-jobs",
|
||||||
"version": "2.1.0",
|
"version": "2.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
"@mdi/js": "^5.9.55",
|
"@mdi/js": "^5.9.55",
|
||||||
"@vuelidate/core": "^2.0.0-alpha.24",
|
"@vuelidate/core": "^2.0.0-alpha.24",
|
||||||
"@vuelidate/validators": "^2.0.0-alpha.21",
|
"@vuelidate/validators": "^2.0.0-alpha.21",
|
||||||
|
"@vueuse/core": "^6.3.3",
|
||||||
"bootstrap": "^5.1.0",
|
"bootstrap": "^5.1.0",
|
||||||
"core-js": "^3.16.3",
|
"core-js": "^3.16.3",
|
||||||
"date-fns": "^2.23.0",
|
"date-fns": "^2.23.0",
|
||||||
|
|
|
@ -10,11 +10,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue"
|
import { defineComponent, onMounted } from "vue"
|
||||||
|
|
||||||
import "bootstrap/dist/css/bootstrap.min.css"
|
import "bootstrap/dist/css/bootstrap.min.css"
|
||||||
|
|
||||||
import { Citizen } from "./api"
|
import { Citizen } from "./api"
|
||||||
|
import { Mutations, useStore } from "./store"
|
||||||
import AppFooter from "./components/layout/AppFooter.vue"
|
import AppFooter from "./components/layout/AppFooter.vue"
|
||||||
import AppNav from "./components/layout/AppNav.vue"
|
import AppNav from "./components/layout/AppNav.vue"
|
||||||
import AppToaster from "./components/layout/AppToaster.vue"
|
import AppToaster from "./components/layout/AppToaster.vue"
|
||||||
|
@ -29,6 +30,10 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
onMounted(() => store.commit(Mutations.SetTitle, "Jobs, Jobs, Jobs"))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return "Yes" for true and "No" for false
|
* Return "Yes" for true and "No" for false
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
form.container
|
form.container
|
||||||
.row
|
.row
|
||||||
.col.col-xs-12.col-sm-6.col-md-4.col-lg-3
|
.col-xs-12.col-sm-6.col-md-4.col-lg-3
|
||||||
continent-list(v-model="criteria.continentId" topLabel="Any" @update:modelValue="updateContinent")
|
continent-list(v-model="criteria.continentId" topLabel="Any" @update:modelValue="updateContinent")
|
||||||
.col.col-xs-12.col-sm-6.col-lg-3
|
.col-xs-12.col-sm-6.col-lg-3
|
||||||
.form-floating
|
.form-floating
|
||||||
input.form-control(type="text" id="regionSearch" placeholder="(free-form text)" :value="criteria.region"
|
input.form-control(type="text" id="regionSearch" placeholder="(free-form text)" :value="criteria.region"
|
||||||
@input="updateValue('region', $event.target.value)")
|
@input="updateValue('region', $event.target.value)")
|
||||||
label(for="regionSearch") Region
|
label(for="regionSearch") Region
|
||||||
.form-text (free-form text)
|
.form-text (free-form text)
|
||||||
.col.col-xs-12.col-sm-6.col-offset-md-2.col-lg-3.col-offset-lg-0
|
.col-xs-12.col-sm-6.col-offset-md-2.col-lg-3.col-offset-lg-0
|
||||||
label.jjj-label Remote Work Opportunity?
|
label.jjj-label Remote Work Opportunity?
|
||||||
br
|
br
|
||||||
.form-check.form-check-inline
|
.form-check.form-check-inline
|
||||||
|
@ -24,13 +24,13 @@ form.container
|
||||||
input.form-check-input(type="radio" id="remoteNo" name="remoteWork" :checked="criteria.remoteWork === 'no'"
|
input.form-check-input(type="radio" id="remoteNo" name="remoteWork" :checked="criteria.remoteWork === 'no'"
|
||||||
@click="updateValue('remoteWork', 'no')")
|
@click="updateValue('remoteWork', 'no')")
|
||||||
label.form-check-label(for="remoteNo") No
|
label.form-check-label(for="remoteNo") No
|
||||||
.col.col-xs-12.col-sm-6.col-lg-3
|
.col-xs-12.col-sm-6.col-lg-3
|
||||||
.form-floating
|
.form-floating
|
||||||
input.form-control(type="text" id="textSearch" placeholder="(free-form text)" :value="criteria.text"
|
input.form-control(type="text" id="textSearch" placeholder="(free-form text)" :value="criteria.text"
|
||||||
@input="updateValue('text', $event.target.value)")
|
@input="updateValue('text', $event.target.value)")
|
||||||
label(for="textSearch") Job Listing Text
|
label(for="textSearch") Job Listing Text
|
||||||
.form-text (free-form text)
|
.form-text (free-form text)
|
||||||
.row: .col.col-xs-12
|
.row: .col
|
||||||
br
|
br
|
||||||
button.btn.btn-outline-primary(type="submit" @click.prevent="$emit('search')") Search
|
button.btn.btn-outline-primary(type="submit" @click.prevent="$emit('search')") Search
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
p(v-if="false")
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, watch } from "vue"
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
title: string
|
|
||||||
}>()
|
|
||||||
|
|
||||||
/** The name of the application */
|
|
||||||
const appName = "Jobs, Jobs, Jobs"
|
|
||||||
|
|
||||||
/** Set the page title based on the input title attribute */
|
|
||||||
const setTitle = () => {
|
|
||||||
if (props.title === "") {
|
|
||||||
document.title = appName
|
|
||||||
} else {
|
|
||||||
document.title = `${props.title} | ${appName}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(setTitle)
|
|
||||||
|
|
||||||
/** Change the page title when the property changes */
|
|
||||||
watch(() => props.title, setTitle)
|
|
||||||
</script>
|
|
81
src/JobsJobsJobs/App/src/components/layout/AppLinks.vue
Normal file
81
src/JobsJobsJobs/App/src/components/layout/AppLinks.vue
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
<template lang="pug">
|
||||||
|
nav
|
||||||
|
template(v-if="isLoggedOn")
|
||||||
|
router-link(to="/citizen/dashboard" @click="hide") #[icon(:icon="mdiViewDashboardVariant")] Dashboard
|
||||||
|
router-link(to="/help-wanted" @click="hide") #[icon(:icon="mdiNewspaperVariantMultipleOutline")] Help Wanted!
|
||||||
|
router-link(to="/profile/search" @click="hide") #[icon(:icon="mdiViewListOutline")] Employment Profiles
|
||||||
|
router-link(to="/success-story/list" @click="hide") #[icon(:icon="mdiThumbUp")] Success Stories
|
||||||
|
.separator
|
||||||
|
router-link(to="/listings/mine" @click="hide") #[icon(:icon="mdiSignText")] My Job Listings
|
||||||
|
router-link(to="/citizen/profile" @click="hide") #[icon(:icon="mdiPencil")] My Employment Profile
|
||||||
|
.separator
|
||||||
|
router-link(to="/citizen/log-off" @click="hide") #[icon(:icon="mdiLogoutVariant")] Log Off
|
||||||
|
template(v-else)
|
||||||
|
router-link(to="/" @click="hide") #[icon(:icon="mdiHome")] Home
|
||||||
|
router-link(to="/profile/seeking" @click="hide") #[icon(:icon="mdiViewListOutline")] Job Seekers
|
||||||
|
router-link(to="/citizen/log-on" @click="hide") #[icon(:icon="mdiLoginVariant")] Log On
|
||||||
|
router-link(to="/how-it-works" @click="hide") #[icon(:icon="mdiHelpCircleOutline")] How It Works
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { useRouter } from "vue-router"
|
||||||
|
import { Offcanvas } from "bootstrap"
|
||||||
|
import { useStore } from "@/store"
|
||||||
|
import {
|
||||||
|
mdiHelpCircleOutline,
|
||||||
|
mdiHome,
|
||||||
|
mdiLoginVariant,
|
||||||
|
mdiLogoutVariant,
|
||||||
|
mdiNewspaperVariantMultipleOutline,
|
||||||
|
mdiPencil,
|
||||||
|
mdiSignText,
|
||||||
|
mdiThumbUp,
|
||||||
|
mdiViewDashboardVariant,
|
||||||
|
mdiViewListOutline
|
||||||
|
} from "@mdi/js"
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
/** Whether a user is logged in or not */
|
||||||
|
const isLoggedOn = computed(() => store.state.user !== undefined)
|
||||||
|
|
||||||
|
/** The current mobile menu */
|
||||||
|
const menu = computed(() => {
|
||||||
|
const elt = document.getElementById("mobileMenu")
|
||||||
|
return elt ? Offcanvas.getOrCreateInstance(elt) : undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Hide the offcanvas menu (if it exists) when a link is clicked */
|
||||||
|
const hide = () => { if (menu.value) menu.value.hide() }
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass" scoped>
|
||||||
|
path
|
||||||
|
fill: white
|
||||||
|
path:hover
|
||||||
|
fill: black
|
||||||
|
a:link, a:visited
|
||||||
|
text-decoration: none
|
||||||
|
color: white
|
||||||
|
nav > a
|
||||||
|
display: block
|
||||||
|
width: 100%
|
||||||
|
border-radius: .25rem
|
||||||
|
padding: .5rem
|
||||||
|
margin: .5rem 0
|
||||||
|
font-size: 1rem
|
||||||
|
> i
|
||||||
|
vertical-align: top
|
||||||
|
margin-right: 1rem
|
||||||
|
&.router-link-exact-active
|
||||||
|
background-color: rgba(255, 255, 255, .2)
|
||||||
|
&:hover
|
||||||
|
background-color: rgba(255, 255, 255, .5)
|
||||||
|
color: black
|
||||||
|
text-decoration: none
|
||||||
|
nav > div.separator
|
||||||
|
border-bottom: solid 1px rgba(255, 255, 255, .75)
|
||||||
|
height: 1px
|
||||||
|
</style>
|
|
@ -1,88 +1,45 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
aside.collapse.show.p-3
|
#mobileMenu.offcanvas.offcanvas-end(v-if="showMobileMenu" tabindex="-1" aria-labelledby="mobileMenuLabel")
|
||||||
|
.offcanvas-header
|
||||||
|
h5#mobileMenuLabel Menu
|
||||||
|
button.btn-close.text-reset(type="button" data-bs-dismiss="offcanvas" aria-label="Close")
|
||||||
|
.offcanvas-body: app-links
|
||||||
|
aside.collapse.show.p-3(v-else)
|
||||||
p.home-link.pb-3: router-link(to="/") Jobs, Jobs, Jobs
|
p.home-link.pb-3: router-link(to="/") Jobs, Jobs, Jobs
|
||||||
p
|
p
|
||||||
nav
|
app-links
|
||||||
template(v-if="isLoggedOn")
|
|
||||||
router-link(to="/citizen/dashboard") #[icon(:icon="mdiViewDashboardVariant")] Dashboard
|
|
||||||
router-link(to="/help-wanted") #[icon(:icon="mdiNewspaperVariantMultipleOutline")] Help Wanted!
|
|
||||||
router-link(to="/profile/search") #[icon(:icon="mdiViewListOutline")] Employment Profiles
|
|
||||||
router-link(to="/success-story/list") #[icon(:icon="mdiThumbUp")] Success Stories
|
|
||||||
.separator
|
|
||||||
router-link(to="/listings/mine") #[icon(:icon="mdiSignText")] My Job Listings
|
|
||||||
router-link(to="/citizen/profile") #[icon(:icon="mdiPencil")] My Employment Profile
|
|
||||||
.separator
|
|
||||||
router-link(to="/citizen/log-off") #[icon(:icon="mdiLogoutVariant")] Log Off
|
|
||||||
template(v-else)
|
|
||||||
router-link(to="/") #[icon(:icon="mdiHome")] Home
|
|
||||||
router-link(to="/profile/seeking") #[icon(:icon="mdiViewListOutline")] Job Seekers
|
|
||||||
router-link(to="/citizen/log-on") #[icon(:icon="mdiLoginVariant")] Log On
|
|
||||||
router-link(to="/how-it-works") #[icon(:icon="mdiHelpCircleOutline")] How It Works
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue"
|
import { useBreakpoints, breakpointsBootstrapV5 } from "@vueuse/core"
|
||||||
import { useStore } from "@/store"
|
import AppLinks from "./AppLinks.vue"
|
||||||
import {
|
|
||||||
mdiHelpCircleOutline,
|
|
||||||
mdiHome,
|
|
||||||
mdiLoginVariant,
|
|
||||||
mdiLogoutVariant,
|
|
||||||
mdiNewspaperVariantMultipleOutline,
|
|
||||||
mdiPencil,
|
|
||||||
mdiSignText,
|
|
||||||
mdiThumbUp,
|
|
||||||
mdiViewDashboardVariant,
|
|
||||||
mdiViewListOutline
|
|
||||||
} from "@mdi/js"
|
|
||||||
|
|
||||||
const store = useStore()
|
const breakpoints = useBreakpoints(breakpointsBootstrapV5)
|
||||||
|
|
||||||
/** Whether a user is logged in or not */
|
/** Whether the mobile menu or the desktop menu should be shown */
|
||||||
const isLoggedOn = computed(() => store.state.user !== undefined)
|
const showMobileMenu = breakpoints.smaller("md")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="sass" scoped>
|
<style lang="sass" scoped>
|
||||||
aside
|
aside,
|
||||||
|
#mobileMenu
|
||||||
background-image: linear-gradient(180deg, darkgreen 0%, green 70%)
|
background-image: linear-gradient(180deg, darkgreen 0%, green 70%)
|
||||||
color: white
|
color: white
|
||||||
font-size: 1.2rem
|
font-size: 1.2rem
|
||||||
|
aside
|
||||||
min-height: 100vh
|
min-height: 100vh
|
||||||
width: 250px
|
width: 250px
|
||||||
min-width: 250px
|
min-width: 250px
|
||||||
position: sticky
|
position: sticky
|
||||||
top: 0
|
top: 0
|
||||||
path
|
.home-link
|
||||||
fill: white
|
font-size: 1.2rem
|
||||||
path:hover
|
text-align: center
|
||||||
fill: black
|
background-color: rgba(0, 0, 0, .4)
|
||||||
a:link, a:visited
|
margin: -1rem
|
||||||
text-decoration: none
|
padding: 1rem
|
||||||
color: white
|
a:link,
|
||||||
// font-weight: 500
|
a:visited
|
||||||
.home-link
|
|
||||||
font-size: 1.2rem
|
|
||||||
text-align: center
|
|
||||||
background-color: rgba(0, 0, 0, .4)
|
|
||||||
margin: -1rem
|
|
||||||
padding: 1rem
|
|
||||||
nav > a
|
|
||||||
display: block
|
|
||||||
width: 100%
|
|
||||||
border-radius: .25rem
|
|
||||||
padding: .5rem
|
|
||||||
margin: .5rem 0
|
|
||||||
font-size: 1rem
|
|
||||||
> i
|
|
||||||
vertical-align: top
|
|
||||||
margin-right: 1rem
|
|
||||||
&.router-link-exact-active
|
|
||||||
background-color: rgba(255, 255, 255, .2)
|
|
||||||
&:hover
|
|
||||||
background-color: rgba(255, 255, 255, .5)
|
|
||||||
color: black
|
|
||||||
text-decoration: none
|
text-decoration: none
|
||||||
nav > div.separator
|
color: white
|
||||||
border-bottom: solid 1px rgba(255, 255, 255, .75)
|
|
||||||
height: 1px
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,16 +1,38 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
nav.navbar.navbar-light.bg-light
|
nav.navbar.navbar-dark(v-if="showMobileHeader")
|
||||||
|
span.navbar-text: router-link(to="/") Jobs, Jobs, Jobs
|
||||||
|
button.btn(data-bs-toggle="offcanvas" data-bs-target="#mobileMenu" aria-controls="mobileMenu")
|
||||||
|
icon(:icon="mdiMenu")
|
||||||
|
nav.navbar.navbar-light.bg-light(v-else)
|
||||||
span
|
span
|
||||||
span.navbar-text.
|
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>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { mdiMenu } from "@mdi/js"
|
||||||
|
import { useBreakpoints, breakpointsBootstrapV5 } from "@vueuse/core"
|
||||||
|
|
||||||
import AudioClip from "@/components/AudioClip.vue"
|
import AudioClip from "@/components/AudioClip.vue"
|
||||||
|
|
||||||
|
const breakpoints = useBreakpoints(breakpointsBootstrapV5)
|
||||||
|
|
||||||
|
/** Whether to show the mobile or desktop header */
|
||||||
|
const showMobileHeader = breakpoints.smaller("md")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="sass" scoped>
|
<style lang="sass" scoped>
|
||||||
.navbar-text
|
.navbar-dark
|
||||||
font-style: italic
|
background-image: linear-gradient(0deg, green 0%, darkgreen 70%)
|
||||||
|
padding-left: 1rem
|
||||||
padding-right: 1rem
|
padding-right: 1rem
|
||||||
|
button
|
||||||
|
padding: 0
|
||||||
|
.navbar-text
|
||||||
|
font-weight: bold
|
||||||
|
color: white
|
||||||
|
.navbar-light
|
||||||
|
.navbar-text
|
||||||
|
font-style: italic
|
||||||
|
padding: 0 1rem 0 0
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
form.container
|
form.container
|
||||||
.row
|
.row
|
||||||
.col.col-xs-12.col-sm-6.col-md-4.col-lg-3
|
.col-xs-12.col-sm-6.col-md-4.col-lg-3
|
||||||
continent-list(v-model="criteria.continentId" topLabel="Any" @update:modelValue="updateContinent")
|
continent-list(v-model="criteria.continentId" topLabel="Any" @update:modelValue="updateContinent")
|
||||||
.col.col-xs-12.col-sm-6.col-md-4.col-lg-3
|
.col-xs-12.col-sm-6.col-md-4.col-lg-3
|
||||||
.form-floating
|
.form-floating
|
||||||
input.form-control.form-control-sm(type="text" id="region" placeholder="(free-form text)"
|
input.form-control.form-control-sm(type="text" id="region" placeholder="(free-form text)"
|
||||||
:value="criteria.region" @input="updateValue('region', $event.target.value)")
|
:value="criteria.region" @input="updateValue('region', $event.target.value)")
|
||||||
label(for="region") Region
|
label(for="region") Region
|
||||||
.form-text (free-form text)
|
.form-text (free-form text)
|
||||||
.col.col-xs-12.col-sm-6.col-offset-md-2.col-lg-3.col-offset-lg-0
|
.col-xs-12.col-sm-6.col-offset-md-2.col-lg-3.col-offset-lg-0
|
||||||
label.jjj-label Seeking Remote Work?
|
label.jjj-label Seeking Remote Work?
|
||||||
br
|
br
|
||||||
.form-check.form-check-inline
|
.form-check.form-check-inline
|
||||||
|
@ -24,13 +24,13 @@ form.container
|
||||||
input.form-check-input(type="radio" id="remoteNo" name="remoteWork" :checked="criteria.remoteWork === 'no'"
|
input.form-check-input(type="radio" id="remoteNo" name="remoteWork" :checked="criteria.remoteWork === 'no'"
|
||||||
@click="updateValue('remoteWork', 'no')")
|
@click="updateValue('remoteWork', 'no')")
|
||||||
label.form-check-label(for="remoteNo") No
|
label.form-check-label(for="remoteNo") No
|
||||||
.col.col-xs-12.col-sm-6.col-lg-3
|
.col-xs-12.col-sm-6.col-lg-3
|
||||||
.form-floating
|
.form-floating
|
||||||
input.form-control.form-control-sm(type="text" id="skillSearch" placeholder="(free-form text)"
|
input.form-control.form-control-sm(type="text" id="skillSearch" placeholder="(free-form text)"
|
||||||
:value="criteria.skill" @input="updateValue('skill', $event.target.value)")
|
:value="criteria.skill" @input="updateValue('skill', $event.target.value)")
|
||||||
label(for="skillSearch") Skill
|
label(for="skillSearch") Skill
|
||||||
.form-text (free-form text)
|
.form-text (free-form text)
|
||||||
.row: .col.col-xs-12
|
.row: .col
|
||||||
br
|
br
|
||||||
button.btn.btn-outline-primary(type="submit" @click.prevent="$emit('search')") Search
|
button.btn.btn-outline-primary(type="submit" @click.prevent="$emit('search')") Search
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
form.container
|
form.container
|
||||||
.row
|
.row
|
||||||
.col.col-xs-12.col-sm-6.col-md-4.col-lg-3
|
.col-xs-12.col-sm-6.col-md-4.col-lg-3
|
||||||
continent-list(v-model="criteria.continentId" topLabel="Any" @update:modelValue="updateContinent")
|
continent-list(v-model="criteria.continentId" topLabel="Any" @update:modelValue="updateContinent")
|
||||||
.col.col-xs-12.col-sm-6.col-offset-md-2.col-lg-3.col-offset-lg-0
|
.col-xs-12.col-sm-6.col-offset-md-2.col-lg-3.col-offset-lg-0
|
||||||
label.jjj-label Seeking Remote Work?
|
label.jjj-label Seeking Remote Work?
|
||||||
br
|
br
|
||||||
.form-check.form-check-inline
|
.form-check.form-check-inline
|
||||||
|
@ -18,19 +18,19 @@ form.container
|
||||||
input.form-check-input(type="radio" id="remoteNo" name="remoteWork" :checked="criteria.remoteWork === 'no'"
|
input.form-check-input(type="radio" id="remoteNo" name="remoteWork" :checked="criteria.remoteWork === 'no'"
|
||||||
@click="updateValue('remoteWork', 'no')")
|
@click="updateValue('remoteWork', 'no')")
|
||||||
label.form-check-label(for="remoteNo") No
|
label.form-check-label(for="remoteNo") No
|
||||||
.col.col-xs-12.col-sm-6.col-lg-3
|
.col-xs-12.col-sm-6.col-lg-3
|
||||||
.form-floating
|
.form-floating
|
||||||
input.form-control(type="text" id="skillSearch" placeholder="(free-form text)" :value="criteria.skill"
|
input.form-control(type="text" id="skillSearch" placeholder="(free-form text)" :value="criteria.skill"
|
||||||
@input="updateValue('skill', $event.target.value)")
|
@input="updateValue('skill', $event.target.value)")
|
||||||
label(for="skillSearch") Skill
|
label(for="skillSearch") Skill
|
||||||
.form-text (free-form text)
|
.form-text (free-form text)
|
||||||
.col.col-xs-12.col-sm-6.col-lg-3
|
.col-xs-12.col-sm-6.col-lg-3
|
||||||
.form-floating
|
.form-floating
|
||||||
input.form-control(type="text" id="bioSearch" placeholder="(free-form text)" :value="criteria.bioExperience"
|
input.form-control(type="text" id="bioSearch" placeholder="(free-form text)" :value="criteria.bioExperience"
|
||||||
@input="updateValue('bioExperience', $event.target.value)")
|
@input="updateValue('bioExperience', $event.target.value)")
|
||||||
label(for="bioSearch") Bio / Experience
|
label(for="bioSearch") Bio / Experience
|
||||||
.form-text (free-form text)
|
.form-text (free-form text)
|
||||||
.row: .col.col-xs-12
|
.row: .col
|
||||||
br
|
br
|
||||||
button.btn.btn-outline-primary(type="submit" @click.prevent="$emit('search')") Search
|
button.btn.btn-outline-primary(type="submit" @click.prevent="$emit('search')") Search
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
.row.pb-3
|
.row.pb-3
|
||||||
.col.col-xs-2.col-md-1.align-self-center
|
.col-xs-2.col-md-1.align-self-center
|
||||||
button.btn.btn-sm.btn-outline-danger.rounded-pill(title="Delete" @click.prevent="$emit('remove')") −
|
button.btn.btn-sm.btn-outline-danger.rounded-pill(title="Delete" @click.prevent="$emit('remove')") −
|
||||||
.col.col-xs-10.col-md-6
|
.col-xs-10.col-md-6
|
||||||
.form-floating
|
.form-floating
|
||||||
input.form-control(type="text" :id="`skillDesc${skill.id}`" maxlength="100"
|
input.form-control(type="text" :id="`skillDesc${skill.id}`" maxlength="100"
|
||||||
placeholder="A skill (language, design technique, process, etc.)" :value="skill.description"
|
placeholder="A skill (language, design technique, process, etc.)" :value="skill.description"
|
||||||
@input="updateValue('description', $event.target.value)")
|
@input="updateValue('description', $event.target.value)")
|
||||||
label.jjj-label(:for="`skillDesc${skill.id}`") Skill
|
label.jjj-label(:for="`skillDesc${skill.id}`") Skill
|
||||||
.form-text A skill (language, design technique, process, etc.)
|
.form-text A skill (language, design technique, process, etc.)
|
||||||
.col.col-xs-12.col-md-5
|
.col-xs-12.col-md-5
|
||||||
.form-floating
|
.form-floating
|
||||||
input.form-control(type="text" :id="`skillNotes${skill.id}`" maxlength="100"
|
input.form-control(type="text" :id="`skillNotes${skill.id}`" maxlength="100"
|
||||||
placeholder="A further description of the skill (100 characters max)" :value="skill.notes"
|
placeholder="A further description of the skill (100 characters max)" :value="skill.notes"
|
||||||
|
|
|
@ -3,13 +3,11 @@ import App from "./App.vue"
|
||||||
import router from "./router"
|
import router from "./router"
|
||||||
import store, { key } from "./store"
|
import store, { key } from "./store"
|
||||||
import Icon from "./components/Icon.vue"
|
import Icon from "./components/Icon.vue"
|
||||||
import PageTitle from "./components/PageTitle.vue"
|
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
.use(router)
|
.use(router)
|
||||||
.use(store, key)
|
.use(store, key)
|
||||||
|
|
||||||
app.component("Icon", Icon)
|
app.component("Icon", Icon)
|
||||||
app.component("PageTitle", PageTitle)
|
|
||||||
|
|
||||||
app.mount("#app")
|
app.mount("#app")
|
||||||
|
|
|
@ -3,10 +3,9 @@ import {
|
||||||
createWebHistory,
|
createWebHistory,
|
||||||
RouteLocationNormalized,
|
RouteLocationNormalized,
|
||||||
RouteLocationNormalizedLoaded,
|
RouteLocationNormalizedLoaded,
|
||||||
RouteRecordName,
|
|
||||||
RouteRecordRaw
|
RouteRecordRaw
|
||||||
} from "vue-router"
|
} from "vue-router"
|
||||||
import store from "@/store"
|
import store, { Mutations } from "@/store"
|
||||||
import Home from "@/views/Home.vue"
|
import Home from "@/views/Home.vue"
|
||||||
import LogOn from "@/views/citizen/LogOn.vue"
|
import LogOn from "@/views/citizen/LogOn.vue"
|
||||||
|
|
||||||
|
@ -29,124 +28,141 @@ const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
name: "Home",
|
name: "Home",
|
||||||
component: Home
|
component: Home,
|
||||||
|
meta: { title: "Welcome!" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/how-it-works",
|
path: "/how-it-works",
|
||||||
name: "HowItWorks",
|
name: "HowItWorks",
|
||||||
component: () => import(/* webpackChunkName: "help" */ "../views/HowItWorks.vue")
|
component: () => import(/* webpackChunkName: "help" */ "../views/HowItWorks.vue"),
|
||||||
|
meta: { title: "How It Works" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/privacy-policy",
|
path: "/privacy-policy",
|
||||||
name: "PrivacyPolicy",
|
name: "PrivacyPolicy",
|
||||||
component: () => import(/* webpackChunkName: "legal" */ "../views/PrivacyPolicy.vue")
|
component: () => import(/* webpackChunkName: "legal" */ "../views/PrivacyPolicy.vue"),
|
||||||
|
meta: { title: "Privacy Policy" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/terms-of-service",
|
path: "/terms-of-service",
|
||||||
name: "TermsOfService",
|
name: "TermsOfService",
|
||||||
component: () => import(/* webpackChunkName: "legal" */ "../views/TermsOfService.vue")
|
component: () => import(/* webpackChunkName: "legal" */ "../views/TermsOfService.vue"),
|
||||||
|
meta: { title: "Terms of Service" }
|
||||||
},
|
},
|
||||||
// Citizen URLs
|
// Citizen URLs
|
||||||
{
|
{
|
||||||
path: "/citizen/log-on",
|
path: "/citizen/log-on",
|
||||||
name: "LogOn",
|
name: "LogOn",
|
||||||
component: LogOn
|
component: LogOn,
|
||||||
|
meta: { title: "Log On" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/citizen/:abbr/authorized",
|
path: "/citizen/:abbr/authorized",
|
||||||
name: "CitizenAuthorized",
|
name: "CitizenAuthorized",
|
||||||
component: () => import(/* webpackChunkName: "dashboard" */ "../views/citizen/Authorized.vue")
|
component: () => import(/* webpackChunkName: "dashboard" */ "../views/citizen/Authorized.vue"),
|
||||||
|
meta: { title: "Logging On" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/citizen/dashboard",
|
path: "/citizen/dashboard",
|
||||||
name: "Dashboard",
|
name: "Dashboard",
|
||||||
component: () => import(/* webpackChunkName: "dashboard" */ "../views/citizen/Dashboard.vue")
|
component: () => import(/* webpackChunkName: "dashboard" */ "../views/citizen/Dashboard.vue"),
|
||||||
|
meta: { auth: true, title: "Dashboard" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/citizen/profile",
|
path: "/citizen/profile",
|
||||||
name: "EditProfile",
|
name: "EditProfile",
|
||||||
component: () => import(/* webpackChunkName: "profedit" */ "../views/citizen/EditProfile.vue")
|
component: () => import(/* webpackChunkName: "profedit" */ "../views/citizen/EditProfile.vue"),
|
||||||
|
meta: { auth: true, title: "Edit Profile" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/citizen/log-off",
|
path: "/citizen/log-off",
|
||||||
name: "LogOff",
|
name: "LogOff",
|
||||||
component: () => import(/* webpackChunkName: "logoff" */ "../views/citizen/LogOff.vue")
|
component: () => import(/* webpackChunkName: "logoff" */ "../views/citizen/LogOff.vue"),
|
||||||
|
meta: { auth: true, title: "Logging Off" }
|
||||||
},
|
},
|
||||||
// Job Listing URLs
|
// Job Listing URLs
|
||||||
{
|
{
|
||||||
path: "/help-wanted",
|
path: "/help-wanted",
|
||||||
name: "HelpWanted",
|
name: "HelpWanted",
|
||||||
component: () => import(/* webpackChunkName: "joblist" */ "../views/listing/HelpWanted.vue")
|
component: () => import(/* webpackChunkName: "joblist" */ "../views/listing/HelpWanted.vue"),
|
||||||
|
meta: { auth: true, title: "Help Wanted" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/listing/:id/edit",
|
path: "/listing/:id/edit",
|
||||||
name: "EditListing",
|
name: "EditListing",
|
||||||
component: () => import(/* webpackChunkName: "jobedit" */ "../views/listing/ListingEdit.vue")
|
component: () => import(/* webpackChunkName: "jobedit" */ "../views/listing/ListingEdit.vue"),
|
||||||
|
meta: { auth: true, title: "Edit Job Listing" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/listing/:id/expire",
|
path: "/listing/:id/expire",
|
||||||
name: "ExpireListing",
|
name: "ExpireListing",
|
||||||
component: () => import(/* webpackChunkName: "jobedit" */ "../views/listing/ListingExpire.vue")
|
component: () => import(/* webpackChunkName: "jobedit" */ "../views/listing/ListingExpire.vue"),
|
||||||
|
meta: { auth: true, title: "Expire Job Listing" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/listing/:id/view",
|
path: "/listing/:id/view",
|
||||||
name: "ViewListing",
|
name: "ViewListing",
|
||||||
component: () => import(/* webpackChunkName: "joblist" */ "../views/listing/ListingView.vue")
|
component: () => import(/* webpackChunkName: "joblist" */ "../views/listing/ListingView.vue"),
|
||||||
|
meta: { auth: true, title: "Loading Job Listing..." }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/listings/mine",
|
path: "/listings/mine",
|
||||||
name: "MyListings",
|
name: "MyListings",
|
||||||
component: () => import(/* webpackChunkName: "joblist" */ "../views/listing/MyListings.vue")
|
component: () => import(/* webpackChunkName: "joblist" */ "../views/listing/MyListings.vue"),
|
||||||
|
meta: { auth: true, title: "My Job Listings" }
|
||||||
},
|
},
|
||||||
// Profile URLs
|
// Profile URLs
|
||||||
{
|
{
|
||||||
path: "/profile/:id/view",
|
path: "/profile/:id/view",
|
||||||
name: "ViewProfile",
|
name: "ViewProfile",
|
||||||
component: () => import(/* webpackChunkName: "profview" */ "../views/profile/ProfileView.vue")
|
component: () => import(/* webpackChunkName: "profview" */ "../views/profile/ProfileView.vue"),
|
||||||
|
meta: { auth: true, title: "Loading Profile..." }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/profile/search",
|
path: "/profile/search",
|
||||||
name: "SearchProfiles",
|
name: "SearchProfiles",
|
||||||
component: () => import(/* webpackChunkName: "profview" */ "../views/profile/ProfileSearch.vue")
|
component: () => import(/* webpackChunkName: "profview" */ "../views/profile/ProfileSearch.vue"),
|
||||||
|
meta: { auth: true, title: "Search Profiles" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/profile/seeking",
|
path: "/profile/seeking",
|
||||||
name: "PublicSearchProfiles",
|
name: "PublicSearchProfiles",
|
||||||
component: () => import(/* webpackChunkName: "seeking" */ "../views/profile/Seeking.vue")
|
component: () => import(/* webpackChunkName: "seeking" */ "../views/profile/Seeking.vue"),
|
||||||
|
meta: { auth: false, title: "People Seeking Work" }
|
||||||
},
|
},
|
||||||
// "So Long" URLs
|
// "So Long" URLs
|
||||||
{
|
{
|
||||||
path: "/so-long/options",
|
path: "/so-long/options",
|
||||||
name: "DeletionOptions",
|
name: "DeletionOptions",
|
||||||
component: () => import(/* webpackChunkName: "so-long" */ "../views/so-long/DeletionOptions.vue")
|
component: () => import(/* webpackChunkName: "so-long" */ "../views/so-long/DeletionOptions.vue"),
|
||||||
|
meta: { auth: true, title: "Account Deletion Options" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/so-long/success/:abbr",
|
path: "/so-long/success/:abbr",
|
||||||
name: "DeletionSuccess",
|
name: "DeletionSuccess",
|
||||||
component: () => import(/* webpackChunkName: "so-long" */ "../views/so-long/DeletionSuccess.vue")
|
component: () => import(/* webpackChunkName: "so-long" */ "../views/so-long/DeletionSuccess.vue"),
|
||||||
|
meta: { auth: false, title: "Account Deletion Success" }
|
||||||
},
|
},
|
||||||
// Success Story URLs
|
// Success Story URLs
|
||||||
{
|
{
|
||||||
path: "/success-story/list",
|
path: "/success-story/list",
|
||||||
name: "ListStories",
|
name: "ListStories",
|
||||||
component: () => import(/* webpackChunkName: "success" */ "../views/success-story/StoryList.vue")
|
component: () => import(/* webpackChunkName: "success" */ "../views/success-story/StoryList.vue"),
|
||||||
|
meta: { auth: false, title: "Success Stories" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/success-story/:id/edit",
|
path: "/success-story/:id/edit",
|
||||||
name: "EditStory",
|
name: "EditStory",
|
||||||
component: () => import(/* webpackChunkName: "succedit" */ "../views/success-story/StoryEdit.vue")
|
component: () => import(/* webpackChunkName: "succedit" */ "../views/success-story/StoryEdit.vue"),
|
||||||
|
meta: { auth: false, title: "Edit Success Story" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/success-story/:id/view",
|
path: "/success-story/:id/view",
|
||||||
name: "ViewStory",
|
name: "ViewStory",
|
||||||
component: () => import(/* webpackChunkName: "success" */ "../views/success-story/StoryView.vue")
|
component: () => import(/* webpackChunkName: "success" */ "../views/success-story/StoryView.vue"),
|
||||||
|
meta: { auth: false, title: "Success Story" }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
/** The routes that do not require logins */
|
|
||||||
const publicRoutes : Array<RouteRecordName> = [
|
|
||||||
"Home", "HowItWorks", "PrivacyPolicy", "TermsOfService", "LogOn", "CitizenAuthorized", "PublicSearchProfiles",
|
|
||||||
"DeletionSuccess"
|
|
||||||
]
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(process.env.BASE_URL),
|
history: createWebHistory(process.env.BASE_URL),
|
||||||
|
@ -158,11 +174,12 @@ const router = createRouter({
|
||||||
})
|
})
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
router.beforeEach((to : RouteLocationNormalized, from : RouteLocationNormalized) =>{
|
router.beforeEach((to : RouteLocationNormalized, from : RouteLocationNormalized) => {
|
||||||
if (store.state.user === undefined && !publicRoutes.includes(to.name ?? "")) {
|
if (store.state.user === undefined && (to.meta.auth || false)) {
|
||||||
window.localStorage.setItem(AFTER_LOG_ON_URL, to.fullPath)
|
window.localStorage.setItem(AFTER_LOG_ON_URL, to.fullPath)
|
||||||
return "/citizen/log-on"
|
return "/citizen/log-on"
|
||||||
}
|
}
|
||||||
|
store.commit(Mutations.SetTitle, to.meta.title ?? "")
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useTitle } from "@vueuse/core"
|
||||||
import { InjectionKey } from "vue"
|
import { InjectionKey } from "vue"
|
||||||
import { createStore, Store, useStore as baseUseStore } from "vuex"
|
import { createStore, Store, useStore as baseUseStore } from "vuex"
|
||||||
import api, { Continent, Instance, LogOnSuccess } from "../api"
|
import api, { Continent, Instance, LogOnSuccess } from "../api"
|
||||||
|
@ -6,14 +7,16 @@ import * as Mutations from "./mutations"
|
||||||
|
|
||||||
/** The state tracked by the application */
|
/** The state tracked by the application */
|
||||||
export interface State {
|
export interface State {
|
||||||
|
/** The document's current title */
|
||||||
|
pageTitle : string
|
||||||
/** The currently logged-on user */
|
/** The currently logged-on user */
|
||||||
user: LogOnSuccess | undefined
|
user : LogOnSuccess | undefined
|
||||||
/** The state of the log on process */
|
/** The state of the log on process */
|
||||||
logOnState: string
|
logOnState : string
|
||||||
/** All continents (use `ensureContinents` action) */
|
/** All continents (use `ensureContinents` action) */
|
||||||
continents: Continent[]
|
continents : Continent[]
|
||||||
/** All instances (use `ensureInstances` action) */
|
/** All instances (use `ensureInstances` action) */
|
||||||
instances: Instance[]
|
instances : Instance[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An injection key to identify this state with Vue */
|
/** An injection key to identify this state with Vue */
|
||||||
|
@ -24,9 +27,13 @@ export function useStore () : Store<State> {
|
||||||
return baseUseStore(key)
|
return baseUseStore(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The application name */
|
||||||
|
const appName = "Jobs, Jobs, Jobs"
|
||||||
|
|
||||||
export default createStore({
|
export default createStore({
|
||||||
state: () : State => {
|
state: () : State => {
|
||||||
return {
|
return {
|
||||||
|
pageTitle: "",
|
||||||
user: undefined,
|
user: undefined,
|
||||||
logOnState: "<em>Welcome back!</em>",
|
logOnState: "<em>Welcome back!</em>",
|
||||||
continents: [],
|
continents: [],
|
||||||
|
@ -34,6 +41,10 @@ export default createStore({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
|
[Mutations.SetTitle]: (state, title : string) => {
|
||||||
|
state.pageTitle = title === "" ? appName : `${title} | ${appName}`
|
||||||
|
useTitle(state.pageTitle)
|
||||||
|
},
|
||||||
[Mutations.SetUser]: (state, user : LogOnSuccess) => { state.user = user },
|
[Mutations.SetUser]: (state, user : LogOnSuccess) => { state.user = user },
|
||||||
[Mutations.ClearUser]: (state) => { state.user = undefined },
|
[Mutations.ClearUser]: (state) => { state.user = undefined },
|
||||||
[Mutations.SetLogOnState]: (state, message : string) => { state.logOnState = message },
|
[Mutations.SetLogOnState]: (state, message : string) => { state.logOnState = message },
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
/** Set the page title */
|
||||||
|
export const SetTitle = "setTitle"
|
||||||
|
|
||||||
/** Set the logged-on user */
|
/** Set the logged-on user */
|
||||||
export const SetUser = "setUser"
|
export const SetUser = "setUser"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(title="Welcome!")
|
|
||||||
p
|
p
|
||||||
p.
|
p.
|
||||||
Welcome to Jobs, Jobs, Jobs (AKA No Agenda Careers), where citizens of Gitmo Nation can assist one another in
|
Welcome to Jobs, Jobs, Jobs (AKA No Agenda Careers), where citizens of Gitmo Nation can assist one another in
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(title="How It Works")
|
|
||||||
h3 How It Works
|
h3 How It Works
|
||||||
h5.pb-3.text-muted: em Last Updated August 29#[sup th], 2021
|
h5.pb-3.text-muted: em Last Updated August 29#[sup th], 2021
|
||||||
p: em.
|
p: em.
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(title="Privacy Policy")
|
|
||||||
h3 Privacy Policy
|
h3 Privacy Policy
|
||||||
p: em (as of September 6#[sup th], 2021)
|
p: em (as of September 6#[sup th], 2021)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(title="Terms of Service")
|
|
||||||
h3 Terms of Service
|
h3 Terms of Service
|
||||||
p: em (as of September 6#[sup th], 2021)
|
p: em (as of September 6#[sup th], 2021)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(title="Logging on...")
|
|
||||||
p
|
p
|
||||||
p(v-html="message")
|
p(v-html="message")
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article.container
|
article.container
|
||||||
page-title(title="Dashboard")
|
|
||||||
h3.pb-4 Welcome, {{user.name}}
|
h3.pb-4 Welcome, {{user.name}}
|
||||||
load-data(:load="retrieveData"): .row.row-cols-1.row-cols-md-2
|
load-data(:load="retrieveData"): .row.row-cols-1.row-cols-md-2
|
||||||
.col: .card.h-100
|
.col: .card.h-100
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(title="Edit Profile")
|
|
||||||
h3.pb-3 My Employment Profile
|
h3.pb-3 My Employment Profile
|
||||||
load-data(:load="retrieveData"): form.row.g-3
|
load-data(:load="retrieveData"): form.row.g-3
|
||||||
.col-12.col-sm-10.col-md-8.col-lg-6
|
.col-12.col-sm-10.col-md-8.col-lg-6
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(title="Logging off...")
|
|
||||||
p
|
p
|
||||||
p.fst-italic Logging off…
|
p.fst-italic Logging off…
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(title="Help Wanted")
|
|
||||||
h3.pb-3 Help Wanted
|
h3.pb-3 Help Wanted
|
||||||
p(v-if="!searched").
|
p(v-if="!searched").
|
||||||
Enter relevant criteria to find results, or just click “Search” to see all current job listings.
|
Enter relevant criteria to find results, or just click “Search” to see all current job listings.
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(:title="isNew ? 'Add a Job Listing' : 'Edit Job Listing'")
|
|
||||||
h3.pb-3(v-if="isNew") Add a Job Listing
|
h3.pb-3(v-if="isNew") Add a Job Listing
|
||||||
h3.pb-3(v-else) Edit Job Listing
|
h3.pb-3(v-else) Edit Job Listing
|
||||||
load-data(:load="retrieveData"): form.row.g-3
|
load-data(:load="retrieveData"): form.row.g-3
|
||||||
|
@ -44,7 +43,7 @@ import { required } from "@vuelidate/validators"
|
||||||
|
|
||||||
import api, { Listing, ListingForm, LogOnSuccess } from "@/api"
|
import api, { Listing, ListingForm, LogOnSuccess } from "@/api"
|
||||||
import { toastError, toastSuccess } from "@/components/layout/AppToaster.vue"
|
import { toastError, toastSuccess } from "@/components/layout/AppToaster.vue"
|
||||||
import { useStore } from "@/store"
|
import { Mutations, useStore } from "@/store"
|
||||||
|
|
||||||
import ContinentList from "@/components/ContinentList.vue"
|
import ContinentList from "@/components/ContinentList.vue"
|
||||||
import LoadData from "@/components/LoadData.vue"
|
import LoadData from "@/components/LoadData.vue"
|
||||||
|
@ -99,6 +98,7 @@ const v$ = useVuelidate(rules, listing, { $lazy: true })
|
||||||
|
|
||||||
/** Retrieve the listing being edited (or set up the form for a new listing) */
|
/** Retrieve the listing being edited (or set up the form for a new listing) */
|
||||||
const retrieveData = async (errors : string[]) => {
|
const retrieveData = async (errors : string[]) => {
|
||||||
|
if (isNew.value) store.commit(Mutations.SetTitle, "Add a Job Listing")
|
||||||
const listResult = isNew.value ? newListing : await api.listings.retreive(id, user)
|
const listResult = isNew.value ? newListing : await api.listings.retreive(id, user)
|
||||||
if (typeof listResult === "string") {
|
if (typeof listResult === "string") {
|
||||||
errors.push(listResult)
|
errors.push(listResult)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(title="Expire Job Listing")
|
|
||||||
load-data(:load="retrieveListing")
|
load-data(:load="retrieveListing")
|
||||||
h3.pb-3 Expire Job Listing ({{listing.title}})
|
h3.pb-3 Expire Job Listing ({{listing.title}})
|
||||||
p: em.
|
p: em.
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(:title="title")
|
|
||||||
load-data(:load="retrieveListing")
|
load-data(:load="retrieveListing")
|
||||||
h3
|
h3
|
||||||
| {{it.listing.title}}
|
| {{it.listing.title}}
|
||||||
|
@ -24,7 +23,7 @@ import { formatNeededBy } from "./"
|
||||||
import api, { Citizen, ListingForView, LogOnSuccess } from "@/api"
|
import api, { Citizen, ListingForView, LogOnSuccess } from "@/api"
|
||||||
import { citizenName } from "@/App.vue"
|
import { citizenName } from "@/App.vue"
|
||||||
import { toHtml } from "@/markdown"
|
import { toHtml } from "@/markdown"
|
||||||
import { useStore } from "@/store"
|
import { Mutations, useStore } from "@/store"
|
||||||
import LoadData from "@/components/LoadData.vue"
|
import LoadData from "@/components/LoadData.vue"
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
@ -48,6 +47,7 @@ const retrieveListing = async (errors : string[]) => {
|
||||||
errors.push("Job Listing not found")
|
errors.push("Job Listing not found")
|
||||||
} else {
|
} else {
|
||||||
it.value = listingResp
|
it.value = listingResp
|
||||||
|
store.commit(Mutations.SetTitle, `${listingResp.listing.title} | Job Listing`)
|
||||||
const citizenResp = await api.citizen.retrieve(listingResp.listing.citizenId, user)
|
const citizenResp = await api.citizen.retrieve(listingResp.listing.citizenId, user)
|
||||||
if (typeof citizenResp === "string") {
|
if (typeof citizenResp === "string") {
|
||||||
errors.push(citizenResp)
|
errors.push(citizenResp)
|
||||||
|
@ -59,9 +59,6 @@ const retrieveListing = async (errors : string[]) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The page title (changes once the listing is loaded) */
|
|
||||||
const title = computed(() => it.value ? `${it.value.listing.title} | Job Listing` : "Loading Job Listing...")
|
|
||||||
|
|
||||||
/** The HTML details of the job listing */
|
/** The HTML details of the job listing */
|
||||||
const details = computed(() => toHtml(it.value?.listing.text ?? ""))
|
const details = computed(() => toHtml(it.value?.listing.text ?? ""))
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(title="My Job Listings")
|
|
||||||
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")
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(title="Search Profiles")
|
|
||||||
h3.pb-3 Search Profiles
|
h3.pb-3 Search Profiles
|
||||||
p(v-if="!searched").
|
p(v-if="!searched").
|
||||||
Enter one or more criteria to filter results, or just click “Search” to list all profiles.
|
Enter one or more criteria to filter results, or just click “Search” to list all profiles.
|
||||||
|
@ -13,23 +12,25 @@ article
|
||||||
thead: tr
|
thead: tr
|
||||||
th(scope="col") Profile
|
th(scope="col") Profile
|
||||||
th(scope="col") Name
|
th(scope="col") Name
|
||||||
th.text-center(scope="col") Seeking?
|
th.text-center(scope="col" v-if="wideDisplay") Seeking?
|
||||||
th.text-center(scope="col") Remote?
|
th.text-center(scope="col") Remote?
|
||||||
th.text-center(scope="col") Full-Time?
|
th.text-center(scope="col" v-if="wideDisplay") Full-Time?
|
||||||
th(scope="col") Last Updated
|
th(scope="col" v-if="wideDisplay") 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="{ 'fw-bold' : profile.seekingEmployment }") {{profile.displayName}}
|
td(:class="{ 'fw-bold' : profile.seekingEmployment }") {{profile.displayName}}
|
||||||
td.text-center {{yesOrNo(profile.seekingEmployment)}}
|
td.text-center(v-if="wideDisplay") {{yesOrNo(profile.seekingEmployment)}}
|
||||||
td.text-center {{yesOrNo(profile.remoteWork)}}
|
td.text-center {{yesOrNo(profile.remoteWork)}}
|
||||||
td.text-center {{yesOrNo(profile.fullTime)}}
|
td.text-center(v-if="wideDisplay") {{yesOrNo(profile.fullTime)}}
|
||||||
td: full-date(:date="profile.lastUpdatedOn")
|
td(v-if="wideDisplay"): full-date(:date="profile.lastUpdatedOn")
|
||||||
p.pt-3(v-else-if="searched") No results found for the specified criteria
|
p.pt-3(v-else-if="searched") No results found for the specified criteria
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent, Ref, ref, watch } from "vue"
|
import { Ref, ref, watch } from "vue"
|
||||||
import { useRoute, useRouter } from "vue-router"
|
import { useRoute, useRouter } from "vue-router"
|
||||||
|
import { useBreakpoints, breakpointsBootstrapV5 } from "@vueuse/core"
|
||||||
|
|
||||||
import { yesOrNo } from "@/App.vue"
|
import { yesOrNo } from "@/App.vue"
|
||||||
import api, { LogOnSuccess, ProfileSearch, ProfileSearchResult } from "@/api"
|
import api, { LogOnSuccess, ProfileSearch, ProfileSearchResult } from "@/api"
|
||||||
import { queryValue } from "@/router"
|
import { queryValue } from "@/router"
|
||||||
|
@ -43,6 +44,7 @@ import ProfileSearchForm from "@/components/profile/SearchForm.vue"
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const breakpoints = useBreakpoints(breakpointsBootstrapV5)
|
||||||
|
|
||||||
/** Any errors encountered while retrieving data */
|
/** Any errors encountered while retrieving data */
|
||||||
const errors : Ref<string[]> = ref([])
|
const errors : Ref<string[]> = ref([])
|
||||||
|
@ -70,6 +72,9 @@ const results : Ref<ProfileSearchResult[]> = ref([])
|
||||||
/** Whether the search criteria should be collapsed */
|
/** Whether the search criteria should be collapsed */
|
||||||
const isCollapsed = ref(searched.value && results.value.length > 0)
|
const isCollapsed = ref(searched.value && results.value.length > 0)
|
||||||
|
|
||||||
|
/** Hide certain columns if the display is too narrow */
|
||||||
|
const wideDisplay = breakpoints.greater("sm")
|
||||||
|
|
||||||
/** Set up the page to match its requested state */
|
/** Set up the page to match its requested state */
|
||||||
const setUpPage = async () => {
|
const setUpPage = async () => {
|
||||||
if (queryValue(route, "searched") === "true") {
|
if (queryValue(route, "searched") === "true") {
|
||||||
|
|
|
@ -34,7 +34,7 @@ import { mdiPencil } from "@mdi/js"
|
||||||
import api, { LogOnSuccess, ProfileForView } from "@/api"
|
import api, { LogOnSuccess, ProfileForView } from "@/api"
|
||||||
import { citizenName } from "@/App.vue"
|
import { citizenName } from "@/App.vue"
|
||||||
import { toHtml } from "@/markdown"
|
import { toHtml } from "@/markdown"
|
||||||
import { useStore } from "@/store"
|
import { Mutations, useStore } from "@/store"
|
||||||
import LoadData from "@/components/LoadData.vue"
|
import LoadData from "@/components/LoadData.vue"
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
@ -66,14 +66,10 @@ const retrieveProfile = async (errors : string[]) => {
|
||||||
errors.push("Profile not found")
|
errors.push("Profile not found")
|
||||||
} else {
|
} else {
|
||||||
it.value = profileResp
|
it.value = profileResp
|
||||||
|
store.commit(Mutations.SetTitle, `Employment profile for ${citizenName(profileResp.citizen)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The title of the page (changes once the profile is loaded) */
|
|
||||||
const title = computed(() => it.value
|
|
||||||
? `Employment profile for ${citizenName(it.value.citizen)}`
|
|
||||||
: "Loading Profile...")
|
|
||||||
|
|
||||||
/** The HTML version of the citizen's professional biography */
|
/** The HTML version of the citizen's professional biography */
|
||||||
const bioHtml = computed(() => toHtml(it.value?.profile.biography ?? ""))
|
const bioHtml = computed(() => toHtml(it.value?.profile.biography ?? ""))
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(title="People Seeking Work")
|
|
||||||
h3.pb-3 People Seeking Work
|
h3.pb-3 People Seeking Work
|
||||||
p(v-if="!searched").
|
p(v-if="!searched").
|
||||||
Enter one or more criteria to filter results, or just click “Search” to list all profiles.
|
Enter one or more criteria to filter results, or just click “Search” to list all profiles.
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(title="Account Deletion Options")
|
|
||||||
h3.pb-3 Account Deletion Options
|
h3.pb-3 Account Deletion Options
|
||||||
h4.pb-3 Option 1 – Delete Your Profile
|
h4.pb-3 Option 1 – Delete Your Profile
|
||||||
p.
|
p.
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(title="Account Deletion Success")
|
|
||||||
h3.pb-3 Account Deletion Success
|
h3.pb-3 Account Deletion Success
|
||||||
p.
|
p.
|
||||||
Your account has been successfully deleted. To revoke the permissions you have previously granted to this
|
Your account has been successfully deleted. To revoke the permissions you have previously granted to this
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(:title="title")
|
|
||||||
h3.pb-3 {{title}}
|
h3.pb-3 {{title}}
|
||||||
load-data(:load="retrieveStory")
|
load-data(:load="retrieveStory")
|
||||||
p(v-if="isNew").
|
p(v-if="isNew").
|
||||||
|
@ -26,7 +25,7 @@ import useVuelidate from "@vuelidate/core"
|
||||||
|
|
||||||
import api, { LogOnSuccess, StoryForm } from "@/api"
|
import api, { LogOnSuccess, StoryForm } from "@/api"
|
||||||
import { toastError, toastSuccess } from "@/components/layout/AppToaster.vue"
|
import { toastError, toastSuccess } from "@/components/layout/AppToaster.vue"
|
||||||
import { useStore } from "@/store"
|
import { Mutations, useStore } from "@/store"
|
||||||
|
|
||||||
import LoadData from "@/components/LoadData.vue"
|
import LoadData from "@/components/LoadData.vue"
|
||||||
import MarkdownEditor from "@/components/MarkdownEditor.vue"
|
import MarkdownEditor from "@/components/MarkdownEditor.vue"
|
||||||
|
@ -45,9 +44,6 @@ const id = route.params.id as string
|
||||||
/** Whether this is a new story */
|
/** Whether this is a new story */
|
||||||
const isNew = computed(() => id === "new")
|
const isNew = computed(() => id === "new")
|
||||||
|
|
||||||
/** The page title */
|
|
||||||
const title = computed(() => isNew.value ? "Tell Your Success Story" : "Edit Success Story")
|
|
||||||
|
|
||||||
/** The form for editing the story */
|
/** The form for editing the story */
|
||||||
const story = reactive(new StoryForm())
|
const story = reactive(new StoryForm())
|
||||||
|
|
||||||
|
@ -64,6 +60,7 @@ const v$ = useVuelidate(rules, story, { $lazy: true })
|
||||||
const retrieveStory = async (errors : string[]) => {
|
const retrieveStory = async (errors : string[]) => {
|
||||||
if (isNew.value) {
|
if (isNew.value) {
|
||||||
story.id = "new"
|
story.id = "new"
|
||||||
|
store.commit(Mutations.SetTitle, "Tell Your Success Story")
|
||||||
} else {
|
} else {
|
||||||
const storyResult = await api.success.retrieve(id, user)
|
const storyResult = await api.success.retrieve(id, user)
|
||||||
if (typeof storyResult === "string") {
|
if (typeof storyResult === "string") {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(title="Success Stories")
|
|
||||||
h3.pb-3 Success Stories
|
h3.pb-3 Success Stories
|
||||||
load-data(:load="retrieveStories")
|
load-data(:load="retrieveStories")
|
||||||
table.table.table-sm.table-hover(v-if="stories?.length > 0")
|
table.table.table-sm.table-hover(v-if="stories?.length > 0")
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(title="Success Story")
|
|
||||||
load-data(:load="retrieveStory")
|
load-data(:load="retrieveStory")
|
||||||
h3
|
h3
|
||||||
| {{citizenName}}’s Success Story
|
| {{citizenName}}’s Success Story
|
||||||
|
|
|
@ -2,5 +2,14 @@ module.exports = {
|
||||||
transpileDependencies: [
|
transpileDependencies: [
|
||||||
'vuetify'
|
'vuetify'
|
||||||
],
|
],
|
||||||
outputDir: '../Api/wwwroot'
|
outputDir: '../Api/wwwroot',
|
||||||
|
configureWebpack: {
|
||||||
|
module: {
|
||||||
|
rules: [{
|
||||||
|
test: /\.mjs$/,
|
||||||
|
include: /node_modules/,
|
||||||
|
type: "javascript/auto"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user