Env swap #21
|
@ -468,9 +468,10 @@ module Success =
|
||||||
.Zip()
|
.Zip()
|
||||||
.Merge(ReqlFunction1(fun it ->
|
.Merge(ReqlFunction1(fun it ->
|
||||||
upcast r
|
upcast r
|
||||||
.HashMap("displayName",
|
.HashMap("citizenName",
|
||||||
r.Branch(it.G("realName" ).Default_("").Ne(""), it.G("realName"),
|
r.Branch(it.G("realName" ).Default_("").Ne(""), it.G("realName"),
|
||||||
it.G("displayName").Default_("").Ne(""), it.G("displayName"),
|
it.G("displayName").Default_("").Ne(""), it.G("displayName"),
|
||||||
it.G("naUser")))))
|
it.G("naUser")))
|
||||||
|
.With("hasStory", it.G("story").Default_("").Gt(""))))
|
||||||
.Pluck("id", "citizenId", "citizenName", "recordedOn", "fromHere", "hasStory")
|
.Pluck("id", "citizenId", "citizenName", "recordedOn", "fromHere", "hasStory")
|
||||||
.RunResultAsync<StoryEntry list> conn)
|
.RunResultAsync<StoryEntry list> conn)
|
||||||
|
|
|
@ -341,7 +341,7 @@ module Success =
|
||||||
let! success = task {
|
let! success = task {
|
||||||
match form.id with
|
match form.id with
|
||||||
| "new" ->
|
| "new" ->
|
||||||
return Some { id = (Guid.NewGuid >> SuccessId) ()
|
return Some { id = SuccessId.create ()
|
||||||
citizenId = citizenId
|
citizenId = citizenId
|
||||||
recordedOn = now
|
recordedOn = now
|
||||||
fromHere = form.fromHere
|
fromHere = form.fromHere
|
||||||
|
@ -397,7 +397,7 @@ let allEndpoints = [
|
||||||
]
|
]
|
||||||
subRoute "/success" [
|
subRoute "/success" [
|
||||||
GET_HEAD [
|
GET_HEAD [
|
||||||
routef "/get/%O" Success.get
|
routef "/%O" Success.get
|
||||||
route "/list" Success.all
|
route "/list" Success.all
|
||||||
]
|
]
|
||||||
POST [ route "/save" Success.save ]
|
POST [ route "/save" Success.save ]
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
PublicSearch,
|
PublicSearch,
|
||||||
PublicSearchResult,
|
PublicSearchResult,
|
||||||
StoryEntry,
|
StoryEntry,
|
||||||
|
StoryForm,
|
||||||
Success
|
Success
|
||||||
} from './types'
|
} from './types'
|
||||||
|
|
||||||
|
@ -137,6 +138,18 @@ export default {
|
||||||
/** API functions for profiles */
|
/** API functions for profiles */
|
||||||
profile: {
|
profile: {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the "seeking employment" flag on the current citizen's profile
|
||||||
|
*
|
||||||
|
* @param user The currently logged-on user
|
||||||
|
* @returns True if the action was successful, or an error string if not
|
||||||
|
*/
|
||||||
|
markEmploymentFound: async (user : LogOnSuccess) : Promise<boolean | string> => {
|
||||||
|
const result = await fetch(apiUrl('profile/employment-found'), reqInit('PATCH', user))
|
||||||
|
if (result.ok) return true
|
||||||
|
return `${result.status} - ${result.statusText} (${await result.text()})`
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search for public profile data using the given parameters
|
* Search for public profile data using the given parameters
|
||||||
*
|
*
|
||||||
|
@ -183,6 +196,7 @@ export default {
|
||||||
*
|
*
|
||||||
* @param data The profile data to be saved
|
* @param data The profile data to be saved
|
||||||
* @param user The currently logged-on user
|
* @param user The currently logged-on user
|
||||||
|
* @returns True if the save was successful, an error string if not
|
||||||
*/
|
*/
|
||||||
save: async (data : ProfileForm, user : LogOnSuccess) : Promise<boolean | string> =>
|
save: async (data : ProfileForm, user : LogOnSuccess) : Promise<boolean | string> =>
|
||||||
apiSend(await fetch(apiUrl('profile/save'), reqInit('POST', user, data)), 'saving profile'),
|
apiSend(await fetch(apiUrl('profile/save'), reqInit('POST', user, data)), 'saving profile'),
|
||||||
|
@ -249,7 +263,17 @@ export default {
|
||||||
* @returns The success story, or an error
|
* @returns The success story, or an error
|
||||||
*/
|
*/
|
||||||
retrieve: async (id : string, user : LogOnSuccess) : Promise<Success | string | undefined> =>
|
retrieve: async (id : string, user : LogOnSuccess) : Promise<Success | string | undefined> =>
|
||||||
apiResult<Success>(await fetch(apiUrl(`success/${id}`), reqInit('GET', user)), `retrieving success story ${id}`)
|
apiResult<Success>(await fetch(apiUrl(`success/${id}`), reqInit('GET', user)), `retrieving success story ${id}`),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a success story
|
||||||
|
*
|
||||||
|
* @param data The data to be saved
|
||||||
|
* @param user The currently logged-on user
|
||||||
|
* @returns True if successful, an error string if not
|
||||||
|
*/
|
||||||
|
save: async (data : StoryForm, user : LogOnSuccess) : Promise<boolean | string> =>
|
||||||
|
apiSend(await fetch(apiUrl('success/save'), reqInit('POST', user, data)), 'saving success story')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -179,6 +179,16 @@ export interface StoryEntry {
|
||||||
hasStory : boolean
|
hasStory : boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The data required to provide a success story */
|
||||||
|
export class StoryForm {
|
||||||
|
/** The ID of this story */
|
||||||
|
id = ''
|
||||||
|
/** Whether the employment was obtained from Jobs, Jobs, Jobs */
|
||||||
|
fromHere = false
|
||||||
|
/** The success story */
|
||||||
|
story = ''
|
||||||
|
}
|
||||||
|
|
||||||
/** A record of success finding employment */
|
/** A record of success finding employment */
|
||||||
export interface Success {
|
export interface Success {
|
||||||
/** The ID of the success report */
|
/** The ID of the success report */
|
||||||
|
|
|
@ -106,9 +106,9 @@ const routes: Array<RouteRecordRaw> = [
|
||||||
component: () => import(/* webpackChunkName: "success" */ '../views/success-story/StoryList.vue')
|
component: () => import(/* webpackChunkName: "success" */ '../views/success-story/StoryList.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/success-story/add',
|
path: '/success-story/edit/:id',
|
||||||
name: 'AddStory',
|
name: 'EditStory',
|
||||||
component: () => import(/* webpackChunkName: "succedit" */ '../views/success-story/StoryAdd.vue')
|
component: () => import(/* webpackChunkName: "succedit" */ '../views/success-story/StoryEdit.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/success-story/view/:id',
|
path: '/success-story/view/:id',
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
<input type="checkbox" id="isSeeking" class="form-check-input" v-model="v$.isSeekingEmployment.$model">
|
<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>
|
<label for="isSeeking" class="form-check-label">I am currently seeking employment</label>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="profile?.seekingEmployment">
|
<p v-if="profile.isSeekingEmployment">
|
||||||
<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/edit/new">telling your fellow
|
||||||
citizens about it!</router-link></em>
|
citizens about it!</router-link></em>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
<page-title :title="pageTitle" />
|
<page-title :title="pageTitle" />
|
||||||
<load-data :load="retrieveProfile">
|
<load-data :load="retrieveProfile">
|
||||||
<h2><a :href="it.citizen.profileUrl" target="_blank">{{citizenName}}</a></h2>
|
<h2><a :href="it.citizen.profileUrl" target="_blank">{{citizenName}}</a></h2>
|
||||||
<h4>{{it.continent.name}}, {{it.profile.region}}</h4>
|
<h4 class="pb-3">{{it.continent.name}}, {{it.profile.region}}</h4>
|
||||||
<p v-html="workTypes"></p>
|
<p v-html="workTypes"></p>
|
||||||
<hr>
|
<hr>
|
||||||
<div v-html="bioHtml"></div>
|
<div v-html="bioHtml"></div>
|
||||||
|
|
||||||
<template v-if="it.profile.skills.length > 0">
|
<template v-if="it.profile.skills.length > 0">
|
||||||
<hr>
|
<hr>
|
||||||
<h4>Skills</h4>
|
<h4 class="pb-3">Skills</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(skill, idx) in it.profile.skills" :key="idx">
|
<li v-for="(skill, idx) in it.profile.skills" :key="idx">
|
||||||
{{skill.description}}<template v-if="skill.notes"> ({{skill.notes}})</template>
|
{{skill.description}}<template v-if="skill.notes"> ({{skill.notes}})</template>
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
<template v-if="it.profile.experience">
|
<template v-if="it.profile.experience">
|
||||||
<hr>
|
<hr>
|
||||||
<h4>Experience / Employment History</h4>
|
<h4 class="pb-3">Experience / Employment History</h4>
|
||||||
<div v-html="expHtml"></div>
|
<div v-html="expHtml"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<article>
|
<article>
|
||||||
<page-title title="Account Deletion Options" />
|
<page-title title="Account Deletion Options" />
|
||||||
<h3>Account Deletion Options</h3>
|
<h3 class="pb-3">Account Deletion Options</h3>
|
||||||
|
|
||||||
<h4>Option 1 – Delete Your Profile</h4>
|
<h4 class="pb-3">Option 1 – Delete Your Profile</h4>
|
||||||
<p>
|
<p>
|
||||||
Utilizing this option will remove your current employment profile and skills. This will preserve any success
|
Utilizing this option will remove your current employment profile and skills. This will preserve any success
|
||||||
stories you may have written, and preserves this application’s knowledge of you. This is what you want to
|
stories you may have written, and preserves this application’s knowledge of you. This is what you want to
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h4>Option 2 – Delete Your Account</h4>
|
<h4 class="pb-3">Option 2 – Delete Your Account</h4>
|
||||||
<p>
|
<p>
|
||||||
This option will make it like you never visited this site. It will delete your profile, skills, success stories,
|
This option will make it like you never visited this site. It will delete your profile, skills, success stories,
|
||||||
and account. This is what you want to use if you want to disappear from this application. Clicking the button
|
and account. This is what you want to use if you want to disappear from this application. Clicking the button
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<article>
|
<article>
|
||||||
<page-title title="Account Deletion Success" />
|
<page-title title="Account Deletion Success" />
|
||||||
<h3>Account Deletion Success</h3>
|
<h3 class="pb-3">Account Deletion Success</h3>
|
||||||
<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
|
||||||
application, find it in <a href="https://noagendasocial.com/oauth/authorized_applications">this list</a> and click
|
application, find it in <a href="https://noagendasocial.com/oauth/authorized_applications">this list</a> and click
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
<template>
|
|
||||||
<p>TODO: convert this view</p>
|
|
||||||
</template>
|
|
142
src/JobsJobsJobs/App/src/views/success-story/StoryEdit.vue
Normal file
142
src/JobsJobsJobs/App/src/views/success-story/StoryEdit.vue
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
<template>
|
||||||
|
<article>
|
||||||
|
<page-title :title="title" />
|
||||||
|
<h3 class="pb-3">{{title}}</h3>
|
||||||
|
|
||||||
|
<load-data :load="retrieveStory">
|
||||||
|
<p v-if="isNew">
|
||||||
|
Congratulations on your employment! Your fellow citizens would enjoy hearing how it all came about; tell us
|
||||||
|
about it below! <em>(These will be visible to other users, but not to the general public.)</em>
|
||||||
|
</p>
|
||||||
|
<form class="row g-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" id="fromHere" class="form-check-input" v-model="v$.fromHere.$model">
|
||||||
|
<label for="fromHere" class="form-check-label">I found my employment here</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<markdown-editor id="story" label="The Success Story" v-model:text="v$.story.$model" />
|
||||||
|
<div class="col-12">
|
||||||
|
<button type="submit" class="btn btn-primary" @click.prevent="saveStory(true)"><icon icon="" /> Save</button>
|
||||||
|
<p v-if="isNew">
|
||||||
|
<em>(Saving this will set “Seeking Employment” to “No” on your profile.)</em>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</load-data>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent, reactive } from 'vue'
|
||||||
|
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'
|
||||||
|
import useVuelidate from '@vuelidate/core'
|
||||||
|
import api, { LogOnSuccess, StoryForm } from '@/api'
|
||||||
|
import { toastError, toastSuccess } from '@/components/layout/AppToaster.vue'
|
||||||
|
import { useStore } from '@/store'
|
||||||
|
|
||||||
|
import LoadData from '@/components/LoadData.vue'
|
||||||
|
import MarkdownEditor from '@/components/MarkdownEditor.vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'StoryEdit',
|
||||||
|
components: {
|
||||||
|
LoadData,
|
||||||
|
MarkdownEditor
|
||||||
|
},
|
||||||
|
setup () {
|
||||||
|
const store = useStore()
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
/** The currently logged-on user */
|
||||||
|
const user = store.state.user as LogOnSuccess
|
||||||
|
|
||||||
|
/** The ID of the story being edited */
|
||||||
|
const id = route.params.id as string
|
||||||
|
|
||||||
|
/** Whether this is a new story */
|
||||||
|
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 */
|
||||||
|
const story = reactive(new StoryForm())
|
||||||
|
|
||||||
|
/** Validator rules */
|
||||||
|
const rules = computed(() => ({
|
||||||
|
fromHere: { },
|
||||||
|
story: { }
|
||||||
|
}))
|
||||||
|
|
||||||
|
/** The validator */
|
||||||
|
const v$ = useVuelidate(rules, story, { $lazy: true })
|
||||||
|
|
||||||
|
/** Retrieve the specified story */
|
||||||
|
const retrieveStory = async (errors : string[]) => {
|
||||||
|
if (isNew.value) {
|
||||||
|
story.id = 'new'
|
||||||
|
} else {
|
||||||
|
const storyResult = await api.success.retrieve(id, user)
|
||||||
|
if (typeof storyResult === 'string') {
|
||||||
|
errors.push(storyResult)
|
||||||
|
} else if (typeof storyResult === 'undefined') {
|
||||||
|
errors.push('Story not found')
|
||||||
|
} else if (storyResult.citizenId !== user.citizenId) {
|
||||||
|
errors.push('Quit messing around')
|
||||||
|
} else {
|
||||||
|
story.id = storyResult.id
|
||||||
|
story.fromHere = storyResult.fromHere
|
||||||
|
story.story = storyResult.story || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Save the success story */
|
||||||
|
const saveStory = async (navigate : boolean) => {
|
||||||
|
const saveResult = await api.success.save(story, user)
|
||||||
|
if (typeof saveResult === 'string') {
|
||||||
|
toastError(saveResult, 'saving success story')
|
||||||
|
} else {
|
||||||
|
if (isNew.value) {
|
||||||
|
const foundResult = await api.profile.markEmploymentFound(user)
|
||||||
|
if (typeof foundResult === 'string') {
|
||||||
|
toastError(foundResult, 'clearing employment flag')
|
||||||
|
} else {
|
||||||
|
toastSuccess('Success Story saved and Seeking Employment flag cleared successfully')
|
||||||
|
if (navigate) {
|
||||||
|
router.push('/success-story/list')
|
||||||
|
v$.value.$reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toastSuccess('Success Story saved successfully')
|
||||||
|
if (navigate) {
|
||||||
|
router.push('/success-story/list')
|
||||||
|
v$.value.$reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Prompt for save if the user navigates away with unsaved changes */
|
||||||
|
onBeforeRouteLeave(async (to, from) => { // eslint-disable-line
|
||||||
|
if (!v$.value.$anyDirty) return true
|
||||||
|
if (confirm('There are unsaved changes; save before leaving?')) {
|
||||||
|
await saveStory(false)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
isNew,
|
||||||
|
retrieveStory,
|
||||||
|
v$,
|
||||||
|
saveStory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<article>
|
<article>
|
||||||
<page-title title="Success Stories" />
|
<page-title title="Success Stories" />
|
||||||
<h3>Success Stories</h3>
|
<h3 class="pb-3">Success Stories</h3>
|
||||||
<load-data :load="retrieveStories">
|
<load-data :load="retrieveStories">
|
||||||
<table v-if="stories?.length > 0" class="table table-sm table-hover">
|
<table v-if="stories?.length > 0" class="table table-sm table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<article>
|
<article>
|
||||||
<page-title title="Success Story" />
|
<page-title title="Success Story" />
|
||||||
<load-data :load="retrieveStory">
|
<load-data :load="retrieveStory">
|
||||||
<h3>{{citizenName}}’s Success Story</h3>
|
<h3 class="pb-3">{{citizenName}}’s Success Story</h3>
|
||||||
<h4 class="text-muted"><full-date-time :date="story.recordedOn" /></h4>
|
<h4 class="text-muted"><full-date-time :date="story.recordedOn" /></h4>
|
||||||
<p v-if="story.fromHere" class="fst-italic"><strong>Found via Jobs, Jobs, Jobs</strong></p>
|
<p v-if="story.fromHere" class="fst-italic"><strong>Found via Jobs, Jobs, Jobs</strong></p>
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -17,6 +17,7 @@ import { useRoute } from 'vue-router'
|
||||||
import marked from 'marked'
|
import marked from 'marked'
|
||||||
import api, { LogOnSuccess, markedOptions, Success } from '@/api'
|
import api, { LogOnSuccess, markedOptions, Success } from '@/api'
|
||||||
import { useStore } from '@/store'
|
import { useStore } from '@/store'
|
||||||
|
|
||||||
import FullDateTime from '@/components/FullDateTime.vue'
|
import FullDateTime from '@/components/FullDateTime.vue'
|
||||||
import LoadData from '@/components/LoadData.vue'
|
import LoadData from '@/components/LoadData.vue'
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user