Improve user experience quality of life
parent
d3abf67d88
commit
42b7e603f0
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { RouterLink, RouterView } from "vue-router";
|
||||
import { useAuthStore } from "./stores/auth";
|
||||
import ProfileDropdown from "./components/ProfileDropdown.vue";
|
||||
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
|
@ -11,26 +12,41 @@ const authStore = useAuthStore();
|
|||
<header>
|
||||
<div class="wrapper">
|
||||
<nav>
|
||||
<h1>availabili.tf</h1>
|
||||
<RouterLink to="/">Home</RouterLink>
|
||||
<RouterLink to="/schedule">Schedule</RouterLink>
|
||||
<div v-if="authStore.isLoggedIn">
|
||||
Welcome {{ authStore.username }}
|
||||
<h1>
|
||||
<RouterLink class="header-link" to="/">availabili.tf</RouterLink>
|
||||
</h1>
|
||||
<div class="nav-links">
|
||||
<a
|
||||
class="button"
|
||||
href="https://github.com/HumanoidSandvichDispenser/availabili.tf"
|
||||
v-tooltip="'View on GitHub'"
|
||||
>
|
||||
<button class="icon">
|
||||
<i class="bi bi-github" />
|
||||
</button>
|
||||
</a>
|
||||
<ProfileDropdown v-if="authStore.isLoggedIn" />
|
||||
<!--button v-if="authStore.isLoggedIn" class="profile-button">
|
||||
Welcome {{ authStore.username }}
|
||||
</button-->
|
||||
<form
|
||||
v-else
|
||||
action="https://steamcommunity.com/openid/login"
|
||||
method="get"
|
||||
>
|
||||
<input type="hidden" name="openid.identity"
|
||||
value="http://specs.openid.net/auth/2.0/identifier_select" />
|
||||
<input type="hidden" name="openid.claimed_id"
|
||||
value="http://specs.openid.net/auth/2.0/identifier_select" />
|
||||
<input type="hidden" name="openid.ns" value="http://specs.openid.net/auth/2.0" />
|
||||
<input type="hidden" name="openid.mode" value="checkid_setup" />
|
||||
<input type="hidden" name="openid.return_to" :value="baseUrl + '/login'" />
|
||||
<!--button type="submit">Log in through Steam</button-->
|
||||
<button type="submit" class="sign-in-button">
|
||||
<img src="https://community.fastly.steamstatic.com/public/images/signinthroughsteam/sits_01.png" />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<form
|
||||
v-else
|
||||
action="https://steamcommunity.com/openid/login"
|
||||
method="get"
|
||||
>
|
||||
<input type="hidden" name="openid.identity"
|
||||
value="http://specs.openid.net/auth/2.0/identifier_select" />
|
||||
<input type="hidden" name="openid.claimed_id"
|
||||
value="http://specs.openid.net/auth/2.0/identifier_select" />
|
||||
<input type="hidden" name="openid.ns" value="http://specs.openid.net/auth/2.0" />
|
||||
<input type="hidden" name="openid.mode" value="checkid_setup" />
|
||||
<input type="hidden" name="openid.return_to" :value="baseUrl + '/login'" />
|
||||
<button type="submit">Log in through Steam</button>
|
||||
</form>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
@ -46,6 +62,10 @@ header {
|
|||
max-height: 100vh;
|
||||
}
|
||||
|
||||
a.header-link {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
margin: 0 auto 2rem;
|
||||
|
@ -53,28 +73,36 @@ header {
|
|||
|
||||
nav {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
nav .nav-links {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
font-size: 11pt;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
button.profile-button {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active {
|
||||
color: var(--crust);
|
||||
background-color: var(--accent);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
nav a {
|
||||
padding: 0.5rem 1rem;
|
||||
color: var(--subtext-0);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
color: var(--accent);
|
||||
background-color: var(--accent-transparent);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
nav > h1 {
|
||||
|
@ -82,6 +110,12 @@ nav > h1 {
|
|||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
button.sign-in-button {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
header {
|
||||
display: flex;
|
||||
|
@ -102,7 +136,6 @@ nav > h1 {
|
|||
|
||||
nav {
|
||||
text-align: left;
|
||||
margin-left: -1rem;
|
||||
font-size: 1rem;
|
||||
|
||||
padding: 1rem 0;
|
||||
|
|
|
@ -271,7 +271,7 @@ hr {
|
|||
/*box-shadow: 0 0 4px var(--text);*/
|
||||
}
|
||||
|
||||
[role="menu"] {
|
||||
[role="menu"], [role="listbox"] {
|
||||
background-color: var(--base);
|
||||
border: 1px solid var(--overlay-0);
|
||||
border-radius: 4px;
|
||||
|
@ -279,6 +279,16 @@ hr {
|
|||
min-width: 8rem;
|
||||
}
|
||||
|
||||
[role="listbox"] [role="option"] {
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
[role="listbox"] [role="option"][data-highlighted] {
|
||||
background-color: var(--surface-0);
|
||||
padding: 4px 10px;
|
||||
}
|
||||
|
||||
[role="menu"] button {
|
||||
background-color: var(--base);
|
||||
width: 100%;
|
||||
|
@ -297,17 +307,6 @@ hr {
|
|||
[role="menu"] button > i.bi.margin {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
/*
|
||||
div[role="menu"] div[role="menuitem"]:first-child button {
|
||||
border-top-left-radius: var(--border-radius);
|
||||
border-top-right-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
div[role="menu"] div[role="menuitem"]:last-child button {
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
border-bottom-right-radius: var(--border-radius);
|
||||
}
|
||||
*/
|
||||
|
||||
[role="menu"] button:hover {
|
||||
background-color: var(--surface-0);
|
||||
|
|
|
@ -1,91 +1,53 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, defineModel, defineProps, ref } from "vue";
|
||||
<script setup lang="ts" generic="T extends AcceptableValue">
|
||||
import { defineModel, defineProps, ref } from "vue";
|
||||
import {
|
||||
ComboboxContent,
|
||||
ComboboxInput,
|
||||
ComboboxItem,
|
||||
ComboboxRoot,
|
||||
ComboboxPortal,
|
||||
ComboboxTrigger,
|
||||
ComboboxAnchor,
|
||||
ComboboxViewport,
|
||||
} from "radix-vue";
|
||||
import type { AcceptableValue } from "node_modules/radix-vue/dist/shared/types";
|
||||
|
||||
const model = defineModel();
|
||||
|
||||
const props = defineProps({
|
||||
options: Array<String>,
|
||||
isDisabled: Boolean,
|
||||
});
|
||||
const selectedValue = defineModel<T>();
|
||||
|
||||
const isOpen = ref(false);
|
||||
const selectedOption = computed(() => props.options[model.value]);
|
||||
|
||||
function selectOption(index) {
|
||||
model.value = index;
|
||||
isOpen.value = false;
|
||||
}
|
||||
withDefaults(defineProps<{
|
||||
values: T[];
|
||||
//mapper? (value: T): string;
|
||||
//keyMapper? (value: T): string;
|
||||
display: string;
|
||||
keyField: string;
|
||||
}>(), {
|
||||
//mapper: (value: T) => value.toString(),
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="{ 'dropdown-container': true, 'is-open': isOpen }">
|
||||
<button @click="isOpen = !isOpen" :disabled="isDisabled">
|
||||
{{ selectedOption }}
|
||||
<i class="bi bi-caret-down-fill"></i>
|
||||
</button>
|
||||
<ul class="dropdown" v-if="isOpen" @blur="isOpen = false">
|
||||
<li v-for="(option, i) in options" :key="i" @click="selectOption(i)">
|
||||
<button :class="{ 'is-selected': i == model }">
|
||||
{{ option }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ComboboxRoot
|
||||
v-model="selectedValue"
|
||||
v-model:open="isOpen"
|
||||
defaultOpen
|
||||
>
|
||||
<ComboboxAnchor>
|
||||
<ComboboxInput />
|
||||
<ComboboxTrigger>
|
||||
hi
|
||||
</ComboboxTrigger>
|
||||
</ComboboxAnchor>
|
||||
|
||||
<ComboboxPortal>
|
||||
<ComboboxContent position="popper">
|
||||
<ComboboxViewport>
|
||||
</ComboboxViewport>
|
||||
</ComboboxContent>
|
||||
</ComboboxPortal>
|
||||
</ComboboxRoot>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dropdown-container {
|
||||
display: inline-block;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.dropdown-container button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
padding: 4px;
|
||||
transition-duration: 200ms;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-container button:hover {
|
||||
background-color: var(--crust);
|
||||
}
|
||||
|
||||
.dropdown-container.is-open ul.dropdown {
|
||||
box-shadow: 1px 1px 8px var(--shadow);
|
||||
}
|
||||
|
||||
ul.dropdown {
|
||||
display: block;
|
||||
background-color: var(--base);
|
||||
position: absolute;
|
||||
margin-top: 8px;
|
||||
padding: 0;
|
||||
z-index: 2;
|
||||
border-radius: 8px;
|
||||
overflow: none;
|
||||
}
|
||||
|
||||
ul.dropdown > li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.dropdown li > button {
|
||||
padding: 8px 16px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.dropdown li > button.is-selected {
|
||||
background-color: var(--accent-transparent);
|
||||
color: var(--accent);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -27,8 +27,8 @@ function disableIntegration() {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<h2>logs.tf Integration</h2>
|
||||
<p>Automatically track match history from logs.tf.</p>
|
||||
<h2>logs.tf Auto-Tracking</h2>
|
||||
<p>Automatically fetch and track match history from logs.tf.</p>
|
||||
<div v-if="model">
|
||||
<div class="form-group margin">
|
||||
<h3>logs.tf API key (optional)</h3>
|
||||
|
|
|
@ -5,11 +5,12 @@ import { useRosterStore } from "../stores/roster";
|
|||
|
||||
const rosterStore = useRosterStore();
|
||||
|
||||
const props = defineProps({
|
||||
roleTitle: String,
|
||||
player: Object as PropType<PlayerTeamRoleFlat>,
|
||||
isRoster: Boolean,
|
||||
isRinger: Boolean,
|
||||
const props = withDefaults(defineProps<{
|
||||
roleTitle: string,
|
||||
player: PlayerTeamRoleFlat | undefined,
|
||||
isRoster: boolean,
|
||||
}>(), {
|
||||
isRoster: false,
|
||||
});
|
||||
|
||||
const isSelected = computed(() => {
|
||||
|
@ -17,11 +18,9 @@ const isSelected = computed(() => {
|
|||
return rosterStore.selectedRole == props.roleTitle;
|
||||
}
|
||||
|
||||
if (props.isRinger) {
|
||||
return rosterStore.selectedPlayers[props.roleTitle]?.playtime == -1;
|
||||
}
|
||||
const selectedPlayers = rosterStore.selectedPlayers;
|
||||
|
||||
return Object.values(rosterStore.selectedPlayers).includes(props.player);
|
||||
return selectedPlayers[props.roleTitle]?.steamId == props.player?.steamId;
|
||||
});
|
||||
|
||||
function onClick() {
|
||||
|
@ -36,25 +35,13 @@ function onClick() {
|
|||
if (isSelected.value) {
|
||||
rosterStore.selectPlayerForRole(undefined, props.roleTitle);
|
||||
} else {
|
||||
if (props.isRinger) {
|
||||
const ringerPlayer: PlayerTeamRoleFlat = {
|
||||
steamId: "0",
|
||||
name: "Ringer",
|
||||
role: props.roleTitle ?? "",
|
||||
isMain: false,
|
||||
availability: 1,
|
||||
playtime: -1,
|
||||
};
|
||||
rosterStore.selectPlayerForRole(ringerPlayer, props.roleTitle);
|
||||
} else {
|
||||
rosterStore.selectPlayerForRole(props.player, props.roleTitle);
|
||||
}
|
||||
rosterStore.selectPlayerForRole(props.player, props.roleTitle);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const playtime = computed(() => {
|
||||
let hours = props.player?.playtime / 3600 ?? 0;
|
||||
let hours = props.player?.playtime ?? 0 / 3600;
|
||||
return hours.toFixed(1);
|
||||
});
|
||||
</script>
|
||||
|
@ -62,7 +49,7 @@ const playtime = computed(() => {
|
|||
<template>
|
||||
<button :class="{
|
||||
'player-card': true,
|
||||
'no-player': !player && !isRinger,
|
||||
'no-player': !player,
|
||||
'selected': isSelected,
|
||||
'can-be-available': player?.availability == 1
|
||||
}" @click="onClick">
|
||||
|
@ -79,21 +66,12 @@ const playtime = computed(() => {
|
|||
(alternate)
|
||||
</span>
|
||||
</span>
|
||||
<span v-if="playtime > 0">
|
||||
<span v-if="Number(playtime) > 0">
|
||||
{{ playtime }} hours
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div v-else-if="isRinger" class="role-info">
|
||||
<span>
|
||||
<h4 class="player-name">Ringer</h4>
|
||||
<div class="subtitle">
|
||||
<span>{{ rosterStore.roleNames[roleTitle] }}</span>
|
||||
<!--span>nobody likes to play {{ roleTitle }}</span-->
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div v-else class="role-info">
|
||||
<span>
|
||||
{{ rosterStore.roleNames[roleTitle] }}
|
||||
|
|
|
@ -131,9 +131,14 @@ const rightIndicator = computed(() => {
|
|||
:availability="player.availability[1]"
|
||||
/>
|
||||
</div>
|
||||
<h3>
|
||||
{{ player.username }}
|
||||
</h3>
|
||||
<a
|
||||
class="player-name"
|
||||
:href="`https://steamcommunity.com/profiles/${player.steamId}`"
|
||||
>
|
||||
<h3>
|
||||
{{ player.username }}
|
||||
</h3>
|
||||
</a>
|
||||
<svg-icon
|
||||
v-if="player.isTeamLeader"
|
||||
:class="[
|
||||
|
@ -256,6 +261,14 @@ const rightIndicator = computed(() => {
|
|||
background-color: var(--green);
|
||||
}
|
||||
|
||||
a.player-name {
|
||||
color: unset;
|
||||
}
|
||||
|
||||
a.player-name:hover {
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
.flex-middle {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
<script setup lang="ts">
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import {
|
||||
DropdownMenuRoot,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuPortal,
|
||||
} from "radix-vue";
|
||||
import { RouterLink } from "vue-router";
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
function logout() {
|
||||
authStore.logout();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuRoot>
|
||||
<DropdownMenuTrigger className="profile-button">
|
||||
{{ authStore.username }}
|
||||
<i class="bi bi-chevron-down" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent className="shadow">
|
||||
<DropdownMenuItem>
|
||||
<RouterLink class="button" :to="{ 'name': 'user-settings' }">
|
||||
<button>
|
||||
<i class="bi bi-gear margin" />
|
||||
Settings
|
||||
</button>
|
||||
</RouterLink>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<RouterLink class="button" to="/teams">
|
||||
<button>
|
||||
<i class="bi bi-people margin" />
|
||||
Teams
|
||||
</button>
|
||||
</RouterLink>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<button class="destructive" @click="logout">
|
||||
<i class="bi bi-box-arrow-right margin" />
|
||||
Log out
|
||||
</button>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuRoot>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.profile-button {
|
||||
background-color: transparent;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.profile-button:hover {
|
||||
background-color: var(--surface-0);
|
||||
}
|
||||
</style>
|
|
@ -6,6 +6,7 @@ import { createPinia } from "pinia";
|
|||
import VueSelect from "vue-select";
|
||||
import { TooltipDirective } from "vue3-tooltip";
|
||||
import "vue3-tooltip/tooltip.css";
|
||||
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
|
||||
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
|
|
|
@ -16,7 +16,7 @@ export const useRosterStore = defineStore("roster", () => {
|
|||
|
||||
// TODO: move roster state to a composable
|
||||
|
||||
const neededRoles: Reactive<Array<String>> = reactive([
|
||||
const neededRoles = ref([
|
||||
"PocketScout",
|
||||
"FlankScout",
|
||||
"PocketSoldier",
|
||||
|
@ -95,7 +95,7 @@ export const useRosterStore = defineStore("roster", () => {
|
|||
"Spy": "Spy",
|
||||
});
|
||||
|
||||
function selectPlayerForRole(player: PlayerTeamRoleFlat, role: string) {
|
||||
function selectPlayerForRole(player: PlayerTeamRoleFlat | undefined, role: string) {
|
||||
if (player && player.steamId) {
|
||||
const existingRole = Object.keys(selectedPlayers).find((selectedRole) => {
|
||||
return selectedPlayers[selectedRole]?.steamId == player.steamId &&
|
||||
|
|
|
@ -79,13 +79,10 @@ onMounted(async () => {
|
|||
<span v-if="!hasAvailablePlayers && rosterStore.selectedRole">
|
||||
No players are currently available for this role.
|
||||
</span>
|
||||
<h3 v-if="hasAvailablePlayers">Alternates</h3>
|
||||
<h3 v-if="hasAlternates">Alternates</h3>
|
||||
<PlayerCard v-for="player in rosterStore.alternateRoles"
|
||||
:player="player"
|
||||
:role-title="player.role" />
|
||||
<PlayerCard v-if="rosterStore.selectedRole"
|
||||
is-ringer
|
||||
:role-title="rosterStore.selectedRole" />
|
||||
<div class="action-buttons">
|
||||
<button class="accent" @click="closeSelection">
|
||||
<i class="bi bi-check" />
|
||||
|
|
|
@ -27,18 +27,24 @@ const hasAvailablePlayers = computed(() => {
|
|||
</div>
|
||||
<div class="column">
|
||||
<h3 v-if="hasAvailablePlayers">Available</h3>
|
||||
<PlayerCard v-for="player in rosterStore.definitelyAvailableAll"
|
||||
:player="player"
|
||||
:role-title="player.role" />
|
||||
<PlayerCard
|
||||
v-for="player in rosterStore.definitelyAvailable"
|
||||
:player="player"
|
||||
:role-title="player.role"
|
||||
:is-roster="false"
|
||||
/>
|
||||
<span v-if="!hasAvailablePlayers">
|
||||
No players are currently available for this role.
|
||||
</span>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h3 v-if="hasAvailablePlayers">Available if needed</h3>
|
||||
<PlayerCard v-for="player in rosterStore.canBeAvailableAll"
|
||||
:player="player"
|
||||
:role-title="player.role" />
|
||||
<PlayerCard
|
||||
v-for="player in rosterStore.canBeAvailable"
|
||||
:player="player"
|
||||
:role-title="player.role"
|
||||
:is-roster="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import DiscordIntegrationForm from "@/components/DiscordIntegrationForm.vue";
|
||||
import IntegrationDetails from "@/components/IntegrationDetails.vue";
|
||||
import LoaderContainer from "@/components/LoaderContainer.vue";
|
||||
import LogsTfIntegrationForm from "@/components/LogsTfIntegrationForm.vue";
|
||||
import { useTeamDetails } from "@/composables/team-details";
|
||||
import { useTeamsStore } from "@/stores/teams";
|
||||
|
@ -11,20 +12,33 @@ const teamsStore = useTeamsStore();
|
|||
const integrationsStore = useIntegrationsStore();
|
||||
const { teamId } = useTeamDetails();
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
//function createIntegration() {
|
||||
// integrationsStore.createIntegration(teamId.value, "discord");
|
||||
//}
|
||||
|
||||
onMounted(() => {
|
||||
isLoading.value = true;
|
||||
teamsStore.fetchTeam(teamId.value)
|
||||
.then(() => integrationsStore.getIntegrations(teamId.value));
|
||||
.then(() => {
|
||||
integrationsStore.getIntegrations(teamId.value)
|
||||
.then(() => {
|
||||
isLoading.value = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="team-integrations">
|
||||
<DiscordIntegrationForm v-model="integrationsStore.discordIntegration" />
|
||||
<LogsTfIntegrationForm v-model="integrationsStore.logsTfIntegration" />
|
||||
<div v-if="isLoading">
|
||||
<LoaderContainer />
|
||||
</div>
|
||||
<template v-else>
|
||||
<DiscordIntegrationForm v-model="integrationsStore.discordIntegration" />
|
||||
<LogsTfIntegrationForm v-model="integrationsStore.logsTfIntegration" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
Loading…
Reference in New Issue