WIP on validate/save profile
This commit is contained in:
parent
058c5e5b77
commit
bd97a3828f
21
src/JobsJobsJobs/App/package-lock.json
generated
21
src/JobsJobsJobs/App/package-lock.json
generated
|
@ -2589,6 +2589,22 @@
|
||||||
"integrity": "sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA==",
|
"integrity": "sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@vuelidate/core": {
|
||||||
|
"version": "2.0.0-alpha.22",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vuelidate/core/-/core-2.0.0-alpha.22.tgz",
|
||||||
|
"integrity": "sha512-HqRWY2c5pW6kxHNCupYyQn1+frjiFjEN6qgP0AwByvSu/JdKHDWlWqC2gJoxPiT3Oy23ulge43jVzHT3UO1r4A==",
|
||||||
|
"requires": {
|
||||||
|
"vue-demi": "^0.9.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@vuelidate/validators": {
|
||||||
|
"version": "2.0.0-alpha.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vuelidate/validators/-/validators-2.0.0-alpha.19.tgz",
|
||||||
|
"integrity": "sha512-5TQxi1Wa6jFPYZ4UIUI8RKR2nqN/yujPxRjUMh5vCOhVXoqXPMfoYdfs5DBtfABhmjj7LTtXrZ78WVMQkL9tNw==",
|
||||||
|
"requires": {
|
||||||
|
"vue-demi": "^0.9.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@webassemblyjs/ast": {
|
"@webassemblyjs/ast": {
|
||||||
"version": "1.9.0",
|
"version": "1.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
|
||||||
|
@ -12853,6 +12869,11 @@
|
||||||
"@vue/shared": "3.1.4"
|
"@vue/shared": "3.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vue-demi": {
|
||||||
|
"version": "0.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.9.1.tgz",
|
||||||
|
"integrity": "sha512-7s1lufRD2l369eFWPjgLvhqCRk0XzGWJsQc7K4q+0mZtixyGIvsK1Cg88P4NcaRIEiBuuN4q1NN4SZKFKwQswA=="
|
||||||
|
},
|
||||||
"vue-eslint-parser": {
|
"vue-eslint-parser": {
|
||||||
"version": "7.8.0",
|
"version": "7.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.8.0.tgz",
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/font": "5.9.55",
|
"@mdi/font": "5.9.55",
|
||||||
|
"@vuelidate/core": "^2.0.0-alpha.22",
|
||||||
|
"@vuelidate/validators": "^2.0.0-alpha.19",
|
||||||
"bootstrap": "^5.1.0",
|
"bootstrap": "^5.1.0",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"date-fns": "^2.23.0",
|
"date-fns": "^2.23.0",
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
Count,
|
Count,
|
||||||
LogOnSuccess,
|
LogOnSuccess,
|
||||||
Profile,
|
Profile,
|
||||||
|
ProfileForm,
|
||||||
ProfileForView,
|
ProfileForView,
|
||||||
ProfileSearch,
|
ProfileSearch,
|
||||||
ProfileSearchResult,
|
ProfileSearchResult,
|
||||||
|
@ -28,13 +29,22 @@ const apiUrl = (url : string) : string => `http://localhost:5000/api/${url}`
|
||||||
* @param user The currently logged-on user
|
* @param user The currently logged-on user
|
||||||
* @returns RequestInit parameters
|
* @returns RequestInit parameters
|
||||||
*/
|
*/
|
||||||
const reqInit = (method : string, user : LogOnSuccess) : RequestInit => {
|
// eslint-disable-next-line
|
||||||
|
const reqInit = (method : string, user : LogOnSuccess, body : any | undefined = undefined) : RequestInit => {
|
||||||
const headers = new Headers()
|
const headers = new Headers()
|
||||||
headers.append('Authorization', `Bearer ${user.jwt}`)
|
headers.append('Authorization', `Bearer ${user.jwt}`)
|
||||||
|
if (body) {
|
||||||
|
headers.append('Content-Type', 'application/json')
|
||||||
|
return {
|
||||||
|
headers,
|
||||||
|
method,
|
||||||
|
cache: 'no-cache',
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
headers,
|
headers,
|
||||||
method
|
method
|
||||||
// mode: 'cors'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +61,18 @@ async function apiResult<T> (resp : Response, action : string) : Promise<T | und
|
||||||
return `Error ${action} - ${await resp.text()}`
|
return `Error ${action} - ${await resp.text()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an update via the API
|
||||||
|
*
|
||||||
|
* @param resp The response received from the API
|
||||||
|
* @param action The action being performed (used in error messages)
|
||||||
|
* @returns True (if the response is a success) or an error string
|
||||||
|
*/
|
||||||
|
async function apiSend (resp : Response, action : string) : Promise<boolean | string> {
|
||||||
|
if (resp.status === 200) return true
|
||||||
|
return `Error ${action} - (${resp.status}) ${await resp.text()}`
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run an API action that does not return a result
|
* Run an API action that does not return a result
|
||||||
*
|
*
|
||||||
|
@ -156,6 +178,15 @@ export default {
|
||||||
retreiveForView: async (id : string, user : LogOnSuccess) : Promise<ProfileForView | string | undefined> =>
|
retreiveForView: async (id : string, user : LogOnSuccess) : Promise<ProfileForView | string | undefined> =>
|
||||||
apiResult<ProfileForView>(await fetch(apiUrl(`profile/view/${id}`), reqInit('GET', user)), 'retrieving profile'),
|
apiResult<ProfileForView>(await fetch(apiUrl(`profile/view/${id}`), reqInit('GET', user)), 'retrieving profile'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a user's profile data
|
||||||
|
*
|
||||||
|
* @param data The profile data to be saved
|
||||||
|
* @param user The currently logged-on user
|
||||||
|
*/
|
||||||
|
save: async (data : ProfileForm, user : LogOnSuccess) : Promise<boolean | string> =>
|
||||||
|
apiSend(await fetch(apiUrl('profile/save'), reqInit('POST', user, data)), 'saving profile'),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search for profiles using the given parameters
|
* Search for profiles using the given parameters
|
||||||
*
|
*
|
||||||
|
|
|
@ -78,27 +78,27 @@ export interface Profile {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The data required to update a profile */
|
/** The data required to update a profile */
|
||||||
export interface ProfileForm {
|
export class ProfileForm {
|
||||||
/** Whether the citizen to whom this profile belongs is actively seeking employment */
|
/** Whether the citizen to whom this profile belongs is actively seeking employment */
|
||||||
isSeekingEmployment : boolean
|
isSeekingEmployment = false
|
||||||
/** Whether this profile should appear in the public search */
|
/** Whether this profile should appear in the public search */
|
||||||
isPublic : boolean
|
isPublic = false
|
||||||
/** The user's real name */
|
/** The user's real name */
|
||||||
realName : string
|
realName = ''
|
||||||
/** The ID of the continent on which the citizen is located */
|
/** The ID of the continent on which the citizen is located */
|
||||||
continentId : string
|
continentId = ''
|
||||||
/** The area within that continent where the citizen is located */
|
/** The area within that continent where the citizen is located */
|
||||||
region : string
|
region = ''
|
||||||
/** If the citizen is available for remote work */
|
/** If the citizen is available for remote work */
|
||||||
remoteWork : boolean
|
remoteWork = false
|
||||||
/** If the citizen is seeking full-time employment */
|
/** If the citizen is seeking full-time employment */
|
||||||
fullTime : boolean
|
fullTime = false
|
||||||
/** The user's professional biography */
|
/** The user's professional biography */
|
||||||
biography : string
|
biography = ''
|
||||||
/** The user's past experience */
|
/** The user's past experience */
|
||||||
experience : string | undefined
|
experience : string | undefined
|
||||||
/** The skills for the user */
|
/** The skills for the user */
|
||||||
skills : Skill[]
|
skills : Skill[] = []
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The data required to show a viewable profile */
|
/** The data required to show a viewable profile */
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="row pb-3">
|
<div class="col-12">
|
||||||
<div class="col col-xs-12">
|
|
||||||
<nav class="nav nav-pills pb-1">
|
<nav class="nav nav-pills pb-1">
|
||||||
<button :class="sourceClass" @click.prevent="showMarkdown">Markdown</button>
|
<button :class="sourceClass" @click.prevent="showMarkdown">Markdown</button>
|
||||||
<button :class="previewClass" @click.prevent="showPreview">Preview</button>
|
<button :class="previewClass" @click.prevent="showPreview">Preview</button>
|
||||||
|
@ -8,12 +7,12 @@
|
||||||
<section v-if="preview" class="preview" v-html="previewHtml">
|
<section v-if="preview" class="preview" v-html="previewHtml">
|
||||||
</section>
|
</section>
|
||||||
<div v-else class="form-floating">
|
<div v-else class="form-floating">
|
||||||
<textarea :id="id" class="form-control md-edit" rows="10" v-text="text"
|
<textarea :id="id" :class="{ 'form-control': true, 'md-edit': true, 'is-invalid': isInvalid }" rows="10"
|
||||||
@input="$emit('update:text', $event.target.value)"></textarea>
|
v-text="text" @input="$emit('update:text', $event.target.value)"></textarea>
|
||||||
|
<div class="invalid-feedback">Please enter some text for {{label}}</div>
|
||||||
<label :for="id">{{label}}</label>
|
<label :for="id">{{label}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -35,7 +34,8 @@ export default defineComponent({
|
||||||
label: {
|
label: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
}
|
},
|
||||||
|
isInvalid: { type: Boolean }
|
||||||
},
|
},
|
||||||
emits: ['update:text'],
|
emits: ['update:text'],
|
||||||
setup (props) {
|
setup (props) {
|
||||||
|
|
|
@ -3,9 +3,8 @@
|
||||||
<page-title title="Edit Profile" />
|
<page-title title="Edit Profile" />
|
||||||
<h3>Employment Profile</h3>
|
<h3>Employment Profile</h3>
|
||||||
<load-data :load="retrieveData">
|
<load-data :load="retrieveData">
|
||||||
<form>
|
<form class="row g-3">
|
||||||
<div class="row pb-3">
|
<div class="col-12 col-sm-10 col-md-8 col-lg-6">
|
||||||
<div class="col col-xs-12 col-sm-10 col-md-8 col-lg-6">
|
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input type="text" id="realName" class="form-control" v-model="profile.realName" maxlength="255"
|
<input type="text" id="realName" class="form-control" v-model="profile.realName" maxlength="255"
|
||||||
placeholder="Leave blank to use your NAS display name">
|
placeholder="Leave blank to use your NAS display name">
|
||||||
|
@ -13,9 +12,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text">Leave blank to use your NAS display name</div>
|
<div class="form-text">Leave blank to use your NAS display name</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="col-12">
|
||||||
<div class="row pb-3">
|
|
||||||
<div class="col col-xs-12">
|
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" v-model="profile.seekingEmployment">
|
<input type="checkbox" class="form-check-input" v-model="profile.seekingEmployment">
|
||||||
<label class="form-check-label">I am currently seeking employment</label>
|
<label class="form-check-label">I am currently seeking employment</label>
|
||||||
|
@ -25,47 +22,49 @@
|
||||||
citizens about it!</router-link></em>
|
citizens about it!</router-link></em>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="col-12 col-sm-6 col-md-4">
|
||||||
<div class="row pb-3">
|
|
||||||
<div class="col col-xs-12 col-sm-6 col-md-4">
|
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<select id="continentId" class="form-select" :value="profile.continentId">
|
<select id="continentId" :class="{ 'form-select': true, 'is-invalid': v$.continentId.$error }"
|
||||||
|
:value="v$.continentId.$model">
|
||||||
<option v-for="c in continents" :key="c.id" :value="c.id">{{c.name}}</option>
|
<option v-for="c in continents" :key="c.id" :value="c.id">{{c.name}}</option>
|
||||||
</select>
|
</select>
|
||||||
<label for="continentId" class="jjj-required">Continent</label>
|
<label for="continentId" class="jjj-required">Continent</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="invalid-feedback">Please select a continent</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-xs-12 col-sm-6 col-md-8">
|
<div class="col-12 col-sm-6 col-md-8">
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input type="text" id="region" class="form-control" v-model="profile.region" maxlength="255"
|
<input type="text" id="region" :class="{ 'form-control': true, 'is-invalid': v$.region.$error }"
|
||||||
placeholder="Country, state, geographic area, etc.">
|
v-model="v$.region.$model" maxlength="255" placeholder="Country, state, geographic area, etc.">
|
||||||
|
<div id="regionFeedback" class="invalid-feedback">Please enter a region</div>
|
||||||
<label for="region" class="jjj-required">Region</label>
|
<label for="region" class="jjj-required">Region</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text">Country, state, geographic area, etc.</div>
|
<div class="form-text">Country, state, geographic area, etc.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<markdown-editor id="bio" label="Professional Biography" v-model:text="profile.biography"
|
||||||
<markdown-editor id="bio" label="Professional Biography" v-model:text="profile.biography" />
|
:isInvalid="v$.biography.$error" />
|
||||||
<div class="row pb-3">
|
<div class="col-12 col-offset-md-2 col-md-4">
|
||||||
<div class="col col-xs-12 col-offset-md-2 col-md-4">
|
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" id="isRemote" class="form-check-input" v-model="profile.remoteWork">
|
<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>
|
<label class="form-check-label" for="isRemote">I am looking for remote work</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-xs-12 col-md-4">
|
<div class="col-12 col-md-4">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" id="isFullTime" class="form-check-input" v-model="profile.fullTime">
|
<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>
|
<label class="form-check-label" for="isFullTime">I am looking for full-time work</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="col-12">
|
||||||
<hr>
|
<hr>
|
||||||
<h4 class="pb-2">
|
<h4 class="pb-2">
|
||||||
Skills
|
Skills
|
||||||
<button class="btn btn-sm btn-outline-primary rounded-pill" @click.prevent="addSkill">Add a Skill</button>
|
<button class="btn btn-sm btn-outline-primary rounded-pill" @click.prevent="addSkill">Add a Skill</button>
|
||||||
</h4>
|
</h4>
|
||||||
|
</div>
|
||||||
<profile-skill-edit v-for="(skill, idx) in profile.skills" :key="skill.id" v-model="profile.skills[idx]"
|
<profile-skill-edit v-for="(skill, idx) in profile.skills" :key="skill.id" v-model="profile.skills[idx]"
|
||||||
@remove="removeSkill(skill.id)" />
|
@remove="removeSkill(skill.id)" />
|
||||||
|
<div class="col-12">
|
||||||
<hr>
|
<hr>
|
||||||
<h4>Experience</h4>
|
<h4>Experience</h4>
|
||||||
<p>
|
<p>
|
||||||
|
@ -73,9 +72,9 @@
|
||||||
use this area to list prior jobs, their dates, and anything else you want to include that’s not
|
use this area to list prior jobs, their dates, and anything else you want to include that’s not
|
||||||
already a part of your Professional Biography above.
|
already a part of your Professional Biography above.
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
<markdown-editor id="experience" label="Experience" v-model:text="profile.experience" />
|
<markdown-editor id="experience" label="Experience" v-model:text="profile.experience" />
|
||||||
<div class="row pb-3">
|
<div class="col-12">
|
||||||
<div class="col col-xs-12">
|
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" id="isPublic" class="form-check-input" v-model="profile.isPublic">
|
<input type="checkbox" id="isPublic" class="form-check-input" v-model="profile.isPublic">
|
||||||
<label class="form-check-label" for="isPublic">
|
<label class="form-check-label" for="isPublic">
|
||||||
|
@ -83,10 +82,9 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="col-12">
|
||||||
<div class="row pt-3">
|
<p v-if="v$.$error" class="text-danger">Please correct the errors above</p>
|
||||||
<div class="col col-xs-12">
|
<button class="btn btn-primary" @click.prevent="saveProfile">Save</button>
|
||||||
<button class="btn btn-primary">Save</button>
|
|
||||||
<template v-if="!isNew">
|
<template v-if="!isNew">
|
||||||
|
|
||||||
<button class="btn btn-outline-secondary" @click.prevent="viewProfile">
|
<button class="btn btn-outline-secondary" @click.prevent="viewProfile">
|
||||||
|
@ -94,7 +92,6 @@
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</load-data>
|
</load-data>
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -106,13 +103,16 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, Ref, ref } from 'vue'
|
import { computed, defineComponent, ref, reactive } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import useVuelidate from '@vuelidate/core'
|
||||||
|
import { required } from '@vuelidate/validators'
|
||||||
import api, { Citizen, LogOnSuccess, Profile, ProfileForm } from '@/api'
|
import api, { Citizen, LogOnSuccess, Profile, ProfileForm } from '@/api'
|
||||||
import { useStore } from '@/store'
|
import { 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'
|
||||||
|
import { toastError, toastSuccess } from '@/components/layout/AppToaster.vue'
|
||||||
import ProfileSkillEdit from '@/components/profile/SkillEdit.vue'
|
import ProfileSkillEdit from '@/components/profile/SkillEdit.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -148,7 +148,17 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The user's current profile (plus a few items, adapted for editing) */
|
/** The user's current profile (plus a few items, adapted for editing) */
|
||||||
const profile : Ref<ProfileForm | undefined> = ref(undefined)
|
const profile = reactive(new ProfileForm())
|
||||||
|
|
||||||
|
/** The validation rules for the form */
|
||||||
|
const rules = {
|
||||||
|
continentId: { required },
|
||||||
|
region: { required },
|
||||||
|
biography: { required }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Initialize form validation */
|
||||||
|
const v$ = useVuelidate(rules, profile /*, { $lazy: true } */)
|
||||||
|
|
||||||
/** Retrieve the user's profile and their real name */
|
/** Retrieve the user's profile and their real name */
|
||||||
const retrieveData = async (errors : string[]) => {
|
const retrieveData = async (errors : string[]) => {
|
||||||
|
@ -164,45 +174,55 @@ export default defineComponent({
|
||||||
errors.push(nameResult)
|
errors.push(nameResult)
|
||||||
}
|
}
|
||||||
if (errors.length > 0) return
|
if (errors.length > 0) return
|
||||||
|
// Update the empty form with appropriate values
|
||||||
const p = isNew.value ? newProfile : profileResult as Profile
|
const p = isNew.value ? newProfile : profileResult as Profile
|
||||||
profile.value = {
|
profile.isSeekingEmployment = p.seekingEmployment
|
||||||
isSeekingEmployment: p.seekingEmployment,
|
profile.isPublic = p.isPublic
|
||||||
isPublic: p.isPublic,
|
profile.continentId = p.continentId
|
||||||
continentId: p.continentId,
|
profile.region = p.region
|
||||||
region: p.region,
|
profile.remoteWork = p.remoteWork
|
||||||
remoteWork: p.remoteWork,
|
profile.fullTime = p.fullTime
|
||||||
fullTime: p.fullTime,
|
profile.biography = p.biography
|
||||||
biography: p.biography,
|
profile.experience = p.experience
|
||||||
experience: p.experience,
|
profile.skills = p.skills
|
||||||
skills: p.skills,
|
profile.realName = typeof nameResult !== 'undefined' ? (nameResult as Citizen).realName || '' : ''
|
||||||
realName: typeof nameResult !== 'undefined' ? (nameResult as Citizen).realName || '' : ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The ID for new skills */
|
/** The ID for new skills */
|
||||||
let newSkillId = 0
|
let newSkillId = 0
|
||||||
|
|
||||||
/** Add a skill to the profile */
|
/** Add a skill to the profile */
|
||||||
const addSkill = () => {
|
const addSkill = () => { profile.skills.push({ id: `new${newSkillId++}`, description: '', notes: undefined }) }
|
||||||
const form = profile.value as ProfileForm
|
|
||||||
form.skills.push({ id: `new${newSkillId}`, description: '', notes: undefined })
|
|
||||||
newSkillId++
|
|
||||||
profile.value = form
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Remove the given skill from the profile */
|
/** Remove the given skill from the profile */
|
||||||
const removeSkill = (skillId : string) => {
|
const removeSkill = (skillId : string) => { profile.skills = profile.skills.filter(s => s.id !== skillId) }
|
||||||
const form = profile.value as ProfileForm
|
|
||||||
form.skills = form.skills.filter(s => s.id !== skillId)
|
|
||||||
profile.value = form
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Save the current profile values */
|
/** Save the current profile values */
|
||||||
const saveProfile = async () => {
|
const saveProfile = async () => {
|
||||||
// TODO
|
v$.value.$touch()
|
||||||
|
if (v$.value.$error) return
|
||||||
|
// Remove any blank skills before submitting
|
||||||
|
profile.skills = profile.skills.filter(s => !(s.description.trim() === '' && (s.notes || '').trim() === ''))
|
||||||
|
const saveResult = await api.profile.save(profile, user)
|
||||||
|
if (typeof saveResult === 'string') {
|
||||||
|
toastError(saveResult, 'saving profile')
|
||||||
|
} else {
|
||||||
|
toastSuccess('Profile Saved Successfuly')
|
||||||
|
v$.value.$reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** View the profile, prompting for save if data has changed */
|
||||||
|
const viewProfile = async () => {
|
||||||
|
alert(v$.value.$dirty)
|
||||||
|
if (v$.value.$dirty && confirm('There are unsaved changes; save before viewing?')) {
|
||||||
|
await saveProfile()
|
||||||
|
router.push(`/profile/view/${user.citizenId}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
v$,
|
||||||
retrieveData,
|
retrieveData,
|
||||||
user,
|
user,
|
||||||
isNew,
|
isNew,
|
||||||
|
@ -211,7 +231,7 @@ export default defineComponent({
|
||||||
addSkill,
|
addSkill,
|
||||||
removeSkill,
|
removeSkill,
|
||||||
saveProfile,
|
saveProfile,
|
||||||
viewProfile: () => router.push(`/profile/view/${user.citizenId}`)
|
viewProfile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue
Block a user