Compare commits
	
		
			No commits in common. "3c83eca3802b3f53accb7b74a273d31b33df56c1" and "40641f80a389c1a8b600a5cbf1eda1570b85c905" have entirely different histories. 
		
	
	
		
			3c83eca380
			...
			40641f80a3
		
	
		
	| 
						 | 
					@ -11,7 +11,6 @@
 | 
				
			||||||
        "@jamescoyle/vue-icon": "^0.1.2",
 | 
					        "@jamescoyle/vue-icon": "^0.1.2",
 | 
				
			||||||
        "@mdi/js": "^7.4.47",
 | 
					        "@mdi/js": "^7.4.47",
 | 
				
			||||||
        "@programic/vue3-tooltip": "^1.0.0",
 | 
					        "@programic/vue3-tooltip": "^1.0.0",
 | 
				
			||||||
        "@vvo/tzdb": "^6.152.0",
 | 
					 | 
				
			||||||
        "axios": "^1.7.7",
 | 
					        "axios": "^1.7.7",
 | 
				
			||||||
        "bootstrap-icons": "^1.11.3",
 | 
					        "bootstrap-icons": "^1.11.3",
 | 
				
			||||||
        "css.gg": "^2.1.4",
 | 
					        "css.gg": "^2.1.4",
 | 
				
			||||||
| 
						 | 
					@ -2122,12 +2121,6 @@
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@vvo/tzdb": {
 | 
					 | 
				
			||||||
      "version": "6.152.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@vvo/tzdb/-/tzdb-6.152.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-PSHIgDk6LjYTyAK7fPLZIliB1vSQg2OXxfkAkRJzUkwuR/Xp5FzmQNx9SmHVZhw/W/Y1x6TE6yO89PFPossswQ==",
 | 
					 | 
				
			||||||
      "license": "MIT"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/abbrev": {
 | 
					    "node_modules/abbrev": {
 | 
				
			||||||
      "version": "2.0.0",
 | 
					      "version": "2.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,6 @@
 | 
				
			||||||
    "@jamescoyle/vue-icon": "^0.1.2",
 | 
					    "@jamescoyle/vue-icon": "^0.1.2",
 | 
				
			||||||
    "@mdi/js": "^7.4.47",
 | 
					    "@mdi/js": "^7.4.47",
 | 
				
			||||||
    "@programic/vue3-tooltip": "^1.0.0",
 | 
					    "@programic/vue3-tooltip": "^1.0.0",
 | 
				
			||||||
    "@vvo/tzdb": "^6.152.0",
 | 
					 | 
				
			||||||
    "axios": "^1.7.7",
 | 
					    "axios": "^1.7.7",
 | 
				
			||||||
    "bootstrap-icons": "^1.11.3",
 | 
					    "bootstrap-icons": "^1.11.3",
 | 
				
			||||||
    "css.gg": "^2.1.4",
 | 
					    "css.gg": "^2.1.4",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,6 @@
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { RouterLink, RouterView } from "vue-router";
 | 
					import { RouterLink, RouterView } from "vue-router";
 | 
				
			||||||
import { useAuthStore } from "./stores/auth";
 | 
					import { useAuthStore } from "./stores/auth";
 | 
				
			||||||
import ProfileDropdown from "./components/ProfileDropdown.vue";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const baseUrl = window.location.origin;
 | 
					const baseUrl = window.location.origin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,41 +11,26 @@ const authStore = useAuthStore();
 | 
				
			||||||
  <header>
 | 
					  <header>
 | 
				
			||||||
    <div class="wrapper">
 | 
					    <div class="wrapper">
 | 
				
			||||||
      <nav>
 | 
					      <nav>
 | 
				
			||||||
        <h1>
 | 
					        <h1>availabili.tf</h1>
 | 
				
			||||||
          <RouterLink class="header-link" to="/">availabili.tf</RouterLink>
 | 
					        <RouterLink to="/">Home</RouterLink>
 | 
				
			||||||
        </h1>
 | 
					        <RouterLink to="/schedule">Schedule</RouterLink>
 | 
				
			||||||
        <div class="nav-links">
 | 
					        <div v-if="authStore.isLoggedIn">
 | 
				
			||||||
          <a
 | 
					          Welcome {{ authStore.username }}
 | 
				
			||||||
            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>
 | 
					        </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>
 | 
					      </nav>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </header>
 | 
					  </header>
 | 
				
			||||||
| 
						 | 
					@ -62,10 +46,6 @@ header {
 | 
				
			||||||
  max-height: 100vh;
 | 
					  max-height: 100vh;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
a.header-link {
 | 
					 | 
				
			||||||
  font-weight: 800;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.logo {
 | 
					.logo {
 | 
				
			||||||
  display: block;
 | 
					  display: block;
 | 
				
			||||||
  margin: 0 auto 2rem;
 | 
					  margin: 0 auto 2rem;
 | 
				
			||||||
| 
						 | 
					@ -73,36 +53,28 @@ a.header-link {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
nav {
 | 
					nav {
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  gap: 8px;
 | 
				
			||||||
  width: 100%;
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  font-size: 12px;
 | 
				
			||||||
  text-align: center;
 | 
					  text-align: center;
 | 
				
			||||||
  margin: 0;
 | 
					  margin: 0;
 | 
				
			||||||
  align-items: center;
 | 
					  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 {
 | 
					nav a.router-link-exact-active {
 | 
				
			||||||
  color: var(--text);
 | 
					  color: var(--crust);
 | 
				
			||||||
 | 
					  background-color: var(--accent);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
nav a {
 | 
					nav a {
 | 
				
			||||||
 | 
					  padding: 0.5rem 1rem;
 | 
				
			||||||
  color: var(--subtext-0);
 | 
					  color: var(--subtext-0);
 | 
				
			||||||
  border-radius: 8px;
 | 
					  border-radius: 8px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
nav a:hover {
 | 
					nav a:hover {
 | 
				
			||||||
  background-color: transparent;
 | 
					  color: var(--accent);
 | 
				
			||||||
 | 
					  background-color: var(--accent-transparent);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
nav > h1 {
 | 
					nav > h1 {
 | 
				
			||||||
| 
						 | 
					@ -110,12 +82,6 @@ nav > h1 {
 | 
				
			||||||
  margin-right: 1rem;
 | 
					  margin-right: 1rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
button.sign-in-button {
 | 
					 | 
				
			||||||
  background-color: transparent;
 | 
					 | 
				
			||||||
  border: none;
 | 
					 | 
				
			||||||
  padding: 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@media (min-width: 1024px) {
 | 
					@media (min-width: 1024px) {
 | 
				
			||||||
  header {
 | 
					  header {
 | 
				
			||||||
    display: flex;
 | 
					    display: flex;
 | 
				
			||||||
| 
						 | 
					@ -136,6 +102,7 @@ button.sign-in-button {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  nav {
 | 
					  nav {
 | 
				
			||||||
    text-align: left;
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					    margin-left: -1rem;
 | 
				
			||||||
    font-size: 1rem;
 | 
					    font-size: 1rem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    padding: 1rem 0;
 | 
					    padding: 1rem 0;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -271,7 +271,7 @@ hr {
 | 
				
			||||||
  /*box-shadow: 0 0 4px var(--text);*/
 | 
					  /*box-shadow: 0 0 4px var(--text);*/
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[role="menu"], [role="listbox"] {
 | 
					[role="menu"] {
 | 
				
			||||||
  background-color: var(--base);
 | 
					  background-color: var(--base);
 | 
				
			||||||
  border: 1px solid var(--overlay-0);
 | 
					  border: 1px solid var(--overlay-0);
 | 
				
			||||||
  border-radius: 4px;
 | 
					  border-radius: 4px;
 | 
				
			||||||
| 
						 | 
					@ -279,16 +279,6 @@ hr {
 | 
				
			||||||
  min-width: 8rem;
 | 
					  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 {
 | 
					[role="menu"] button {
 | 
				
			||||||
  background-color: var(--base);
 | 
					  background-color: var(--base);
 | 
				
			||||||
  width: 100%;
 | 
					  width: 100%;
 | 
				
			||||||
| 
						 | 
					@ -307,6 +297,17 @@ hr {
 | 
				
			||||||
[role="menu"] button > i.bi.margin {
 | 
					[role="menu"] button > i.bi.margin {
 | 
				
			||||||
  margin-right: 0.5em;
 | 
					  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 {
 | 
					[role="menu"] button:hover {
 | 
				
			||||||
  background-color: var(--surface-0);
 | 
					  background-color: var(--surface-0);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,53 +1,91 @@
 | 
				
			||||||
<script setup lang="ts" generic="T extends AcceptableValue">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { defineModel, defineProps, ref } from "vue";
 | 
					import { computed, 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 selectedValue = defineModel<T>();
 | 
					const model = defineModel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					  options: Array<String>,
 | 
				
			||||||
 | 
					  isDisabled: Boolean,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isOpen = ref(false);
 | 
					const isOpen = ref(false);
 | 
				
			||||||
 | 
					const selectedOption = computed(() => props.options[model.value]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
withDefaults(defineProps<{
 | 
					function selectOption(index) {
 | 
				
			||||||
  values: T[];
 | 
					  model.value = index;
 | 
				
			||||||
  //mapper? (value: T): string;
 | 
					  isOpen.value = false;
 | 
				
			||||||
  //keyMapper? (value: T): string;
 | 
					}
 | 
				
			||||||
  display: string;
 | 
					 | 
				
			||||||
  keyField: string;
 | 
					 | 
				
			||||||
}>(), {
 | 
					 | 
				
			||||||
  //mapper: (value: T) => value.toString(),
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <ComboboxRoot
 | 
					  <div :class="{ 'dropdown-container': true, 'is-open': isOpen }">
 | 
				
			||||||
    v-model="selectedValue"
 | 
					    <button @click="isOpen = !isOpen" :disabled="isDisabled">
 | 
				
			||||||
    v-model:open="isOpen"
 | 
					      {{ selectedOption }}
 | 
				
			||||||
    defaultOpen
 | 
					      <i class="bi bi-caret-down-fill"></i>
 | 
				
			||||||
  >
 | 
					    </button>
 | 
				
			||||||
    <ComboboxAnchor>
 | 
					    <ul class="dropdown" v-if="isOpen" @blur="isOpen = false">
 | 
				
			||||||
      <ComboboxInput />
 | 
					      <li v-for="(option, i) in options" :key="i" @click="selectOption(i)">
 | 
				
			||||||
      <ComboboxTrigger>
 | 
					        <button :class="{ 'is-selected': i == model }">
 | 
				
			||||||
        hi
 | 
					          {{ option }}
 | 
				
			||||||
      </ComboboxTrigger>
 | 
					        </button>
 | 
				
			||||||
    </ComboboxAnchor>
 | 
					      </li>
 | 
				
			||||||
 | 
					    </ul>
 | 
				
			||||||
    <ComboboxPortal>
 | 
					  </div>
 | 
				
			||||||
      <ComboboxContent position="popper">
 | 
					 | 
				
			||||||
        <ComboboxViewport>
 | 
					 | 
				
			||||||
        </ComboboxViewport>
 | 
					 | 
				
			||||||
      </ComboboxContent>
 | 
					 | 
				
			||||||
    </ComboboxPortal>
 | 
					 | 
				
			||||||
  </ComboboxRoot>
 | 
					 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped>
 | 
					<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>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,8 +27,8 @@ function disableIntegration() {
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <h2>logs.tf Auto-Tracking</h2>
 | 
					  <h2>logs.tf Integration</h2>
 | 
				
			||||||
  <p>Automatically fetch and track match history from logs.tf.</p>
 | 
					  <p>Automatically track match history from logs.tf.</p>
 | 
				
			||||||
  <div v-if="model">
 | 
					  <div v-if="model">
 | 
				
			||||||
    <div class="form-group margin">
 | 
					    <div class="form-group margin">
 | 
				
			||||||
      <h3>logs.tf API key (optional)</h3>
 | 
					      <h3>logs.tf API key (optional)</h3>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,12 +5,11 @@ import { useRosterStore } from "../stores/roster";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const rosterStore = useRosterStore();
 | 
					const rosterStore = useRosterStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = withDefaults(defineProps<{
 | 
					const props = defineProps({
 | 
				
			||||||
  roleTitle: string,
 | 
					  roleTitle: String,
 | 
				
			||||||
  player: PlayerTeamRoleFlat | undefined,
 | 
					  player: Object as PropType<PlayerTeamRoleFlat>,
 | 
				
			||||||
  isRoster: boolean,
 | 
					  isRoster: Boolean,
 | 
				
			||||||
}>(), {
 | 
					  isRinger: Boolean,
 | 
				
			||||||
  isRoster: false,
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isSelected = computed(() => {
 | 
					const isSelected = computed(() => {
 | 
				
			||||||
| 
						 | 
					@ -18,9 +17,11 @@ const isSelected = computed(() => {
 | 
				
			||||||
    return rosterStore.selectedRole == props.roleTitle;
 | 
					    return rosterStore.selectedRole == props.roleTitle;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const selectedPlayers = rosterStore.selectedPlayers;
 | 
					  if (props.isRinger) {
 | 
				
			||||||
 | 
					    return rosterStore.selectedPlayers[props.roleTitle]?.playtime == -1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return selectedPlayers[props.roleTitle]?.steamId == props.player?.steamId;
 | 
					  return Object.values(rosterStore.selectedPlayers).includes(props.player);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function onClick() {
 | 
					function onClick() {
 | 
				
			||||||
| 
						 | 
					@ -35,13 +36,25 @@ function onClick() {
 | 
				
			||||||
    if (isSelected.value) {
 | 
					    if (isSelected.value) {
 | 
				
			||||||
      rosterStore.selectPlayerForRole(undefined, props.roleTitle);
 | 
					      rosterStore.selectPlayerForRole(undefined, props.roleTitle);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      rosterStore.selectPlayerForRole(props.player, props.roleTitle);
 | 
					      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);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const playtime = computed(() => {
 | 
					const playtime = computed(() => {
 | 
				
			||||||
  let hours = props.player?.playtime ?? 0 / 3600;
 | 
					  let hours = props.player?.playtime / 3600 ?? 0;
 | 
				
			||||||
  return hours.toFixed(1);
 | 
					  return hours.toFixed(1);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -49,7 +62,7 @@ const playtime = computed(() => {
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <button :class="{
 | 
					  <button :class="{
 | 
				
			||||||
    'player-card': true,
 | 
					    'player-card': true,
 | 
				
			||||||
    'no-player': !player,
 | 
					    'no-player': !player && !isRinger,
 | 
				
			||||||
    'selected': isSelected,
 | 
					    'selected': isSelected,
 | 
				
			||||||
    'can-be-available': player?.availability == 1
 | 
					    'can-be-available': player?.availability == 1
 | 
				
			||||||
  }" @click="onClick">
 | 
					  }" @click="onClick">
 | 
				
			||||||
| 
						 | 
					@ -66,12 +79,21 @@ const playtime = computed(() => {
 | 
				
			||||||
              (alternate)
 | 
					              (alternate)
 | 
				
			||||||
            </span>
 | 
					            </span>
 | 
				
			||||||
          </span>
 | 
					          </span>
 | 
				
			||||||
          <span v-if="Number(playtime) > 0">
 | 
					          <span v-if="playtime > 0">
 | 
				
			||||||
            {{ playtime }} hours
 | 
					            {{ playtime }} hours
 | 
				
			||||||
          </span>
 | 
					          </span>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </span>
 | 
					      </span>
 | 
				
			||||||
    </div>
 | 
					    </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">
 | 
					    <div v-else class="role-info">
 | 
				
			||||||
      <span>
 | 
					      <span>
 | 
				
			||||||
        {{ rosterStore.roleNames[roleTitle] }}
 | 
					        {{ rosterStore.roleNames[roleTitle] }}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -131,14 +131,9 @@ const rightIndicator = computed(() => {
 | 
				
			||||||
            :availability="player.availability[1]"
 | 
					            :availability="player.availability[1]"
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <a
 | 
					        <h3>
 | 
				
			||||||
          class="player-name"
 | 
					          {{ player.username }}
 | 
				
			||||||
          :href="`https://steamcommunity.com/profiles/${player.steamId}`"
 | 
					        </h3>
 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <h3>
 | 
					 | 
				
			||||||
            {{ player.username }}
 | 
					 | 
				
			||||||
          </h3>
 | 
					 | 
				
			||||||
        </a>
 | 
					 | 
				
			||||||
        <svg-icon
 | 
					        <svg-icon
 | 
				
			||||||
          v-if="player.isTeamLeader"
 | 
					          v-if="player.isTeamLeader"
 | 
				
			||||||
          :class="[
 | 
					          :class="[
 | 
				
			||||||
| 
						 | 
					@ -261,14 +256,6 @@ const rightIndicator = computed(() => {
 | 
				
			||||||
  background-color: var(--green);
 | 
					  background-color: var(--green);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
a.player-name {
 | 
					 | 
				
			||||||
  color: unset;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
a.player-name:hover {
 | 
					 | 
				
			||||||
  background-color: unset;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.flex-middle {
 | 
					.flex-middle {
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  gap: 8px;
 | 
					  gap: 8px;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,63 +0,0 @@
 | 
				
			||||||
<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,7 +6,6 @@ import { createPinia } from "pinia";
 | 
				
			||||||
import VueSelect from "vue-select";
 | 
					import VueSelect from "vue-select";
 | 
				
			||||||
import { TooltipDirective } from "vue3-tooltip";
 | 
					import { TooltipDirective } from "vue3-tooltip";
 | 
				
			||||||
import "vue3-tooltip/tooltip.css";
 | 
					import "vue3-tooltip/tooltip.css";
 | 
				
			||||||
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import App from "./App.vue";
 | 
					import App from "./App.vue";
 | 
				
			||||||
import router from "./router";
 | 
					import router from "./router";
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,6 @@ import TeamSettingsView from "@/views/TeamSettingsView.vue";
 | 
				
			||||||
import TeamSettingsGeneralView from "@/views/TeamSettings/GeneralView.vue";
 | 
					import TeamSettingsGeneralView from "@/views/TeamSettings/GeneralView.vue";
 | 
				
			||||||
import TeamSettingsIntegrationsView from "@/views/TeamSettings/IntegrationsView.vue";
 | 
					import TeamSettingsIntegrationsView from "@/views/TeamSettings/IntegrationsView.vue";
 | 
				
			||||||
import TeamSettingsInvitesView from "@/views/TeamSettings/InvitesView.vue";
 | 
					import TeamSettingsInvitesView from "@/views/TeamSettings/InvitesView.vue";
 | 
				
			||||||
import UserSettingsView from "@/views/UserSettingsView.vue";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = createRouter({
 | 
					const router = createRouter({
 | 
				
			||||||
  history: createWebHistory(import.meta.env.BASE_URL),
 | 
					  history: createWebHistory(import.meta.env.BASE_URL),
 | 
				
			||||||
| 
						 | 
					@ -73,11 +72,6 @@ const router = createRouter({
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      path: "/settings",
 | 
					 | 
				
			||||||
      name: "user-settings",
 | 
					 | 
				
			||||||
      component: UserSettingsView,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import { defineStore } from "pinia";
 | 
					import { defineStore } from "pinia";
 | 
				
			||||||
import { ref } from "vue";
 | 
					import { ref } from "vue";
 | 
				
			||||||
import { useClientStore } from "./client";
 | 
					import { useClientStore } from "./client";
 | 
				
			||||||
import { useRouter, type LocationQuery } from "vue-router";
 | 
					import type { LocationQuery } from "vue-router";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useAuthStore = defineStore("auth", () => {
 | 
					export const useAuthStore = defineStore("auth", () => {
 | 
				
			||||||
  const clientStore = useClientStore();
 | 
					  const clientStore = useClientStore();
 | 
				
			||||||
| 
						 | 
					@ -13,8 +13,6 @@ export const useAuthStore = defineStore("auth", () => {
 | 
				
			||||||
  const isRegistering = ref(false);
 | 
					  const isRegistering = ref(false);
 | 
				
			||||||
  const hasCheckedAuth = ref(false);
 | 
					  const hasCheckedAuth = ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const router = useRouter();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async function getUser() {
 | 
					  async function getUser() {
 | 
				
			||||||
    hasCheckedAuth.value = true;
 | 
					    hasCheckedAuth.value = true;
 | 
				
			||||||
    return clientStore.call(
 | 
					    return clientStore.call(
 | 
				
			||||||
| 
						 | 
					@ -50,16 +48,8 @@ export const useAuthStore = defineStore("auth", () => {
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async function logout() {
 | 
					  async function setUsername(username: string) {
 | 
				
			||||||
    return client.default.deleteApiLogin()
 | 
					    return client.default.setUsername({ username });
 | 
				
			||||||
      .then(() => router.push("/"));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async function setUsername(name: string) {
 | 
					 | 
				
			||||||
    return client.default.setUsername({ username: name })
 | 
					 | 
				
			||||||
      .then((response) => {
 | 
					 | 
				
			||||||
        username.value = response.username;
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
| 
						 | 
					@ -70,7 +60,6 @@ export const useAuthStore = defineStore("auth", () => {
 | 
				
			||||||
    isRegistering,
 | 
					    isRegistering,
 | 
				
			||||||
    getUser,
 | 
					    getUser,
 | 
				
			||||||
    login,
 | 
					    login,
 | 
				
			||||||
    logout,
 | 
					 | 
				
			||||||
    setUsername,
 | 
					    setUsername,
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,7 +16,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // TODO: move roster state to a composable
 | 
					  // TODO: move roster state to a composable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const neededRoles  = ref([
 | 
					  const neededRoles: Reactive<Array<String>> = reactive([
 | 
				
			||||||
    "PocketScout",
 | 
					    "PocketScout",
 | 
				
			||||||
    "FlankScout",
 | 
					    "FlankScout",
 | 
				
			||||||
    "PocketSoldier",
 | 
					    "PocketSoldier",
 | 
				
			||||||
| 
						 | 
					@ -95,7 +95,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
				
			||||||
    "Spy": "Spy",
 | 
					    "Spy": "Spy",
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function selectPlayerForRole(player: PlayerTeamRoleFlat | undefined, role: string) {
 | 
					  function selectPlayerForRole(player: PlayerTeamRoleFlat, role: string) {
 | 
				
			||||||
    if (player && player.steamId) {
 | 
					    if (player && player.steamId) {
 | 
				
			||||||
      const existingRole = Object.keys(selectedPlayers).find((selectedRole) => {
 | 
					      const existingRole = Object.keys(selectedPlayers).find((selectedRole) => {
 | 
				
			||||||
        return selectedPlayers[selectedRole]?.steamId == player.steamId &&
 | 
					        return selectedPlayers[selectedRole]?.steamId == player.steamId &&
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -79,10 +79,13 @@ onMounted(async () => {
 | 
				
			||||||
        <span v-if="!hasAvailablePlayers && rosterStore.selectedRole">
 | 
					        <span v-if="!hasAvailablePlayers && rosterStore.selectedRole">
 | 
				
			||||||
          No players are currently available for this role.
 | 
					          No players are currently available for this role.
 | 
				
			||||||
        </span>
 | 
					        </span>
 | 
				
			||||||
        <h3 v-if="hasAlternates">Alternates</h3>
 | 
					        <h3 v-if="hasAvailablePlayers">Alternates</h3>
 | 
				
			||||||
        <PlayerCard v-for="player in rosterStore.alternateRoles"
 | 
					        <PlayerCard v-for="player in rosterStore.alternateRoles"
 | 
				
			||||||
                    :player="player"
 | 
					                    :player="player"
 | 
				
			||||||
                    :role-title="player.role" />
 | 
					                    :role-title="player.role" />
 | 
				
			||||||
 | 
					        <PlayerCard v-if="rosterStore.selectedRole"
 | 
				
			||||||
 | 
					                    is-ringer
 | 
				
			||||||
 | 
					                    :role-title="rosterStore.selectedRole" />
 | 
				
			||||||
        <div class="action-buttons">
 | 
					        <div class="action-buttons">
 | 
				
			||||||
          <button class="accent" @click="closeSelection">
 | 
					          <button class="accent" @click="closeSelection">
 | 
				
			||||||
            <i class="bi bi-check" />
 | 
					            <i class="bi bi-check" />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,24 +27,18 @@ const hasAvailablePlayers = computed(() => {
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="column">
 | 
					      <div class="column">
 | 
				
			||||||
        <h3 v-if="hasAvailablePlayers">Available</h3>
 | 
					        <h3 v-if="hasAvailablePlayers">Available</h3>
 | 
				
			||||||
        <PlayerCard
 | 
					        <PlayerCard v-for="player in rosterStore.definitelyAvailableAll"
 | 
				
			||||||
          v-for="player in rosterStore.definitelyAvailable"
 | 
					                    :player="player"
 | 
				
			||||||
          :player="player"
 | 
					                    :role-title="player.role" />
 | 
				
			||||||
          :role-title="player.role"
 | 
					 | 
				
			||||||
          :is-roster="false"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
        <span v-if="!hasAvailablePlayers">
 | 
					        <span v-if="!hasAvailablePlayers">
 | 
				
			||||||
          No players are currently available for this role.
 | 
					          No players are currently available for this role.
 | 
				
			||||||
        </span>
 | 
					        </span>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="column">
 | 
					      <div class="column">
 | 
				
			||||||
        <h3 v-if="hasAvailablePlayers">Available if needed</h3>
 | 
					        <h3 v-if="hasAvailablePlayers">Available if needed</h3>
 | 
				
			||||||
        <PlayerCard
 | 
					        <PlayerCard v-for="player in rosterStore.canBeAvailableAll"
 | 
				
			||||||
          v-for="player in rosterStore.canBeAvailable"
 | 
					                    :player="player"
 | 
				
			||||||
          :player="player"
 | 
					                    :role-title="player.role" />
 | 
				
			||||||
          :role-title="player.role"
 | 
					 | 
				
			||||||
          :is-roster="false"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </main>
 | 
					  </main>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,19 +1,8 @@
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { ref, watch } from "vue";
 | 
					import { ref, watch } from "vue";
 | 
				
			||||||
import { useTeamsStore } from "../stores/teams";
 | 
					import { useTeamsStore } from "../stores/teams";
 | 
				
			||||||
//import timezones from "../assets/timezones.json";
 | 
					import timezones from "../assets/timezones.json";
 | 
				
			||||||
import { useRouter } from "vue-router";
 | 
					import { useRouter } from "vue-router";
 | 
				
			||||||
import ComboBox from "../components/ComboBox.vue";
 | 
					 | 
				
			||||||
import { getTimeZones, type TimeZone } from "@vvo/tzdb";
 | 
					 | 
				
			||||||
import moment from "moment";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const timezones = getTimeZones({
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
console.log(timezones.length);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
console.log(moment.tz.names());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const teams = useTeamsStore();
 | 
					const teams = useTeamsStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,9 +10,10 @@ const router = useRouter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const teamName = ref("");
 | 
					const teamName = ref("");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const timezone = ref<TimeZone>(timezones.find((tz) => tz.name === "America/New_York")!);
 | 
					const timezone = ref(
 | 
				
			||||||
 | 
					  Intl.DateTimeFormat().resolvedOptions().timeZone ??
 | 
				
			||||||
const timezoneStr = ref("");
 | 
					    "Etc/UTC"
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const minuteOffset = ref(0);
 | 
					const minuteOffset = ref(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,8 +21,10 @@ watch(minuteOffset, (newValue) => {
 | 
				
			||||||
  minuteOffset.value = Math.min(Math.max(0, newValue), 59);
 | 
					  minuteOffset.value = Math.min(Math.max(0, newValue), 59);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const webhook = ref("");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function createTeam() {
 | 
					function createTeam() {
 | 
				
			||||||
  teams.createTeam(teamName.value, timezone.value.name, minuteOffset.value)
 | 
					  teams.createTeam(teamName.value, timezone.value, minuteOffset.value)
 | 
				
			||||||
    .then(() => {
 | 
					    .then(() => {
 | 
				
			||||||
      router.push("/");
 | 
					      router.push("/");
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
| 
						 | 
					@ -64,7 +56,7 @@ function createTeam() {
 | 
				
			||||||
                (view all timezones)
 | 
					                (view all timezones)
 | 
				
			||||||
              </a>
 | 
					              </a>
 | 
				
			||||||
            </h3>
 | 
					            </h3>
 | 
				
			||||||
            <v-select :options="timezones" label="name" v-model="timezone" />
 | 
					            <v-select :options="timezones" v-model="timezone" />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div class="form-group" id="minute-offset-group">
 | 
					          <div class="form-group" id="minute-offset-group">
 | 
				
			||||||
            <h3>Minute Offset</h3>
 | 
					            <h3>Minute Offset</h3>
 | 
				
			||||||
| 
						 | 
					@ -72,7 +64,7 @@ function createTeam() {
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <em class="aside">
 | 
					        <em class="aside">
 | 
				
			||||||
          Matches will be scheduled based on {{ timezone.alternativeName }} at
 | 
					          Matches will be scheduled based on {{ timezone }} at
 | 
				
			||||||
          {{ minuteOffset }}
 | 
					          {{ minuteOffset }}
 | 
				
			||||||
          <span v-if="minuteOffset == 1">
 | 
					          <span v-if="minuteOffset == 1">
 | 
				
			||||||
            minute
 | 
					            minute
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,6 @@
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import DiscordIntegrationForm from "@/components/DiscordIntegrationForm.vue";
 | 
					import DiscordIntegrationForm from "@/components/DiscordIntegrationForm.vue";
 | 
				
			||||||
import IntegrationDetails from "@/components/IntegrationDetails.vue";
 | 
					import IntegrationDetails from "@/components/IntegrationDetails.vue";
 | 
				
			||||||
import LoaderContainer from "@/components/LoaderContainer.vue";
 | 
					 | 
				
			||||||
import LogsTfIntegrationForm from "@/components/LogsTfIntegrationForm.vue";
 | 
					import LogsTfIntegrationForm from "@/components/LogsTfIntegrationForm.vue";
 | 
				
			||||||
import { useTeamDetails } from "@/composables/team-details";
 | 
					import { useTeamDetails } from "@/composables/team-details";
 | 
				
			||||||
import { useTeamsStore } from "@/stores/teams";
 | 
					import { useTeamsStore } from "@/stores/teams";
 | 
				
			||||||
| 
						 | 
					@ -12,33 +11,20 @@ const teamsStore = useTeamsStore();
 | 
				
			||||||
const integrationsStore = useIntegrationsStore();
 | 
					const integrationsStore = useIntegrationsStore();
 | 
				
			||||||
const { teamId } = useTeamDetails();
 | 
					const { teamId } = useTeamDetails();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isLoading = ref(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//function createIntegration() {
 | 
					//function createIntegration() {
 | 
				
			||||||
//  integrationsStore.createIntegration(teamId.value, "discord");
 | 
					//  integrationsStore.createIntegration(teamId.value, "discord");
 | 
				
			||||||
//}
 | 
					//}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(() => {
 | 
				
			||||||
  isLoading.value = true;
 | 
					 | 
				
			||||||
  teamsStore.fetchTeam(teamId.value)
 | 
					  teamsStore.fetchTeam(teamId.value)
 | 
				
			||||||
    .then(() => {
 | 
					    .then(() => integrationsStore.getIntegrations(teamId.value));
 | 
				
			||||||
      integrationsStore.getIntegrations(teamId.value)
 | 
					 | 
				
			||||||
        .then(() => {
 | 
					 | 
				
			||||||
          isLoading.value = false;
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="team-integrations">
 | 
					  <div class="team-integrations">
 | 
				
			||||||
    <div v-if="isLoading">
 | 
					    <DiscordIntegrationForm v-model="integrationsStore.discordIntegration" />
 | 
				
			||||||
      <LoaderContainer />
 | 
					    <LogsTfIntegrationForm v-model="integrationsStore.logsTfIntegration" />
 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
    <template v-else>
 | 
					 | 
				
			||||||
      <DiscordIntegrationForm v-model="integrationsStore.discordIntegration" />
 | 
					 | 
				
			||||||
      <LogsTfIntegrationForm v-model="integrationsStore.logsTfIntegration" />
 | 
					 | 
				
			||||||
    </template>
 | 
					 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,43 +0,0 @@
 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { useAuthStore } from "@/stores/auth";
 | 
					 | 
				
			||||||
import { onMounted, ref } from "vue";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const displayName = ref("");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const authStore = useAuthStore();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function save() {
 | 
					 | 
				
			||||||
  authStore.setUsername(displayName.value);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
onMounted(() => {
 | 
					 | 
				
			||||||
  displayName.value = authStore.username;
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
  <main>
 | 
					 | 
				
			||||||
    <div class="user-settings-container">
 | 
					 | 
				
			||||||
      <h1>User Settings</h1>
 | 
					 | 
				
			||||||
      <div class="form-group margin">
 | 
					 | 
				
			||||||
        <h3>
 | 
					 | 
				
			||||||
          Display Name
 | 
					 | 
				
			||||||
        </h3>
 | 
					 | 
				
			||||||
        <input v-model="displayName" />
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <div class="form-group margin">
 | 
					 | 
				
			||||||
        <div class="action-buttons">
 | 
					 | 
				
			||||||
          <button class="accent" @click="save">Save</button>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </main>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped>
 | 
					 | 
				
			||||||
.user-settings-container {
 | 
					 | 
				
			||||||
  align-items: center;
 | 
					 | 
				
			||||||
  max-width: 500px;
 | 
					 | 
				
			||||||
  margin: auto;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -292,9 +292,6 @@ def update_event(player: Player, event_id: int, json: UpdateEventJson, **_):
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            player_event.role = None
 | 
					            player_event.role = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    event.name = json.name
 | 
					 | 
				
			||||||
    event.description = json.description
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    db.session.commit()
 | 
					    db.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    event.update_discord_message()
 | 
					    event.update_discord_message()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,7 @@ class Event(app_db.BaseModel):
 | 
				
			||||||
    name: Mapped[str] = mapped_column(String(255), nullable=False)
 | 
					    name: Mapped[str] = mapped_column(String(255), nullable=False)
 | 
				
			||||||
    start_time: Mapped[datetime] = mapped_column(UtcDateTime, nullable=False)
 | 
					    start_time: Mapped[datetime] = mapped_column(UtcDateTime, nullable=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
 | 
					    description: Mapped[str] = mapped_column(Text, nullable=True)
 | 
				
			||||||
    created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now())
 | 
					    created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now())
 | 
				
			||||||
    discord_message_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
 | 
					    discord_message_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue