Env swap #21
@ -54,7 +54,7 @@ html
|
||||
a:link,
|
||||
a:visited
|
||||
text-decoration: none
|
||||
a:hover
|
||||
a:not(.btn):hover
|
||||
text-decoration: underline
|
||||
label.jjj-required::after
|
||||
color: red
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
Citizen,
|
||||
Continent,
|
||||
Count,
|
||||
Listing,
|
||||
LogOnSuccess,
|
||||
Profile,
|
||||
ProfileForm,
|
||||
@ -135,6 +136,19 @@ export default {
|
||||
apiResult<Continent[]>(await fetch(apiUrl('continent/all'), { method: 'GET' }), 'retrieving continents')
|
||||
},
|
||||
|
||||
/** API functions for job listings */
|
||||
listings: {
|
||||
|
||||
/**
|
||||
* Retrieve the job listings posted by the current citizen
|
||||
*
|
||||
* @param user The currently logged-on user
|
||||
* @returns The job listings the user has posted, or an error string
|
||||
*/
|
||||
mine: async (user : LogOnSuccess) : Promise<Listing[] | string | undefined> =>
|
||||
apiResult<Listing[]>(await fetch(apiUrl('listings/mine'), reqInit('GET', user)), 'retrieving your job listings')
|
||||
},
|
||||
|
||||
/** API functions for profiles */
|
||||
profile: {
|
||||
|
||||
|
@ -31,6 +31,34 @@ export interface Count {
|
||||
count : number
|
||||
}
|
||||
|
||||
/** A job listing */
|
||||
export interface Listing {
|
||||
/** The ID of the job listing */
|
||||
id : string
|
||||
/** The ID of the citizen who posted the job listing */
|
||||
citizenId : string
|
||||
/** When this job listing was created (date) */
|
||||
createdOn : string
|
||||
/** The short title of the job listing */
|
||||
title : string
|
||||
/** The ID of the continent on which the job is located */
|
||||
continentId : string
|
||||
/** The region in which the job is located */
|
||||
region : string
|
||||
/** Whether this listing is for remote work */
|
||||
remoteWork : boolean
|
||||
/** Whether this listing has expired */
|
||||
isExpired : boolean
|
||||
/** When this listing was last updated (date) */
|
||||
updatedOn : string
|
||||
/** The details of this job */
|
||||
text : string
|
||||
/** When this job needs to be filled (date) */
|
||||
neededBy : string | undefined
|
||||
/** Was this job filled as part of its appearance on Jobs, Jobs, Jobs? */
|
||||
wasFilledHere : boolean | undefined
|
||||
}
|
||||
|
||||
/** A successful logon */
|
||||
export interface LogOnSuccess {
|
||||
/** The JSON Web Token (JWT) to use for API access */
|
||||
|
92
src/JobsJobsJobs/App/src/components/MaybeSave.vue
Normal file
92
src/JobsJobsJobs/App/src/components/MaybeSave.vue
Normal file
@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="modal fade" id="maybeSaveModal" tabindex="-1" aria-labelledby="maybeSaveLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="maybeSaveLabel">Unsaved Changes</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
You have modified the data on this page since it was last saved. What would you like to do?
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @click.prevent="onStay">Stay on This Page</button>
|
||||
<button type="button" class="btn btn-primary" @click.prevent="onSave">Save Changes</button>
|
||||
<button type="button" class="btn btn-danger" @click.prevent="onDiscard">Discard Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, ref, Ref, watch } from 'vue'
|
||||
import { RouteLocationNormalized, useRouter } from 'vue-router'
|
||||
import { Validation } from '@vuelidate/core'
|
||||
import { Modal } from 'bootstrap'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MaybeSave',
|
||||
props: {
|
||||
isShown: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
toRoute: {
|
||||
// Can't type this because it's not filled until just before the modal is shown
|
||||
required: true
|
||||
},
|
||||
saveAction: {
|
||||
type: Function
|
||||
},
|
||||
validator: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
emits: ['close', 'discard', 'cancel'],
|
||||
setup (props, { emit }) {
|
||||
const router = useRouter()
|
||||
|
||||
/** The route where we tried to go */
|
||||
const newRoute = computed(() => props.toRoute as RouteLocationNormalized)
|
||||
|
||||
/** Reference to the modal dialog (we can't get it until the component is rendered) */
|
||||
const modal : Ref<Modal | undefined> = ref(undefined)
|
||||
|
||||
/** Save changes (if required) and go to the next route */
|
||||
const onSave = async () => {
|
||||
if (props.saveAction) await Promise.resolve(props.saveAction())
|
||||
emit('close')
|
||||
router.push(newRoute.value)
|
||||
}
|
||||
|
||||
/** Discard changes (if required) and go to the next route */
|
||||
const onDiscard = () => {
|
||||
if (props.validator) (props.validator as Validation).$reset()
|
||||
emit('close')
|
||||
router.push(newRoute.value)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
modal.value = new Modal(document.getElementById('maybeSaveModal') as HTMLElement,
|
||||
{ backdrop: 'static', keyboard: false })
|
||||
})
|
||||
|
||||
/** Show or hide the modal based on the property value changing */
|
||||
watch(() => props.isShown, (toShow) => {
|
||||
if (modal.value) {
|
||||
if (toShow) {
|
||||
modal.value.show()
|
||||
} else {
|
||||
modal.value.hide()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
onStay: () => emit('close'),
|
||||
onSave,
|
||||
onDiscard
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
@ -72,9 +72,20 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'LogOff',
|
||||
component: () => import(/* webpackChunkName: "logoff" */ '../views/citizen/LogOff.vue')
|
||||
},
|
||||
// Job Listing URLs
|
||||
{
|
||||
path: '/listing/:id/edit',
|
||||
name: 'EditListing',
|
||||
component: () => import(/* webpackChunkName: "jobedit" */ '../views/listing/ListingEdit.vue')
|
||||
},
|
||||
{
|
||||
path: '/listings/mine',
|
||||
name: 'MyListings',
|
||||
component: () => import(/* webpackChunkName: "joblist" */ '../views/listing/MyListings.vue')
|
||||
},
|
||||
// Profile URLs
|
||||
{
|
||||
path: '/profile/view/:id',
|
||||
path: '/profile/:id/view',
|
||||
name: 'ViewProfile',
|
||||
component: () => import(/* webpackChunkName: "profview" */ '../views/profile/ProfileView.vue')
|
||||
},
|
||||
@ -106,12 +117,12 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () => import(/* webpackChunkName: "success" */ '../views/success-story/StoryList.vue')
|
||||
},
|
||||
{
|
||||
path: '/success-story/edit/:id',
|
||||
path: '/success-story/:id/edit',
|
||||
name: 'EditStory',
|
||||
component: () => import(/* webpackChunkName: "succedit" */ '../views/success-story/StoryEdit.vue')
|
||||
},
|
||||
{
|
||||
path: '/success-story/view/:id',
|
||||
path: '/success-story/:id/view',
|
||||
name: 'ViewStory',
|
||||
component: () => import(/* webpackChunkName: "success" */ '../views/success-story/StoryView.vue')
|
||||
}
|
||||
|
@ -27,10 +27,11 @@
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<template v-if="profile">
|
||||
<button class="btn btn-outline-secondary" @click="viewProfile">View Profile</button>
|
||||
<button class="btn btn-outline-secondary" @click="editProfile">Edit Profile</button>
|
||||
<router-link class="btn btn-outline-secondary"
|
||||
:to="`/profile/${user.citizenId}/view`">View Profile</router-link>
|
||||
<router-link class="btn btn-outline-secondary" to="/citizen/profile">Edit Profile</router-link>
|
||||
</template>
|
||||
<button v-else class="btn btn-primary" @click="editProfile">Create Profile</button>
|
||||
<router-link v-else class="btn btn-primary" to="/citizen/profile">Create Profile</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -53,7 +54,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-outline-secondary" @click="searchProfiles">Search Profiles</button>
|
||||
<router-link class="btn btn-outline-secondary" to="/profile/search">Search Profiles</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -69,7 +70,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, Ref, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import api, { LogOnSuccess, Profile } from '@/api'
|
||||
import { useStore } from '@/store'
|
||||
|
||||
@ -84,7 +84,6 @@ export default defineComponent({
|
||||
},
|
||||
setup () {
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
/** The currently logged-in user */
|
||||
const user = store.state.user as LogOnSuccess
|
||||
@ -114,10 +113,7 @@ export default defineComponent({
|
||||
retrieveData,
|
||||
user,
|
||||
profile,
|
||||
profileCount,
|
||||
viewProfile: () => router.push(`/profile/view/${user.citizenId}`),
|
||||
editProfile: () => router.push('/citizen/profile'),
|
||||
searchProfiles: () => router.push('/profile/search')
|
||||
profileCount
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -18,7 +18,7 @@
|
||||
<label for="isSeeking" class="form-check-label">I am currently seeking employment</label>
|
||||
</div>
|
||||
<p v-if="profile.isSeekingEmployment">
|
||||
<em>If you have found employment, consider <router-link to="/success-story/edit/new">telling your fellow
|
||||
<em>If you have found employment, consider <router-link to="/success-story/new/edit">telling your fellow
|
||||
citizens about it!</router-link></em>
|
||||
</p>
|
||||
</div>
|
||||
@ -84,12 +84,14 @@
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<p v-if="v$.$error" class="text-danger">Please correct the errors above</p>
|
||||
<button class="btn btn-primary" @click.prevent="saveProfile">Save</button>
|
||||
<button class="btn btn-primary" @click.prevent="saveProfile">
|
||||
<icon icon="content-save-outline" /> Save
|
||||
</button>
|
||||
<template v-if="!isNew">
|
||||
|
||||
<button class="btn btn-outline-secondary" @click.prevent="viewProfile">
|
||||
<router-link class="btn btn-outline-secondary" :to="`/profile/${user.citizenId}/view`">
|
||||
<icon icon="file-account-outline" /> View Your User Profile
|
||||
</button>
|
||||
</router-link>
|
||||
</template>
|
||||
</div>
|
||||
</form>
|
||||
@ -99,20 +101,24 @@
|
||||
(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>
|
||||
<maybe-save :isShown="confirmNavShown" :toRoute="nextRoute" :saveAction="saveProfile" :validator="v$"
|
||||
@close="confirmClose" />
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, reactive } from 'vue'
|
||||
import { onBeforeRouteLeave, useRouter } from 'vue-router'
|
||||
import { computed, defineComponent, ref, reactive, Ref } from 'vue'
|
||||
import { onBeforeRouteLeave, RouteLocationNormalized } from 'vue-router'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { required } from '@vuelidate/validators'
|
||||
|
||||
import api, { Citizen, LogOnSuccess, Profile, ProfileForm } 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'
|
||||
import { toastError, toastSuccess } from '@/components/layout/AppToaster.vue'
|
||||
import MaybeSave from '@/components/MaybeSave.vue'
|
||||
import ProfileSkillEdit from '@/components/profile/SkillEdit.vue'
|
||||
|
||||
export default defineComponent({
|
||||
@ -120,11 +126,11 @@ export default defineComponent({
|
||||
components: {
|
||||
LoadData,
|
||||
MarkdownEditor,
|
||||
MaybeSave,
|
||||
ProfileSkillEdit
|
||||
},
|
||||
setup () {
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
/** The currently logged-on user */
|
||||
const user = store.state.user as LogOnSuccess
|
||||
@ -240,13 +246,17 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether the navigation confirmation is shown */
|
||||
const confirmNavShown = ref(false)
|
||||
|
||||
/** The "next" route (will be navigated or cleared) */
|
||||
const nextRoute : Ref<RouteLocationNormalized | undefined> = ref(undefined)
|
||||
|
||||
/** 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()
|
||||
return true
|
||||
}
|
||||
nextRoute.value = to
|
||||
confirmNavShown.value = true
|
||||
return false
|
||||
})
|
||||
|
||||
@ -261,7 +271,9 @@ export default defineComponent({
|
||||
addSkill,
|
||||
removeSkill,
|
||||
saveProfile,
|
||||
viewProfile: () => router.push(`/profile/view/${user.citizenId}`)
|
||||
confirmNavShown,
|
||||
nextRoute,
|
||||
confirmClose: () => { confirmNavShown.value = false }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
3
src/JobsJobsJobs/App/src/views/listing/ListingEdit.vue
Normal file
3
src/JobsJobsJobs/App/src/views/listing/ListingEdit.vue
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<p>TODO: placeholder</p>
|
||||
</template>
|
70
src/JobsJobsJobs/App/src/views/listing/MyListings.vue
Normal file
70
src/JobsJobsJobs/App/src/views/listing/MyListings.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<article>
|
||||
<page-title title="My Job Listings" />
|
||||
<h3 class="pb-3">My Job Listings</h3>
|
||||
<p>
|
||||
<router-link class="btn btn-primary-outline" to="/listing/new/edit">Add a New Job Listing</router-link>
|
||||
</p>
|
||||
<load-data :load="getListings">
|
||||
<table v-if="listings.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Action</th>
|
||||
<th>Title</th>
|
||||
<th>Created</th>
|
||||
<th>Updated</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="listing in listings" :key="listing.id">
|
||||
<td><router-link :to="`/listing/${listing.Id}/edit`">Edit</router-link></td>
|
||||
<td>{{listing.Title}}</td>
|
||||
<td><full-date-time :date="listing.createdOn" /></td>
|
||||
<td><full-date-time :date="listing.updatedOn" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p v-else class="fst-italic">No job listings found</p>
|
||||
</load-data>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, Ref, ref } from 'vue'
|
||||
import api, { Listing, LogOnSuccess } from '@/api'
|
||||
import { useStore } from '@/store'
|
||||
|
||||
import FullDateTime from '@/components/FullDateTime.vue'
|
||||
import LoadData from '@/components/LoadData.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MyListings',
|
||||
components: {
|
||||
FullDateTime,
|
||||
LoadData
|
||||
},
|
||||
setup () {
|
||||
const store = useStore()
|
||||
|
||||
/** The listings for the user */
|
||||
const listings : Ref<Listing[]> = ref([])
|
||||
|
||||
/** Retrieve the job listing posted by the current citizen */
|
||||
const getListings = async (errors : string[]) => {
|
||||
const listResult = await api.listings.mine(store.state.user as LogOnSuccess)
|
||||
if (typeof listResult === 'string') {
|
||||
errors.push(listResult)
|
||||
} else if (typeof listResult === 'undefined') {
|
||||
errors.push('API call returned 404 (this should not happen)')
|
||||
} else {
|
||||
listings.value = listResult
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getListings,
|
||||
listings
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
@ -25,7 +25,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="profile in results" :key="profile.citzenId">
|
||||
<td><router-link :to="`/profile/view/${profile.citizenId}`">View</router-link></td>
|
||||
<td><router-link :to="`/profile/${profile.citizenId}/view`">View</router-link></td>
|
||||
<td :class="{ 'font-weight-bold' : profile.seekingEmployment }">{{profile.displayName}}</td>
|
||||
<td class="text-center">{{yesOrNo(profile.seekingEmployment)}}</td>
|
||||
<td class="text-center">{{yesOrNo(profile.remoteWork)}}</td>
|
||||
|
@ -26,7 +26,9 @@
|
||||
|
||||
<template v-if="user.citizenId === it.citizen.id">
|
||||
<br><br>
|
||||
<button class="btn btn-primary" @click="editProfile"><icon icon="pencil" /> Edit Your Profile</button>
|
||||
<router-link class="btn btn-primary" to="/citizen/profile">
|
||||
<icon icon="pencil" /> Edit Your Profile
|
||||
</router-link>
|
||||
</template>
|
||||
</load-data>
|
||||
</article>
|
||||
@ -34,7 +36,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, Ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useRoute } from 'vue-router'
|
||||
import marked from 'marked'
|
||||
import api, { LogOnSuccess, markedOptions, ProfileForView } from '@/api'
|
||||
import { useStore } from '@/store'
|
||||
@ -46,7 +48,6 @@ export default defineComponent({
|
||||
setup () {
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
/** The currently logged-on user */
|
||||
const user = store.state.user as LogOnSuccess
|
||||
@ -96,8 +97,7 @@ export default defineComponent({
|
||||
workTypes,
|
||||
citizenName,
|
||||
bioHtml: computed(() => marked(it.value?.profile.biography || '', markedOptions)),
|
||||
expHtml: computed(() => marked(it.value?.profile.experience || '', markedOptions)),
|
||||
editProfile: () => router.push('/citizen/profile')
|
||||
expHtml: computed(() => marked(it.value?.profile.experience || '', markedOptions))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -17,19 +17,23 @@
|
||||
</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>
|
||||
<button type="submit" class="btn btn-primary" @click.prevent="saveStory(true)">
|
||||
<icon icon="content-save-outline" /> Save
|
||||
</button>
|
||||
<p v-if="isNew">
|
||||
<em>(Saving this will set “Seeking Employment” to “No” on your profile.)</em>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</load-data>
|
||||
<maybe-save :isShown="confirmNavShown" :toRoute="nextRoute" :saveAction="doSave" :validator="v$"
|
||||
@close="confirmClose" />
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive } from 'vue'
|
||||
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'
|
||||
import { computed, defineComponent, reactive, ref, Ref } from 'vue'
|
||||
import { onBeforeRouteLeave, RouteLocationNormalized, useRoute, useRouter } from 'vue-router'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import api, { LogOnSuccess, StoryForm } from '@/api'
|
||||
import { toastError, toastSuccess } from '@/components/layout/AppToaster.vue'
|
||||
@ -37,12 +41,14 @@ import { useStore } from '@/store'
|
||||
|
||||
import LoadData from '@/components/LoadData.vue'
|
||||
import MarkdownEditor from '@/components/MarkdownEditor.vue'
|
||||
import MaybeSave from '@/components/MaybeSave.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'StoryEdit',
|
||||
components: {
|
||||
LoadData,
|
||||
MarkdownEditor
|
||||
MarkdownEditor,
|
||||
MaybeSave
|
||||
},
|
||||
setup () {
|
||||
const store = useStore()
|
||||
@ -105,28 +111,32 @@ export default defineComponent({
|
||||
toastError(foundResult, 'clearing employment flag')
|
||||
} else {
|
||||
toastSuccess('Success Story saved and Seeking Employment flag cleared successfully')
|
||||
v$.value.$reset()
|
||||
if (navigate) {
|
||||
router.push('/success-story/list')
|
||||
v$.value.$reset()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
toastSuccess('Success Story saved successfully')
|
||||
v$.value.$reset()
|
||||
if (navigate) {
|
||||
router.push('/success-story/list')
|
||||
v$.value.$reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether the navigation confirmation is shown */
|
||||
const confirmNavShown = ref(false)
|
||||
|
||||
/** The "next" route (will be navigated or cleared) */
|
||||
const nextRoute : Ref<RouteLocationNormalized | undefined> = ref(undefined)
|
||||
|
||||
/** 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
|
||||
}
|
||||
nextRoute.value = to
|
||||
confirmNavShown.value = true
|
||||
return false
|
||||
})
|
||||
|
||||
@ -135,7 +145,11 @@ export default defineComponent({
|
||||
isNew,
|
||||
retrieveStory,
|
||||
v$,
|
||||
saveStory
|
||||
saveStory,
|
||||
confirmNavShown,
|
||||
nextRoute,
|
||||
doSave: async () => await saveStory(false),
|
||||
confirmClose: () => { confirmNavShown.value = false }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -15,10 +15,10 @@
|
||||
<tbody>
|
||||
<tr v-for="story in stories" :key="story.id">
|
||||
<td>
|
||||
<router-link v-if="story.hasStory" :to="`/success-story/view/${story.id}`">View</router-link>
|
||||
<router-link v-if="story.hasStory" :to="`/success-story/${story.id}/view`">View</router-link>
|
||||
<em v-else>None</em>
|
||||
<template v-if="story.citizenId === user.citizenId">
|
||||
~ <router-link :to="`/success-story/edit/${story.id}`">Edit</router-link>
|
||||
~ <router-link :to="`/success-story/${story.id}/edit`">Edit</router-link>
|
||||
</template>
|
||||
</td>
|
||||
<td>{{story.citizenName}}</td>
|
||||
|
Loading…
Reference in New Issue
Block a user