generated from alphane/template
Initial commit
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<PageLoader message="ALPHANE" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
|
||||
import PageLoader from "@/components/common/PageLoader.vue"
|
||||
|
||||
const router = useRouter()
|
||||
const timeout = ref<number>()
|
||||
|
||||
onMounted(() => {
|
||||
timeout.value = setTimeout(() => {
|
||||
router.push({ name: "DashboardPage" })
|
||||
}, 5000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
clearTimeout(timeout.value)
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div v-if="isSystemAdmin">
|
||||
<AppCard :to="{ name: 'administration/DashboardPage' }">You are a system admin</AppCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useBreadcrumbs from "@/use/use-breadcrumbs"
|
||||
import useCurrentUser from "@/use/use-current-user"
|
||||
|
||||
import AppCard from "@/components/common/AppCard.vue"
|
||||
|
||||
const { isSystemAdmin } = useCurrentUser<true>()
|
||||
|
||||
useBreadcrumbs()
|
||||
</script>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<UserProfileCard :user-id="currentUser.id" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useBreadcrumbs from "@/use/use-breadcrumbs"
|
||||
import useCurrentUser from "@/use/use-current-user"
|
||||
|
||||
import UserProfileCard from "@/components/users/UserProfileCard.vue"
|
||||
|
||||
const { currentUser } = useCurrentUser<true>()
|
||||
|
||||
useBreadcrumbs("My Profile", [
|
||||
{
|
||||
title: "My Profile",
|
||||
to: {
|
||||
name: "ProfilePage",
|
||||
},
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div class="pa-3">
|
||||
<v-row class="h-100vh auth">
|
||||
<v-col
|
||||
cols="12"
|
||||
lg="5"
|
||||
xl="4"
|
||||
class="d-flex align-center justify-center bg-surface"
|
||||
>
|
||||
<div class="mt-xl-0 mt-5 mw-100 text-center">
|
||||
<div
|
||||
class="px-8"
|
||||
style="max-width: 500px"
|
||||
>
|
||||
<img
|
||||
class="d-inline-block d-lg-none"
|
||||
src="@/assets/app_logo_small.png"
|
||||
style="height: 66px; transform: rotate(-12deg)"
|
||||
/>
|
||||
<h2
|
||||
class="text-h1 textPrimary font-weight-semibold mb-0"
|
||||
style="font-size: 2.5rem !important"
|
||||
>
|
||||
ALPHANE
|
||||
</h2>
|
||||
<div
|
||||
class="card-subtitle mb-6"
|
||||
style="font-size: 1.2rem"
|
||||
>
|
||||
Helicopter Maintenance Tracking Platform
|
||||
</div>
|
||||
<p>
|
||||
This application is will streamline maintenance control decision making and unlock
|
||||
operational constraints.
|
||||
</p>
|
||||
<h6 class="text-h6 text-medium-emphasis d-flex align-center mt-6 font-weight-medium">
|
||||
<v-btn
|
||||
block
|
||||
color="primary"
|
||||
@click="doLogin"
|
||||
>Sign in</v-btn
|
||||
>
|
||||
</h6>
|
||||
|
||||
<div class="my-6 text-medium-emphasis">
|
||||
If you don't have an account, create one to get started using ROTYR. It's free to sign
|
||||
up and only takes a few seconds.
|
||||
</div>
|
||||
|
||||
<!-- <v-btn
|
||||
block
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
class="mt-3"
|
||||
:to="{ name: 'SignUpPage' }"
|
||||
text="Sign Up"
|
||||
/> -->
|
||||
<div
|
||||
v-if="isAuthenticated"
|
||||
class="mt-5 text-center"
|
||||
>
|
||||
<v-btn
|
||||
color="warning"
|
||||
variant="text"
|
||||
@click="logoutWrapper"
|
||||
>Sign out</v-btn
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
lg="7"
|
||||
xl="8"
|
||||
class="d-none d-lg-flex align-center justify-center authentication position-relative"
|
||||
>
|
||||
<div class="text-center">
|
||||
<img
|
||||
src="@/assets/app_logo_splash.png"
|
||||
class="position-relative"
|
||||
style="opacity: 0.15; width: 80%"
|
||||
/>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from "vue"
|
||||
import { useAuth0 } from "@auth0/auth0-vue"
|
||||
|
||||
import useCurrentUser from "@/use/use-current-user"
|
||||
|
||||
const { reset: resetCurrentUser } = useCurrentUser()
|
||||
const { isAuthenticated, loginWithRedirect, logout } = useAuth0()
|
||||
|
||||
onMounted(() => {
|
||||
resetCurrentUser()
|
||||
})
|
||||
|
||||
function doLogin() {
|
||||
loginWithRedirect({
|
||||
appState: { target: "/dashboard" },
|
||||
})
|
||||
}
|
||||
async function logoutWrapper() {
|
||||
await logout({
|
||||
logoutParams: {
|
||||
returnTo: window.location.origin,
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-container>
|
||||
Return to <router-link :to="{ name: returnTo.name }">{{ returnTo.title }}</router-link>
|
||||
|
||||
<v-row class="mt-5">
|
||||
<v-col cols="12">
|
||||
<v-card
|
||||
outlined
|
||||
class="pa-3"
|
||||
:loading="isLoading"
|
||||
>
|
||||
<v-card-title
|
||||
>Environment Information
|
||||
<v-btn
|
||||
class="ma-0 ml-1"
|
||||
icon
|
||||
size="small"
|
||||
color="success"
|
||||
title="refresh"
|
||||
@click="refresh"
|
||||
>
|
||||
<v-icon>mdi-cached</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-list dense>
|
||||
<v-list-item> Release Tag: {{ environment.releaseTag }} </v-list-item>
|
||||
<v-list-item> Git Commit Hash: {{ environment.gitCommitHash }} </v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, reactive, ref } from "vue"
|
||||
import { useAuth0 } from "@auth0/auth0-vue"
|
||||
|
||||
import http from "@/api/http-client"
|
||||
|
||||
const { isAuthenticated } = useAuth0()
|
||||
|
||||
const returnTo = computed<{ name: string; title: string }>(() => {
|
||||
if (isAuthenticated.value) {
|
||||
return {
|
||||
name: "DashboardPage",
|
||||
title: "Dashboard",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: "SignInPage",
|
||||
title: "Sign In",
|
||||
}
|
||||
})
|
||||
|
||||
const environment = reactive({
|
||||
releaseTag: "not-set",
|
||||
gitCommitHash: "not-set",
|
||||
})
|
||||
|
||||
const isLoading = ref(true)
|
||||
|
||||
onMounted(async () => {
|
||||
await refresh()
|
||||
})
|
||||
|
||||
async function fetchVersion() {
|
||||
return http
|
||||
.get("/_status")
|
||||
.then(({ data }) => {
|
||||
environment.releaseTag = data.RELEASE_TAG
|
||||
environment.gitCommitHash = data.GIT_COMMIT_HASH
|
||||
return data
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
console.error(`Error fetching version: ${error}`)
|
||||
})
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
isLoading.value = true
|
||||
try {
|
||||
await fetchVersion()
|
||||
} catch (error) {
|
||||
console.error(`Error fetching version: ${error}`)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-row>
|
||||
<!-- Users Card -->
|
||||
<v-col
|
||||
v-if="isSystemAdmin"
|
||||
cols="12"
|
||||
md="6"
|
||||
lg="4"
|
||||
>
|
||||
<v-card
|
||||
elevation="10"
|
||||
:to="{ name: 'administration/UsersPage' }"
|
||||
hover
|
||||
>
|
||||
<v-card-item>
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar
|
||||
color="success"
|
||||
size="56"
|
||||
class="mr-4"
|
||||
>
|
||||
<v-icon
|
||||
size="32"
|
||||
color="white"
|
||||
>
|
||||
mdi-account-group
|
||||
</v-icon>
|
||||
</v-avatar>
|
||||
<div>
|
||||
<v-card-title class="text-h5">Users</v-card-title>
|
||||
<v-card-subtitle>Manage users and permissions</v-card-subtitle>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-item>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-text>
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<span class="text-h3 font-weight-bold">{{ usersCount }}</span>
|
||||
<v-icon
|
||||
size="large"
|
||||
color="success"
|
||||
>
|
||||
mdi-chevron-right
|
||||
</v-icon>
|
||||
</div>
|
||||
<div class="text-caption text-medium-emphasis mt-1">Total Users</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- Settings Card -->
|
||||
<v-col
|
||||
v-if="isSystemAdmin"
|
||||
cols="12"
|
||||
md="6"
|
||||
lg="4"
|
||||
>
|
||||
<v-card
|
||||
elevation="10"
|
||||
:to="{ name: 'administration/SettingsPage' }"
|
||||
hover
|
||||
>
|
||||
<v-card-item>
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar
|
||||
color="info"
|
||||
size="56"
|
||||
class="mr-4"
|
||||
>
|
||||
<v-icon
|
||||
size="32"
|
||||
color="white"
|
||||
>
|
||||
mdi-cog
|
||||
</v-icon>
|
||||
</v-avatar>
|
||||
<div>
|
||||
<v-card-title class="text-h5">Settings</v-card-title>
|
||||
<v-card-subtitle>System configuration</v-card-subtitle>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-item>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-text>
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<span class="text-h3 font-weight-bold"> </span>
|
||||
|
||||
<v-icon
|
||||
size="large"
|
||||
color="info"
|
||||
>
|
||||
mdi-chevron-right
|
||||
</v-icon>
|
||||
</div>
|
||||
<div class="text-caption text-medium-emphasis mt-1">Configure System</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useBreadcrumbs from "@/use/use-breadcrumbs"
|
||||
import useUsers from "@/use/use-users"
|
||||
import useCurrentUser from "@/use/use-current-user"
|
||||
|
||||
const { isSystemAdmin } = useCurrentUser()
|
||||
|
||||
const { totalCount: usersCount } = useUsers()
|
||||
|
||||
useBreadcrumbs("Administration Dashboard", [])
|
||||
</script>
|
||||
@@ -0,0 +1,14 @@
|
||||
<template><div></div></template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useBreadcrumbs from "@/use/use-breadcrumbs"
|
||||
|
||||
useBreadcrumbs("Settings", [
|
||||
{
|
||||
title: "Settings",
|
||||
to: {
|
||||
name: "administration/SettingsPage",
|
||||
},
|
||||
},
|
||||
])
|
||||
</script>
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<HeaderActionsCard
|
||||
title="Users"
|
||||
elevation="10"
|
||||
>
|
||||
<template
|
||||
v-if="isSystemAdmin"
|
||||
#header-actions
|
||||
>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
:to="{
|
||||
name: 'administration/users/UserNewPage',
|
||||
}"
|
||||
>
|
||||
<v-icon class="mr-3">mdi-plus</v-icon>
|
||||
Add User
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<UsersDataTableServer />
|
||||
</HeaderActionsCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue"
|
||||
|
||||
import useBreadcrumbs from "@/use/use-breadcrumbs"
|
||||
import useCurrentUser from "@/use/use-current-user"
|
||||
|
||||
import HeaderActionsCard from "@/components/common/HeaderActionsCard.vue"
|
||||
import UsersDataTableServer from "@/components/users/UsersDataTableServer.vue"
|
||||
|
||||
const { isSystemAdmin } = useCurrentUser()
|
||||
|
||||
const breadcrumbs = computed(() => [
|
||||
{
|
||||
title: "Users",
|
||||
to: {
|
||||
name: "administration/UsersPage",
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
useBreadcrumbs("Users", breadcrumbs)
|
||||
</script>
|
||||
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<UserEditCardForm
|
||||
:user-id="userId"
|
||||
:return-to="{
|
||||
name: 'administration/users/UserPage',
|
||||
params: {
|
||||
userId,
|
||||
},
|
||||
}"
|
||||
@saved="refreshUserOrCurrentUser"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue"
|
||||
|
||||
import useBreadcrumbs from "@/use/use-breadcrumbs"
|
||||
import useCurrentUser from "@/use/use-current-user"
|
||||
import useUser from "@/use/use-user"
|
||||
import useSnack from "@/use/use-snack"
|
||||
|
||||
import UserEditCardForm from "@/components/users/UserEditCardForm.vue"
|
||||
|
||||
const props = defineProps<{
|
||||
userId: string
|
||||
}>()
|
||||
|
||||
const userId = computed(() => parseInt(props.userId))
|
||||
const { user, refresh } = useUser(userId)
|
||||
|
||||
const { currentUser, refresh: refreshCurrentUser } = useCurrentUser<true>()
|
||||
const snack = useSnack()
|
||||
|
||||
async function refreshUserOrCurrentUser() {
|
||||
if (userId.value === currentUser.value.id) {
|
||||
await refreshCurrentUser()
|
||||
snack.info("Logged-in user updated. App refreshed.")
|
||||
} else {
|
||||
await refresh()
|
||||
}
|
||||
}
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
return [
|
||||
{
|
||||
title: "Users",
|
||||
to: {
|
||||
name: "administration/UsersPage",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: user.value?.displayName || user.value?.email,
|
||||
to: {
|
||||
name: "administration/users/UserPage",
|
||||
params: {
|
||||
userId: props.userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
useBreadcrumbs("User Details", breadcrumbs)
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,353 @@
|
||||
<template>
|
||||
<HeaderActionsFormCard
|
||||
ref="headerActionsFormCard"
|
||||
title="New User"
|
||||
@submit.prevent="createUser"
|
||||
>
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<v-text-field
|
||||
v-model="userAttributes.firstName"
|
||||
label="First name *"
|
||||
required
|
||||
:rules="[required]"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<v-text-field
|
||||
v-model="userAttributes.lastName"
|
||||
label="Last name *"
|
||||
required
|
||||
:rules="[required]"
|
||||
@update:focused="autoFillDependentFields"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<v-text-field
|
||||
v-model="userAttributes.displayName"
|
||||
label="Display name *"
|
||||
required
|
||||
:rules="[required]"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<UserEmailUniqueTextField
|
||||
v-model="userAttributes.email"
|
||||
label="Email *"
|
||||
:rules="[required, minimum(2), email]"
|
||||
required
|
||||
validate-on="blur"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<v-text-field
|
||||
v-model="userAttributes.title"
|
||||
label="Title"
|
||||
clearable
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<v-text-field
|
||||
v-model="userAttributes.pilotLicense"
|
||||
label="Pilot License"
|
||||
placeholder="e.g., CPL-123456"
|
||||
clearable
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<v-text-field
|
||||
v-model="userAttributes.ameLicense"
|
||||
label="AME License"
|
||||
placeholder="e.g., AME-789012"
|
||||
clearable
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<h4 class="mt-3">Images</h4>
|
||||
<v-divider class="mt-1 mb-2" />
|
||||
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<v-file-input
|
||||
v-model="profileImageFile"
|
||||
accept="image/*"
|
||||
prepend-icon="mdi-camera"
|
||||
label="Upload Profile Image"
|
||||
clearable
|
||||
@change="handleProfileImageUpload"
|
||||
/>
|
||||
<div
|
||||
v-if="profileImagePreview"
|
||||
class="mt-4 d-flex justify-center"
|
||||
>
|
||||
<v-img
|
||||
:src="profileImagePreview"
|
||||
max-width="200"
|
||||
max-height="200"
|
||||
class="rounded elevation-2"
|
||||
cover
|
||||
/>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<v-file-input
|
||||
v-model="signatureImageFile"
|
||||
accept="image/*"
|
||||
prepend-icon="mdi-draw"
|
||||
label="Upload Signature"
|
||||
clearable
|
||||
@change="handleSignatureImageUpload"
|
||||
/>
|
||||
<div
|
||||
v-if="signatureImagePreview"
|
||||
class="mt-4 d-flex justify-center"
|
||||
>
|
||||
<v-img
|
||||
:src="signatureImagePreview"
|
||||
max-width="300"
|
||||
max-height="150"
|
||||
class="rounded elevation-2"
|
||||
contain
|
||||
/>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<h4 class="mt-3">Notification Details</h4>
|
||||
<v-divider class="mt-1 mb-2" />
|
||||
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<v-switch
|
||||
v-model="userAttributes.emailNotificationsEnabled"
|
||||
label="Email notifications enabled?"
|
||||
inset
|
||||
center-affix
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<template #actions>
|
||||
<div class="d-flex">
|
||||
<v-btn
|
||||
:loading="isLoading"
|
||||
type="submit"
|
||||
>
|
||||
Create User
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
color="warning"
|
||||
class="ml-4"
|
||||
variant="outlined"
|
||||
:loading="isLoading"
|
||||
:to="{
|
||||
name: 'administration/UsersPage',
|
||||
}"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</HeaderActionsFormCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import { isEmpty, isNil } from "lodash"
|
||||
|
||||
import { required, minimum, email } from "@/utils/validators"
|
||||
import { resizeToStandard } from "@/utils/image-resizer"
|
||||
import usersApi, { User } from "@/api/users-api"
|
||||
import useBreadcrumbs from "@/use/use-breadcrumbs"
|
||||
import useSnack from "@/use/use-snack"
|
||||
|
||||
import HeaderActionsFormCard from "@/components/shared/cards/HeaderActionsFormCard.vue"
|
||||
import UserEmailUniqueTextField from "@/components/users/UserEmailUniqueTextField.vue"
|
||||
|
||||
const userAttributes = ref<Partial<User>>({
|
||||
email: "",
|
||||
displayName: "",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
title: null,
|
||||
pilotLicense: null,
|
||||
ameLicense: null,
|
||||
emailNotificationsEnabled: true,
|
||||
})
|
||||
|
||||
// Image handling
|
||||
const profileImageFile = ref<File[] | File | null>(null)
|
||||
const signatureImageFile = ref<File[] | File | null>(null)
|
||||
const profileImagePreview = ref<string | null>(null)
|
||||
const signatureImagePreview = ref<string | null>(null)
|
||||
|
||||
async function handleProfileImageUpload() {
|
||||
if (!profileImageFile.value) {
|
||||
profileImagePreview.value = null
|
||||
userAttributes.value.profileImage = null
|
||||
return
|
||||
}
|
||||
|
||||
// Handle both array and single file
|
||||
const file = Array.isArray(profileImageFile.value)
|
||||
? profileImageFile.value[0]
|
||||
: profileImageFile.value
|
||||
|
||||
if (!file) {
|
||||
profileImagePreview.value = null
|
||||
userAttributes.value.profileImage = null
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure we have a valid File object
|
||||
if (!(file instanceof File)) {
|
||||
console.error("Invalid file type:", file)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Resize image to 512x512 before upload
|
||||
const base64DataUrl = await resizeToStandard(file)
|
||||
profileImagePreview.value = base64DataUrl
|
||||
userAttributes.value.profileImage = base64DataUrl
|
||||
} catch (error) {
|
||||
console.error("Error resizing profile image:", error)
|
||||
snack.error("Failed to process profile image")
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSignatureImageUpload() {
|
||||
if (!signatureImageFile.value) {
|
||||
signatureImagePreview.value = null
|
||||
userAttributes.value.signatureImage = null
|
||||
return
|
||||
}
|
||||
|
||||
// Handle both array and single file
|
||||
const file = Array.isArray(signatureImageFile.value)
|
||||
? signatureImageFile.value[0]
|
||||
: signatureImageFile.value
|
||||
|
||||
if (!file) {
|
||||
signatureImagePreview.value = null
|
||||
userAttributes.value.signatureImage = null
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure we have a valid File object
|
||||
if (!(file instanceof File)) {
|
||||
console.error("Invalid file type:", file)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Resize image to 512x512 before upload
|
||||
const base64DataUrl = await resizeToStandard(file)
|
||||
signatureImagePreview.value = base64DataUrl
|
||||
userAttributes.value.signatureImage = base64DataUrl
|
||||
} catch (error) {
|
||||
console.error("Error resizing signature image:", error)
|
||||
snack.error("Failed to process signature image")
|
||||
}
|
||||
}
|
||||
|
||||
function autoFillDependentFields(focused: boolean) {
|
||||
if (focused) return
|
||||
|
||||
const { email, displayName, firstName, lastName } = userAttributes.value
|
||||
if ((isNil(firstName) || isEmpty(firstName)) && (isNil(lastName) || isEmpty(lastName))) return
|
||||
|
||||
if (isNil(displayName) || isEmpty(displayName)) {
|
||||
userAttributes.value.displayName = [firstName, lastName].filter(Boolean).join(" ")
|
||||
}
|
||||
|
||||
if (isNil(email) || isEmpty(email)) {
|
||||
userAttributes.value.email = `${firstName}.${lastName}@unknown.ca`
|
||||
}
|
||||
}
|
||||
|
||||
const headerActionsFormCard = ref<InstanceType<typeof HeaderActionsFormCard> | null>(null)
|
||||
const isLoading = ref(false)
|
||||
const snack = useSnack()
|
||||
const router = useRouter()
|
||||
|
||||
async function createUser() {
|
||||
if (headerActionsFormCard.value === null) return
|
||||
|
||||
const { valid } = await headerActionsFormCard.value.validate()
|
||||
if (!valid) return
|
||||
|
||||
const userAttributesWithAuthSubject = {
|
||||
...userAttributes.value,
|
||||
authSubject: userAttributes.value.email,
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
try {
|
||||
const { user } = await usersApi.create(userAttributesWithAuthSubject)
|
||||
snack.success("User created!")
|
||||
return router.push({
|
||||
name: "administration/users/UserPage",
|
||||
params: {
|
||||
userId: user.id,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
snack.error(`Failed to create user: ${error}`)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
useBreadcrumbs("User Creation", [
|
||||
{
|
||||
title: "Users",
|
||||
to: {
|
||||
name: "administration/UsersPage",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "New User",
|
||||
to: {
|
||||
name: "administration/users/UserNewPage",
|
||||
},
|
||||
},
|
||||
])
|
||||
</script>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<v-empty-state
|
||||
headline="Whoops, 403"
|
||||
title="Access Forbidden"
|
||||
text="You do not have permission to access that page"
|
||||
:image="SplashImage"
|
||||
>
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center">
|
||||
<v-btn
|
||||
color="primary"
|
||||
width="200"
|
||||
variant="outlined"
|
||||
@click="goBack"
|
||||
>Back</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center">
|
||||
<!-- href="/" performs a more aggressive refresh than using to="xxx" -->
|
||||
<v-btn
|
||||
href="/dashboard"
|
||||
color="primary"
|
||||
width="200"
|
||||
variant="outlined"
|
||||
>Home</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center">
|
||||
<v-btn
|
||||
width="200"
|
||||
@click="signOut"
|
||||
>Logout</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-divider
|
||||
class="mt-10 mb-6"
|
||||
thickness="1"
|
||||
/>
|
||||
<p>Site: {{ APPLICATION_NAME }}</p>
|
||||
<p>Version: {{ releaseTag }}</p>
|
||||
<p>Commit Hash: {{ gitCommitHash }}</p>
|
||||
</v-empty-state>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import SplashImage from "@/assets/SplashImage.png"
|
||||
import { useAuth0 } from "@auth0/auth0-vue"
|
||||
|
||||
import { APPLICATION_NAME } from "@/config"
|
||||
import useCurrentUser from "@/use/use-current-user"
|
||||
import useStatus from "@/use/use-status"
|
||||
|
||||
const { logout } = useAuth0()
|
||||
const { reset: resetCurrentUser } = useCurrentUser()
|
||||
|
||||
const { releaseTag, gitCommitHash } = useStatus()
|
||||
|
||||
function goBack() {
|
||||
window.history.back()
|
||||
}
|
||||
|
||||
async function signOut() {
|
||||
resetCurrentUser()
|
||||
|
||||
const returnTo = encodeURI(window.location.origin + "/sign-in")
|
||||
return logout({
|
||||
logoutParams: {
|
||||
returnTo,
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<v-empty-state
|
||||
headline="Whoops, 500"
|
||||
title="Internal Server Error"
|
||||
:text="'Oops! The server encountered an unexpected error. Please\u00a0contact\u00a0support.'"
|
||||
:image="SplashImage"
|
||||
>
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center">
|
||||
<v-btn
|
||||
color="primary"
|
||||
width="200"
|
||||
variant="outlined"
|
||||
@click="goBack"
|
||||
>Back</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center">
|
||||
<!-- href="/" performs a more aggressive refresh than using to="xxx" -->
|
||||
<v-btn
|
||||
href="/dashboard"
|
||||
color="primary"
|
||||
width="200"
|
||||
variant="outlined"
|
||||
>Home</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center">
|
||||
<v-btn
|
||||
width="200"
|
||||
@click="signOut"
|
||||
>Logout</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-divider
|
||||
class="mt-10 mb-6"
|
||||
thickness="1"
|
||||
/>
|
||||
<p>Site: {{ APPLICATION_NAME }}</p>
|
||||
<p>Version: {{ releaseTag }}</p>
|
||||
<p>Commit Hash: {{ gitCommitHash }}</p>
|
||||
</v-empty-state>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import SplashImage from "@/assets/SplashImage.png"
|
||||
import { useAuth0 } from "@auth0/auth0-vue"
|
||||
|
||||
import { APPLICATION_NAME } from "@/config"
|
||||
import useCurrentUser from "@/use/use-current-user"
|
||||
import useStatus from "@/use/use-status"
|
||||
|
||||
const { logout } = useAuth0()
|
||||
const { reset: resetCurrentUser } = useCurrentUser()
|
||||
|
||||
const { releaseTag, gitCommitHash } = useStatus()
|
||||
|
||||
function goBack() {
|
||||
window.history.back()
|
||||
}
|
||||
|
||||
async function signOut() {
|
||||
resetCurrentUser()
|
||||
|
||||
const returnTo = encodeURI(window.location.origin + "/sign-in")
|
||||
return logout({
|
||||
logoutParams: {
|
||||
returnTo,
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<v-empty-state
|
||||
headline="Whoops, 404"
|
||||
title="Page Not Found"
|
||||
text="Oops! The page you're looking doesn't exist."
|
||||
:image="SplashImage"
|
||||
>
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center">
|
||||
<v-btn
|
||||
color="primary"
|
||||
width="200"
|
||||
variant="outlined"
|
||||
@click="goBack"
|
||||
>Back</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center">
|
||||
<!-- href="/" performs a more aggressive refresh than using to="xxx" -->
|
||||
<v-btn
|
||||
href="/dashboard"
|
||||
color="primary"
|
||||
width="200"
|
||||
variant="outlined"
|
||||
>Home</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center">
|
||||
<v-btn
|
||||
width="200"
|
||||
@click="signOut"
|
||||
>Logout</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-divider
|
||||
class="mt-10 mb-6"
|
||||
thickness="1"
|
||||
/>
|
||||
<p>Site: {{ APPLICATION_NAME }}</p>
|
||||
<p>Version: {{ releaseTag }}</p>
|
||||
<p>Commit Hash: {{ gitCommitHash }}</p>
|
||||
</v-empty-state>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import SplashImage from "@/assets/SplashImage.png"
|
||||
import { useAuth0 } from "@auth0/auth0-vue"
|
||||
|
||||
import { APPLICATION_NAME } from "@/config"
|
||||
import useCurrentUser from "@/use/use-current-user"
|
||||
import useStatus from "@/use/use-status"
|
||||
|
||||
const { logout } = useAuth0()
|
||||
const { reset: resetCurrentUser } = useCurrentUser()
|
||||
|
||||
const { releaseTag, gitCommitHash } = useStatus()
|
||||
|
||||
function goBack() {
|
||||
window.history.back()
|
||||
}
|
||||
|
||||
async function signOut() {
|
||||
resetCurrentUser()
|
||||
|
||||
const returnTo = encodeURI(window.location.origin + "/sign-in")
|
||||
return logout({
|
||||
logoutParams: {
|
||||
returnTo,
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<v-empty-state
|
||||
headline="Whoops, 401"
|
||||
title="Unauthorized"
|
||||
text="If you think this is an error, please contact support. Alternatively, try logging out and signing back in."
|
||||
:image="SplashImage"
|
||||
>
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center">
|
||||
<v-btn
|
||||
color="primary"
|
||||
width="200"
|
||||
variant="outlined"
|
||||
@click="goBack"
|
||||
>Back</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center">
|
||||
<!-- href="/" performs a more aggressive refresh than using to="xxx" -->
|
||||
<v-btn
|
||||
href="/dashboard"
|
||||
color="primary"
|
||||
width="200"
|
||||
variant="outlined"
|
||||
>Home</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center">
|
||||
<v-btn
|
||||
width="200"
|
||||
@click="signOut"
|
||||
>Logout</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-divider
|
||||
class="mt-10 mb-6"
|
||||
thickness="1"
|
||||
/>
|
||||
<p>Site: {{ APPLICATION_NAME }}</p>
|
||||
<p>Version: {{ releaseTag }}</p>
|
||||
<p>Commit Hash: {{ gitCommitHash }}</p>
|
||||
</v-empty-state>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import SplashImage from "@/assets/SplashImage.png"
|
||||
import { useAuth0 } from "@auth0/auth0-vue"
|
||||
|
||||
import { APPLICATION_NAME } from "@/config"
|
||||
import useCurrentUser from "@/use/use-current-user"
|
||||
import useStatus from "@/use/use-status"
|
||||
|
||||
const { logout } = useAuth0()
|
||||
const { reset: resetCurrentUser } = useCurrentUser()
|
||||
|
||||
const { releaseTag, gitCommitHash } = useStatus()
|
||||
|
||||
function goBack() {
|
||||
window.history.back()
|
||||
}
|
||||
|
||||
async function signOut() {
|
||||
resetCurrentUser()
|
||||
|
||||
const returnTo = encodeURI(window.location.origin + "/sign-in")
|
||||
return logout({
|
||||
logoutParams: {
|
||||
returnTo,
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<UserEditCardForm
|
||||
:user-id="currentUser.id"
|
||||
:return-to="{
|
||||
name: 'ProfilePage',
|
||||
}"
|
||||
@saved="refreshWrapper"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useBreadcrumbs from "@/use/use-breadcrumbs"
|
||||
import useCurrentUser from "@/use/use-current-user"
|
||||
import useSnack from "@/use/use-snack"
|
||||
|
||||
import UserEditCardForm from "@/components/users/UserEditCardForm.vue"
|
||||
|
||||
const { currentUser, refresh } = useCurrentUser<true>()
|
||||
const snack = useSnack()
|
||||
|
||||
async function refreshWrapper() {
|
||||
await refresh()
|
||||
snack.info("Logged-in user updated. App refreshed.")
|
||||
}
|
||||
|
||||
useBreadcrumbs("Edit My Profile", [
|
||||
{
|
||||
title: "My Profile",
|
||||
to: {
|
||||
name: "ProfilePage",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Edit",
|
||||
to: {
|
||||
name: "profile/ProfileEditPage",
|
||||
},
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
Reference in New Issue
Block a user