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
|
||||
skills = form.skills
|
||||
|> 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
|
||||
notes = noneIfBlank s.notes
|
||||
})
|
||||
|
@ -1,5 +1,5 @@
|
||||
<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>
|
||||
</template>
|
||||
@ -81,3 +81,9 @@ export default defineComponent({
|
||||
name: 'AppToaster'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
#toastHost
|
||||
position: sticky
|
||||
bottom: 0
|
||||
</style>
|
||||
|
@ -20,8 +20,7 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
span
|
||||
.navbar-text
|
||||
font-style: italic
|
||||
padding-right: 1rem
|
||||
// padding: 0 .5rem 0 0
|
||||
</style>
|
||||
|
@ -38,7 +38,7 @@ export default defineComponent({
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['remove', 'update:modelValue'],
|
||||
emits: ['input', 'remove', 'update:modelValue'],
|
||||
setup (props, { emit }) {
|
||||
/** The skill being edited */
|
||||
const skill : Ref<Skill> = ref({ ...props.modelValue as Skill })
|
||||
@ -48,6 +48,7 @@ export default defineComponent({
|
||||
updateValue: (key : string, value : string) => {
|
||||
skill.value = { ...skill.value, [key]: value }
|
||||
emit('update:modelValue', skill.value)
|
||||
emit('input')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<article>
|
||||
<page-title title="Edit Profile" />
|
||||
<h3>Employment Profile</h3>
|
||||
<h3 class="pb-3">Employment Profile</h3>
|
||||
<load-data :load="retrieveData">
|
||||
<form class="row g-3">
|
||||
<div class="col-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"
|
||||
<input type="text" id="realName" class="form-control" v-model="v$.realName.$model" maxlength="255"
|
||||
placeholder="Leave blank to use your NAS display name">
|
||||
<label for="realName">Real Name</label>
|
||||
</div>
|
||||
@ -14,8 +14,8 @@
|
||||
</div>
|
||||
<div class="col-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>
|
||||
<input type="checkbox" id="isSeeking" class="form-check-input" v-model="v$.isSeekingEmployment.$model">
|
||||
<label for="isSeeking" 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
|
||||
@ -25,7 +25,7 @@
|
||||
<div class="col-12 col-sm-6 col-md-4">
|
||||
<div class="form-floating">
|
||||
<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>
|
||||
</select>
|
||||
<label for="continentId" class="jjj-required">Continent</label>
|
||||
@ -41,17 +41,17 @@
|
||||
</div>
|
||||
<div class="form-text">Country, state, geographic area, etc.</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" />
|
||||
<div class="col-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">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -63,7 +63,7 @@
|
||||
</h4>
|
||||
</div>
|
||||
<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">
|
||||
<hr>
|
||||
<h4>Experience</h4>
|
||||
@ -73,10 +73,10 @@
|
||||
already a part of your Professional Biography above.
|
||||
</p>
|
||||
</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="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">
|
||||
Allow my profile to be searched publicly (outside NA Social)
|
||||
</label>
|
||||
@ -104,7 +104,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { onBeforeRouteLeave, useRouter } from 'vue-router'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { required } from '@vuelidate/validators'
|
||||
import api, { Citizen, LogOnSuccess, Profile, ProfileForm } from '@/api'
|
||||
@ -151,14 +151,21 @@ export default defineComponent({
|
||||
const profile = reactive(new ProfileForm())
|
||||
|
||||
/** The validation rules for the form */
|
||||
const rules = {
|
||||
const rules = computed(() => ({
|
||||
realName: { },
|
||||
isSeekingEmployment: { },
|
||||
isPublic: { },
|
||||
continentId: { required },
|
||||
region: { required },
|
||||
biography: { required }
|
||||
}
|
||||
remoteWork: { },
|
||||
fullTime: { },
|
||||
biography: { required },
|
||||
experience: { },
|
||||
skills: { }
|
||||
}))
|
||||
|
||||
/** 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 */
|
||||
const retrieveData = async (errors : string[]) => {
|
||||
@ -188,14 +195,35 @@ export default defineComponent({
|
||||
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 */
|
||||
let newSkillId = 0
|
||||
|
||||
/** 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 */
|
||||
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 */
|
||||
const saveProfile = async () => {
|
||||
@ -212,14 +240,15 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
/** 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?')) {
|
||||
/** If the user has unsaved changes, give them an opportunity to save before moving on */
|
||||
onBeforeRouteLeave(async (to, from) => { // eslint-disable-line
|
||||
if (!v$.value.$anyDirty) return true
|
||||
if (confirm('There are unsaved changes; save before viewing?')) {
|
||||
await saveProfile()
|
||||
router.push(`/profile/view/${user.citizenId}`)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
return {
|
||||
v$,
|
||||
@ -228,10 +257,11 @@ export default defineComponent({
|
||||
isNew,
|
||||
profile,
|
||||
continents: computed(() => store.state.continents),
|
||||
continentChanged,
|
||||
addSkill,
|
||||
removeSkill,
|
||||
saveProfile,
|
||||
viewProfile
|
||||
viewProfile: () => router.push(`/profile/view/${user.citizenId}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user