Fixed a whole lotta broken stuff by changing state from a string to a number
This commit is contained in:
108
ui/package-lock.json
generated
108
ui/package-lock.json
generated
@@ -35,7 +35,8 @@
|
||||
"@types/node": "^24.2.1",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"vite": "^7.0.6",
|
||||
"vite-plugin-vue-devtools": "^8.0.0"
|
||||
"vite-plugin-vue-devtools": "^8.0.0",
|
||||
"vue-tsc": "^3.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
@@ -1884,6 +1885,35 @@
|
||||
"vue": "^3.2.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/language-core": {
|
||||
"version": "2.4.27",
|
||||
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.27.tgz",
|
||||
"integrity": "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/source-map": "2.4.27"
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/source-map": {
|
||||
"version": "2.4.27",
|
||||
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.27.tgz",
|
||||
"integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@volar/typescript": {
|
||||
"version": "2.4.27",
|
||||
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.27.tgz",
|
||||
"integrity": "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/language-core": "2.4.27",
|
||||
"path-browserify": "^1.0.1",
|
||||
"vscode-uri": "^3.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/babel-helper-vue-transform-on": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.5.0.tgz",
|
||||
@@ -2083,6 +2113,22 @@
|
||||
"rfdc": "^1.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/language-core": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.2.4.tgz",
|
||||
"integrity": "sha512-bqBGuSG4KZM45KKTXzGtoCl9cWju5jsaBKaJJe3h5hRAAWpZUuj5G+L+eI01sPIkm4H6setKRlw7E85wLdDNew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/language-core": "2.4.27",
|
||||
"@vue/compiler-dom": "^3.5.0",
|
||||
"@vue/shared": "^3.5.0",
|
||||
"alien-signals": "^3.0.0",
|
||||
"muggle-string": "^0.4.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"picomatch": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.18.tgz",
|
||||
@@ -2171,6 +2217,13 @@
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/alien-signals": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.2.tgz",
|
||||
"integrity": "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ansis": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansis/-/ansis-4.1.0.tgz",
|
||||
@@ -3123,6 +3176,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/muggle-string": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
|
||||
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
@@ -3216,6 +3276,13 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/path-browserify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
|
||||
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
@@ -3646,6 +3713,21 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.10.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
||||
@@ -3932,6 +4014,13 @@
|
||||
"vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.18.tgz",
|
||||
@@ -3974,6 +4063,23 @@
|
||||
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vue-tsc": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.4.tgz",
|
||||
"integrity": "sha512-xj3YCvSLNDKt1iF9OcImWHhmYcihVu9p4b9s4PGR/qp6yhW+tZJaypGxHScRyOrdnHvaOeF+YkZOdKwbgGvp5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/typescript": "2.4.27",
|
||||
"@vue/language-core": "3.2.4"
|
||||
},
|
||||
"bin": {
|
||||
"vue-tsc": "bin/vue-tsc.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"@types/node": "^24.2.1",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"vite": "^7.0.6",
|
||||
"vite-plugin-vue-devtools": "^8.0.0"
|
||||
"vite-plugin-vue-devtools": "^8.0.0",
|
||||
"vue-tsc": "^3.2.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
<script setup>
|
||||
import { RouterView } from 'vue-router';
|
||||
import Button from './components/ui/button/Button.vue';
|
||||
import { useUserStore } from './stores/user';
|
||||
import Alert from './components/ui/alert/Alert.vue';
|
||||
import AlertDescription from './components/ui/alert/AlertDescription.vue';
|
||||
import Navbar from './components/Navigation/Navbar.vue';
|
||||
import { cancelLOA } from './api/loa';
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router';
|
||||
import Button from './components/ui/button/Button.vue';
|
||||
import { useUserStore } from './stores/user';
|
||||
import Alert from './components/ui/alert/Alert.vue';
|
||||
import AlertDescription from './components/ui/alert/AlertDescription.vue';
|
||||
import Navbar from './components/Navigation/Navbar.vue';
|
||||
import { cancelLOA } from './api/loa';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
function formatDate(dateStr) {
|
||||
if (!dateStr) return "";
|
||||
return new Date(dateStr).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
function formatDate(dateStr) {
|
||||
if (!dateStr) return "";
|
||||
return new Date(dateStr).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
const environment = import.meta.env.VITE_ENVIRONMENT;
|
||||
const version = import.meta.env.VITE_APPLICATION_VERSION;
|
||||
//@ts-ignore
|
||||
const environment = import.meta.env.VITE_ENVIRONMENT;
|
||||
//@ts-ignore
|
||||
const version = import.meta.env.VITE_APPLICATION_VERSION;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -36,12 +38,15 @@ const version = import.meta.env.VITE_APPLICATION_VERSION;
|
||||
</Alert>
|
||||
<Alert v-if="userStore.user?.LOAs?.[0]" class="m-2 mx-auto max-w-5xl" variant="info">
|
||||
<AlertDescription class="flex flex-row items-center text-nowrap gap-5 mx-auto">
|
||||
<p v-if="new Date(userStore.user?.LOAs?.[0].extended_till || userStore.user?.LOAs?.[0].end_date) > new Date()">
|
||||
LOA until <strong>{{ formatDate(userStore.user?.LOAs?.[0].extended_till || userStore.user?.LOAs?.[0].end_date) }}</strong>
|
||||
</p>
|
||||
<p v-else>
|
||||
LOA expired on <strong>{{ formatDate(userStore.user?.LOAs?.[0].extended_till || userStore.user?.LOAs?.[0].end_date) }}</strong>
|
||||
</p>
|
||||
<p
|
||||
v-if="new Date(userStore.user?.LOAs?.[0].extended_till || userStore.user?.LOAs?.[0].end_date) > new Date()">
|
||||
LOA until <strong>{{ formatDate(userStore.user?.LOAs?.[0].extended_till ||
|
||||
userStore.user?.LOAs?.[0].end_date) }}</strong>
|
||||
</p>
|
||||
<p v-else>
|
||||
LOA expired on <strong>{{ formatDate(userStore.user?.LOAs?.[0].extended_till ||
|
||||
userStore.user?.LOAs?.[0].end_date) }}</strong>
|
||||
</p>
|
||||
<Button variant="secondary"
|
||||
@click="async () => { await cancelLOA(userStore.user.LOAs?.[0].id); userStore.loadUser(); }">End
|
||||
LOA</Button>
|
||||
@@ -52,5 +57,3 @@ const version = import.meta.env.VITE_APPLICATION_VERSION;
|
||||
<RouterView class="flex-1 min-h-0"></RouterView>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Discharge } from "@shared/schemas/dischargeSchema";
|
||||
import { memberSettings, Member, MemberLight, MemberCardDetails, PaginatedMembers } from "@shared/types/member";
|
||||
import { memberSettings, Member, MemberLight, MemberCardDetails, PaginatedMembers, MemberState } from "@shared/types/member";
|
||||
|
||||
// @ts-ignore
|
||||
const addr = import.meta.env.VITE_APIHOST;
|
||||
@@ -18,7 +18,7 @@ export async function getMembersFiltered(params: {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
search?: string;
|
||||
status?: string;
|
||||
status?: string | MemberState;
|
||||
unitId?: string;
|
||||
} = {}): Promise<PaginatedMembers> {
|
||||
|
||||
@@ -27,7 +27,7 @@ export async function getMembersFiltered(params: {
|
||||
if (params.page) query.append('page', params.page.toString());
|
||||
if (params.pageSize) query.append('pageSize', params.pageSize.toString());
|
||||
if (params.search) query.append('search', params.search);
|
||||
if (params.status && params.status !== 'all') query.append('status', params.status);
|
||||
if (params.status && params.status !== 'all') query.append('status', String(params.status));
|
||||
if (params.unitId && params.unitId !== 'all') query.append('unitId', params.unitId);
|
||||
|
||||
const response = await fetch(`${addr}/members/filtered?${query.toString()}`, {
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useAuth } from '@/composables/useAuth';
|
||||
import { ArrowUpRight, CircleArrowOutUpRight } from 'lucide-vue-next';
|
||||
import DropdownMenuGroup from '../ui/dropdown-menu/DropdownMenuGroup.vue';
|
||||
import DropdownMenuSeparator from '../ui/dropdown-menu/DropdownMenuSeparator.vue';
|
||||
import { MemberState } from '@shared/types/member';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const auth = useAuth();
|
||||
@@ -51,7 +52,7 @@ function blurAfter() {
|
||||
<img src="/17RBN_Logo.png" class="w-10 h-10 object-contain"></img>
|
||||
</RouterLink>
|
||||
<!-- Member navigation -->
|
||||
<div v-if="auth.accountStatus.value == 'member'" class="h-15 flex items-center justify-center">
|
||||
<div v-if="auth.accountStatus.value == MemberState.Member" class="h-15 flex items-center justify-center">
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList class="gap-3">
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Calendar } from 'lucide-vue-next';
|
||||
import MemberCard from '../members/MemberCard.vue';
|
||||
import Spinner from '../ui/spinner/Spinner.vue';
|
||||
import { CopyLink } from '@/lib/copyLink';
|
||||
import { MemberState } from '@shared/types/member';
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
@@ -86,7 +87,7 @@ async function setAttendance(state: CalendarAttendance) {
|
||||
|
||||
const canEditEvent = computed(() => {
|
||||
if (!userStore.isLoggedIn) return false;
|
||||
if (userStore.state !== 'member') return false;
|
||||
if (userStore.state !== MemberState.Member) return false;
|
||||
if (userStore.user.member.member_id == activeEvent.value.creator_id)
|
||||
return true;
|
||||
});
|
||||
@@ -231,7 +232,7 @@ defineExpose({ forceReload })
|
||||
<CircleAlert></CircleAlert> This event has been cancelled
|
||||
</div>
|
||||
</section>
|
||||
<section v-if="isPast && userStore.state === 'member'" class="w-full">
|
||||
<section v-if="isPast && userStore.state === MemberState.Member" class="w-full">
|
||||
<ButtonGroup class="flex w-full justify-center">
|
||||
<Button variant="outline" class="flex-1"
|
||||
:class="myAttendance?.status === CalendarAttendance.Attending ? 'border-2 border-primary text-primary' : ''"
|
||||
|
||||
@@ -10,14 +10,9 @@ import FormInput from './components/form/FormInput.vue'
|
||||
|
||||
import * as Sentry from "@sentry/vue";
|
||||
|
||||
|
||||
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
|
||||
|
||||
app.use(createPinia())
|
||||
const pinia = createPinia()
|
||||
app.use(pinia)
|
||||
app.use(router)
|
||||
|
||||
if (import.meta.env.VITE_DISABLE_GLITCHTIP === "true") {
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useCalendarEvents } from '@/composables/useCalendarEvents'
|
||||
import { useCalendarNavigation } from '@/composables/useCalendarNavigation'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { CalendarOptions } from '@fullcalendar/core'
|
||||
import { MemberState } from '@shared/types/member'
|
||||
|
||||
const monthLabels = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
@@ -50,7 +51,7 @@ const dialogRef = ref<any>(null)
|
||||
// NEW: handle day/time slot clicks to start creating an event
|
||||
function onDateClick(arg: { dateStr: string }) {
|
||||
if (!userStore.isLoggedIn) return;
|
||||
if (userStore.state !== 'member') return;
|
||||
if (userStore.state !== MemberState.Member) return;
|
||||
dialogRef.value?.openDialog(arg.dateStr);
|
||||
}
|
||||
|
||||
@@ -198,7 +199,7 @@ onMounted(() => {
|
||||
@click="goToday">
|
||||
Today
|
||||
</button>
|
||||
<button v-if="userStore.isLoggedIn && userStore.state === 'member'"
|
||||
<button v-if="userStore.isLoggedIn && userStore.state === MemberState.Member"
|
||||
class="cursor-pointer ml-1 inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:opacity-90"
|
||||
@click="onCreateEvent">
|
||||
<Plus class="h-4 w-4" />
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { getWelcomeMessage } from '@/api/docs';
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { MemberState } from '@shared/types/member';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
@@ -14,7 +15,7 @@ function goToApplication() {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (user.state == 'member') {
|
||||
if (user.state == MemberState.Member) {
|
||||
let policy = await getWelcomeMessage() as any;
|
||||
welcomeRef.value.innerHTML = policy;
|
||||
}
|
||||
@@ -25,7 +26,7 @@ const welcomeRef = ref<HTMLElement>(null);
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="user.state == 'member'" class="mt-10">
|
||||
<div v-if="user.state == MemberState.Member" class="mt-10">
|
||||
<div ref="welcomeRef" class="bookstack-container">
|
||||
<!-- bookstack -->
|
||||
</div>
|
||||
|
||||
@@ -1,90 +1,93 @@
|
||||
<script setup lang="ts">
|
||||
import ApplicationForm from '@/components/application/ApplicationForm.vue';
|
||||
import Button from '@/components/ui/button/Button.vue';
|
||||
import {
|
||||
Stepper,
|
||||
StepperDescription,
|
||||
StepperIndicator,
|
||||
StepperItem,
|
||||
StepperSeparator,
|
||||
StepperTitle,
|
||||
StepperTrigger,
|
||||
} from '@/components/ui/stepper'
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { Check, Circle, Dot, Users, X } from 'lucide-vue-next'
|
||||
import { computed, ref } from 'vue';
|
||||
import Application from './Application.vue';
|
||||
import { restartApplication } from '@/api/application';
|
||||
import ApplicationForm from '@/components/application/ApplicationForm.vue';
|
||||
import Button from '@/components/ui/button/Button.vue';
|
||||
import {
|
||||
Stepper,
|
||||
StepperDescription,
|
||||
StepperIndicator,
|
||||
StepperItem,
|
||||
StepperSeparator,
|
||||
StepperTitle,
|
||||
StepperTrigger,
|
||||
} from '@/components/ui/stepper'
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { Check, Circle, Dot, Users, X } from 'lucide-vue-next'
|
||||
import { computed, ref } from 'vue';
|
||||
import Application from './Application.vue';
|
||||
import { restartApplication } from '@/api/application';
|
||||
import { MemberState } from '@shared/types/member';
|
||||
|
||||
function goToLogin() {
|
||||
const redirectUrl = encodeURIComponent(window.location.origin + '/join')
|
||||
//@ts-ignore
|
||||
const addr = import.meta.env.VITE_APIHOST;
|
||||
window.location.href = `${addr}/login?redirect=${redirectUrl}`;
|
||||
}
|
||||
|
||||
let userStore = useUserStore();
|
||||
|
||||
const steps = computed(() => {
|
||||
const isDenied = userStore.state === 'denied'
|
||||
|
||||
return [
|
||||
{
|
||||
step: 1,
|
||||
title: 'Create account',
|
||||
description: 'Begin by setting up your account',
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
title: 'Submit application',
|
||||
description: 'Provide a few details about yourself',
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
title: 'Application review',
|
||||
description: 'Our team will review your submission',
|
||||
},
|
||||
{
|
||||
step: 4,
|
||||
title: isDenied ? 'Application denied' : 'Acceptance',
|
||||
description: isDenied
|
||||
? 'Your application was not approved'
|
||||
: 'Get started with the 17th Rangers',
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const currentStep = computed<number>(() => {
|
||||
if (!userStore.isLoggedIn)
|
||||
return 1;
|
||||
switch (userStore.state) {
|
||||
case "guest":
|
||||
return 2;
|
||||
break;
|
||||
case "applicant":
|
||||
return 3;
|
||||
break;
|
||||
case "member":
|
||||
return 5;
|
||||
break;
|
||||
case "denied":
|
||||
return 5;
|
||||
break;
|
||||
case "retired":
|
||||
return 5;
|
||||
break;
|
||||
function goToLogin() {
|
||||
const redirectUrl = encodeURIComponent(window.location.origin + '/join')
|
||||
//@ts-ignore
|
||||
const addr = import.meta.env.VITE_APIHOST;
|
||||
window.location.href = `${addr}/login?redirect=${redirectUrl}`;
|
||||
}
|
||||
})
|
||||
|
||||
const finalPanel = ref<'app' | 'message'>('message');
|
||||
let userStore = useUserStore();
|
||||
|
||||
const reloadKey = ref(0);
|
||||
const steps = computed(() => {
|
||||
const isDenied = userStore.state === MemberState.Denied
|
||||
|
||||
async function restartApp() {
|
||||
await restartApplication();
|
||||
await userStore.loadUser();
|
||||
reloadKey.value++;
|
||||
}
|
||||
return [
|
||||
{
|
||||
step: 1,
|
||||
title: 'Create account',
|
||||
description: 'Begin by setting up your account',
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
title: 'Submit application',
|
||||
description: 'Provide a few details about yourself',
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
title: 'Application review',
|
||||
description: 'Our team will review your submission',
|
||||
},
|
||||
{
|
||||
step: 4,
|
||||
title: isDenied ? 'Application denied' : 'Acceptance',
|
||||
description: isDenied
|
||||
? 'Your application was not approved'
|
||||
: 'Get started with the 17th Rangers',
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const currentStep = computed<number>(() => {
|
||||
if (!userStore.isLoggedIn)
|
||||
return 1;
|
||||
switch (userStore.state) {
|
||||
case MemberState.Guest:
|
||||
return 2;
|
||||
break;
|
||||
case MemberState.Applicant:
|
||||
return 3;
|
||||
break;
|
||||
case MemberState.Member:
|
||||
return 5;
|
||||
break;
|
||||
case MemberState.Denied:
|
||||
return 5;
|
||||
break;
|
||||
case MemberState.Retired:
|
||||
return 5;
|
||||
case MemberState.Discharged:
|
||||
return 5;
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
const finalPanel = ref<'app' | 'message'>('message');
|
||||
|
||||
const reloadKey = ref(0);
|
||||
|
||||
async function restartApp() {
|
||||
await restartApplication();
|
||||
await userStore.loadUser();
|
||||
reloadKey.value++;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -104,7 +107,8 @@ async function restartApp() {
|
||||
size="icon" class="z-10 rounded-full shrink-0"
|
||||
:class="[state === 'active' && 'ring-2 ring-ring ring-offset-2 ring-offset-background']">
|
||||
<template v-if="state === 'completed'">
|
||||
<X v-if="step.step === 4 && userStore.state === 'denied'" class="size-5" />
|
||||
<X v-if="step.step === 4 && userStore.state === MemberState.Denied"
|
||||
class="size-5" />
|
||||
<Check v-else class="size-5" />
|
||||
</template>
|
||||
<Circle v-if="state === 'active'" />
|
||||
@@ -160,7 +164,7 @@ async function restartApp() {
|
||||
</div>
|
||||
<div v-if="finalPanel === 'message'">
|
||||
<!-- Accepted message -->
|
||||
<div v-if="userStore.state === 'member'">
|
||||
<div v-if="userStore.state === MemberState.Member">
|
||||
<h1 class="text-3xl sm:text-4xl font-bold mb-4 text-left">
|
||||
Welcome to the 17th Ranger Battalion
|
||||
</h1>
|
||||
@@ -232,7 +236,7 @@ async function restartApp() {
|
||||
</div>
|
||||
</div>
|
||||
<!-- Denied message -->
|
||||
<div v-else-if="userStore.state === 'denied'">
|
||||
<div v-else-if="userStore.state === MemberState.Denied">
|
||||
<div class="w-full max-w-2xl flex flex-col gap-8">
|
||||
<h1 class="text-3xl sm:text-4xl font-bold text-left">
|
||||
Application Not Approved
|
||||
@@ -263,7 +267,8 @@ async function restartApp() {
|
||||
<Button class="w-min" @click="restartApp">New Application</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="userStore.state === 'retired'">
|
||||
<div
|
||||
v-else-if="userStore.state === MemberState.Discharged || userStore.state === MemberState.Retired">
|
||||
<div class="w-full max-w-2xl flex flex-col gap-8">
|
||||
<h1 class="text-3xl sm:text-4xl font-bold text-left">
|
||||
You have retired from the 17th Ranger Battalion
|
||||
|
||||
@@ -135,11 +135,15 @@ onMounted(() => {
|
||||
const isDischargeOpen = ref(false)
|
||||
const targetMember = ref(null)
|
||||
|
||||
function openDischargeModal(member) {
|
||||
function openDischargeModal(member: Member) {
|
||||
targetMember.value = member
|
||||
isDischargeOpen.value = true
|
||||
}
|
||||
|
||||
function suspendMember(member: Member) {
|
||||
|
||||
}
|
||||
|
||||
function handleDischargeSuccess(data) {
|
||||
fetchMembers();
|
||||
}
|
||||
@@ -186,8 +190,8 @@ function handleDischargeSuccess(data) {
|
||||
</Select>
|
||||
<div v-if="filters.status !== 'all' || filters.unitId !== 'all'"
|
||||
class="h-4 w-[1px] bg-border mx-1" />
|
||||
<Button v-if="filters.status !== MemberState.Member || filters.unitId !== 'all'" variant="ghost" size="sm"
|
||||
class="h-8 px-2 text-xs text-muted-foreground"
|
||||
<Button v-if="filters.status !== MemberState.Member || filters.unitId !== 'all'" variant="ghost"
|
||||
size="sm" class="h-8 px-2 text-xs text-muted-foreground"
|
||||
@click="filters.status = MemberState.Member; filters.unitId = 'all'">
|
||||
Clear Filters
|
||||
</Button>
|
||||
@@ -250,6 +254,10 @@ function handleDischargeSuccess(data) {
|
||||
class="text-destructive focus:bg-destructive focus:text-destructive-foreground font-medium">
|
||||
Discharge Member
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="suspendMember(member)"
|
||||
class="text-destructive focus:bg-destructive focus:text-destructive-foreground font-medium">
|
||||
Suspend Member
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { MemberState } from '@shared/types/member';
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const router = createRouter({
|
||||
@@ -48,6 +49,7 @@ const router = createRouter({
|
||||
]
|
||||
})
|
||||
|
||||
//@ts-ignore
|
||||
const addr = import.meta.env.VITE_APIHOST;
|
||||
|
||||
|
||||
@@ -69,12 +71,12 @@ router.beforeEach(async (to) => {
|
||||
|
||||
|
||||
// Must be a member
|
||||
if (to.meta.memberOnly && user.state !== 'member') {
|
||||
if (to.meta.memberOnly && user.state !== MemberState.Member) {
|
||||
return '/unauthorized'
|
||||
}
|
||||
|
||||
// Must have specific role
|
||||
if (to.meta.roles && !user.hasRole('Dev') && !user.hasAnyRole(to.meta.roles)) {
|
||||
if (to.meta.roles && !user.hasRole('Dev') && !user.hasAnyRole(to.meta.roles as string[])) {
|
||||
return '/unauthorized'
|
||||
}
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { myData } from '@shared/types/member'
|
||||
import { MemberState, myData } from '@shared/types/member'
|
||||
|
||||
|
||||
const POLL_INTERVAL = 10_000
|
||||
@@ -10,7 +10,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
const user = ref<myData>(null)
|
||||
const roles = computed(() => new Set(user.value?.roles?.map(r => r.name) ?? []));
|
||||
const loaded = ref(false);
|
||||
const state = computed<string | undefined>(() => user.value?.state || undefined);
|
||||
const state = computed<MemberState | undefined>(() => user.value?.state || undefined);
|
||||
const isLoggedIn = computed(() => user.value !== null)
|
||||
const displayName = computed(() => user.value?.member.displayName || user.value?.member.member_name)
|
||||
|
||||
@@ -38,6 +38,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
return requiredRoles.some(r => roles.value.has(r))
|
||||
}
|
||||
|
||||
//watcher to kick you off a page if your perms are revoked
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
watch(user, (newUser) => {
|
||||
@@ -46,7 +47,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
const currentRoute = route.meta
|
||||
|
||||
// Member-only route
|
||||
if (currentRoute.memberOnly && state.value !== 'member') {
|
||||
if (currentRoute.memberOnly && state.value !== MemberState.Member) {
|
||||
router.replace('/unauthorized')
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user