WIP on edit profile

Also many infrastructure / placeholder additions/changes
This commit is contained in:
Daniel J. Summers 2021-07-24 23:22:52 -04:00
parent d36b01cae5
commit e8696e0e94
17 changed files with 534 additions and 160 deletions

View File

@ -22,6 +22,7 @@ let configureApp (app : IApplicationBuilder) =
e.MapFallbackToFile "index.html" |> ignore) e.MapFallbackToFile "index.html" |> ignore)
|> ignore |> ignore
open Newtonsoft.Json
open NodaTime open NodaTime
open Microsoft.AspNetCore.Authentication.JwtBearer open Microsoft.AspNetCore.Authentication.JwtBearer
open Microsoft.Extensions.Configuration open Microsoft.Extensions.Configuration
@ -36,6 +37,10 @@ let configureServices (svc : IServiceCollection) =
svc.AddLogging () |> ignore svc.AddLogging () |> ignore
svc.AddCors () |> ignore svc.AddCors () |> ignore
let jsonCfg = JsonSerializerSettings ()
Data.Converters.all () |> List.iter jsonCfg.Converters.Add
svc.AddSingleton<Json.ISerializer> (NewtonsoftJson.Serializer jsonCfg) |> ignore
let svcs = svc.BuildServiceProvider () let svcs = svc.BuildServiceProvider ()
let cfg = svcs.GetRequiredService<IConfiguration> () let cfg = svcs.GetRequiredService<IConfiguration> ()

View File

@ -1,4 +1,4 @@
import { Count, LogOnSuccess, Profile } from './types' import { Citizen, Continent, Count, LogOnSuccess, Profile } from './types'
/** /**
* Create a URL that will access the API * Create a URL that will access the API
@ -39,6 +39,46 @@ export default {
const resp = await fetch(apiUrl(`citizen/log-on/${code}`), { method: 'GET', mode: 'cors' }) const resp = await fetch(apiUrl(`citizen/log-on/${code}`), { method: 'GET', mode: 'cors' })
if (resp.status === 200) return await resp.json() as LogOnSuccess if (resp.status === 200) return await resp.json() as LogOnSuccess
return `Error logging on - ${await resp.text()}` return `Error logging on - ${await resp.text()}`
},
/**
* Retrieve a citizen by their ID
*
* @param id The citizen ID to be retrieved
* @param user The currently logged-on user
* @returns The citizen, or an error
*/
retrieve: async (id : string, user : LogOnSuccess) : Promise<Citizen | string> => {
const resp = await fetch(apiUrl(`citizen/get/${id}`), reqInit('GET', user))
if (resp.status === 200) return await resp.json() as Citizen
return `Error retrieving citizen ${id} - ${await resp.text()}`
},
/**
* Delete the current citizen's entire Jobs, Jobs, Jobs record
*
* @param user The currently logged-on user
* @returns Undefined if successful, an error if not
*/
delete: async (user : LogOnSuccess) : Promise<string | undefined> => {
const resp = await fetch(apiUrl('citizen'), reqInit('DELETE', user))
if (resp.status === 200) return undefined
return `Error deleting citizen - ${await resp.text()}`
}
},
/** API functions for continents */
continent: {
/**
* Get all continents
*
* @returns All continents, or an error
*/
all: async () : Promise<Continent[] | string> => {
const resp = await fetch(apiUrl('continent/all'), { method: 'GET' })
if (resp.status === 200) return await resp.json() as Continent[]
return `Error retrieving continents - ${await resp.text()}`
} }
}, },
@ -71,7 +111,19 @@ export default {
const result = await resp.json() as Count const result = await resp.json() as Count
return result.count return result.count
} }
return `Error counting profiles = ${await resp.text()}` return `Error counting profiles - ${await resp.text()}`
},
/**
* Delete the current user's employment profile
*
* @param user The currently logged-on user
* @returns Undefined if successful, an error if not
*/
delete: async (user : LogOnSuccess) : Promise<string | undefined> => {
const resp = await fetch(apiUrl('profile'), reqInit('DELETE', user))
if (resp.status === 200) return undefined
return `Error deleting profile - ${await resp.text()}`
} }
} }
} }

View File

@ -1,4 +1,30 @@
/** A user of Jobs, Jobs, Jobs */
export interface Citizen {
/** The ID of the user */
id : string
/** The handle by which the user is known on Mastodon */
naUser : string
/** The user's display name from Mastodon (updated every login) */
displayName : string | undefined
/** The user's real name */
realName : string | undefined
/** The URL for the user's Mastodon profile */
profileUrl : string
/** When the user joined Jobs, Jobs, Jobs */
joinedOn : number
/** When the user last logged in */
lastSeenOn : number
}
/** A continent */
export interface Continent {
/** The ID of the continent */
id : string
/** The name of the continent */
name : string
}
/** A successful logon */ /** A successful logon */
export interface LogOnSuccess { export interface LogOnSuccess {
/** The JSON Web Token (JWT) to use for API access */ /** The JSON Web Token (JWT) to use for API access */

View File

@ -0,0 +1,52 @@
<template>
<nav class="nav nav-pills">
<a href="#" class="nav-link @MarkdownClass" @click.prevent="showMarkdown">Markdown</a>
<a href="#" class="nav-link @PreviewClass" @click.prevent="showPreview">Preview</a>
</nav>
<section v-if="preview" class="preview" v-html="previewHtml">
</section>
<textarea v-else :id="id" class="form-control" rows="10" v-text="text"
@input="$emit('update:text', $event.target.value)"></textarea>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'MarkdownEditor',
props: {
id: {
type: String,
required: true
},
text: {
type: String,
required: true
}
},
emits: ['update:text'],
setup (props) {
/** Whether to show the Markdown preview */
const preview = ref(false)
/** The HTML rendered for preview purposes */
const previewHtml = ref('')
/** Show the Markdown source */
const showMarkdown = () => { preview.value = false }
/** Show the Markdown preview */
const showPreview = () => {
// TODO: render markdown as HTML
previewHtml.value = props.text
preview.value = true
}
return {
preview,
previewHtml,
showMarkdown,
showPreview
}
}
})
</script>

View File

@ -0,0 +1,48 @@
<template>
<div v-if="loading">Loading&hellip;</div>
<template v-else>
<div v-if="errors.length > 0">
<p v-for="(error, idx) in errors" :key="idx">{{error}}</p>
</div>
<slot v-else></slot>
</template>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue'
export default defineComponent({
name: 'LoadData',
props: {
load: {
type: Function,
required: true
}
},
setup (props) {
/** Type the input function */
const func = props.load as (errors: string[]) => Promise<unknown>
/** Errors encountered during loading */
const errors : string[] = []
/** Whether we are currently loading data */
const loading = ref(true)
/** Call the data load function */
const loadData = async () => {
try {
await func(errors)
} finally {
loading.value = false
}
}
onMounted(loadData)
return {
loading,
errors
}
}
})
</script>

View File

@ -17,7 +17,42 @@ const routes: Array<RouteRecordRaw> = [
}, },
{ {
path: '/citizen/profile', path: '/citizen/profile',
component: () => import(/* webpackChurchName: "profedit" */ '../views/citizen/EditProfile.vue') component: () => import(/* webpackChunkName: "profedit" */ '../views/citizen/EditProfile.vue')
},
{
path: '/citizen/log-off',
component: () => import(/* webpackChunkName: "logoff" */ '../views/citizen/LogOff.vue')
},
// Profile URLs
{
path: '/profile/view/:id',
component: () => import(/* webpackChunkName: "profview" */ '../views/profile/ProfileView.vue')
},
{
path: '/profile/search',
component: () => import(/* webpackChunkName: "profview" */ '../views/profile/ProfileSearch.vue')
},
{
path: '/profile/seeking',
component: () => import(/* webpackChunkName: "seeking" */ '../views/profile/Seeking.vue')
},
// "So Long" URLs
{
path: '/so-long/options',
component: () => import(/* webpackChunkName: "so-long" */ '../views/so-long/DeletionOptions.vue')
},
{
path: '/so-long/success',
component: () => import(/* webpackChunkName: "so-long" */ '../views/so-long/DeletionSuccess.vue')
},
// Success Story URLs
{
path: '/success-story/list',
component: () => import(/* webpackChunkName: "succview" */ '../views/success-story/StoryList.vue')
},
{
path: '/success-story/add',
component: () => import(/* webpackChunkName: "succedit" */ '../views/success-story/StoryAdd.vue')
} }
] ]

View File

@ -1,11 +1,12 @@
import { InjectionKey } from 'vue' import { InjectionKey } from 'vue'
import { createStore, Store, useStore as baseUseStore } from 'vuex' import { createStore, Store, useStore as baseUseStore } from 'vuex'
import api, { LogOnSuccess } from '../api' import api, { Continent, LogOnSuccess } from '../api'
/** The state tracked by the application */ /** The state tracked by the application */
export interface State { export interface State {
user: LogOnSuccess | undefined user: LogOnSuccess | undefined
logOnState: string logOnState: string
continents: Continent[]
} }
/** An injection key to identify this state with Vue */ /** An injection key to identify this state with Vue */
@ -20,15 +21,22 @@ export default createStore({
state: () : State => { state: () : State => {
return { return {
user: undefined, user: undefined,
logOnState: 'Logging you on with No Agenda Social...' logOnState: 'Logging you on with No Agenda Social...',
continents: []
} }
}, },
mutations: { mutations: {
setUser (state, user: LogOnSuccess) { setUser (state, user: LogOnSuccess) {
state.user = user state.user = user
}, },
clearUser (state) {
state.user = undefined
},
setLogOnState (state, message) { setLogOnState (state, message) {
state.logOnState = message state.logOnState = message
},
setContinents (state, continents : Continent[]) {
state.continents = continents
} }
}, },
actions: { actions: {
@ -39,6 +47,15 @@ export default createStore({
} else { } else {
commit('setUser', logOnResult) commit('setUser', logOnResult)
} }
},
async ensureContinents ({ state, commit }) {
if (state.continents.length > 0) return
const theSeven = await api.continent.all()
if (typeof theSeven === 'string') {
console.error(theSeven)
} else {
commit('setContinents', theSeven)
}
} }
}, },
modules: { modules: {

View File

@ -1,29 +1,30 @@
<template> <template>
<h3>Welcome, {{user.name}}</h3> <h3>Welcome, {{user.name}}</h3>
<load-data :load="retrieveData">
<template v-if="profile"> <template v-if="profile">
<p>
Your employment profile was last updated {{profile.lastUpdatedOn}}. Your profile currently lists
{{profile.skills.length}} skill<span v-if="profile.skills.length !== 1">s</span>.
</p>
<p><router-link :to="'/profile/view/' + user.citizenId">View Your Employment Profile</router-link></p>
<p v-if="profile.seekingEmployment">
Your profile indicates that you are seeking employment. Once you find it,
<router-link to="/success-story/add">tell your fellow citizens about it!</router-link>
</p>
</template>
<template v-else>
<p>
You do not have an employment profile established; click &ldquo;Edit Profile&rdquo; in the menu to get
started!
</p>
</template>
<hr>
<p> <p>
Your employment profile was last updated {{profile.lastUpdatedOn}}. Your profile currently lists There <span v-if="profileCount === 1">is</span><span v-else>are</span> <span v-if="profileCount === 0">no</span><span v-else>{{profileCount}}</span>
{{profile.skills.length}} skill<span v-if="profile.skills.length !== 1">s</span>. employment profile<span v-if="profileCount !== 1">s</span> from citizens of Gitmo Nation.
<span v-if="profileCount > 0">Take a look around and see if you can help them find work!</span>
</p> </p>
<p><router-link :to="'/profile/view/' + user.citizenId">View Your Employment Profile</router-link></p> </load-data>
<p v-if="profile.seekingEmployment">
Your profile indicates that you are seeking employment. Once you find it,
<router-link to="/success-story/add">tell your fellow citizens about it!</router-link>
</p>
</template>
<template v-else>
<p>
You do not have an employment profile established; click &ldquo;Edit Profile&rdquo; in the menu to get
started!
</p>
</template>
<hr>
<p>
There <span v-if="profileCount === 1">is</span><span v-else>are</span> <span v-if="profileCount === 0">no</span><span v-else>{{profileCount}}</span>
employment profile<span v-if="profileCount !== 1">s</span> from citizens of Gitmo Nation.
<span v-if="profileCount > 0">Take a look around and see if you can help them find work!</span>
</p>
<hr> <hr>
<p> <p>
To see how this application works, check out &ldquo;How It Works&rdquo; in the sidebar (last updated June To see how this application works, check out &ldquo;How It Works&rdquo; in the sidebar (last updated June
@ -32,47 +33,44 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, onMounted, Ref, ref } from 'vue' import { defineComponent, Ref, ref } from 'vue'
import api, { LogOnSuccess, Profile } from '../../api' import api, { LogOnSuccess, Profile } from '../../api'
import { useStore } from '../../store' import { useStore } from '../../store'
import LoadData from '../../components/shared/LoadData.vue'
export default defineComponent({ export default defineComponent({
name: 'Dashboard', name: 'Dashboard',
components: { LoadData },
setup () { setup () {
const store = useStore() const store = useStore()
/** The currently logged-in user */ /** The currently logged-in user */
const user = store.state.user as LogOnSuccess const user = store.state.user as LogOnSuccess
/** Error messages from data retrieval */
const errorMessages : string[] = []
/** The user's profile */ /** The user's profile */
const profile : Ref<Profile | undefined> = ref(undefined) const profile : Ref<Profile | undefined> = ref(undefined)
/** A count of profiles in the system */ /** A count of profiles in the system */
const profileCount = ref(0) const profileCount = ref(0)
const retrieveData = async () => { const retrieveData = async (errors : string[]) => {
const profileResult = await api.profile.retreive(undefined, user) const profileResult = await api.profile.retreive(undefined, user)
if (typeof profileResult === 'string') { if (typeof profileResult === 'string') {
errorMessages.push(profileResult) errors.push(profileResult)
} else if (typeof profileResult !== 'undefined') { } else if (typeof profileResult !== 'undefined') {
profile.value = profileResult profile.value = profileResult
} }
const count = await api.profile.count(user) const count = await api.profile.count(user)
if (typeof count === 'string') { if (typeof count === 'string') {
errorMessages.push(count) errors.push(count)
} else { } else {
profileCount.value = count profileCount.value = count
} }
} }
onMounted(retrieveData)
return { return {
retrieveData,
user, user,
errorMessages,
profile, profile,
profileCount profileCount
} }

View File

@ -1,123 +1,103 @@
<template> <template>
<h3>Employment Profile</h3> <h3>Employment Profile</h3>
<v-form> <load-data :load="retrieveData">
<v-container> <form>
<v-row> <v-container>
<v-col cols="12" sm="10" md="8" lg="6"> <v-row>
<v-text-field label="Real Name" <v-col cols="12" sm="10" md="8" lg="6">
placeholder="Leave blank to use your NAS display name" <label for="realName">Real Name</label>
counter="255" <input type="text" id="realName" v-model="realName" maxlength="255"
maxlength="255" placeholder="Leave blank to use your NAS display name">
:value="realName"></v-text-field> </v-col>
<div class="form-group"> </v-row>
<label for="realName" class="jjj-label">Real Name</label> <v-row>
[InputText id="realName" @bind-Value=@ProfileForm.RealName class="form-control" <v-col>
placeholder="Leave blank to use your NAS display name" /] <label>
[ValidationMessage For=@(() => ProfileForm.RealName) /] <input type="checkbox" v-model="profile.seekingEmployment">
</div> I am currently seeking employment
</v-col> </label>
</v-row>
<div class="form-row">
<div class="col">
<div class="form-check">
[InputCheckbox id="seeking" class="form-check-input" @bind-Value=@ProfileForm.IsSeekingEmployment /]
<label for="seeking" class="form-check-label">I am currently seeking employment</label>
<em v-if="profile?.seekingEmployment">&nbsp; &nbsp; If you have found employment, consider <em v-if="profile?.seekingEmployment">&nbsp; &nbsp; If you have found employment, consider
<router-link to="/success-story/add">telling your fellow citizens about it</router-link> <router-link to="/success-story/add">telling your fellow citizens about it</router-link>
</em> </em>
</div> </v-col>
</div> </v-row>
</div> <v-row>
<div class="form-row"> <v-col cols="12" sm="6" md="4">
<div class="col col-xs-12 col-sm-6 col-md-4">
<div class="form-group">
<label for="continentId" class="jjj-required">Continent</label> <label for="continentId" class="jjj-required">Continent</label>
[InputSelect id="continentId" @bind-Value=@ProfileForm.ContinentId class="form-control"] <select id="continentId">
<option>&ndash; Select &ndash;</option> <option v-for="c in continents" :key="c.id" :value="c.id"
@foreach (var (id, name) in Continents) :selected="c.id === profile?.continentId ? 'selected' : null">{{c.name}}</option>
{ </select>
<option value="@id">@name</option> </v-col>
} <v-col cols="12" sm="6" md="8">
[/InputSelect]
[ValidationMessage For=@(() => ProfileForm.ContinentId) /]
</div>
</div>
<div class="col col-xs-12 col-sm-6 col-md-8">
<div class="form-group">
<label for="region" class="jjj-required">Region</label> <label for="region" class="jjj-required">Region</label>
[InputText id="region" @bind-Value=@ProfileForm.Region class="form-control" <input type="text" id="region" v-model="profile.region" maxlength="255"
placeholder="Country, state, geographic area, etc." /] placeholder="Country, state, geographic area, etc.">
[ValidationMessage For=@(() => ProfileForm.Region) /] </v-col>
</div> </v-row>
</div> <v-row>
</div> <v-col>
<div class="form-row">
<div class="col">
<div class="form-group">
<label for="bio" class="jjj-required">Professional Biography</label> <label for="bio" class="jjj-required">Professional Biography</label>
[MarkdownEditor Id="bio" @bind-Text=@ProfileForm.Biography /] <markdown-editor id="bio" v-model:text="profile.biography" />
[ValidationMessage For=@(() => ProfileForm.Biography) /] </v-col>
</div> </v-row>
</div> <v-row>
</div> <v-col cols="12" sm="12" offset-md="2" md="4">
<div class="form-row"> <label>
<div class="col col-xs-12 col-sm-12 offset-md-2 col-md-4"> <input type="checkbox" v-model="profile.remoteWork">
<div class="form-check"> I am looking for remote work
[InputCheckbox id="isRemote" class="form-check-input" @bind-Value=@ProfileForm.RemoteWork /] </label>
<label for="isRemote" class="form-check-label">I am looking for remote work</label> </v-col>
</div> <v-col cols="12" sm="12" md="4">
</div> <label>
<div class="col col-xs-12 col-sm-12 col-md-4"> <input type="checkbox" v-model="profile.fullTime">
<div class="form-check"> I am looking for full-time work
[InputCheckbox id="isFull" class="form-check-input" @bind-Value=@ProfileForm.FullTime /] </label>
<label for="isFull" class="form-check-label">I am looking for full-time work</label> </v-col>
</div> </v-row>
</div> <hr>
</div> <h4>
<hr> Skills &nbsp;
<h4> <button type="button" class="btn btn-outline-primary" @onclick="AddNewSkill">Add a Skill</button>
Skills &nbsp; </h4>
<button type="button" class="btn btn-outline-primary" @onclick="AddNewSkill">Add a Skill</button> @foreach (var skill in ProfileForm.Skills)
</h4> {
@foreach (var skill in ProfileForm.Skills) [SkillEdit Skill=@skill OnRemove=@RemoveSkill /]
{ }
[SkillEdit Skill=@skill OnRemove=@RemoveSkill /] <hr>
} <h4>Experience</h4>
<hr> <p>
<h4>Experience</h4> This application does not have a place to individually list your chronological job history; however, you can
<p> use this area to list prior jobs, their dates, and anything else you want to include that&rsquo;s not already a
This application does not have a place to individually list your chronological job history; however, you can part of your Professional Biography above.
use this area to list prior jobs, their dates, and anything else you want to include that&rsquo;s not already a </p>
part of your Professional Biography above. <v-row>
</p> <v-col>
<div class="form-row"> <markdown-editor id="experience" v-model:text="profile.experience" />
<div class="col"> </v-col>
[MarkdownEditor Id="experience" @bind-Text=@ProfileForm.Experience /] </v-row>
</div> <v-row>
</div> <v-col>
<div class="form-row"> <label>
<div class="col"> <input type="checkbox" v-model="profile.isPublic">
<div class="form-check">
[InputCheckbox id="isPublic" class="form-check-input" @bind-Value=@ProfileForm.IsPublic /]
<label for="isPublic" class="form-check-label">
Allow my profile to be searched publicly (outside NA Social) Allow my profile to be searched publicly (outside NA Social)
</label> </label>
</div> </v-col>
</div> </v-row>
</div> <v-row>
<div class="form-row"> <v-col>
<div class="col"> <br>
<br> <button type="submit" class="btn btn-outline-primary">Save</button>
<button type="submit" class="btn btn-outline-primary">Save</button> </v-col>
</div> </v-row>
</div> </v-container>
</v-container> </form>
</v-form> <p v-if="!isNew">
<p v-if="!isNew"> <br><router-link :to="`/profile/view/${user.citizenId}`"><v-icon icon="file-account-outline" /> View Your User
<br><router-link :to="'/profile/view/' + user.citizenId"><v-icon icon="file-account-outline" /> View Your User Profile</router-link>
Profile</router-link> </p>
</p> </load-data>
<p> <p>
<br>If you want to delete your profile, or your entire account, <router-link to="/so-long/options">see your deletion <br>If you want to delete your profile, or your entire account, <router-link to="/so-long/options">see your deletion
options here</router-link>. options here</router-link>.
@ -126,12 +106,18 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, onMounted, ref } from 'vue' import { computed, defineComponent, Ref, ref } from 'vue'
import api, { LogOnSuccess, Profile } from '../../api' import api, { LogOnSuccess, Profile } from '../../api'
import MarkdownEditor from '../../components/MarkdownEditor.vue'
import LoadData from '../../components/shared/LoadData.vue'
import { useStore } from '../../store' import { useStore } from '../../store'
export default defineComponent({ export default defineComponent({
name: 'EditProfile', name: 'EditProfile',
components: {
LoadData,
MarkdownEditor
},
setup () { setup () {
const store = useStore() const store = useStore()
@ -141,35 +127,39 @@ export default defineComponent({
/** Whether this is a new profile */ /** Whether this is a new profile */
const isNew = ref(false) const isNew = ref(false)
/** Errors that may be encountered */
const errorMessages : string[] = []
/** The user's current profile */ /** The user's current profile */
let profile : Profile | undefined const profile : Ref<Profile | undefined> = ref(undefined)
/** The user's real name */ /** The user's real name */
let realName : string | undefined const realName : Ref<string | undefined> = ref(undefined)
/** Retrieve the user's profile */ /** Retrieve the user's profile and their real name */
const loadProfile = async () => { const retrieveData = async (errors : string[]) => {
await store.dispatch('ensureContinents')
const profileResult = await api.profile.retreive(undefined, user) const profileResult = await api.profile.retreive(undefined, user)
if (typeof profileResult === 'string') { if (typeof profileResult === 'string') {
errorMessages.push(profileResult) errors.push(profileResult)
} else if (typeof profileResult === 'undefined') { } else if (typeof profileResult === 'undefined') {
isNew.value = true isNew.value = true
} else { } else {
profile = profileResult profile.value = profileResult
// console.info(JSON.stringify(profile))
}
const nameResult = await api.citizen.retrieve(user.citizenId, user)
if (typeof nameResult === 'string') {
errors.push(nameResult)
} else {
realName.value = nameResult.realName || ''
} }
} }
onMounted(loadProfile)
return { return {
retrieveData,
user, user,
isNew, isNew,
errorMessages,
profile, profile,
realName realName,
continents: computed(() => store.state.continents)
} }
} }
}) })

View File

@ -0,0 +1,25 @@
<template>
<p>Logging off&hellip;</p>
</template>
<script lang="ts">
import { defineComponent, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from '../../store'
export default defineComponent({
name: 'LogOff',
setup () {
const store = useStore()
const router = useRouter()
onMounted(() => {
store.commit('clearUser')
router.push('/')
// TODO: toast
})
return { }
}
})
</script>

View File

@ -0,0 +1,3 @@
<template>
<p>TODO: convert this view</p>
</template>

View File

@ -0,0 +1,20 @@
<template>
<p>TODO: convert this template</p>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { useRoute } from 'vue-router'
export default defineComponent({
name: 'ProfileEdit',
setup () {
const route = useRoute()
const profileId = route.params.id
return {
profileId
}
}
})
</script>

View File

@ -0,0 +1,3 @@
<template>
<p>TODO: convert this view</p>
</template>

View File

@ -0,0 +1,82 @@
<template>
<h3>Account Deletion Options</h3>
<p v-if="error !== ''">{{error}}</p>
<h4>Option 1 &ndash; Delete Your Profile</h4>
<p>
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&rsquo;s knowledge of you. This is what you want to use if you
want to clear out your profile and start again (and remove the current one from others&rsquo; view).
</p>
<p class="text-center">
<button class="btn btn-danger" @click="deleteProfile">Delete Your Profile</button>
</p>
<hr>
<h4>Option 2 &ndash; Delete Your Account</h4>
<p>
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 below
<strong>will not</strong> affect your No Agenda Social account in any way; its effects are limited to Jobs, Jobs,
Jobs.
</p>
<p>
<em>
(This will not revoke this application&rsquo;s permissions on No Agenda Social; you will have to remove this
yourself. The confirmation message has a link where you can do this; once the page loads, find the
<strong>Jobs, Jobs, Jobs</strong> entry, and click the <strong>&times; Revoke</strong> link for that entry.)
</em>
</p>
<p class="text-center">
<button class="btn btn-danger" @click="deleteAccount">Delete Your Entire Account</button>
</p>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import { useRouter } from 'vue-router'
import api, { LogOnSuccess } from '../../api'
import { useStore } from '../../store'
export default defineComponent({
name: 'DeletionOptions',
setup () {
const store = useStore()
const router = useRouter()
/** Error message encountered during actions */
const error = ref('')
/** Delete the profile only; redirect to home page on success */
const deleteProfile = async () => {
const resp = await api.profile.delete(store.state.user as LogOnSuccess)
if (typeof resp === 'string') {
error.value = resp
} else {
// TODO: notify
router.push('/citizen/dashboard')
}
}
/** Delete everything pertaining to the user's account */
const deleteAccount = async () => {
const resp = await api.citizen.delete(store.state.user as LogOnSuccess)
if (typeof resp === 'string') {
error.value = resp
} else {
store.commit('clearUser')
// TODO: notify
router.push('/so-long/success')
}
}
return {
error,
deleteProfile,
deleteAccount
}
}
})
</script>

View File

@ -0,0 +1,12 @@
<template>
<h3>Account Deletion Success</h3>
<p>
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
<strong>&times; Revoke</strong>. Otherwise, clicking &ldquo;Log On&rdquo; in the left-hand menu will create a new,
empty account without prompting you further.
</p>
<p>
Thank you for participating, and thank you for your courage. #GitmoNation
</p>
</template>

View File

@ -0,0 +1,3 @@
<template>
<p>TODO: convert this view</p>
</template>

View File

@ -0,0 +1,3 @@
<template>
<p>TODO: convert this view</p>
</template>