Complete toast implementation
This commit is contained in:
parent
0679fe04ef
commit
058c5e5b77
|
@ -9,21 +9,18 @@
|
||||||
<component :is="Component" />
|
<component :is="Component" />
|
||||||
</transition>
|
</transition>
|
||||||
</router-view>
|
</router-view>
|
||||||
<button @click.prevent="showToast('howdy', 'danger')">Show toast</button>
|
|
||||||
</main>
|
</main>
|
||||||
<app-footer />
|
<app-footer />
|
||||||
<div aria-live="polite" aria-atomic="true">
|
<app-toaster />
|
||||||
<div class="toast-container position-absolute p-3 bottom-0 start-50 translate-middle-x" id="toasts"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import { Toast } from 'bootstrap'
|
|
||||||
import AppFooter from './components/layout/AppFooter.vue'
|
import AppFooter from './components/layout/AppFooter.vue'
|
||||||
import AppNav from './components/layout/AppNav.vue'
|
import AppNav from './components/layout/AppNav.vue'
|
||||||
|
import AppToaster from './components/layout/AppToaster.vue'
|
||||||
import TitleBar from './components/layout/TitleBar.vue'
|
import TitleBar from './components/layout/TitleBar.vue'
|
||||||
|
|
||||||
import 'bootstrap/dist/css/bootstrap.min.css'
|
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||||
|
@ -34,12 +31,8 @@ export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
AppFooter,
|
AppFooter,
|
||||||
AppNav,
|
AppNav,
|
||||||
|
AppToaster,
|
||||||
TitleBar
|
TitleBar
|
||||||
},
|
|
||||||
setup () {
|
|
||||||
return {
|
|
||||||
showToast
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -52,33 +45,6 @@ export default defineComponent({
|
||||||
export function yesOrNo (cond : boolean) : string {
|
export function yesOrNo (cond : boolean) : string {
|
||||||
return cond ? 'Yes' : 'No'
|
return cond ? 'Yes' : 'No'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a toast notification
|
|
||||||
*
|
|
||||||
* @param text The text of the notification
|
|
||||||
* @param type The type of notification to show (defaults to 'success')
|
|
||||||
* @param heading The optional text to use for the heading
|
|
||||||
*/
|
|
||||||
export function showToast (text : string, type = 'success', heading : string | undefined) : void {
|
|
||||||
let header : HTMLDivElement | undefined
|
|
||||||
if (heading) {
|
|
||||||
header = document.createElement('div')
|
|
||||||
header.className = 'toast-header'
|
|
||||||
header.innerHTML = 'The Header'
|
|
||||||
}
|
|
||||||
const body = document.createElement('div')
|
|
||||||
body.className = 'toast-body'
|
|
||||||
body.innerHTML = text
|
|
||||||
|
|
||||||
const toast = document.createElement('div')
|
|
||||||
if (header) toast.appendChild(header)
|
|
||||||
toast.appendChild(body)
|
|
||||||
toast.className = `toast bg-${type} text-white`
|
|
||||||
|
|
||||||
;(document.getElementById('toasts') as HTMLDivElement).appendChild(toast)
|
|
||||||
new Toast(toast).show()
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="sass">
|
<style lang="sass">
|
||||||
|
|
83
src/JobsJobsJobs/App/src/components/layout/AppToaster.vue
Normal file
83
src/JobsJobsJobs/App/src/components/layout/AppToaster.vue
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<template>
|
||||||
|
<div aria-live="polite" aria-atomic="true">
|
||||||
|
<div class="toast-container position-absolute p-3 bottom-0 start-50 translate-middle-x" id="toasts"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { Toast } from 'bootstrap'
|
||||||
|
|
||||||
|
/** Remove a toast once it's hidden */
|
||||||
|
const removeToast = (event : Event) => (event.target as HTMLDivElement).remove()
|
||||||
|
|
||||||
|
/** Create a toast, add it to the DOM, and show it */
|
||||||
|
const createToast = (level : 'success' | 'warning' | 'danger', message : string, process : string | undefined) => {
|
||||||
|
let header : HTMLDivElement | undefined
|
||||||
|
if (level !== 'success') {
|
||||||
|
// Create a heading, optionally including the process that generated the message
|
||||||
|
const heading = (typ : string) : string => {
|
||||||
|
const proc = process ? ` (${process})` : ''
|
||||||
|
return `<span class="me-auto"><strong>${typ.toUpperCase()}</strong>${proc}</span>`
|
||||||
|
}
|
||||||
|
header = document.createElement('div')
|
||||||
|
header.className = 'toast-header'
|
||||||
|
header.innerHTML = heading(level === 'warning' ? level : 'error')
|
||||||
|
// Include a close button, as these will not auto-close
|
||||||
|
const close = document.createElement('button')
|
||||||
|
close.type = 'button'
|
||||||
|
close.className = 'btn-close'
|
||||||
|
close.setAttribute('data-bs-dismiss', 'toast')
|
||||||
|
close.setAttribute('aria-label', 'Close')
|
||||||
|
header.appendChild(close)
|
||||||
|
}
|
||||||
|
const body = document.createElement('div')
|
||||||
|
body.className = 'toast-body'
|
||||||
|
body.innerHTML = message
|
||||||
|
|
||||||
|
const toastEl = document.createElement('div')
|
||||||
|
toastEl.className = `toast bg-${level} text-white`
|
||||||
|
toastEl.setAttribute('role', 'alert')
|
||||||
|
toastEl.setAttribute('aria-live', 'assertlive')
|
||||||
|
toastEl.setAttribute('aria-atomic', 'true')
|
||||||
|
toastEl.addEventListener('hidden.bs.toast', removeToast)
|
||||||
|
if (header) toastEl.appendChild(header)
|
||||||
|
toastEl.appendChild(body)
|
||||||
|
|
||||||
|
;(document.getElementById('toasts') as HTMLDivElement).appendChild(toastEl)
|
||||||
|
new Toast(toastEl, { autohide: level === 'success' }).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a success toast
|
||||||
|
*
|
||||||
|
* @param message The message to be displayed
|
||||||
|
*/
|
||||||
|
export function toastSuccess (message : string) : void {
|
||||||
|
createToast('success', message, undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a warning toast
|
||||||
|
*
|
||||||
|
* @param message The message to be displayed
|
||||||
|
* @param process The process which generated the warning (optional)
|
||||||
|
*/
|
||||||
|
export function toastWarning (message : string, process : string | undefined) : void {
|
||||||
|
createToast('warning', message, process)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate an error toast
|
||||||
|
*
|
||||||
|
* @param message The message to be displayed
|
||||||
|
* @param process The process which generated the error (optional)
|
||||||
|
*/
|
||||||
|
export function toastError (message : string, process : string | undefined) : void {
|
||||||
|
createToast('danger', message, process)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'AppToaster'
|
||||||
|
})
|
||||||
|
</script>
|
|
@ -9,6 +9,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, onMounted } from 'vue'
|
import { defineComponent, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { toastSuccess } from '@/components/layout/AppToaster.vue'
|
||||||
import { useStore } from '@/store'
|
import { useStore } from '@/store'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -19,8 +20,8 @@ export default defineComponent({
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
store.commit('clearUser')
|
store.commit('clearUser')
|
||||||
|
toastSuccess('Log Off Successful | <strong>Have a Nice Day!</strong>')
|
||||||
router.push('/')
|
router.push('/')
|
||||||
// TODO: toast
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return { }
|
return { }
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
<page-title title="Account Deletion Options" />
|
<page-title title="Account Deletion Options" />
|
||||||
<h3>Account Deletion Options</h3>
|
<h3>Account Deletion Options</h3>
|
||||||
|
|
||||||
<p v-if="error !== ''">{{error}}</p>
|
|
||||||
|
|
||||||
<h4>Option 1 – Delete Your Profile</h4>
|
<h4>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
|
||||||
|
@ -38,9 +36,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import api, { LogOnSuccess } from '@/api'
|
import api, { LogOnSuccess } from '@/api'
|
||||||
|
import { toastError, toastSuccess } from '@/components/layout/AppToaster.vue'
|
||||||
import { useStore } from '@/store'
|
import { useStore } from '@/store'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -49,16 +48,13 @@ export default defineComponent({
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
/** Error message encountered during actions */
|
|
||||||
const error = ref('')
|
|
||||||
|
|
||||||
/** Delete the profile only; redirect to home page on success */
|
/** Delete the profile only; redirect to home page on success */
|
||||||
const deleteProfile = async () => {
|
const deleteProfile = async () => {
|
||||||
const resp = await api.profile.delete(store.state.user as LogOnSuccess)
|
const resp = await api.profile.delete(store.state.user as LogOnSuccess)
|
||||||
if (typeof resp === 'string') {
|
if (typeof resp === 'string') {
|
||||||
error.value = resp
|
toastError(resp, 'Deleting Profile')
|
||||||
} else {
|
} else {
|
||||||
// TODO: notify
|
toastSuccess('Profile Deleted Successfully')
|
||||||
router.push('/citizen/dashboard')
|
router.push('/citizen/dashboard')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,16 +63,15 @@ export default defineComponent({
|
||||||
const deleteAccount = async () => {
|
const deleteAccount = async () => {
|
||||||
const resp = await api.citizen.delete(store.state.user as LogOnSuccess)
|
const resp = await api.citizen.delete(store.state.user as LogOnSuccess)
|
||||||
if (typeof resp === 'string') {
|
if (typeof resp === 'string') {
|
||||||
error.value = resp
|
toastError(resp, 'Deleting Account')
|
||||||
} else {
|
} else {
|
||||||
store.commit('clearUser')
|
store.commit('clearUser')
|
||||||
// TODO: notify
|
toastSuccess('Account Deleted Successfully')
|
||||||
router.push('/so-long/success')
|
router.push('/so-long/success')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
error,
|
|
||||||
deleteProfile,
|
deleteProfile,
|
||||||
deleteAccount
|
deleteAccount
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user