Create sign-in and sign-out workflow in SPA

This commit is contained in:
Eli Ribble 2026-04-16 17:14:57 +00:00
parent 08a1b5b81d
commit b6d1bd9ee2
No known key found for this signature in database
15 changed files with 761 additions and 460 deletions

View file

@ -0,0 +1,123 @@
<style scoped>
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
}
.btn:disabled {
cursor: not-allowed;
}
</style>
<template>
<button
:class="buttonClasses"
:disabled="disabled || loading"
@click="handleClick"
>
<!-- Loading Spinner -->
<span
v-if="loading"
class="spinner-border spinner-border-sm me-2"
role="status"
aria-hidden="true"
></span>
<!-- Icon (only show when not loading) -->
<i v-if="icon && !loading" :class="iconClasses"></i>
<!-- Button Text -->
<span v-if="text">{{ text }}</span>
<!-- Slot for additional content -->
<slot></slot>
</button>
</template>
<script setup>
import { computed } from "vue";
// Define props
const props = defineProps({
text: {
type: String,
default: "",
},
icon: {
type: String,
default: "",
},
variant: {
type: String,
default: "primary",
validator: (value) =>
[
"primary",
"secondary",
"success",
"danger",
"warning",
"info",
"light",
"dark",
"link",
"outline-primary",
"outline-secondary",
"outline-success",
"outline-danger",
"outline-warning",
"outline-info",
"outline-light",
"outline-dark",
].includes(value),
},
size: {
type: String,
default: "",
validator: (value) => ["", "sm", "lg"].includes(value),
},
loading: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
block: {
type: Boolean,
default: false,
},
});
// Define emits
const emit = defineEmits(["click"]);
// Computed classes for button
const buttonClasses = computed(() => {
return [
"btn",
`btn-${props.variant}`,
{
[`btn-${props.size}`]: props.size,
"w-100": props.block,
disabled: props.loading,
},
];
});
// Computed classes for icon
const iconClasses = computed(() => {
return [
props.icon,
{ "me-2": props.text }, // Add margin if there's text
];
});
// Handle click event
const handleClick = (event) => {
if (!props.loading && !props.disabled) {
emit("click", event);
}
};
</script>

View file

@ -1,11 +1,15 @@
<template>
<div id="content">
<div v-if="session.loading">Loading...</div>
<div v-else-if="session.error">Error: {{ session.error }}</div>
<slot />
</div>
</template>
<script setup lang="ts">
// No imports needed for this simple component
import { useSessionStore } from "@/store/session";
const session = useSessionStore();
</script>
<style scoped></style>

View file

@ -197,6 +197,9 @@
label="Configuration"
/>
</li>
<li>
<NavigationLink to="/signout" icon="door-open" label="Signout" />
</li>
<li>
<NavigationLink to="/_/sudo" icon="god" label="Sudo" />
</li>