Out with Vuetify, in with vanilla Bootstrap

This commit is contained in:
Daniel J. Summers 2021-08-07 14:14:53 -04:00
parent 7e0f98d249
commit 8fbdc858af
24 changed files with 445 additions and 494 deletions

View File

@ -1200,6 +1200,12 @@
"fastq": "^1.6.0"
}
},
"@popperjs/core": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.3.tgz",
"integrity": "sha512-xDu17cEfh7Kid/d95kB6tZsLOmSWKCZKtprnhVepjsSaCij+lM3mItSJDuuHDMbCWTh8Ejmebwb+KONcCJ0eXQ==",
"dev": true
},
"@soda/friendly-errors-webpack-plugin": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.0.tgz",
@ -1280,6 +1286,16 @@
"@types/node": "*"
}
},
"@types/bootstrap": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.1.0.tgz",
"integrity": "sha512-cR+eQJ/IrgcJZheb7xkKPiPNOa48zkc6fLZ4U9lDNNQp3qiiq3tW1xgrd+VzVJCram/Bnh+DdBsdsPdXKIYClA==",
"dev": true,
"requires": {
"@popperjs/core": "^2.9.2",
"@types/jquery": "*"
}
},
"@types/connect": {
"version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
@ -1347,6 +1363,15 @@
"@types/node": "*"
}
},
"@types/jquery": {
"version": "3.5.6",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.6.tgz",
"integrity": "sha512-SmgCQRzGPId4MZQKDj9Hqc6kSXFNWZFHpELkyK8AQhf8Zr6HKfCzFv9ZC1Fv3FyQttJZOlap3qYb12h61iZAIg==",
"dev": true,
"requires": {
"@types/sizzle": "*"
}
},
"@types/json-schema": {
"version": "7.0.8",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.8.tgz",
@ -1424,6 +1449,12 @@
"@types/node": "*"
}
},
"@types/sizzle": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz",
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
"dev": true
},
"@types/source-list-map": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
@ -3403,6 +3434,11 @@
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true
},
"bootstrap": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.0.tgz",
"integrity": "sha512-bs74WNI9BgBo3cEovmdMHikSKoXnDgA6VQjJ7TyTotU6L7d41ZyCEEelPwkYEzsG/Zjv3ie9IE3EMAje0W9Xew=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -8989,29 +9025,6 @@
"boolbase": "~1.0.0"
}
},
"null-loader": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/null-loader/-/null-loader-3.0.0.tgz",
"integrity": "sha512-hf5sNLl8xdRho4UPBOOeoIwT3WhjYcMUQm0zj44EhD6UscMAz72o2udpoDFBgykucdEDGIcd6SXbc/G6zssbzw==",
"dev": true,
"requires": {
"loader-utils": "^1.2.3",
"schema-utils": "^1.0.0"
},
"dependencies": {
"schema-utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
"integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
"dev": true,
"requires": {
"ajv": "^6.1.0",
"ajv-errors": "^1.0.0",
"ajv-keywords": "^3.1.0"
}
}
}
},
"num2fraction": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
@ -10635,15 +10648,6 @@
"picomatch": "^2.2.1"
}
},
"rechoir": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
"integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
"dev": true,
"requires": {
"resolve": "^1.1.6"
}
},
"regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -10991,11 +10995,6 @@
"inherits": "^2.0.1"
}
},
"roboto-fontface": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz",
"integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g=="
},
"run-async": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
@ -11359,17 +11358,6 @@
"integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==",
"dev": true
},
"shelljs": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz",
"integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==",
"dev": true,
"requires": {
"glob": "^7.0.0",
"interpret": "^1.0.0",
"rechoir": "^0.6.2"
}
},
"signal-exit": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
@ -12865,43 +12853,6 @@
"@vue/shared": "3.1.4"
}
},
"vue-cli-plugin-vuetify": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/vue-cli-plugin-vuetify/-/vue-cli-plugin-vuetify-2.4.1.tgz",
"integrity": "sha512-ZfhvQ13X41atsCSKdSGiHnSJlhAccv4QIFOZmd8kyq6NktpeeWlvQz/dEKBf6u1AWKmKdwiCDuxS2VNT9fxhOA==",
"dev": true,
"requires": {
"null-loader": "^3.0.0",
"semver": "^7.1.2",
"shelljs": "^0.8.3"
},
"dependencies": {
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
"vue-eslint-parser": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.8.0.tgz",
@ -12994,11 +12945,6 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true
},
"vuetify": {
"version": "3.0.0-alpha.9",
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.0.0-alpha.9.tgz",
"integrity": "sha512-F8xwdAp65gUY0kf0lGKMKhYZDAA2j6cqLEc63uqIOq/dZ8R2k3ZSfBMGtt+jOMCt/w28BZ7rx7AuLxPA5oF/pA=="
},
"vuex": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",

View File

@ -10,17 +10,17 @@
},
"dependencies": {
"@mdi/font": "5.9.55",
"bootstrap": "^5.1.0",
"core-js": "^3.6.5",
"date-fns": "^2.23.0",
"date-fns-tz": "^1.1.4",
"marked": "^2.1.3",
"roboto-fontface": "*",
"vue": "^3.0.0",
"vue-router": "^4.0.0-0",
"vuetify": "^3.0.0-alpha.0",
"vuex": "^4.0.0-0"
},
"devDependencies": {
"@types/bootstrap": "^5.1.0",
"@types/marked": "^2.0.4",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
@ -41,7 +41,6 @@
"eslint-plugin-vue": "^7.0.0",
"sass": "~1.32.0",
"sass-loader": "^10.0.0",
"typescript": "~4.1.5",
"vue-cli-plugin-vuetify": "~2.4.1"
"typescript": "~4.1.5"
}
}

View File

@ -1,24 +1,18 @@
<template>
<v-app>
<v-navigation-drawer app>
<app-nav />
</v-navigation-drawer>
<v-app-bar color="secondary" app>
<div class="jjj-app">
<app-nav />
<div class="jjj-main">
<title-bar />
</v-app-bar>
<v-main>
<v-container fluid>
<main class="container-fluid">
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</v-container>
</v-main>
<v-footer app>
</main>
<app-footer />
</v-footer>
</v-app>
</div>
</div>
</template>
<script lang="ts">
@ -27,6 +21,9 @@ import AppFooter from './components/layout/AppFooter.vue'
import AppNav from './components/layout/AppNav.vue'
import TitleBar from './components/layout/TitleBar.vue'
import 'bootstrap/dist/css/bootstrap.min.css'
import '@mdi/font/css/materialdesignicons.css'
export default defineComponent({
name: 'App',
components: {
@ -51,32 +48,20 @@ export function yesOrNo (cond : boolean) : string {
// Overall app styles
html
scroll-behavior: smooth
h3
font-size: 1.75rem
h4
font-size: 1.5rem
h1, h2, h3, h4, h5
margin-bottom: .5rem
font-weight: 500
line-height: 1.2
p
padding-bottom: 1rem
ul
padding-bottom: 1rem
margin-left: 1.5rem
li
list-style-type: disc
a:link,
a:visited
text-decoration: none
a:hover
text-decoration: underline
label.jjj-required::after
color: red
content: ' *'
// Styles for this component
.v-navigation-drawer
background-image: linear-gradient(180deg, darkgreen 0%, green 70%)
height: 100vh
.v-app-bar
height: 3.5rem !important
.jjj-app
display: flex
align-items: center
justify-content: center
.v-footer
flex-direction: row-reverse
flex-direction: row
.jjj-main
flex-grow: 1
// Route transitions
.fade-enter-active,
.fade-leave-active

View File

@ -1,16 +1,12 @@
<template>
<v-card>
<v-card-header>
<v-card-header-text>
<v-card-title>
<div class="card">
<div class="card-body">
<h6 class="card-title">
<a href="#" :class="{ 'cp-c': isCollapsed, 'cp-o': !isCollapsed }" @click.prevent="toggle">{{headerText}}</a>
</v-card-title>
</v-card-header-text>
</v-card-header>
<v-card-text v-if="!isCollapsed">
<slot></slot>
</v-card-text>
</v-card>
</h6>
<slot v-if="!isCollapsed"></slot>
</div>
</div>
</template>
<script lang="ts">

View File

@ -0,0 +1,22 @@
<template>
<span :class="iconClass"></span>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Icon',
props: {
icon: {
type: String,
required: true
}
},
setup (props) {
return {
iconClass: `mdi mdi-${props.icon}`
}
}
})
</script>

View File

@ -1,12 +1,19 @@
<template>
<nav class="nav nav-pills">
<v-btn rounded="pill" :color="sourceColor" @click="showMarkdown">Markdown</v-btn> &nbsp;
<v-btn rounded="pill" :color="previewColor" @click="showPreview">Preview</v-btn>
</nav>
<section v-if="preview" class="preview" v-html="previewHtml">
</section>
<textarea v-else :id="id" class="form-control" rows="10" v-text="text"
@input="$emit('update:text', $event.target.value)"></textarea>
<div class="row pb-3">
<div class="col col-xs-12">
<nav class="nav nav-pills pb-1">
<button :class="sourceClass" @click.prevent="showMarkdown">Markdown</button> &nbsp;
<button :class="previewClass" @click.prevent="showPreview">Preview</button>
</nav>
<section v-if="preview" class="preview" v-html="previewHtml">
</section>
<div v-else class="form-floating">
<textarea :id="id" class="form-control md-edit" rows="10" v-text="text"
@input="$emit('update:text', $event.target.value)"></textarea>
<label :for="id">{{label}}</label>
</div>
</div>
</div>
</template>
<script lang="ts">
@ -24,6 +31,10 @@ export default defineComponent({
text: {
type: String,
required: true
},
label: {
type: String,
required: true
}
},
emits: ['update:text'],
@ -45,19 +56,28 @@ export default defineComponent({
preview.value = true
}
/** Button classes for the selected button */
const selected = 'btn btn-primary btn-sm rounded-pill'
/** Button classes for the unselected button */
const unselected = 'btn btn-outline-secondary btn-sm rounded-pill'
return {
preview,
previewHtml,
showMarkdown,
showPreview,
sourceColor: computed(() => preview.value ? '' : 'primary'),
previewColor: computed(() => preview.value ? 'primary' : '')
sourceClass: computed(() => preview.value ? unselected : selected),
previewClass: computed(() => preview.value ? selected : unselected)
}
}
})
</script>
<style lang="sass" scoped>
textarea
.md-edit
width: 100%
// When wrapping this with Bootstrap's floating label, it shrinks the input down to what a normal one-line input
// would be; this overrides that for the textarea in this component specifically
height: inherit !important
</style>

View File

@ -1,8 +1,10 @@
<template>
<p>
Jobs, Jobs, Jobs v{{appVersion}} &bull; <router-link to="/privacy-policy">Privacy Policy</router-link>
&bull; <router-link to="/terms-of-service">Terms of Service</router-link>
</p>
<footer>
<p class="text-muted">
Jobs, Jobs, Jobs v{{appVersion}} &bull; <router-link to="/privacy-policy">Privacy Policy</router-link>
&bull; <router-link to="/terms-of-service">Terms of Service</router-link>
</p>
</footer>
</template>
<script lang="ts">
@ -24,15 +26,12 @@ export default defineComponent({
</script>
<style lang="sass" scoped>
footer
display: flex
flex-direction: row-reverse
p
padding-top: 2rem
color: rgba(0, 0, 0, .4)
padding-right: .5rem
font-style: italic
font-size: .8rem
a:link,
a:visited
color: rgba(0, 0, 0, .4)
text-decoration: none
a:hover
text-decoration: underline
</style>

View File

@ -1,21 +1,21 @@
<template>
<aside>
<aside class="collapse show p-3">
<p class="home-link"><router-link to="/">Jobs, Jobs, Jobs</router-link></p>
<p>&nbsp;</p>
<nav>
<template v-if="isLoggedOn">
<router-link to="/citizen/dashboard"><v-icon icon="mdi-view-dashboard-variant" />Dashboard</router-link>
<router-link to="/citizen/profile"><v-icon icon="mdi-pencil" /> Edit Your Profile</router-link>
<router-link to="/profile/search"><v-icon icon="mdi-view-list-outline" /> View Profiles</router-link>
<router-link to="/success-story/list"><v-icon icon="mdi-thumb-up" /> Success Stories</router-link>
<router-link to="/citizen/log-off"><v-icon icon="mdi-logout-variant" /> Log Off</router-link>
<router-link to="/citizen/dashboard"><icon icon="view-dashboard-variant" /> Dashboard</router-link>
<router-link to="/citizen/profile"><icon icon="pencil" /> Edit Your Profile</router-link>
<router-link to="/profile/search"><icon icon="view-list-outline" /> View Profiles</router-link>
<router-link to="/success-story/list"><icon icon="thumb-up" /> Success Stories</router-link>
<router-link to="/citizen/log-off"><icon icon="logout-variant" /> Log Off</router-link>
</template>
<template v-else>
<router-link to="/"><v-icon icon="mdi-home" /> Home</router-link>
<router-link to="/profile/seeking"><v-icon icon="mdi-view-list-outline" /> Job Seekers</router-link>
<router-link to="/citizen/log-on"><v-icon icon="mdi-login-variant" /> Log On</router-link>
<router-link to="/"><icon icon="home" /> Home</router-link>
<router-link to="/profile/seeking"><icon icon="view-list-outline" /> Job Seekers</router-link>
<router-link to="/citizen/log-on"><icon icon="login-variant" /> Log On</router-link>
</template>
<router-link to="/how-it-works"><v-icon icon="mdi-help-circle-outline" /> How It Works</router-link>
<router-link to="/how-it-works"><icon icon="help-circle-outline" /> How It Works</router-link>
</nav>
</aside>
</template>
@ -39,13 +39,18 @@ export default defineComponent({
<style lang="sass" scoped>
aside
background-image: linear-gradient(180deg, darkgreen 0%, green 70%)
color: white
margin: 1rem
font-size: 1.2rem
height: 100vh
width: 250px
min-width: 250px
position: sticky
top: 0
a:link, a:visited
text-decoration: none
color: white
font-weight: 500
// font-weight: 500
.home-link
font-size: 1.2rem
text-align: center

View File

@ -1,5 +1,10 @@
<template>
<p>(...and Jobs &ndash; <audio-clip clip="pelosi-jobs">Let's Vote for Jobs!</audio-clip>)</p>
<nav class="navbar navbar-light bg-light">
<span></span>
<span class="navbar-text">
(...and Jobs &ndash; <audio-clip clip="pelosi-jobs">Let's Vote for Jobs!</audio-clip>)
</span>
</nav>
</template>
<script lang="ts">
@ -15,9 +20,8 @@ export default defineComponent({
</script>
<style lang="sass" scoped>
p
width: 100%
text-align: right
span
font-style: italic
padding: 0 .5rem 0 0
padding-right: 1rem
// padding: 0 .5rem 0 0
</style>

View File

@ -1,56 +1,62 @@
<template>
<form>
<v-container>
<v-row>
<v-col cols="12" sm="6" md="4" lg="3">
<label for="continentId" class="jjj-label">Continent</label>
<select id="continentId" class="form-control form-control-sm"
:value="criteria.continentId" @change="updateValue('continentId', $event.target.value)">
<form class="container">
<div class="row">
<div class="col col-xs-12 col-sm-6 col-md-4 col-lg-3">
<div class="form-floating">
<select id="continentId" class="form-select" :value="criteria.continentId"
@change="updateValue('continentId', $event.target.value)">
<option value="">&ndash; Any &ndash;</option>
<option v-for="c in continents" :key="c.id" :value="c.id">{{c.name}}</option>
</select>
</v-col>
<v-col cols="12" sm="6" md="4" lg="3">
<label for="region" class="jjj-label">Region</label>
<label for="continentId">Continent</label>
</div>
</div>
<div class="col col-xs-12 col-sm-6 col-md-4 col-lg-3">
<div class="form-floating">
<input type="text" id="region" class="form-control form-control-sm" placeholder="(free-form text)"
:value="criteria.region" @input="updateValue('region', $event.target.value)">
</v-col>
<v-col cols="12" sm="6" offset-md="2" lg="3" offset-lg="0">
<label class="jjj-label">Seeking Remote Work?</label><br>
<div class="form-check form-check-inline">
<input type="radio" id="remoteNull" name="remoteWork" class="form-check-input"
:checked="criteria.remoteWork === ''" @click="updateValue('remoteWork', '')">
<label for="remoteNull" class="form-check-label">No Selection</label>
</div>
<div class="form-check form-check-inline">
<input type="radio" id="remoteYes" name="remoteWork" class="form-check-input"
:checked="criteria.remoteWork === 'yes'" @click="updateValue('remoteWork', 'yes')">
<label for="remoteYes" class="form-check-label">Yes</label>
</div>
<div class="form-check form-check-inline">
<input type="radio" id="remoteNo" name="remoteWork" class="form-check-input"
:checked="criteria.remoteWork === 'no'" @click="updateValue('remoteWork', 'no')">
<label for="remoteNo" class="form-check-label">No</label>
</div>
</v-col>
<v-col cols="12" sm="6" lg="3">
<label for="skillSearch" class="jjj-label">Skill</label>
<label for="region">Region</label>
</div>
<div class="form-text">(free-form text)</div>
</div>
<div class="col col-xs-12 col-sm-6 col-offset-md-2 col-lg-3 col-offset-lg-0">
<label class="jjj-label">Seeking Remote Work?</label><br>
<div class="form-check form-check-inline">
<input type="radio" id="remoteNull" name="remoteWork" class="form-check-input"
:checked="criteria.remoteWork === ''" @click="updateValue('remoteWork', '')">
<label for="remoteNull" class="form-check-label">No Selection</label>
</div>
<div class="form-check form-check-inline">
<input type="radio" id="remoteYes" name="remoteWork" class="form-check-input"
:checked="criteria.remoteWork === 'yes'" @click="updateValue('remoteWork', 'yes')">
<label for="remoteYes" class="form-check-label">Yes</label>
</div>
<div class="form-check form-check-inline">
<input type="radio" id="remoteNo" name="remoteWork" class="form-check-input"
:checked="criteria.remoteWork === 'no'" @click="updateValue('remoteWork', 'no')">
<label for="remoteNo" class="form-check-label">No</label>
</div>
</div>
<div class="col col-xs-12 col-sm-6 col-lg-3">
<div class="form-floating">
<input type="text" id="skillSearch" class="form-control form-control-sm" placeholder="(free-form text)"
:value="criteria.skill" @input="updateValue('skill', $event.target.value)">
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<br>
<v-btn type="submit" color="primary" variant="outlined" @click.prevent="$emit('search')">Search</v-btn>
</v-col>
</v-row>
</v-container>
<label for="skillSearch">Skill</label>
</div>
<div class="form-text">(free-form text)</div>
</div>
</div>
<div class="row">
<div class="col col-xs-12">
<br>
<button type="submit" class="btn btn-outline-primary" @click.prevent="$emit('search')">Search</button>
</div>
</div>
</form>
</template>
<script lang="ts">
import { computed, defineComponent, onMounted } from 'vue'
import { computed, defineComponent, onMounted, ref, Ref } from 'vue'
import { PublicSearch } from '@/api'
import { useStore } from '@/store'
@ -67,7 +73,7 @@ export default defineComponent({
const store = useStore()
/** The initial search criteria passed; this is what we'll update and emit when data changes */
const criteria : PublicSearch = { ...props.modelValue as PublicSearch }
const criteria : Ref<PublicSearch> = ref({ ...props.modelValue as PublicSearch })
/** Make sure we have continents */
onMounted(async () => await store.dispatch('ensureContinents'))
@ -75,7 +81,10 @@ export default defineComponent({
return {
criteria,
continents: computed(() => store.state.continents),
updateValue: (key : string, value : string) => emit('update:modelValue', { ...criteria, [key]: value })
updateValue: (key : string, value : string) => {
criteria.value = { ...criteria.value, [key]: value }
emit('update:modelValue', criteria.value)
}
}
}
})

View File

@ -1,56 +1,62 @@
<template>
<form>
<v-container>
<v-row>
<v-col cols="12" sm="6" md="4" lg="3">
<label for="continentId" class="jjj-label">Continent</label>
<select id="continentId" class="form-control form-control-sm"
<form class="container">
<div class="row">
<div class="col col-xs-12 col-sm-6 col-md-4 col-lg-3">
<div class="form-floating">
<select id="continentId" class="form-select"
:value="criteria.continentId" @change="updateValue('continentId', $event.target.value)">
<option value="">&ndash; Any &ndash;</option>
<option v-for="c in continents" :key="c.id" :value="c.id">{{c.name}}</option>
</select>
</v-col>
<v-col cols="12" sm="6" offset-md="2" lg="3" offset-lg="0">
<label class="jjj-label">Seeking Remote Work?</label><br>
<div class="form-check form-check-inline">
<input type="radio" id="remoteNull" name="remoteWork" class="form-check-input"
:checked="criteria.remoteWork === ''" @click="updateValue('remoteWork', '')">
<label for="remoteNull" class="form-check-label">No Selection</label>
</div>
<div class="form-check form-check-inline">
<input type="radio" id="remoteYes" name="remoteWork" class="form-check-input"
:checked="criteria.remoteWork === 'yes'" @click="updateValue('remoteWork', 'yes')">
<label for="remoteYes" class="form-check-label">Yes</label>
</div>
<div class="form-check form-check-inline">
<input type="radio" id="remoteNo" name="remoteWork" class="form-check-input"
:checked="criteria.remoteWork === 'no'" @click="updateValue('remoteWork', 'no')">
<label for="remoteNo" class="form-check-label">No</label>
</div>
</v-col>
<v-col cols="12" sm="6" lg="3">
<label for="skillSearch" class="jjj-label">Skill</label>
<input type="text" id="skillSearch" class="form-control form-control-sm" placeholder="(free-form text)"
<label for="continentId">Continent</label>
</div>
</div>
<div class="col col-xs-12 col-sm-6 col-offset-md-2 col-lg-3 col-offset-lg-0">
<label class="jjj-label">Seeking Remote Work?</label><br>
<div class="form-check form-check-inline">
<input type="radio" id="remoteNull" name="remoteWork" class="form-check-input"
:checked="criteria.remoteWork === ''" @click="updateValue('remoteWork', '')">
<label for="remoteNull" class="form-check-label">No Selection</label>
</div>
<div class="form-check form-check-inline">
<input type="radio" id="remoteYes" name="remoteWork" class="form-check-input"
:checked="criteria.remoteWork === 'yes'" @click="updateValue('remoteWork', 'yes')">
<label for="remoteYes" class="form-check-label">Yes</label>
</div>
<div class="form-check form-check-inline">
<input type="radio" id="remoteNo" name="remoteWork" class="form-check-input"
:checked="criteria.remoteWork === 'no'" @click="updateValue('remoteWork', 'no')">
<label for="remoteNo" class="form-check-label">No</label>
</div>
</div>
<div class="col col-xs-12 col-sm-6 col-lg-3">
<div class="form-floating">
<input type="text" id="skillSearch" class="form-control" placeholder="(free-form text)"
:value="criteria.skill" @input="updateValue('skill', $event.target.value)">
</v-col>
<v-col cols="12" sm="6" lg="3">
<label for="bioSearch" class="jjj-label">Bio / Experience</label>
<input type="text" id="bioSearch" class="form-control form-control-sm" placeholder="(free-form text)"
<label for="skillSearch" class="jjj-label">Skill</label>
</div>
<div class="form-text">(free-form text)</div>
</div>
<div class="col col-xs-12 col-sm-6 col-lg-3">
<div class="form-floating">
<input type="text" id="bioSearch" class="form-control" placeholder="(free-form text)"
:value="criteria.bioExperience" @input="updateValue('bioExperience', $event.target.value)">
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<br>
<v-btn type="submit" color="primary" variant="outlined" @click.prevent="$emit('search')">Search</v-btn>
</v-col>
</v-row>
</v-container>
<label for="bioSearch" class="jjj-label">Bio / Experience</label>
</div>
<div class="form-text">(free-form text)</div>
</div>
</div>
<div class="row">
<div class="col col-xs-12">
<br>
<button type="submit" class="btn btn-outline-primary" @click.prevent="$emit('search')">Search</button>
</div>
</div>
</form>
</template>
<script lang="ts">
import { computed, defineComponent, onMounted } from 'vue'
import { computed, defineComponent, onMounted, Ref, ref } from 'vue'
import { ProfileSearch } from '@/api'
import { useStore } from '@/store'
@ -67,7 +73,7 @@ export default defineComponent({
const store = useStore()
/** The initial search criteria passed; this is what we'll update and emit when data changes */
const criteria : ProfileSearch = { ...props.modelValue as ProfileSearch }
const criteria : Ref<ProfileSearch> = ref({ ...props.modelValue as ProfileSearch })
/** Make sure we have continents */
onMounted(async () => await store.dispatch('ensureContinents'))
@ -75,7 +81,10 @@ export default defineComponent({
return {
criteria,
continents: computed(() => store.state.continents),
updateValue: (key : string, value : string) => emit('update:modelValue', { ...criteria, [key]: value })
updateValue: (key : string, value : string) => {
criteria.value = { ...criteria.value, [key]: value }
emit('update:modelValue', criteria.value)
}
}
}
})

View File

@ -1,26 +1,33 @@
<template>
<v-row>
<v-col cols="2" md="1">
<br>
<v-btn color="danger" variant="outlined" title="Delete" @click="$emit('remove')">&minus;</v-btn>
</v-col>
<v-col cols="10" md="6">
<label :for="`skillDesc${skill.id}`" class="jjj-label">Skill</label>
<input type="text" :id="`skillDesc${skill.id}`" maxlength="100"
placeholder="A skill (language, design technique, process, etc.)"
:value="skill.description" @input="updateValue('description', $event.target.value)">
</v-col>
<v-col cols="12" md="5">
<label :for="`skillNotes${skill.id}`" class="jjj-label">Notes</label>
<input type="text" :id="`skillNotes${skill.id}`" maxlength="100"
placeholder="A further description of the skill (100 characters max)"
:value="skill.notes" @input="updateValue('notes', $event.target.value)">
</v-col>
</v-row>
<div class="row pb-3">
<div class="col col-xs-2 col-md-1 align-self-center">
<button class="btn btn-sm btn-outline-danger rounded-pill" title="Delete" @click.prevent="$emit('remove')">
&nbsp;&minus;&nbsp;
</button>
</div>
<div class="col col-xs-10 col-md-6">
<div class="form-floating">
<input type="text" :id="`skillDesc${skill.id}`" class="form-control" maxlength="100"
placeholder="A skill (language, design technique, process, etc.)"
:value="skill.description" @input="updateValue('description', $event.target.value)">
<label :for="`skillDesc${skill.id}`" class="jjj-label">Skill</label>
</div>
<div class="form-text">A skill (language, design technique, process, etc.)</div>
</div>
<div class="col col-xs-12 col-md-5">
<div class="form-floating">
<input type="text" :id="`skillNotes${skill.id}`" class="form-control" maxlength="100"
placeholder="A further description of the skill (100 characters max)"
:value="skill.notes" @input="updateValue('notes', $event.target.value)">
<label :for="`skillNotes${skill.id}`" class="jjj-label">Notes</label>
</div>
<div class="form-text">A further description of the skill (100 characters max)</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { defineComponent, Ref, ref } from 'vue'
import { Skill } from '@/api'
export default defineComponent({
@ -34,11 +41,14 @@ export default defineComponent({
emits: ['remove', 'update:modelValue'],
setup (props, { emit }) {
/** The skill being edited */
const skill : Skill = { ...props.modelValue as Skill }
const skill : Ref<Skill> = ref({ ...props.modelValue as Skill })
return {
skill,
updateValue: (key : string, value : string) => emit('update:modelValue', { ...skill, [key]: value })
updateValue: (key : string, value : string) => {
skill.value = { ...skill.value, [key]: value }
emit('update:modelValue', skill.value)
}
}
}
})

View File

@ -1,15 +1,15 @@
import { createApp } from 'vue'
import vuetify from './plugins/vuetify'
import App from './App.vue'
import router from './router'
import store, { key } from './store'
import Icon from './components/Icon.vue'
import PageTitle from './components/PageTitle.vue'
const app = createApp(App)
.use(router)
.use(store, key)
.use(vuetify)
app.component('Icon', Icon)
app.component('PageTitle', PageTitle)
app.mount('#app')

View File

@ -1,48 +0,0 @@
import '@mdi/font/css/materialdesignicons.css'
import 'vuetify/lib/styles/main.sass'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/lib/components'
import * as directives from 'vuetify/lib/directives'
const jjjTheme = {
dark: false,
colors: {
background: '#ffffff',
surface: '#ffffff',
primary: '#007bff',
'primary-darken-1': '#3700b3',
secondary: '#f7f7f7',
'secondary-darken-1': '#018786',
error: '#b00020',
info: '#2196f3',
success: '#4caf50',
warning: '#fb8c00'
},
variables: {
'border-color': '#000000',
'border-opacity': 0.12,
'high-emphasis-opacity': 0.87,
'medium-emphasis-opacity': 0.60,
'disabled-opacity': 0.38,
'activated-opacity': 0.12,
'hover-opacity': 0.04,
'focus-opacity': 0.12,
'selected-opacity': 0.08,
'dragged-opacity': 0.08,
'pressed-opacity': 0.16,
'kbd-background-color': '#212529',
'kbd-color': '#FFFFFF',
'code-background-color': '#C2C2C2'
}
}
export default createVuetify({
components,
directives,
theme: {
defaultTheme: 'jjjTheme',
themes: {
jjjTheme
}
}
})

View File

@ -1,6 +1,7 @@
<template>
<article>
<page-title title="Welcome!" />
<p>&nbsp;</p>
<p>
Welcome to Jobs, Jobs, Jobs (AKA No Agenda Careers), where citizens of Gitmo Nation can assist one another in
finding employment. This will enable them to continue providing value-for-value to Adam and John, as they continue

View File

@ -1,6 +1,7 @@
<template>
<article>
<page-title title="Logging on..." />
<p>&nbsp;</p>
<p v-html="message"></p>
</article>
</template>

View File

@ -1,19 +1,15 @@
<template>
<article>
<article class="container">
<page-title title="Dashboard" />
<h3>Welcome, {{user.name}}</h3>
<load-data :load="retrieveData">
<v-row class="spaced">
<v-col cols="12" md="6">
<v-card elevation="6">
<v-card-header>
<v-card-header-text>
<v-card-title>Your Profile</v-card-title>
<v-card-subtitle>Last updated <full-date-time :date="profile.lastUpdatedOn" /></v-card-subtitle>
</v-card-header-text>
</v-card-header>
<v-card-text>
<div v-if="profile">
<div class="row row-cols-1 row-cols-md-2">
<div class="col">
<div class="card h-100">
<h5 class="card-header">Your Profile</h5>
<div class="card-body">
<h6 class="card-subtitle mb-3 text-muted">Last updated <full-date :date="profile.lastUpdatedOn" /></h6>
<p v-if="profile" class="card-text">
Your profile currently lists {{profile.skills.length}}
skill<template v-if="profile.skills.length !== 1">s</template>.
<span v-if="profile.seekingEmployment">
@ -21,51 +17,48 @@
Your profile indicates that you are seeking employment. Once you find it,
<router-link to="/success-story/add">tell your fellow citizens about it!</router-link>
</span>
</div>
<div v-else>
</p>
<p v-else class="card-text">
You do not have an employment profile established; click below (or &ldquo;Edit Profile&rdquo; in the
menu) to get started!
</div>
</v-card-text>
<v-card-actions>
</p>
</div>
<div class="card-footer">
<template v-if="profile">
<v-btn v-if="profile" @click="viewProfile">View Profile</v-btn>
<v-btn @click="editProfile">Edit Profile</v-btn>
<button class="btn btn-outline-secondary" @click="viewProfile">View Profile</button> &nbsp; &nbsp;
<button class="btn btn-outline-secondary" @click="editProfile">Edit Profile</button>
</template>
<v-btn v-else @click="editProfile">Create Profile</v-btn>
</v-card-actions>
</v-card>
</v-col>
<v-col cols="12" md="6">
<v-card elevation="6">
<v-card-header>
<v-card-header-text>
<v-card-title>Other Citizens</v-card-title>
<v-card-subtitle>
<template v-if="profileCount === 0">No</template><template v-else>{{profileCount}} Total</template>
Employment Profile<template v-if="profileCount !== 1">s</template>
</v-card-subtitle>
</v-card-header-text>
</v-card-header>
<v-card-text>
<div v-if="profileCount === 1 && profile">
<button v-else class="btn btn-primary" @click="editProfile">Create Profile</button>
</div>
</div>
</div>
<div class="col">
<div class="card h-100">
<h5 class="card-header">Other Citizens</h5>
<div class="card-body">
<h6 class="card-subtitle mb-3 text-muted">
<template v-if="profileCount === 0">No</template><template v-else>{{profileCount}} Total</template>
Employment Profile<template v-if="profileCount !== 1">s</template>
</h6>
<p v-if="profileCount === 1 && profile" class="card-text">
It looks like, for now, it&rsquo;s just you&hellip;
</div>
<div v-else-if="profileCount > 0">
</p>
<p v-else-if="profileCount > 0" class="card-text">
Take a look around and see if you can help them find work!
</div>
<div v-else>
</p>
<p v-else class="card-text">
You can click below, but you will not find anything&hellip;
</div>
</v-card-text>
<v-card-actions>
<v-btn @click="searchProfiles">Search Profiles</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</p>
</div>
<div class="card-footer">
<button class="btn btn-outline-secondary" @click="searchProfiles">Search Profiles</button>
</div>
</div>
</div>
</div>
</load-data>
<p class="spaced">
<p>&nbsp;</p>
<p>
To see how this application works, check out &ldquo;How It Works&rdquo; in the sidebar (last updated June
14<sup>th</sup>, 2021).
</p>
@ -77,14 +70,15 @@ import { defineComponent, Ref, ref } from 'vue'
import { useRouter } from 'vue-router'
import api, { LogOnSuccess, Profile } from '@/api'
import { useStore } from '@/store'
import FullDateTime from '@/components/FullDateTime.vue'
import FullDate from '@/components/FullDate.vue'
import LoadData from '@/components/LoadData.vue'
export default defineComponent({
name: 'Dashboard',
components: {
LoadData,
FullDateTime
FullDate,
LoadData
},
setup () {
const store = useStore()
@ -126,8 +120,3 @@ export default defineComponent({
}
})
</script>
<style lang="sass" scoped>
.spaced
margin-top: 1rem
</style>

View File

@ -4,104 +4,103 @@
<h3>Employment Profile</h3>
<load-data :load="retrieveData">
<form>
<v-container>
<v-row>
<v-col cols="12" sm="10" md="8" lg="6">
<label for="realName">Real Name</label>
<input type="text" id="realName" v-model="profile.realName" maxlength="255"
<div class="row pb-3">
<div class="col col-xs-12 col-sm-10 col-md-8 col-lg-6">
<div class="form-floating">
<input type="text" id="realName" class="form-control" v-model="profile.realName" maxlength="255"
placeholder="Leave blank to use your NAS display name">
</v-col>
</v-row>
<v-row>
<v-col>
<label>
<input type="checkbox" v-model="profile.seekingEmployment">
I am currently seeking employment
</label>
<em v-if="profile?.seekingEmployment">&nbsp; &nbsp; If you have found employment, consider
<router-link to="/success-story/add">telling your fellow citizens about it</router-link>
</em>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="6" md="4">
<label for="continentId" class="jjj-required">Continent</label>
<select id="continentId">
<option v-for="c in continents" :key="c.id" :value="c.id"
:selected="c.id === profile?.continentId ? 'selected' : null">{{c.name}}</option>
<label for="realName">Real Name</label>
</div>
<div class="form-text">Leave blank to use your NAS display name</div>
</div>
</div>
<div class="row pb-3">
<div class="col col-xs-12">
<div class="form-check">
<input type="checkbox" class="form-check-input" v-model="profile.seekingEmployment">
<label class="form-check-label">I am currently seeking employment</label>
</div>
<p v-if="profile?.seekingEmployment">
<em>If you have found employment, consider <router-link to="/success-story/add">telling your fellow
citizens about it!</router-link></em>
</p>
</div>
</div>
<div class="row pb-3">
<div class="col col-xs-12 col-sm-6 col-md-4">
<div class="form-floating">
<select id="continentId" class="form-select" :value="profile.continentId">
<option v-for="c in continents" :key="c.id" :value="c.id">{{c.name}}</option>
</select>
</v-col>
<v-col cols="12" sm="6" md="8">
<label for="region" class="jjj-required">Region</label>
<input type="text" id="region" v-model="profile.region" maxlength="255"
<label for="continentId" class="jjj-required">Continent</label>
</div>
</div>
<div class="col col-xs-12 col-sm-6 col-md-8">
<div class="form-floating">
<input type="text" id="region" class="form-control" v-model="profile.region" maxlength="255"
placeholder="Country, state, geographic area, etc.">
</v-col>
</v-row>
<v-row>
<v-col>
<label for="bio" class="jjj-required">Professional Biography</label>
<markdown-editor id="bio" v-model:text="profile.biography" />
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" offset-md="2" md="4">
<label>
<input type="checkbox" v-model="profile.remoteWork">
I am looking for remote work
</label>
</v-col>
<v-col cols="12" sm="12" md="4">
<label>
<input type="checkbox" v-model="profile.fullTime">
I am looking for full-time work
</label>
</v-col>
</v-row>
<hr>
<h4>
Skills &nbsp;
<v-btn color="primary" variant="outlined" @click="addSkill">Add a Skill</v-btn>
</h4>
<profile-skill-edit v-for="(skill, idx) in profile.skills" :key="skill.id" v-model="profile.skills[idx]"
@remove="removeSkill(skill.id)" />
<hr>
<h4>Experience</h4>
<p>
This application does not have a place to individually list your chronological job history; however, you can
use this area to list prior jobs, their dates, and anything else you want to include that&rsquo;s not
already a part of your Professional Biography above.
</p>
<v-row>
<v-col>
<markdown-editor id="experience" v-model:text="profile.experience" />
</v-col>
</v-row>
<v-row>
<v-col>
<label>
<input type="checkbox" v-model="profile.isPublic">
<label for="region" class="jjj-required">Region</label>
</div>
<div class="form-text">Country, state, geographic area, etc.</div>
</div>
</div>
<markdown-editor id="bio" label="Professional Biography" v-model:text="profile.biography" />
<div class="row pb-3">
<div class="col col-xs-12 col-offset-md-2 col-md-4">
<div class="form-check">
<input type="checkbox" id="isRemote" class="form-check-input" v-model="profile.remoteWork">
<label class="form-check-label" for="isRemote">I am looking for remote work</label>
</div>
</div>
<div class="col col-xs-12 col-md-4">
<div class="form-check">
<input type="checkbox" id="isFullTime" class="form-check-input" v-model="profile.fullTime">
<label class="form-check-label" for="isFullTime">I am looking for full-time work</label>
</div>
</div>
</div>
<hr>
<h4 class="pb-2">
Skills &nbsp;
<button class="btn btn-sm btn-outline-primary rounded-pill" @click.prevent="addSkill">Add a Skill</button>
</h4>
<profile-skill-edit v-for="(skill, idx) in profile.skills" :key="skill.id" v-model="profile.skills[idx]"
@remove="removeSkill(skill.id)" />
<hr>
<h4>Experience</h4>
<p>
This application does not have a place to individually list your chronological job history; however, you can
use this area to list prior jobs, their dates, and anything else you want to include that&rsquo;s not
already a part of your Professional Biography above.
</p>
<markdown-editor id="experience" label="Experience" v-model:text="profile.experience" />
<div class="row pb-3">
<div class="col col-xs-12">
<div class="form-check">
<input type="checkbox" id="isPublic" class="form-check-input" v-model="profile.isPublic">
<label class="form-check-label" for="isPublic">
Allow my profile to be searched publicly (outside NA Social)
</label>
</v-col>
</v-row>
<v-row>
<v-col>
<br>
<v-btn text color="primary">Save</v-btn>
</v-col>
</v-row>
</v-container>
</div>
</div>
</div>
<div class="row pt-3">
<div class="col col-xs-12">
<button class="btn btn-primary">Save</button>
<template v-if="!isNew">
&nbsp; &nbsp;
<button class="btn btn-outline-secondary" @click.prevent="viewProfile">
<icon icon="file-account-outline" />&nbsp; View Your User Profile
</button>
</template>
</div>
</div>
</form>
<p v-if="!isNew">
<br>
<v-btn color="primary" @click="viewProfile">
<v-icon icon="mdi-file-account-outline" />&nbsp; View Your User Profile
</v-btn>
</p>
</load-data>
<p>
<br>If you want to delete your profile, or your entire account, <router-link to="/so-long/options">see your
deletion options here</router-link>.
<hr>
<p class="text-muted fst-italic">
(If you want to delete your profile, or your entire account, <router-link to="/so-long/options">see your deletion
options here</router-link>.)
</p>
</article>
</template>

View File

@ -1,7 +1,8 @@
<template>
<article>
<page-title title="Logging off..." />
<p>Logging off&hellip;</p>
<p>&nbsp;</p>
<p class="fst-italic">Logging off&hellip;</p>
</article>
</template>

View File

@ -1,5 +1,8 @@
<template>
<p><em>Sending you over to No Agenda Social to log on; see you back in just a second&hellip;</em></p>
<article>
<p>&nbsp;</p>
<p class="fst-italic">Sending you over to No Agenda Social to log on; see you back in just a second&hellip;</p>
</article>
</template>
<script lang="ts">

View File

@ -26,7 +26,7 @@
<template v-if="user.citizenId === it.citizen.id">
<br><br>
<v-btn color="primary" @click="editProfile"><v-icon icon="mdi-pencil" />&nbsp; Edit Your Profile</v-btn>
<button class="btn btn-primary" @click="editProfile"><icon icon="pencil" />&nbsp; Edit Your Profile</button>
</template>
</load-data>
</article>

View File

@ -12,7 +12,7 @@
use if you want to clear out your profile and start again (and remove the current one from others&rsquo; view).
</p>
<p class="text-center">
<v-btn color="error" @click="deleteProfile">Delete Your Profile</v-btn>
<button class="btn btn-danger" @click.prevent="deleteProfile">Delete Your Profile</button>
</p>
<hr>
@ -32,7 +32,7 @@
</em>
</p>
<p class="text-center">
<v-btn color="error" @click="deleteAccount">Delete Your Entire Account</v-btn>
<button class="btn btn-danger" @click.prevent="deleteAccount">Delete Your Entire Account</button>
</p>
</article>
</template>

View File

@ -39,14 +39,15 @@
import { defineComponent, ref, Ref } from 'vue'
import api, { LogOnSuccess, StoryEntry } from '@/api'
import { useStore } from '@/store'
import FullDate from '@/components/FullDate.vue'
import LoadData from '@/components/LoadData.vue'
export default defineComponent({
name: 'StoryList',
components: {
LoadData,
FullDate
FullDate,
LoadData
},
setup () {
const store = useStore()