Profile edit complete
This commit is contained in:
parent
bd97a3828f
commit
33c0e5ff5b
|
@ -258,7 +258,9 @@ module Profile =
|
||||||
experience = noneIfBlank form.experience |> Option.map Text
|
experience = noneIfBlank form.experience |> Option.map Text
|
||||||
skills = form.skills
|
skills = form.skills
|
||||||
|> List.map (fun s ->
|
|> List.map (fun s ->
|
||||||
{ id = SkillId.ofString s.id
|
{ id = match s.id.StartsWith "new" with
|
||||||
|
| true -> SkillId.create ()
|
||||||
|
| false -> SkillId.ofString s.id
|
||||||
description = s.description
|
description = s.description
|
||||||
notes = noneIfBlank s.notes
|
notes = noneIfBlank s.notes
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div aria-live="polite" aria-atomic="true">
|
<div aria-live="polite" aria-atomic="true" id="toastHost">
|
||||||
<div class="toast-container position-absolute p-3 bottom-0 start-50 translate-middle-x" id="toasts"></div>
|
<div class="toast-container position-absolute p-3 bottom-0 start-50 translate-middle-x" id="toasts"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -81,3 +81,9 @@ export default defineComponent({
|
||||||
name: 'AppToaster'
|
name: 'AppToaster'
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass" scoped>
|
||||||
|
#toastHost
|
||||||
|
position: sticky
|
||||||
|
bottom: 0
|
||||||
|
</style>
|
||||||
|
|
|
@ -20,8 +20,7 @@ export default defineComponent({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="sass" scoped>
|
<style lang="sass" scoped>
|
||||||
span
|
.navbar-text
|
||||||
font-style: italic
|
font-style: italic
|
||||||
padding-right: 1rem
|
padding-right: 1rem
|
||||||
// padding: 0 .5rem 0 0
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -38,7 +38,7 @@ export default defineComponent({
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['remove', 'update:modelValue'],
|
emits: ['input', 'remove', 'update:modelValue'],
|
||||||
setup (props, { emit }) {
|
setup (props, { emit }) {
|
||||||
/** The skill being edited */
|
/** The skill being edited */
|
||||||
const skill : Ref<Skill> = ref({ ...props.modelValue as Skill })
|
const skill : Ref<Skill> = ref({ ...props.modelValue as Skill })
|
||||||
|
@ -48,6 +48,7 @@ export default defineComponent({
|
||||||
updateValue: (key : string, value : string) => {
|
updateValue: (key : string, value : string) => {
|
||||||
skill.value = { ...skill.value, [key]: value }
|
skill.value = { ...skill.value, [key]: value }
|
||||||
emit('update:modelValue', skill.value)
|
emit('update:modelValue', skill.value)
|
||||||
|
emit('input')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<article>
|
<article>
|
||||||
<page-title title="Edit Profile" />
|
<page-title title="Edit Profile" />
|
||||||
<h3>Employment Profile</h3>
|
<h3 class="pb-3">Employment Profile</h3>
|
||||||
<load-data :load="retrieveData">
|
<load-data :load="retrieveData">
|
||||||
<form class="row g-3">
|
<form class="row g-3">
|
||||||
<div class="col-12 col-sm-10 col-md-8 col-lg-6">
|
<div class="col-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="v$.realName.$model" maxlength="255"
|
||||||
placeholder="Leave blank to use your NAS display name">
|
placeholder="Leave blank to use your NAS display name">
|
||||||
<label for="realName">Real Name</label>
|
<label for="realName">Real Name</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,8 +14,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" v-model="profile.seekingEmployment">
|
<input type="checkbox" id="isSeeking" class="form-check-input" v-model="v$.isSeekingEmployment.$model">
|
||||||
<label class="form-check-label">I am currently seeking employment</label>
|
<label for="isSeeking" class="form-check-label">I am currently seeking employment</label>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="profile?.seekingEmployment">
|
<p v-if="profile?.seekingEmployment">
|
||||||
<em>If you have found employment, consider <router-link to="/success-story/add">telling your fellow
|
<em>If you have found employment, consider <router-link to="/success-story/add">telling your fellow
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
<div class="col-12 col-sm-6 col-md-4">
|
<div class="col-12 col-sm-6 col-md-4">
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<select id="continentId" :class="{ 'form-select': true, 'is-invalid': v$.continentId.$error }"
|
<select id="continentId" :class="{ 'form-select': true, 'is-invalid': v$.continentId.$error }"
|
||||||
:value="v$.continentId.$model">
|
:value="v$.continentId.$model" @change="continentChanged">
|
||||||
<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>
|
||||||
|
@ -41,17 +41,17 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text">Country, state, geographic area, etc.</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"
|
<markdown-editor id="bio" label="Professional Biography" v-model:text="v$.biography.$model"
|
||||||
:isInvalid="v$.biography.$error" />
|
:isInvalid="v$.biography.$error" />
|
||||||
<div class="col-12 col-offset-md-2 col-md-4">
|
<div class="col-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="v$.remoteWork.$model">
|
||||||
<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-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="v$.fullTime.$model">
|
||||||
<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>
|
||||||
|
@ -63,7 +63,7 @@
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</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)" @input="v$.skills.$touch" />
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<hr>
|
<hr>
|
||||||
<h4>Experience</h4>
|
<h4>Experience</h4>
|
||||||
|
@ -73,10 +73,10 @@
|
||||||
already a part of your Professional Biography above.
|
already a part of your Professional Biography above.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<markdown-editor id="experience" label="Experience" v-model:text="profile.experience" />
|
<markdown-editor id="experience" label="Experience" v-model:text="v$.experience.$model" />
|
||||||
<div class="col-12">
|
<div class="col-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="v$.isPublic.$model">
|
||||||
<label class="form-check-label" for="isPublic">
|
<label class="form-check-label" for="isPublic">
|
||||||
Allow my profile to be searched publicly (outside NA Social)
|
Allow my profile to be searched publicly (outside NA Social)
|
||||||
</label>
|
</label>
|
||||||
|
@ -104,7 +104,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, ref, reactive } from 'vue'
|
import { computed, defineComponent, ref, reactive } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { onBeforeRouteLeave, useRouter } from 'vue-router'
|
||||||
import useVuelidate from '@vuelidate/core'
|
import useVuelidate from '@vuelidate/core'
|
||||||
import { required } from '@vuelidate/validators'
|
import { required } from '@vuelidate/validators'
|
||||||
import api, { Citizen, LogOnSuccess, Profile, ProfileForm } from '@/api'
|
import api, { Citizen, LogOnSuccess, Profile, ProfileForm } from '@/api'
|
||||||
|
@ -151,14 +151,21 @@ export default defineComponent({
|
||||||
const profile = reactive(new ProfileForm())
|
const profile = reactive(new ProfileForm())
|
||||||
|
|
||||||
/** The validation rules for the form */
|
/** The validation rules for the form */
|
||||||
const rules = {
|
const rules = computed(() => ({
|
||||||
|
realName: { },
|
||||||
|
isSeekingEmployment: { },
|
||||||
|
isPublic: { },
|
||||||
continentId: { required },
|
continentId: { required },
|
||||||
region: { required },
|
region: { required },
|
||||||
biography: { required }
|
remoteWork: { },
|
||||||
}
|
fullTime: { },
|
||||||
|
biography: { required },
|
||||||
|
experience: { },
|
||||||
|
skills: { }
|
||||||
|
}))
|
||||||
|
|
||||||
/** Initialize form validation */
|
/** Initialize form validation */
|
||||||
const v$ = useVuelidate(rules, profile /*, { $lazy: true } */)
|
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[]) => {
|
||||||
|
@ -188,14 +195,35 @@ export default defineComponent({
|
||||||
profile.realName = typeof nameResult !== 'undefined' ? (nameResult as Citizen).realName || '' : ''
|
profile.realName = typeof nameResult !== 'undefined' ? (nameResult as Citizen).realName || '' : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the continent field as changed
|
||||||
|
*
|
||||||
|
* (This works around a really strange sequence where, if the "touch" call is directly wired up to the onChange
|
||||||
|
* event, the first time a value is selected, it doesn't stick (although the field is marked as touched). On second
|
||||||
|
* and subsequent times, it worked. The solution here is to grab the value and update the reactive source for the
|
||||||
|
* form, then manually set the field to touched; this restores the expected behavior. This is probably why the
|
||||||
|
* library doesn't hook into the onChange event to begin with...)
|
||||||
|
*/
|
||||||
|
const continentChanged = (e : Event) : boolean => {
|
||||||
|
profile.continentId = (e.target as HTMLSelectElement).value
|
||||||
|
v$.value.continentId.$touch()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
/** 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 = () => { profile.skills.push({ id: `new${newSkillId++}`, description: '', notes: undefined }) }
|
const addSkill = () => {
|
||||||
|
profile.skills.push({ id: `new${newSkillId++}`, description: '', notes: undefined })
|
||||||
|
v$.value.skills.$touch()
|
||||||
|
}
|
||||||
|
|
||||||
/** Remove the given skill from the profile */
|
/** Remove the given skill from the profile */
|
||||||
const removeSkill = (skillId : string) => { profile.skills = profile.skills.filter(s => s.id !== skillId) }
|
const removeSkill = (skillId : string) => {
|
||||||
|
profile.skills = profile.skills.filter(s => s.id !== skillId)
|
||||||
|
v$.value.skills.$touch()
|
||||||
|
}
|
||||||
|
|
||||||
/** Save the current profile values */
|
/** Save the current profile values */
|
||||||
const saveProfile = async () => {
|
const saveProfile = async () => {
|
||||||
|
@ -212,14 +240,15 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** View the profile, prompting for save if data has changed */
|
/** If the user has unsaved changes, give them an opportunity to save before moving on */
|
||||||
const viewProfile = async () => {
|
onBeforeRouteLeave(async (to, from) => { // eslint-disable-line
|
||||||
alert(v$.value.$dirty)
|
if (!v$.value.$anyDirty) return true
|
||||||
if (v$.value.$dirty && confirm('There are unsaved changes; save before viewing?')) {
|
if (confirm('There are unsaved changes; save before viewing?')) {
|
||||||
await saveProfile()
|
await saveProfile()
|
||||||
router.push(`/profile/view/${user.citizenId}`)
|
return true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
v$,
|
v$,
|
||||||
|
@ -228,10 +257,11 @@ export default defineComponent({
|
||||||
isNew,
|
isNew,
|
||||||
profile,
|
profile,
|
||||||
continents: computed(() => store.state.continents),
|
continents: computed(() => store.state.continents),
|
||||||
|
continentChanged,
|
||||||
addSkill,
|
addSkill,
|
||||||
removeSkill,
|
removeSkill,
|
||||||
saveProfile,
|
saveProfile,
|
||||||
viewProfile
|
viewProfile: () => router.push(`/profile/view/${user.citizenId}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue
Block a user